├── .github └── workflows │ └── build-and-test.yml ├── .gitignore ├── LICENSE ├── MarcoCli └── Sources │ ├── Args.swift │ ├── Utilities.swift │ └── main.swift ├── MarcoKit.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ └── xcschemes │ ├── MarcoCli.xcscheme │ ├── MarcoKit.xcscheme │ └── MarcoKitTests.xcscheme ├── MarcoKit ├── API │ ├── AsExtensions.swift │ ├── Conversions.swift │ ├── Marco.swift │ ├── MarcoParsingError.swift │ ├── MarcoVisitor.swift │ ├── NumberExtensions.swift │ └── Values │ │ ├── MarcoArray.swift │ │ ├── MarcoBoolLiteral.swift │ │ ├── MarcoDocument.swift │ │ ├── MarcoDoubleLiteral.swift │ │ ├── MarcoIntLiteral.swift │ │ ├── MarcoNullLiteral.swift │ │ ├── MarcoNumberLiteral.swift │ │ ├── MarcoObject.swift │ │ ├── MarcoStringLiteral.swift │ │ └── MarcoValue.swift ├── Info.plist ├── MarcoKit.h ├── Node │ ├── MarcoNode.swift │ ├── literal │ │ ├── MarcoBoolLiteralNode.swift │ │ ├── MarcoIdentifierNode.swift │ │ ├── MarcoNullLiteralNode.swift │ │ ├── MarcoStringLiteralNode.swift │ │ ├── MarcoStructuralElementNode.swift │ │ ├── MarcoWhitespaceNode.swift │ │ └── number │ │ │ ├── MarcoColorLiteralNode.swift │ │ │ ├── MarcoDoubleLiteralNode.swift │ │ │ ├── MarcoHexLiteralNode.swift │ │ │ └── MarcoIntLiteralNode.swift │ └── tree │ │ ├── CollectionUtil.swift │ │ ├── MarcoArrayNode.swift │ │ ├── MarcoDocumentNode.swift │ │ ├── MarcoKeyValuePairNode.swift │ │ └── MarcoObjectNode.swift ├── Parser │ ├── MarcoParser.swift │ └── MarcoParserState.swift └── Utilities │ ├── JsonToMarcoConverter.swift │ ├── MarcoNodeUtil.swift │ ├── MinifyingVisitor.swift │ ├── MutatingPrettifyingVisitor.swift │ ├── PrettifyingVisitor.swift │ └── ToJsonVisitor.swift ├── MarcoKitTests ├── Info.plist ├── Sources │ ├── DirectApiAccessTest.swift │ ├── ErrorTest.swift │ ├── FromJsonConversionTest.swift │ ├── JsonConversionTest.swift │ ├── MinifyTest.swift │ ├── ModificationTest.swift │ ├── ParseTest.swift │ ├── PrettifyTest.swift │ └── Utilities │ │ ├── XCTestCaseWithTestData.swift │ │ ├── marcoUtils.swift │ │ └── testUtils.swift └── TestData │ ├── error │ ├── array1.marco │ ├── array2.marco │ ├── array3.marco │ ├── array4.marco │ ├── array5.marco │ ├── color.marco │ ├── equals.marco │ ├── hex.marco │ ├── json.marco │ ├── jsonNonStrict.marco │ ├── number1.marco │ ├── number2.marco │ ├── object1.marco │ ├── object1.marcoConfig │ ├── object10.marco │ ├── object11.marco │ ├── object12.marco │ ├── object13.marco │ ├── object14.marco │ ├── object15.marcoConfig │ ├── object2.marco │ ├── object3.marco │ ├── object4.marco │ ├── object5.marco │ ├── object6.marco │ ├── object7.marco │ ├── object8.marco │ ├── object9.marco │ ├── simple.marco │ └── truefalse.marco │ ├── fromJson │ ├── marta.marco │ ├── marta.marcoConfig │ └── specialChars.marco │ ├── json │ ├── empty.marcoConfig │ ├── simple.marco │ └── simple.marcoConfig │ ├── minify │ ├── array1.marco │ ├── array2.marco │ ├── colorLiteral.marco │ ├── doubleLiteral.marco │ ├── emptyArray.marco │ ├── emptyObject.marco │ ├── emptyObject.marcoConfig │ ├── falseLiteral.marco │ ├── intLiteral.marco │ ├── negativeDoubleLiteral.marco │ ├── negativeIntLiteral.marco │ ├── nullLiteral.marco │ ├── object1.marco │ ├── object1.marcoConfig │ ├── simple.marco │ ├── simple.marcoConfig │ ├── stringLiteral.marco │ └── trueLiteral.marco │ ├── modification │ ├── addArray1.marco │ ├── addArray10.marco │ ├── addArray11.marco │ ├── addArray12.marco │ ├── addArray13.marco │ ├── addArray14.marco │ ├── addArray15.marco │ ├── addArray16.marco │ ├── addArray17.marco │ ├── addArray18.marco │ ├── addArray2.marco │ ├── addArray3.marco │ ├── addArray4.marco │ ├── addArray5.marco │ ├── addArray6.marco │ ├── addArray7.marco │ ├── addArray8.marco │ ├── addArray9.marco │ ├── addDict1.marco │ ├── addDict2.marco │ ├── addDict3.marco │ ├── addDict4.marco │ ├── addDict5.marcoConfig │ ├── nested1.marco │ ├── nested2.marco │ ├── nested3.marco │ ├── nested4.marco │ ├── nested5.marco │ ├── removeArray1.marco │ ├── removeArray10.marco │ ├── removeArray11.marco │ ├── removeArray12.marco │ ├── removeArray13.marco │ ├── removeArray14.marco │ ├── removeArray2.marco │ ├── removeArray3.marco │ ├── removeArray4.marco │ ├── removeArray5.marco │ ├── removeArray6.marco │ ├── removeArray7.marco │ ├── removeArray8.marco │ ├── removeArray9.marco │ ├── removeDict1.marco │ ├── removeDict2.marco │ ├── removeDict3.marcoConfig │ └── removeDict4.marcoConfig │ ├── parse │ ├── colorLiteral.marco │ ├── colorLiterals.marco │ ├── doubleLiteral.marco │ ├── emptyArray.marco │ ├── emptyObject.marco │ ├── falseLiteral.marco │ ├── intLiteral.marco │ ├── negativeDoubleLiteral.marco │ ├── negativeIntLiteral.marco │ ├── nestedArray10.marco │ ├── nestedArray2.marco │ ├── nestedArray50.marco │ ├── nestedObject2.marco │ ├── nestedObject50.marco │ ├── nullLiteral.marco │ ├── object1.marco │ ├── object2.marco │ ├── simple.marco │ ├── stringLiteral.marco │ └── trueLiteral.marco │ └── prettify │ ├── array1.marco │ ├── colorLiteral.marco │ ├── doubleLiteral.marco │ ├── emptyArray.marco │ ├── emptyObject.marco │ ├── emptyObject.marcoConfig │ ├── falseLiteral.marco │ ├── intLiteral.marco │ ├── negativeDoubleLiteral.marco │ ├── negativeIntLiteral.marco │ ├── nullLiteral.marco │ ├── object1.marco │ ├── object1.marcoConfig │ ├── object2.marco │ ├── object2.marcoConfig │ ├── simple.marco │ ├── simple.marcoConfig │ ├── stringLiteral.marco │ └── trueLiteral.marco ├── README.md ├── docs ├── GRAMMAR.md ├── MARCO.md └── api │ ├── Classes │ ├── Marco.html │ ├── Marco │ │ └── Options.html │ ├── MarcoNonStrictParsingError.html │ └── MarcoParsingError.html │ ├── Extensions.html │ ├── Extensions │ ├── Bool.html │ ├── CGFloat.html │ ├── Double.html │ ├── Float.html │ ├── Int.html │ ├── Int32.html │ ├── Int64.html │ ├── NSColor.html │ ├── NSNull.html │ ├── String.html │ ├── UInt32.html │ └── UInt64.html │ ├── Other Classes.html │ ├── Other Protocols.html │ ├── Parsing.html │ ├── Protocols.html │ ├── Protocols │ ├── MarcoArray.html │ ├── MarcoBoolLiteral.html │ ├── MarcoDocument.html │ ├── MarcoDoubleLiteral.html │ ├── MarcoIdentifier.html │ ├── MarcoIntLiteral.html │ ├── MarcoNullLiteral.html │ ├── MarcoNumberLiteral.html │ ├── MarcoObject.html │ ├── MarcoStringLiteral.html │ ├── MarcoValue.html │ └── MarcoVisitor.html │ ├── Values.html │ ├── Visitor.html │ ├── css │ ├── highlight.css │ └── jazzy.css │ ├── docsets │ ├── MarcoKit.docset │ │ └── Contents │ │ │ ├── Info.plist │ │ │ └── Resources │ │ │ ├── Documents │ │ │ ├── Classes │ │ │ │ ├── Marco.html │ │ │ │ ├── Marco │ │ │ │ │ └── Options.html │ │ │ │ ├── MarcoNonStrictParsingError.html │ │ │ │ └── MarcoParsingError.html │ │ │ ├── Extensions.html │ │ │ ├── Extensions │ │ │ │ ├── Bool.html │ │ │ │ ├── CGFloat.html │ │ │ │ ├── Double.html │ │ │ │ ├── Float.html │ │ │ │ ├── Int.html │ │ │ │ ├── Int32.html │ │ │ │ ├── Int64.html │ │ │ │ ├── NSColor.html │ │ │ │ ├── NSNull.html │ │ │ │ ├── String.html │ │ │ │ ├── UInt32.html │ │ │ │ └── UInt64.html │ │ │ ├── Other Classes.html │ │ │ ├── Other Protocols.html │ │ │ ├── Parsing.html │ │ │ ├── Protocols.html │ │ │ ├── Protocols │ │ │ │ ├── MarcoArray.html │ │ │ │ ├── MarcoBoolLiteral.html │ │ │ │ ├── MarcoDocument.html │ │ │ │ ├── MarcoDoubleLiteral.html │ │ │ │ ├── MarcoIdentifier.html │ │ │ │ ├── MarcoIntLiteral.html │ │ │ │ ├── MarcoNullLiteral.html │ │ │ │ ├── MarcoNumberLiteral.html │ │ │ │ ├── MarcoObject.html │ │ │ │ ├── MarcoStringLiteral.html │ │ │ │ ├── MarcoValue.html │ │ │ │ └── MarcoVisitor.html │ │ │ ├── Values.html │ │ │ ├── Visitor.html │ │ │ ├── css │ │ │ │ ├── highlight.css │ │ │ │ └── jazzy.css │ │ │ ├── img │ │ │ │ ├── carat.png │ │ │ │ ├── dash.png │ │ │ │ ├── gh.png │ │ │ │ └── spinner.gif │ │ │ ├── index.html │ │ │ ├── js │ │ │ │ ├── jazzy.js │ │ │ │ ├── jazzy.search.js │ │ │ │ ├── jquery.min.js │ │ │ │ ├── lunr.min.js │ │ │ │ └── typeahead.jquery.js │ │ │ ├── search.json │ │ │ └── undocumented.json │ │ │ └── docSet.dsidx │ └── MarcoKit.tgz │ ├── img │ ├── carat.png │ ├── dash.png │ ├── gh.png │ └── spinner.gif │ ├── index.html │ ├── js │ ├── jazzy.js │ ├── jazzy.search.js │ ├── jquery.min.js │ ├── lunr.min.js │ └── typeahead.jquery.js │ ├── search.json │ └── undocumented.json ├── jazzy.bash └── jazzy.json /.github/workflows/build-and-test.yml: -------------------------------------------------------------------------------- 1 | name: "Build and Test" 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | workflow_dispatch: 9 | jobs: 10 | build: 11 | runs-on: macOS-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | - uses: maxim-lobanov/setup-xcode@v1 15 | with: 16 | xcode-version: latest-stable 17 | - name: Run Tests 18 | run: xcodebuild test -scheme MarcoKitTests 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # AppCode 2 | .idea/**/workspace.xml 3 | .idea/**/tasks.xml 4 | .idea/**/usage.statistics.xml 5 | .idea/**/dictionaries 6 | .idea/**/shelf 7 | .idea/**/contentModel.xml 8 | 9 | # Xcode stuff 10 | *.moved-aside 11 | *.xcuserstate 12 | *~.nib 13 | 14 | # User settings 15 | *.pbxuser 16 | !default.pbxuser 17 | *.mode1v3 18 | !default.mode1v3 19 | *.mode2v3 20 | !default.mode2v3 21 | *.perspectivev3 22 | !default.perspectivev3 23 | xcuserdata/ 24 | 25 | # Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | # System files 32 | .DS_Store 33 | .Trashes 34 | *.swp 35 | 36 | # Jazzy 37 | build/ -------------------------------------------------------------------------------- /MarcoCli/Sources/Args.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | internal struct Args { 4 | var command: String 5 | var args: [String] 6 | var options: [String] 7 | } 8 | 9 | internal func parseArgs(rawArgs: [String]) -> Args? { 10 | guard !rawArgs.isEmpty else { return nil } 11 | let command = rawArgs[0] 12 | var args = [String]() 13 | var options = [String]() 14 | 15 | for rawArg in rawArgs.dropFirst() { 16 | if (rawArg.hasPrefix("--")) { 17 | options.append(String(rawArg.dropFirst(2))) 18 | } else { 19 | args.append(rawArg) 20 | } 21 | } 22 | 23 | return Args(command: command, args: args, options: options) 24 | } 25 | -------------------------------------------------------------------------------- /MarcoCli/Sources/Utilities.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | func printErr(_ message: String) { 4 | var standardError = FileHandle.standardError 5 | print(message, to: &standardError) 6 | } 7 | 8 | extension FileHandle : TextOutputStream { 9 | public func write(_ string: String) { 10 | guard let data = string.data(using: .utf8) else { return } 11 | self.write(data) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /MarcoCli/Sources/main.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | fileprivate func printUsage() { 4 | print("Usage:") 5 | print("marco j2m – convert JSON to Marco") 6 | print("marco m2j – convert Marco to Json") 7 | } 8 | 9 | fileprivate func cli() { 10 | guard 11 | let args = parseArgs(rawArgs: Array(CommandLine.arguments.dropFirst())), 12 | args.args.count == 1 13 | else { 14 | printUsage() 15 | return 16 | } 17 | 18 | let path = args.args[0] 19 | let isConfig = args.options.contains("config") 20 | let fn: (String, Bool) -> () 21 | 22 | switch (args.command) { 23 | case "j2m": fn = jsonToMarco 24 | case "m2j": fn = marcoToJson 25 | default: 26 | printUsage() 27 | return 28 | } 29 | 30 | fn(path, isConfig) 31 | } 32 | 33 | private func jsonToMarco(path: String, isConfig: Bool) { 34 | let jsonObject: Any 35 | 36 | do { 37 | let text = readFile(path: path) 38 | guard let data = text.data(using: .utf8) else { 39 | printErr("Only UTF-8 JSON files are supported") 40 | exit(1) 41 | } 42 | jsonObject = try JSONSerialization.jsonObject(with: data) 43 | } catch let e { 44 | printErr("Unable to parse JSON document: \(e.localizedDescription)") 45 | exit(1) 46 | } 47 | 48 | let marco: MarcoDocument 49 | if (isConfig) { 50 | guard let obj = jsonObject as? [String: Any] else { 51 | printErr("JSON value is not an object, can't generate a Marco config") 52 | exit(1) 53 | } 54 | 55 | marco = Marco.configFromJson(object: obj) 56 | } else { 57 | marco = Marco.fromJson(object: jsonObject) 58 | } 59 | 60 | print(Marco.prettify(marco), terminator: "") 61 | } 62 | 63 | private func marcoToJson(path: String, isConfig: Bool) { 64 | let marco: MarcoDocument 65 | 66 | do { 67 | let options: Marco.Options = isConfig ? .config : [] 68 | marco = try Marco.parse(readFile(path: path), options: options) 69 | } catch let e { 70 | printErr("Unable to parse Marco document: \(e.localizedDescription)") 71 | exit(1) 72 | } 73 | 74 | print(Marco.toJsonString(marco), terminator: "") 75 | } 76 | 77 | private func readFile(path: String) -> String { 78 | do { 79 | return try String(contentsOf: URL(fileURLWithPath: path), encoding: .utf8) 80 | } catch let e { 81 | printErr("Unable to read file '\(path)': \(e.localizedDescription)") 82 | exit(1) 83 | } 84 | } 85 | 86 | cli() 87 | -------------------------------------------------------------------------------- /MarcoKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /MarcoKit.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /MarcoKit.xcodeproj/xcshareddata/xcschemes/MarcoCli.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 66 | 67 | 70 | 71 | 74 | 75 | 76 | 77 | 83 | 85 | 91 | 92 | 93 | 94 | 96 | 97 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /MarcoKit.xcodeproj/xcshareddata/xcschemes/MarcoKit.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 46 | 47 | 57 | 58 | 64 | 65 | 71 | 72 | 73 | 74 | 76 | 77 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /MarcoKit.xcodeproj/xcshareddata/xcschemes/MarcoKitTests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 14 | 15 | 17 | 23 | 24 | 25 | 26 | 27 | 37 | 38 | 44 | 45 | 47 | 48 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /MarcoKit/API/AsExtensions.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | 3 | public extension MarcoValue { 4 | /** Safely casts this value to an object value. */ 5 | var asObject: MarcoObject? { 6 | guard let object = self as? MarcoObject else { return nil } 7 | return object 8 | } 9 | 10 | /** Safely casts this value to an array value. */ 11 | var asArray: MarcoArray? { 12 | guard let array = self as? MarcoArray else { return nil } 13 | return array 14 | } 15 | 16 | /** Safely casts this value to a string value. */ 17 | var asStringLiteral: MarcoStringLiteral? { 18 | guard let literal = self as? MarcoStringLiteral else { return nil } 19 | return literal 20 | } 21 | 22 | /** Safely casts this value to an string value, and returns the backing `String` if the cast was successful. */ 23 | var asString: String? { 24 | guard let literal = self as? MarcoStringLiteral else { return nil } 25 | return literal.value 26 | } 27 | 28 | /** Safely casts this value to an string value, and returns the backing `String` if the cast was successful. 29 | Returns an empty String otherwise. 30 | */ 31 | var asStringOrEmpty: String { 32 | return asString ?? "" 33 | } 34 | 35 | /** Safely casts this value to a number value. */ 36 | var asNumberLiteral: MarcoNumberLiteral? { 37 | guard let literal = self as? MarcoNumberLiteral else { return nil } 38 | return literal 39 | } 40 | 41 | /** Safely casts this value to a integer value. */ 42 | var asIntLiteral: MarcoIntLiteral? { 43 | guard let literal = self as? MarcoIntLiteral else { return nil } 44 | return literal 45 | } 46 | 47 | /** Safely casts this value to a double value. */ 48 | var asDoubleLiteral: MarcoDoubleLiteral? { 49 | guard let literal = self as? MarcoDoubleLiteral else { return nil } 50 | return literal 51 | } 52 | 53 | /** Safely casts this value to an integer value, and returns the backing `Int` if the cast was successful. */ 54 | var asInt: Int? { 55 | guard let literal = self as? MarcoIntLiteral else { return nil } 56 | return literal.intValue 57 | } 58 | 59 | /** Safely casts this value to an integer value, and returns the backing `Int` if the cast was successful. 60 | Returns `0` otherwise. 61 | */ 62 | var asIntOrZero: Int { 63 | return asInt ?? 0 64 | } 65 | 66 | /** Safely casts this value to a double value, and returns the backing `Double` if the cast was successful. */ 67 | var asDouble: Double? { 68 | guard let literal = self as? MarcoDoubleLiteral else { return nil } 69 | return literal.doubleValue 70 | } 71 | 72 | /** Safely casts this value to a double value, and returns the backing `Double` if the cast was successful. 73 | Returns `0.0` otherwise. 74 | */ 75 | var asDoubleOrZero: Double { 76 | return asDouble ?? 0 77 | } 78 | 79 | /** Safely casts this value to a boolean value. */ 80 | var asBoolLiteral: MarcoBoolLiteral? { 81 | guard let literal = self as? MarcoBoolLiteral else { return nil } 82 | return literal 83 | } 84 | 85 | /** Safely casts this value to a boolean value, and returns the backing `Bool` if the cast was successful. */ 86 | var asBool: Bool? { 87 | guard let literal = self as? MarcoBoolLiteral else { return nil } 88 | return literal.value 89 | } 90 | 91 | /** Safely casts this value to a boolean value, and returns the backing `Bool` if the cast was successful. 92 | Returns `false` otherwise. 93 | */ 94 | var asBoolOrFalse: Bool { 95 | return asBool ?? false 96 | } 97 | 98 | /** Safely casts this value to a boolean value, and returns the backing `Bool` if the cast was successful. 99 | Returns `true` otherwise. 100 | */ 101 | var asBoolOrTrue: Bool { 102 | return asBool ?? true 103 | } 104 | 105 | /** Safely casts this value to a null value. */ 106 | var asNullLiteral: MarcoNullLiteral? { 107 | guard let literal = self as? MarcoNullLiteral else { return nil } 108 | return literal 109 | } 110 | 111 | /** True if this value is a null value. */ 112 | var isNull: Bool { 113 | return self is MarcoNullLiteral 114 | } 115 | 116 | /** Safely casts this value to a color integer value, and converts it into an `NSColor`. */ 117 | func asColor(useAlpha: Bool = false) -> NSColor? { 118 | guard let intValue = asInt else { return nil } 119 | return getColor(intValue, useAlpha: useAlpha) 120 | } 121 | 122 | /** Safely casts this value to a color integer value, and converts it into an `NSColor`. */ 123 | var asColor: NSColor? { 124 | if let colorNode = self as? MarcoColorLiteralNode { 125 | return getColor(colorNode.intValue, useAlpha: colorNode.useAlpha) 126 | } 127 | 128 | guard let intValue = asInt else { return nil } 129 | let useAlpha = (intValue & 0xff000000) != 0 130 | return getColor(intValue, useAlpha: useAlpha) 131 | } 132 | 133 | private func getColor(_ intValue: Int, useAlpha: Bool = false) -> NSColor { 134 | let alpha = useAlpha ? CGFloat((intValue & 0xff000000) >> 24) / 255.0 : 1.0 135 | let red = CGFloat((intValue & 0xff0000) >> 16) / 255.0 136 | let green = CGFloat((intValue & 0xff00) >> 8) / 255.0 137 | let blue = CGFloat((intValue & 0xff) >> 0) / 255.0 138 | let color = NSColor(calibratedRed: red, green: green, blue: blue, alpha: alpha) 139 | return color 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /MarcoKit/API/Conversions.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | 3 | public extension String { 4 | /** Returns a Marco string value. */ 5 | var toMarco: MarcoStringLiteral { 6 | return MarcoStringLiteralNode(value: self) 7 | } 8 | } 9 | 10 | public extension Int { 11 | /** Returns a Marco integer value. */ 12 | var toMarco: MarcoIntLiteral { 13 | return MarcoIntLiteralNode(value: self) 14 | } 15 | } 16 | 17 | public extension Int32 { 18 | /** Returns a Marco integer value. */ 19 | var toMarco: MarcoIntLiteral { 20 | return MarcoIntLiteralNode(value: Int(self)) 21 | } 22 | } 23 | 24 | public extension UInt32 { 25 | /** Returns a Marco integer value. */ 26 | var toMarco: MarcoIntLiteral { 27 | return MarcoIntLiteralNode(value: Int(self)) 28 | } 29 | } 30 | 31 | public extension Int64 { 32 | /** Returns a Marco integer value. */ 33 | var toMarco: MarcoIntLiteral { 34 | return MarcoIntLiteralNode(value: Int(self)) 35 | } 36 | } 37 | 38 | public extension UInt64 { 39 | /** Returns a Marco integer value. */ 40 | var toMarco: MarcoIntLiteral { 41 | return MarcoIntLiteralNode(value: Int(self)) 42 | } 43 | } 44 | 45 | public extension Double { 46 | /** Returns a Marco double value. */ 47 | var toMarco: MarcoDoubleLiteral { 48 | return MarcoDoubleLiteralNode(value: self) 49 | } 50 | } 51 | 52 | public extension Float { 53 | /** Returns a Marco double value. */ 54 | var toMarco: MarcoDoubleLiteral { 55 | return MarcoDoubleLiteralNode(value: Double(self)) 56 | } 57 | } 58 | 59 | public extension CGFloat { 60 | /** Returns a Marco double value. */ 61 | var toMarco: MarcoDoubleLiteral { 62 | return MarcoDoubleLiteralNode(value: Double(self)) 63 | } 64 | } 65 | 66 | public extension NSNull { 67 | /** Returns a Marco null value. */ 68 | var toMarco: MarcoNullLiteral { 69 | return MarcoNullLiteralNode() 70 | } 71 | } 72 | 73 | public extension Bool { 74 | /** Returns a Marco boolean value. */ 75 | var toMarco: MarcoBoolLiteral { 76 | return MarcoBoolLiteralNode(value: self) 77 | } 78 | } 79 | 80 | public extension NSColor { 81 | /** returns a Marco color integer value. */ 82 | var toMarco: MarcoIntLiteral { 83 | return MarcoColorLiteralNode(text: hexValue) 84 | } 85 | 86 | private var hexValue: String { 87 | var r: CGFloat = 0 88 | var g: CGFloat = 0 89 | var b: CGFloat = 0 90 | var a: CGFloat = 0 91 | 92 | self.getRed(&r, green: &g, blue: &b, alpha: &a) 93 | 94 | // TODO support opacity 95 | let rgb:Int = (Int)(r * 255) << 16 | (Int)(g * 255) << 8 | (Int)(b * 255) 96 | return String(format:"#%06x", rgb) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /MarcoKit/API/Marco.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /** Marco utilities. */ 4 | public class Marco { 5 | private init() {} 6 | 7 | /** Parsing options. */ 8 | public struct Options: OptionSet, Sendable { 9 | public let rawValue: UInt 10 | 11 | public init(rawValue: UInt) { 12 | self.rawValue = rawValue 13 | } 14 | 15 | /** Parse a configuration file. */ 16 | public static let config = Options(rawValue: 1) 17 | 18 | public static let nonStrict = Options(rawValue: 2) 19 | 20 | public static let showContextInErrors = Options(rawValue: 4) 21 | } 22 | 23 | /** Returns a parsed document. */ 24 | public static func parse(_ text: String, options: Options = []) -> (document: MarcoDocument?, errors: [MarcoParsingError]) { 25 | return MarcoParser.parse(text: text, options: options) 26 | } 27 | 28 | /** Wraps a `value` with a `MarcoDocument` value. */ 29 | public static func document(from value: MarcoValue) -> MarcoDocument { 30 | return MarcoDocumentNode(children: [value as! MarcoNode], valueIndex: 0) 31 | } 32 | 33 | /** Returns an empty Marco array value. */ 34 | public static func emptyArray() -> MarcoArray { 35 | return MarcoArrayNode(children: [ 36 | MarcoStructuralElementNode(.leftSquareBracket), 37 | MarcoStructuralElementNode(.rightSquareBracket) 38 | ], elementIndices: []) 39 | } 40 | 41 | /** Returns an empty Marco object value. */ 42 | public static func emptyObject() -> MarcoObject { 43 | return MarcoObjectNode(children: [ 44 | MarcoStructuralElementNode(.leftCurlyBracket), 45 | MarcoStructuralElementNode(.rightCurlyBracket) 46 | ], keyMappings: [:], isConfig: false) 47 | } 48 | 49 | /** Returns an empty configuration value. */ 50 | public static func emptyConfig() -> MarcoObject { 51 | return MarcoObjectNode(children: [], keyMappings: [:], isConfig: true) 52 | } 53 | 54 | /** Returns an array value with the given elements. */ 55 | public static func array(_ elements: MarcoValue...) -> MarcoArray { 56 | return array(elements: elements) 57 | } 58 | 59 | /** Returns an array value with the given elements. */ 60 | public static func array(elements: [MarcoValue]) -> MarcoArray { 61 | var children = [MarcoNode](), elementIndices = [Int]() 62 | children.reserveCapacity(elements.count * 2 + 1) 63 | elementIndices.reserveCapacity(elements.count) 64 | 65 | children.append(MarcoStructuralElementNode(.leftSquareBracket)) 66 | 67 | for element in elements { 68 | if (children.count > 1) { 69 | children.append(WS(" ")) 70 | } 71 | 72 | elementIndices.append(children.count) 73 | children.append(element as! MarcoNode) 74 | } 75 | 76 | children.append(MarcoStructuralElementNode(.rightSquareBracket)) 77 | 78 | return MarcoArrayNode(children: children, elementIndices: elementIndices) 79 | } 80 | 81 | /** Returns an object value with the given elements. */ 82 | public static func object(_ elements: (String, MarcoValue)...) -> MarcoObject { 83 | return object(elements: elements) 84 | } 85 | 86 | /** Returns an object value with the given elements. */ 87 | public static func object(elements: [(String, MarcoValue)]) -> MarcoObject { 88 | var children = [MarcoNode](), keyMappings = [String: Int]() 89 | children.reserveCapacity(elements.count * 2 + 1) 90 | keyMappings.reserveCapacity(elements.count) 91 | 92 | children.append(MarcoStructuralElementNode(.leftCurlyBracket)) 93 | 94 | for (key, value) in elements { 95 | if (children.count > 1) { 96 | children.append(WS(" ")) 97 | } 98 | 99 | let keyNode: MarcoNode 100 | if (MarcoParser.isSimpleKey(key: key)) { 101 | keyNode = MarcoIdentifierNode(name: key) 102 | } else { 103 | keyNode = MarcoStringLiteralNode(value: key) 104 | } 105 | 106 | let keyValuePairNode = MarcoKeyValuePairNode( 107 | children: [keyNode, WS(" "), value as! MarcoNode], 108 | keyIndex: 0, valueIndex: 2) 109 | 110 | keyMappings[key] = children.count 111 | children.append(keyValuePairNode) 112 | } 113 | 114 | children.append(MarcoStructuralElementNode(.rightCurlyBracket)) 115 | 116 | return MarcoObjectNode(children: children, keyMappings: keyMappings, isConfig: false) 117 | } 118 | } 119 | 120 | public extension Marco { 121 | /** 122 | Returns a minified copy of the value. 123 | 124 | This does not change the original value. 125 | All insignificant whitespaces will be removed. 126 | */ 127 | static func minify(_ value: MarcoValue) -> MarcoValue { 128 | return value.accept(MinifyingVisitor.instance) 129 | } 130 | 131 | /** 132 | Prettifies the value. 133 | 134 | This does not change the original value. 135 | All existing formatting will be removed. 136 | */ 137 | static func prettify(_ value: MarcoValue, reorderKeys: Bool = true) -> MarcoValue { 138 | return value.accept( 139 | PrettifyingVisitor(forceNewLine: false, isRecursive: true, reorderKeys: reorderKeys), 140 | data: 0) 141 | } 142 | 143 | /** Returns a JSON string for the given Marco object. */ 144 | static func toJsonString(_ value: MarcoValue) -> String { 145 | return value.accept(ToJsonVisitor.instance) 146 | } 147 | 148 | /** Returns a Marco object got from the parsed JSON representation. */ 149 | static func fromJson(object json: Any) -> MarcoDocument { 150 | return JsonToMarcoConverter.instance.convert(json: json) 151 | } 152 | 153 | /** Returns a Marco configuration object from the parsed JSON representation. */ 154 | static func configFromJson(object json: [String: Any]) -> MarcoDocument { 155 | return JsonToMarcoConverter.instance.convertConfig(json: json) 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /MarcoKit/API/MarcoParsingError.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /** Marco parsing error. */ 4 | public struct MarcoParsingError: Error, Sendable { 5 | /** Error message. */ 6 | public let message: String 7 | 8 | /** Error offset. */ 9 | public let range: Range 10 | 11 | internal init(message: String, range: Range) { 12 | self.message = message 13 | self.range = range 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /MarcoKit/API/MarcoVisitor.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /** Marco value visitor. */ 4 | public protocol MarcoVisitor { 5 | /** Return value type. */ 6 | associatedtype ReturnType 7 | 8 | /** Additional data type. */ 9 | associatedtype Data 10 | 11 | /** Value handler. */ 12 | func visitValue(value: MarcoValue, data: Data) -> ReturnType 13 | 14 | /** Document value handler. */ 15 | func visitDocument(value: MarcoDocument, data: Data) -> ReturnType 16 | 17 | /** Object value handler. */ 18 | func visitObject(value: MarcoObject, data: Data) -> ReturnType 19 | 20 | /** Array value handler. */ 21 | func visitArray(value: MarcoArray, data: Data) -> ReturnType 22 | 23 | /** Null value handler. */ 24 | func visitNull(value: MarcoNullLiteral, data: Data) -> ReturnType 25 | 26 | /** Boolean value handler. */ 27 | func visitBool(value: MarcoBoolLiteral, data: Data) -> ReturnType 28 | 29 | /** String value handler. */ 30 | func visitString(value: MarcoStringLiteral, data: Data) -> ReturnType 31 | 32 | /** Number value handler. */ 33 | func visitNumber(value: MarcoNumberLiteral, data: Data) -> ReturnType 34 | 35 | /** Integer value handler. */ 36 | func visitInt(value: MarcoIntLiteral, data: Data) -> ReturnType 37 | 38 | /** Double value handler. */ 39 | func visitDouble(value: MarcoDoubleLiteral, data: Data) -> ReturnType 40 | } 41 | 42 | public extension MarcoVisitor { 43 | func visitInt(value: MarcoIntLiteral, data: Data) -> ReturnType { 44 | return visitNumber(value: value, data: data) 45 | } 46 | 47 | func visitDouble(value: MarcoDoubleLiteral, data: Data) -> ReturnType { 48 | return visitNumber(value: value, data: data) 49 | } 50 | 51 | func visitDocument(value: MarcoDocument, data: Data) -> ReturnType { 52 | return visitValue(value: value, data: data) 53 | } 54 | 55 | func visitObject(value: MarcoObject, data: Data) -> ReturnType { 56 | return visitValue(value: value, data: data) 57 | } 58 | 59 | func visitArray(value: MarcoArray, data: Data) -> ReturnType { 60 | return visitValue(value: value, data: data) 61 | } 62 | 63 | func visitNull(value: MarcoNullLiteral, data: Data) -> ReturnType { 64 | return visitValue(value: value, data: data) 65 | } 66 | 67 | func visitBool(value: MarcoBoolLiteral, data: Data) -> ReturnType { 68 | return visitValue(value: value, data: data) 69 | } 70 | 71 | func visitString(value: MarcoStringLiteral, data: Data) -> ReturnType { 72 | return visitValue(value: value, data: data) 73 | } 74 | 75 | func visitNumber(value: MarcoNumberLiteral, data: Data) -> ReturnType { 76 | return visitValue(value: value, data: data) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /MarcoKit/API/NumberExtensions.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public extension MarcoNumberLiteral { 4 | var int32Value: Int32 { 5 | return Int32(intValue) 6 | } 7 | 8 | var uint32value: UInt32 { 9 | return UInt32(intValue) 10 | } 11 | 12 | var int64Value: Int64 { 13 | return Int64(intValue) 14 | } 15 | 16 | var uint64Value: UInt64 { 17 | return UInt64(intValue) 18 | } 19 | 20 | var floatValue: Float { 21 | return Float(doubleValue) 22 | } 23 | 24 | var cgFloatValue: CGFloat { 25 | return CGFloat(doubleValue) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /MarcoKit/API/Values/MarcoArray.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /** Marco array value. */ 4 | public protocol MarcoArray: MarcoValue { 5 | /** Element count. */ 6 | var count: Int { get } 7 | 8 | /** Returns an element on an `index` position. */ 9 | subscript(index: Int) -> MarcoValue { get set } 10 | 11 | /** Inserts an element on an `index` position. */ 12 | func insert(_ value: MarcoValue, at index: Int) 13 | 14 | /** Removes an element on an `index` position. */ 15 | func remove(at index: Int) 16 | } 17 | 18 | public extension MarcoArray { 19 | /** True if the array is empty. */ 20 | var isEmpty: Bool { 21 | return count == 0 22 | } 23 | 24 | /** True if the array is not empty. */ 25 | var isNotEmpty: Bool { 26 | return count > 0 27 | } 28 | 29 | /** Appends an element to the array. */ 30 | func add(_ value: MarcoValue) { 31 | insert(value, at: count) 32 | } 33 | 34 | /** Returns all elements. The new array instance will be created. */ 35 | var elements: [MarcoValue] { 36 | let count = self.count 37 | 38 | var result = [MarcoValue]() 39 | result.reserveCapacity(count) 40 | 41 | for i in 0.. ()) rethrows { 50 | for i in 0.. Bool) rethrows -> Bool { 57 | for i in 0.. Bool) rethrows -> Bool { 68 | return try !any(predicate: predicate) 69 | } 70 | 71 | /** Returns `true` if `predicate` returns `true` at least for one element. */ 72 | func any(predicate: (MarcoValue) throws -> Bool) rethrows -> Bool { 73 | for i in 0.. Bool) rethrows -> MarcoValue? { 84 | for i in 0.. Bool) rethrows -> Int? { 96 | for i in 0.. Bool { 108 | for i in 0.. MarcoArraySequence { 120 | return MarcoArraySequence(self) 121 | } 122 | 123 | func accept(_ visitor: V, data: D) -> R where V: MarcoVisitor, V.ReturnType == R, V.Data == D { 124 | return visitor.visitArray(value: self, data: data) 125 | } 126 | 127 | func equals(other: MarcoValue) -> Bool { 128 | guard let other = other as? MarcoArray, self.count == other.count else { return false } 129 | return (0.. MarcoArraySequence { 144 | return self 145 | } 146 | 147 | public mutating func next() -> MarcoValue? { 148 | precondition(array.count == self.count, "Concurrent array modification detected") 149 | guard self.count > index else { return nil } 150 | let result = array[index] 151 | index += 1 152 | return result 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /MarcoKit/API/Values/MarcoBoolLiteral.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /** Marco boolean value. */ 4 | public protocol MarcoBoolLiteral: MarcoValue { 5 | /** `Bool` value. */ 6 | var value: Bool { get } 7 | } 8 | 9 | public extension MarcoBoolLiteral { 10 | func accept(_ visitor: V, data: D) -> R where V: MarcoVisitor, V.ReturnType == R, V.Data == D { 11 | return visitor.visitBool(value: self, data: data) 12 | } 13 | 14 | func equals(other: MarcoValue) -> Bool { 15 | return self.value == (other as? MarcoBoolLiteral)?.value 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /MarcoKit/API/Values/MarcoDocument.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /** Marco document value. */ 4 | public protocol MarcoDocument: MarcoValue { 5 | var text: String { get } 6 | 7 | /** Root value. */ 8 | var value: MarcoValue { get set } 9 | 10 | /** Updates offsets for all child values recursively. */ 11 | func updateOffsets() 12 | } 13 | 14 | public extension MarcoDocument { 15 | func accept(_ visitor: V, data: D) -> R where V: MarcoVisitor, V.ReturnType == R, V.Data == D { 16 | return visitor.visitDocument(value: self, data: data) 17 | } 18 | 19 | var description: String { 20 | return value.description 21 | } 22 | 23 | func equals(other: MarcoValue) -> Bool { 24 | guard let other = other as? MarcoDocument else { return false } 25 | return self.value.equals(other: other.value) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /MarcoKit/API/Values/MarcoDoubleLiteral.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /** Marco double value. */ 4 | public protocol MarcoDoubleLiteral: MarcoNumberLiteral {} 5 | 6 | public extension MarcoDoubleLiteral { 7 | var intValue: Int { 8 | return Int(doubleValue) 9 | } 10 | 11 | func accept(_ visitor: V, data: D) -> R where V: MarcoVisitor, V.ReturnType == R, V.Data == D { 12 | return visitor.visitDouble(value: self, data: data) 13 | } 14 | 15 | func equals(other: MarcoValue) -> Bool { 16 | return self.doubleValue == (other as? MarcoDoubleLiteral)?.doubleValue 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /MarcoKit/API/Values/MarcoIntLiteral.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /** Marco integer value. */ 4 | public protocol MarcoIntLiteral: MarcoNumberLiteral { 5 | /** True if this literal is a color literal. */ 6 | var isColor: Bool { get } 7 | } 8 | 9 | public extension MarcoIntLiteral { 10 | var doubleValue: Double { 11 | return Double(intValue) 12 | } 13 | 14 | func accept(_ visitor: V, data: D) -> R where V: MarcoVisitor, V.ReturnType == R, V.Data == D { 15 | return visitor.visitInt(value: self, data: data) 16 | } 17 | 18 | func equals(other: MarcoValue) -> Bool { 19 | return self.intValue == (other as? MarcoIntLiteral)?.intValue 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /MarcoKit/API/Values/MarcoNullLiteral.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /** Marco null value. */ 4 | public protocol MarcoNullLiteral: MarcoValue {} 5 | 6 | public extension MarcoNullLiteral { 7 | func accept(_ visitor: V, data: D) -> R where V: MarcoVisitor, V.ReturnType == R, V.Data == D { 8 | return visitor.visitNull(value: self, data: data) 9 | } 10 | 11 | func equals(other: MarcoValue) -> Bool { 12 | return other is MarcoNullLiteral 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /MarcoKit/API/Values/MarcoNumberLiteral.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /** Marco number value. */ 4 | public protocol MarcoNumberLiteral: MarcoValue { 5 | /** Int value. Check if this value is `MarcoIntLiteral` to avoid conversion. */ 6 | var intValue: Int { get } 7 | 8 | /** Double value. Check if this value is `MarcoDoubleLiteral` to avoid conversion. */ 9 | var doubleValue: Double { get } 10 | } 11 | 12 | public extension MarcoNumberLiteral { 13 | func accept(_ visitor: V, data: D) -> R where V: MarcoVisitor, V.ReturnType == R, V.Data == D { 14 | return visitor.visitNumber(value: self, data: data) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /MarcoKit/API/Values/MarcoObject.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /** Marco object value. */ 4 | public protocol MarcoObject: MarcoValue { 5 | /** Element count. */ 6 | var count: Int { get } 7 | 8 | /** All value keys. */ 9 | var keys: [String] { get } 10 | 11 | /** Returns an element for a specified `key`. */ 12 | subscript(key: String) -> MarcoValue? { get set } 13 | 14 | /** Returns a key identifier for a specified `key`. */ 15 | func identifier(key: String) -> MarcoIdentifier? 16 | 17 | /** Removes an element with a specified `key`. */ 18 | @discardableResult 19 | func remove(for key: String) -> Bool 20 | } 21 | 22 | public extension MarcoObject { 23 | /** True if the object is empty. */ 24 | var isEmpty: Bool { 25 | return count == 0 26 | } 27 | 28 | /** True if the object is not empty. */ 29 | var isNotEmpty: Bool { 30 | return count > 0 31 | } 32 | 33 | /** Returns all elements. The new array instance will be created. */ 34 | var elements: [String: MarcoValue] { 35 | var result = [String: MarcoValue]() 36 | result.reserveCapacity(keys.count) 37 | 38 | for key in keys { 39 | result[key] = self[key] 40 | } 41 | 42 | return result 43 | } 44 | 45 | /** Iterates over all elements. */ 46 | func forEach(_ body: (String, MarcoValue) throws -> ()) rethrows { 47 | for key in keys { 48 | try body(key, self[key]!) 49 | } 50 | } 51 | 52 | /** Returns `true` if `predicate` returns `true` for all elements. */ 53 | func all(predicate: (String, MarcoValue) throws -> Bool) rethrows -> Bool { 54 | for key in keys { 55 | if !(try predicate(key, self[key]!)) { 56 | return false 57 | } 58 | } 59 | 60 | return true 61 | } 62 | 63 | /** Returns `true` if `predicate` returns `false` for all elements. */ 64 | func none(predicate: (String, MarcoValue) throws -> Bool) rethrows -> Bool { 65 | return try !any(predicate: predicate) 66 | } 67 | 68 | /** Returns `true` if `predicate` returns `true` at least for one element. */ 69 | func any(predicate: (String, MarcoValue) throws -> Bool) rethrows -> Bool { 70 | for key in keys { 71 | if try predicate(key, self[key]!) { 72 | return true 73 | } 74 | } 75 | 76 | return false 77 | } 78 | 79 | /** Returns an element with a specified key sequence. */ 80 | subscript(keys: String...) -> MarcoValue? { 81 | return self[keys] 82 | } 83 | 84 | /** Returns an element with a specified key sequence. */ 85 | subscript(keys: [String]) -> MarcoValue? { 86 | var current: MarcoValue = self 87 | for key in keys { 88 | guard let next = (current as? MarcoObject)?[key] else { return nil } 89 | current = next 90 | } 91 | 92 | return current 93 | } 94 | 95 | /** Returns a `Sequence` representation of the object. */ 96 | func sequence() -> MarcoObjectSequence { 97 | return MarcoObjectSequence(self) 98 | } 99 | 100 | func accept(_ visitor: V, data: D) -> R where V: MarcoVisitor, V.ReturnType == R, V.Data == D { 101 | return visitor.visitObject(value: self, data: data) 102 | } 103 | 104 | func equals(other: MarcoValue) -> Bool { 105 | guard let other = other as? MarcoObject else { return false } 106 | let selfKeys = self.keys, otherKeys = other.keys 107 | 108 | return selfKeys == otherKeys && selfKeys.allSatisfy { key in 109 | guard let selfItem = self[key], let otherItem = other[key] else { return false } 110 | return selfItem.equals(other: otherItem) 111 | } 112 | } 113 | } 114 | 115 | /** Marco object key identifier. */ 116 | public protocol MarcoIdentifier { 117 | /** Identifier text. */ 118 | var value: String { get } 119 | 120 | /** Element offset in a parent. Call `MarcoDocument`.`updateOffsets()` to initialize this property. */ 121 | var offset: Int { get } 122 | 123 | /** Element range. */ 124 | var range: Range { get } 125 | } 126 | 127 | public struct MarcoObjectSequence: Sequence, IteratorProtocol { 128 | private let object: MarcoObject 129 | private let keys: [String] 130 | private var index = 0 131 | 132 | init(_ object: MarcoObject) { 133 | self.object = object 134 | self.keys = object.keys 135 | } 136 | 137 | public func makeIterator() -> MarcoObjectSequence { 138 | return self 139 | } 140 | 141 | public mutating func next() -> (String, MarcoValue)? { 142 | precondition(object.count == keys.count, "Concurrent object modification detected") 143 | guard keys.count > index else { return nil } 144 | let key = keys[index] 145 | guard let value = object[key] else { preconditionFailure("Concurrent object modification detected") } 146 | index += 1 147 | return (key, value) 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /MarcoKit/API/Values/MarcoStringLiteral.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /** Marco string value. */ 4 | public protocol MarcoStringLiteral: MarcoValue { 5 | var value: String { get } 6 | } 7 | 8 | public extension MarcoStringLiteral { 9 | func accept(_ visitor: V, data: D) -> R where V: MarcoVisitor, V.ReturnType == R, V.Data == D { 10 | return visitor.visitString(value: self, data: data) 11 | } 12 | 13 | func equals(other: MarcoValue) -> Bool { 14 | return value == (other as? MarcoStringLiteral)?.value 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /MarcoKit/API/Values/MarcoValue.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /** Marco value. */ 4 | public protocol MarcoValue: AnyObject, CustomStringConvertible { 5 | /** Element offset in a parent. Call `MarcoDocument`.`updateOffsets()` to initialize this property. */ 6 | var offset: Int { get } 7 | 8 | /** Element range. */ 9 | var range: Range { get } 10 | 11 | /** Value text. */ 12 | var text: String { get } 13 | 14 | /** Pass this element to the given `visitor`. */ 15 | func accept(_ visitor: V, data: D) -> R where V : MarcoVisitor, V.ReturnType == R, V.Data == D 16 | 17 | /** Compares Marco nodes. Returns `true` if the content of both nodes is recursively equivalent. */ 18 | func equals(other: MarcoValue) -> Bool 19 | } 20 | 21 | public extension MarcoValue { 22 | var description: String { 23 | return text 24 | } 25 | 26 | func accept(_ visitor: V) -> R where V : MarcoVisitor, V.ReturnType == R, V.Data == () { 27 | return accept(visitor, data: ()) 28 | } 29 | 30 | /** Returns an underlying Marco document, or `nil` if the current value is not currently attached to a document. */ 31 | var document: MarcoDocument? { 32 | if let doc = self as? MarcoDocument { 33 | return doc 34 | } 35 | 36 | guard var parent = (self as! MarcoNode).parent else { return nil } 37 | while (true) { 38 | if let doc = parent as? MarcoDocument { 39 | return doc 40 | } 41 | 42 | guard let newParent = parent.parent else { return nil } 43 | parent = newParent 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /MarcoKit/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSHumanReadableCopyright 22 | Copyright © 2019-2020 Yan Zhulanow. All rights reserved. 23 | 24 | 25 | -------------------------------------------------------------------------------- /MarcoKit/MarcoKit.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | //! Project version number for marco. 4 | FOUNDATION_EXPORT double marcoVersionNumber; 5 | 6 | //! Project version string for marco. 7 | FOUNDATION_EXPORT const unsigned char marcoVersionString[]; 8 | -------------------------------------------------------------------------------- /MarcoKit/Node/MarcoNode.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | internal protocol MarcoNode : AnyObject { 4 | var offset: Int { get set } 5 | 6 | var parent: MarcoNode? { get set } 7 | var text: String { get } 8 | 9 | func clone() -> MarcoNode 10 | } 11 | 12 | internal protocol MarcoValueNode: MarcoNode, MarcoValue {} 13 | 14 | internal extension MarcoNode { 15 | var range: Range { 16 | return offset..<(offset + text.count) 17 | } 18 | } 19 | 20 | internal protocol MarcoTreeNode : MarcoNode { 21 | var children: [MarcoNode] { get set } 22 | 23 | func updateOffsets() 24 | } 25 | 26 | internal protocol MarcoCollectionNode : MarcoTreeNode, MarcoValueNode { 27 | var hasEnclosingElements: Bool { get } 28 | } 29 | 30 | internal protocol MarcoIdentifierLikeNode : MarcoNode, MarcoIdentifier { 31 | var value: String { get } 32 | } 33 | 34 | internal extension MarcoTreeNode { 35 | var text: String { 36 | var result = "" 37 | 38 | for child in children { 39 | result.append(child.text) 40 | } 41 | 42 | return result 43 | } 44 | 45 | func setSelfParentForChildren() { 46 | for child in children { 47 | precondition(child.parent == nil) 48 | child.parent = self 49 | } 50 | } 51 | 52 | func updateOffsets() { 53 | var offset = self.offset 54 | for child in children { 55 | child.offset = offset 56 | if let treeNode = child as? MarcoTreeNode { 57 | treeNode.updateOffsets() 58 | } 59 | offset += child.text.count 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /MarcoKit/Node/literal/MarcoBoolLiteralNode.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | internal class MarcoBoolLiteralNode : MarcoValueNode, MarcoBoolLiteral { 4 | weak var parent: MarcoNode? = nil 5 | var offset: Int = 0 6 | 7 | let value: Bool 8 | 9 | init(value: Bool) { 10 | self.value = value 11 | } 12 | 13 | var text: String { 14 | return value ? "true" : "false" 15 | } 16 | 17 | func clone() -> MarcoNode { 18 | return MarcoBoolLiteralNode(value: value) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /MarcoKit/Node/literal/MarcoIdentifierNode.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | internal class MarcoIdentifierNode : MarcoIdentifierLikeNode, CustomStringConvertible { 4 | weak var parent: MarcoNode? = nil 5 | var offset: Int = 0 6 | 7 | let value: String 8 | 9 | var text: String { 10 | return value 11 | } 12 | 13 | init(name: String) { 14 | self.value = name 15 | } 16 | 17 | func clone() -> MarcoNode { 18 | return MarcoIdentifierNode(name: value) 19 | } 20 | 21 | var description: String { 22 | return text 23 | } 24 | } -------------------------------------------------------------------------------- /MarcoKit/Node/literal/MarcoNullLiteralNode.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | internal class MarcoNullLiteralNode : MarcoValueNode, MarcoNullLiteral { 4 | weak var parent: MarcoNode? = nil 5 | var offset: Int = 0 6 | 7 | init() {} 8 | 9 | var text: String { 10 | return "null" 11 | } 12 | 13 | func clone() -> MarcoNode { 14 | return MarcoNullLiteralNode() 15 | } 16 | } 17 | 18 | -------------------------------------------------------------------------------- /MarcoKit/Node/literal/MarcoStringLiteralNode.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | internal class MarcoStringLiteralNode : MarcoValueNode, MarcoStringLiteral, MarcoIdentifierLikeNode { 4 | weak var parent: MarcoNode? = nil 5 | var offset: Int = 0 6 | 7 | let text: String 8 | let value: String 9 | 10 | init(text: String) { 11 | self.text = text 12 | self.value = MarcoStringLiteralNode.calculateValue(text: text) 13 | } 14 | 15 | init(value: String) { 16 | self.value = value 17 | self.text = MarcoStringLiteralNode.calculateText(value: value) 18 | } 19 | 20 | private init(text: String, value: String) { 21 | self.text = text 22 | self.value = value 23 | } 24 | 25 | private static func calculateValue(text: String) -> String { 26 | precondition(text.first == "\"" && text.last == "\"") 27 | 28 | var value: String = "" 29 | var iterator = text.dropFirst().dropLast().makeIterator() 30 | 31 | while let current = iterator.next() { 32 | if (current == "\"") { 33 | preconditionFailure("Unexpected '\"' in String literal \(text)") 34 | } else if (current == "\\") { 35 | guard let escaped = iterator.next() else { 36 | preconditionFailure("Unexpected escaped character in String literal \(text)") 37 | } 38 | switch (escaped) { 39 | case "\"": value.append("\"") 40 | case "\\": value.append("\\") 41 | case "n": value.append("\n") 42 | case "t": value.append("\t") 43 | case "r": value.append("\r") 44 | case "u": 45 | let chars = String([iterator.next()!, iterator.next()!, iterator.next()!, iterator.next()!]) 46 | guard let hex = Int(chars, radix: 16) else { preconditionFailure("Invalid hex literal: \(chars)") } 47 | guard let scalar = UnicodeScalar(hex) else { preconditionFailure("Invalid scalar: \(chars)") } 48 | value.append(Character(scalar)) 49 | default: value.append(escaped) 50 | } 51 | } else { 52 | value.append(current) 53 | } 54 | } 55 | 56 | return value 57 | } 58 | 59 | private static func calculateText(value: String) -> String { 60 | var text: String = "\"" 61 | 62 | for char in value { 63 | switch (char) { 64 | case "\"": text.append("\\\"") 65 | case "\\": text.append("\\\\") 66 | case "\n": text.append("\\n") 67 | case "\t": text.append("\\t") 68 | case "\r": text.append("\\r") 69 | default: 70 | let scalars = char.unicodeScalars 71 | if scalars.count == 1, let scalar = scalars.first, scalar.isASCII && scalar.value < 32 { 72 | text.append("\\u" + getScalarString(scalar: scalar)) 73 | } else { 74 | text.append(char) 75 | } 76 | } 77 | } 78 | 79 | return text + "\"" 80 | } 81 | 82 | private static func getScalarString(scalar: UnicodeScalar) -> String { 83 | let rawText = String(scalar.value, radix: 16, uppercase: true) 84 | precondition(rawText.count <= 4) 85 | if (rawText.count < 4) { 86 | return String(repeating: "0", count: 4 - rawText.count) + rawText 87 | } else { 88 | return rawText 89 | } 90 | } 91 | 92 | func clone() -> MarcoNode { 93 | return MarcoStringLiteralNode(text: text, value: value) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /MarcoKit/Node/literal/MarcoStructuralElementNode.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | internal class MarcoStructuralElementNode : MarcoNode, CustomStringConvertible { 4 | weak var parent: MarcoNode? = nil 5 | var offset: Int = 0 6 | 7 | let kind: Kind 8 | 9 | init(_ kind: Kind) { 10 | self.kind = kind 11 | } 12 | 13 | var text: String { 14 | return String(kind.rawValue) 15 | } 16 | 17 | enum Kind : Character { 18 | case leftSquareBracket = "[", rightSquareBracket = "]", 19 | leftCurlyBracket = "{", rightCurlyBracket = "}", 20 | ignoring = "!" 21 | } 22 | 23 | func clone() -> MarcoNode { 24 | return MarcoStructuralElementNode(kind) 25 | } 26 | 27 | var description: String { 28 | return text 29 | } 30 | } -------------------------------------------------------------------------------- /MarcoKit/Node/literal/MarcoWhitespaceNode.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | internal class MarcoWhitespaceNode : MarcoNode, CustomStringConvertible { 4 | weak var parent: MarcoNode? = nil 5 | var offset: Int = 0 6 | 7 | let text: String 8 | let containsNewLine: Bool 9 | 10 | init(text: String, containsNewLine: Bool) { 11 | self.text = text 12 | self.containsNewLine = containsNewLine 13 | } 14 | 15 | func clone() -> MarcoNode { 16 | return MarcoWhitespaceNode(text: text, containsNewLine: containsNewLine) 17 | } 18 | 19 | var description: String { 20 | return text 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /MarcoKit/Node/literal/number/MarcoColorLiteralNode.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | internal class MarcoColorLiteralNode : MarcoValueNode, MarcoIntLiteral { 4 | weak var parent: MarcoNode? = nil 5 | var offset: Int = 0 6 | 7 | let text: String 8 | let intValue: Int 9 | let useAlpha: Bool 10 | 11 | var isColor: Bool { 12 | return true 13 | } 14 | 15 | init(text: String) { 16 | precondition(MarcoColorLiteralNode.checkSyntax(text: text)) 17 | self.text = text 18 | (intValue, useAlpha) = MarcoColorLiteralNode.calculateValue(text: text) 19 | } 20 | 21 | private init(text: String, value: Int, useAlpha: Bool) { 22 | self.text = text 23 | self.intValue = value 24 | self.useAlpha = useAlpha 25 | } 26 | 27 | private static func checkSyntax(text: String) -> Bool { 28 | guard text.count == 4 || text.count == 7 || text.count == 9 else { return false } 29 | guard text[text.startIndex] == "#" else { return false } 30 | guard (text.dropFirst().allSatisfy { HEX_SYMBOL_SET.contains($0) }) else { return false } 31 | return true 32 | } 33 | 34 | private static func calculateValue(text: String) -> (Int, Bool) { 35 | if (text.count == 4) { 36 | var full = String(text[text.startIndex]) 37 | for ch in text.dropFirst() { 38 | full.append(ch) 39 | full.append(ch) 40 | } 41 | return MarcoColorLiteralNode.calculateValue(text: full) 42 | } 43 | 44 | let useAlpha = text.count == 9 45 | let scanner = Scanner(string: text) 46 | scanner.currentIndex = text.index(after: text.startIndex) 47 | 48 | let value = scanner.scanUInt64(representation: .hexadecimal) ?? 0 49 | return (Int(value), useAlpha) 50 | } 51 | 52 | func clone() -> MarcoNode { 53 | return MarcoColorLiteralNode(text: text, value: intValue, useAlpha: useAlpha) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /MarcoKit/Node/literal/number/MarcoDoubleLiteralNode.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | internal class MarcoDoubleLiteralNode : MarcoValueNode, MarcoDoubleLiteral { 4 | weak var parent: MarcoNode? = nil 5 | var offset: Int = 0 6 | 7 | let text: String 8 | let doubleValue: Double 9 | 10 | init(text: String) { 11 | self.text = text 12 | self.doubleValue = Double(text) ?? 0 13 | } 14 | 15 | init(value: Double) { 16 | self.doubleValue = value 17 | self.text = String(value) 18 | } 19 | 20 | private init(text: String, value: Double) { 21 | self.doubleValue = value 22 | self.text = text 23 | } 24 | 25 | func clone() -> MarcoNode { 26 | return MarcoDoubleLiteralNode(text: text, value: doubleValue) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /MarcoKit/Node/literal/number/MarcoHexLiteralNode.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | internal let HEX_SYMBOL_SET: Set = Set("0123456789ABCDEFabcdef") 4 | 5 | internal class MarcoHexLiteralNode : MarcoValueNode, MarcoIntLiteral { 6 | weak var parent: MarcoNode? = nil 7 | var offset: Int = 0 8 | 9 | let text: String 10 | let intValue: Int 11 | 12 | var isColor: Bool { 13 | return false 14 | } 15 | 16 | init(text: String) { 17 | precondition(MarcoHexLiteralNode.checkSyntax(text: text)) 18 | self.text = text 19 | self.intValue = MarcoHexLiteralNode.calculateValue(text: text) 20 | } 21 | 22 | private init(text: String, value: Int) { 23 | self.text = text 24 | self.intValue = value 25 | } 26 | 27 | private static func checkSyntax(text: String) -> Bool { 28 | guard text.count >= 3, text.first == "0" else { return false } 29 | 30 | let x = text[text.index(after: text.startIndex)] 31 | guard 32 | x == "X" || x == "x", 33 | (text.dropFirst(2).allSatisfy { HEX_SYMBOL_SET.contains($0) }) 34 | else { return false } 35 | 36 | return true 37 | } 38 | 39 | private static func calculateValue(text: String) -> Int { 40 | let scanner = Scanner(string: text) 41 | scanner.currentIndex = text.index(text.startIndex, offsetBy: 2) 42 | 43 | let value = scanner.scanInt64(representation: .hexadecimal) ?? 0 44 | return Int(value) 45 | } 46 | 47 | func clone() -> MarcoNode { 48 | return MarcoHexLiteralNode(text: text, value: intValue) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /MarcoKit/Node/literal/number/MarcoIntLiteralNode.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | internal class MarcoIntLiteralNode : MarcoValueNode, MarcoIntLiteral { 4 | weak var parent: MarcoNode? = nil 5 | var offset: Int = 0 6 | 7 | let text: String 8 | let intValue: Int 9 | 10 | var isColor: Bool { 11 | return false 12 | } 13 | 14 | init(text: String) { 15 | self.text = text 16 | self.intValue = Int(text) ?? 0 17 | } 18 | 19 | init(value: Int) { 20 | self.intValue = value 21 | self.text = String(intValue) 22 | } 23 | 24 | private init(text: String, value: Int) { 25 | self.text = text 26 | self.intValue = value 27 | } 28 | 29 | func clone() -> MarcoNode { 30 | return MarcoIntLiteralNode(text: text, value: intValue) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /MarcoKit/Node/tree/CollectionUtil.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | internal extension MarcoCollectionNode { 4 | func collectionRemove(rawIndex: Int) -> Range { 5 | let range: Range 6 | 7 | let itemCount = children.reduce(0) { (count, node) in count + (isItem(node) ? 1 : 0) } 8 | 9 | if (itemCount == 1) { 10 | let startIndex = hasEnclosingElements ? 1 : 0 11 | let endIndex = children.count - 1 - (hasEnclosingElements ? 1 : 0) 12 | range = startIndex..<(endIndex + 1) 13 | } else if ((children.first { isItem($0) }) === children[rawIndex]) { 14 | let startIndex = rawIndex 15 | var endIndex = startIndex + 1 16 | while (endIndex < children.count - 1) { 17 | guard children[endIndex] is MarcoWhitespaceNode else { break } 18 | endIndex += 1 19 | } 20 | 21 | range = startIndex.. 0) { 26 | guard children[startIndex] is MarcoWhitespaceNode else { break } 27 | startIndex -= 1 28 | } 29 | 30 | range = (startIndex + 1)..<(endIndex + 1) 31 | } 32 | 33 | range.forEach { children[$0].parent = nil } 34 | children.removeSubrange(range) 35 | 36 | return range 37 | } 38 | 39 | func collectionInsert(index: Int, node valueNode: MarcoNode) -> (delta: Int, pos: Int) { 40 | func insertNodes(_ nodes: MarcoNode..., at index: Int) { 41 | nodes.forEach { $0.parent = self } 42 | children.insert(contentsOf: nodes, at: index) 43 | } 44 | 45 | func getRawIndex(index: Int, includeIgnored: Bool) -> Int { 46 | var isIgnored = false 47 | var current = 0 48 | 49 | for (rawIndex, child) in children.enumerated() { 50 | if !isIgnored, isItem(child) { 51 | if (current == index) { 52 | return rawIndex 53 | } 54 | current += 1 55 | } 56 | 57 | isIgnored = !includeIgnored && child.isIgnoring 58 | } 59 | 60 | preconditionFailure("Child with index \(index) not found") 61 | } 62 | 63 | let itemCount = children.reduce(0) { (count, node) in count + (isItem(node) ? 1 : 0) } 64 | 65 | if (itemCount == 0) { 66 | var newChildren: [MarcoNode] 67 | 68 | if !isComplexNode(valueNode) { 69 | newChildren = [WS(" "), valueNode, WS(" ")] 70 | } else { 71 | let indentInParent = self.indentInParent() 72 | let indent = indentInParent + SINGLE_INDENT 73 | valueNode.applyIndent(indent: indent) 74 | 75 | newChildren = [WSnl("\n" + indent), valueNode, WSnl("\n" + indentInParent)] 76 | } 77 | 78 | if hasEnclosingElements { 79 | newChildren.insert(children.first!, at: 0) 80 | newChildren.append(children.last!) 81 | } 82 | 83 | newChildren.forEach { $0.parent = self } 84 | let oldChildren = self.children 85 | self.children = newChildren 86 | oldChildren.forEach { $0.parent = nil } 87 | 88 | return (0, hasEnclosingElements ? 2 : 1) 89 | } 90 | 91 | let selfContainsNewLine = children.first { ($0 as? MarcoWhitespaceNode)?.containsNewLine ?? false } != nil 92 | if (!selfContainsNewLine && isComplexNode(valueNode)) { 93 | prettifySelf() 94 | } 95 | 96 | if (index == 0) { 97 | let whitespace = whitespaceBeforeChild(index: getRawIndex(index: 0, includeIgnored: true)) 98 | 99 | if (whitespace.contains("\n")) { 100 | let indent = whitespace.textAfterNewLine() 101 | valueNode.applyIndent(indent: indent + SINGLE_INDENT) 102 | 103 | if let existingWhitespace = children[1] as? MarcoWhitespaceNode, existingWhitespace.containsNewLine { 104 | insertNodes(WSnl("\n" + indent), valueNode, at: 1) 105 | return (2, 2) 106 | } else { 107 | insertNodes(WSnl("\n" + indent), valueNode, WSnl("\n" + indent), at: 1) 108 | return (3, 2) 109 | } 110 | } else { 111 | insertNodes(valueNode, WS(" "), at: 1) 112 | return (2, 1) 113 | } 114 | } 115 | 116 | let rawIndexBeforeElement: Int 117 | if (index == Int.max) { 118 | rawIndexBeforeElement = (children.lastIndex { isItem($0) })! 119 | } else { 120 | rawIndexBeforeElement = getRawIndex(index: index - 1, includeIgnored: false) 121 | } 122 | 123 | let rawIndexToInsert = rawIndexBeforeElement + 1 124 | let whitespace = whitespaceBeforeChild(index: rawIndexBeforeElement) 125 | 126 | if (whitespace.contains("\n") || itemCount == 1) { 127 | let indent = whitespace.textAfterNewLine() 128 | valueNode.applyIndent(indent: indent) 129 | insertNodes(WSnl("\n" + indent), valueNode, at: rawIndexToInsert) 130 | } else { 131 | insertNodes(WS(" "), valueNode, at: rawIndexToInsert) 132 | } 133 | 134 | return (2, rawIndexToInsert + 1) 135 | } 136 | 137 | private func prettifySelf() { 138 | let indentInParent = self.indentInParent() 139 | let visitor = MutatingPrettifyingVisitor(forceNewLine: true, isRecursive: false, reorderKeys: false) 140 | _ = self.accept(visitor, data: 0) 141 | self.applyIndent(indent: indentInParent) 142 | } 143 | 144 | private func isItem(_ node: MarcoNode) -> Bool { 145 | return node is MarcoValueNode || node is MarcoKeyValuePairNode 146 | } 147 | 148 | private func isComplexNode(_ node: MarcoNode) -> Bool { 149 | return node is MarcoCollectionNode 150 | || node is MarcoStringLiteralNode 151 | || node is MarcoKeyValuePairNode 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /MarcoKit/Node/tree/MarcoArrayNode.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | internal class MarcoArrayNode : MarcoCollectionNode, MarcoValueNode, MarcoArray { 4 | weak var parent: MarcoNode? = nil 5 | var offset: Int = 0 6 | 7 | var children: [MarcoNode] 8 | var elementIndices: [Int] 9 | 10 | init(children: [MarcoNode], elementIndices: [Int]) { 11 | self.children = children 12 | self.elementIndices = elementIndices 13 | 14 | setSelfParentForChildren() 15 | } 16 | 17 | var hasEnclosingElements: Bool { 18 | return true 19 | } 20 | 21 | var count: Int { 22 | return elementIndices.count 23 | } 24 | 25 | subscript(index: Int) -> MarcoValue { 26 | get { 27 | checkElementIndex(index) 28 | return children[elementIndices[index]] as! MarcoValue 29 | } 30 | set(value) { 31 | checkElementIndex(index) 32 | 33 | let valueNode = castToNodeCheckParent(value.unwrapDocument()) 34 | let rawIndex = elementIndices[index] 35 | valueNode.applyIndent(indent: whitespaceBeforeChild(index: rawIndex).textAfterNewLine()) 36 | valueNode.parent = self 37 | 38 | let previousNode = children[rawIndex] 39 | children[rawIndex] = valueNode 40 | previousNode.parent = nil 41 | } 42 | } 43 | 44 | var elements: [MarcoValue] { 45 | var result = [MarcoValue]() 46 | result.reserveCapacity(elementIndices.count) 47 | 48 | for index in elementIndices { 49 | result.append(children[index] as! MarcoValue) 50 | } 51 | 52 | return result 53 | } 54 | 55 | func insert(_ value: MarcoValue, at index: Int) { 56 | checkElementIndex(index, allowSizeIndex: true) 57 | let valueNode = castToNodeCheckParent(value.unwrapDocument()) 58 | 59 | let (delta, pos) = collectionInsert(index: index, node: valueNode) 60 | 61 | if (delta != 0) { 62 | shiftElementIndices(from: index, delta: delta) 63 | } 64 | 65 | elementIndices.insert(pos, at: index) 66 | } 67 | 68 | func remove(at index: Int) { 69 | checkElementIndex(index) 70 | 71 | let range = collectionRemove(rawIndex: elementIndices[index]) 72 | 73 | shiftElementIndices(from: index + 1, delta: -range.count) 74 | elementIndices.remove(at: index) 75 | } 76 | 77 | func clone() -> MarcoNode { 78 | let newChildren = children.map { $0.clone() } 79 | return MarcoArrayNode(children: newChildren, elementIndices: elementIndices) 80 | } 81 | 82 | private func checkElementIndex(_ index: Int, allowSizeIndex: Bool = false) { 83 | let indexLimit = allowSizeIndex ? elements.count + 1 : elements.count 84 | precondition( 85 | index >= 0 && index < indexLimit, 86 | "Index \(index) is out of bounds: [0, \(indexLimit))") 87 | } 88 | 89 | private func shiftElementIndices(from: Int, delta: Int) { 90 | var index = from 91 | while (index < elementIndices.count) { 92 | elementIndices[index] += delta 93 | index += 1 94 | } 95 | } 96 | } -------------------------------------------------------------------------------- /MarcoKit/Node/tree/MarcoDocumentNode.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | internal class MarcoDocumentNode : MarcoTreeNode, MarcoValueNode, MarcoDocument { 4 | weak var parent: MarcoNode? = nil 5 | var offset: Int = 0 6 | 7 | var children: [MarcoNode] 8 | var valueIndex: Int 9 | 10 | init(children: [MarcoNode], valueIndex: Int) { 11 | self.children = children 12 | self.valueIndex = valueIndex 13 | 14 | setSelfParentForChildren() 15 | } 16 | 17 | var value: MarcoValue { 18 | get { 19 | return children[valueIndex] as! MarcoValue 20 | } 21 | set { 22 | let oldValueNode = value as! MarcoValueNode 23 | let newValueNode = newValue as! MarcoValueNode 24 | 25 | newValueNode.parent = self 26 | children[valueIndex] = newValueNode 27 | oldValueNode.parent = nil 28 | } 29 | } 30 | 31 | func clone() -> MarcoNode { 32 | return MarcoDocumentNode(children: children, valueIndex: valueIndex) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /MarcoKit/Node/tree/MarcoKeyValuePairNode.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | internal class MarcoKeyValuePairNode : MarcoTreeNode { 4 | weak var parent: MarcoNode? = nil 5 | var offset: Int = 0 6 | 7 | var children: [MarcoNode] 8 | 9 | private let keyIndex: Int 10 | private let valueIndex: Int 11 | 12 | init(children: [MarcoNode], keyIndex: Int, valueIndex: Int) { 13 | self.children = children 14 | self.keyIndex = keyIndex 15 | self.valueIndex = valueIndex 16 | 17 | setSelfParentForChildren() 18 | } 19 | 20 | var value: MarcoValueNode { 21 | get { 22 | return children[valueIndex] as! MarcoValueNode 23 | } 24 | set { 25 | let oldValue = value 26 | newValue.parent = self 27 | children[valueIndex] = newValue 28 | oldValue.parent = nil 29 | } 30 | } 31 | 32 | var key: MarcoIdentifierLikeNode { 33 | return children[keyIndex] as! MarcoIdentifierLikeNode 34 | } 35 | 36 | func clone() -> MarcoNode { 37 | let newChildren = children.map { $0.clone() } 38 | return MarcoKeyValuePairNode(children: newChildren, keyIndex: keyIndex, valueIndex: valueIndex) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /MarcoKit/Node/tree/MarcoObjectNode.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | internal class MarcoObjectNode : MarcoCollectionNode, MarcoValueNode, MarcoObject { 4 | let hasEnclosingElements: Bool 5 | 6 | weak var parent: MarcoNode? = nil 7 | var offset: Int = 0 8 | 9 | var children: [MarcoNode] 10 | var keyMappings: [String: Int] 11 | 12 | init(children: [MarcoNode], keyMappings: [String: Int], isConfig: Bool) { 13 | self.hasEnclosingElements = !isConfig 14 | self.children = children 15 | self.keyMappings = keyMappings 16 | 17 | setSelfParentForChildren() 18 | } 19 | 20 | var count: Int { 21 | return keyMappings.count 22 | } 23 | 24 | var keys: [String] { 25 | return keyMappings.sorted { (f, s) in f.value < s.value }.map { $0.key } 26 | } 27 | 28 | subscript(key: String) -> MarcoValue? { 29 | get { 30 | guard let index = keyMappings[key] else { return nil } 31 | return (children[index] as! MarcoKeyValuePairNode).value 32 | } 33 | set { 34 | guard let value = newValue?.unwrapDocument() else { 35 | _ = remove(for: key) 36 | return 37 | } 38 | 39 | guard let rawIndex = keyMappings[key] else { 40 | insert(key: key, value: value) 41 | return 42 | } 43 | 44 | let valueNode = castToNodeCheckParent(value) 45 | valueNode.applyIndent(indent: whitespaceBeforeChild(index: rawIndex).textAfterNewLine()) 46 | (children[rawIndex] as! MarcoKeyValuePairNode).value = valueNode 47 | } 48 | } 49 | 50 | func identifier(key: String) -> MarcoIdentifier? { 51 | guard let index = keyMappings[key] else { return nil } 52 | return (children[index] as! MarcoKeyValuePairNode).key 53 | } 54 | 55 | func clone() -> MarcoNode { 56 | let newChildren = children.map { $0.clone() } 57 | return MarcoObjectNode(children: newChildren, keyMappings: keyMappings, isConfig: !hasEnclosingElements) 58 | } 59 | 60 | @discardableResult 61 | func remove(for key: String) -> Bool { 62 | guard let rawIndex = keyMappings[key] else { return false } 63 | 64 | let delta = collectionRemove(rawIndex: rawIndex).count 65 | 66 | var newKeyMappings = self.keyMappings 67 | newKeyMappings.removeValue(forKey: key) 68 | 69 | for (key, value) in newKeyMappings { 70 | if (value > rawIndex) { 71 | newKeyMappings[key] = value - delta 72 | } 73 | } 74 | 75 | self.keyMappings = newKeyMappings 76 | 77 | return true 78 | } 79 | 80 | private func insert(key: String, value: MarcoValue) { 81 | let index = keyMappings.count 82 | 83 | let keyNode: MarcoNode = MarcoParser.isSimpleKey(key: key) 84 | ? MarcoIdentifierNode(name: key) : MarcoStringLiteralNode(value: key) 85 | 86 | let valueNode = castToNodeCheckParent(value.unwrapDocument()) 87 | 88 | let keyValuePairNode = MarcoKeyValuePairNode( 89 | children: [keyNode, WS(" "), valueNode], 90 | keyIndex: 0, valueIndex: 2) 91 | 92 | let (delta, pos) = collectionInsert(index: index, node: keyValuePairNode) 93 | 94 | var newKeyMappings = self.keyMappings 95 | if (delta != 0) { 96 | for (key, value) in newKeyMappings { 97 | if (value >= pos) { 98 | newKeyMappings[key] = value + delta 99 | } 100 | } 101 | } 102 | 103 | newKeyMappings[key] = pos 104 | self.keyMappings = newKeyMappings 105 | } 106 | 107 | private func isFirstPair(index: Int) -> Bool { 108 | var current = index - 1 109 | while (current > 0) { 110 | if (children[current] is MarcoKeyValuePairNode) { 111 | return false 112 | } 113 | current -= 1 114 | } 115 | 116 | return true 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /MarcoKit/Parser/MarcoParserState.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | internal class MarcoParserState { 4 | private let text: String 5 | private let showContextInErrors: Bool 6 | 7 | private(set) var index: String.Index 8 | 9 | private(set) var recordedErrors = [MarcoParsingError]() 10 | 11 | init(text: String, showContextInErrors: Bool) { 12 | self.text = text 13 | self.showContextInErrors = showContextInErrors 14 | 15 | self.index = self.text.startIndex 16 | } 17 | 18 | var isEof: Bool { 19 | return index == text.endIndex 20 | } 21 | 22 | func advance() throws(MarcoParsingError) -> Character { 23 | try checkIndex() 24 | let ch = text[index] 25 | index = text.index(after: index) 26 | return ch 27 | } 28 | 29 | func current() throws(MarcoParsingError) -> Character { 30 | try checkIndex() 31 | return text[index] 32 | } 33 | 34 | func currentOrNull() -> Character? { 35 | guard !isEof else { return nil } 36 | return text[index] 37 | } 38 | 39 | func record(error: MarcoParsingError) { 40 | recordedErrors.append(error) 41 | } 42 | 43 | private func checkIndex() throws(MarcoParsingError) { 44 | guard !isEof else { 45 | let lineRange = text.lineRange(for: lastCharacterRange()) 46 | throw error("Unexpected end of file", range: lineRange) 47 | } 48 | } 49 | } 50 | 51 | internal extension MarcoParserState { 52 | func skip() throws(MarcoParsingError) { 53 | _ = try advance() 54 | } 55 | 56 | func match(_ char: Character) throws(MarcoParsingError) { 57 | let indexBefore = self.index 58 | if ((try advance()) != char) { 59 | throw unexpectedCharacter(expected: char, range: indexBefore.. Range { 76 | guard !text.isEmpty else { return text.startIndex..? = nil 89 | ) -> MarcoParsingError { 90 | let actual: String 91 | if let char = currentOrNull() { 92 | switch (char) { 93 | case "\n": actual = "\\n" 94 | case "\r": actual = "\\r" 95 | case "\t": actual = "\\t" 96 | default: actual = String(char) 97 | } 98 | } else { 99 | actual = "" 100 | } 101 | 102 | var message = "\(title) '\(actual)'" 103 | if let char = expected { 104 | message.append(", expected '\(char)'") 105 | } 106 | 107 | return error(message, range: range ?? lastCharacterRange()) 108 | } 109 | 110 | func error(_ message: String) -> MarcoParsingError { 111 | return MarcoParsingError(message: message, range: lastCharacterRange()) 112 | } 113 | 114 | func error(_ message: String, indexBefore: String.Index) -> MarcoParsingError { 115 | return MarcoParsingError(message: message, range: indexBefore..) -> MarcoParsingError { 119 | return MarcoParsingError(message: message, range: range) 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /MarcoKit/Utilities/JsonToMarcoConverter.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | internal struct JsonToMarcoConverter { 4 | static let instance = JsonToMarcoConverter() 5 | private init() {} 6 | 7 | func convert(json: Any?) -> MarcoDocument { 8 | let node = convertValue(json: json) 9 | return MarcoDocumentNode(children: [node], valueIndex: 0) 10 | } 11 | 12 | func convertConfig(json: [String: Any]) -> MarcoDocument { 13 | let node = convertObject(json, isConfig: true) 14 | return MarcoDocumentNode(children: [node], valueIndex: 0) 15 | } 16 | 17 | private func convertValue(json: Any?) -> MarcoValueNode { 18 | if (json is NSNull) { 19 | return MarcoNullLiteralNode() 20 | } else if let json = json as? String { 21 | return MarcoStringLiteralNode(value: json) 22 | } else if let json = json as? NSNumber { 23 | let typeId = CFGetTypeID(json as CFTypeRef) 24 | if (typeId == CFBooleanGetTypeID()) { 25 | return MarcoBoolLiteralNode(value: json.boolValue) 26 | } else if (typeId == CFNumberGetTypeID()) { 27 | switch (CFNumberGetType(json as CFNumber)) { 28 | case .sInt8Type, .sInt16Type, .sInt32Type, .sInt64Type, .cfIndexType, 29 | .shortType, .intType, .longType, .longLongType, .nsIntegerType: 30 | return MarcoIntLiteralNode(value: json.intValue) 31 | case .float32Type, .float64Type, .floatType, .doubleType, .cgFloatType: 32 | return MarcoDoubleLiteralNode(value: json.doubleValue) 33 | case .charType: 34 | return MarcoStringLiteralNode(value: json.stringValue) 35 | @unknown default: 36 | preconditionFailure("Unexpected value type") 37 | } 38 | } else { 39 | preconditionFailure("Unknown element type: \(json)") 40 | } 41 | } else if let json = json as? [Any] { 42 | return convertArray(json) 43 | } else if let json = json as? [String: Any] { 44 | return convertObject(json, isConfig: false) 45 | } else { 46 | preconditionFailure("Unknown element type: \(String(describing: json))") 47 | } 48 | } 49 | 50 | private func convertObject(_ dict: [String: Any], isConfig: Bool) -> MarcoObjectNode { 51 | var nodes = [MarcoNode]() 52 | var keyMappings = [String: Int]() 53 | 54 | nodes.reserveCapacity(dict.count * 2 - 1 + (isConfig ? 0 : 2)) 55 | keyMappings.reserveCapacity(dict.count) 56 | 57 | if (!isConfig) { 58 | nodes.append(MarcoStructuralElementNode(.leftCurlyBracket)) 59 | } 60 | 61 | let keys = dict.keys.sorted() 62 | var isFirst = true 63 | 64 | for key in keys { 65 | let value = dict[key] 66 | let keyNode: MarcoNode = MarcoParser.isSimpleKey(key: key) 67 | ? MarcoIdentifierNode(name: key) : MarcoStringLiteralNode(value: key) 68 | let valueNode = convertValue(json: value) 69 | 70 | if (isFirst) { 71 | isFirst = false 72 | } else { 73 | nodes.append(WS(" ")) 74 | } 75 | 76 | keyMappings[key] = nodes.count 77 | nodes.append(MarcoKeyValuePairNode(children: [keyNode, WS(" "), valueNode], keyIndex: 0, valueIndex: 2)) 78 | } 79 | 80 | if (!isConfig) { 81 | nodes.append(MarcoStructuralElementNode(.rightCurlyBracket)) 82 | } 83 | 84 | return MarcoObjectNode(children: nodes, keyMappings: keyMappings, isConfig: isConfig) 85 | } 86 | 87 | private func convertArray(_ array: [Any]) -> MarcoArrayNode { 88 | var nodes = [MarcoNode]() 89 | var elementIndices = [Int]() 90 | 91 | nodes.reserveCapacity(array.count * 2 + 1) 92 | elementIndices.reserveCapacity(array.count) 93 | 94 | nodes.append(MarcoStructuralElementNode(.leftSquareBracket)) 95 | 96 | for value in array { 97 | if (!nodes.isEmpty) { 98 | nodes.append(WS(" ")) 99 | } 100 | 101 | elementIndices.append(nodes.count) 102 | nodes.append(convertValue(json: value)) 103 | } 104 | 105 | nodes.append(MarcoStructuralElementNode(.rightSquareBracket)) 106 | 107 | return MarcoArrayNode(children: nodes, elementIndices: elementIndices) 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /MarcoKit/Utilities/MarcoNodeUtil.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | internal let SINGLE_INDENT = " " 4 | 5 | internal func castToNodeCheckParent(_ value: MarcoValue) -> MarcoValueNode { 6 | guard let node = value as? MarcoValueNode 7 | else { preconditionFailure("Value \(value.description) must be a node") } 8 | checkParent(node) 9 | return node 10 | } 11 | 12 | internal func checkParent(_ node: MarcoNode) { 13 | guard node.parent == nil 14 | else { preconditionFailure("Node \(node.text) already has a parent: \(node.parent.debugDescription)") } 15 | } 16 | 17 | internal extension MarcoValue { 18 | func unwrapDocument() -> MarcoValue { 19 | if let document = self as? MarcoDocument { 20 | return document.value 21 | } else { 22 | return self 23 | } 24 | } 25 | } 26 | 27 | internal extension MarcoNode { 28 | func withOffset(_ offset: Int) -> MarcoNode { 29 | self.offset = offset 30 | return self 31 | } 32 | 33 | var isIgnoring: Bool { 34 | if let structural = self as? MarcoStructuralElementNode, structural.kind == .ignoring { 35 | return true 36 | } 37 | 38 | return false 39 | } 40 | 41 | func indentInParent() -> String { 42 | guard let parent = self.parent as? MarcoTreeNode else { return "" } 43 | if (parent is MarcoKeyValuePairNode) { 44 | return parent.indentInParent() 45 | } 46 | 47 | guard var index = indexInParent() else { return "" } 48 | index -= 1 49 | 50 | while (index >= 0) { 51 | defer { index -= 1 } 52 | let node = parent.children[index] 53 | 54 | if node.isIgnoring { 55 | continue 56 | } else if let whitespace = node as? MarcoWhitespaceNode { 57 | return whitespace.text.textAfterNewLine() 58 | } else { 59 | break 60 | } 61 | } 62 | 63 | return "" 64 | } 65 | 66 | func indexInParent() -> Int? { 67 | guard let parent = self.parent as? MarcoTreeNode else { return nil } 68 | 69 | for (index, node) in parent.children.enumerated() { 70 | if (node === self) { 71 | return index 72 | } 73 | } 74 | 75 | return nil 76 | } 77 | 78 | func applyIndent(indent: String) { 79 | guard let treeNode = self as? MarcoTreeNode else { return } 80 | treeNode.applyIndent(indent: indent) 81 | } 82 | } 83 | 84 | internal extension MarcoTreeNode { 85 | func applyIndent(indent: String) { 86 | guard !indent.isEmpty else { return } 87 | 88 | for index in 0.. String { 106 | var currentIndex = index - 1 107 | while (currentIndex > 0) { 108 | defer { currentIndex -= 1} 109 | 110 | let node = children[currentIndex] 111 | if (node.isIgnoring) { 112 | continue 113 | } else if let whitespace = node as? MarcoWhitespaceNode { 114 | return whitespace.text 115 | } 116 | } 117 | 118 | return "" 119 | } 120 | 121 | func whitespaceAfterChild(index: Int) -> String { 122 | let whitespaceIndex = index + 1 123 | guard whitespaceIndex < children.count else { return "" } 124 | guard let node = children[whitespaceIndex] as? MarcoWhitespaceNode else { return "" } 125 | return node.text 126 | } 127 | } 128 | 129 | internal extension String { 130 | func textAfterNewLine() -> String { 131 | guard let index = lastIndex(of: "\n") else { return self } 132 | let nextIndex = self.index(after: index) 133 | return String(self[nextIndex...]) 134 | } 135 | } 136 | 137 | internal func WS(_ text: String) -> MarcoWhitespaceNode { 138 | return MarcoWhitespaceNode(text: text, containsNewLine: false) 139 | } 140 | 141 | internal func WSnl(_ text: String) -> MarcoWhitespaceNode { 142 | return MarcoWhitespaceNode(text: text, containsNewLine: true) 143 | } 144 | -------------------------------------------------------------------------------- /MarcoKit/Utilities/MinifyingVisitor.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | internal struct MinifyingVisitor : MarcoVisitor, Sendable { 4 | public static let instance = MinifyingVisitor() 5 | private init() {} 6 | 7 | typealias ReturnType = MarcoValueNode 8 | typealias Data = () 9 | 10 | func visitValue(value: MarcoValue, data: ()) -> MarcoValueNode { 11 | return (value as! MarcoNode).clone() as! MarcoValueNode 12 | } 13 | 14 | func visitDocument(value: MarcoDocument, data: ()) -> MarcoValueNode { 15 | let newChild = value.value.accept(self) 16 | return MarcoDocumentNode(children: [newChild], valueIndex: 0) 17 | } 18 | 19 | func visitArray(value: MarcoArray, data: ()) -> MarcoValueNode { 20 | var children = [MarcoNode](), elementIndices = [Int](), isIgnored = false 21 | children.reserveCapacity(value.count * 2 + 1) 22 | elementIndices.reserveCapacity(value.count) 23 | 24 | for node in (value as! MarcoArrayNode).children { 25 | guard !(node is MarcoWhitespaceNode) else { continue } 26 | 27 | if let valueNode = node as? MarcoValueNode { 28 | if (!elementIndices.isEmpty) { 29 | children.append(WS(" ")) 30 | } 31 | 32 | if (node is MarcoValue && !isIgnored) { 33 | elementIndices.append(children.count) 34 | } 35 | 36 | children.append(valueNode.accept(self)) 37 | } else { 38 | children.append(node.clone()) 39 | } 40 | 41 | isIgnored = node.isIgnoring 42 | } 43 | 44 | return MarcoArrayNode(children: children, elementIndices: elementIndices) 45 | } 46 | 47 | func visitObject(value: MarcoObject, data: ()) -> MarcoValueNode { 48 | var children = [MarcoNode](), keyMappings = [String: Int]() 49 | var index = 0 50 | 51 | let valueNode = (value as! MarcoObjectNode) 52 | let oldChildren = valueNode.children 53 | 54 | if (valueNode.hasEnclosingElements) { 55 | children.append(MarcoStructuralElementNode(.leftCurlyBracket)) 56 | } 57 | 58 | while (index < oldChildren.count) { 59 | defer { index += 1 } 60 | 61 | let node = oldChildren[index] 62 | guard let keyValuePair = node as? MarcoKeyValuePairNode else { continue } 63 | 64 | if (!keyMappings.isEmpty) { 65 | children.append(WS(" ")) 66 | } 67 | 68 | let key = keyValuePair.key 69 | let newKey: MarcoNode 70 | 71 | if let key = key as? MarcoIdentifierNode { 72 | newKey = key.clone() 73 | } else { 74 | newKey = (key as! MarcoValue).accept(self) 75 | } 76 | 77 | keyMappings[key.value] = children.count 78 | 79 | let newKeyValuePair = MarcoKeyValuePairNode(children: [ 80 | newKey, WS(" "), keyValuePair.value.accept(self) 81 | ], keyIndex: 0, valueIndex: 2) 82 | 83 | children.append(newKeyValuePair) 84 | } 85 | 86 | if (valueNode.hasEnclosingElements) { 87 | children.append(MarcoStructuralElementNode(.rightCurlyBracket)) 88 | } 89 | 90 | return MarcoObjectNode(children: children, keyMappings: keyMappings, isConfig: !valueNode.hasEnclosingElements) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /MarcoKit/Utilities/MutatingPrettifyingVisitor.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | class MutatingPrettifyingVisitor : PrettifyingVisitor { 4 | override func visitDocument(value: MarcoDocument, data: Int) -> MarcoValueNode { 5 | let result = super.visitDocument(value: value, data: data) as! MarcoDocumentNode 6 | let node = value as! MarcoDocumentNode 7 | replaceChildren(container: node, children: result.children) 8 | node.valueIndex = result.valueIndex 9 | return node 10 | } 11 | 12 | override func visitObject(value: MarcoObject, data: Int) -> MarcoValueNode { 13 | let result = super.visitObject(value: value, data: data) as! MarcoObjectNode 14 | let node = value as! MarcoObjectNode 15 | replaceChildren(container: node, children: result.children) 16 | node.keyMappings = result.keyMappings 17 | return node 18 | } 19 | 20 | override func visitArray(value: MarcoArray, data: Int) -> MarcoValueNode { 21 | let result = super.visitArray(value: value, data: data) as! MarcoArrayNode 22 | let node = value as! MarcoArrayNode 23 | replaceChildren(container: node, children: result.children) 24 | node.elementIndices = result.elementIndices 25 | return node 26 | } 27 | 28 | private func replaceChildren(container: MarcoTreeNode, children: [MarcoNode]) { 29 | let oldChildren = container.children 30 | children.forEach { $0.parent = container } 31 | container.children = children 32 | oldChildren.forEach { $0.parent = nil } 33 | } 34 | } -------------------------------------------------------------------------------- /MarcoKit/Utilities/PrettifyingVisitor.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | internal class PrettifyingVisitor : MarcoVisitor { 4 | private let indent: String 5 | private let forceNewLine: Bool 6 | private let isRecursive: Bool 7 | private let reorderKeys: Bool 8 | 9 | init(indent: String = " ", forceNewLine: Bool, isRecursive: Bool, reorderKeys: Bool) { 10 | self.indent = indent 11 | self.forceNewLine = forceNewLine 12 | self.isRecursive = isRecursive 13 | self.reorderKeys = reorderKeys 14 | } 15 | 16 | typealias ReturnType = MarcoValueNode 17 | typealias Data = Int 18 | 19 | func visitValue(value: MarcoValue, data: Int) -> MarcoValueNode { 20 | return (value as! MarcoNode).clone() as! MarcoValueNode 21 | } 22 | 23 | private func prettifyIfNeeded(_ value: MarcoValue, data: Int) -> MarcoValueNode { 24 | guard isRecursive else { 25 | return (value as! MarcoValueNode).clone() as! MarcoValueNode 26 | } 27 | 28 | return value.accept(self, data: data) 29 | } 30 | 31 | func visitDocument(value: MarcoDocument, data: Int) -> MarcoValueNode { 32 | let newChild = prettifyIfNeeded(value.value, data: data) 33 | return MarcoDocumentNode(children: [newChild], valueIndex: 0) 34 | } 35 | 36 | func visitObject(value: MarcoObject, data: Int) -> MarcoValueNode { 37 | let valueNode = (value as! MarcoObjectNode) 38 | 39 | guard value.count > 0 else { 40 | let children: [MarcoNode] 41 | if (valueNode.hasEnclosingElements) { 42 | children = [ 43 | MarcoStructuralElementNode(.leftCurlyBracket), 44 | MarcoStructuralElementNode(.rightCurlyBracket) 45 | ] 46 | } else { 47 | children = [] 48 | } 49 | 50 | return MarcoObjectNode(children: children, keyMappings: [:], isConfig: !valueNode.hasEnclosingElements) 51 | } 52 | 53 | var nodes = [MarcoNode]() 54 | var keyMappings = [String: Int]() 55 | 56 | nodes.reserveCapacity(value.count * 2 + 1) 57 | keyMappings.reserveCapacity(value.count) 58 | 59 | let currentIndent = String(repeating: indent, count: data) 60 | let childIndent = valueNode.hasEnclosingElements ? currentIndent + indent : currentIndent 61 | 62 | if (valueNode.hasEnclosingElements) { 63 | nodes.append(MarcoStructuralElementNode(.leftCurlyBracket)) 64 | nodes.append(WSnl("\n" + childIndent)) 65 | } 66 | 67 | let sortedKeys = reorderKeys 68 | ? sortObjectKeys(keys: value.keys) { value[$0]!.isSimple } 69 | : value.keys 70 | 71 | for key in sortedKeys { 72 | let dataForKey = valueNode.hasEnclosingElements ? (data + 1) : data 73 | let valueNodeForKey = prettifyIfNeeded(value[key]!, data: dataForKey) 74 | 75 | if (!keyMappings.isEmpty) { 76 | if (!valueNodeForKey.isSimple) { 77 | nodes.append(WSnl("\n\n" + childIndent)) 78 | } else { 79 | nodes.append(WSnl("\n" + childIndent)) 80 | } 81 | } 82 | 83 | let keyNode = getKeyNode(key: key) 84 | keyMappings[key] = nodes.count 85 | 86 | let keyValuePairNode = MarcoKeyValuePairNode( 87 | children: [keyNode, WS(" "), valueNodeForKey], 88 | keyIndex: 0, valueIndex: 2) 89 | 90 | nodes.append(keyValuePairNode) 91 | } 92 | 93 | if (valueNode.hasEnclosingElements) { 94 | nodes.append(WSnl("\n" + currentIndent)) 95 | nodes.append(MarcoStructuralElementNode(.rightCurlyBracket)) 96 | } 97 | 98 | return MarcoObjectNode(children: nodes, keyMappings: keyMappings, isConfig: !valueNode.hasEnclosingElements) 99 | } 100 | 101 | private func sortObjectKeys(keys: [String], isSimple: (String) -> Bool) -> [String] { 102 | return keys.sorted { f, s in 103 | let fIsSimple = isSimple(f) 104 | let sIsSimple = isSimple(s) 105 | 106 | if (fIsSimple && !sIsSimple) { 107 | return true 108 | } else if (!fIsSimple && sIsSimple) { 109 | return false 110 | } else { 111 | return f < s 112 | } 113 | } 114 | } 115 | 116 | private func getKeyNode(key: String) -> MarcoNode { 117 | if (MarcoParser.isSimpleKey(key: key)) { 118 | return MarcoIdentifierNode(name: key) 119 | } else { 120 | return MarcoStringLiteralNode(value: key) 121 | } 122 | } 123 | 124 | func visitArray(value: MarcoArray, data: Int) -> MarcoValueNode { 125 | guard value.count > 0 else { 126 | return MarcoArrayNode(children: [ 127 | MarcoStructuralElementNode(.leftSquareBracket), 128 | MarcoStructuralElementNode(.rightSquareBracket) 129 | ], elementIndices: []) 130 | } 131 | 132 | var nodes = [MarcoNode]() 133 | var elementIndices = [Int]() 134 | 135 | nodes.reserveCapacity(value.count * 2 + 1) 136 | elementIndices.reserveCapacity(value.count) 137 | 138 | nodes.append(MarcoStructuralElementNode(.leftSquareBracket)) 139 | 140 | if (!forceNewLine && value.count < 3 && value.all { $0.isPrimitive }) { 141 | nodes.append(WS(" ")) 142 | 143 | for index in 0.. String { 13 | preconditionFailure() 14 | } 15 | 16 | func visitNumber(value: MarcoNumberLiteral, data: ()) -> String { 17 | preconditionFailure() 18 | } 19 | 20 | func visitDocument(value: MarcoDocument, data: ()) -> String { 21 | return value.value.accept(self) 22 | } 23 | 24 | func visitInt(value: MarcoIntLiteral, data: ()) -> String { 25 | return encodeValue(value.intValue) 26 | } 27 | 28 | func visitDouble(value: MarcoDoubleLiteral, data: ()) -> String { 29 | return encodeValue(value.doubleValue) 30 | } 31 | 32 | func visitObject(value: MarcoObject, data: ()) -> String { 33 | let v = value.keys.map { k in encodeValue(k) + ": " + value[k]!.accept(self) }.joined(separator: ", ") 34 | return "{" + v + "}" 35 | } 36 | 37 | func visitArray(value: MarcoArray, data: ()) -> String { 38 | return "[" + value.elements.map { $0.accept(self) }.joined(separator: ", ") + "]" 39 | } 40 | 41 | func visitNull(value: MarcoNullLiteral, data: ()) -> String { 42 | return "null" 43 | } 44 | 45 | func visitBool(value: MarcoBoolLiteral, data: ()) -> String { 46 | return (value.value) ? "true" : "false" 47 | } 48 | 49 | func visitString(value: MarcoStringLiteral, data: ()) -> String { 50 | return encodeValue(value.value) 51 | } 52 | 53 | private func encodeValue(_ value: T) -> String where T : Encodable { 54 | let array = [value] 55 | let arrayJson = String(data: try! jsonEncoder.encode(array), encoding: .utf8)! 56 | assert(arrayJson.hasPrefix("[") && arrayJson.hasSuffix("]")) 57 | return String(arrayJson.dropFirst().dropLast()) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /MarcoKitTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /MarcoKitTests/Sources/DirectApiAccessTest.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XCTest 3 | import MarcoKit 4 | 5 | class DirectApiAccessTest: XCTestCase { 6 | override func setUp() { 7 | super.setUp() 8 | continueAfterFailure = false 9 | } 10 | 11 | private func testHelloWorld(document: MarcoDocument) { 12 | XCTAssert(document.value is MarcoObject) 13 | let outerObject = document.value as! MarcoObject 14 | XCTAssert(outerObject.count == 1) 15 | 16 | let helloValue = outerObject["hello"] 17 | XCTAssert(helloValue is MarcoObject) 18 | let helloObject = helloValue as! MarcoObject 19 | XCTAssert(helloObject.count == 1) 20 | 21 | let worldValue = helloObject["world"] 22 | XCTAssert(worldValue is MarcoStringLiteral) 23 | XCTAssert((worldValue as! MarcoStringLiteral).value == "x") 24 | 25 | let worldValue2 = outerObject["hello", "world"] 26 | XCTAssert((worldValue2 as? MarcoStringLiteral)?.value == "x") 27 | } 28 | 29 | func testHelloWorld() { 30 | let document = try! Marco.parse(""" 31 | { hello { world "x" } } 32 | """) 33 | 34 | testHelloWorld(document: document) 35 | } 36 | 37 | func testHelloWorldConfig() { 38 | let document = try! Marco.parse(""" 39 | hello { world "x" } 40 | """, options: .config) 41 | 42 | testHelloWorld(document: document) 43 | } 44 | 45 | func testArrayAsSequence() { 46 | let document = try! Marco.parse("[1 2 3]") 47 | let arr = document.value as! MarcoArray 48 | let max = arr.sequence().map { ($0 as! MarcoIntLiteral).intValue }.max() 49 | XCTAssertEqual(3, max) 50 | } 51 | 52 | func testNewObject() { 53 | let obj = Marco.object(("foo", 1.toMarco), ("bar", 2.toMarco), ("baz", 3.toMarco)) 54 | XCTAssertEqual(3, obj["baz"]?.asInt ?? 0) 55 | } 56 | 57 | func testNewArray() { 58 | let arr = Marco.array(1.toMarco, 2.toMarco, 3.toMarco) 59 | XCTAssertEqual(3, arr[2].asIntOrZero) 60 | } 61 | 62 | func testSpecialChars() { 63 | let document = try! Marco.parse("\"\\t\\r\\n\\u0041\"") 64 | let str = document.value.asString! 65 | XCTAssertEqual("\t\r\nA", str) 66 | } 67 | 68 | func testAdding() { 69 | let obj = Marco.object(("foo", 1.toMarco), ("bar", 2.toMarco), ("baz", 3.toMarco)) 70 | obj["boo"] = "foo".toMarco 71 | let text = Marco.prettify(obj, reorderKeys: false).text 72 | XCTAssertEqual("{\n foo 1\n bar 2\n baz 3\n boo \"foo\"\n}", text) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /MarcoKitTests/Sources/ErrorTest.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XCTest 3 | import MarcoKit 4 | 5 | class ErrorTest : XCTestCaseWithTestData { 6 | func test() { 7 | test(groupName: "error") 8 | } 9 | 10 | override func doTest(url: URL, testData: TestData) { 11 | let documentText = testData.before 12 | 13 | func render(error: MarcoParsingError) -> String { 14 | let startOffset = documentText.distance(from: documentText.startIndex, to: error.range.lowerBound) 15 | let endOffset = documentText.distance(from: documentText.startIndex, to: error.range.upperBound) 16 | return "\(startOffset)-\(endOffset): \(error.message)" 17 | } 18 | 19 | do { 20 | _ = try tryParse(url: url, content: documentText) 21 | } catch let e { 22 | if let e = e as? MarcoParsingError { 23 | assertEquals(testData.after, render(error: e), url) 24 | } else if let e = e as? MarcoNonStrictParsingError { 25 | var text = e.document.text + "\n" 26 | e.errors.forEach { text += "\n" + render(error: $0) } 27 | assertEquals(testData.after, text, url) 28 | } else { 29 | assertEquals(testData.after, e.localizedDescription, url) 30 | } 31 | return 32 | } 33 | 34 | assertEquals(testData.after, "There were no parsing errors.", url) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /MarcoKitTests/Sources/FromJsonConversionTest.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XCTest 3 | import MarcoKit 4 | 5 | class FromJsonConversionTest : XCTestCaseWithTestData { 6 | func test() { 7 | test(groupName: "fromJson") 8 | } 9 | 10 | override func doTest(url: URL, testData: TestData) { 11 | let jsonObject = try! JSONSerialization.jsonObject(with: testData.before.data(using: .utf8)!) 12 | let rawMarco = fromJson(jsonObject, url: url) 13 | _ = try! tryParse(url: url, content: rawMarco.text) 14 | let prettifiedMarco = Marco.prettify(rawMarco) 15 | assertEquals(testData.after, prettifiedMarco.text, url) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /MarcoKitTests/Sources/JsonConversionTest.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XCTest 3 | import MarcoKit 4 | 5 | class JsonConversionTest : XCTestCaseWithTestData { 6 | func test() { 7 | test(groupName: "json") 8 | } 9 | 10 | override func doTest(url: URL, testData: TestData) { 11 | let value = Marco.prettify(parse(url: url, content: testData.before)) 12 | let jsonString = Marco.toJsonString(value) 13 | let jsonObject = try! JSONSerialization.jsonObject(with: jsonString.data(using: .utf8)!) 14 | let rawMarco = fromJson(jsonObject, url: url) 15 | _ = try! tryParse(url: url, content: rawMarco.text) 16 | let prettifiedMarco = Marco.prettify(rawMarco) 17 | let actual = (jsonString + "\n\n" + prettifiedMarco.text).trimmingCharacters(in: .whitespacesAndNewlines) 18 | assertEquals(testData.after, actual, url) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /MarcoKitTests/Sources/MinifyTest.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XCTest 3 | import MarcoKit 4 | 5 | class MinifyTest : XCTestCaseWithTestData { 6 | func test() { 7 | test(groupName: "minify") 8 | } 9 | 10 | override func doTest(url: URL, testData: TestData) { 11 | let value = parse(url: url, content: testData.before) 12 | assertEquals(testData.after, Marco.minify(value).text, url) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /MarcoKitTests/Sources/ModificationTest.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XCTest 3 | import MarcoKit 4 | 5 | class ModificationTest : XCTestCaseWithTestData { 6 | func test() { 7 | test(groupName: "modification") 8 | } 9 | 10 | override func doTest(url: URL, testData: TestData) { 11 | let value = parse(url: url, content: testData.before).value 12 | do { 13 | try modify(value: value, testData: testData) 14 | } catch let e { 15 | fail(e.localizedDescription, url) 16 | } 17 | assertEquals(testData.after, value.text, url) 18 | } 19 | 20 | private func modify(value: MarcoValue, testData: TestData) throws { 21 | for modification in testData.modifications { 22 | let kind = modification.kind, data = modification.data, content = modification.content 23 | switch (kind) { 24 | case .add: applyAdd(value: value, data: data, valueToAdd: try Marco.parse(content).value) 25 | case .set: applySet(value: value, data: data, valueToSet: try Marco.parse(content).value) 26 | case .remove: applyRemove(value: value, data: data) 27 | case .get: applyGet(value: value, data: data, expected: content) 28 | } 29 | } 30 | } 31 | 32 | private func applyAdd(value: MarcoValue, data: [String], valueToAdd: MarcoValue) { 33 | let arrayTargetValue = getTargetValue(value, path: data) 34 | 35 | if let array = arrayTargetValue as? MarcoArray { 36 | array.add(valueToAdd) 37 | } else { 38 | let objectTargetValue = getTargetValue(value, path: Array(data.dropLast()), createIfMissing: true) 39 | if let object = objectTargetValue as? MarcoObject { 40 | let key = data.last! 41 | object[key] = valueToAdd 42 | } else { 43 | preconditionFailure("Error chunk kind, array or object expected") 44 | } 45 | } 46 | } 47 | 48 | private func applyGet(value: MarcoValue, data: [String], expected: String) { 49 | if let targetValue = getTargetValue(value, path: data) { 50 | XCTAssertEqual(expected, targetValue.text) 51 | } else { 52 | XCTAssertEqual(expected, "null") 53 | } 54 | } 55 | 56 | private func applyRemove(value: MarcoValue, data: [String]) { 57 | let targetValue = getTargetValue(value, path: Array(data.dropLast())) 58 | let key = data.last! 59 | 60 | if let array = targetValue as? MarcoArray { 61 | array.remove(at: Int(key)!) 62 | } else if let object = targetValue as? MarcoObject { 63 | object.remove(for: key) 64 | } else { 65 | preconditionFailure("Error chunk kind, array or object expected") 66 | } 67 | } 68 | 69 | private func applySet(value: MarcoValue, data: [String], valueToSet: MarcoValue) { 70 | let targetValue = getTargetValue(value, path: Array(data.dropLast())) 71 | let key = data.last! 72 | 73 | if let array = targetValue as? MarcoArray { 74 | array[Int(key)!] = valueToSet 75 | } else if let object = targetValue as? MarcoObject { 76 | object[key] = valueToSet 77 | } else { 78 | preconditionFailure("Error chunk kind, array or object expected") 79 | } 80 | } 81 | 82 | func getTargetValue(_ value: MarcoValue, path: Array, createIfMissing: Bool = false) -> MarcoValue? { 83 | var current = value 84 | for chunk in path { 85 | if let array = current as? MarcoArray { 86 | current = array[Int(chunk)!] 87 | } else if let object = current as? MarcoObject { 88 | if let next = object[chunk] { 89 | current = next 90 | } else if (createIfMissing) { 91 | let newCurrent = Marco.emptyObject() 92 | object[chunk] = newCurrent 93 | current = newCurrent 94 | } else { 95 | return nil 96 | } 97 | } else { 98 | return nil 99 | } 100 | } 101 | return current 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /MarcoKitTests/Sources/ParseTest.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XCTest 3 | import MarcoKit 4 | 5 | class ParseTest : XCTestCaseWithTestData { 6 | func test() { 7 | test(groupName: "parse") 8 | } 9 | 10 | override func doTest(url: URL, testData: TestData) { 11 | let value = parse(url: url, content: testData.before) 12 | assertEquals(testData.before, value.text, url) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /MarcoKitTests/Sources/PrettifyTest.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XCTest 3 | import MarcoKit 4 | 5 | class PrettifyTest : XCTestCaseWithTestData { 6 | func test() { 7 | test(groupName: "prettify") 8 | } 9 | 10 | override func doTest(url: URL, testData: TestData) { 11 | let value: MarcoDocument = parse(url: url, content: testData.before) 12 | assertEquals(testData.after, Marco.prettify(value).text, url) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /MarcoKitTests/Sources/Utilities/XCTestCaseWithTestData.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XCTest 3 | 4 | class XCTestCaseWithTestData : XCTestCase { 5 | func doTest(url: URL, testData: TestData) {} 6 | 7 | func test(groupName: String) { 8 | let dirUrl = Bundle(for: ParseTest.self).resourceURL!.appendingPathComponent("TestData/" + groupName) 9 | for obj in FileManager.default.enumerator(at: dirUrl, includingPropertiesForKeys: nil)! { 10 | runSingleFile(url: obj as! URL) 11 | } 12 | } 13 | 14 | func test(groupName: String, testName: String) { 15 | let dirUrl = Bundle(for: ParseTest.self).resourceURL!.appendingPathComponent("TestData/" + groupName) 16 | for obj in FileManager.default.enumerator(at: dirUrl, includingPropertiesForKeys: nil)! { 17 | let url = obj as! URL 18 | guard (url.lastPathComponent as NSString).deletingPathExtension == testName else { continue } 19 | runSingleFile(url: url) 20 | } 21 | } 22 | 23 | private func runSingleFile(url: URL) { 24 | let ext = url.pathExtension 25 | guard ext == "marco" || ext == "marcoConfig" else { return } 26 | let content = try! String(contentsOf: url) 27 | let sections = parseSections(content: content) 28 | guard (sections.contains { $0.name == "before" }) else { 29 | fail("'before' section not found", url) 30 | return 31 | } 32 | let testData = parseTestData(sections: parseSections(content: content)) 33 | doTest(url: url, testData: testData) 34 | } 35 | 36 | private func parseSections(content: String) -> [Section] { 37 | var sections = [Section]() 38 | var name = "", sectionData = "", sectionContent = "" 39 | 40 | func saveCurrentSection() { 41 | guard !name.isEmpty else { return } 42 | let prettyContent = sectionContent.trimmingCharacters(in: .whitespacesAndNewlines) 43 | sections.append(Section(name: name, data: sectionData, content: prettyContent)) 44 | name = "" 45 | sectionData = "" 46 | sectionContent = "" 47 | } 48 | 49 | content.enumerateLines { line, _ in 50 | if (line.hasPrefix("--")) { 51 | saveCurrentSection() 52 | 53 | let splits = line.dropFirst(2).split(separator: " ", maxSplits: 1).map { String($0) } 54 | name = String(splits[0]) 55 | sectionData = splits.count == 2 ? String(splits[1]) : "" 56 | 57 | return 58 | } 59 | 60 | if (!sectionContent.isEmpty) { 61 | sectionContent += "\n" 62 | } 63 | 64 | sectionContent += line 65 | } 66 | 67 | saveCurrentSection() 68 | return sections 69 | } 70 | 71 | private func parseTestData(sections: [Section]) -> TestData { 72 | let before = sections.first { $0.name == "before" }!.content 73 | let after = sections.first { $0.name == "after" }?.content ?? "" 74 | 75 | let modifications: [Modification] = sections 76 | .filter { s in s.name != "before" && s.name != "after" } 77 | .map { s in 78 | let kind = Modification.Kind.parse(s.name) 79 | let data = s.data.split(separator: ",").map { $0.trimmingCharacters(in: .whitespaces) } 80 | return Modification(kind: kind, data: data, content: s.content) 81 | } 82 | 83 | return TestData(before: before, after: after, modifications: modifications) 84 | } 85 | } 86 | 87 | fileprivate class Section { 88 | let name: String 89 | let data: String 90 | let content: String 91 | 92 | init(name: String, data: String, content: String) { 93 | self.name = name 94 | self.data = data 95 | self.content = content 96 | } 97 | } 98 | 99 | class TestData { 100 | let before: String 101 | let after: String 102 | let modifications: [Modification] 103 | 104 | init(before: String, after: String, modifications: [Modification]) { 105 | self.before = before 106 | self.after = after 107 | self.modifications = modifications 108 | } 109 | } 110 | 111 | class Modification { 112 | enum Kind { 113 | case add, remove, set, get 114 | } 115 | 116 | let kind: Kind 117 | let data: [String] 118 | let content: String 119 | 120 | init(kind: Kind, data: [String], content: String) { 121 | self.kind = kind 122 | self.data = data 123 | self.content = content 124 | } 125 | } 126 | 127 | fileprivate extension Modification.Kind { 128 | static func parse(_ name: String) -> Modification.Kind { 129 | switch (name) { 130 | case "add": return .add 131 | case "remove": return .remove 132 | case "set": return .set 133 | case "get": return .get 134 | default: preconditionFailure("Unexpected modification kind \(name)") 135 | } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /MarcoKitTests/Sources/Utilities/marcoUtils.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XCTest 3 | import MarcoKit 4 | 5 | func parse(url: URL, content: String) -> MarcoDocument { 6 | do { 7 | return try tryParse(url: url, content: content) 8 | } catch let e { 9 | fail(e.localizedDescription, url) 10 | preconditionFailure("Test crashed.") 11 | } 12 | } 13 | 14 | func tryParse(url: URL, content: String) throws -> MarcoDocument { 15 | var options: Marco.Options = [.showContextInErrors] 16 | 17 | if (url.pathExtension == "marcoConfig") { 18 | options.insert(.config) 19 | } 20 | 21 | if (url.lastPathComponent.contains("NonStrict")) { 22 | options.insert(.nonStrict) 23 | } 24 | 25 | return try Marco.parse(content, options: options) 26 | } 27 | 28 | func fromJson(_ obj: Any, url: URL) -> MarcoDocument { 29 | let isConfig = url.pathExtension == "marcoConfig" 30 | if (isConfig) { 31 | return Marco.configFromJson(object: obj as! [String: Any]) 32 | } else { 33 | return Marco.fromJson(object: obj) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /MarcoKitTests/Sources/Utilities/testUtils.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XCTest 3 | 4 | func assertNoThrow(block: () throws -> T) -> T { 5 | var result: T? = nil 6 | XCTAssertNoThrow(try { 7 | result = try block() 8 | }()) 9 | return result! 10 | } 11 | 12 | func assertEquals(_ expected: T, _ actual: T, _ url: URL) { 13 | guard expected != actual else { return } 14 | let text = "\nExpected (\(url.relativePath)):\n\(expected)\n\nActual:\n\(actual)" 15 | XCTFail(text) 16 | } 17 | 18 | func fail(_ message: String, _ url: URL) { 19 | let text = url.relativePath + "\n" + message 20 | XCTFail(text) 21 | } 22 | 23 | fileprivate class TestObj {} 24 | 25 | fileprivate extension URL { 26 | var relativePath: String { 27 | let selfPath = self.path 28 | let resourcesPath = Bundle(for: TestObj.self).resourceURL!.path 29 | precondition(self.path.hasPrefix(resourcesPath + "/")) 30 | return String(selfPath.dropFirst(resourcesPath.count + 1)) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /MarcoKitTests/TestData/error/array1.marco: -------------------------------------------------------------------------------- 1 | --before 2 | [ 3 | 4 | --after 5 | 0-1: Unexpected end of file 6 | -------------------------------------------------------------------------------- /MarcoKitTests/TestData/error/array2.marco: -------------------------------------------------------------------------------- 1 | --before 2 | [t 3 | 4 | --after 5 | 1-2: 'true' expected 6 | -------------------------------------------------------------------------------- /MarcoKitTests/TestData/error/array3.marco: -------------------------------------------------------------------------------- 1 | --before 2 | [][] 3 | 4 | --after 5 | 2-3: End of file expected, got '[' 6 | -------------------------------------------------------------------------------- /MarcoKitTests/TestData/error/array4.marco: -------------------------------------------------------------------------------- 1 | --before 2 | [[[[[[[[]]]]]]] 3 | 4 | --after 5 | 0-15: Unexpected end of file 6 | -------------------------------------------------------------------------------- /MarcoKitTests/TestData/error/array5.marco: -------------------------------------------------------------------------------- 1 | --before 2 | [[[[[[[[]]]]]]]]] 3 | 4 | --after 5 | 16-17: End of file expected, got ']' 6 | -------------------------------------------------------------------------------- /MarcoKitTests/TestData/error/color.marco: -------------------------------------------------------------------------------- 1 | --before 2 | #ffffgg 3 | 4 | --after 5 | 5-6: Color literal expected, got 'g' 6 | -------------------------------------------------------------------------------- /MarcoKitTests/TestData/error/equals.marco: -------------------------------------------------------------------------------- 1 | --before 2 | { 3 | a = 1 4 | b = 2 5 | } 6 | 7 | --after 8 | 8-9: Unexpected value character '=' 9 | -------------------------------------------------------------------------------- /MarcoKitTests/TestData/error/hex.marco: -------------------------------------------------------------------------------- 1 | --before 2 | { 3 | foo 0x 4 | } 5 | 6 | --after 7 | 12-13: Hexadecimal literal expected '\n' 8 | -------------------------------------------------------------------------------- /MarcoKitTests/TestData/error/json.marco: -------------------------------------------------------------------------------- 1 | --before 2 | {"hello": "world", "array": [1, 2, 3, true, false, "a", {}, [], [1]]} 3 | 4 | --after 5 | 8-9: Unexpected value character ':' 6 | -------------------------------------------------------------------------------- /MarcoKitTests/TestData/error/jsonNonStrict.marco: -------------------------------------------------------------------------------- 1 | --before 2 | {"hello": "world", "array": [1, 2, 3, true, false, "a", {}, [], [1]]} 3 | 4 | --after 5 | {"hello": "world", "array": [1, 2, 3, true, false, "a", {}, [], [1]]} 6 | 7 | 8-9: Invalid character: Marco does not use ':' as a separator 8 | 17-18: Invalid character: Marco does not use ',' as a separator 9 | 26-27: Invalid character: Marco does not use ':' as a separator 10 | 30-31: Invalid character: Marco does not use ',' as a separator 11 | 33-34: Invalid character: Marco does not use ',' as a separator 12 | 36-37: Invalid character: Marco does not use ',' as a separator 13 | 42-43: Invalid character: Marco does not use ',' as a separator 14 | 49-50: Invalid character: Marco does not use ',' as a separator 15 | 54-55: Invalid character: Marco does not use ',' as a separator 16 | 58-59: Invalid character: Marco does not use ',' as a separator 17 | 62-63: Invalid character: Marco does not use ',' as a separator 18 | -------------------------------------------------------------------------------- /MarcoKitTests/TestData/error/number1.marco: -------------------------------------------------------------------------------- 1 | --before 2 | 1.0f 3 | 4 | --after 5 | 3-4: End of file expected, got 'f' 6 | -------------------------------------------------------------------------------- /MarcoKitTests/TestData/error/number2.marco: -------------------------------------------------------------------------------- 1 | --before 2 | 1.0.2 3 | 4 | --after 5 | 3-4: Duplicating '.' in a number literal 6 | -------------------------------------------------------------------------------- /MarcoKitTests/TestData/error/object1.marco: -------------------------------------------------------------------------------- 1 | --before 2 | { 3 | 4 | --after 5 | 0-1: '}' expected 6 | -------------------------------------------------------------------------------- /MarcoKitTests/TestData/error/object1.marcoConfig: -------------------------------------------------------------------------------- 1 | --before 2 | foo 3 | 4 | --after 5 | 0-3: Unexpected end of file 6 | -------------------------------------------------------------------------------- /MarcoKitTests/TestData/error/object10.marco: -------------------------------------------------------------------------------- 1 | --before 2 | {"a"0 "b"1} 3 | 4 | --after 5 | 4-5: Whitespace required in a key-value pair 6 | -------------------------------------------------------------------------------- /MarcoKitTests/TestData/error/object11.marco: -------------------------------------------------------------------------------- 1 | --before 2 | { 3 | foo{ index 0 } 4 | } 5 | 6 | --after 7 | 9-20: Whitespace required in a key-value pair 8 | -------------------------------------------------------------------------------- /MarcoKitTests/TestData/error/object12.marco: -------------------------------------------------------------------------------- 1 | --before 2 | { 3 | "bar"{ index 1 } 4 | } 5 | 6 | --after 7 | 11-22: Whitespace required in a key-value pair 8 | -------------------------------------------------------------------------------- /MarcoKitTests/TestData/error/object13.marco: -------------------------------------------------------------------------------- 1 | --before 2 | { 3 | baz[0] 4 | } 5 | 6 | --after 7 | 9-12: Whitespace required in a key-value pair 8 | -------------------------------------------------------------------------------- /MarcoKitTests/TestData/error/object14.marco: -------------------------------------------------------------------------------- 1 | --before 2 | { 3 | "boo"[1] 4 | } 5 | 6 | --after 7 | 11-14: Whitespace required in a key-value pair 8 | -------------------------------------------------------------------------------- /MarcoKitTests/TestData/error/object15.marcoConfig: -------------------------------------------------------------------------------- 1 | --before 2 | foo { 3 | a 1 4 | }} 5 | 6 | --after 7 | 15-16: Whitespace required between object entries 8 | -------------------------------------------------------------------------------- /MarcoKitTests/TestData/error/object2.marco: -------------------------------------------------------------------------------- 1 | --before 2 | {a 3 | 4 | --after 5 | 0-2: Unexpected end of file 6 | -------------------------------------------------------------------------------- /MarcoKitTests/TestData/error/object3.marco: -------------------------------------------------------------------------------- 1 | --before 2 | {a "b" c 3 | 4 | --after 5 | 0-8: Unexpected end of file 6 | -------------------------------------------------------------------------------- /MarcoKitTests/TestData/error/object4.marco: -------------------------------------------------------------------------------- 1 | --before 2 | {}{} 3 | 4 | --after 5 | 2-3: End of file expected, got '{' 6 | -------------------------------------------------------------------------------- /MarcoKitTests/TestData/error/object5.marco: -------------------------------------------------------------------------------- 1 | --before 2 | {a {b {c "d"}} 3 | 4 | --after 5 | 13-14: '}' expected 6 | -------------------------------------------------------------------------------- /MarcoKitTests/TestData/error/object6.marco: -------------------------------------------------------------------------------- 1 | --before 2 | {@} 3 | 4 | --after 5 | 2-3: Unexpected identifier character '}' 6 | -------------------------------------------------------------------------------- /MarcoKitTests/TestData/error/object7.marco: -------------------------------------------------------------------------------- 1 | --before 2 | {5 5} 3 | 4 | --after 5 | 2-3: Unexpected identifier character ' ' 6 | -------------------------------------------------------------------------------- /MarcoKitTests/TestData/error/object8.marco: -------------------------------------------------------------------------------- 1 | --before 2 | {a trueb false} 3 | 4 | --after 5 | 7-8: Whitespace required between object entries 6 | -------------------------------------------------------------------------------- /MarcoKitTests/TestData/error/object9.marco: -------------------------------------------------------------------------------- 1 | --before 2 | {a 0 a 1} 3 | 4 | --after 5 | 5-6: Duplicating key 'a' 6 | -------------------------------------------------------------------------------- /MarcoKitTests/TestData/error/simple.marco: -------------------------------------------------------------------------------- 1 | --before 2 | foo 3 | 4 | --after 5 | 0-2: 'false' expected 6 | -------------------------------------------------------------------------------- /MarcoKitTests/TestData/error/truefalse.marco: -------------------------------------------------------------------------------- 1 | --before 2 | [truefalse] 3 | 4 | --after 5 | 5-6: Whitespace required between array entries 6 | -------------------------------------------------------------------------------- /MarcoKitTests/TestData/fromJson/specialChars.marco: -------------------------------------------------------------------------------- 1 | --before 2 | ["\n\t\r\\\"\u0041\u0008"] 3 | 4 | --after 5 | [ "\n\t\r\\\"A\u0008" ] -------------------------------------------------------------------------------- /MarcoKitTests/TestData/json/empty.marcoConfig: -------------------------------------------------------------------------------- 1 | --before 2 | 3 | --after 4 | {} -------------------------------------------------------------------------------- /MarcoKitTests/TestData/json/simple.marco: -------------------------------------------------------------------------------- 1 | --before 2 | { 3 | hello "world" 4 | array [1 2 3 true false null "a" {} [] [1]] 5 | } 6 | 7 | --after 8 | {"hello": "world", "array": [1, 2, 3, true, false, null, "a", {}, [], [1]]} 9 | 10 | { 11 | hello "world" 12 | 13 | array [ 14 | 1 15 | 2 16 | 3 17 | true 18 | false 19 | null 20 | "a" 21 | {} 22 | [] 23 | [ 1 ] 24 | ] 25 | } -------------------------------------------------------------------------------- /MarcoKitTests/TestData/json/simple.marcoConfig: -------------------------------------------------------------------------------- 1 | --before 2 | hello "world" 3 | array [1 2 3 true false null "a" {} [] [1]] 4 | 5 | --after 6 | {"hello": "world", "array": [1, 2, 3, true, false, null, "a", {}, [], [1]]} 7 | 8 | hello "world" 9 | 10 | array [ 11 | 1 12 | 2 13 | 3 14 | true 15 | false 16 | null 17 | "a" 18 | {} 19 | [] 20 | [ 1 ] 21 | ] -------------------------------------------------------------------------------- /MarcoKitTests/TestData/minify/array1.marco: -------------------------------------------------------------------------------- 1 | --before 2 | [ 3 | true 4 | false 5 | 1 6 | "a" 7 | "b" 8 | 3 9 | "c" 10 | ] 11 | 12 | --after 13 | [true false 1 "a" "b" 3 "c"] -------------------------------------------------------------------------------- /MarcoKitTests/TestData/minify/array2.marco: -------------------------------------------------------------------------------- 1 | --before 2 | [ 3 | 1 4 | 2 5 | [ 6 | 3 7 | 4 8 | [ 9 | 5 10 | 6 11 | [7 8] 12 | ] 13 | ] 14 | ] 15 | 16 | --after 17 | [1 2 [3 4 [5 6 [7 8]]]] -------------------------------------------------------------------------------- /MarcoKitTests/TestData/minify/colorLiteral.marco: -------------------------------------------------------------------------------- 1 | --before 2 | #cc0000 3 | 4 | --after 5 | #cc0000 -------------------------------------------------------------------------------- /MarcoKitTests/TestData/minify/doubleLiteral.marco: -------------------------------------------------------------------------------- 1 | --before 2 | 5.0 3 | 4 | --after 5 | 5.0 -------------------------------------------------------------------------------- /MarcoKitTests/TestData/minify/emptyArray.marco: -------------------------------------------------------------------------------- 1 | --before 2 | [ 3 | 4 | ] 5 | 6 | --after 7 | [] -------------------------------------------------------------------------------- /MarcoKitTests/TestData/minify/emptyObject.marco: -------------------------------------------------------------------------------- 1 | --before 2 | { 3 | } 4 | 5 | --after 6 | {} -------------------------------------------------------------------------------- /MarcoKitTests/TestData/minify/emptyObject.marcoConfig: -------------------------------------------------------------------------------- 1 | --before 2 | 3 | --after -------------------------------------------------------------------------------- /MarcoKitTests/TestData/minify/falseLiteral.marco: -------------------------------------------------------------------------------- 1 | --before 2 | false 3 | 4 | --after 5 | false -------------------------------------------------------------------------------- /MarcoKitTests/TestData/minify/intLiteral.marco: -------------------------------------------------------------------------------- 1 | --before 2 | 5 3 | 4 | --after 5 | 5 -------------------------------------------------------------------------------- /MarcoKitTests/TestData/minify/negativeDoubleLiteral.marco: -------------------------------------------------------------------------------- 1 | --before 2 | -12.375 3 | 4 | --after 5 | -12.375 -------------------------------------------------------------------------------- /MarcoKitTests/TestData/minify/negativeIntLiteral.marco: -------------------------------------------------------------------------------- 1 | --before 2 | -5 3 | 4 | --after 5 | -5 -------------------------------------------------------------------------------- /MarcoKitTests/TestData/minify/nullLiteral.marco: -------------------------------------------------------------------------------- 1 | --before 2 | null 3 | 4 | --after 5 | null -------------------------------------------------------------------------------- /MarcoKitTests/TestData/minify/object1.marco: -------------------------------------------------------------------------------- 1 | --before 2 | { 3 | a 1 4 | "b" 2 5 | c.d 3 6 | !e 4 7 | !f 5 8 | g 6 9 | h { 10 | x 2 11 | y 4 12 | z [ 6 7 8 ] 13 | } 14 | } 15 | 16 | --after 17 | {a 1 "b" 2 c.d 3 e 4 f 5 g 6 h {x 2 y 4 z [6 7 8]}} -------------------------------------------------------------------------------- /MarcoKitTests/TestData/minify/object1.marcoConfig: -------------------------------------------------------------------------------- 1 | --before 2 | a 1 3 | "b" 2 4 | c.d 3 5 | !e 4 6 | !f 5 7 | g 6 8 | h { 9 | x 2 10 | y 4 11 | z [ 6 7 8 ] 12 | } 13 | 14 | --after 15 | a 1 "b" 2 c.d 3 e 4 f 5 g 6 h {x 2 y 4 z [6 7 8]} -------------------------------------------------------------------------------- /MarcoKitTests/TestData/minify/simple.marco: -------------------------------------------------------------------------------- 1 | --before 2 | { 3 | a "b" 4 | c 5 5 | } 6 | 7 | --after 8 | {a "b" c 5} -------------------------------------------------------------------------------- /MarcoKitTests/TestData/minify/simple.marcoConfig: -------------------------------------------------------------------------------- 1 | --before 2 | a "b" 3 | c 5 4 | 5 | --after 6 | a "b" c 5 -------------------------------------------------------------------------------- /MarcoKitTests/TestData/minify/stringLiteral.marco: -------------------------------------------------------------------------------- 1 | --before 2 | "abc" 3 | 4 | --after 5 | "abc" -------------------------------------------------------------------------------- /MarcoKitTests/TestData/minify/trueLiteral.marco: -------------------------------------------------------------------------------- 1 | --before 2 | true 3 | 4 | --after 5 | true -------------------------------------------------------------------------------- /MarcoKitTests/TestData/modification/addArray1.marco: -------------------------------------------------------------------------------- 1 | --before 2 | [ 3 | "John" 4 | ] 5 | 6 | --add 7 | "Smith" 8 | 9 | --after 10 | [ 11 | "John" 12 | "Smith" 13 | ] -------------------------------------------------------------------------------- /MarcoKitTests/TestData/modification/addArray10.marco: -------------------------------------------------------------------------------- 1 | --before 2 | [] 3 | 4 | --add 5 | { 6 | firstName "Jack" 7 | "lastName" "Smith" 8 | } 9 | 10 | --after 11 | [ 12 | { 13 | firstName "Jack" 14 | "lastName" "Smith" 15 | } 16 | ] -------------------------------------------------------------------------------- /MarcoKitTests/TestData/modification/addArray11.marco: -------------------------------------------------------------------------------- 1 | --before 2 | [ 3 | !"foo" 4 | ] 5 | 6 | --add 7 | "bar" 8 | 9 | --after 10 | [ 11 | "bar" 12 | !"foo" 13 | ] -------------------------------------------------------------------------------- /MarcoKitTests/TestData/modification/addArray12.marco: -------------------------------------------------------------------------------- 1 | --before 2 | [ 3 | !{a 0 b 1} 4 | ] 5 | 6 | --add 7 | {c 3 d 4} 8 | 9 | --after 10 | [ 11 | {c 3 d 4} 12 | !{a 0 b 1} 13 | ] -------------------------------------------------------------------------------- /MarcoKitTests/TestData/modification/addArray13.marco: -------------------------------------------------------------------------------- 1 | --before 2 | [ 3 | !{a 0 b 1} 4 | ] 5 | 6 | --add 7 | {c 3 8 | d 4} 9 | 10 | --after 11 | [ 12 | {c 3 13 | d 4} 14 | !{a 0 b 1} 15 | ] -------------------------------------------------------------------------------- /MarcoKitTests/TestData/modification/addArray14.marco: -------------------------------------------------------------------------------- 1 | --before 2 | [ 3 | 1 4 | 2 5 | 3 6 | 4 7 | 5 8 | ] 9 | 10 | --add 11 | 6 12 | 13 | --after 14 | [ 15 | 1 16 | 2 17 | 3 18 | 4 19 | 5 20 | 6 21 | ] -------------------------------------------------------------------------------- /MarcoKitTests/TestData/modification/addArray15.marco: -------------------------------------------------------------------------------- 1 | --before 2 | [ 3 | 1 4 | 2 5 | 3 6 | 4 7 | 5 8 | ] 9 | 10 | --add 11 | { 12 | "a" "b" 13 | } 14 | 15 | --after 16 | [ 17 | 1 18 | 2 19 | 3 20 | 4 21 | 5 22 | { 23 | "a" "b" 24 | } 25 | ] -------------------------------------------------------------------------------- /MarcoKitTests/TestData/modification/addArray16.marco: -------------------------------------------------------------------------------- 1 | --before 2 | [ 3 | !0 4 | 1 5 | !2 6 | ] 7 | 8 | --add 9 | 3 10 | 11 | --after 12 | [ 13 | !0 14 | 1 15 | 3 16 | !2 17 | ] -------------------------------------------------------------------------------- /MarcoKitTests/TestData/modification/addArray17.marco: -------------------------------------------------------------------------------- 1 | --before 2 | [ 3 | [] 4 | ] 5 | 6 | --add 0 7 | [] 8 | 9 | --add 0, 0 10 | [] 11 | 12 | --add 0, 0, 0 13 | [] 14 | 15 | --after 16 | [ 17 | [ 18 | [ 19 | [ 20 | [] 21 | ] 22 | ] 23 | ] 24 | ] -------------------------------------------------------------------------------- /MarcoKitTests/TestData/modification/addArray18.marco: -------------------------------------------------------------------------------- 1 | --before 2 | [ 5 ] 3 | 4 | --add 5 | { 6 | a 0 7 | b 1 8 | } 9 | 10 | --after 11 | [ 12 | 5 13 | { 14 | a 0 15 | b 1 16 | } 17 | ] -------------------------------------------------------------------------------- /MarcoKitTests/TestData/modification/addArray2.marco: -------------------------------------------------------------------------------- 1 | --before 2 | ["John"] 3 | 4 | --add 5 | "Smith" 6 | 7 | --after 8 | [ 9 | "John" 10 | "Smith" 11 | ] -------------------------------------------------------------------------------- /MarcoKitTests/TestData/modification/addArray3.marco: -------------------------------------------------------------------------------- 1 | --before 2 | ["John" ] 3 | 4 | --add 5 | "Smith" 6 | 7 | --after 8 | [ 9 | "John" 10 | "Smith" 11 | ] -------------------------------------------------------------------------------- /MarcoKitTests/TestData/modification/addArray4.marco: -------------------------------------------------------------------------------- 1 | --before 2 | ["John" 3 | ] 4 | 5 | --add 6 | "Smith" 7 | 8 | --after 9 | ["John" 10 | "Smith" 11 | ] 12 | -------------------------------------------------------------------------------- /MarcoKitTests/TestData/modification/addArray5.marco: -------------------------------------------------------------------------------- 1 | --before 2 | [ 3 | "John" 4 | "Smith" 5 | ] 6 | 7 | --add 8 | "Mary" 9 | 10 | --after 11 | [ 12 | "John" 13 | "Smith" 14 | "Mary" 15 | ] -------------------------------------------------------------------------------- /MarcoKitTests/TestData/modification/addArray6.marco: -------------------------------------------------------------------------------- 1 | --before 2 | [ 3 | "John" 4 | "Smith" 5 | ] 6 | 7 | --add 8 | "Mary" 9 | 10 | --after 11 | [ 12 | "John" 13 | "Smith" 14 | "Mary" 15 | ] -------------------------------------------------------------------------------- /MarcoKitTests/TestData/modification/addArray7.marco: -------------------------------------------------------------------------------- 1 | --before 2 | [ 3 | "John" 4 | "Smith" 5 | ] 6 | 7 | --add 8 | { 9 | color #000000 10 | shape "circle" 11 | border { 12 | visible true 13 | color #ff0000 14 | } 15 | } 16 | 17 | --after 18 | [ 19 | "John" 20 | "Smith" 21 | { 22 | color #000000 23 | shape "circle" 24 | border { 25 | visible true 26 | color #ff0000 27 | } 28 | } 29 | ] -------------------------------------------------------------------------------- /MarcoKitTests/TestData/modification/addArray8.marco: -------------------------------------------------------------------------------- 1 | --before 2 | [ 3 | "John" 4 | "Smith" 5 | ] 6 | 7 | --add 8 | [[[]]] 9 | 10 | --after 11 | [ 12 | "John" 13 | "Smith" 14 | [[[]]] 15 | ] -------------------------------------------------------------------------------- /MarcoKitTests/TestData/modification/addArray9.marco: -------------------------------------------------------------------------------- 1 | --before 2 | [] 3 | 4 | --add 5 | "A" 6 | 7 | --after 8 | [ 9 | "A" 10 | ] -------------------------------------------------------------------------------- /MarcoKitTests/TestData/modification/addDict1.marco: -------------------------------------------------------------------------------- 1 | --before 2 | { 3 | "a" 1 4 | } 5 | 6 | --add b 7 | 2 8 | 9 | --after 10 | { 11 | "a" 1 12 | b 2 13 | } -------------------------------------------------------------------------------- /MarcoKitTests/TestData/modification/addDict2.marco: -------------------------------------------------------------------------------- 1 | --before 2 | [ 3 | "foo" 4 | { 5 | "a" 1 6 | } 7 | ] 8 | 9 | --add 1, b 10 | 2 11 | 12 | --after 13 | [ 14 | "foo" 15 | { 16 | "a" 1 17 | b 2 18 | } 19 | ] -------------------------------------------------------------------------------- /MarcoKitTests/TestData/modification/addDict3.marco: -------------------------------------------------------------------------------- 1 | --before 2 | {} 3 | 4 | --add b 5 | { 6 | f "foo" 7 | b "bar" 8 | } 9 | 10 | --after 11 | { 12 | b { 13 | f "foo" 14 | b "bar" 15 | } 16 | } -------------------------------------------------------------------------------- /MarcoKitTests/TestData/modification/addDict4.marco: -------------------------------------------------------------------------------- 1 | --before 2 | {} 3 | 4 | --add a 5 | {} 6 | 7 | --add a, b 8 | {} 9 | 10 | --add a, b, c 11 | {} 12 | 13 | --add a, b, c, d 14 | {} 15 | 16 | --after 17 | { 18 | a { 19 | b { 20 | c { 21 | d {} 22 | } 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /MarcoKitTests/TestData/modification/addDict5.marcoConfig: -------------------------------------------------------------------------------- 1 | --before 2 | foo "John" 3 | 4 | --add bar 5 | "Mary" 6 | 7 | --after 8 | foo "John" 9 | bar "Mary" 10 | -------------------------------------------------------------------------------- /MarcoKitTests/TestData/modification/nested1.marco: -------------------------------------------------------------------------------- 1 | --before 2 | [ 3 | {a 0 b 1} 4 | [ 5 | "a" 6 | "b" 7 | "c" 8 | ] 9 | ] 10 | 11 | --remove 1, 0 12 | 13 | --after 14 | [ 15 | {a 0 b 1} 16 | [ 17 | "b" 18 | "c" 19 | ] 20 | ] -------------------------------------------------------------------------------- /MarcoKitTests/TestData/modification/nested2.marco: -------------------------------------------------------------------------------- 1 | --before 2 | [[[0]]] 3 | 4 | --remove 0, 0, 0 5 | 6 | --after 7 | [[[]]] -------------------------------------------------------------------------------- /MarcoKitTests/TestData/modification/nested3.marco: -------------------------------------------------------------------------------- 1 | --before 2 | [[[0 1 2]]] 3 | 4 | --remove 0, 0, 0 5 | 6 | --after 7 | [[[1 2]]] -------------------------------------------------------------------------------- /MarcoKitTests/TestData/modification/nested4.marco: -------------------------------------------------------------------------------- 1 | --before 2 | [[[0 1 2]]] 3 | 4 | --remove 0, 0, 2 5 | 6 | --after 7 | [[[0 1]]] -------------------------------------------------------------------------------- /MarcoKitTests/TestData/modification/nested5.marco: -------------------------------------------------------------------------------- 1 | --before 2 | [[[ 3 | 0 1 2 4 | ]]] 5 | 6 | --remove 0, 0, 2 7 | 8 | --after 9 | [[[ 10 | 0 1 11 | ]]] -------------------------------------------------------------------------------- /MarcoKitTests/TestData/modification/removeArray1.marco: -------------------------------------------------------------------------------- 1 | --before 2 | [ 3 | "John" 4 | ] 5 | 6 | --remove 0 7 | 8 | --after 9 | [] -------------------------------------------------------------------------------- /MarcoKitTests/TestData/modification/removeArray10.marco: -------------------------------------------------------------------------------- 1 | --before 2 | [ 1 2 3 ] 3 | 4 | --remove 1 5 | 6 | --after 7 | [ 1 3 ] -------------------------------------------------------------------------------- /MarcoKitTests/TestData/modification/removeArray11.marco: -------------------------------------------------------------------------------- 1 | --before 2 | [ 1 2 3 ] 3 | 4 | --remove 2 5 | 6 | --after 7 | [ 1 2 ] -------------------------------------------------------------------------------- /MarcoKitTests/TestData/modification/removeArray12.marco: -------------------------------------------------------------------------------- 1 | --before 2 | [ 1 2 3 ] 3 | 4 | --remove 0 5 | 6 | --after 7 | [ 2 3 ] -------------------------------------------------------------------------------- /MarcoKitTests/TestData/modification/removeArray13.marco: -------------------------------------------------------------------------------- 1 | --before 2 | [ 1 2 3 ] 3 | 4 | --remove 1 5 | 6 | --after 7 | [ 1 3 ] -------------------------------------------------------------------------------- /MarcoKitTests/TestData/modification/removeArray14.marco: -------------------------------------------------------------------------------- 1 | --before 2 | [ 1 2 3 ] 3 | 4 | --remove 2 5 | 6 | --after 7 | [ 1 2 ] -------------------------------------------------------------------------------- /MarcoKitTests/TestData/modification/removeArray2.marco: -------------------------------------------------------------------------------- 1 | --before 2 | [ 3 | 1 4 | 2 5 | 3 6 | ] 7 | 8 | --remove 0 9 | 10 | --after 11 | [ 12 | 2 13 | 3 14 | ] -------------------------------------------------------------------------------- /MarcoKitTests/TestData/modification/removeArray3.marco: -------------------------------------------------------------------------------- 1 | --before 2 | [ 3 | 1 4 | 2 5 | 3 6 | ] 7 | 8 | --remove 1 9 | 10 | --after 11 | [ 12 | 1 13 | 3 14 | ] -------------------------------------------------------------------------------- /MarcoKitTests/TestData/modification/removeArray4.marco: -------------------------------------------------------------------------------- 1 | --before 2 | [ 3 | 1 4 | 2 5 | 3 6 | ] 7 | 8 | --remove 2 9 | 10 | --after 11 | [ 12 | 1 13 | 2 14 | ] -------------------------------------------------------------------------------- /MarcoKitTests/TestData/modification/removeArray5.marco: -------------------------------------------------------------------------------- 1 | --before 2 | [ 3 | 1 4 | 2 5 | ] 6 | 7 | --remove 0 8 | 9 | --after 10 | [ 11 | 2 12 | ] -------------------------------------------------------------------------------- /MarcoKitTests/TestData/modification/removeArray6.marco: -------------------------------------------------------------------------------- 1 | --before 2 | [ 3 | 1 4 | 2 5 | ] 6 | 7 | --remove 1 8 | 9 | --after 10 | [ 11 | 1 12 | ] -------------------------------------------------------------------------------- /MarcoKitTests/TestData/modification/removeArray7.marco: -------------------------------------------------------------------------------- 1 | --before 2 | [ 3 | ["a"] 4 | { 5 | b 0 6 | c 1 7 | } 8 | ] 9 | 10 | --remove 1 11 | 12 | --after 13 | [ 14 | ["a"] 15 | ] -------------------------------------------------------------------------------- /MarcoKitTests/TestData/modification/removeArray8.marco: -------------------------------------------------------------------------------- 1 | --before 2 | [ 3 | ["a"] 4 | { 5 | b 0 6 | c 1 7 | } 8 | ] 9 | 10 | --remove 0 11 | 12 | --after 13 | [ 14 | { 15 | b 0 16 | c 1 17 | } 18 | ] -------------------------------------------------------------------------------- /MarcoKitTests/TestData/modification/removeArray9.marco: -------------------------------------------------------------------------------- 1 | --before 2 | [ 1 2 3 ] 3 | 4 | --remove 0 5 | 6 | --after 7 | [ 2 3 ] -------------------------------------------------------------------------------- /MarcoKitTests/TestData/modification/removeDict1.marco: -------------------------------------------------------------------------------- 1 | --before 2 | { 3 | "a" 1 4 | "b" 2 5 | } 6 | 7 | --remove a 8 | 9 | --after 10 | { 11 | "b" 2 12 | } -------------------------------------------------------------------------------- /MarcoKitTests/TestData/modification/removeDict2.marco: -------------------------------------------------------------------------------- 1 | --before 2 | { 3 | "a" 1 4 | "b" 2 5 | } 6 | 7 | --remove b 8 | 9 | --after 10 | { 11 | "a" 1 12 | } -------------------------------------------------------------------------------- /MarcoKitTests/TestData/modification/removeDict3.marcoConfig: -------------------------------------------------------------------------------- 1 | --before 2 | foo { 3 | a 1 4 | b 2 5 | } 6 | 7 | bar {} 8 | 9 | --remove foo 10 | 11 | --add foo 12 | { 13 | c 3 14 | d 4 15 | } 16 | 17 | --after 18 | bar {} 19 | foo { 20 | c 3 21 | d 4 22 | } 23 | -------------------------------------------------------------------------------- /MarcoKitTests/TestData/modification/removeDict4.marcoConfig: -------------------------------------------------------------------------------- 1 | --before 2 | foo { 3 | a 1 4 | } 5 | 6 | bar { 7 | b 2 8 | } 9 | 10 | --add baz, c 11 | 3 12 | 13 | --get bar, b 14 | 2 15 | 16 | --after 17 | foo { 18 | a 1 19 | } 20 | 21 | bar { 22 | b 2 23 | } 24 | baz { 25 | c 3 26 | } 27 | -------------------------------------------------------------------------------- /MarcoKitTests/TestData/parse/colorLiteral.marco: -------------------------------------------------------------------------------- 1 | --before 2 | #cc0000 -------------------------------------------------------------------------------- /MarcoKitTests/TestData/parse/colorLiterals.marco: -------------------------------------------------------------------------------- 1 | --before 2 | [#ccc #ff0000 #aaff00ff #CCC #FF0000 #aaFF00fF #12345678 #90AbCdEf] -------------------------------------------------------------------------------- /MarcoKitTests/TestData/parse/doubleLiteral.marco: -------------------------------------------------------------------------------- 1 | --before 2 | 5.0 -------------------------------------------------------------------------------- /MarcoKitTests/TestData/parse/emptyArray.marco: -------------------------------------------------------------------------------- 1 | --before 2 | [ 3 | 4 | ] -------------------------------------------------------------------------------- /MarcoKitTests/TestData/parse/emptyObject.marco: -------------------------------------------------------------------------------- 1 | --before 2 | { 3 | } -------------------------------------------------------------------------------- /MarcoKitTests/TestData/parse/falseLiteral.marco: -------------------------------------------------------------------------------- 1 | --before 2 | false -------------------------------------------------------------------------------- /MarcoKitTests/TestData/parse/intLiteral.marco: -------------------------------------------------------------------------------- 1 | --before 2 | 5 -------------------------------------------------------------------------------- /MarcoKitTests/TestData/parse/negativeDoubleLiteral.marco: -------------------------------------------------------------------------------- 1 | --before 2 | -12.375 -------------------------------------------------------------------------------- /MarcoKitTests/TestData/parse/negativeIntLiteral.marco: -------------------------------------------------------------------------------- 1 | --before 2 | -5 -------------------------------------------------------------------------------- /MarcoKitTests/TestData/parse/nestedArray10.marco: -------------------------------------------------------------------------------- 1 | --before 2 | [[[[[[[[[[10]]]]]]]]]] -------------------------------------------------------------------------------- /MarcoKitTests/TestData/parse/nestedArray2.marco: -------------------------------------------------------------------------------- 1 | --before 2 | [[2]] -------------------------------------------------------------------------------- /MarcoKitTests/TestData/parse/nestedArray50.marco: -------------------------------------------------------------------------------- 1 | --before 2 | [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[50]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]] -------------------------------------------------------------------------------- /MarcoKitTests/TestData/parse/nestedObject2.marco: -------------------------------------------------------------------------------- 1 | --before 2 | {a {a 0}} -------------------------------------------------------------------------------- /MarcoKitTests/TestData/parse/nestedObject50.marco: -------------------------------------------------------------------------------- 1 | --before 2 | {a {a {a {a {a {a {a {a {a {a {a {a {a {a {a {a {a {a {a {a {a {a {a {a {a {a {a {a {a {a {a {a {a {a {a {a {a {a {a {a {a {a {a {a {a {a {a {a {a {a 50}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}} -------------------------------------------------------------------------------- /MarcoKitTests/TestData/parse/nullLiteral.marco: -------------------------------------------------------------------------------- 1 | --before 2 | null -------------------------------------------------------------------------------- /MarcoKitTests/TestData/parse/object1.marco: -------------------------------------------------------------------------------- 1 | --before 2 | { 3 | !toDelete true 4 | 5 | firstName "Jack" 6 | familyName "Smith" 7 | age 16 8 | gender "M" 9 | ownsCar false 10 | !ownsApartment true 11 | ownsApartment false 12 | eyeColor #1111cc 13 | 14 | hobbies [ "Swimming" "Dancing" "Music" ] 15 | 16 | family { 17 | mother { 18 | firstName "Mary" 19 | familyName "Smith" 20 | age 40 21 | } 22 | 23 | !father { 24 | "firstName" "Nick" 25 | familyName "Smith" 26 | age 41 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /MarcoKitTests/TestData/parse/object2.marco: -------------------------------------------------------------------------------- 1 | --before 2 | { 3 | a 1 4 | b.c 2 5 | "d.e" 3 6 | !f 4 7 | !g.h 5 8 | "i.j" 6 9 | 10 | a_b 7 11 | a___b 8 12 | a___b____c______e__________f 9 13 | $a 10 14 | $a_b 11 15 | $a....b 12 16 | a...................................................b 13 17 | !a...................................................c 14 18 | 19 | hello 1 20 | привет 2 21 | おはよう 3 22 | 日本語 4 23 | Àbc 5 24 | } -------------------------------------------------------------------------------- /MarcoKitTests/TestData/parse/simple.marco: -------------------------------------------------------------------------------- 1 | --before 2 | { hello "world" } -------------------------------------------------------------------------------- /MarcoKitTests/TestData/parse/stringLiteral.marco: -------------------------------------------------------------------------------- 1 | --before 2 | "Hello, world!" -------------------------------------------------------------------------------- /MarcoKitTests/TestData/parse/trueLiteral.marco: -------------------------------------------------------------------------------- 1 | --before 2 | true -------------------------------------------------------------------------------- /MarcoKitTests/TestData/prettify/array1.marco: -------------------------------------------------------------------------------- 1 | --before 2 | [[[[[[[[[[[[[[[5]]]]]]]]]]]]]]] 3 | 4 | --after 5 | [ 6 | [ 7 | [ 8 | [ 9 | [ 10 | [ 11 | [ 12 | [ 13 | [ 14 | [ 15 | [ 16 | [ 17 | [ 18 | [ 19 | [ 5 ] 20 | ] 21 | ] 22 | ] 23 | ] 24 | ] 25 | ] 26 | ] 27 | ] 28 | ] 29 | ] 30 | ] 31 | ] 32 | ] 33 | ] -------------------------------------------------------------------------------- /MarcoKitTests/TestData/prettify/colorLiteral.marco: -------------------------------------------------------------------------------- 1 | --before 2 | #cc0000 3 | 4 | --after 5 | #cc0000 -------------------------------------------------------------------------------- /MarcoKitTests/TestData/prettify/doubleLiteral.marco: -------------------------------------------------------------------------------- 1 | --before 2 | 5.0 3 | 4 | --after 5 | 5.0 -------------------------------------------------------------------------------- /MarcoKitTests/TestData/prettify/emptyArray.marco: -------------------------------------------------------------------------------- 1 | --before 2 | [ 3 | 4 | ] 5 | 6 | --after 7 | [] -------------------------------------------------------------------------------- /MarcoKitTests/TestData/prettify/emptyObject.marco: -------------------------------------------------------------------------------- 1 | --before 2 | { 3 | } 4 | 5 | --after 6 | {} -------------------------------------------------------------------------------- /MarcoKitTests/TestData/prettify/emptyObject.marcoConfig: -------------------------------------------------------------------------------- 1 | --before 2 | 3 | --after -------------------------------------------------------------------------------- /MarcoKitTests/TestData/prettify/falseLiteral.marco: -------------------------------------------------------------------------------- 1 | --before 2 | false 3 | 4 | --after 5 | false -------------------------------------------------------------------------------- /MarcoKitTests/TestData/prettify/intLiteral.marco: -------------------------------------------------------------------------------- 1 | --before 2 | 5 3 | 4 | --after 5 | 5 -------------------------------------------------------------------------------- /MarcoKitTests/TestData/prettify/negativeDoubleLiteral.marco: -------------------------------------------------------------------------------- 1 | --before 2 | -12.375 3 | 4 | --after 5 | -12.375 -------------------------------------------------------------------------------- /MarcoKitTests/TestData/prettify/negativeIntLiteral.marco: -------------------------------------------------------------------------------- 1 | --before 2 | -5 3 | 4 | --after 5 | -5 -------------------------------------------------------------------------------- /MarcoKitTests/TestData/prettify/nullLiteral.marco: -------------------------------------------------------------------------------- 1 | --before 2 | null 3 | 4 | --after 5 | null -------------------------------------------------------------------------------- /MarcoKitTests/TestData/prettify/object1.marco: -------------------------------------------------------------------------------- 1 | --before 2 | { 3 | hello "world" 4 | array [1 2 3] 5 | } 6 | 7 | --after 8 | { 9 | hello "world" 10 | 11 | array [ 12 | 1 13 | 2 14 | 3 15 | ] 16 | } -------------------------------------------------------------------------------- /MarcoKitTests/TestData/prettify/object1.marcoConfig: -------------------------------------------------------------------------------- 1 | --before 2 | hello "world" 3 | array [1 2 3] 4 | 5 | --after 6 | hello "world" 7 | 8 | array [ 9 | 1 10 | 2 11 | 3 12 | ] -------------------------------------------------------------------------------- /MarcoKitTests/TestData/prettify/object2.marco: -------------------------------------------------------------------------------- 1 | --before 2 | {a {b {c {d {e {f {g 0}}}}}}} 3 | 4 | --after 5 | { 6 | a { 7 | b { 8 | c { 9 | d { 10 | e { 11 | f { 12 | g 0 13 | } 14 | } 15 | } 16 | } 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /MarcoKitTests/TestData/prettify/object2.marcoConfig: -------------------------------------------------------------------------------- 1 | --before 2 | a {b {c {d {e {f {g 0}}}}}} 3 | 4 | --after 5 | a { 6 | b { 7 | c { 8 | d { 9 | e { 10 | f { 11 | g 0 12 | } 13 | } 14 | } 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /MarcoKitTests/TestData/prettify/simple.marco: -------------------------------------------------------------------------------- 1 | --before 2 | { a "b" c 5 } 3 | 4 | --after 5 | { 6 | a "b" 7 | c 5 8 | } -------------------------------------------------------------------------------- /MarcoKitTests/TestData/prettify/simple.marcoConfig: -------------------------------------------------------------------------------- 1 | --before 2 | a "b" 3 | c 5 4 | 5 | --after 6 | a "b" 7 | c 5 -------------------------------------------------------------------------------- /MarcoKitTests/TestData/prettify/stringLiteral.marco: -------------------------------------------------------------------------------- 1 | --before 2 | "abc" 3 | 4 | --after 5 | "abc" -------------------------------------------------------------------------------- /MarcoKitTests/TestData/prettify/trueLiteral.marco: -------------------------------------------------------------------------------- 1 | --before 2 | true 3 | 4 | --after 5 | true -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MarcoKit: Marco parsing library for Swift 2 | 3 | This is a Swift library for working with Marco configuration/value files. 4 | Also, this is a reference implementation of the Marco parser. 5 | 6 | ## What is Marco? 7 | 8 | Marco is a new configuration and data serialization format. You can read more about it [here](https://github.com/marta-file-manager/MarcoKit/blob/master/docs/MARCO.md). 9 | 10 | ## Library features 11 | 12 | MarcoKit allows you to: 13 | 14 | - Parse Marco value/configuration files; 15 | - Modify and serialize documents back to `String` (formatting is preserved); 16 | - Create new Marco documents; 17 | - Minify or prettify Marco documents; 18 | - Convert Marco to Objective-C representation of JSON and vice-versa. 19 | 20 | ## Carthage 21 | 22 | MarcoKit can be imported as a [Carthage](https://github.com/Carthage/Carthage) dependency: 23 | 24 | `github "marta-file-manager/MarcoKit" "v0.1"` 25 | 26 | ## Documentation 27 | 28 | The API reference is available [here](https://marta-file-manager.github.io/MarcoKit/api/index.html). 29 | 30 | ## Performance 31 | 32 | While Marco syntax as easy to parse as JSON, MarcoKit backs Marco values with AST elements. This allows you to modify the document contents without losing the formatting and to implement code insight features. However, this comes as a cost of performance. 33 | 34 | I didn't do any serious performance comparison with other serialization libraries, but the testing shows that using MarcoKit may not be the best idea if you have huge data chunks. However, the difference is unnoticeable for common configuration file use-cases. 35 | 36 | ## Contributing 37 | 38 | MarcoKit is available under the [Apache 2.0 License](https://github.com/marta-file-manager/MarcoKit/blob/master/LICENSE). 39 | 40 | If you send a Pull Request, please ensure that all existing tests are passing ("All Tests" scheme, Product → Test). 41 | If you fixed a bug, add the new test or modify the existing one to check the new behavior. 42 | If you plan to change the Marco format, consider making an issue describing your problem and a proposed solution. -------------------------------------------------------------------------------- /docs/GRAMMAR.md: -------------------------------------------------------------------------------- 1 | # Marco Grammar 2 | 3 | This file contains a formal grammar description of the Marco syntax. 4 | 5 | ## Legend 6 | 7 | `foo?` means "zero or one occurrences of `foo`". 8 | `foo*` means zero or more occurrences of `foo`". 9 | `foo+` means "one or more occurrences of `foo`". 10 | `foo[]` means "exactly <num> occurrences of `foo`". 11 | `'' | '' | ... | ''` means "any of the described strings". 12 | 13 | ## Predefined symbols 14 | 15 | `` = Any Unicode character from Unicode categories L* and M*. 16 | `` = Any Unicode character excluding : U+0020 (Space), U+0009 (Horizontal Tab), U+000A (New Line) or U+000D (Carriage Return). 17 | 18 | ## Grammar 19 | 20 | - The input text must be encoded using UTF-8. 21 | - A byte order mark **must not** be present in the beginning of the input text. 22 | - Recommended file extension: `.marco`. 23 | - Recommended MIME type: `application/marco`. 24 | 25 |
 26 | ValueFile (root)
 27 |   : Whitespace*, Value, Whitespace*
 28 | 
 29 | ConfigurationFile (root)
 30 |   : Whitespace*
 31 |   : Whitespace*, KeyValuePairs, Whitespace*
 32 | 
 33 | Value
 34 |   : IntLiteral
 35 |   : DoubleLiteral
 36 |   : StringLiteral
 37 |   : BooleanLiteral
 38 |   : NullLiteral
 39 |   : Array
 40 |   : Object
 41 | 
 42 | IntLiteral
 43 |   : RegularIntLiteral
 44 |   : HexLiteral
 45 |   : ColorLiteral
 46 | 
 47 | RegularIntLiteral
 48 |   : PositiveRegularIntLiteral
 49 |   : '-', PositiveRegularIntLiteral
 50 | 
 51 | PositiveRegularIntLiteral
 52 |   : Digit
 53 |   : OneNine, Digit*
 54 | 
 55 | HexLiteral
 56 |   : '0x', HexDigit*
 57 |   : '-', '0x', HexDigit*
 58 | 
 59 | ColorLiteral
 60 |   : '#', HexDigit[3]
 61 |   : '#', HexDigit[6]
 62 |   : '#', HexDigit[8]
 63 | 
 64 | DoubleLiteral
 65 |   : RegularIntLiteral, '.', PositiveRegularIntLiteral
 66 |   : RegularIntLiteral, '.', PositiveRegularIntLiteral, 'e' | 'E', PositiveRegularIntLiteral
 67 | 
 68 | StringLiteral
 69 |   : '"', Character*, '"'
 70 | 
 71 | Character
 72 |   : '\', 'n' | 't' | 'r' | '\', '"'
 73 |   : '\u', HexDigit[4]
 74 |   : <Unescaped>
 75 | 
 76 | BooleanLiteral
 77 |   : 'true' | 'false'
 78 | 
 79 | NullLiteral
 80 |   : 'null'
 81 | 
 82 | Array
 83 |   : '[', Whitespace*, ']'
 84 |   : '[', Whitespace*, Elements, Whitespace*, ']'
 85 | 
 86 | Elements
 87 |   : '!'?, Value
 88 |   : '!'?, Value, Whitespace*, Elements
 89 | 
 90 | Object
 91 |   : '{', Whitespace*, '}'
 92 |   : '{', Whitespace*, KeyValuePairs, Whitespace*, '}'
 93 | 
 94 | KeyValuePairs
 95 |   : '!'?, KeyValuePair
 96 |   : '!'?, KeyValuePair, Whitespace+, KeyValuePairs
 97 | 
 98 | KeyValuePair
 99 |   : Key, Whitespace+, Value
