` = 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 |
--------------------------------------------------------------------------------
/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/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/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.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/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 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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.
--------------------------------------------------------------------------------
/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/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/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.. 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 |
--------------------------------------------------------------------------------