Skip to content

Commit

Permalink
rename command support
Browse files Browse the repository at this point in the history
placeholder type
general cleanup
  • Loading branch information
g-Off committed Nov 20, 2018
1 parent f230ade commit b8877c8
Show file tree
Hide file tree
Showing 13 changed files with 282 additions and 146 deletions.
58 changes: 20 additions & 38 deletions Sources/stringray/Commands/Command.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,47 +16,29 @@ protocol Command {
func run(with arguments: ArgumentParser.Result) throws
}

struct SaveOptions : OptionSet {
public private(set) var rawValue: UInt
public init(rawValue: UInt) { self.rawValue = rawValue }

public static let sortedKeys = SaveOptions(rawValue: 1 << 0)
public static let writeFile = SaveOptions(rawValue: 1 << 1)
}

extension Command {

func write(to url: Foundation.URL, table: StringsTable, options: SaveOptions) throws {
var table = table
if options.contains(.sortedKeys) {
table.sort()
}
if options.contains(.writeFile) {
for (languageId, languageEntries) in table.entries {
let fileURL = try url.stringsURL(tableName: table.name, locale: languageId)
guard let outputStream = OutputStream(url: fileURL, append: false) else { continue }
outputStream.open()
var firstEntry = true
for entry in languageEntries {
if !firstEntry {
outputStream.write(string: "\n")
}
firstEntry = false
if let comment = entry.comment {
outputStream.write(string: "\(comment)\n")
}
outputStream.write(string: "\(entry.keyedValue)\n")
func write(to url: Foundation.URL, table: StringsTable) throws {
for (languageId, languageEntries) in table.entries {
let fileURL = try url.stringsURL(tableName: table.name, locale: languageId)
guard let outputStream = OutputStream(url: fileURL, append: false) else { continue }
outputStream.open()
var firstEntry = true
for entry in languageEntries {
if !firstEntry {
outputStream.write(string: "\n")
}
outputStream.close()
}

for (languageId, languageEntries) in table.dictEntries {
let fileURL = try url.stringsDictURL(tableName: table.name, locale: languageId)
let encoder = PropertyListEncoder()
encoder.outputFormat = .xml
let data = try encoder.encode(languageEntries)
try data.write(to: fileURL, options: [.atomic])
firstEntry = false
outputStream.write(string: "\(entry)\n")
}
outputStream.close()
}

for (languageId, languageEntries) in table.dictEntries {
let fileURL = try url.stringsDictURL(tableName: table.name, locale: languageId)
let encoder = PropertyListEncoder()
encoder.outputFormat = .xml
let data = try encoder.encode(languageEntries)
try data.write(to: fileURL, options: [.atomic])
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions Sources/stringray/Commands/MoveCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ struct MoveCommand: Command {
let filteredTable = fromTable.withKeys(matching: matching)
toTable.addEntries(from: filteredTable)
fromTable.removeEntries(from: filteredTable)
try write(to: to.resourceDirectory, table: toTable, options: [.writeFile])
try write(to: from.resourceDirectory, table: fromTable, options: [.writeFile])
try write(to: to.resourceDirectory, table: toTable)
try write(to: from.resourceDirectory, table: fromTable)
}
}
15 changes: 11 additions & 4 deletions Sources/stringray/Commands/RenameCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ struct RenameCommand: Command {
private struct Arguments {
var inputFile: Foundation.URL!
var matching: [Match] = []
var replacements: [String] = []
}

let command: String = "rename"
let overview: String = ""
let overview: String = "Renames a key with another key."

private let binder: ArgumentBinder<Arguments>

Expand All @@ -25,6 +26,7 @@ struct RenameCommand: Command {

let inputFile = subparser.add(positional: "inputFile", kind: PathArgument.self, optional: false, usage: "", completion: .filename)
let prefix = subparser.add(option: "--prefix", shortName: "-p", kind: [String].self, strategy: .oneByOne, usage: "", completion: nil)
let replacement = subparser.add(option: "--replacement", shortName: "-r", kind: [String].self, strategy: .oneByOne, usage: "", completion: nil)

binder.bind(positional: inputFile) { (arguments, inputFile) in
arguments.inputFile = URL(fileURLWithPath: inputFile.path.asString)
Expand All @@ -34,15 +36,20 @@ struct RenameCommand: Command {
return .prefix($0)
}
}
binder.bind(option: replacement) { (arguments, replacements) in
arguments.replacements = replacements
}
}

func run(with arguments: ArgumentParser.Result) throws {
var commandArgs = Arguments()
try binder.fill(parseResult: arguments, into: &commandArgs)
try rename(url: commandArgs.inputFile, matching: commandArgs.matching)
try rename(url: commandArgs.inputFile, matching: commandArgs.matching, replacements: commandArgs.replacements)
}

private func rename(url: Foundation.URL, matching: [Match]) throws {
// TODO: implement
private func rename(url: Foundation.URL, matching: [Match], replacements replacementStrings: [String]) throws {
var table = try StringsTable(url: url)
table.replace(matches: matching, replacements: replacementStrings)
try write(to: url.resourceDirectory, table: table)
}
}
6 changes: 4 additions & 2 deletions Sources/stringray/Commands/SortCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ struct SortCommand: Command {
var inputFile: Foundation.URL!
}
let command: String = "sort"
let overview: String = "Sorts the keys in the given strings table."
let overview: String = "Sorts the given strings table alphabetically by key."

private let binder: ArgumentBinder<Arguments>

Expand All @@ -34,6 +34,8 @@ struct SortCommand: Command {
}

private func sort(url: Foundation.URL) throws {
try write(to: url, table: try StringsTable(url: url), options: [.writeFile, .sortedKeys])
var table = try StringsTable(url: url)
table.sort()
try write(to: url, table: table)
}
}
36 changes: 20 additions & 16 deletions Sources/stringray/Extensions/URL+Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,24 @@ import Foundation

extension URL {
var tableName: String? {
return tableComponents?.name
var url = self
if ["strings", "stringsdict"].contains(url.pathExtension) {
url.deletePathExtension()
return url.lastPathComponent
}
return nil
}

var locale: String? {
return tableComponents?.locale
var locale: Locale? {
var url = self
if ["strings", "stringsdict"].contains(url.pathExtension) {
url.deleteLastPathComponent()
}
if url.pathExtension == "lproj" {
url.deletePathExtension()
return Locale(identifier: url.lastPathComponent)
}
return nil
}

var resourceDirectory: URL {
Expand All @@ -27,15 +40,6 @@ extension URL {
return dir
}

var tableComponents: (name: String, locale: String)? {
guard pathExtension == "strings" || pathExtension == "stringsdict" else { return nil }
let name = deletingPathExtension().lastPathComponent
let lproj = deletingLastPathComponent()
guard lproj.pathExtension == "lproj" else { return nil }
let base = lproj.deletingPathExtension().lastPathComponent
return (name, base)
}

var lprojURLs: [URL] {
let directories = try? FileManager.default.contentsOfDirectory(at: self, includingPropertiesForKeys: nil, options: []).filter { (url) -> Bool in
return url.pathExtension == "lproj"
Expand All @@ -59,16 +63,16 @@ extension URL {
}
}

func stringsURL(tableName: String, locale: String) throws -> URL {
func stringsURL(tableName: String, locale: Locale) throws -> URL {
return try fileURL(tableName: tableName, locale: locale, ext: "strings", create: true)
}

func stringsDictURL(tableName: String, locale: String) throws -> URL {
func stringsDictURL(tableName: String, locale: Locale) throws -> URL {
return try fileURL(tableName: tableName, locale: locale, ext: "stringsdict", create: true)
}

private func fileURL(tableName: String, locale: String, ext: String, create: Bool) throws -> URL {
let lprojURL = appendingPathComponent("\(locale).lproj", isDirectory: true)
private func fileURL(tableName: String, locale: Locale, ext: String, create: Bool) throws -> URL {
let lprojURL = appendingPathComponent("\(locale.identifier).lproj", isDirectory: true)
if create {
try FileManager.default.createDirectory(at: lprojURL, withIntermediateDirectories: true, attributes: nil)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ struct MissingLocalizationLintRule: LintRule {
for missingEntry in missingEntries {
let file = URL(fileURLWithPath: "\(entry.key).lproj/\(table.name).strings", relativeTo: url)
let location = LintRuleViolation.Location(file: file, line: nil)
let reason = "\(entry.key), \(missingEntry.keyedValue.key)"
let reason = "\(entry.key), \(missingEntry.key)"
let violation = LintRuleViolation(location: location, severity: .warning, reason: reason)
violations.append(violation)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ struct OrphanedLocalizationLintRule: LintRule {
let orphanedEntries = entry.value.subtracting(baseEntries)
for orphanedEntry in orphanedEntries {
let file = URL(fileURLWithPath: "\(entry.key).lproj/\(table.name).strings", relativeTo: url)
let location = LintRuleViolation.Location(file: file, line: orphanedEntry.keyedValue.line)
let reason = "\(entry.key), \(orphanedEntry.keyedValue.key)"
guard let line = orphanedEntry.location?.line else { continue }
let location = LintRuleViolation.Location(file: file, line: line)
let reason = "\(entry.key), \(orphanedEntry.key)"
let violation = LintRuleViolation(location: location, severity: .warning, reason: reason)
violations.append(violation)
}
Expand Down
10 changes: 10 additions & 0 deletions Sources/stringray/Strings Table/Match.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,16 @@ enum Match {
return false
}
}

func replacing(with newPrefix: String, in string: String) -> String? {
switch self {
case .prefix(let prefix):
guard let range = string.range(of: prefix) else { return nil }
return string.replacingCharacters(in: range, with: newPrefix)
default:
return nil
}
}
}

extension Array where Element == Match {
Expand Down
75 changes: 75 additions & 0 deletions Sources/stringray/Strings Table/PlaceholderType.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
//
// PlaceholderType.swift
// stringray
//
// Created by Geoffrey Foster on 2018-11-13.
//

import Foundation

public enum PlaceholderType: String, Codable {
case object = "String"
case float = "Float"
case int = "Int"
case char = "CChar"
case cString = "UnsafePointer<CChar>"
case pointer = "UnsafeRawPointer"

static let unknown = pointer

init?(_ string: String) {
guard let char = string.lowercased().first else {
return nil
}
switch char {
case "@":
self = .object
case "a", "e", "f", "g":
self = .float
case "d", "i", "o", "u", "x":
self = .int
case "c":
self = .char
case "s":
self = .cString
case "p":
self = .pointer
default:
return nil
}
}

private static let formatTypesRegEx: NSRegularExpression = {
// %d/%i/%o/%u/%x with their optional length modifiers like in "%lld"
let patternInt = "(?:h|hh|l|ll|q|z|t|j)?([dioux])"
// valid flags for float
let patternFloat = "[aefg]"
// like in "%3$" to make positional specifiers
let position = "((?<position>[1-9]\\d*)\\$)?"
// precision like in "%1.2f"
let precision = "[-+# 0]?\\d?(?:\\.\\d)?"

let pattern = "(?:^|(?<!%)(?:%%)*)%\(position)\(precision)(?<type>@|\(patternInt)|\(patternFloat)|[csp])"
return try! NSRegularExpression(pattern: pattern, options: [.caseInsensitive])
}()

// "I give %d apples to %@" --> [.int, .string]
static func placeholders(from formatString: String) throws -> [(PlaceholderType, Int?)] {
let range = NSRange(formatString.startIndex..<formatString.endIndex, in: formatString)

let placeholders: [(PlaceholderType, Int?)] = formatTypesRegEx.matches(in: formatString, options: [], range: range).compactMap { (match) in
if let typeRange = Range(match.range(withName: "type"), in: formatString),
let placeholderType = PlaceholderType(String(formatString[typeRange])) {
var position: Int?
if let locationRange = Range(match.range(withName: "position"), in: formatString) {
position = Int(formatString[locationRange])
}

return (placeholderType, position)
}
// TODO: throw when nil
return nil
}
return placeholders
}
}
Loading

0 comments on commit b8877c8

Please sign in to comment.