100 | 
101 | Key
102 |   : Identifier
103 |   : StringLiteral
104 | 
105 | Identifier
106 |   : IdentifierStart, IdentifierRemaining*
107 | 
108 | IdentifierStart
109 |   : <Letter>
110 |   : '$' | '_'
111 | 
112 | IdentifierRemaining
113 |   : <Letter>
114 |   : Digit
115 |   : '$' | '_'
116 | 
117 | OneNine
118 |   : '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
119 | 
120 | Digit
121 |   : '0'
122 |   : OneNine
123 | 
124 | HexDigit
125 |   : Digit
126 |   : 'A' | 'a' | 'B' | 'b' | 'C' | 'c' | 'D' | 'd' | 'E' | 'e' | 'F' | 'f'
127 | 
128 | Whitespace
129 |   : U+0020 (Space)
130 |   : U+0009 (Horizontal Tab)
131 |   : U+000A (New Line)
132 |   : U+000D (Carriage Return)
133 | 
-------------------------------------------------------------------------------- /docs/MARCO.md: -------------------------------------------------------------------------------- 1 | # Marco syntax 2 | 3 | This document is an informal explanation of Marco syntax. 4 | You can find the formal grammar [here](GRAMMAR.md). 5 | 6 | ## Motivation 7 | 8 | Marco was created as a configuration file format for [Marta](https://marta.yanex.org), a macOS file manager. Previously, Marta used JSON, though it had a number of issues: 9 | 10 | - Verbosity: enclosing '{}' for the root object, quoted object keys. 11 | - Harder to read and write. 12 | - Harder to make code insight features such as auto-formatting or completion. 13 | - High ceremony: forbidden dangling commas. 14 | - Easier to make a mistake. 15 | 16 | So I investigated what the alternatives are. However, surprisingly, I didn't find anything good enough. The most well-thought thing is [HOCON](https://github.com/lightbend/config/blob/master/HOCON.md#hocon-human-optimized-config-object-notation), though it has too many complex features, making it harder both to implement and use. Especially if the format is not well-known, its simplicity is a key for the good user experience. 17 | 18 | So I started with JSON and removed all syntax that seemed impractical for the hand-written configuration files. I also added hexadecimal and color literals as they're pretty common in configs. 19 | 20 | ## Marco Features 21 | 22 | - Clean syntax for writing configuration/data; 23 | - Easily convertible to JSON and vice-versa; 24 | - Two number types: `Int` and `Double`; 25 | - Color and hexadecimal literals; 26 | - Value escaping. 27 | 28 | ### Example 29 | 30 | Here is an example of a simple Marco object: 31 | 32 | ``` 33 | { 34 | firstName "John" 35 | lastName "Smith" 36 | age 31 37 | city "New York" 38 | eyeColor #408002 39 | parents [ 40 | {type "Father" firstName "Alex" lastName "Smith"} 41 | {type "Mother" firstName "Mary" lastName "Smith"} 42 | ] 43 | } 44 | ``` 45 | 46 | As you may see, Marco looks pretty like JSON but without commas or colons. Object keys are not required to be string literals. 47 | 48 | Marco does not insist on particular formatting rules. New lines are not required and you are free to format your document as you want. However, the default prettifier uses the four-space indentation for nested elements, and I recommend you to do so. 49 | 50 | ### Example (configuration object) 51 | 52 | Marco supports a special kind of files -- configuration files. The only difference is that you do not need to enclose your keys in `{}`: 53 | 54 | ``` 55 | firstName "John" 56 | lastName "Smith" 57 | age 31 58 | city "New York" 59 | eyeColor #408002 60 | 61 | parents [ 62 | {type "Father" firstName "Alex" lastName "Smith"} 63 | {type "Mother" firstName "Mary" lastName "Smith"} 64 | ] 65 | ``` 66 | 67 | ### Marco Types 68 | 69 | Marta has 7 types or values: `Int`, `Double`, `String`, `Boolean`, `Array`, `Object`, and `Null`. 70 | 71 | - `Int` is a signed integer type. Bounds may vary in different implementations. Minimal allowed bound is a native word size. 72 | - Decimal literals: `0`, `-5`, `10000`. 73 | - Hexadecimal literals: `0xff`, `0xABCDEF`. Letter case does not matter. 74 | - Color literals: `#AARRGGBB`, `#RRGGBB` or `#RGB` (parsed as `#RRGGBB`). `A` means 'alpha' (opacity), `R` means 'red', `G` means 'green', `B` means 'blue'. 75 | - `Double` is a double-precision floating-point type. 76 | - Decimal literals: `5`, `5.0`, `1E-6`. 77 | - A number literal is always parsed as `Int` unless it contains a dot `.` or `E`/`e`. 78 | - `String` is a UTF-8 string type. Strings can contain any symbols, including zero symbol. Strings are enclosed in double quotes. 79 | - Simple strings: `"Hello, world"`, `""`, `"おはよう"`. 80 | - Allowed escape sequences: 81 | - `\n`, `\t`, `\r`: `"New\nline"`. 82 | - `\"` for escaping `"`, `\\` for escaping `\`. 83 | - `\uABCD` for a Unicode characters (U+0000 through U+FFFF). `\u0008` is a backspace symbol, `\u0041` is "A". 84 | - `Boolean`: `true` or `false`. 85 | - `Array`: an iterable list of elements. 86 | Format: `[ ... ]`. 87 | - `[]` is an empty array. 88 | - `[1 "foo"]` is an array containing two elements of different types, `1`: Int and `"foo"`: String. 89 | - `[[1] [2]]` is an array containing two nested arrays. 90 | - There must be at least a single whitespace symbol between the elements. `["foo"4]` is not a correct array. 91 | - `Object` is a key-value map. A key is a `String` literal or an identifier. 92 | Format: `[ ... ]`. 93 | - Key may be an identifier if it starts with a letter, `$` or `_`, and all other characters are letters, numbers, `$`, `_` or `.`. `foo5` is a valid identifier, `5foo` is not. 94 | - Keys `"foo"`, `foo`, and `"\u0066\u006F\u006F"` are the same. 95 | - Keys "foo" and "Foo" are the different keys. 96 | - Duplicated keys are prohibited. 97 | - `{}` is an empty object. 98 | - `{a "foo" "b" 3}` is an object containing two key-value pairs. `a` is mapped to `"foo"`: String, `b` is mapped to `3`: Int. 99 | - There must be at least a single whitespace symbol between the key-value pairs and after the key. 100 | - `Null` type represents a lack of value: `null`. 101 | 102 | ### Value Escaping 103 | 104 | You can comment out a value by adding the leading `!`: 105 | 106 | ``` 107 | { 108 | firstName "John" 109 | lastName "Smith" 110 | !age 31 111 | age 32 112 | parents [ 113 | !{type "Father" firstName "Alex" lastName "Smith"} 114 | {type "Mother" firstName "Mary" lastName "Smith"} 115 | ] 116 | } 117 | ``` 118 | 119 | In the snippet above the key-value pair `age 31` and the "Father" parent array element are commented. Note that, although being escaped, the values are still parsed so they can't contain syntax errors. 120 | 121 | Keys in escaped key-value pairs can be duplicated. This allows you to comment one value and replace it with an another. Note that this doesn't affect the escaped value structure: escaped objects still can't contain duplicate keys. -------------------------------------------------------------------------------- /docs/api/css/highlight.css: -------------------------------------------------------------------------------- 1 | /* Credit to https://gist.github.com/wataru420/2048287 */ 2 | .highlight { 3 | /* Comment */ 4 | /* Error */ 5 | /* Keyword */ 6 | /* Operator */ 7 | /* Comment.Multiline */ 8 | /* Comment.Preproc */ 9 | /* Comment.Single */ 10 | /* Comment.Special */ 11 | /* Generic.Deleted */ 12 | /* Generic.Deleted.Specific */ 13 | /* Generic.Emph */ 14 | /* Generic.Error */ 15 | /* Generic.Heading */ 16 | /* Generic.Inserted */ 17 | /* Generic.Inserted.Specific */ 18 | /* Generic.Output */ 19 | /* Generic.Prompt */ 20 | /* Generic.Strong */ 21 | /* Generic.Subheading */ 22 | /* Generic.Traceback */ 23 | /* Keyword.Constant */ 24 | /* Keyword.Declaration */ 25 | /* Keyword.Pseudo */ 26 | /* Keyword.Reserved */ 27 | /* Keyword.Type */ 28 | /* Literal.Number */ 29 | /* Literal.String */ 30 | /* Name.Attribute */ 31 | /* Name.Builtin */ 32 | /* Name.Class */ 33 | /* Name.Constant */ 34 | /* Name.Entity */ 35 | /* Name.Exception */ 36 | /* Name.Function */ 37 | /* Name.Namespace */ 38 | /* Name.Tag */ 39 | /* Name.Variable */ 40 | /* Operator.Word */ 41 | /* Text.Whitespace */ 42 | /* Literal.Number.Float */ 43 | /* Literal.Number.Hex */ 44 | /* Literal.Number.Integer */ 45 | /* Literal.Number.Oct */ 46 | /* Literal.String.Backtick */ 47 | /* Literal.String.Char */ 48 | /* Literal.String.Doc */ 49 | /* Literal.String.Double */ 50 | /* Literal.String.Escape */ 51 | /* Literal.String.Heredoc */ 52 | /* Literal.String.Interpol */ 53 | /* Literal.String.Other */ 54 | /* Literal.String.Regex */ 55 | /* Literal.String.Single */ 56 | /* Literal.String.Symbol */ 57 | /* Name.Builtin.Pseudo */ 58 | /* Name.Variable.Class */ 59 | /* Name.Variable.Global */ 60 | /* Name.Variable.Instance */ 61 | /* Literal.Number.Integer.Long */ } 62 | .highlight .c { 63 | color: #999988; 64 | font-style: italic; } 65 | .highlight .err { 66 | color: #a61717; 67 | background-color: #e3d2d2; } 68 | .highlight .k { 69 | color: #000000; 70 | font-weight: bold; } 71 | .highlight .o { 72 | color: #000000; 73 | font-weight: bold; } 74 | .highlight .cm { 75 | color: #999988; 76 | font-style: italic; } 77 | .highlight .cp { 78 | color: #999999; 79 | font-weight: bold; } 80 | .highlight .c1 { 81 | color: #999988; 82 | font-style: italic; } 83 | .highlight .cs { 84 | color: #999999; 85 | font-weight: bold; 86 | font-style: italic; } 87 | .highlight .gd { 88 | color: #000000; 89 | background-color: #ffdddd; } 90 | .highlight .gd .x { 91 | color: #000000; 92 | background-color: #ffaaaa; } 93 | .highlight .ge { 94 | color: #000000; 95 | font-style: italic; } 96 | .highlight .gr { 97 | color: #aa0000; } 98 | .highlight .gh { 99 | color: #999999; } 100 | .highlight .gi { 101 | color: #000000; 102 | background-color: #ddffdd; } 103 | .highlight .gi .x { 104 | color: #000000; 105 | background-color: #aaffaa; } 106 | .highlight .go { 107 | color: #888888; } 108 | .highlight .gp { 109 | color: #555555; } 110 | .highlight .gs { 111 | font-weight: bold; } 112 | .highlight .gu { 113 | color: #aaaaaa; } 114 | .highlight .gt { 115 | color: #aa0000; } 116 | .highlight .kc { 117 | color: #000000; 118 | font-weight: bold; } 119 | .highlight .kd { 120 | color: #000000; 121 | font-weight: bold; } 122 | .highlight .kp { 123 | color: #000000; 124 | font-weight: bold; } 125 | .highlight .kr { 126 | color: #000000; 127 | font-weight: bold; } 128 | .highlight .kt { 129 | color: #445588; } 130 | .highlight .m { 131 | color: #009999; } 132 | .highlight .s { 133 | color: #d14; } 134 | .highlight .na { 135 | color: #008080; } 136 | .highlight .nb { 137 | color: #0086B3; } 138 | .highlight .nc { 139 | color: #445588; 140 | font-weight: bold; } 141 | .highlight .no { 142 | color: #008080; } 143 | .highlight .ni { 144 | color: #800080; } 145 | .highlight .ne { 146 | color: #990000; 147 | font-weight: bold; } 148 | .highlight .nf { 149 | color: #990000; } 150 | .highlight .nn { 151 | color: #555555; } 152 | .highlight .nt { 153 | color: #000080; } 154 | .highlight .nv { 155 | color: #008080; } 156 | .highlight .ow { 157 | color: #000000; 158 | font-weight: bold; } 159 | .highlight .w { 160 | color: #bbbbbb; } 161 | .highlight .mf { 162 | color: #009999; } 163 | .highlight .mh { 164 | color: #009999; } 165 | .highlight .mi { 166 | color: #009999; } 167 | .highlight .mo { 168 | color: #009999; } 169 | .highlight .sb { 170 | color: #d14; } 171 | .highlight .sc { 172 | color: #d14; } 173 | .highlight .sd { 174 | color: #d14; } 175 | .highlight .s2 { 176 | color: #d14; } 177 | .highlight .se { 178 | color: #d14; } 179 | .highlight .sh { 180 | color: #d14; } 181 | .highlight .si { 182 | color: #d14; } 183 | .highlight .sx { 184 | color: #d14; } 185 | .highlight .sr { 186 | color: #009926; } 187 | .highlight .s1 { 188 | color: #d14; } 189 | .highlight .ss { 190 | color: #990073; } 191 | .highlight .bp { 192 | color: #999999; } 193 | .highlight .vc { 194 | color: #008080; } 195 | .highlight .vg { 196 | color: #008080; } 197 | .highlight .vi { 198 | color: #008080; } 199 | .highlight .il { 200 | color: #009999; } 201 | -------------------------------------------------------------------------------- /docs/api/css/jazzy.css: -------------------------------------------------------------------------------- 1 | *, *:before, *:after { 2 | box-sizing: inherit; } 3 | 4 | body { 5 | margin: 0; 6 | background: #fff; 7 | color: #333; 8 | font: 16px/1.7 "Helvetica Neue", Helvetica, Arial, sans-serif; 9 | letter-spacing: .2px; 10 | -webkit-font-smoothing: antialiased; 11 | box-sizing: border-box; } 12 | 13 | h1 { 14 | font-size: 2rem; 15 | font-weight: 700; 16 | margin: 1.275em 0 0.6em; } 17 | 18 | h2 { 19 | font-size: 1.75rem; 20 | font-weight: 700; 21 | margin: 1.275em 0 0.3em; } 22 | 23 | h3 { 24 | font-size: 1.5rem; 25 | font-weight: 700; 26 | margin: 1em 0 0.3em; } 27 | 28 | h4 { 29 | font-size: 1.25rem; 30 | font-weight: 700; 31 | margin: 1.275em 0 0.85em; } 32 | 33 | h5 { 34 | font-size: 1rem; 35 | font-weight: 700; 36 | margin: 1.275em 0 0.85em; } 37 | 38 | h6 { 39 | font-size: 1rem; 40 | font-weight: 700; 41 | margin: 1.275em 0 0.85em; 42 | color: #777; } 43 | 44 | p { 45 | margin: 0 0 1em; } 46 | 47 | ul, ol { 48 | padding: 0 0 0 2em; 49 | margin: 0 0 0.85em; } 50 | 51 | blockquote { 52 | margin: 0 0 0.85em; 53 | padding: 0 15px; 54 | color: #858585; 55 | border-left: 4px solid #e5e5e5; } 56 | 57 | img { 58 | max-width: 100%; } 59 | 60 | a { 61 | color: #4183c4; 62 | text-decoration: none; } 63 | a:hover, a:focus { 64 | outline: 0; 65 | text-decoration: underline; } 66 | 67 | table { 68 | background: #fff; 69 | width: 100%; 70 | border-collapse: collapse; 71 | border-spacing: 0; 72 | overflow: auto; 73 | margin: 0 0 0.85em; } 74 | 75 | tr:nth-child(2n) { 76 | background-color: #fbfbfb; } 77 | 78 | th, td { 79 | padding: 6px 13px; 80 | border: 1px solid #ddd; } 81 | 82 | pre { 83 | margin: 0 0 1.275em; 84 | padding: .85em 1em; 85 | overflow: auto; 86 | background: #f7f7f7; 87 | font-size: .85em; 88 | font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace; } 89 | 90 | code { 91 | font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace; } 92 | 93 | p > code, li > code { 94 | background: #f7f7f7; 95 | padding: .2em; } 96 | p > code:before, p > code:after, li > code:before, li > code:after { 97 | letter-spacing: -.2em; 98 | content: "\00a0"; } 99 | 100 | pre code { 101 | padding: 0; 102 | white-space: pre; } 103 | 104 | .content-wrapper { 105 | display: flex; 106 | flex-direction: column; } 107 | @media (min-width: 768px) { 108 | .content-wrapper { 109 | flex-direction: row; } } 110 | 111 | .header { 112 | display: flex; 113 | padding: 8px; 114 | font-size: 0.875em; 115 | background: #444; 116 | color: #999; } 117 | 118 | .header-col { 119 | margin: 0; 120 | padding: 0 8px; } 121 | 122 | .header-col--primary { 123 | flex: 1; } 124 | 125 | .header-link { 126 | color: #fff; } 127 | 128 | .header-icon { 129 | padding-right: 6px; 130 | vertical-align: -4px; 131 | height: 16px; } 132 | 133 | .breadcrumbs { 134 | font-size: 0.875em; 135 | padding: 8px 16px; 136 | margin: 0; 137 | background: #fbfbfb; 138 | border-bottom: 1px solid #ddd; } 139 | 140 | .carat { 141 | height: 10px; 142 | margin: 0 5px; } 143 | 144 | .navigation { 145 | order: 2; } 146 | @media (min-width: 768px) { 147 | .navigation { 148 | order: 1; 149 | width: 25%; 150 | max-width: 300px; 151 | padding-bottom: 64px; 152 | overflow: hidden; 153 | word-wrap: normal; 154 | background: #fbfbfb; 155 | border-right: 1px solid #ddd; } } 156 | 157 | .nav-groups { 158 | list-style-type: none; 159 | padding-left: 0; } 160 | 161 | .nav-group-name { 162 | border-bottom: 1px solid #ddd; 163 | padding: 8px 0 8px 16px; } 164 | 165 | .nav-group-name-link { 166 | color: #333; } 167 | 168 | .nav-group-tasks { 169 | margin: 8px 0; 170 | padding: 0 0 0 8px; } 171 | 172 | .nav-group-task { 173 | font-size: 1em; 174 | list-style-type: none; 175 | white-space: nowrap; } 176 | 177 | .nav-group-task-link { 178 | color: #808080; } 179 | 180 | .main-content { 181 | order: 1; } 182 | @media (min-width: 768px) { 183 | .main-content { 184 | order: 2; 185 | flex: 1; 186 | padding-bottom: 60px; } } 187 | 188 | .section { 189 | padding: 0 32px; 190 | border-bottom: 1px solid #ddd; } 191 | 192 | .section-content { 193 | max-width: 834px; 194 | margin: 0 auto; 195 | padding: 16px 0; } 196 | 197 | .section-name { 198 | color: #666; 199 | display: block; } 200 | 201 | .declaration .highlight { 202 | overflow-x: initial; 203 | padding: 8px 0; 204 | margin: 0; 205 | background-color: transparent; 206 | border: none; } 207 | 208 | .task-group-section { 209 | border-top: 1px solid #ddd; } 210 | 211 | .task-group { 212 | padding-top: 0px; } 213 | 214 | .task-name-container a[name]:before { 215 | content: ""; 216 | display: block; } 217 | 218 | .item-container { 219 | padding: 0; } 220 | 221 | .item { 222 | padding-top: 8px; 223 | width: 100%; 224 | list-style-type: none; } 225 | .item a[name]:before { 226 | content: ""; 227 | display: block; } 228 | .item .token { 229 | padding-left: 3px; 230 | margin-left: 0px; 231 | font-size: 1rem; } 232 | .item .declaration-note { 233 | font-size: .85em; 234 | color: #808080; 235 | font-style: italic; } 236 | 237 | .pointer-container { 238 | border-bottom: 1px solid #ddd; 239 | left: -23px; 240 | padding-bottom: 13px; 241 | position: relative; 242 | width: 110%; } 243 | 244 | .pointer { 245 | left: 21px; 246 | top: 7px; 247 | display: block; 248 | position: absolute; 249 | width: 12px; 250 | height: 12px; 251 | border-left: 1px solid #ddd; 252 | border-top: 1px solid #ddd; 253 | background: #fff; 254 | transform: rotate(45deg); } 255 | 256 | .height-container { 257 | display: none; 258 | position: relative; 259 | width: 100%; 260 | overflow: hidden; } 261 | .height-container .section { 262 | background: #fff; 263 | border: 1px solid #ddd; 264 | border-top-width: 0; 265 | padding-top: 10px; 266 | padding-bottom: 5px; 267 | padding: 8px 16px; } 268 | 269 | .aside, .language { 270 | padding: 6px 12px; 271 | margin: 12px 0; 272 | border-left: 5px solid #dddddd; 273 | overflow-y: hidden; } 274 | .aside .aside-title, .language .aside-title { 275 | font-size: 9px; 276 | letter-spacing: 2px; 277 | text-transform: uppercase; 278 | padding-bottom: 0; 279 | margin: 0; 280 | color: #aaa; 281 | -webkit-user-select: none; } 282 | .aside p:last-child, .language p:last-child { 283 | margin-bottom: 0; } 284 | 285 | .language { 286 | border-left: 5px solid #cde9f4; } 287 | .language .aside-title { 288 | color: #4183c4; } 289 | 290 | .aside-warning { 291 | border-left: 5px solid #ff6666; } 292 | .aside-warning .aside-title { 293 | color: #ff0000; } 294 | 295 | .graybox { 296 | border-collapse: collapse; 297 | width: 100%; } 298 | .graybox p { 299 | margin: 0; 300 | word-break: break-word; 301 | min-width: 50px; } 302 | .graybox td { 303 | border: 1px solid #ddd; 304 | padding: 5px 25px 5px 10px; 305 | vertical-align: middle; } 306 | .graybox tr td:first-of-type { 307 | text-align: right; 308 | padding: 7px; 309 | vertical-align: top; 310 | word-break: normal; 311 | width: 40px; } 312 | 313 | .slightly-smaller { 314 | font-size: 0.9em; } 315 | 316 | .footer { 317 | padding: 8px 16px; 318 | background: #444; 319 | color: #ddd; 320 | font-size: 0.8em; } 321 | .footer p { 322 | margin: 8px 0; } 323 | .footer a { 324 | color: #fff; } 325 | 326 | html.dash .header, html.dash .breadcrumbs, html.dash .navigation { 327 | display: none; } 328 | html.dash .height-container { 329 | display: block; } 330 | 331 | form[role=search] input { 332 | font: 16px/1.7 "Helvetica Neue", Helvetica, Arial, sans-serif; 333 | font-size: 14px; 334 | line-height: 24px; 335 | padding: 0 10px; 336 | margin: 0; 337 | border: none; 338 | border-radius: 1em; } 339 | .loading form[role=search] input { 340 | background: white url(../img/spinner.gif) center right 4px no-repeat; } 341 | form[role=search] .tt-menu { 342 | margin: 0; 343 | min-width: 300px; 344 | background: #fbfbfb; 345 | color: #333; 346 | border: 1px solid #ddd; } 347 | form[role=search] .tt-highlight { 348 | font-weight: bold; } 349 | form[role=search] .tt-suggestion { 350 | font: 16px/1.7 "Helvetica Neue", Helvetica, Arial, sans-serif; 351 | padding: 0 8px; } 352 | form[role=search] .tt-suggestion span { 353 | display: table-cell; 354 | white-space: nowrap; } 355 | form[role=search] .tt-suggestion .doc-parent-name { 356 | width: 100%; 357 | text-align: right; 358 | font-weight: normal; 359 | font-size: 0.9em; 360 | padding-left: 16px; } 361 | form[role=search] .tt-suggestion:hover, 362 | form[role=search] .tt-suggestion.tt-cursor { 363 | cursor: pointer; 364 | background-color: #4183c4; 365 | color: #fff; } 366 | form[role=search] .tt-suggestion:hover .doc-parent-name, 367 | form[role=search] .tt-suggestion.tt-cursor .doc-parent-name { 368 | color: #fff; } 369 | -------------------------------------------------------------------------------- /docs/api/docsets/MarcoKit.docset/Contents/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleIdentifier 6 | com.jazzy.marcokit 7 | CFBundleName 8 | MarcoKit 9 | DocSetPlatformFamily 10 | marcokit 11 | isDashDocset 12 | 13 | dashIndexFilePath 14 | index.html 15 | isJavaScriptEnabled 16 | 17 | DashDocSetFamily 18 | dashtoc 19 | 20 | 21 | -------------------------------------------------------------------------------- /docs/api/docsets/MarcoKit.docset/Contents/Resources/Documents/css/highlight.css: -------------------------------------------------------------------------------- 1 | /* Credit to https://gist.github.com/wataru420/2048287 */ 2 | .highlight { 3 | /* Comment */ 4 | /* Error */ 5 | /* Keyword */ 6 | /* Operator */ 7 | /* Comment.Multiline */ 8 | /* Comment.Preproc */ 9 | /* Comment.Single */ 10 | /* Comment.Special */ 11 | /* Generic.Deleted */ 12 | /* Generic.Deleted.Specific */ 13 | /* Generic.Emph */ 14 | /* Generic.Error */ 15 | /* Generic.Heading */ 16 | /* Generic.Inserted */ 17 | /* Generic.Inserted.Specific */ 18 | /* Generic.Output */ 19 | /* Generic.Prompt */ 20 | /* Generic.Strong */ 21 | /* Generic.Subheading */ 22 | /* Generic.Traceback */ 23 | /* Keyword.Constant */ 24 | /* Keyword.Declaration */ 25 | /* Keyword.Pseudo */ 26 | /* Keyword.Reserved */ 27 | /* Keyword.Type */ 28 | /* Literal.Number */ 29 | /* Literal.String */ 30 | /* Name.Attribute */ 31 | /* Name.Builtin */ 32 | /* Name.Class */ 33 | /* Name.Constant */ 34 | /* Name.Entity */ 35 | /* Name.Exception */ 36 | /* Name.Function */ 37 | /* Name.Namespace */ 38 | /* Name.Tag */ 39 | /* Name.Variable */ 40 | /* Operator.Word */ 41 | /* Text.Whitespace */ 42 | /* Literal.Number.Float */ 43 | /* Literal.Number.Hex */ 44 | /* Literal.Number.Integer */ 45 | /* Literal.Number.Oct */ 46 | /* Literal.String.Backtick */ 47 | /* Literal.String.Char */ 48 | /* Literal.String.Doc */ 49 | /* Literal.String.Double */ 50 | /* Literal.String.Escape */ 51 | /* Literal.String.Heredoc */ 52 | /* Literal.String.Interpol */ 53 | /* Literal.String.Other */ 54 | /* Literal.String.Regex */ 55 | /* Literal.String.Single */ 56 | /* Literal.String.Symbol */ 57 | /* Name.Builtin.Pseudo */ 58 | /* Name.Variable.Class */ 59 | /* Name.Variable.Global */ 60 | /* Name.Variable.Instance */ 61 | /* Literal.Number.Integer.Long */ } 62 | .highlight .c { 63 | color: #999988; 64 | font-style: italic; } 65 | .highlight .err { 66 | color: #a61717; 67 | background-color: #e3d2d2; } 68 | .highlight .k { 69 | color: #000000; 70 | font-weight: bold; } 71 | .highlight .o { 72 | color: #000000; 73 | font-weight: bold; } 74 | .highlight .cm { 75 | color: #999988; 76 | font-style: italic; } 77 | .highlight .cp { 78 | color: #999999; 79 | font-weight: bold; } 80 | .highlight .c1 { 81 | color: #999988; 82 | font-style: italic; } 83 | .highlight .cs { 84 | color: #999999; 85 | font-weight: bold; 86 | font-style: italic; } 87 | .highlight .gd { 88 | color: #000000; 89 | background-color: #ffdddd; } 90 | .highlight .gd .x { 91 | color: #000000; 92 | background-color: #ffaaaa; } 93 | .highlight .ge { 94 | color: #000000; 95 | font-style: italic; } 96 | .highlight .gr { 97 | color: #aa0000; } 98 | .highlight .gh { 99 | color: #999999; } 100 | .highlight .gi { 101 | color: #000000; 102 | background-color: #ddffdd; } 103 | .highlight .gi .x { 104 | color: #000000; 105 | background-color: #aaffaa; } 106 | .highlight .go { 107 | color: #888888; } 108 | .highlight .gp { 109 | color: #555555; } 110 | .highlight .gs { 111 | font-weight: bold; } 112 | .highlight .gu { 113 | color: #aaaaaa; } 114 | .highlight .gt { 115 | color: #aa0000; } 116 | .highlight .kc { 117 | color: #000000; 118 | font-weight: bold; } 119 | .highlight .kd { 120 | color: #000000; 121 | font-weight: bold; } 122 | .highlight .kp { 123 | color: #000000; 124 | font-weight: bold; } 125 | .highlight .kr { 126 | color: #000000; 127 | font-weight: bold; } 128 | .highlight .kt { 129 | color: #445588; } 130 | .highlight .m { 131 | color: #009999; } 132 | .highlight .s { 133 | color: #d14; } 134 | .highlight .na { 135 | color: #008080; } 136 | .highlight .nb { 137 | color: #0086B3; } 138 | .highlight .nc { 139 | color: #445588; 140 | font-weight: bold; } 141 | .highlight .no { 142 | color: #008080; } 143 | .highlight .ni { 144 | color: #800080; } 145 | .highlight .ne { 146 | color: #990000; 147 | font-weight: bold; } 148 | .highlight .nf { 149 | color: #990000; } 150 | .highlight .nn { 151 | color: #555555; } 152 | .highlight .nt { 153 | color: #000080; } 154 | .highlight .nv { 155 | color: #008080; } 156 | .highlight .ow { 157 | color: #000000; 158 | font-weight: bold; } 159 | .highlight .w { 160 | color: #bbbbbb; } 161 | .highlight .mf { 162 | color: #009999; } 163 | .highlight .mh { 164 | color: #009999; } 165 | .highlight .mi { 166 | color: #009999; } 167 | .highlight .mo { 168 | color: #009999; } 169 | .highlight .sb { 170 | color: #d14; } 171 | .highlight .sc { 172 | color: #d14; } 173 | .highlight .sd { 174 | color: #d14; } 175 | .highlight .s2 { 176 | color: #d14; } 177 | .highlight .se { 178 | color: #d14; } 179 | .highlight .sh { 180 | color: #d14; } 181 | .highlight .si { 182 | color: #d14; } 183 | .highlight .sx { 184 | color: #d14; } 185 | .highlight .sr { 186 | color: #009926; } 187 | .highlight .s1 { 188 | color: #d14; } 189 | .highlight .ss { 190 | color: #990073; } 191 | .highlight .bp { 192 | color: #999999; } 193 | .highlight .vc { 194 | color: #008080; } 195 | .highlight .vg { 196 | color: #008080; } 197 | .highlight .vi { 198 | color: #008080; } 199 | .highlight .il { 200 | color: #009999; } 201 | -------------------------------------------------------------------------------- /docs/api/docsets/MarcoKit.docset/Contents/Resources/Documents/img/carat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marta-file-manager/MarcoKit/fc7178402ee77e721859707364f25b0be26b1a3c/docs/api/docsets/MarcoKit.docset/Contents/Resources/Documents/img/carat.png -------------------------------------------------------------------------------- /docs/api/docsets/MarcoKit.docset/Contents/Resources/Documents/img/dash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marta-file-manager/MarcoKit/fc7178402ee77e721859707364f25b0be26b1a3c/docs/api/docsets/MarcoKit.docset/Contents/Resources/Documents/img/dash.png -------------------------------------------------------------------------------- /docs/api/docsets/MarcoKit.docset/Contents/Resources/Documents/img/gh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marta-file-manager/MarcoKit/fc7178402ee77e721859707364f25b0be26b1a3c/docs/api/docsets/MarcoKit.docset/Contents/Resources/Documents/img/gh.png -------------------------------------------------------------------------------- /docs/api/docsets/MarcoKit.docset/Contents/Resources/Documents/img/spinner.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marta-file-manager/MarcoKit/fc7178402ee77e721859707364f25b0be26b1a3c/docs/api/docsets/MarcoKit.docset/Contents/Resources/Documents/img/spinner.gif -------------------------------------------------------------------------------- /docs/api/docsets/MarcoKit.docset/Contents/Resources/Documents/js/jazzy.js: -------------------------------------------------------------------------------- 1 | window.jazzy = {'docset': false} 2 | if (typeof window.dash != 'undefined') { 3 | document.documentElement.className += ' dash' 4 | window.jazzy.docset = true 5 | } 6 | if (navigator.userAgent.match(/xcode/i)) { 7 | document.documentElement.className += ' xcode' 8 | window.jazzy.docset = true 9 | } 10 | 11 | // On doc load, toggle the URL hash discussion if present 12 | $(document).ready(function() { 13 | if (!window.jazzy.docset) { 14 | var linkToHash = $('a[href="' + window.location.hash +'"]'); 15 | linkToHash.trigger("click"); 16 | } 17 | }); 18 | 19 | // On token click, toggle its discussion and animate token.marginLeft 20 | $(".token").click(function(event) { 21 | if (window.jazzy.docset) { 22 | return; 23 | } 24 | var link = $(this); 25 | var animationDuration = 300; 26 | $content = link.parent().parent().next(); 27 | $content.slideToggle(animationDuration); 28 | 29 | // Keeps the document from jumping to the hash. 30 | var href = $(this).attr('href'); 31 | if (history.pushState) { 32 | history.pushState({}, '', href); 33 | } else { 34 | location.hash = href; 35 | } 36 | event.preventDefault(); 37 | }); 38 | 39 | // Dumb down quotes within code blocks that delimit strings instead of quotations 40 | // https://github.com/realm/jazzy/issues/714 41 | $("code q").replaceWith(function () { 42 | return ["\"", $(this).contents(), "\""]; 43 | }); 44 | -------------------------------------------------------------------------------- /docs/api/docsets/MarcoKit.docset/Contents/Resources/Documents/js/jazzy.search.js: -------------------------------------------------------------------------------- 1 | $(function(){ 2 | var searchIndex = lunr(function() { 3 | this.ref('url'); 4 | this.field('name'); 5 | }); 6 | 7 | var $typeahead = $('[data-typeahead]'); 8 | var $form = $typeahead.parents('form'); 9 | var searchURL = $form.attr('action'); 10 | 11 | function displayTemplate(result) { 12 | return result.name; 13 | } 14 | 15 | function suggestionTemplate(result) { 16 | var t = '
'; 17 | t += '' + result.name + ''; 18 | if (result.parent_name) { 19 | t += '' + result.parent_name + ''; 20 | } 21 | t += '
'; 22 | return t; 23 | } 24 | 25 | $typeahead.one('focus', function() { 26 | $form.addClass('loading'); 27 | 28 | $.getJSON(searchURL).then(function(searchData) { 29 | $.each(searchData, function (url, doc) { 30 | searchIndex.add({url: url, name: doc.name}); 31 | }); 32 | 33 | $typeahead.typeahead( 34 | { 35 | highlight: true, 36 | minLength: 3 37 | }, 38 | { 39 | limit: 10, 40 | display: displayTemplate, 41 | templates: { suggestion: suggestionTemplate }, 42 | source: function(query, sync) { 43 | var results = searchIndex.search(query).map(function(result) { 44 | var doc = searchData[result.ref]; 45 | doc.url = result.ref; 46 | return doc; 47 | }); 48 | sync(results); 49 | } 50 | } 51 | ); 52 | $form.removeClass('loading'); 53 | $typeahead.trigger('focus'); 54 | }); 55 | }); 56 | 57 | var baseURL = searchURL.slice(0, -"search.json".length); 58 | 59 | $typeahead.on('typeahead:select', function(e, result) { 60 | window.location = baseURL + result.url; 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /docs/api/docsets/MarcoKit.docset/Contents/Resources/Documents/undocumented.json: -------------------------------------------------------------------------------- 1 | { 2 | "warnings": [ 3 | { 4 | "file": "/Users/yan/projects/marco/marco/api/MarcoParsingError.swift", 5 | "line": 17, 6 | "symbol": "MarcoParsingError.localizedDescription", 7 | "symbol_kind": "source.lang.swift.decl.var.instance", 8 | "warning": "undocumented" 9 | }, 10 | { 11 | "file": "/Users/yan/projects/marco/marco/api/MarcoParsingError.swift", 12 | "line": 32, 13 | "symbol": "MarcoParsingError.ErrorKind", 14 | "symbol_kind": "source.lang.swift.decl.enum", 15 | "warning": "undocumented" 16 | }, 17 | { 18 | "file": "/Users/yan/projects/marco/marco/api/MarcoParsingError.swift", 19 | "line": 45, 20 | "symbol": "MarcoNonStrictParsingError.localizedDescription", 21 | "symbol_kind": "source.lang.swift.decl.var.instance", 22 | "warning": "undocumented" 23 | }, 24 | { 25 | "file": "/Users/yan/projects/marco/marco/api/NumberExtensions.swift", 26 | "line": 4, 27 | "symbol": "MarcoNumberLiteral.int32Value", 28 | "symbol_kind": "source.lang.swift.decl.var.instance", 29 | "warning": "undocumented" 30 | }, 31 | { 32 | "file": "/Users/yan/projects/marco/marco/api/NumberExtensions.swift", 33 | "line": 8, 34 | "symbol": "MarcoNumberLiteral.uint32value", 35 | "symbol_kind": "source.lang.swift.decl.var.instance", 36 | "warning": "undocumented" 37 | }, 38 | { 39 | "file": "/Users/yan/projects/marco/marco/api/NumberExtensions.swift", 40 | "line": 12, 41 | "symbol": "MarcoNumberLiteral.int64Value", 42 | "symbol_kind": "source.lang.swift.decl.var.instance", 43 | "warning": "undocumented" 44 | }, 45 | { 46 | "file": "/Users/yan/projects/marco/marco/api/NumberExtensions.swift", 47 | "line": 16, 48 | "symbol": "MarcoNumberLiteral.uint64Value", 49 | "symbol_kind": "source.lang.swift.decl.var.instance", 50 | "warning": "undocumented" 51 | }, 52 | { 53 | "file": "/Users/yan/projects/marco/marco/api/NumberExtensions.swift", 54 | "line": 20, 55 | "symbol": "MarcoNumberLiteral.floatValue", 56 | "symbol_kind": "source.lang.swift.decl.var.instance", 57 | "warning": "undocumented" 58 | }, 59 | { 60 | "file": "/Users/yan/projects/marco/marco/api/NumberExtensions.swift", 61 | "line": 24, 62 | "symbol": "MarcoNumberLiteral.cgFloatValue", 63 | "symbol_kind": "source.lang.swift.decl.var.instance", 64 | "warning": "undocumented" 65 | }, 66 | { 67 | "file": "/Users/yan/projects/marco/marco/api/values/MarcoArray.swift", 68 | "line": 128, 69 | "symbol": "MarcoArraySequence", 70 | "symbol_kind": "source.lang.swift.decl.struct", 71 | "warning": "undocumented" 72 | }, 73 | { 74 | "file": "/Users/yan/projects/marco/marco/api/values/MarcoDocument.swift", 75 | "line": 5, 76 | "symbol": "MarcoDocument.text", 77 | "symbol_kind": "source.lang.swift.decl.var.instance", 78 | "warning": "undocumented" 79 | }, 80 | { 81 | "file": "/Users/yan/projects/marco/marco/api/values/MarcoObject.swift", 82 | "line": 117, 83 | "symbol": "MarcoObjectSequence", 84 | "symbol_kind": "source.lang.swift.decl.struct", 85 | "warning": "undocumented" 86 | }, 87 | { 88 | "file": "/Users/yan/projects/marco/marco/api/values/MarcoStringLiteral.swift", 89 | "line": 5, 90 | "symbol": "MarcoStringLiteral.value", 91 | "symbol_kind": "source.lang.swift.decl.var.instance", 92 | "warning": "undocumented" 93 | }, 94 | { 95 | "file": "/Users/yan/projects/marco/marco/api/values/MarcoValue.swift", 96 | "line": 23, 97 | "symbol": "MarcoValue.accept(_:)", 98 | "symbol_kind": "source.lang.swift.decl.function.method.instance", 99 | "warning": "undocumented" 100 | }, 101 | { 102 | "file": "/Users/yan/projects/marco/marco/node/MarcoNode.swift", 103 | "line": null, 104 | "symbol": "MarcoTreeNode", 105 | "symbol_kind": "source.lang.swift.decl.extension", 106 | "warning": "undocumented" 107 | } 108 | ], 109 | "source_directory": "/Users/yan/projects/marco" 110 | } -------------------------------------------------------------------------------- /docs/api/docsets/MarcoKit.docset/Contents/Resources/docSet.dsidx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marta-file-manager/MarcoKit/fc7178402ee77e721859707364f25b0be26b1a3c/docs/api/docsets/MarcoKit.docset/Contents/Resources/docSet.dsidx -------------------------------------------------------------------------------- /docs/api/docsets/MarcoKit.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marta-file-manager/MarcoKit/fc7178402ee77e721859707364f25b0be26b1a3c/docs/api/docsets/MarcoKit.tgz -------------------------------------------------------------------------------- /docs/api/img/carat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marta-file-manager/MarcoKit/fc7178402ee77e721859707364f25b0be26b1a3c/docs/api/img/carat.png -------------------------------------------------------------------------------- /docs/api/img/dash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marta-file-manager/MarcoKit/fc7178402ee77e721859707364f25b0be26b1a3c/docs/api/img/dash.png -------------------------------------------------------------------------------- /docs/api/img/gh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marta-file-manager/MarcoKit/fc7178402ee77e721859707364f25b0be26b1a3c/docs/api/img/gh.png -------------------------------------------------------------------------------- /docs/api/img/spinner.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marta-file-manager/MarcoKit/fc7178402ee77e721859707364f25b0be26b1a3c/docs/api/img/spinner.gif -------------------------------------------------------------------------------- /docs/api/js/jazzy.js: -------------------------------------------------------------------------------- 1 | window.jazzy = {'docset': false} 2 | if (typeof window.dash != 'undefined') { 3 | document.documentElement.className += ' dash' 4 | window.jazzy.docset = true 5 | } 6 | if (navigator.userAgent.match(/xcode/i)) { 7 | document.documentElement.className += ' xcode' 8 | window.jazzy.docset = true 9 | } 10 | 11 | // On doc load, toggle the URL hash discussion if present 12 | $(document).ready(function() { 13 | if (!window.jazzy.docset) { 14 | var linkToHash = $('a[href="' + window.location.hash +'"]'); 15 | linkToHash.trigger("click"); 16 | } 17 | }); 18 | 19 | // On token click, toggle its discussion and animate token.marginLeft 20 | $(".token").click(function(event) { 21 | if (window.jazzy.docset) { 22 | return; 23 | } 24 | var link = $(this); 25 | var animationDuration = 300; 26 | $content = link.parent().parent().next(); 27 | $content.slideToggle(animationDuration); 28 | 29 | // Keeps the document from jumping to the hash. 30 | var href = $(this).attr('href'); 31 | if (history.pushState) { 32 | history.pushState({}, '', href); 33 | } else { 34 | location.hash = href; 35 | } 36 | event.preventDefault(); 37 | }); 38 | 39 | // Dumb down quotes within code blocks that delimit strings instead of quotations 40 | // https://github.com/realm/jazzy/issues/714 41 | $("code q").replaceWith(function () { 42 | return ["\"", $(this).contents(), "\""]; 43 | }); 44 | -------------------------------------------------------------------------------- /docs/api/js/jazzy.search.js: -------------------------------------------------------------------------------- 1 | $(function(){ 2 | var searchIndex = lunr(function() { 3 | this.ref('url'); 4 | this.field('name'); 5 | }); 6 | 7 | var $typeahead = $('[data-typeahead]'); 8 | var $form = $typeahead.parents('form'); 9 | var searchURL = $form.attr('action'); 10 | 11 | function displayTemplate(result) { 12 | return result.name; 13 | } 14 | 15 | function suggestionTemplate(result) { 16 | var t = '
'; 17 | t += '' + result.name + ''; 18 | if (result.parent_name) { 19 | t += '' + result.parent_name + ''; 20 | } 21 | t += '
'; 22 | return t; 23 | } 24 | 25 | $typeahead.one('focus', function() { 26 | $form.addClass('loading'); 27 | 28 | $.getJSON(searchURL).then(function(searchData) { 29 | $.each(searchData, function (url, doc) { 30 | searchIndex.add({url: url, name: doc.name}); 31 | }); 32 | 33 | $typeahead.typeahead( 34 | { 35 | highlight: true, 36 | minLength: 3 37 | }, 38 | { 39 | limit: 10, 40 | display: displayTemplate, 41 | templates: { suggestion: suggestionTemplate }, 42 | source: function(query, sync) { 43 | var results = searchIndex.search(query).map(function(result) { 44 | var doc = searchData[result.ref]; 45 | doc.url = result.ref; 46 | return doc; 47 | }); 48 | sync(results); 49 | } 50 | } 51 | ); 52 | $form.removeClass('loading'); 53 | $typeahead.trigger('focus'); 54 | }); 55 | }); 56 | 57 | var baseURL = searchURL.slice(0, -"search.json".length); 58 | 59 | $typeahead.on('typeahead:select', function(e, result) { 60 | window.location = baseURL + result.url; 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /docs/api/undocumented.json: -------------------------------------------------------------------------------- 1 | { 2 | "warnings": [ 3 | { 4 | "file": "/Users/yan/projects/marco/marco/api/MarcoParsingError.swift", 5 | "line": 17, 6 | "symbol": "MarcoParsingError.localizedDescription", 7 | "symbol_kind": "source.lang.swift.decl.var.instance", 8 | "warning": "undocumented" 9 | }, 10 | { 11 | "file": "/Users/yan/projects/marco/marco/api/MarcoParsingError.swift", 12 | "line": 32, 13 | "symbol": "MarcoParsingError.ErrorKind", 14 | "symbol_kind": "source.lang.swift.decl.enum", 15 | "warning": "undocumented" 16 | }, 17 | { 18 | "file": "/Users/yan/projects/marco/marco/api/MarcoParsingError.swift", 19 | "line": 45, 20 | "symbol": "MarcoNonStrictParsingError.localizedDescription", 21 | "symbol_kind": "source.lang.swift.decl.var.instance", 22 | "warning": "undocumented" 23 | }, 24 | { 25 | "file": "/Users/yan/projects/marco/marco/api/NumberExtensions.swift", 26 | "line": 4, 27 | "symbol": "MarcoNumberLiteral.int32Value", 28 | "symbol_kind": "source.lang.swift.decl.var.instance", 29 | "warning": "undocumented" 30 | }, 31 | { 32 | "file": "/Users/yan/projects/marco/marco/api/NumberExtensions.swift", 33 | "line": 8, 34 | "symbol": "MarcoNumberLiteral.uint32value", 35 | "symbol_kind": "source.lang.swift.decl.var.instance", 36 | "warning": "undocumented" 37 | }, 38 | { 39 | "file": "/Users/yan/projects/marco/marco/api/NumberExtensions.swift", 40 | "line": 12, 41 | "symbol": "MarcoNumberLiteral.int64Value", 42 | "symbol_kind": "source.lang.swift.decl.var.instance", 43 | "warning": "undocumented" 44 | }, 45 | { 46 | "file": "/Users/yan/projects/marco/marco/api/NumberExtensions.swift", 47 | "line": 16, 48 | "symbol": "MarcoNumberLiteral.uint64Value", 49 | "symbol_kind": "source.lang.swift.decl.var.instance", 50 | "warning": "undocumented" 51 | }, 52 | { 53 | "file": "/Users/yan/projects/marco/marco/api/NumberExtensions.swift", 54 | "line": 20, 55 | "symbol": "MarcoNumberLiteral.floatValue", 56 | "symbol_kind": "source.lang.swift.decl.var.instance", 57 | "warning": "undocumented" 58 | }, 59 | { 60 | "file": "/Users/yan/projects/marco/marco/api/NumberExtensions.swift", 61 | "line": 24, 62 | "symbol": "MarcoNumberLiteral.cgFloatValue", 63 | "symbol_kind": "source.lang.swift.decl.var.instance", 64 | "warning": "undocumented" 65 | }, 66 | { 67 | "file": "/Users/yan/projects/marco/marco/api/values/MarcoArray.swift", 68 | "line": 128, 69 | "symbol": "MarcoArraySequence", 70 | "symbol_kind": "source.lang.swift.decl.struct", 71 | "warning": "undocumented" 72 | }, 73 | { 74 | "file": "/Users/yan/projects/marco/marco/api/values/MarcoDocument.swift", 75 | "line": 5, 76 | "symbol": "MarcoDocument.text", 77 | "symbol_kind": "source.lang.swift.decl.var.instance", 78 | "warning": "undocumented" 79 | }, 80 | { 81 | "file": "/Users/yan/projects/marco/marco/api/values/MarcoObject.swift", 82 | "line": 117, 83 | "symbol": "MarcoObjectSequence", 84 | "symbol_kind": "source.lang.swift.decl.struct", 85 | "warning": "undocumented" 86 | }, 87 | { 88 | "file": "/Users/yan/projects/marco/marco/api/values/MarcoStringLiteral.swift", 89 | "line": 5, 90 | "symbol": "MarcoStringLiteral.value", 91 | "symbol_kind": "source.lang.swift.decl.var.instance", 92 | "warning": "undocumented" 93 | }, 94 | { 95 | "file": "/Users/yan/projects/marco/marco/api/values/MarcoValue.swift", 96 | "line": 23, 97 | "symbol": "MarcoValue.accept(_:)", 98 | "symbol_kind": "source.lang.swift.decl.function.method.instance", 99 | "warning": "undocumented" 100 | }, 101 | { 102 | "file": "/Users/yan/projects/marco/marco/node/MarcoNode.swift", 103 | "line": null, 104 | "symbol": "MarcoTreeNode", 105 | "symbol_kind": "source.lang.swift.decl.extension", 106 | "warning": "undocumented" 107 | } 108 | ], 109 | "source_directory": "/Users/yan/projects/marco" 110 | } -------------------------------------------------------------------------------- /jazzy.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | jazzy --config jazzy.json -------------------------------------------------------------------------------- /jazzy.json: -------------------------------------------------------------------------------- 1 | { 2 | "xcodebuild_arguments": ["-scheme", "MarcoKit"], 3 | "author": "Yan Zhulanow", 4 | "author_url": "https://marta.yanex.org", 5 | "module": "MarcoKit", 6 | "module_version": "1.0", 7 | "source_directory": ".", 8 | "framework_root": ".", 9 | "output": "docs/api", 10 | "use_safe_filenames": true, 11 | "hide_documentation_coverage": true, 12 | "skip_undocumented": true, 13 | "readme": "README.md", 14 | "theme": "fullwidth", 15 | 16 | "custom_categories": 17 | [ 18 | { 19 | "name": "Values", 20 | "children": [ 21 | "MarcoValue", 22 | "MarcoDocument", 23 | "MarcoNumberLiteral", 24 | "MarcoIntLiteral", 25 | "MarcoDoubleLiteral", 26 | "MarcoStringLiteral", 27 | "MarcoBoolLiteral", 28 | "MarcoArray", 29 | "MarcoObject", 30 | "MarcoIdentifier", 31 | "MarcoNullLiteral" 32 | ] 33 | }, 34 | { 35 | "name": "Parsing", 36 | "children": [ 37 | "Marco", 38 | "MarcoParsingError", 39 | "MarcoNonStrictParsingError" 40 | ] 41 | }, 42 | { 43 | "name": "Visitor", 44 | "children": [ 45 | "MarcoVisitor" 46 | ] 47 | }, { 48 | "name": "Extensions", 49 | "children": [ 50 | "Int", 51 | "Int32", 52 | "UInt32", 53 | "Int64", 54 | "UInt64", 55 | "NSColor", 56 | "Float", 57 | "Double", 58 | "CGFloat", 59 | "String", 60 | "Bool", 61 | "NSNull" 62 | ] 63 | } 64 | ] 65 | } --------------------------------------------------------------------------------