<commit_before>//
//  CompanyIdentifier.swift
//  
//
//  Created by <NAME> on 6/12/22.
//

import Foundation

extension GenerateTool {
    
    static func parseCSV(input: URL) throws -> [UInt16: String] {
        let csvParser = CompanyIdentifierCSVParser()
        try csvParser.parse(url: input)
        return csvParser.data
    }
    
    static func companyIdentifiers(from data: [UInt16: String]) -> [(id: UInt16, name: String, member: String)] {
        let blacklist: [UInt16] = [
            .max // remove internal use identifier
        ]
        let companies = data
            .sorted(by: { $0.key < $1.key })
            .filter { blacklist.contains($0.key) == false }
        var memberNames = [UInt16: String]()
        memberNames.reserveCapacity(companies.count)
        for (id, name) in companies {
            let sanitizedName = name.sanitizeName()
            let llamaCaseName = sanitizedName.llamaCase()
            var memberName = llamaCaseName
            // prevent duplicate entries
            var duplicateNumber = 1
            while memberNames.values.contains(memberName) {
                duplicateNumber += 1
                memberName = llamaCaseName + "\(duplicateNumber)"
            }
            memberNames[id] = memberName
        }
        return companies.map { ($0, $1, memberNames[$0]!) }
    }
    
    static func generateCompanyIdentifiers(input: URL, output: [URL]) throws {
        let data = try parseCSV(input: input)
        try generateCompanyIdentifierExtensions(data, output: output[0])
        try generateCompanyIdentifierNames(data, output: output[1])
    }
    
    static func generateCompanyIdentifierExtensions(_ data: [UInt16: String], output: URL) throws {
        
        var generatedCode = ""
        let companies = companyIdentifiers(from: data)
        
        func 🖨(_ text: String) {
            generatedCode += text + "\n"
        }
                
        🖨("//")
        🖨("//  CompanyIdentifiers.swift")
        🖨("//  Bluetooth")
        🖨("//")
        🖨("")
        🖨("public extension CompanyIdentifier {")
        🖨("")
        
        for (id, name, memberName) in companies {
            
            🖨("    /// " + name + " " + "(`\(id)`)")
            🖨("    @_alwaysEmitIntoClient")
            🖨("    static var " + memberName + ": CompanyIdentifier {")
            🖨("        return CompanyIdentifier(rawValue: \(id))")
            🖨("    }")
            🖨("")
        }
        
        🖨("}")
        
        try generatedCode.write(toFile: output.path, atomically: true, encoding: .utf8)
        print("Generated Swift \(output.path)")
    }
    
    static func generateCompanyIdentifierNames(_ data: [UInt16: String], output: URL) throws {
        
        var generatedCode = ""
        let companies = companyIdentifiers(from: data)
        
        func 🖨(_ text: String) {
            generatedCode += text + "\n"
        }
        
        🖨("//")
        🖨("//  CompanyIdentifierNames.swift")
        🖨("//  Bluetooth")
        🖨("//")
        🖨("")
        🖨("internal extension CompanyIdentifier {")
        🖨("")
        🖨("    static let companyIdentifiers: [UInt16: String] = {")
        🖨("")
        🖨("        var companyIdentifiers = [UInt16: String]()")
        🖨("        companyIdentifiers.reserveCapacity(\(companies.count))")
        🖨("")
        
        for (id, name, _) in companies {
            🖨("        companyIdentifiers[\(id)] = #\"\(name)\"#")
        }
        
        🖨("        return companyIdentifiers")
        🖨("    }()")
        🖨("}")
        
        try generatedCode.write(toFile: output.path, atomically: true, encoding: .utf8)
        print("Generated Swift \(output.path)")
    }
    
