Skip to content
This repository has been archived by the owner on Sep 6, 2018. It is now read-only.

Parser protocol to unify parser interfaces. #46

Merged
merged 11 commits into from
Jul 7, 2017
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,12 @@ Due to the removal of legacy code, there are a few breaking changes in this new
* Images: switch back from `actool` to an internal parser to fix numerous issues with the former.
[David Jennes](https://github.com/djbe)
[#43](https://github.com/SwiftGen/templates/issues/43)
* Refactor the colors parser into a generic parser that will invoke the correct type-specific parser based on the file extension. This allows us to support multiple input files.
* Refactor the colors parser into a generic parser that will invoke the correct type-specific parser based on the file extension. This allows us to support multiple input files.
[David Jennes](https://github.com/djbe)
[#40](https://github.com/SwiftGen/templates/issues/40)
* Refactor all parsers to conform to a `Parser` protocol to unify the interfaces.
[David Jennes](https://github.com/djbe)
[#46](https://github.com/SwiftGen/templates/issues/46)

## 1.1.0

Expand Down
160 changes: 82 additions & 78 deletions Pods/Pods.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

29 changes: 19 additions & 10 deletions Sources/Parsers/AssetsCatalogParser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ struct Catalog {
let entries: [Entry]
}

public final class AssetsCatalogParser {
public final class AssetsCatalogParser: Parser {
public enum Error: Swift.Error, CustomStringConvertible {
case invalidFile

Expand All @@ -30,10 +30,19 @@ public final class AssetsCatalogParser {
}

var catalogs = [Catalog]()
public var warningHandler: Parser.MessageHandler?

public init() {}
public static let commandInfo = CommandInfo(
name: "assets",
description: "generate code for items in your Assets Catalog(s)",
pathDescription: "Asset Catalog file(s)."
)

public func parseCatalog(at path: Path) throws {
public init(options: [String: Any] = [:], warningHandler: Parser.MessageHandler? = nil) {
self.warningHandler = warningHandler
}

public func parse(path: Path) throws {
guard path.extension == AssetCatalog.extension else {
throw AssetsCatalogParser.Error.invalidFile
}
Expand All @@ -45,7 +54,7 @@ public final class AssetsCatalogParser {
}
}

// MARK: - Plist processing
// MARK: - Catalog processing

private enum AssetCatalog {
static let `extension` = "xcassets"
Expand All @@ -59,12 +68,12 @@ private enum AssetCatalog {
enum Item {
static let imageSet = "imageset"

/**
* This is a list of supported asset catalog item types, for now we just
* support `image set`s. If you want to add support for new types, just add
* it to this whitelist, and add the necessary code to the
* `process(items:withPrefix:)` method.
*/
/**
* This is a list of supported asset catalog item types, for now we just
* support `image set`s. If you want to add support for new types, just add
* it to this whitelist, and add the necessary code to the
* `process(items:withPrefix:)` method.
*/
static let supported = [imageSet]
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,12 @@ import Foundation
import PathKit

public enum ColorsParserError: Error, CustomStringConvertible {
case duplicateExtensionParser(ext: String, existing: String, new: String)
case invalidHexColor(path: Path, string: String, key: String?)
case invalidFile(path: Path, reason: String)
case unsupportedFileType(path: Path, supported: [String])

public var description: String {
switch self {
case .duplicateExtensionParser(let ext, let existing, let new):
return "error: Parser \(new) tried to register the file type '\(ext)' already registered by \(existing)."
case .invalidHexColor(let path, let string, let key):
let keyInfo = key.flatMap { " for key \"\($0)\"" } ?? ""
return "error: Invalid hex color \"\(string)\" found\(keyInfo) (\(path))."
Expand All @@ -41,18 +38,27 @@ protocol ColorsFileTypeParser: class {
func parseFile(at path: Path) throws -> Palette
}

public final class ColorsFileParser {
public final class ColorsParser: Parser {
private var parsers = [String: ColorsFileTypeParser.Type]()
var palettes = [Palette]()
public var warningHandler: Parser.MessageHandler?

public init() throws {
try register(parser: ColorsCLRFileParser.self)
try register(parser: ColorsJSONFileParser.self)
try register(parser: ColorsTextFileParser.self)
try register(parser: ColorsXMLFileParser.self)
public static let commandInfo = CommandInfo(
name: "colors",
description: "generate code for color palettes",
pathDescription: "Colors.txt|.clr|.xml|.json file(s) to parse."
)

public init(options: [String: Any] = [:], warningHandler: Parser.MessageHandler? = nil) {
self.warningHandler = warningHandler

register(parser: ColorsCLRFileParser.self)
register(parser: ColorsJSONFileParser.self)
register(parser: ColorsTextFileParser.self)
register(parser: ColorsXMLFileParser.self)
}

public func parseFile(at path: Path) throws {
public func parse(path: Path) throws {
guard let parserType = parsers[path.extension?.lowercased() ?? ""] else {
throw ColorsParserError.unsupportedFileType(path: path, supported: Array(parsers.keys))
}
Expand All @@ -63,12 +69,11 @@ public final class ColorsFileParser {
palettes += [palette]
}

func register(parser: ColorsFileTypeParser.Type) throws {
func register(parser: ColorsFileTypeParser.Type) {
for ext in parser.extensions {
guard parsers[ext] == nil else {
throw ColorsParserError.duplicateExtensionParser(ext: ext,
existing: String(describing: parsers[ext]!),
new: String(describing: parser))
if let old = parsers[ext] {
warningHandler?("error: Parser \(parser) tried to register the file type '\(ext)' already" +
"registered by \(old).", #file, #line)
}
parsers[ext] = parser
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,20 +74,29 @@ extension CTFont {

// MARK: FontsFileParser

public final class FontsFileParser {
public var entries: [String: Set<Font>] = [:]

public init() {}
public final class FontsParser: Parser {
var entries: [String: Set<Font>] = [:]
public var warningHandler: Parser.MessageHandler?

public static let commandInfo = CommandInfo(
name: "fonts",
description: "generate code for your fonts",
pathDescription: "Directory(ies) to parse."
)

public init(options: [String: Any] = [:], warningHandler: Parser.MessageHandler? = nil) {
self.warningHandler = warningHandler
}

public func parseFile(at path: Path) {
public func parse(path: Path) {
let dirChildren = path.iterateChildren(options: [.skipsHiddenFiles, .skipsPackageDescendants])
for file in dirChildren {
var value: AnyObject? = nil
let url = file.url as NSURL
try? url.getResourceValue(&value, forKey: URLResourceKey.typeIdentifierKey)
guard let uti = value as? String else {
print("Unable to determine the Universal Type Identifier for file \(file)")
continue
warningHandler?("Unable to determine the Universal Type Identifier for file \(file)", #file, #line)
continue
}
guard UTTypeConformsTo(uti as CFString, "public.font" as CFString) else { continue }
let fonts = CTFont.parse(file: file, relativeTo: path)
Expand Down
40 changes: 40 additions & 0 deletions Sources/Parsers/Parser.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//
// SwiftGenKit
// Copyright (c) 2017 SwiftGen
// MIT License
//

import Foundation
import PathKit

public struct CommandInfo {
public var name: String
public var description: String
public var pathDescription: String
}

public protocol Parser {
init(options: [String: Any], warningHandler: MessageHandler?) throws

// Command info
static var commandInfo: CommandInfo { get }

// Parsing and context generation
func parse(path: Path) throws
func parse(paths: [Path]) throws
func stencilContext() -> [String: Any]

/// This callback will be called when a Parser want to emit a diagnostics message
/// You can set this on the usage-site to a closure that prints the diagnostics in any way you see fit
/// Arguments are (message, file, line)
typealias MessageHandler = (String, String, UInt) -> Void
var warningHandler: MessageHandler? { get set }
}

public extension Parser {
func parse(paths: [Path]) throws {
for path in paths {
try parse(path: path)
}
}
}
37 changes: 24 additions & 13 deletions Sources/Parsers/StoryboardParser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,33 @@ struct Storyboard {
let segues: Set<Segue>
}

public final class StoryboardParser {
public final class StoryboardParser: Parser {
var storyboards = [Storyboard]()
public var warningHandler: Parser.MessageHandler?

public init() {}
public static let commandInfo = CommandInfo(
name: "storyboards",
description: "generate code for your storyboard scenes and segues",
pathDescription: "Directory to scan for .storyboard files. Can also be a path to a single .storyboard"
)

public func addStoryboard(at path: Path) throws {
public init(options: [String: Any] = [:], warningHandler: Parser.MessageHandler? = nil) {
self.warningHandler = warningHandler
}

public func parse(path: Path) throws {
if path.extension == "storyboard" {
try addStoryboard(at: path)
} else {
let dirChildren = path.iterateChildren(options: [.skipsHiddenFiles, .skipsPackageDescendants])

for file in dirChildren where file.extension == "storyboard" {
try addStoryboard(at: file)
}
}
}

func addStoryboard(at path: Path) throws {
guard let document = Kanna.XML(xml: try path.read(), encoding: .utf8) else {
throw ColorsParserError.invalidFile(path: path, reason: "Unknown XML parser error.")
}
Expand Down Expand Up @@ -111,16 +132,6 @@ public final class StoryboardParser {
segues: segues)]
}

public func parseDirectory(at path: Path) throws {
let iterator = path.makeIterator()

while let subPath = iterator.next() {
if subPath.extension == "storyboard" {
try addStoryboard(at: subPath)
}
}
}

var modules: Set<String> {
return Set<String>(storyboards.flatMap(collectModules(from:)))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
import Foundation
import PathKit

public enum StringsFileParserError: Error, CustomStringConvertible {
public enum StringsParserError: Error, CustomStringConvertible {
case duplicateTable(name: String)
case failureOnLoading(path: String)
case invalidFormat
case invalidPlaceholder(previous: StringsFileParser.PlaceholderType, new: StringsFileParser.PlaceholderType)
case invalidPlaceholder(previous: StringsParser.PlaceholderType, new: StringsParser.PlaceholderType)

public var description: String {
switch self {
Expand All @@ -27,25 +27,34 @@ public enum StringsFileParserError: Error, CustomStringConvertible {
}
}

public final class StringsFileParser {
public final class StringsParser: Parser {
var tables = [String: [Entry]]()
public var warningHandler: Parser.MessageHandler?

public init() {}
public static let commandInfo = CommandInfo(
name: "strings",
description: "generate code for your Localizable.strings file(s)",
pathDescription: "Strings file(s) to parse."
)

public init(options: [String: Any] = [:], warningHandler: Parser.MessageHandler? = nil) {
self.warningHandler = warningHandler
}

// Localizable.strings files are generally UTF16, not UTF8!
public func parseFile(at path: Path) throws {
public func parse(path: Path) throws {
let name = path.lastComponentWithoutExtension

guard tables[name] == nil else {
throw StringsFileParserError.duplicateTable(name: name)
throw StringsParserError.duplicateTable(name: name)
}
guard let data = try? path.read() else {
throw StringsFileParserError.failureOnLoading(path: path.string)
throw StringsParserError.failureOnLoading(path: path.string)
}

let plist = try PropertyListSerialization.propertyList(from: data, format: nil)
guard let dict = plist as? [String: String] else {
throw StringsFileParserError.invalidFormat
throw StringsParserError.invalidFormat
}

tables[name] = try dict.map { key, translation in
Expand Down Expand Up @@ -87,7 +96,7 @@ public final class StringsFileParser {
}

public static func placeholders(fromFormat str: String) throws -> [PlaceholderType] {
return try StringsFileParser.placeholders(fromFormat: str)
return try StringsParser.placeholders(fromFormat: str)
}
}

Expand Down Expand Up @@ -185,7 +194,7 @@ public final class StringsFileParser {
}
let previous = list[insertionPos-1]
guard previous == .unknown || previous == p else {
throw StringsFileParserError.invalidPlaceholder(previous: previous, new: p)
throw StringsParserError.invalidPlaceholder(previous: previous, new: p)
}
list[insertionPos-1] = p
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/Stencil/ColorsContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import Foundation
- `blue` : `String` — hex value of the blue component
- `alpha`: `String` — hex value of the alpha component
*/
extension ColorsFileParser {
extension ColorsParser {
public func stencilContext() -> [String: Any] {
let palettes: [[String: Any]] = self.palettes
.sorted(by: { $0.name < $1.name })
Expand Down
2 changes: 1 addition & 1 deletion Sources/Stencil/FontsContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import Foundation
- `style`: `String` — the designer's description of the font's style, like "bold", "oblique", …
*/

extension FontsFileParser {
extension FontsParser {
public func stencilContext() -> [String: Any] {
// turn into array of dictionaries
let families = entries.map { (name: String, family: Set<Font>) -> [String: Any] in
Expand Down
2 changes: 1 addition & 1 deletion Sources/Stencil/StringsContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ private extension String {
- `types`: `Array<String>` — defined only if localized string has parameter placeholders like `%d` and `%@` etc.
Contains a list of types like `"String"`, `"Int"`, etc
*/
extension StringsFileParser {
extension StringsParser {
public func stencilContext() -> [String: Any] {
let entryToStringMapper = { (entry: Entry, keyPath: [String]) -> [String: Any] in
let levelName = entry.keyStructure.last ?? ""
Expand Down
Loading