Skip to content
This repository has been archived by the owner on Jun 29, 2022. It is now read-only.

Commit

Permalink
Add find strings scripts to package
Browse files Browse the repository at this point in the history
  • Loading branch information
hectr committed Oct 2, 2019
1 parent 4adc59d commit 02c38f0
Show file tree
Hide file tree
Showing 34 changed files with 367 additions and 79 deletions.
25 changes: 23 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,36 @@ let package = Package(
targets: [
.target(
name: "unstringify",
dependencies: ["Unstringified"]),
dependencies: ["Unstringified", "UnstringifyFramework"]),
.target(
name: "Unstringified",
dependencies: []),
.target(
name: "findStrings",
dependencies: ["UnstringifyFramework"]),
.target(
name: "findAndroidStrings",
dependencies: ["UnstringifyFramework"]),
.target(
name: "UnstringifyFramework",
dependencies: []),
.target(
name: "CLITestFramework",
dependencies: []),
.testTarget(
name: "UnstringifyTests",
dependencies: ["unstringify"]),
dependencies: ["CLITestFramework", "unstringify"]),
.testTarget(
name: "UnstringifiedTests",
dependencies: ["Unstringified"]),
.testTarget(
name: "FindStringsTests",
dependencies: ["CLITestFramework", "findStrings"]),
.testTarget(
name: "FindAndroidStringsTests",
dependencies: ["CLITestFramework", "findAndroidStrings"]),
.testTarget(
name: "UnstringifyFrameworkTests",
dependencies: ["UnstringifyFramework"]),
]
)
26 changes: 26 additions & 0 deletions Sources/CLITestFramework/XCTest/XCTestCase+execute.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import class Foundation.Bundle
import XCTest

extension XCTestCase {
public func execute(product: String) throws -> String {
guard #available(macOS 10.13, *) else {
fatalError("Some of the APIs that we use in the test are available in macOS 10.13 and above.")
}

let fooBinary = productsDirectory.appendingPathComponent(product)

let process = Process()
process.executableURL = fooBinary

let pipe = Pipe()
process.standardOutput = pipe

try process.run()
process.waitUntilExit()

let data = pipe.fileHandleForReading.readDataToEndOfFile()
guard let output = String(data: data, encoding: .utf8) else { fatalError("output cannot be nil") }

return output
}
}
16 changes: 16 additions & 0 deletions Sources/CLITestFramework/XCTest/XCTestCase+productsDirectory.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import class Foundation.Bundle
import XCTest