    static func generateCompanyIdentifierTests(input: URL, output: URL) throws {
        
        let data = try parseCSV(input: input)
        
        var generatedCode = ""
        let companies = companyIdentifiers(from: data)
        
        func 🖨(_ text: String) {
            generatedCode += text + "\n"
        }
        
        // generate unit test for extensions
        generatedCode = """
        //
        //  CompanyIdentifierTests.swift
        //  Bluetooth
        //
        
        import XCTest
        import Foundation
        @testable import Bluetooth
        
        // swiftlint:disable type_body_length
        final class CompanyIdentifierTests: XCTestCase {
        
            func testCompanies() {
        
        
        """
        
        // generate test methods
        
        for (id, name, memberName) in companies {
                        
            🖨("""
                    // \(name)
                    XCTAssertEqual(CompanyIdentifier.\(memberName).rawValue, \(id))
                    XCTAssertEqual(CompanyIdentifier.\(memberName).name, #\"\(name)\"#)
                    XCTAssertEqual(CompanyIdentifier.\(memberName).description, #\"\(name)\"#)
                
            """)
        }
        
        🖨("""
                }
            
            }
            // swiftlint:enable type_body_length
            """)
        
        try generatedCode.write(toFile: output.path, atomically: true, encoding: .utf8)
        print("Generated Swift \(output.path)")
    }
}

final class CompanyIdentifierCSVParser: CSVParserDelegate {
    
    private(set) var data = [UInt16: String]()
    
    private var state: Line?
    
    private var error: Error?
    
    init() { }
    
    func parse(url: URL) throws {
        error = nil
        defer { error = nil }
        let parser = CSV.Parser(url: url, configuration: .detectConfigurationForContentsOfURL(url)!)!
        parser.delegate = self
        try parser.parse()
        if let error = self.error {
            throw error
        }
    }
    
