diff --git a/Sources/XCStringsMigrator/XMReverter.swift b/Sources/XCStringsMigrator/XMReverter.swift index ecbf1c8..78dc571 100644 --- a/Sources/XCStringsMigrator/XMReverter.swift +++ b/Sources/XCStringsMigrator/XMReverter.swift @@ -4,6 +4,7 @@ public struct XMReverter { private var path: String private var outputPath: String private var lineSpacing: Bool + private var finalNewline: Bool var standardOutput: (Any...) -> Void var createDirectory: (URL) throws -> Void var writeString: (String, URL) throws -> Void @@ -11,11 +12,13 @@ public struct XMReverter { public init( path: String, outputPath: String, - lineSpacing: Bool + lineSpacing: Bool, + finalNewline: Bool ) { self.path = path self.outputPath = outputPath self.lineSpacing = lineSpacing + self.finalNewline = finalNewline self.standardOutput = { Swift.print($0.map({ "\($0)" }).joined(separator: " ")) } @@ -75,11 +78,11 @@ public struct XMReverter { let outputFileURL = outputFolderURL .appending(path: stringsData.tableName) .appendingPathExtension("strings") - let separator = lineSpacing ? "\n\n" : "\n" let text = stringsData.values .sorted(by: { $0.key < $1.key }) .map { "\($0.key.debugDescription) = \($0.value.debugDescription);" } - .joined(separator: separator) + .joined(separator: lineSpacing ? "\n\n" : "\n") + .appending(finalNewline ? "\n" : "") try writeString(text, outputFileURL) standardOutput("Succeeded to export strings file.") } catch { diff --git a/Sources/xcstrings-migrator/Revert.swift b/Sources/xcstrings-migrator/Revert.swift index affa32f..05ad828 100644 --- a/Sources/xcstrings-migrator/Revert.swift +++ b/Sources/xcstrings-migrator/Revert.swift @@ -25,12 +25,19 @@ struct Revert: ParsableCommand { ) var lineSpacing: Bool = false + @Flag( + name: [.customShort("n"), .customLong("final-newline")], + help: "Insert a newline at the end of file." + ) + var finalNewline: Bool = false + mutating func run() throws { do { try XMReverter( path: path, outputPath: outputPath, - lineSpacing: lineSpacing + lineSpacing: lineSpacing, + finalNewline: finalNewline ).run() } catch let error as XMError { Swift.print("error:", error.errorDescription!) diff --git a/Sources/xcstrings-migrator/main.swift b/Sources/xcstrings-migrator/main.swift index 221d8ab..6450a35 100644 --- a/Sources/xcstrings-migrator/main.swift +++ b/Sources/xcstrings-migrator/main.swift @@ -4,7 +4,7 @@ struct Entrance: ParsableCommand { static var configuration = CommandConfiguration( commandName: "xcstrings-migrator", abstract: "A tool to migrate the legacy strings file to xcstrings file.", - version: "1.1.0", + version: "1.2.0", subcommands: [Migrate.self, Revert.self], defaultSubcommand: Migrate.self ) diff --git a/Tests/XCStringsMigratorTests/XMReverterTests.swift b/Tests/XCStringsMigratorTests/XMReverterTests.swift index 890ed96..9d998df 100644 --- a/Tests/XCStringsMigratorTests/XMReverterTests.swift +++ b/Tests/XCStringsMigratorTests/XMReverterTests.swift @@ -6,28 +6,28 @@ final class XMReverterTests: XCTestCase { func test_extractXCStrings() throws { try XCTContext.runActivity(named: "If path extension is not xcstrings, error is thrown.") { _ in let url = try XCTUnwrap(Bundle.module.resourceURL).appending(path: "dummy") - let sut = XMReverter(path: url.path(), outputPath: "", lineSpacing: false) + let sut = XMReverter(path: url.path(), outputPath: "", lineSpacing: false, finalNewline: false) XCTAssertThrowsError(try sut.extractXCStrings()) { error in XCTAssertEqual(error as? XMError, XMError.xcstringsFileNotFound) } } try XCTContext.runActivity(named: "If path extension is xcstrings but file does not exist, error is thrown.") { _ in let url = try XCTUnwrap(Bundle.module.resourceURL).appending(path: "not-exist.xcstrings") - let sut = XMReverter(path: url.path(), outputPath: "", lineSpacing: false) + let sut = XMReverter(path: url.path(), outputPath: "", lineSpacing: false, finalNewline: false) XCTAssertThrowsError(try sut.extractXCStrings()) { error in XCTAssertEqual(error as? XMError, XMError.xcstringsFileNotFound) } } try XCTContext.runActivity(named: "If path extension is xcstrings and file exists but is broken, error is thrown.") { _ in let url = try XCTUnwrap(Bundle.module.url(forResource: "Broken", withExtension: "xcstrings", subdirectory: "Reverter")) - let sut = XMReverter(path: url.path(), outputPath: "", lineSpacing: false) + let sut = XMReverter(path: url.path(), outputPath: "", lineSpacing: false, finalNewline: false) XCTAssertThrowsError(try sut.extractXCStrings()) { error in XCTAssertEqual(error as? XMError, XMError.xcstringsFileIsBroken) } } try XCTContext.runActivity(named: "") { _ in let url = try XCTUnwrap(Bundle.module.url(forResource: "Localizable", withExtension: "xcstrings", subdirectory: "Reverter")) - let sut = XMReverter(path: url.path(), outputPath: "", lineSpacing: false) + let sut = XMReverter(path: url.path(), outputPath: "", lineSpacing: false, finalNewline: false) let actual = try sut.extractXCStrings() let expect = XCStrings( sourceLanguage: "en", @@ -57,7 +57,7 @@ final class XMReverterTests: XCTestCase { func test_convertToStringsData() { XCTContext.runActivity(named: "XCStrings is converted to StringsData array.") { _ in - let sut = XMReverter(path: "", outputPath: "", lineSpacing: false) + let sut = XMReverter(path: "", outputPath: "", lineSpacing: false, finalNewline: false) let input = XCStrings( sourceLanguage: "test", strings: [ @@ -102,8 +102,8 @@ final class XMReverterTests: XCTestCase { } func test_exportStringsFile() throws { - try XCTContext.runActivity(named: "If StringsData is valid and line spacing is false, file is successfully exported without line spacing.") { _ in - var sut = XMReverter(path: "", outputPath: "output", lineSpacing: false) + try XCTContext.runActivity(named: "If StringsData is valid and other options are false, file is successfully exported without line spacing.") { _ in + var sut = XMReverter(path: "", outputPath: "output", lineSpacing: false, finalNewline: false) var standardOutputs = [String]() sut.standardOutput = { items in standardOutputs.append(contentsOf: items.map({ "\($0)" })) @@ -135,7 +135,7 @@ final class XMReverterTests: XCTestCase { XCTAssertEqual(writeStrings, [details]) } try XCTContext.runActivity(named: "If StringsData is valid and line spacing is true, file is successfully exported with line spacing.") { _ in - var sut = XMReverter(path: "", outputPath: "output", lineSpacing: true) + var sut = XMReverter(path: "", outputPath: "output", lineSpacing: true, finalNewline: false) var standardOutputs = [String]() sut.standardOutput = { items in standardOutputs.append(contentsOf: items.map({ "\($0)" })) @@ -169,8 +169,41 @@ final class XMReverterTests: XCTestCase { XCTAssertEqual(standardOutputs, ["Succeeded to export strings file."]) XCTAssertEqual(writeStrings, [details]) } + try XCTContext.runActivity(named: "If StringsData is valid and final newline is true, file is successfully exported with a newline at the end.") { _ in + var sut = XMReverter(path: "", outputPath: "output", lineSpacing: false, finalNewline: true) + var standardOutputs = [String]() + sut.standardOutput = { items in + standardOutputs.append(contentsOf: items.map({ "\($0)" })) + } + var writeStrings = [String]() + sut.writeString = { text, url in + XCTAssertEqual(url.path(), "output/test.lproj/Localizable.strings") + writeStrings.append(text) + } + + let input = StringsData( + tableName: "Localizable", + language: "test", + values: [ + "\"Hello %@\"": "\"Hello %@\"", + "Count = %lld": "Count = %lld", + "key": "value", + "path": "/", + ] + ) + try sut.exportStringsFile(input) + let details = #""" + "\"Hello %@\"" = "\"Hello %@\""; + "Count = %lld" = "Count = %lld"; + "key" = "value"; + "path" = "/"; + + """# + XCTAssertEqual(standardOutputs, ["Succeeded to export strings file."]) + XCTAssertEqual(writeStrings, [details]) + } try XCTContext.runActivity(named: "If exporting file fails, an error is thrown.") { _ in - var sut = XMReverter(path: "", outputPath: "", lineSpacing: false) + var sut = XMReverter(path: "", outputPath: "", lineSpacing: false, finalNewline: false) var standardOutputs = [String]() sut.standardOutput = { items in standardOutputs.append(contentsOf: items.map({ "\($0)" }))