extension XCTestCase {
/// Returns path to the built products directory.
public var productsDirectory: URL {
#if os(macOS)
for bundle in Bundle.allBundles where bundle.bundlePath.hasSuffix(".xctest") {
return bundle.bundleURL.deletingLastPathComponent()
}
fatalError("couldn't find the products directory")
#else
return Bundle.main.bundleURL
#endif
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Foundation

extension String {
func containsHTML() throws -> Bool {
public func containsHTML() throws -> Bool {
return try !matches(regex: "\\<([a-zA-Z0-9]+)\\>.*?\\<\\/\\1\\>").isEmpty
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Foundation

extension String {
func formatSpecifiers() throws -> [String] {
public func formatSpecifiers() throws -> [String] {
// Regex pattern for format specifiers (`% flags width .precision size type`):
//
// - mark: %
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Foundation

extension String {
var isInfoPlistValueLine: Bool {
public var isInfoPlistValueLine: Bool {
// precondition: there aren't multiline comments
return hasPrefix("\"infoplist_")
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Foundation

extension String {
var isKeyValueLine: Bool {
public var isKeyValueLine: Bool {
// precondition: there aren't multiline comments
return hasPrefix("\"")
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Foundation

extension String {
var localizableKey: String? {
public var localizableKey: String? {
// "x" = "y"; -> "x -> x
return components(separatedBy: "\" = \"").first?.removingCharacter(at: 0)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Foundation

extension String {
func matches(regex pattern: String) throws -> [String] {
public func matches(regex pattern: String) throws -> [String] {
let regex = try NSRegularExpression(pattern: pattern)
let nsString = self as NSString
let results = regex.matches(in: self, range: NSRange(location: 0, length: nsString.length))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Foundation

extension String {
func removingCharacter(at position: UInt) -> String {
public func removingCharacter(at position: UInt) -> String {
var string = self
let index = string.index(string.startIndex, offsetBy: Int(position))
string.remove(at: index)
Expand Down
16 changes: 16 additions & 0 deletions Sources/UnstringifyFramework/findFilesRecursively.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import Foundation

public func findFilesRecursively(rootPath: String, extensions: [String], fileManager: FileManager = FileManager.default) throws -> [String] {
let directoryPath = rootPath
.appending("/\n")
.replacingOccurrences(of: "//\n", with: "/")
.replacingOccurrences(of: "/\n", with: "/")
let enumerator = fileManager.enumerator(atPath: directoryPath)
var filePaths = Set<String>()
while let filePath = enumerator?.nextObject() as? String {
if extensions.contains(URL(fileURLWithPath: filePath).pathExtension) {
filePaths.insert(directoryPath.appending(filePath))
}
}
return Array(filePaths)
}
11 changes: 11 additions & 0 deletions Sources/UnstringifyFramework/parseKeys.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import Foundation

public func parseKeys(_ content: String) throws -> [String] {
let keyValueLines = content.components(separatedBy: "\n").filter { $0.isKeyValueLine }
var keys = Set<String>()
for line in keyValueLines {
guard let key = line.localizableKey else { continue }
keys.insert(key)
}
return Array(keys)
}
6 changes: 6 additions & 0 deletions Sources/UnstringifyFramework/readFile.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import Foundation

public func readFile(at path: String) throws -> String {
let fileURL = URL(fileURLWithPath: path)
return try String(contentsOf: fileURL)
}
21 changes: 21 additions & 0 deletions Sources/findAndroidStrings/Internal/generateReport.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import Foundation
import UnstringifyFramework

func generateReport(files: [String], keys: [String], outputPath: String, excludedFile: String) throws {
var matches = [String: [String]]()
for filePath in files where !filePath.hasSuffix(excludedFile) {
let contents = try readFile(at: filePath)
for key in keys {
if contents.range(of: "@string/\(key)") != nil || contents.range(of: "R.string.\(key)") != nil {
var matchesForKey = matches[key] ?? []
matchesForKey.append(filePath)
matches[key] = matchesForKey
} else {
matches[key] = matches[key] ?? []
}
}
}
let csv = matches.reduce(["Keys;File\n"]) { return $0 + ["\"\($1.key)\"; \($1.value.map { "\($0)" }.joined(separator: "; "))"] }.joined(separator: "\n")
let data = csv.data(using: String.Encoding.utf8, allowLossyConversion: false)
FileManager.default.createFile(atPath: outputPath, contents: data, attributes: nil)
}
28 changes: 28 additions & 0 deletions Sources/findAndroidStrings/Internal/parseArguments.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import Foundation

typealias Arguments = (localizablePath: String, rootPath: String, outputPath: String, excludedFile: String)

func parseArguments(from arguments: [String] = CommandLine.arguments) throws -> Arguments {
enum Error: Swift.Error {
case tooFewArguments
case tooManyArguments
}

func printUsage() {
print("\nUsage: \(arguments[0]) localizable_strings_path android_project_root_path csv_output_path [excluded_file]\n")
}

if arguments.count < 4 {
printUsage()
throw Error.tooFewArguments
} else if arguments.count > 5 {
printUsage()
throw Error.tooManyArguments
}

if arguments.count == 5 {
return (arguments[1], arguments[2], arguments[3], arguments[4])
} else {
return (arguments[1], arguments[2], arguments[3], "gen-strings.xml")
}
}
13 changes: 13 additions & 0 deletions Sources/findAndroidStrings/main.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import Foundation
import UnstringifyFramework

let arguments = try parseArguments()
let stringsFile = try readFile(at: arguments.localizablePath)
print("Finding localizable keys...")
let keys = try parseKeys(stringsFile)
print("Gathering android files...")
let files = try findFilesRecursively(rootPath: arguments.rootPath, extensions: ["kt", "xml"])
print("Generating report...")
try generateReport(files: files, keys: keys, outputPath: arguments.outputPath, excludedFile: arguments.excludedFile)

print("Done!")
21 changes: 21 additions & 0 deletions Sources/findStrings/Internal/generateReport.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import Foundation
import UnstringifyFramework

func generateReport(files: [String], keys: [String], outputPath: String, excludedFile: String) throws {
var matches = [String: [String]]()
for filePath in files where !filePath.hasSuffix(excludedFile) {
let contents = try readFile(at: filePath)
for key in keys {
if contents.range(of: key) != nil {
var matchesForKey = matches[key] ?? []
matchesForKey.append(filePath)
matches[key] = matchesForKey
} else {
matches[key] = matches[key] ?? []
}
}
}
let csv = matches.reduce(["Keys;File\n"]) { return $0 + ["\"\($1.key)\"; \($1.value.map { "\($0)" }.joined(separator: "; "))"] }.joined(separator: "\n")
let data = csv.data(using: String.Encoding.utf8, allowLossyConversion: false)
FileManager.default.createFile(atPath: outputPath, contents: data, attributes: nil)
}
29 changes: 29 additions & 0 deletions Sources/findStrings/Internal/parseArguments.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import Foundation

typealias Arguments = (rootPath: String, localizablePath: String, outputPath: String, excludedFile: String)

func parseArguments(from arguments: [String] = CommandLine.arguments) throws -> Arguments {
enum Error: Swift.Error {
case tooFewArguments
case tooManyArguments
}

func printUsage() {
print("\nUsage: \(arguments[0]) root_path localizable_path output_path [excluded_file]\n")
}

if arguments.count < 4 {
printUsage()
throw Error.tooFewArguments
} else if arguments.count > 5 {
printUsage()
throw Error.tooManyArguments
}

if arguments.count == 5 {
return (arguments[1], arguments[2], arguments[3], arguments[4])
} else {
return (arguments[1], arguments[2], arguments[3], "Unstringify.generated.swift")
}
}

13 changes: 13 additions & 0 deletions Sources/findStrings/main.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import Foundation
import UnstringifyFramework

let arguments = try parseArguments()
let stringsFile = try readFile(at: arguments.localizablePath)
print("Finding localizable keys...")
let keys = try parseKeys(stringsFile)
print("Gathering swift files...")
let files = try findFilesRecursively(rootPath: arguments.rootPath, extensions: ["swift"])
print("Generating report...")
try generateReport(files: files, keys: keys, outputPath: arguments.outputPath, excludedFile: arguments.excludedFile)

print("Done!")
6 changes: 1 addition & 5 deletions Sources/unstringify/Internal/generateEnums.swift
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
import Foundation
import UnstringifyFramework

func generateKeys(localizablePath: String) throws -> Keys {
let content = try readFile(at: localizablePath)
return try parseKeys(content)
}

private func readFile(at path: String) throws -> String {
let fileURL = URL(fileURLWithPath: path)
return try String(contentsOf: fileURL)
}
2 changes: 1 addition & 1 deletion Sources/unstringify/Internal/parseArguments.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ func parseArguments(from arguments: [String] = CommandLine.arguments) throws ->
}

func printUsage() {
print("Usage: \(arguments[0]) inputPath outputPath")
print("\nUsage: \(arguments[0]) inputPath outputPath\n")
}

if arguments.count < 3 {
Expand Down
1 change: 1 addition & 0 deletions Sources/unstringify/Internal/parseKeys.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Foundation
import UnstringifyFramework

typealias Keys = (standard: [String], formatted: [FormatKey], rich: [String], richFormatted: [FormatKey])

Expand Down
14 changes: 14 additions & 0 deletions Tests/FindAndroidStringsTests/FindAndroidStringsTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import XCTest
import CLITestFramework

final class FindAndroidStringsTests: XCTestCase {
func testUsage() throws {
let output = try execute(product: "findAndroidStrings")
XCTAssertTrue(output.hasPrefix("\nUsage: "))
XCTAssertTrue(output.hasSuffix("/findAndroidStrings localizable_strings_path android_project_root_path csv_output_path [excluded_file]\n\n"))
}

static var allTests = [
("testUsage", testUsage),
]
}
18 changes: 18 additions & 0 deletions Tests/FindAndroidStringsTests/XCTestManifests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#if !canImport(ObjectiveC)
import XCTest

extension FindAndroidStringsTests {
// DO NOT MODIFY: This is autogenerated, use:
// `swift test --generate-linuxmain`
// to regenerate.
static let __allTests__FindAndroidStringsTests = [
("testUsage", testUsage),
]
}

public func __allTests() -> [XCTestCaseEntry] {
return [
testCase(FindAndroidStringsTests.__allTests__FindAndroidStringsTests),
]
}
#endif
14 changes: 14 additions & 0 deletions Tests/FindStringsTests/FindStringsTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import XCTest
import CLITestFramework

final class FindStringsTests: XCTestCase {
func testUsage() throws {
let output = try execute(product: "findStrings")
XCTAssertTrue(output.hasPrefix("\nUsage: "))
XCTAssertTrue(output.hasSuffix("/findStrings root_path localizable_path output_path [excluded_file]\n\n"))
}

static var allTests = [
("testUsage", testUsage),
]
}
Loading

0 comments on commit 02c38f0

Please sign in to comment.