    /// Called when the parser begins parsing.
    func parserDidBeginDocument(_ parser: CSV.Parser) {
        state = nil<commit_msg>Improve CSV parsing error handling<commit_after>//
//  CompanyIdentifier.swift
//  
//
//  Created by <NAME> on 6/12/22.
//

import Foundation

extension GenerateTool {
    
    static func parseCSV(input: URL) throws -> [UInt16: String] {
        let csvParser = CompanyIdentifierCSVParser()
        try csvParser.parse(url: input)
        return csvParser.data
    }
    
    static func companyIdentifiers(from data: [UInt16: String]) -> [(id: UInt16, name: String, member: String)] {
        let blacklist: [UInt16] = [
            .max // remove internal use identifier
        ]
        let companies = data
            .sorted(by: { $0.key < $1.key })
            .filter { blacklist.contains($0.key) == false }
        var memberNames = [UInt16: String]()
        memberNames.reserveCapacity(companies.count)
        for (id, name) in companies {
            let sanitizedName = name.sanitizeName()
            let llamaCaseName = sanitizedName.llamaCase()
            var memberName = llamaCaseName
            // prevent duplicate entries
            var duplicateNumber = 1
            while memberNames.values.contains(memberName) {
                duplicateNumber += 1
                memberName = llamaCaseName + "\(duplicateNumber)"
            }
            memberNames[id] = memberName
        }
        return companies.map { ($0, $1, memberNames[$0]!) }
    }
    
    static func generateCompanyIdentifiers(input: URL, output: [URL]) throws {
        let data = try parseCSV(input: input)
        try generateCompanyIdentifierExtensions(data, output: output[0])
        try generateCompanyIdentifierNames(data, output: output[1])
    }
    
    static func generateCompanyIdentifierExtensions(_ data: [UInt16: String], output: URL) throws {
        
        var generatedCode = ""
        let companies = companyIdentifiers(from: data)
        
        func 🖨(_ text: String) {
            generatedCode += text + "\n"
        }
                
        🖨("//")
        🖨("//  CompanyIdentifiers.swift")
        🖨("//  Bluetooth")
        🖨("//")
        🖨("")
        🖨("public extension CompanyIdentifier {")
        🖨("")
        
        for (id, name, memberName) in companies {
            
            🖨("    /// " + name + " " + "(`\(id)`)")
            🖨("    @_alwaysEmitIntoClient")
            🖨("    static var " + memberName + ": CompanyIdentifier {")
            🖨("        return CompanyIdentifier(rawValue: \(id))")
            🖨("    }")
            🖨("")
        }
        
        🖨("}")
        
        try generatedCode.write(toFile: output.path, atomically: true, encoding: .utf8)
        print("Generated Swift \(output.path)")
    }
    
    static func generateCompanyIdentifierNames(_ data: [UInt16: String], output: URL) throws {
        
        var generatedCode = ""
        let companies = companyIdentifiers(from: data)
        
        func 🖨(_ text: String) {
            generatedCode += text + "\n"
        }
        
        🖨("//")
        🖨("//  CompanyIdentifierNames.swift")
        🖨("//  Bluetooth")
        🖨("//")
        🖨("")
        🖨("internal extension CompanyIdentifier {")
        🖨("")
        🖨("    static let companyIdentifiers: [UInt16: String] = {")
        🖨("")
        🖨("        var companyIdentifiers = [UInt16: String]()")
        🖨("        companyIdentifiers.reserveCapacity(\(companies.count))")
        🖨("")
        
        for (id, name, _) in companies {
            🖨("        companyIdentifiers[\(id)] = #\"\(name)\"#")
        }
        
        🖨("        return companyIdentifiers")
        🖨("    }()")
        🖨("}")
        
        try generatedCode.write(toFile: output.path, atomically: true, encoding: .utf8)
        print("Generated Swift \(output.path)")
    }
    
    static func generateCompanyIdentifierTests(input: URL, output: URL) throws {
        
        let data = try parseCSV(input: input)
        
        var generatedCode = ""
        let companies = companyIdentifiers(from: data)
        
        func 🖨(_ text: String) {
            generatedCode += text + "\n"
        }
        
        // generate unit test for extensions
        generatedCode = """
        //
        //  CompanyIdentifierTests.swift
        //  Bluetooth
        //
        
        import XCTest
        import Foundation
        @testable import Bluetooth
        
        // swiftlint:disable type_body_length
        final class CompanyIdentifierTests: XCTestCase {
        
            func testCompanies() {
        
        
        """
        
        // generate test methods
        
        for (id, name, memberName) in companies {
                        
            🖨("""
                    // \(name)
                    XCTAssertEqual(CompanyIdentifier.\(memberName).rawValue, \(id))
                    XCTAssertEqual(CompanyIdentifier.\(memberName).name, #\"\(name)\"#)
                    XCTAssertEqual(CompanyIdentifier.\(memberName).description, #\"\(name)\"#)
                
            """)
        }
        
        🖨("""
                }
            
            }
            // swiftlint:enable type_body_length
            """)
        
        try generatedCode.write(toFile: output.path, atomically: true, encoding: .utf8)
        print("Generated Swift \(output.path)")
    }
}

final class CompanyIdentifierCSVParser: CSVParserDelegate {
    
    private(set) var data = [UInt16: String]()
    
    private var state: Line?
    
    private var error: Error?
    
    init() { }
    
    func parse(url: URL) throws {
        error = nil
        defer { error = nil }
        guard let parser = CSV.Parser(
            url: url,
            configuration: .detectConfigurationForContentsOfURL(url) ?? .init(delimiter: ",", encoding: .utf8)
        ) else {
            throw CSVError(description: "Invalid file \(url)")
        }
        parser.delegate = self
        try parser.parse()
        if let error = self.error {
            throw error
        }
    }
    
    /// Called when the parser begins parsing.
    func parserDidBeginDocument(_ parser: CSV.Parser) {
        state = nil
{'fraction_non_alphanumeric': 0.12318900571239341, 'fraction_numerical': 0.0063746998923751965, 'mean_word_length': 1.5900514579759863, 'pattern_counts': {'":': 2, '<': 7, '<?xml version=': 0, '>': 9, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6338728', 'n_tokens_mistral': 3513, 'n_tokens_neox': 3344, 'n_words': 782}