From 5c8cb03bc70ae5ab966d372feb1145e8ef685502 Mon Sep 17 00:00:00 2001 From: Geoffrey Foster Date: Sat, 2 Feb 2019 10:21:01 -0500 Subject: [PATCH 1/2] moving most of the code into a framework --- Package.swift | 28 ++++++++-- Sources/RayGun/Exports.swift | 11 ++++ .../Extensions/URL+Extensions.swift | 22 ++++---- Sources/RayGun/Lint Rules/LintRule.swift | 56 +++++++++++++++++++ .../Lint Rules/Linter.swift | 33 +++++------ .../MissingLocalizationLintRule.swift | 10 ++-- .../MissingPlaceholderLintRule.swift | 4 +- .../OrphanedLocalizationLintRule.swift | 4 +- .../Reporters/Reporter.swift | 2 +- .../Cache/CachedStringsTable.swift | 4 +- .../Strings Table/Match.swift | 6 +- .../Strings Table/PlaceholderType.swift | 0 .../StringsTable+DictEntry.swift | 7 ++- .../Strings Table/StringsTable+Entry.swift | 6 +- .../Strings Table/StringsTable.swift | 34 +++++------ .../Strings Table/StringsTableLoader.swift | 22 +++++--- .../Utilities/OrderedSet.swift | 56 +++++++++---------- Sources/stringray/Commands/CopyCommand.swift | 2 +- Sources/stringray/Commands/LintCommand.swift | 15 ++--- Sources/stringray/Commands/MoveCommand.swift | 2 +- .../stringray/Commands/RenameCommand.swift | 2 +- Sources/stringray/Commands/SortCommand.swift | 2 +- Sources/stringray/Lint Rules/LintRule.swift | 56 ------------------- .../stringray/Reporters/ConsoleReporter.swift | 5 +- .../stringray/Reporters/XcodeReporter.swift | 1 + 25 files changed, 212 insertions(+), 178 deletions(-) create mode 100644 Sources/RayGun/Exports.swift rename Sources/{stringray => RayGun}/Extensions/URL+Extensions.swift (79%) create mode 100644 Sources/RayGun/Lint Rules/LintRule.swift rename Sources/{stringray => RayGun}/Lint Rules/Linter.swift (75%) rename Sources/{stringray => RayGun}/Lint Rules/MissingLocalizationLintRule.swift (73%) rename Sources/{stringray => RayGun}/Lint Rules/MissingPlaceholderLintRule.swift (82%) rename Sources/{stringray => RayGun}/Lint Rules/OrphanedLocalizationLintRule.swift (80%) rename Sources/{stringray => RayGun}/Reporters/Reporter.swift (89%) rename Sources/{stringray => RayGun}/Strings Table/Cache/CachedStringsTable.swift (96%) rename Sources/{stringray => RayGun}/Strings Table/Match.swift (82%) rename Sources/{stringray => RayGun}/Strings Table/PlaceholderType.swift (100%) rename Sources/{stringray => RayGun}/Strings Table/StringsTable+DictEntry.swift (95%) rename Sources/{stringray => RayGun}/Strings Table/StringsTable+Entry.swift (82%) rename Sources/{stringray => RayGun}/Strings Table/StringsTable.swift (81%) rename Sources/{stringray => RayGun}/Strings Table/StringsTableLoader.swift (92%) rename Sources/{stringray => RayGun}/Utilities/OrderedSet.swift (64%) delete mode 100644 Sources/stringray/Lint Rules/LintRule.swift diff --git a/Package.swift b/Package.swift index b82ab0a..76b4f38 100644 --- a/Package.swift +++ b/Package.swift @@ -3,24 +3,42 @@ import PackageDescription let package = Package( name: "stringray", + products: [ + .executable( + name: "stringray", + targets: ["stringray"] + ), + .library( + name: "RayGun", + targets: ["RayGun"] + ) + ], dependencies: [ .package(url: "https://github.com/jpsim/Yams.git", from: "1.0.1"), .package(url: "https://github.com/scottrhoyt/SwiftyTextTable.git", from: "0.5.0"), .package(url: "https://github.com/g-Off/XcodeProject.git", from: "0.4.0"), - .package(url: "https://github.com/g-Off/CommandRegistry.git", .branch("master")) + .package(url: "https://github.com/g-Off/CommandRegistry.git", .branch("master")), + .package(url: "https://github.com/apple/swift-package-manager.git", from: "0.3.0") ], targets: [ .target( name: "stringray", dependencies: [ + "CommandRegistry", + "RayGun", + "SwiftyTextTable" + ] + ), + .target( + name: "RayGun", + dependencies: [ + "Utility", "Yams", - "SwiftyTextTable", - "XcodeProject", - "CommandRegistry" + "XcodeProject" ] ), .testTarget( name: "stringrayTests", - dependencies: ["stringray"]), + dependencies: ["RayGun"]), ] ) diff --git a/Sources/RayGun/Exports.swift b/Sources/RayGun/Exports.swift new file mode 100644 index 0000000..812f4b4 --- /dev/null +++ b/Sources/RayGun/Exports.swift @@ -0,0 +1,11 @@ +// +// Exports.swift +// RayGun +// +// Created by Geoffrey Foster on 2019-02-01. +// + +@_exported import Basic +@_exported import SwiftyTextTable +@_exported import Utility +@_exported import XcodeProject diff --git a/Sources/stringray/Extensions/URL+Extensions.swift b/Sources/RayGun/Extensions/URL+Extensions.swift similarity index 79% rename from Sources/stringray/Extensions/URL+Extensions.swift rename to Sources/RayGun/Extensions/URL+Extensions.swift index 6011c45..856e271 100644 --- a/Sources/stringray/Extensions/URL+Extensions.swift +++ b/Sources/RayGun/Extensions/URL+Extensions.swift @@ -7,8 +7,8 @@ import Foundation -extension URL { - var tableName: String? { +extension Foundation.URL { + public var tableName: String? { var url = self if ["strings", "stringsdict"].contains(url.pathExtension) { url.deletePathExtension() @@ -17,7 +17,7 @@ extension URL { return nil } - var locale: Locale? { + public var locale: Locale? { var url = self if ["strings", "stringsdict"].contains(url.pathExtension) { url.deleteLastPathComponent() @@ -29,7 +29,7 @@ extension URL { return nil } - var resourceDirectory: URL { + public var resourceDirectory: Foundation.URL { var dir = self if dir.pathExtension == "strings" || dir.pathExtension == "stringsdict" { dir.deleteLastPathComponent() @@ -40,22 +40,22 @@ extension URL { return dir } - var lprojURLs: [URL] { + var lprojURLs: [Foundation.URL] { let directories = try? FileManager.default.contentsOfDirectory(at: self, includingPropertiesForKeys: nil, options: []).filter { (url) -> Bool in return url.pathExtension == "lproj" } return directories ?? [] } - func stringsFiles(tableName: String) -> [URL] { + func stringsFiles(tableName: String) -> [Foundation.URL] { return files(tableName: tableName, ext: "strings") } - func stringsDictFiles(tableName: String) -> [URL] { + func stringsDictFiles(tableName: String) -> [Foundation.URL] { return files(tableName: tableName, ext: "stringsdict") } - private func files(tableName: String, ext: String) -> [URL] { + private func files(tableName: String, ext: String) -> [Foundation.URL] { return lprojURLs.compactMap { (lprojURL) in let url = lprojURL.appendingPathComponent(tableName).appendingPathExtension(ext) guard let reachable = try? url.checkResourceIsReachable(), reachable == true else { return nil } @@ -63,15 +63,15 @@ extension URL { } } - func stringsURL(tableName: String, locale: Locale) throws -> URL { + func stringsURL(tableName: String, locale: Locale) throws -> Foundation.URL { return try fileURL(tableName: tableName, locale: locale, ext: "strings", create: true) } - func stringsDictURL(tableName: String, locale: Locale) throws -> URL { + func stringsDictURL(tableName: String, locale: Locale) throws -> Foundation.URL { return try fileURL(tableName: tableName, locale: locale, ext: "stringsdict", create: true) } - private func fileURL(tableName: String, locale: Locale, ext: String, create: Bool) throws -> URL { + private func fileURL(tableName: String, locale: Locale, ext: String, create: Bool) throws -> Foundation.URL { let lprojURL = appendingPathComponent("\(locale.identifier).lproj", isDirectory: true) if create { try FileManager.default.createDirectory(at: lprojURL, withIntermediateDirectories: true, attributes: nil) diff --git a/Sources/RayGun/Lint Rules/LintRule.swift b/Sources/RayGun/Lint Rules/LintRule.swift new file mode 100644 index 0000000..e408c13 --- /dev/null +++ b/Sources/RayGun/Lint Rules/LintRule.swift @@ -0,0 +1,56 @@ +// +// LintRule.swift +// stringray +// +// Created by Geoffrey Foster on 2018-11-07. +// + +import Foundation + +public protocol LintRule { + var info: RuleInfo { get } + func scan(table: StringsTable, url: Foundation.URL, config: Linter.Config.Rule?) throws -> [LintRuleViolation] +} + +public struct RuleInfo { + public let identifier: String + public let name: String + public let description: String + public let severity: Severity +} + +public enum Severity: String, CustomStringConvertible, Decodable { + case warning + case error + + public var description: String { + return rawValue + } +} + +public struct LintRuleViolation { + public struct Location: CustomStringConvertible { + public let file: Foundation.URL + public let line: Int? + + public var description: String { + var path = file.lastPathComponent + if let line = line { + path.append(":\(line)") + } + return path + } + } + + public let locale: Locale + public let location: Location + public let severity: Severity + public let reason: String + + public init(locale: Locale, location: Location, severity: Severity, reason: String) { + self.locale = locale + self.location = location + self.severity = severity + self.reason = reason + } +} diff --git a/Sources/stringray/Lint Rules/Linter.swift b/Sources/RayGun/Lint Rules/Linter.swift similarity index 75% rename from Sources/stringray/Lint Rules/Linter.swift rename to Sources/RayGun/Lint Rules/Linter.swift index 99688d8..24779dc 100644 --- a/Sources/stringray/Lint Rules/Linter.swift +++ b/Sources/RayGun/Lint Rules/Linter.swift @@ -8,9 +8,9 @@ import Foundation import Yams -struct Linter { - struct Config: Decodable { - struct Rule: Decodable { +public struct Linter { + public struct Config: Decodable { + public struct Rule: Decodable { let severity: Severity } let included: [String] @@ -23,18 +23,18 @@ struct Linter { case rules } - init() { + public init() { self.included = [] self.excluded = [] self.rules = [:] } - init(url: URL) throws { + public init(url: Foundation.URL) throws { let string = try String(contentsOf: url, encoding: .utf8) self = try YAMLDecoder().decode(Config.self, from: string, userInfo: [:]) } - init(from decoder: Decoder) throws { + public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) self.included = try container.decodeIfPresent([String].self, forKey: .included) ?? [] self.excluded = try container.decodeIfPresent([String].self, forKey: .excluded) ?? [] @@ -42,38 +42,39 @@ struct Linter { } } - static let fileName = ".stringray.yml" + public static let fileName = ".stringray.yml" - static let allRules: [LintRule] = [ + public static let allRules: [LintRule] = [ MissingLocalizationLintRule(), OrphanedLocalizationLintRule(), MissingPlaceholderLintRule() ] - struct Error: LocalizedError { - var violations: [LintRuleViolation] - init(_ violations: [LintRuleViolation]) { + public struct Error: LocalizedError { + public private(set) var violations: [LintRuleViolation] + + public init(_ violations: [LintRuleViolation]) { self.violations = violations } - var errorDescription: String? { + public var errorDescription: String? { let errorCount = violations.filter { $0.severity == .error }.count let warningCount = violations.filter { $0.severity == .warning }.count return "Encountered \(errorCount) errors and \(warningCount) warnings." } } - let rules: [LintRule] + public let rules: [LintRule] private let reporter: Reporter private let config: Config - init(rules: [LintRule] = Linter.allRules, reporter: Reporter = ConsoleReporter(), config: Config = Config()) { + public init(rules: [LintRule] = Linter.allRules, reporter: Reporter, config: Config = Config()) { self.rules = rules self.reporter = reporter self.config = config } - private func run(on table: StringsTable, url: URL) throws -> [LintRuleViolation] { + private func run(on table: StringsTable, url: Foundation.URL) throws -> [LintRuleViolation] { var runnableRules = self.rules let includedRules = Set(config.included) @@ -93,7 +94,7 @@ struct Linter { } } - func report(on table: StringsTable, url: URL) throws { + public func report(on table: StringsTable, url: Foundation.URL) throws { let violations = try run(on: table, url: url) var outputStream = LinterOutputStream(fileHandle: FileHandle.standardOutput) reporter.generateReport(for: violations, to: &outputStream) diff --git a/Sources/stringray/Lint Rules/MissingLocalizationLintRule.swift b/Sources/RayGun/Lint Rules/MissingLocalizationLintRule.swift similarity index 73% rename from Sources/stringray/Lint Rules/MissingLocalizationLintRule.swift rename to Sources/RayGun/Lint Rules/MissingLocalizationLintRule.swift index 25d9ebc..066625a 100644 --- a/Sources/stringray/Lint Rules/MissingLocalizationLintRule.swift +++ b/Sources/RayGun/Lint Rules/MissingLocalizationLintRule.swift @@ -10,11 +10,11 @@ import Foundation struct MissingLocalizationLintRule: LintRule { let info: RuleInfo = RuleInfo(identifier: "missing_localization", name: "Missing Localization", description: "", severity: .warning) - func scan(table: StringsTable, url: URL, config: Linter.Config.Rule?) throws -> [LintRuleViolation] { + func scan(table: StringsTable, url: Foundation.URL, config: Linter.Config.Rule?) throws -> [LintRuleViolation] { return scanEntries(table: table, url: url, config: config) + scanDictEntries(table: table, url: url, config: config) } - private func scanEntries(table: StringsTable, url: URL, config: Linter.Config.Rule?) -> [LintRuleViolation] { + private func scanEntries(table: StringsTable, url: Foundation.URL, config: Linter.Config.Rule?) -> [LintRuleViolation] { var violations: [LintRuleViolation] = [] var entries = table.entries entries.removeValue(forKey: table.base) @@ -22,7 +22,7 @@ struct MissingLocalizationLintRule: LintRule { for entry in entries { let missingEntries = baseEntries.subtracting(entry.value) for missingEntry in missingEntries { - let file = URL(fileURLWithPath: "\(entry.key.identifier).lproj/\(table.name).strings", relativeTo: url) + let file = Foundation.URL(fileURLWithPath: "\(entry.key.identifier).lproj/\(table.name).strings", relativeTo: url) let location = LintRuleViolation.Location(file: file, line: nil) let reason = "Missing \(missingEntry.key)" let violation = LintRuleViolation(locale: entry.key, location: location, severity: config?.severity ?? info.severity, reason: reason) @@ -32,7 +32,7 @@ struct MissingLocalizationLintRule: LintRule { return violations } - private func scanDictEntries(table: StringsTable, url: URL, config: Linter.Config.Rule?) -> [LintRuleViolation] { + private func scanDictEntries(table: StringsTable, url: Foundation.URL, config: Linter.Config.Rule?) -> [LintRuleViolation] { var violations: [LintRuleViolation] = [] var dictEntries = table.dictEntries dictEntries.removeValue(forKey: table.base) @@ -40,7 +40,7 @@ struct MissingLocalizationLintRule: LintRule { for dictEntry in dictEntries { let missingDictEntries = baseDictEntries.filter { !dictEntry.value.keys.contains($0.key) } for missingDictEntry in missingDictEntries { - let file = URL(fileURLWithPath: "\(dictEntry.key.identifier).lproj/\(table.name).stringsdict", relativeTo: url) + let file = Foundation.URL(fileURLWithPath: "\(dictEntry.key.identifier).lproj/\(table.name).stringsdict", relativeTo: url) let location = LintRuleViolation.Location(file: file, line: nil) let reason = "Missing \(missingDictEntry.key)" let violation = LintRuleViolation(locale: dictEntry.key, location: location, severity: config?.severity ?? info.severity, reason: reason) diff --git a/Sources/stringray/Lint Rules/MissingPlaceholderLintRule.swift b/Sources/RayGun/Lint Rules/MissingPlaceholderLintRule.swift similarity index 82% rename from Sources/stringray/Lint Rules/MissingPlaceholderLintRule.swift rename to Sources/RayGun/Lint Rules/MissingPlaceholderLintRule.swift index abf9625..b03de00 100644 --- a/Sources/stringray/Lint Rules/MissingPlaceholderLintRule.swift +++ b/Sources/RayGun/Lint Rules/MissingPlaceholderLintRule.swift @@ -10,7 +10,7 @@ import Foundation struct MissingPlaceholderLintRule: LintRule { let info: RuleInfo = RuleInfo(identifier: "missing_placeholder", name: "Missing Placeholder", description: "", severity: .error) - func scan(table: StringsTable, url: URL, config: Linter.Config.Rule?) throws -> [LintRuleViolation] { + func scan(table: StringsTable, url: Foundation.URL, config: Linter.Config.Rule?) throws -> [LintRuleViolation] { var violations: [LintRuleViolation] = [] var placeholders: [String: [PlaceholderType]] = [:] try table.baseEntries.forEach { @@ -20,7 +20,7 @@ struct MissingPlaceholderLintRule: LintRule { try entry.value.forEach { let placeholder = try PlaceholderType.orderedPlaceholders(from: $0.value) if let basePlaceholder = placeholders[$0.key], placeholder != basePlaceholder { - let file = URL(fileURLWithPath: "\(entry.key.identifier).lproj/\(table.name).strings", relativeTo: url) + let file = Foundation.URL(fileURLWithPath: "\(entry.key.identifier).lproj/\(table.name).strings", relativeTo: url) let line = $0.location?.line let location = LintRuleViolation.Location(file: file, line: line) let reason = "Mismatched placeholders \($0.key)" diff --git a/Sources/stringray/Lint Rules/OrphanedLocalizationLintRule.swift b/Sources/RayGun/Lint Rules/OrphanedLocalizationLintRule.swift similarity index 80% rename from Sources/stringray/Lint Rules/OrphanedLocalizationLintRule.swift rename to Sources/RayGun/Lint Rules/OrphanedLocalizationLintRule.swift index 990acaf..5137215 100644 --- a/Sources/stringray/Lint Rules/OrphanedLocalizationLintRule.swift +++ b/Sources/RayGun/Lint Rules/OrphanedLocalizationLintRule.swift @@ -10,7 +10,7 @@ import Foundation struct OrphanedLocalizationLintRule: LintRule { let info: RuleInfo = RuleInfo(identifier: "orphaned_localization", name: "Orphaned Localization", description: "", severity: .warning) - func scan(table: StringsTable, url: URL, config: Linter.Config.Rule?) throws -> [LintRuleViolation] { + func scan(table: StringsTable, url: Foundation.URL, config: Linter.Config.Rule?) throws -> [LintRuleViolation] { var violations: [LintRuleViolation] = [] var entries = table.entries entries.removeValue(forKey: table.base) @@ -18,7 +18,7 @@ struct OrphanedLocalizationLintRule: LintRule { for entry in entries { let orphanedEntries = entry.value.subtracting(baseEntries) for orphanedEntry in orphanedEntries { - let file = URL(fileURLWithPath: "\(entry.key.identifier).lproj/\(table.name).strings", relativeTo: url) + let file = Foundation.URL(fileURLWithPath: "\(entry.key.identifier).lproj/\(table.name).strings", relativeTo: url) guard let line = orphanedEntry.location?.line else { continue } let location = LintRuleViolation.Location(file: file, line: line) let reason = "Orphaned \(orphanedEntry.key)" diff --git a/Sources/stringray/Reporters/Reporter.swift b/Sources/RayGun/Reporters/Reporter.swift similarity index 89% rename from Sources/stringray/Reporters/Reporter.swift rename to Sources/RayGun/Reporters/Reporter.swift index 6ae815e..4fff136 100644 --- a/Sources/stringray/Reporters/Reporter.swift +++ b/Sources/RayGun/Reporters/Reporter.swift @@ -7,6 +7,6 @@ import Foundation -protocol Reporter { +public protocol Reporter { func generateReport(for violations: [LintRuleViolation], to outputStream: inout Target) } diff --git a/Sources/stringray/Strings Table/Cache/CachedStringsTable.swift b/Sources/RayGun/Strings Table/Cache/CachedStringsTable.swift similarity index 96% rename from Sources/stringray/Strings Table/Cache/CachedStringsTable.swift rename to Sources/RayGun/Strings Table/Cache/CachedStringsTable.swift index 1aa8c85..0e00398 100644 --- a/Sources/stringray/Strings Table/Cache/CachedStringsTable.swift +++ b/Sources/RayGun/Strings Table/Cache/CachedStringsTable.swift @@ -29,9 +29,9 @@ struct CachedStringsTable: Codable { return stringsTable.dictEntries[locale] } - func isCacheValid(for locale: Locale, type: LocalizationType, base: URL) -> Bool { + func isCacheValid(for locale: Locale, type: LocalizationType, base: Foundation.URL) -> Bool { do { - let fileURL: URL + let fileURL: Foundation.URL switch type { case .strings: fileURL = try base.stringsURL(tableName: stringsTable.name, locale: locale) diff --git a/Sources/stringray/Strings Table/Match.swift b/Sources/RayGun/Strings Table/Match.swift similarity index 82% rename from Sources/stringray/Strings Table/Match.swift rename to Sources/RayGun/Strings Table/Match.swift index 0a2ddbb..1b702ea 100644 --- a/Sources/stringray/Strings Table/Match.swift +++ b/Sources/RayGun/Strings Table/Match.swift @@ -7,11 +7,11 @@ import Foundation -enum Match { +public enum Match { case prefix(String) case regex(NSRegularExpression) - func matches(key: String) -> Bool { + public func matches(key: String) -> Bool { switch self { case .prefix(let prefix): return key.hasPrefix(prefix) @@ -20,7 +20,7 @@ enum Match { } } - func replacing(with newPrefix: String, in string: String) -> String? { + public 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 } diff --git a/Sources/stringray/Strings Table/PlaceholderType.swift b/Sources/RayGun/Strings Table/PlaceholderType.swift similarity index 100% rename from Sources/stringray/Strings Table/PlaceholderType.swift rename to Sources/RayGun/Strings Table/PlaceholderType.swift diff --git a/Sources/stringray/Strings Table/StringsTable+DictEntry.swift b/Sources/RayGun/Strings Table/StringsTable+DictEntry.swift similarity index 95% rename from Sources/stringray/Strings Table/StringsTable+DictEntry.swift rename to Sources/RayGun/Strings Table/StringsTable+DictEntry.swift index d21e5dd..c7b6092 100644 --- a/Sources/stringray/Strings Table/StringsTable+DictEntry.swift +++ b/Sources/RayGun/Strings Table/StringsTable+DictEntry.swift @@ -8,7 +8,7 @@ import Foundation extension StringsTable { - struct DictEntry: Codable, Hashable { + public struct DictEntry: Codable, Hashable { private struct _DictKey: CodingKey, Equatable { var stringValue: String var intValue: Int? @@ -28,6 +28,7 @@ extension StringsTable { return _DictKey(stringValue: key)! } } + struct PluralizationRule: Codable, Hashable { private enum CodingKeys: String, CodingKey { case zero, one, two, few, many, other @@ -73,7 +74,7 @@ extension StringsTable { let formatKey: String let pluralizations: [String: PluralizationRule] - init(from decoder: Decoder) throws { + public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: _DictKey.self) self.formatKey = try container.decode(String.self, forKey: _DictKey.localizedFormatKey) let allKeys = container.allKeys.filter { @@ -86,7 +87,7 @@ extension StringsTable { self.pluralizations = Dictionary(elements, uniquingKeysWith: { (lhs, _) in return lhs }) } - func encode(to encoder: Encoder) throws { + public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: _DictKey.self) try container.encode(formatKey, forKey: _DictKey.localizedFormatKey) try pluralizations.forEach { diff --git a/Sources/stringray/Strings Table/StringsTable+Entry.swift b/Sources/RayGun/Strings Table/StringsTable+Entry.swift similarity index 82% rename from Sources/stringray/Strings Table/StringsTable+Entry.swift rename to Sources/RayGun/Strings Table/StringsTable+Entry.swift index f9ec1b6..221824a 100644 --- a/Sources/stringray/Strings Table/StringsTable+Entry.swift +++ b/Sources/RayGun/Strings Table/StringsTable+Entry.swift @@ -8,8 +8,8 @@ import Foundation extension StringsTable { - struct Entry: Codable, Hashable, CustomStringConvertible { - struct Location: Codable { + public struct Entry: Codable, Hashable, CustomStringConvertible { + public struct Location: Codable { var comment: Int? = nil var line: Int = NSNotFound } @@ -19,7 +19,7 @@ extension StringsTable { var key: String = "" var value: String = "" - var description: String { + public var description: String { var string = "" if let comment = comment { string.append("/* \(comment) */\n") diff --git a/Sources/stringray/Strings Table/StringsTable.swift b/Sources/RayGun/Strings Table/StringsTable.swift similarity index 81% rename from Sources/stringray/Strings Table/StringsTable.swift rename to Sources/RayGun/Strings Table/StringsTable.swift index 72f46b1..b86c348 100644 --- a/Sources/stringray/Strings Table/StringsTable.swift +++ b/Sources/RayGun/Strings Table/StringsTable.swift @@ -8,9 +8,9 @@ import Foundation -struct StringsTable: Codable { - typealias EntriesType = [Locale: OrderedSet] - typealias DictEntriesType = [Locale: [String: DictEntry]] +public struct StringsTable: Codable { + public typealias EntriesType = [Locale: OrderedSet] + public typealias DictEntriesType = [Locale: [String: DictEntry]] private enum CodingKeys: String, CodingKey { case name @@ -19,10 +19,10 @@ struct StringsTable: Codable { case dictEntries } - let name: String - let base: Locale - private(set) var entries: EntriesType = [:] - private(set) var dictEntries: DictEntriesType = [:] + public let name: String + public let base: Locale + public private(set) var entries: EntriesType = [:] + public private(set) var dictEntries: DictEntriesType = [:] private var allLanguageKeys: Set { var keys: Set = [] @@ -31,21 +31,21 @@ struct StringsTable: Codable { return keys } - var baseEntries: OrderedSet { + public var baseEntries: OrderedSet { return entries[base] ?? [] } - var localizedEntries: EntriesType { + public var localizedEntries: EntriesType { var localizedEntries = entries localizedEntries.removeValue(forKey: base) return localizedEntries } - var baseDictEntries: [String: DictEntry] { + public var baseDictEntries: [String: DictEntry] { return dictEntries[base] ?? [:] } - init(name: String, base: Locale, entries: EntriesType, dictEntries: DictEntriesType) { + public init(name: String, base: Locale, entries: EntriesType, dictEntries: DictEntriesType) { self.name = name self.base = base self.entries = entries @@ -59,7 +59,7 @@ struct StringsTable: Codable { return OrderedSet(matchingEntries) } - func withKeys(matching: [Match]) -> StringsTable { + public func withKeys(matching: [Match]) -> StringsTable { var filteredEntries: EntriesType = [:] var filteredDictEntries: DictEntriesType = [:] @@ -81,7 +81,7 @@ struct StringsTable: Codable { return table } - mutating func addEntries(from table: StringsTable) { + public mutating func addEntries(from table: StringsTable) { for (languageId, languageEntries) in table.entries { entries[languageId, default: []].formUnion(languageEntries) } @@ -93,7 +93,7 @@ struct StringsTable: Codable { } } - mutating func removeEntries(from table: StringsTable) { + public mutating func removeEntries(from table: StringsTable) { for (languageId, languageEntries) in table.entries { entries[languageId]?.subtract(languageEntries) } @@ -105,7 +105,7 @@ struct StringsTable: Codable { } } - mutating func sort() { + public mutating func sort() { for (languageId, languageEntries) in entries { var sortedLanguageEntries = languageEntries sortedLanguageEntries.sort { (lhs, rhs) -> Bool in @@ -115,7 +115,7 @@ struct StringsTable: Codable { } } - mutating func remove(keys: Set) { + public mutating func remove(keys: Set) { for (locale, entry) in entries { let filtered = entry.filter { return !keys.contains($0.key) @@ -134,7 +134,7 @@ struct StringsTable: Codable { dictEntries[locale]?[otherKey] = entry } - mutating func replace(matches: [Match], replacements replacementStrings: [String]) { + public mutating func replace(matches: [Match], replacements replacementStrings: [String]) { for (match, replacement) in zip(matches, replacementStrings) { for localizedEntries in entriesMatching(match) { localizedEntries.value.forEach { diff --git a/Sources/stringray/Strings Table/StringsTableLoader.swift b/Sources/RayGun/Strings Table/StringsTableLoader.swift similarity index 92% rename from Sources/stringray/Strings Table/StringsTableLoader.swift rename to Sources/RayGun/Strings Table/StringsTableLoader.swift index cdfdeb1..e82c2a8 100644 --- a/Sources/stringray/Strings Table/StringsTableLoader.swift +++ b/Sources/RayGun/Strings Table/StringsTableLoader.swift @@ -7,7 +7,7 @@ import Foundation -struct StringsTableLoader { +public struct StringsTableLoader { private enum Error: String, Swift.Error, LocalizedError { case invalidURL @@ -19,7 +19,7 @@ struct StringsTableLoader { } } - struct Options: OptionSet { + public struct Options: OptionSet { public private(set) var rawValue: UInt public init(rawValue: UInt) { self.rawValue = rawValue } @@ -27,9 +27,13 @@ struct StringsTableLoader { public static let ignoreCached = Options(rawValue: 1 << 1) } - var options: Options = [] + public var options: Options = [] - func load(url: URL) throws -> StringsTable { + public init() { + + } + + public func load(url: Foundation.URL) throws -> StringsTable { let resourceDirectory = url.resourceDirectory guard let name = url.tableName, let base = url.locale else { throw Error.invalidURL @@ -37,7 +41,7 @@ struct StringsTableLoader { return try self.load(url: resourceDirectory, name: name, base: base) } - func load(url: URL, name: String, base: Locale) throws -> StringsTable { + public func load(url: Foundation.URL, name: String, base: Locale) throws -> StringsTable { var entries: StringsTable.EntriesType = [:] var dictEntries: StringsTable.DictEntriesType = [:] @@ -68,7 +72,7 @@ struct StringsTableLoader { return StringsTable(name: name, base: base, entries: entries, dictEntries: dictEntries) } - func write(to url: Foundation.URL, table: StringsTable) throws { + public func write(to url: Foundation.URL, table: StringsTable) throws { for (languageId, languageEntries) in table.entries where !languageEntries.isEmpty { let fileURL = try url.stringsURL(tableName: table.name, locale: languageId) guard let outputStream = OutputStream(url: fileURL, append: false) else { continue } @@ -93,7 +97,7 @@ struct StringsTableLoader { } } - func writeCache(table: StringsTable, baseURL: URL) throws { + public func writeCache(table: StringsTable, baseURL: Foundation.URL) throws { var cacheKeys: [String: Date] = [:] for (languageId, languageEntries) in table.entries where !languageEntries.isEmpty { @@ -130,7 +134,7 @@ struct StringsTableLoader { return URL(fileURLWithPath: filePath, relativeTo: cacheURL) } - private func load(from url: URL, options: Options) throws -> OrderedSet { + private func load(from url: Foundation.URL, options: Options) throws -> OrderedSet { func lineNumber(scanLocation: Int, newlineLocations: [Int]) -> Int { var lastIndex = 0 for (index, newlineLocation) in newlineLocations.enumerated() { @@ -190,7 +194,7 @@ struct StringsTableLoader { return OrderedSet(entries) } - private func load(from url: URL) throws -> [String: StringsTable.DictEntry] { + private func load(from url: Foundation.URL) throws -> [String: StringsTable.DictEntry] { let data = try Data(contentsOf: url) let decoder = PropertyListDecoder() return try decoder.decode([String: StringsTable.DictEntry].self, from: data) diff --git a/Sources/stringray/Utilities/OrderedSet.swift b/Sources/RayGun/Utilities/OrderedSet.swift similarity index 64% rename from Sources/stringray/Utilities/OrderedSet.swift rename to Sources/RayGun/Utilities/OrderedSet.swift index 3f76cef..93beb1e 100644 --- a/Sources/stringray/Utilities/OrderedSet.swift +++ b/Sources/RayGun/Utilities/OrderedSet.swift @@ -7,48 +7,48 @@ import Foundation -struct OrderedSet: Hashable, ExpressibleByArrayLiteral { - typealias ArrayLiteralElement = Element - typealias Index = Int +public struct OrderedSet: Hashable, ExpressibleByArrayLiteral { + public typealias ArrayLiteralElement = Element + public typealias Index = Int private var orderedStorage: [Element] = [] private var storage: Set = [] - init(arrayLiteral elements: OrderedSet.ArrayLiteralElement...) { + public init(arrayLiteral elements: OrderedSet.ArrayLiteralElement...) { self.init(array: elements) } - init(_ array: [Element]) { + public init(_ array: [Element]) { self.init(array: array) } - init(array: [Element]) { + public init(array: [Element]) { for element in array { append(element) } } - init() { + public init() { orderedStorage = [] storage = [] } - func contains(_ element: Element) -> Bool { + public func contains(_ element: Element) -> Bool { return storage.contains(element) } - func contains(where predicate: (Element) throws -> Bool) rethrows -> Bool { + public func contains(where predicate: (Element) throws -> Bool) rethrows -> Bool { return try storage.contains(where: predicate) } - var isEmpty: Bool { + public var isEmpty: Bool { return storage.isEmpty } } extension OrderedSet: SetAlgebra { @discardableResult - mutating func insert(_ newMember: Element) -> (inserted: Bool, memberAfterInsert: Element) { + public mutating func insert(_ newMember: Element) -> (inserted: Bool, memberAfterInsert: Element) { let result = storage.insert(newMember) if result.inserted { orderedStorage.append(newMember) @@ -57,7 +57,7 @@ extension OrderedSet: SetAlgebra { } @discardableResult - mutating func update(with newMember: Element) -> Element? { + public mutating func update(with newMember: Element) -> Element? { if contains(newMember), let index = orderedStorage.index(of: newMember) { orderedStorage[index] = newMember } @@ -69,44 +69,44 @@ extension OrderedSet: SetAlgebra { } @discardableResult - mutating func remove(_ member: Element) -> Element? { + public mutating func remove(_ member: Element) -> Element? { guard let index = orderedStorage.index(of: member) else { return nil } orderedStorage.remove(at: index) storage.remove(member) return member } - func union(_ other: OrderedSet) -> OrderedSet { + public func union(_ other: OrderedSet) -> OrderedSet { var newSet = self newSet.formUnion(other) return newSet } - mutating func formUnion(_ other: OrderedSet) { + public mutating func formUnion(_ other: OrderedSet) { for element in other { append(element) } } - func intersection(_ other: OrderedSet) -> OrderedSet { + public func intersection(_ other: OrderedSet) -> OrderedSet { var newSet = self newSet.formIntersection(other) return newSet } - mutating func formIntersection(_ other: OrderedSet) { + public mutating func formIntersection(_ other: OrderedSet) { for item in self where !other.contains(item) { remove(item) } } - func symmetricDifference(_ other: OrderedSet) -> OrderedSet { + public func symmetricDifference(_ other: OrderedSet) -> OrderedSet { var newSet = self newSet.formSymmetricDifference(other) return newSet } - mutating func formSymmetricDifference(_ other: OrderedSet) { + public mutating func formSymmetricDifference(_ other: OrderedSet) { for member in other { if contains(member) { remove(member) @@ -118,7 +118,7 @@ extension OrderedSet: SetAlgebra { } extension OrderedSet: Codable where Element: Codable { - init(from decoder: Decoder) throws { + public init(from decoder: Decoder) throws { var container = try decoder.unkeyedContainer() while !container.isAtEnd { let element = try container.decode(Element.self) @@ -126,7 +126,7 @@ extension OrderedSet: Codable where Element: Codable { } } - func encode(to encoder: Encoder) throws { + public func encode(to encoder: Encoder) throws { var container = encoder.unkeyedContainer() try container.encode(contentsOf: orderedStorage) } @@ -143,15 +143,15 @@ extension OrderedSet: Sequence { } extension OrderedSet: MutableCollection { - mutating func sort(by areInIncreasingOrder: (Element, Element) throws -> Bool) rethrows { + public mutating func sort(by areInIncreasingOrder: (Element, Element) throws -> Bool) rethrows { try orderedStorage.sort(by: areInIncreasingOrder) } - mutating func append(_ newElement: Element) { + public mutating func append(_ newElement: Element) { insert(newElement, at: endIndex) } - mutating func insert(_ newElement: Element, at index: Index) { + public mutating func insert(_ newElement: Element, at index: Index) { guard !contains(newElement) else { return } storage.insert(newElement) orderedStorage.insert(newElement, at: index) @@ -167,7 +167,7 @@ extension OrderedSet: MutableCollection { storage.insert(newMember) } - subscript(index: Index) -> Element { + public subscript(index: Index) -> Element { get { return orderedStorage[index] } @@ -180,15 +180,15 @@ extension OrderedSet: MutableCollection { } } - var startIndex: Index { + public var startIndex: Index { return orderedStorage.startIndex } - var endIndex: Index { + public var endIndex: Index { return orderedStorage.endIndex } - func index(after i: Index) -> Index { + public func index(after i: Index) -> Index { return i + 1 } } diff --git a/Sources/stringray/Commands/CopyCommand.swift b/Sources/stringray/Commands/CopyCommand.swift index 2c46355..1b6ed1b 100644 --- a/Sources/stringray/Commands/CopyCommand.swift +++ b/Sources/stringray/Commands/CopyCommand.swift @@ -6,7 +6,7 @@ // import Foundation -import Utility +import RayGun import CommandRegistry struct CopyCommand: Command { diff --git a/Sources/stringray/Commands/LintCommand.swift b/Sources/stringray/Commands/LintCommand.swift index 302bb8f..60c9359 100644 --- a/Sources/stringray/Commands/LintCommand.swift +++ b/Sources/stringray/Commands/LintCommand.swift @@ -6,10 +6,7 @@ // import Foundation -import Utility -import Basic -import SwiftyTextTable -import XcodeProject +import RayGun import CommandRegistry struct LintCommand: Command { @@ -135,7 +132,7 @@ struct LintCommand: Command { } private func listRules() { - let linter = Linter() + let linter = Linter(reporter: ConsoleReporter()) let rules = linter.rules let columns = [ TextTableColumn(header: "id"), @@ -159,7 +156,7 @@ struct LintCommand: Command { var loader = StringsTableLoader() loader.options = [.lineNumbers] let linter = Linter(reporter: reporter, config: config) - var allError = Linter.Error([]) + var violations: [LintRuleViolation] = [] try inputs.forEach { print("Linting: \($0.tableName)") @@ -168,11 +165,11 @@ struct LintCommand: Command { try linter.report(on: table, url: $0.resourceURL) try loader.writeCache(table: table, baseURL: $0.resourceURL) } catch let error as Linter.Error { - allError.violations.append(contentsOf: error.violations) + violations.append(contentsOf: error.violations) } } - if !allError.violations.isEmpty { - throw allError + if !violations.isEmpty { + throw Linter.Error(violations) } } } diff --git a/Sources/stringray/Commands/MoveCommand.swift b/Sources/stringray/Commands/MoveCommand.swift index 7119996..c92f945 100644 --- a/Sources/stringray/Commands/MoveCommand.swift +++ b/Sources/stringray/Commands/MoveCommand.swift @@ -6,8 +6,8 @@ // import Foundation -import Utility import CommandRegistry +import RayGun struct MoveCommand: Command { private struct Arguments { diff --git a/Sources/stringray/Commands/RenameCommand.swift b/Sources/stringray/Commands/RenameCommand.swift index 164bb0a..04dac71 100644 --- a/Sources/stringray/Commands/RenameCommand.swift +++ b/Sources/stringray/Commands/RenameCommand.swift @@ -6,7 +6,7 @@ // import Foundation -import Utility +import RayGun import CommandRegistry struct RenameCommand: Command { diff --git a/Sources/stringray/Commands/SortCommand.swift b/Sources/stringray/Commands/SortCommand.swift index 42899b6..ad08ec9 100644 --- a/Sources/stringray/Commands/SortCommand.swift +++ b/Sources/stringray/Commands/SortCommand.swift @@ -6,7 +6,7 @@ // import Foundation -import Utility +import RayGun import CommandRegistry struct SortCommand: Command { diff --git a/Sources/stringray/Lint Rules/LintRule.swift b/Sources/stringray/Lint Rules/LintRule.swift deleted file mode 100644 index 5f60025..0000000 --- a/Sources/stringray/Lint Rules/LintRule.swift +++ /dev/null @@ -1,56 +0,0 @@ -// -// LintRule.swift -// stringray -// -// Created by Geoffrey Foster on 2018-11-07. -// - -import Foundation - -protocol LintRule { - var info: RuleInfo { get } - func scan(table: StringsTable, url: URL, config: Linter.Config.Rule?) throws -> [LintRuleViolation] -} - -struct RuleInfo { - let identifier: String - let name: String - let description: String - let severity: Severity -} - -enum Severity: String, CustomStringConvertible, Decodable { - case warning - case error - - var description: String { - return rawValue - } -} - -struct LintRuleViolation { - struct Location: CustomStringConvertible { - let file: URL - let line: Int? - - var description: String { - var path = file.lastPathComponent - if let line = line { - path.append(":\(line)") - } - return path - } - } - - let locale: Locale - let location: Location - let severity: Severity - let reason: String - - public init(locale: Locale, location: Location, severity: Severity, reason: String) { - self.locale = locale - self.location = location - self.severity = severity - self.reason = reason - } -} diff --git a/Sources/stringray/Reporters/ConsoleReporter.swift b/Sources/stringray/Reporters/ConsoleReporter.swift index 5072830..bb4b022 100644 --- a/Sources/stringray/Reporters/ConsoleReporter.swift +++ b/Sources/stringray/Reporters/ConsoleReporter.swift @@ -6,6 +6,7 @@ // import Foundation +import RayGun import SwiftyTextTable struct ConsoleReporter: Reporter { @@ -15,11 +16,11 @@ struct ConsoleReporter: Reporter { } extension LintRuleViolation: TextTableRepresentable { - static var columnHeaders: [String] { + public static var columnHeaders: [String] { return ["Locale", "Location", "Severity", "Reason"] } - var tableValues: [CustomStringConvertible] { + public var tableValues: [CustomStringConvertible] { return [locale.identifier, location, severity, reason] } } diff --git a/Sources/stringray/Reporters/XcodeReporter.swift b/Sources/stringray/Reporters/XcodeReporter.swift index ee5a4b8..197974c 100644 --- a/Sources/stringray/Reporters/XcodeReporter.swift +++ b/Sources/stringray/Reporters/XcodeReporter.swift @@ -6,6 +6,7 @@ // import Foundation +import RayGun struct XcodeReporter: Reporter { func generateReport(for violations: [LintRuleViolation], to outputStream: inout Target) { From fe55e69cff1636aa11953bb3b49fed40ee3d8792 Mon Sep 17 00:00:00 2001 From: Geoffrey Foster Date: Tue, 4 Jun 2019 16:44:21 -0400 Subject: [PATCH 2/2] swift 5, swift package manager 5 missing comment lint rule --- .gitignore | 1 + Dockerfile | 5 +++ Makefile | 44 ------------------- Overrides.xcconfig | 1 - Package.resolved | 12 ++--- Package.swift | 17 ++++--- Sources/RayGun/Exports.swift | 11 ----- Sources/RayGun/Lint Rules/Linter.swift | 9 +--- .../Lint Rules/MissingCommentLintRule.swift | 25 +++++++++++ .../RayGun/Strings Table/StringsTable.swift | 4 +- Sources/RayGun/Utilities/OrderedSet.swift | 4 +- Sources/stringray/Commands/CopyCommand.swift | 18 ++++---- Sources/stringray/Commands/LintCommand.swift | 6 ++- Sources/stringray/Commands/MoveCommand.swift | 2 + .../stringray/Commands/RenameCommand.swift | 4 +- Sources/stringray/Commands/SortCommand.swift | 12 ++--- Sources/stringray/Version.swift | 2 +- Sources/stringray/main.swift | 9 ++++ Tests/stringrayTests/LinterTests.swift | 36 +++++++++++++++ Version.swift.template | 5 --- 20 files changed, 126 insertions(+), 101 deletions(-) create mode 100644 Dockerfile delete mode 100644 Makefile delete mode 100644 Overrides.xcconfig delete mode 100644 Sources/RayGun/Exports.swift create mode 100644 Sources/RayGun/Lint Rules/MissingCommentLintRule.swift create mode 100644 Tests/stringrayTests/LinterTests.swift delete mode 100644 Version.swift.template diff --git a/.gitignore b/.gitignore index 02c0875..0c62eec 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ /.build /Packages /*.xcodeproj +.swiftpm diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..dfba428 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,5 @@ +FROM swift:4.2.1 +COPY Package.swift ./Package.swift +COPY Sources ./Sources +COPY Tests ./Tests +RUN swift test --configuration debug diff --git a/Makefile b/Makefile deleted file mode 100644 index 3eb263f..0000000 --- a/Makefile +++ /dev/null @@ -1,44 +0,0 @@ -TOOL_NAME = stringray -VERSION = 0.3.0 - -REPO = https://github.com/g-Off/$(TOOL_NAME) -RELEASE_TAR = $(REPO)/archive/$(VERSION).tar.gz -SHA = $(shell curl -L -s $(RELEASE_TAR) | shasum -a 256 | sed 's/ .*//') - -PREFIX = /usr/local -INSTALL_PATH = $(PREFIX)/bin/$(TOOL_NAME) -BUILD_PATH = $(shell swift build --show-bin-path -c $(CONFIGURATION))/$(TOOL_NAME) - -SWIFTC_FLAGS = -Xswiftc "-target" -Xswiftc "x86_64-apple-macosx10.13" -CONFIGURATION = debug - -debug: generate_version -debug: build - -generate_version: - @sed 's/__VERSION__/$(VERSION)/g' Version.swift.template > Sources/stringray/Version.swift - -release: - @echo $(SHA) - -build: - swift build --configuration $(CONFIGURATION) $(SWIFTC_FLAGS) - -install: CONFIGURATION = release -install: SWIFTC_FLAGS += --static-swift-stdlib --disable-sandbox -install: clean build - mkdir -p $(PREFIX)/bin - cp -f $(BUILD_PATH) $(INSTALL_PATH) - -test: - swift test $(SWIFTC_FLAGS) - -xcode: generate_version -xcode: - swift package generate-xcodeproj --xcconfig-overrides=Overrides.xcconfig - xed . - -clean: - swift package clean - -.PHONY: debug release build test xcode clean install diff --git a/Overrides.xcconfig b/Overrides.xcconfig deleted file mode 100644 index 1b57358..0000000 --- a/Overrides.xcconfig +++ /dev/null @@ -1 +0,0 @@ -MACOSX_DEPLOYMENT_TARGET = 10.13 diff --git a/Package.resolved b/Package.resolved index a0130cc..b1e5448 100644 --- a/Package.resolved +++ b/Package.resolved @@ -24,8 +24,8 @@ "repositoryURL": "https://github.com/scottrhoyt/SwiftyTextTable.git", "state": { "branch": null, - "revision": "7b8661865f0d9590a4b7c146237fecd99f3d8406", - "version": "0.8.2" + "revision": "c6df6cf533d120716bff38f8ff9885e1ce2a4ac3", + "version": "0.9.0" } }, { @@ -33,8 +33,8 @@ "repositoryURL": "https://github.com/g-Off/XcodeProject.git", "state": { "branch": null, - "revision": "a1e12a8659684039258b79761c65abb9ec98fc94", - "version": "0.4.0" + "revision": "f5095a860de4cd1f0e635957bed7c4b80392dac8", + "version": "0.5.0" } }, { @@ -42,8 +42,8 @@ "repositoryURL": "https://github.com/jpsim/Yams.git", "state": { "branch": null, - "revision": "26ab35f50ea891e8edefcc9d975db2f6b67e1d68", - "version": "1.0.1" + "revision": "b08dba4bcea978bf1ad37703a384097d3efce5af", + "version": "1.0.2" } } ] diff --git a/Package.swift b/Package.swift index 76b4f38..ad65b3b 100644 --- a/Package.swift +++ b/Package.swift @@ -1,8 +1,11 @@ -// swift-tools-version:4.2 +// swift-tools-version:5.0 import PackageDescription let package = Package( name: "stringray", + platforms: [ + .macOS(.v10_14) + ], products: [ .executable( name: "stringray", @@ -16,8 +19,8 @@ let package = Package( dependencies: [ .package(url: "https://github.com/jpsim/Yams.git", from: "1.0.1"), .package(url: "https://github.com/scottrhoyt/SwiftyTextTable.git", from: "0.5.0"), - .package(url: "https://github.com/g-Off/XcodeProject.git", from: "0.4.0"), - .package(url: "https://github.com/g-Off/CommandRegistry.git", .branch("master")), + .package(url: "https://github.com/g-Off/XcodeProject.git", from: "0.5.0-alpha.3"), + .package(url: "https://github.com/g-Off/CommandRegistry.git", from: "0.1.0"), .package(url: "https://github.com/apple/swift-package-manager.git", from: "0.3.0") ], targets: [ @@ -26,15 +29,15 @@ let package = Package( dependencies: [ "CommandRegistry", "RayGun", - "SwiftyTextTable" + "SwiftyTextTable", + "XcodeProject", + "Utility", + "Yams", ] ), .target( name: "RayGun", dependencies: [ - "Utility", - "Yams", - "XcodeProject" ] ), .testTarget( diff --git a/Sources/RayGun/Exports.swift b/Sources/RayGun/Exports.swift deleted file mode 100644 index 812f4b4..0000000 --- a/Sources/RayGun/Exports.swift +++ /dev/null @@ -1,11 +0,0 @@ -// -// Exports.swift -// RayGun -// -// Created by Geoffrey Foster on 2019-02-01. -// - -@_exported import Basic -@_exported import SwiftyTextTable -@_exported import Utility -@_exported import XcodeProject diff --git a/Sources/RayGun/Lint Rules/Linter.swift b/Sources/RayGun/Lint Rules/Linter.swift index 24779dc..b9bbfb2 100644 --- a/Sources/RayGun/Lint Rules/Linter.swift +++ b/Sources/RayGun/Lint Rules/Linter.swift @@ -6,7 +6,6 @@ // import Foundation -import Yams public struct Linter { public struct Config: Decodable { @@ -29,11 +28,6 @@ public struct Linter { self.rules = [:] } - public init(url: Foundation.URL) throws { - let string = try String(contentsOf: url, encoding: .utf8) - self = try YAMLDecoder().decode(Config.self, from: string, userInfo: [:]) - } - public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) self.included = try container.decodeIfPresent([String].self, forKey: .included) ?? [] @@ -47,7 +41,8 @@ public struct Linter { public static let allRules: [LintRule] = [ MissingLocalizationLintRule(), OrphanedLocalizationLintRule(), - MissingPlaceholderLintRule() + MissingPlaceholderLintRule(), + MissingCommentLintRule() ] public struct Error: LocalizedError { diff --git a/Sources/RayGun/Lint Rules/MissingCommentLintRule.swift b/Sources/RayGun/Lint Rules/MissingCommentLintRule.swift new file mode 100644 index 0000000..d4f4402 --- /dev/null +++ b/Sources/RayGun/Lint Rules/MissingCommentLintRule.swift @@ -0,0 +1,25 @@ +// +// MissingCommentLintRule.swift +// RayGun +// +// Created by Geoffrey Foster on 2019-06-02. +// + +import Foundation + +struct MissingCommentLintRule: LintRule { + let info: RuleInfo = RuleInfo(identifier: "missing_comment", name: "Missing Comment", description: "", severity: .error) + + func scan(table: StringsTable, url: Foundation.URL, config: Linter.Config.Rule?) throws -> [LintRuleViolation] { + var violations: [LintRuleViolation] = [] + let file = Foundation.URL(fileURLWithPath: "\(table.base.identifier).lproj/\(table.name).strings", relativeTo: url) + for entry in table.baseEntries where entry.comment == nil { + let line = entry.location?.line + let location = LintRuleViolation.Location(file: file, line: line) + let reason = "Mismatched placeholders \(entry.key)" + let violation = LintRuleViolation(locale: table.base, location: location, severity: config?.severity ?? info.severity, reason: reason) + violations.append(violation) + } + return violations + } +} diff --git a/Sources/RayGun/Strings Table/StringsTable.swift b/Sources/RayGun/Strings Table/StringsTable.swift index b86c348..dabaabc 100644 --- a/Sources/RayGun/Strings Table/StringsTable.swift +++ b/Sources/RayGun/Strings Table/StringsTable.swift @@ -45,7 +45,7 @@ public struct StringsTable: Codable { return dictEntries[base] ?? [:] } - public init(name: String, base: Locale, entries: EntriesType, dictEntries: DictEntriesType) { + public init(name: String, base: Locale, entries: EntriesType = [:], dictEntries: DictEntriesType = [:]) { self.name = name self.base = base self.entries = entries @@ -125,7 +125,7 @@ public struct StringsTable: Codable { } private mutating func replace(entry: Entry, with otherEntry: Entry, locale: Locale) { - guard let index = entries[locale]?.index(of: entry) else { return } + guard let index = entries[locale]?.firstIndex(of: entry) else { return } entries[locale]?[index] = otherEntry } diff --git a/Sources/RayGun/Utilities/OrderedSet.swift b/Sources/RayGun/Utilities/OrderedSet.swift index 93beb1e..188c97c 100644 --- a/Sources/RayGun/Utilities/OrderedSet.swift +++ b/Sources/RayGun/Utilities/OrderedSet.swift @@ -58,7 +58,7 @@ extension OrderedSet: SetAlgebra { @discardableResult public mutating func update(with newMember: Element) -> Element? { - if contains(newMember), let index = orderedStorage.index(of: newMember) { + if contains(newMember), let index = orderedStorage.firstIndex(of: newMember) { orderedStorage[index] = newMember } let result = storage.update(with: newMember) @@ -70,7 +70,7 @@ extension OrderedSet: SetAlgebra { @discardableResult public mutating func remove(_ member: Element) -> Element? { - guard let index = orderedStorage.index(of: member) else { return nil } + guard let index = orderedStorage.firstIndex(of: member) else { return nil } orderedStorage.remove(at: index) storage.remove(member) return member diff --git a/Sources/stringray/Commands/CopyCommand.swift b/Sources/stringray/Commands/CopyCommand.swift index 1b6ed1b..5d71d5c 100644 --- a/Sources/stringray/Commands/CopyCommand.swift +++ b/Sources/stringray/Commands/CopyCommand.swift @@ -6,8 +6,10 @@ // import Foundation -import RayGun import CommandRegistry +import Basic +import Utility +import RayGun struct CopyCommand: Command { private struct Arguments { @@ -17,17 +19,17 @@ struct CopyCommand: Command { } let command: String = "copy" let overview: String = "Copy keys matching the given pattern from one strings table to another." - + private let binder: ArgumentBinder - + init(parser: ArgumentParser) { binder = ArgumentBinder() let subparser = parser.add(subparser: command, overview: overview) - + let inputFile = subparser.add(positional: "inputFile", kind: PathArgument.self, optional: false, usage: "", completion: .filename) let outputFile = subparser.add(positional: "outputFile", kind: PathArgument.self, optional: false, usage: "", completion: .filename) let prefix = subparser.add(option: "--prefix", shortName: "-p", kind: [String].self, strategy: .oneByOne, usage: "", completion: nil) - + binder.bind(positional: inputFile) { (arguments, inputFile) in arguments.inputFile = URL(fileURLWithPath: inputFile.path.asString) } @@ -40,18 +42,18 @@ struct CopyCommand: Command { } } } - + func run(with arguments: ArgumentParser.Result) throws { var commandArgs = Arguments() try binder.fill(parseResult: arguments, into: &commandArgs) try copy(from: commandArgs.inputFile, to: commandArgs.outputFile, matching: commandArgs.matching) } - + private func copy(from: Foundation.URL, to: Foundation.URL, matching: [Match]) throws { let loader = StringsTableLoader() let fromTable = try loader.load(url: from) var toTable = try loader.load(url: to) - + let filteredTable = fromTable.withKeys(matching: matching) toTable.addEntries(from: filteredTable) try loader.write(to: to.resourceDirectory, table: toTable) diff --git a/Sources/stringray/Commands/LintCommand.swift b/Sources/stringray/Commands/LintCommand.swift index 60c9359..a68eefb 100644 --- a/Sources/stringray/Commands/LintCommand.swift +++ b/Sources/stringray/Commands/LintCommand.swift @@ -6,8 +6,12 @@ // import Foundation -import RayGun import CommandRegistry +import Basic +import Utility +import RayGun +import XcodeProject +import SwiftyTextTable struct LintCommand: Command { private struct Arguments { diff --git a/Sources/stringray/Commands/MoveCommand.swift b/Sources/stringray/Commands/MoveCommand.swift index c92f945..766140f 100644 --- a/Sources/stringray/Commands/MoveCommand.swift +++ b/Sources/stringray/Commands/MoveCommand.swift @@ -7,6 +7,8 @@ import Foundation import CommandRegistry +import Basic +import Utility import RayGun struct MoveCommand: Command { diff --git a/Sources/stringray/Commands/RenameCommand.swift b/Sources/stringray/Commands/RenameCommand.swift index 04dac71..ae02496 100644 --- a/Sources/stringray/Commands/RenameCommand.swift +++ b/Sources/stringray/Commands/RenameCommand.swift @@ -6,8 +6,10 @@ // import Foundation -import RayGun import CommandRegistry +import Basic +import Utility +import RayGun struct RenameCommand: Command { private struct Arguments { diff --git a/Sources/stringray/Commands/SortCommand.swift b/Sources/stringray/Commands/SortCommand.swift index ad08ec9..e448fab 100644 --- a/Sources/stringray/Commands/SortCommand.swift +++ b/Sources/stringray/Commands/SortCommand.swift @@ -8,6 +8,8 @@ import Foundation import RayGun import CommandRegistry +import Basic +import Utility struct SortCommand: Command { private struct Arguments { @@ -15,25 +17,25 @@ struct SortCommand: Command { } let command: String = "sort" let overview: String = "Sorts the given strings table alphabetically by key." - + private let binder: ArgumentBinder - + init(parser: ArgumentParser) { binder = ArgumentBinder() let subparser = parser.add(subparser: command, overview: overview) let inputFile = subparser.add(positional: "inputFile", kind: PathArgument.self, optional: false, usage: "", completion: .filename) - + binder.bind(positional: inputFile) { (arguments, inputFile) in arguments.inputFile = URL(fileURLWithPath: inputFile.path.asString) } } - + func run(with arguments: ArgumentParser.Result) throws { var commandArgs = Arguments() try binder.fill(parseResult: arguments, into: &commandArgs) try sort(url: commandArgs.inputFile) } - + private func sort(url: Foundation.URL) throws { let loader = StringsTableLoader() var table = try loader.load(url: url) diff --git a/Sources/stringray/Version.swift b/Sources/stringray/Version.swift index 1e5e950..1781604 100644 --- a/Sources/stringray/Version.swift +++ b/Sources/stringray/Version.swift @@ -1,5 +1,5 @@ import Utility extension Version { - static var current: Version = "0.3.0" + static var current: Version = "0.3.1" } diff --git a/Sources/stringray/main.swift b/Sources/stringray/main.swift index f393736..9833a2b 100644 --- a/Sources/stringray/main.swift +++ b/Sources/stringray/main.swift @@ -9,6 +9,15 @@ import Foundation import Utility import CommandRegistry +import Yams +import RayGun + +extension Linter.Config { + public init(url: Foundation.URL) throws { + let string = try String(contentsOf: url, encoding: .utf8) + self = try YAMLDecoder().decode(Linter.Config.self, from: string, userInfo: [:]) + } +} var registry = Registry(usage: " ", overview: "", version: Version.current) registry.register(command: MoveCommand.self) diff --git a/Tests/stringrayTests/LinterTests.swift b/Tests/stringrayTests/LinterTests.swift new file mode 100644 index 0000000..6b33472 --- /dev/null +++ b/Tests/stringrayTests/LinterTests.swift @@ -0,0 +1,36 @@ +// +// LinterTests.swift +// stringrayTests +// +// Created by Geoffrey Foster on 2019-02-01. +// + +import XCTest +@testable import RayGun + +class LinterTests: XCTestCase { + struct TestReporter: Reporter { + func generateReport(for violations: [LintRuleViolation], to outputStream: inout Target) where Target : TextOutputStream { + + } + } + func testLint() { + var entries = StringsTable.EntriesType() + let baseLocale = Locale(identifier: "en") + let otherLocale = Locale(identifier: "fr") + let key = "key" + entries[baseLocale, default: OrderedSet()].append( + StringsTable.Entry(location: nil, comment: nil, key: key, value: "%1$@ mentioned you on %2$@ at %3$@") + ) + entries[otherLocale, default: OrderedSet()].append( + StringsTable.Entry(location: nil, comment: nil, key: key, value: "%1$@ mentioné youé oné %1$@ até %1$@") + ) + let table = StringsTable(name: "Test", base: baseLocale, entries: entries) + let linter = Linter(reporter: TestReporter()) + do { + try linter.report(on: table, url: URL(fileURLWithPath: "file://does/not/exist")) + } catch { + print("hello") + } + } +} diff --git a/Version.swift.template b/Version.swift.template deleted file mode 100644 index a6e9e7b..0000000 --- a/Version.swift.template +++ /dev/null @@ -1,5 +0,0 @@ -import Utility - -extension Version { - static var current: Version = "__VERSION__" -}