├── .codebeatsettings ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── Package.swift ├── README.md ├── Sources └── Toml │ ├── Date.swift │ ├── Evaluator.swift │ ├── Grammar.swift │ ├── Helpers.swift │ ├── Lexer.swift │ ├── Parser.swift │ ├── Path.swift │ ├── Regex.swift │ ├── Serialize.swift │ ├── String.swift │ ├── Tokens.swift │ └── Toml.swift └── Tests ├── LinuxMain.swift └── TomlTests ├── TomlTests.swift ├── array-empty.toml ├── array-mixed-types-arrays-and-ints.toml ├── array-mixed-types-ints-and-floats.toml ├── array-mixed-types-strings-and-ints.toml ├── array-nospaces.toml ├── arrays-hetergeneous.toml ├── arrays-nested.toml ├── arrays.toml ├── bool.toml ├── comments-everywhere.toml ├── date-format.toml ├── datetime-malformed-no-leads.toml ├── datetime-malformed-no-secs.toml ├── datetime-malformed-no-t.toml ├── datetime-malformed-with-milli.toml ├── datetime.toml ├── duplicate-key-table.toml ├── duplicate-keys.toml ├── duplicate-tables.toml ├── empty-implicit-table.toml ├── empty-table.toml ├── empty.toml ├── example.toml ├── expected-nested-tables.toml ├── expected-serialize.toml ├── float-no-leading-zero.toml ├── float-no-trailing-digits.toml ├── float.toml ├── hard_example-error1.toml ├── hard_example-error2.toml ├── hard_example-error3.toml ├── hard_example-error4.toml ├── hard_example.toml ├── hard_example_unicode.toml ├── implicit-and-explicit-after.toml ├── implicit-and-explicit-before.toml ├── implicit-groups.toml ├── integer.toml ├── key-empty.toml ├── key-equals-nospace.toml ├── key-hash.toml ├── key-newline.toml ├── key-open-bracket.toml ├── key-single-open-bracket.toml ├── key-space-error.toml ├── key-space.toml ├── key-special-chars.toml ├── key-start-bracket.toml ├── key-two-equals.toml ├── long-float.toml ├── long-integer.toml ├── multiline-string.toml ├── nested-tables.toml ├── raw-multiline-string.toml ├── raw-string.toml ├── serialize.toml ├── simple.toml ├── string-bad-byte-escape.toml ├── string-bad-escape.toml ├── string-byte-escapes.toml ├── string-empty.toml ├── string-escapes.toml ├── string-no-close.toml ├── string-simple.toml ├── string-with-pound.toml ├── table-array-implicit.toml ├── table-array-malformed-bracket.toml ├── table-array-malformed-empty.toml ├── table-array-many.toml ├── table-array-nest.toml ├── table-array-one.toml ├── table-empty-invalid.toml ├── table-empty.toml ├── table-nested-brackets-close.toml ├── table-nested-brackets-open.toml ├── table-sub-empty.toml ├── table-whitespace-invalid.toml ├── table-whitespace.toml ├── table-with-pound-invalid.toml ├── table-with-pound.toml ├── text-after-array-entries.toml ├── text-after-integer.toml ├── text-after-string.toml ├── text-after-table.toml ├── text-before-array-separator.toml ├── text-in-array.toml ├── toml-example.toml ├── unicode-escape.toml └── unicode-literal.toml /.codebeatsettings: -------------------------------------------------------------------------------- 1 | { 2 | "SWIFT": { 3 | "TOTAL_LOC": [225, 275, 350, 450], 4 | "LOC": [45, 60, 80, 100], 5 | "TOTAL_COMPLEXITY": [150, 200, 300, 400], 6 | "TOO_MANY_FUNCTIONS": [35, 40, 45, 50], 7 | "CYCLO": [100, 180, 280, 400], 8 | "ABC": [25, 40, 60, 80], 9 | "BLOCK_NESTING": [4, 5, 6, 7] 10 | } 11 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | .swiftpm 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | os: 2 | - osx 3 | language: generic 4 | sudo: required 5 | osx_image: xcode11.5 6 | install: 7 | - eval "$(curl -sL https://swiftenv.fuller.li/install.sh)" 8 | script: 9 | - swift build 10 | - swift test 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](http://keepachangelog.com/) 5 | and this project adheres to [Semantic Versioning](http://semver.org/). 6 | 7 | ## [Unreleased] 8 | 9 | ## [1.0.0] - 2018-02-20 10 | ### Added 11 | - Linux support 12 | 13 | ### Changed 14 | - Updated to Swift 4.0.3 15 | - Significant performance improvements 16 | - Move equatable == method to a static member to better conform with Swift3 conventions 17 | - SetValueProtocol method renamed from func setValue(key: , value: ) to 18 | func set(value:, for:) 19 | - Add support for running tests from XCode 20 | - Updated examples in README.md to reflect current API 21 | - Update String handling to not use .characters view per Swift4 conventions 22 | - Use range(at:) instead of rangeAt(_:) 23 | 24 | ## [0.4.0] - 2016-08-24 25 | ### Added 26 | - Serialize a `Toml` object to a valid TOML string 27 | - Resolve the full key path to tables through `prefixPath: [String]` 28 | - All accessors have [String] and String... (variadic) based variants 29 | 30 | ### Changed 31 | - [Issue #4] All accessor methods now return `Optionals` and do not throw. Notably, this 32 | means that `TomlError.KeyError` is no longer thrown if a missing key is 33 | requested. 34 | - `Toml.keys` removed; use: `keyNames: Set<[String]>` to access key names 35 | - `tableNames: Set<[String]>` is now part of the public API for accessing table names 36 | - `Toml.description` now returns a valid TOML string 37 | - `Toml.setValue(_:[String])` is now part of the public API 38 | - `Toml.float` has been removed 39 | 40 | ## [0.3.1] - 2016-08-23 41 | ### Changed 42 | - `hasKey(_ key: [String]) throws -> Bool` changed to 43 | `hasKey(key: [String], includeTables: Bool = true) throws -> Bool` to support 44 | inclusion of inline tables as valid key paths. 45 | - `hasKey(_ key: ...) throws -> Bool` now returns true if the key path refers 46 | to a table 47 | 48 | ### Fixed 49 | - [Issue #3] `hasTable(_: [String])` now correctly returns true for inline tables 50 | - `table(_: String...)` now correctly returns a Toml table for the requested 51 | key path 52 | 53 | ## [0.3.0] - 2016-08-19 54 | ### Added 55 | - [Issue #2] Support for iterating over all tables at a given level with `tables(_: [String])` 56 | method. 57 | - Added public API call for retrieving a TOML table at a specified level with 58 | `table(from: [String])` method. 59 | 60 | ### Changed 61 | - Rename `arrayWithPath(keyPath: [String])` to `array(_: [String])` for consistency 62 | with the rest of the public API. 63 | 64 | ### Fixed 65 | - `hasTable(_: [String])` now correctly returns true for implicitly defined tables. 66 | 67 | ## [0.2.0] - 2016-08-16 68 | ### Changed 69 | - Updated to Swift 3.0 preview 6 70 | 71 | ## [0.1.0] - 2016-08-14 72 | ### Added 73 | - Parse [TOML 0.4.0](https://github.com/toml-lang/toml) files with Swift 3.0 74 | 75 | [Unreleased]: https://github.com/jdfergason/swift-toml/compare/v1.0.0...HEAD 76 | [1.0.0]: https://github.com/jdfergason/swift-toml/compare/v0.4.0...v1.0.0 77 | [0.4.0]: https://github.com/jdfergason/swift-toml/compare/v0.3.1...v0.4.0 78 | [0.3.1]: https://github.com/jdfergason/swift-toml/compare/v0.3.0...v0.3.1 79 | [0.3.0]: https://github.com/jdfergason/swift-toml/compare/v0.2.0...v0.3.0 80 | [0.2.0]: https://github.com/jdfergason/swift-toml/compare/v0.1.0...v0.2.0 81 | [0.1.0]: https://github.com/jdfergason/swift-toml/tree/v0.1.0 82 | [Issue #2]: https://github.com/jdfergason/swift-toml/issues/2 83 | [Issue #3]: https://github.com/jdfergason/swift-toml/issues/3 84 | [Issue #4]: https://github.com/jdfergason/swift-toml/issues/4 85 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2016 JD Fergason 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.0 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "Toml", 7 | products: [ 8 | .library( 9 | name: "Toml", 10 | targets: ["Toml"]), 11 | ], 12 | dependencies: [ 13 | // Dependencies declare other packages that this package depends on. 14 | // .package(url: /* package url */, from: "1.0.0"), 15 | ], 16 | targets: [ 17 | .target( 18 | name: "Toml", 19 | dependencies: []), 20 | .testTarget( 21 | name: "TomlTests", 22 | dependencies: ["Toml"]), 23 | ] 24 | ) 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/jdfergason/swift-toml.svg?branch=master)](https://travis-ci.org/jdfergason/swift-toml) 2 | [![codebeat badge](https://codebeat.co/badges/21ffbe72-dd12-4d9d-ad01-cfdf423ea5fa)](https://codebeat.co/projects/github-com-jdfergason-swift-toml) 3 | 4 | # SwiftToml 5 | 6 | SwiftToml is a TOML parser written in the swift language. TOML is an intuitive 7 | configuration file format that is designed to be easy for humans to read and 8 | computers to parse. 9 | 10 | SwiftToml currently parses files that conform to version 0.4.0 of the TOML spec. 11 | 12 | For full details of writing TOML files see the [TOML documentation](https://github.com/toml-lang/toml). 13 | 14 | # Quickstart 15 | 16 | TOML files are parsed using one of two functions: 17 | 18 | 1. Read TOML from file 19 | 2. Parse TOML from string 20 | 21 | Both functions return a Toml object of parsed key/value pairs 22 | 23 | ## Parse TOML from file on disk 24 | 25 | ```swift 26 | import Toml 27 | let toml = try Toml(contentsOfFile: "/path/to/file.toml") 28 | ``` 29 | 30 | ## Parse TOML from string 31 | 32 | ```swift 33 | import Toml 34 | let toml = try Toml(withString: "answer = 42") 35 | ``` 36 | 37 | ## Get raw values from TOML document 38 | 39 | ```swift 40 | import Toml 41 | let toml = try Toml(contentsOfFile: "/path/to/file.toml") 42 | 43 | // string value 44 | print(toml.string("table1", "name")) 45 | 46 | // boolean value 47 | print(toml.bool("table1", "manager")) 48 | 49 | // integer value 50 | print(toml.int("table1", "age")) 51 | 52 | // double value 53 | print(toml.double("table1", "rating")) 54 | 55 | // date value 56 | print(toml.date("table1", "birthday")) 57 | 58 | // get value and resolve type at runtime 59 | print(try toml.value("title")!) 60 | 61 | // get array of type [String] 62 | let array: [String] = toml.array("locations")! 63 | 64 | // get table 65 | let table1 = toml.table("table1") 66 | 67 | // iterate over all tables at the root level 68 | for (tablePath, table) in toml.tables() { ... } 69 | 70 | // iterate over all tables under table1 71 | for (tablePath, table) in toml.tables("table1") { ... } 72 | ``` 73 | 74 | ## Installation 75 | 76 | Add the project to to your Package.swift file as a dependency: 77 | 78 | dependencies: [ 79 | .Package(url: "http://github.com/jdfergason/swift-toml", majorVersion: 1) 80 | ] 81 | 82 | ## Compatibility 83 | 84 | SwiftToml is compatible with Swift 4.0.3 and TOML 0.4.0. 85 | 86 | It has been tested with Swift 4.0.3 on Mac OS X and Ubuntu 16.04. 87 | 88 | ## Tests 89 | 90 | To run the unit tests checkout the repository and type: 91 | 92 | swift test 93 | 94 | ## License 95 | 96 | [Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) 97 | -------------------------------------------------------------------------------- /Sources/Toml/Date.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016-2018 JD Fergason 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | private func buildDateFormatter(format: String) -> DateFormatter { 20 | let formatter = DateFormatter() 21 | formatter.dateFormat = format 22 | formatter.timeZone = TimeZone(secondsFromGMT: 0) 23 | formatter.calendar = Calendar(identifier: .iso8601) 24 | formatter.locale = Locale(identifier: "en_US_POSIX") 25 | return formatter 26 | } 27 | 28 | private var rfc3339fractionalformatter = 29 | buildDateFormatter(format: "yyyy'-'MM'-'dd'T'HH':'mm':'ss.SSSSSSZZZZZ") 30 | 31 | private var rfc3339formatter: DateFormatter = 32 | buildDateFormatter(format: "yyyy'-'MM'-'dd'T'HH':'mm':'ssZZZZZ") 33 | 34 | private func localTimeOffset() -> String { 35 | let totalSeconds: Int = TimeZone.current.secondsFromGMT() 36 | let minutes: Int = (totalSeconds / 60) % 60 37 | let hours: Int = totalSeconds / 3600 38 | return String(format: "%02d%02d", hours, minutes) 39 | } 40 | 41 | extension Date { 42 | 43 | // rfc3339 w fractional seconds w/ time offset 44 | init?(rfc3339String: String, fractionalSeconds: Bool = true, localTime: Bool = false) { 45 | var dateStr = rfc3339String 46 | var dateFormatter: DateFormatter 47 | 48 | if localTime { 49 | dateStr += localTimeOffset() 50 | } 51 | 52 | dateFormatter = fractionalSeconds ? rfc3339fractionalformatter : rfc3339formatter 53 | 54 | if let d = dateFormatter.date(from: dateStr) { 55 | self.init(timeInterval: 0, since: d) 56 | } else { 57 | return nil 58 | } 59 | } 60 | 61 | func rfc3339String() -> String { 62 | return rfc3339fractionalformatter.string(from: self) 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /Sources/Toml/Evaluator.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016-2018 JD Fergason 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | /** 20 | Class to evaluate input text with a regular expression and return tokens 21 | */ 22 | class Evaluator { 23 | let regex: String 24 | let generator: TokenGenerator 25 | let push: [String]? 26 | let pop: Bool 27 | let multiline: Bool 28 | 29 | init (regex: String, generator: @escaping TokenGenerator, 30 | push: [String]? = nil, pop: Bool = false, multiline: Bool = false) { 31 | self.regex = regex 32 | self.generator = generator 33 | self.push = push 34 | self.pop = pop 35 | self.multiline = multiline 36 | } 37 | 38 | func evaluate (_ content: String) throws -> 39 | (token: Token?, index: String.Index)? { 40 | var token: Token? 41 | var index: String.Index 42 | 43 | var options: NSRegularExpression.Options = [] 44 | 45 | if multiline { 46 | options = [.dotMatchesLineSeparators] 47 | } 48 | 49 | if let m = content.match(self.regex, options: options) { 50 | token = try self.generator(m) 51 | index = content.index(content.startIndex, offsetBy: m.count) 52 | return (token, index) 53 | } 54 | 55 | return nil 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Sources/Toml/Grammar.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016-2018 JD Fergason 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | class Grammar { 20 | var grammar = [String: [Evaluator]]() 21 | 22 | init() { 23 | grammar["comment"] = commentEvaluators() 24 | grammar["string"] = stringEvaluators() 25 | grammar["literalString"] = literalStringEvaluators() 26 | grammar["multilineString"] = multiLineStringEvaluators() 27 | grammar["multilineLiteralString"] = multiLineStringLiteralEvaluators() 28 | grammar["tableName"] = tableNameEvaluators() 29 | grammar["tableArray"] = tableArrayEvaluators() 30 | grammar["value"] = valueEvaluators() 31 | grammar["array"] = arrayEvaluators() 32 | grammar["inlineTable"] = inlineTableEvaluators() 33 | grammar["root"] = rootEvaluators() 34 | } 35 | 36 | private func commentEvaluators() -> [Evaluator] { 37 | return [ 38 | Evaluator(regex: "[\r\n]", generator: { _ in nil }, pop: true), 39 | // to enable saving comments in the tokenizer use the following line 40 | // Evaluator(regex: ".*", generator: { (r: String) in .Comment(r.trim()) }, pop: true) 41 | Evaluator(regex: ".*", generator: { _ in nil }, pop: true) 42 | ] 43 | } 44 | 45 | private func stringEvaluators() -> [Evaluator] { 46 | return [ 47 | Evaluator(regex: "\"", generator: { _ in nil }, pop: true), 48 | Evaluator(regex: "([\\u0020-\\u0021\\u0023-\\u005B\\u005D-\\uFFFF]|\\\\\"|\\\\)+", 49 | generator: { (r: String) in .Identifier(try r.replaceEscapeSequences()) }) 50 | ] 51 | } 52 | 53 | private func literalStringEvaluators() -> [Evaluator] { 54 | return [ 55 | Evaluator(regex: "'", generator: { _ in nil }, pop: true), 56 | Evaluator(regex: "([\\u0020-\\u0026\\u0028-\\uFFFF])+", 57 | generator: { (r: String) in .Identifier(r) }) 58 | ] 59 | } 60 | 61 | private func multiLineStringEvaluators() -> [Evaluator] { 62 | let validUnicodeChars = "\\u0020-\\u0021\\u0023-\\uFFFF" 63 | return [ 64 | Evaluator(regex: "\"\"\"", generator: { _ in nil }, pop: true), 65 | // Note: Does not allow multi-line strings that end with double qoutes. 66 | // This is a common limitation of a variety of parsers I have tested 67 | Evaluator(regex: "([\n" + validUnicodeChars + "]\"?\"?)*[\n" + validUnicodeChars + "]+", 68 | generator: { 69 | (r: String) in 70 | .Identifier(try r.trim().stripLineContinuation().replaceEscapeSequences()) 71 | }, multiline: true) 72 | ] 73 | } 74 | 75 | private func multiLineStringLiteralEvaluators() -> [Evaluator] { 76 | let validUnicodeChars = "\n\\u0020-\\u0026\\u0028-\\uFFFF" 77 | return [ 78 | Evaluator(regex: "'''", generator: { _ in nil }, pop: true), 79 | Evaluator(regex: "([" + validUnicodeChars + "]'?'?)*[" + validUnicodeChars + "]+", 80 | generator: { (r: String) in .Identifier(r.trim()) }, multiline: true) 81 | ] 82 | } 83 | 84 | private func tableNameEvaluators() -> [Evaluator] { 85 | let tableErrorStr = "Invalid table name declaration" 86 | return [ 87 | Evaluator(regex: "\"", generator: { _ in nil }, push: ["string"]), 88 | Evaluator(regex: "'", generator: { _ in nil }, push: ["literalString"]), 89 | Evaluator(regex: "\\.", generator: { _ in .TableSep }), 90 | // opening [ are prohibited directly within a table declaration 91 | Evaluator(regex: "\\[", generator: { _ in throw TomlError.SyntaxError(tableErrorStr) }), 92 | // hashes are prohibited directly within a table declaration 93 | Evaluator(regex: "#", generator: { _ in throw TomlError.SyntaxError(tableErrorStr) }), 94 | Evaluator(regex: "[A-Za-z0-9_-]+", generator: { (r: String) in .Identifier(r) }), 95 | Evaluator(regex: "\\]\\]", generator: { _ in .TableArrayEnd }, pop: true), 96 | Evaluator(regex: "\\]", generator: { _ in .TableEnd }, pop: true), 97 | ] 98 | } 99 | 100 | private func tableArrayEvaluators() -> [Evaluator] { 101 | let tableErrorStr = "Invalid table name declaration" 102 | return [ 103 | Evaluator(regex: "\"", generator: { _ in nil }, push: ["string"]), 104 | Evaluator(regex: "'", generator: { _ in nil }, push: ["literalString"]), 105 | Evaluator(regex: "\\.", generator: { _ in .TableSep }), 106 | // opening [ are prohibited directly within a table declaration 107 | Evaluator(regex: "\\[", generator: { _ in throw TomlError.SyntaxError(tableErrorStr) }), 108 | // hashes are prohibited directly within a table declaration 109 | Evaluator(regex: "#", generator: { _ in throw TomlError.SyntaxError(tableErrorStr) }), 110 | Evaluator(regex: "[A-Za-z0-9_-]+", generator: { (r: String) in .Identifier(r) }), 111 | Evaluator(regex: "\\]\\]", generator: { _ in .TableArrayEnd }, pop: true), 112 | ] 113 | } 114 | 115 | private func dateValueEvaluators() -> [Evaluator] { 116 | let dateTimeStr = "\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}" 117 | return [ 118 | // Dates, RFC 3339 w/ fractional seconds and time offset 119 | Evaluator(regex: dateTimeStr + ".\\d+(Z|z|[-\\+]\\d{2}:\\d{2})", generator: { 120 | (r: String) in 121 | if let date = Date(rfc3339String: r) { 122 | return Token.DateTime(date) 123 | } else { 124 | throw TomlError.InvalidDateFormat("####-##-##T##:##:##.###+/-##:## (\(r))") 125 | } 126 | }, pop: true), 127 | // RFC 3339 w/o fractional seconds and time offset 128 | Evaluator(regex: dateTimeStr + "(Z|z|[-\\+]\\d{2}:\\d{2})", generator: { 129 | (r: String) in 130 | if let date = Date(rfc3339String: r, fractionalSeconds: false) { 131 | return Token.DateTime(date) 132 | } else { 133 | throw TomlError.InvalidDateFormat("####-##-##T##:##:##+/-##:## (\(r))") 134 | } 135 | }, pop: true), 136 | // Dates, RFC 3339 w/ fractional seconds and w/o time offset 137 | Evaluator(regex: dateTimeStr + ".\\d+", generator: { (r: String) in 138 | if let date = Date(rfc3339String: r, localTime: true) { 139 | return Token.DateTime(date) 140 | } else { 141 | throw TomlError.InvalidDateFormat("####-##-##T##:##:##.### (\(r))") 142 | } 143 | }, pop: true), 144 | // Dates, RFC 3339 w/o fractional seconds and w/o time offset 145 | Evaluator(regex: dateTimeStr, generator: { (r: String) in 146 | if let date = Date(rfc3339String: r, fractionalSeconds: false, localTime: true) { 147 | return Token.DateTime(date) 148 | } else { 149 | throw TomlError.InvalidDateFormat("####-##-##T##:##:## (\(r))") 150 | } 151 | }, pop: true), 152 | // Date only 153 | Evaluator(regex: "\\d{4}-\\d{2}-\\d{2}", generator: { (r: String) in 154 | if let date = Date(rfc3339String: r + "T00:00:00.0", localTime: true) { 155 | return Token.DateTime(date) 156 | } else { 157 | throw TomlError.InvalidDateFormat("####-##-## (\(r))") 158 | } 159 | }, pop: true) 160 | ] 161 | } 162 | 163 | private func stringValueEvaluators() -> [Evaluator] { 164 | return [ 165 | // Multi-line string values (must come before single-line test) 166 | // Special case, empty multi-line string 167 | Evaluator(regex: "\"\"\"\"\"\"", generator: {_ in .Identifier("") }, pop: true), 168 | Evaluator(regex: "\"\"\"", generator: { _ in nil }, 169 | push: ["multilineString"], pop: true), 170 | // Multi-line literal string values (must come before single-line test) 171 | Evaluator(regex: "'''", generator: { _ in nil }, 172 | push: ["multilineLiteralString"], pop: true), 173 | // Special case, empty multi-line string literal 174 | Evaluator(regex: "''''''", generator: { _ in .Identifier("") }, 175 | push: ["multilineLiteralString"], pop: true), 176 | // empty single line strings 177 | Evaluator(regex: "\"\"", generator: { _ in .Identifier("") }, pop: true), 178 | Evaluator(regex: "''", generator: { _ in .Identifier("") }, pop: true), 179 | // String values 180 | Evaluator(regex: "\"", generator: { _ in nil }, push: ["string"], pop: true), 181 | // Literal string values 182 | Evaluator(regex: "'", generator: { _ in nil }, push: ["literalString"], pop: true), 183 | ] 184 | } 185 | 186 | private func doubleValueEvaluators() -> [Evaluator] { 187 | let generator: TokenGenerator = { (val: String) in .DoubleNumber(Double(val)!) } 188 | return [ 189 | // Double values with exponent 190 | Evaluator(regex: "[-\\+]?[0-9]+(\\.[0-9]+)?[eE][-\\+]?[0-9]+", 191 | generator: generator, pop:true), 192 | // Double values no exponent 193 | Evaluator(regex: "[-\\+]?[0-9]+\\.[0-9]+", generator: generator, pop: true), 194 | ] 195 | } 196 | 197 | private func intValueEvaluators() -> [Evaluator] { 198 | return [ 199 | // Integer values 200 | Evaluator(regex: "[-\\+]?[0-9]+", 201 | generator: { (r: String) in .IntegerNumber(Int(r)!) }, pop: true), 202 | ] 203 | } 204 | 205 | private func booleanValueEvaluators() -> [Evaluator] { 206 | return [ 207 | // Boolean values 208 | Evaluator(regex: "true", generator: { (r: String) in .Boolean(true) }, pop: true), 209 | Evaluator(regex: "false", generator: { (r: String) in .Boolean(false) }, pop: true), 210 | ] 211 | } 212 | 213 | private func whitespaceValueEvaluators() -> [Evaluator] { 214 | return [ 215 | // Ignore white-space 216 | Evaluator(regex: "[ \t]", generator: { _ in nil }), 217 | ] 218 | } 219 | 220 | private func arrayValueEvaluators() -> [Evaluator] { 221 | return [ 222 | // Arrays 223 | Evaluator(regex: "\\[", generator: { 224 | _ in .ArrayBegin 225 | }, push: ["array", "array"], pop: true), 226 | ] 227 | } 228 | 229 | private func inlineTableValueEvaluators() -> [Evaluator] { 230 | return [ 231 | // Inline tables 232 | Evaluator(regex: "\\{", generator: { 233 | _ in .InlineTableBegin 234 | }, push: ["inlineTable"], pop: true), 235 | ] 236 | } 237 | 238 | private func valueEvaluators() -> [Evaluator] { 239 | let typeEvaluators = stringValueEvaluators() + dateValueEvaluators() + 240 | doubleValueEvaluators() + intValueEvaluators() + booleanValueEvaluators() 241 | 242 | return whitespaceValueEvaluators() + arrayValueEvaluators() + 243 | inlineTableValueEvaluators() + typeEvaluators 244 | } 245 | 246 | private func arrayEvaluators() -> [Evaluator] { 247 | return [ 248 | // Ignore white-space 249 | Evaluator(regex: "[ \n\t]", generator: { _ in nil }), 250 | 251 | // Comments 252 | Evaluator(regex: "#", generator: { _ in nil }, push: ["comment"]), 253 | 254 | // Arrays 255 | Evaluator(regex: "\\[", generator: { _ in .ArrayBegin }, push: ["array"]), 256 | Evaluator(regex: "\\]", generator: { _ in .ArrayEnd }, pop: true), 257 | Evaluator(regex: ",", generator: { _ in nil }, push: ["array"]), 258 | ] + valueEvaluators() 259 | } 260 | 261 | private func stringKeyEvaluator() -> [Evaluator] { 262 | let validUnicodeChars = "\\u0020-\\u0021\\u0023-\\u005B\\u005D-\\uFFFF" 263 | return [ 264 | // bare key 265 | Evaluator(regex: "[a-zA-Z0-9_-]+[ \t]*=", 266 | generator: { 267 | (r: String) in 268 | .Key(String(r[.. [Evaluator] { 286 | return [ 287 | // Ignore white-space and commas 288 | Evaluator(regex: "[ \t,]", generator: { _ in nil }), 289 | // inline-table 290 | Evaluator(regex: "\\{", generator: { _ in .InlineTableBegin }, push: ["inlineTable"]), 291 | Evaluator(regex: "\\}", generator: { _ in .InlineTableEnd }, pop: true), 292 | ] + stringKeyEvaluator() 293 | } 294 | 295 | private func rootEvaluators() -> [Evaluator] { 296 | return [ 297 | // Ignore white-space 298 | Evaluator(regex: "[ \t\r\n]", generator: { _ in nil }), 299 | // Comments 300 | Evaluator(regex: "#", generator: { _ in nil }, push: ["comment"]), 301 | ] + stringKeyEvaluator() + [ 302 | // Array of tables (must come before table) 303 | Evaluator(regex: "\\[\\[", generator: { _ in .TableArrayBegin }, push: ["tableArray"]), 304 | // Tables 305 | Evaluator(regex: "\\[", generator: { _ in .TableBegin }, push: ["tableName"]), 306 | ] 307 | } 308 | } 309 | -------------------------------------------------------------------------------- /Sources/Toml/Helpers.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016-2018 JD Fergason 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | class ArrayWrapper: SetValueProtocol { 20 | var array: [Any] 21 | 22 | init(array: [Any]) { 23 | self.array = array 24 | } 25 | 26 | public func set(value: Any, for: [String]) { 27 | array.append(value) 28 | } 29 | } 30 | 31 | /** 32 | Utility function to cast an array to a given type or throw an error 33 | 34 | - Parameter array: Input array to cast to type T 35 | - Parameter out: Array to store result in 36 | 37 | - Throws: `TomlError.MixedArrayType` if array cannot be cast to appropriate type 38 | */ 39 | func checkAndSetArray(check: [Any], key: [String], out: inout T) throws { 40 | // allow empty arrays 41 | if check.isEmpty { 42 | out.set(value: check, for: key) 43 | return 44 | } 45 | 46 | // convert array to proper type 47 | switch check[0] { 48 | case is Int: 49 | if let typedArray = check as? [Int] { 50 | out.set(value: typedArray, for: key) 51 | } else { 52 | throw TomlError.MixedArrayType("Int") 53 | } 54 | case is Double: 55 | if let typedArray = check as? [Double] { 56 | out.set(value: typedArray, for: key) 57 | } else { 58 | throw TomlError.MixedArrayType("Double") 59 | } 60 | case is String: 61 | if let typedArray = check as? [String] { 62 | out.set(value: typedArray, for: key) 63 | } else { 64 | throw TomlError.MixedArrayType("String") 65 | } 66 | case is Bool: 67 | if let typedArray = check as? [Bool] { 68 | out.set(value: typedArray, for: key) 69 | } else { 70 | throw TomlError.MixedArrayType("Bool") 71 | } 72 | case is Date: 73 | if let typedArray = check as? [Date] { 74 | out.set(value: typedArray, for: key) 75 | } else { 76 | throw TomlError.MixedArrayType("Date") 77 | } 78 | default: 79 | // array of arrays leave as any 80 | out.set(value: check, for: key) 81 | } 82 | } 83 | 84 | /** 85 | Utility for trimming string identifiers in key/value pairs 86 | */ 87 | func trimStringIdentifier(_ input: String, _ quote: String = "\"") -> String { 88 | let pattern = quote + "(.+)" + quote + "[ \t]*=" 89 | let regex = try! NSRegularExpression(pattern: pattern, options: []) 90 | let matches = regex.matches(in: input, options: [], 91 | range: NSMakeRange(0, input.utf16.count)) 92 | let nss = NSString(string: input) 93 | return nss.substring(with: matches[0].range(at: 1)) 94 | } 95 | 96 | func getKeyPathFromTable(tokens: [Token]) -> [String] { 97 | var subKeyPath = [String]() 98 | subKeyPathLoop: for token in tokens { 99 | switch token { 100 | case .Identifier(let val): 101 | subKeyPath.append(val) 102 | case .TableSep, .TableArrayBegin, .TableBegin: 103 | continue 104 | default: 105 | break subKeyPathLoop 106 | } 107 | } 108 | 109 | return subKeyPath 110 | } 111 | 112 | func consumeTableIdentifierTokens(tableTokens: inout [Token], tokens: inout [Token]) { 113 | while !tokens.isEmpty { 114 | let nestedToken = tokens[0] 115 | tableTokens.append(nestedToken) 116 | tokens.remove(at: 0) 117 | if case .TableEnd = nestedToken { 118 | break 119 | } else if case .TableArrayEnd = nestedToken { 120 | break 121 | } 122 | } 123 | } 124 | 125 | func getTableTokens(keyPath: [String], tokens: inout [Token]) -> [Token] { 126 | var tableTokens = [Token]() 127 | nestedTableLoop: while tokens.count > 0 { 128 | let tableToken = tokens[0] 129 | 130 | // need to include sub tables 131 | switch tableToken { 132 | case .TableBegin, .TableArrayBegin: 133 | // get the key path of the new table 134 | let subKeyPath = getKeyPathFromTable(tokens: tokens) 135 | 136 | // If the new table is nested within the current one 137 | // include it, otherwise we are finished. 138 | if subKeyPath.count == 1 { 139 | // top-level - break 140 | break nestedTableLoop 141 | } 142 | 143 | if subKeyPath[0] != keyPath[0] { 144 | // nested table but not part of this table group 145 | break nestedTableLoop 146 | } 147 | 148 | // this table should be included because it's a 149 | // nested table 150 | 151 | // .TableBegin || .TableArrayBegin 152 | tokens.remove(at: 0) 153 | tableTokens.append(tableToken) 154 | 155 | // skip first name 156 | tokens.remove(at: 0) // Identifier 157 | tokens.remove(at: 0) // .TableSep 158 | 159 | consumeTableIdentifierTokens(tableTokens: &tableTokens, tokens: &tokens) 160 | default: 161 | tokens.remove(at: 0) 162 | tableTokens.append(tableToken) 163 | } 164 | } 165 | 166 | return tableTokens 167 | } 168 | 169 | func extractTableTokens(tokens: inout [Token], inline: Bool = false) -> [Token] { 170 | var tableTokens = [Token]() 171 | while !tokens.isEmpty { 172 | let tableToken = tokens[0] 173 | 174 | if inline { 175 | tokens.remove(at: 0) 176 | } 177 | 178 | if case .InlineTableEnd = tableToken { 179 | if inline { 180 | break 181 | } 182 | } else if case .TableBegin = tableToken { 183 | break 184 | } else if case .TableArrayBegin = tableToken { 185 | break 186 | } 187 | 188 | if !inline { 189 | tokens.remove(at: 0) 190 | } 191 | 192 | tableTokens.append(tableToken) 193 | } 194 | 195 | return tableTokens 196 | } 197 | -------------------------------------------------------------------------------- /Sources/Toml/Lexer.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016-2018 JD Fergason 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | /** 20 | Convert an input string of TOML to a stream of tokens 21 | */ 22 | class Lexer { 23 | let input: String 24 | var grammar: [String: [Evaluator]] 25 | 26 | init(input: String, grammar: [String: [Evaluator]]) { 27 | self.input = input 28 | self.grammar = grammar 29 | } 30 | 31 | func tokenize() throws -> [Token] { 32 | var tokens = [Token]() 33 | var content = input 34 | var stack = [String]() 35 | 36 | stack.append("root") 37 | 38 | while content.count > 0 { 39 | var matched = false 40 | 41 | // check content against evaluators to produce tokens 42 | for evaluator in grammar[stack.last!]! { 43 | if let e = try evaluator.evaluate(content) { 44 | if let t = e.token { 45 | tokens.append(t) 46 | } 47 | 48 | // should we pop the stack? 49 | if evaluator.pop { 50 | stack.removeLast() 51 | } 52 | 53 | // should we push onto the stack? 54 | if let pushItmes = evaluator.push { 55 | stack = stack + pushItmes 56 | } 57 | 58 | content = String(content[e.index...]) 59 | matched = true 60 | break 61 | } 62 | } 63 | 64 | if !matched { 65 | throw TomlError.SyntaxError(content) 66 | } 67 | } 68 | return tokens 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /Sources/Toml/Parser.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016-2018 JD Fergason 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | // MARK: Parse 20 | 21 | class Parser { 22 | var keyPath: [String] = [] 23 | var currentKey = "." 24 | var declaredTables = Set() 25 | var toml: Toml = Toml() 26 | 27 | // MARK: Initializers 28 | 29 | convenience init(toml: Toml) { 30 | self.init() 31 | self.toml = toml 32 | } 33 | 34 | // MARK: Parsing 35 | 36 | public func parse(string: String) throws { 37 | // Convert input into tokens 38 | let lexer = Lexer(input: string, grammar: Grammar().grammar) 39 | let tokens = try lexer.tokenize() 40 | try parse(tokens: tokens) 41 | } 42 | 43 | /** 44 | Parse a TOML token stream construct a dictionary. 45 | 46 | - Parameter tokens: Token stream describing a TOML data structure 47 | */ 48 | private func parse(tokens: [Token]) throws { 49 | // A dispatch table for parsing TOML tables 50 | let TokenMap: [Token: (Token, inout [Token]) throws -> ()] = [ 51 | .Identifier("1"): setValue, 52 | .IntegerNumber(1): setValue, 53 | .DoubleNumber(1.0): setValue, 54 | .Boolean(true): setValue, 55 | .DateTime(Date()): setValue, 56 | .TableBegin: setTable, 57 | .ArrayBegin: setArray, 58 | .TableArrayBegin: setTableArray, 59 | .InlineTableBegin: setInlineTable 60 | ] 61 | 62 | // Convert tokens to values in the Toml 63 | var myTokens = tokens 64 | 65 | while !myTokens.isEmpty { 66 | let token = myTokens.remove(at: 0) 67 | if case .Key(let val) = token { 68 | currentKey = val 69 | } else { 70 | try TokenMap[token]!(token, &myTokens) 71 | } 72 | } 73 | } 74 | 75 | /** 76 | Given a TOML token stream construct an array. 77 | 78 | - Parameter tokens: Token stream describing array 79 | 80 | - Returns: Array populated with values from token stream 81 | */ 82 | private func parse(tokens: inout [Token]) throws -> [Any] { 83 | var array: [Any] = [Any]() 84 | 85 | while !tokens.isEmpty { 86 | let token = tokens.remove(at: 0) 87 | switch token { 88 | case .Identifier(let val): 89 | array.append(val) 90 | case .IntegerNumber(let val): 91 | array.append(val) 92 | case .DoubleNumber(let val): 93 | array.append(val) 94 | case .Boolean(let val): 95 | array.append(val) 96 | case .DateTime(let val): 97 | array.append(val) 98 | case .InlineTableBegin: 99 | array.append(try processInlineTable(tokens: &tokens)) 100 | case .ArrayBegin: 101 | var wrap = ArrayWrapper(array: array) 102 | try checkAndSetArray(check: parse(tokens: &tokens), key: [""], out: &wrap) 103 | array = wrap.array 104 | default: 105 | return array 106 | } 107 | } 108 | 109 | return array 110 | } 111 | 112 | private func processInlineTable(tokens: inout [Token]) throws -> Toml { 113 | let tableTokens = extractTableTokens(tokens: &tokens, inline: true) 114 | let tableParser = Parser() 115 | try tableParser.parse(tokens: tableTokens) 116 | return tableParser.toml 117 | } 118 | 119 | /** 120 | Given a value token set its value in the `table` 121 | 122 | - Parameter currToken: A value token that is currently being parsed 123 | - Parameter tokens: Array of remaining tokens in the stream 124 | */ 125 | private func setValue(currToken: Token, tokens: inout [Token]) throws { 126 | var key = keyPath 127 | key.append(currentKey) 128 | 129 | if toml.hasKey(key: key) { 130 | throw TomlError.DuplicateKey(String(describing: key)) 131 | } 132 | 133 | toml.set(value: currToken.value as Any, for: key) 134 | } 135 | 136 | /** 137 | Given a table extract all associated tokens from the stream and create 138 | a new dictionary. 139 | 140 | - Parameter currToken: A `Token.TableBegin` token 141 | - Parameter table: Parent table to save resulting table to 142 | */ 143 | private func setTable(currToken: Token, tokens: inout [Token]) throws { 144 | var tableExists = false 145 | var emptyTableSep = false 146 | // clear out the keyPath 147 | keyPath.removeAll() 148 | 149 | while !tokens.isEmpty { 150 | let subToken = tokens.remove(at: 0) 151 | if case .TableEnd = subToken { 152 | if keyPath.count < 1 { 153 | throw TomlError.SyntaxError("Table name must not be blank") 154 | } 155 | 156 | let keyPathStr = String(describing: keyPath) 157 | if toml.hasKey(key: keyPath, includeTables: false) || declaredTables.contains(keyPathStr) { 158 | throw TomlError.DuplicateKey(String(describing: keyPath)) 159 | } 160 | 161 | declaredTables.insert(keyPathStr) 162 | let tableTokens = extractTableTokens(tokens: &tokens) 163 | try parse(tokens: tableTokens) 164 | tableExists = true 165 | break 166 | } else if case .TableSep = subToken { 167 | if emptyTableSep { 168 | throw TomlError.SyntaxError("Must not have un-named implicit tables") 169 | } 170 | emptyTableSep = true 171 | } else if case .Identifier(let val) = subToken { 172 | emptyTableSep = false 173 | keyPath.append(val) 174 | toml.setTable(key: keyPath) 175 | } 176 | } 177 | 178 | if !tableExists { 179 | throw TomlError.SyntaxError("Table must contain at least a closing bracket") 180 | } 181 | } 182 | 183 | private func setTableArray(currToken: Token, tokens: inout [Token]) throws { 184 | // clear out the keyPath 185 | keyPath.removeAll() 186 | 187 | tableLoop: while !tokens.isEmpty { 188 | let subToken = tokens.remove(at: 0) 189 | if case .TableArrayEnd = subToken { 190 | if keyPath.count < 1 { 191 | throw TomlError.SyntaxError("Table array name must not be blank") 192 | } 193 | 194 | let tableTokens = getTableTokens(keyPath: keyPath, tokens: &tokens) 195 | 196 | if toml.hasKey(key: keyPath) { 197 | var arr: [Toml] = toml.array(keyPath)! 198 | let tableParser = Parser() 199 | try tableParser.parse(tokens: tableTokens) 200 | arr.append(tableParser.toml) 201 | toml.set(value: arr, for: keyPath) 202 | } else { 203 | let tableParser = Parser() 204 | try tableParser.parse(tokens: tableTokens) 205 | toml.set(value: [tableParser.toml], for: keyPath) 206 | } 207 | break tableLoop 208 | } else if case .Identifier(let val) = subToken { 209 | keyPath.append(val) 210 | } 211 | } 212 | } 213 | 214 | /** 215 | Given an inline table extract all associated tokens from the stream 216 | and create a new dictionary. 217 | 218 | - Parameter currToken: A `Token.InlineTableBegin` token 219 | - Parameter table: Parent table to save resulting inline table to 220 | */ 221 | private func setInlineTable(currToken: Token, tokens: inout [Token]) throws { 222 | keyPath.append(currentKey) 223 | 224 | let tableTokens = extractTableTokens(tokens: &tokens, inline: true) 225 | try parse(tokens: tableTokens) 226 | 227 | toml.setTable(key: keyPath) 228 | 229 | // This was an inline table so remove from keyPath 230 | keyPath.removeLast() 231 | } 232 | 233 | /** 234 | Given an array save it to the parent table 235 | 236 | - Parameter currToken: A `Token.ArrayBegin` token 237 | - Parameter table: Parent table to save resulting inline table to 238 | */ 239 | private func setArray(currToken: Token, tokens: inout [Token]) throws { 240 | let arr: [Any] = try parse(tokens: &tokens) 241 | 242 | var myKeyPath = keyPath 243 | myKeyPath.append(currentKey) 244 | 245 | // allow empty arrays 246 | if arr.isEmpty { 247 | toml.set(value: arr, for: myKeyPath) 248 | return 249 | } 250 | 251 | try checkAndSetArray(check: arr, key: myKeyPath, out: &toml) 252 | } 253 | } 254 | -------------------------------------------------------------------------------- /Sources/Toml/Path.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016-2018 JD Fergason 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /** 18 | Abstraction for TOML key paths 19 | */ 20 | public struct Path: Hashable, Equatable { 21 | internal(set) public var components: [String] 22 | 23 | init(_ components: [String]) { 24 | self.components = components 25 | } 26 | 27 | func begins(with: [String]) -> Bool { 28 | if with.count > components.count { 29 | return false 30 | } 31 | 32 | for x in with.indices { 33 | if components[x] != with[x] { 34 | return false 35 | } 36 | } 37 | 38 | return true 39 | } 40 | 41 | public func hash(into hasher: inout Hasher) { 42 | hasher.combine(componentsHashValue) 43 | } 44 | 45 | private var componentsHashValue: Int { 46 | return components.reduce(0, { $0 ^ $1.hashValue }) 47 | } 48 | 49 | public static func == (lhs: Path, rhs: Path) -> Bool { 50 | return lhs.components == rhs.components 51 | } 52 | 53 | static func + (lhs: Path, rhs: Path) -> Path { 54 | return Path(lhs.components + rhs.components) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Sources/Toml/Regex.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016-2018 JD Fergason 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | var expressions = [String: NSRegularExpression]() 20 | 21 | extension String { 22 | func match(_ regex: String, options: NSRegularExpression.Options = []) -> String? { 23 | let expression: NSRegularExpression 24 | 25 | if let cachedRegexp = expressions[regex] { 26 | expression = cachedRegexp 27 | } else { 28 | expression = try! NSRegularExpression(pattern: "^\(regex)", options: options) 29 | expressions[regex] = expression 30 | } 31 | 32 | let range = expression.rangeOfFirstMatch(in: self, options: [], 33 | range: NSMakeRange(0, self.count)) 34 | if range.location != NSNotFound { 35 | return NSString(string: self).substring(with: range) 36 | } 37 | return nil 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Sources/Toml/Serialize.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016-2018 JD Fergason 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | /** 20 | Get list of top level keys and optionally sort 21 | */ 22 | private func filterKeys(keys: Set) -> [String] { 23 | var myKeys = keys.filter({ $0.components.count == 1}).map({ $0.components.first! }) 24 | myKeys.sort() 25 | return myKeys 26 | } 27 | 28 | /** 29 | Serialize all keys 30 | */ 31 | private func serializeKeys(toml: Toml) -> [String] { 32 | var result: [String] = [] 33 | 34 | // serialize all key/value pairs at the base level 35 | // only top level keys should be serialized at this time 36 | // lower levels will be serialized by subsequent calls to this 37 | // function 38 | let myKeys = filterKeys(keys: toml.keyNames) 39 | 40 | for key in myKeys { 41 | result.append("\(quoted(key)) = \(toml.valueDescription([key])!)") 42 | } 43 | 44 | return result 45 | } 46 | 47 | /** 48 | Serialize all tables 49 | */ 50 | private func serializeTable(toml: Toml) -> [String] { 51 | var result: [String] = [] 52 | 53 | // serialize each table 54 | let tableNames = filterKeys(keys: toml.tableNames) 55 | 56 | for tableName in tableNames { 57 | let table = toml.table(tableName)! 58 | 59 | // get table title 60 | var titleParts = [tableName] 61 | if let prefixPath = toml.prefixPath { 62 | titleParts = prefixPath.components + [tableName] 63 | } 64 | let title = titleParts.map(quoted).joined(separator: ".") 65 | 66 | // get key value pairs for table 67 | let tableString = serializeKeys(toml: table).joined(separator: "\n") 68 | 69 | // if it's not an an empty table, add it 70 | if !tableString.isEmpty { 71 | result.append("[\(title)]\n" + tableString) 72 | } 73 | 74 | result += serializeTable(toml: table) 75 | } 76 | 77 | return result 78 | } 79 | 80 | /** 81 | Serialize inline table 82 | */ 83 | func serializeInlineTable(toml: Toml) -> String { 84 | let keys = serializeKeys(toml: toml) 85 | var tables: [String] = [] 86 | for (_, table) in toml.tables() { 87 | tables.append(serializeInlineTable(toml: table)) 88 | } 89 | let result = (keys + tables).joined(separator: ", ") 90 | return "{\(result)}" 91 | } 92 | 93 | /** 94 | Serialize array of tables 95 | */ 96 | func serializeArrayOfTables(tables: [Toml]) -> String { 97 | var result: [String] = [] 98 | for table in tables { 99 | result.append(serializeInlineTable(toml: table)) 100 | } 101 | return "[\(result.joined(separator: ",\n "))]" 102 | } 103 | 104 | /** 105 | Serialize a toml object to a string. 106 | 107 | Note: If the toml object was constructed from an existing TOML string 108 | then the string returned by this function will parse to an identical 109 | hashmap; however, it may not be an identical string. 110 | 111 | In particular, the resulting string will have the following properties: 112 | 113 | * inline tables will no longer be inline 114 | * arrays of tables will be inline 115 | * strings will always be single line unicode strings with the minimal 116 | number of characters escaped 117 | * no comments will appear in the document 118 | * whitespace will be standardized to a single space before and after the 119 | '=' symbol in key/value pairs and each new table will have a preceding 120 | blank line 121 | * key pairs and tables will be output in alpha-numeric order 122 | 123 | - Parameter toml: Toml object to serialize 124 | 125 | - Returns: The Toml object serialized to Toml 126 | */ 127 | func serialize(toml: Toml) -> String { 128 | var result = "" 129 | 130 | result += serializeKeys(toml: toml).joined(separator: "\n") 131 | result += "\n\n" + serializeTable(toml: toml).joined(separator: "\n\n") 132 | 133 | return result.trim() 134 | } 135 | -------------------------------------------------------------------------------- /Sources/Toml/String.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016-2018 JD Fergason 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | func getUnicodeChar(unicode: String) throws -> String { 20 | // check if it's a valid character 21 | let code = Int(strtoul(unicode, nil, 16)) 22 | 23 | if code < 0x0 || (code > 0xD7FF && code < 0xE000) || code > 0x10FFFF { 24 | throw TomlError.InvalidUnicodeCharacter(code) 25 | } 26 | 27 | return String(describing: UnicodeScalar(code)!) 28 | } 29 | 30 | func checkEscape(char: Character, escape: inout Bool) throws -> (String, Int) { 31 | var unicodeSize = -1 32 | var s: String = "" 33 | 34 | switch char { 35 | case "n": 36 | s = "\n" 37 | escape = false 38 | case "\\": 39 | s = "\\" 40 | escape = false 41 | case "\"": 42 | s = "\"" 43 | escape = false 44 | case "f": 45 | s = "\u{000C}" 46 | escape = false 47 | case "b": 48 | s = "\u{0008}" 49 | escape = false 50 | case "t": 51 | s = "\t" 52 | escape = false 53 | case "r": 54 | s = "\r" 55 | escape = false 56 | case "u": 57 | unicodeSize = 4 58 | case "U": 59 | unicodeSize = 8 60 | default: 61 | throw TomlError.InvalidEscapeSequence("\\" + String(describing: char)) 62 | } 63 | 64 | return (s, unicodeSize) 65 | } 66 | 67 | extension String { 68 | func trim() -> String { 69 | return self.trimmingCharacters(in: NSCharacterSet.whitespacesAndNewlines) 70 | } 71 | 72 | func stripLineContinuation() -> String { 73 | var s = self 74 | let regex = try! NSRegularExpression(pattern: "\\\\(\\s+)", 75 | options: [.dotMatchesLineSeparators]) 76 | let matches = regex.matches(in: s, options: [], 77 | range: NSMakeRange(0, s.utf16.count)) 78 | let nss = NSString(string: s) 79 | 80 | for match in matches { 81 | let m0 = nss.substring(with: match.range(at: 0)) 82 | s = s.replacingOccurrences(of: m0, with: "") 83 | } 84 | 85 | return s 86 | } 87 | 88 | func replaceEscapeSequences() throws -> String { 89 | var s = "" // new string that is being constructed 90 | var escape = false 91 | var unicode = "" 92 | var unicodeSize = -1 93 | 94 | for char in self { 95 | if escape { 96 | if unicodeSize == 0 { 97 | s += try getUnicodeChar(unicode: unicode) 98 | s += String(describing: char) 99 | 100 | escape = false 101 | unicodeSize = -1 102 | unicode = "" 103 | } else if unicodeSize > 0 { 104 | unicodeSize -= 1 105 | unicode += String(describing: char) 106 | } else { 107 | let (newChar, size) = try checkEscape(char: char, escape: &escape) 108 | s += newChar 109 | unicodeSize = size 110 | } 111 | } else if char == "\\" { 112 | escape = true 113 | } else { 114 | s += String(describing: char) 115 | } 116 | } 117 | 118 | if unicodeSize == 0 { 119 | s += try getUnicodeChar(unicode: unicode) 120 | } 121 | 122 | return s 123 | } 124 | } 125 | 126 | // Mark: String related array extensions 127 | 128 | func quoted(_ value: String) -> String { 129 | if let _ = value.match(".*[\\u0020-\\u002B\\u002E-\\u002F\\u003A-\\u0040" + 130 | "\\u005B-\\u005E\\u0060\\u007B-\\uFFFF]+.*") { 131 | return "\"\(value)\"" 132 | } 133 | 134 | return value 135 | } 136 | 137 | /** 138 | Escape the string according to the rules of a single line Toml string 139 | 140 | - Parameters string: The string to escape 141 | 142 | - Returns: Escaped version of the string 143 | */ 144 | func escape(string: String) -> String { 145 | var result: String 146 | let escapeMap = ["\n": "\\n", "\r": "\\r", "\t": "\\t", "\"": "\\\""] 147 | 148 | // must escape \ first because it is the escape character 149 | result = string.replacingOccurrences(of: "\\", with: "\\\\") 150 | for (key, val) in escapeMap { 151 | result = result.replacingOccurrences(of: key, with: val) 152 | } 153 | 154 | return result 155 | } 156 | -------------------------------------------------------------------------------- /Sources/Toml/Tokens.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016-2018 JD Fergason 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | enum Token: Hashable { 20 | case Identifier(String) 21 | case Key(String) 22 | case IntegerNumber(Int) 23 | case DoubleNumber(Double) 24 | case Boolean(Bool) 25 | case DateTime(Date) 26 | case ArrayBegin 27 | case ArrayEnd 28 | case TableArrayBegin 29 | case TableArrayEnd 30 | case InlineTableBegin 31 | case InlineTableEnd 32 | case TableBegin 33 | case TableSep 34 | case TableEnd 35 | case Comment(String) 36 | 37 | func hash(into hasher: inout Hasher) { 38 | hasher.combine(caseHashValue) 39 | } 40 | 41 | private var caseHashValue: Int { 42 | switch self { 43 | case .Identifier: 44 | return 0 45 | case .Key: 46 | return 1 47 | case .IntegerNumber: 48 | return 2 49 | case .DoubleNumber: 50 | return 3 51 | case .Boolean: 52 | return 4 53 | case .DateTime: 54 | return 5 55 | case .ArrayBegin: 56 | return 6 57 | case .ArrayEnd: 58 | return 7 59 | case .TableArrayBegin: 60 | return 8 61 | case .TableArrayEnd: 62 | return 9 63 | case .InlineTableBegin: 64 | return 10 65 | case .InlineTableEnd: 66 | return 11 67 | case .TableBegin: 68 | return 12 69 | case .TableSep: 70 | return 13 71 | case .TableEnd: 72 | return 14 73 | case .Comment: 74 | return 15 75 | } 76 | } 77 | 78 | var value : Any? { 79 | switch self { 80 | case .Identifier(let val): 81 | return val 82 | case .Key(let val): 83 | return val 84 | case .IntegerNumber(let val): 85 | return val 86 | case .DoubleNumber(let val): 87 | return val 88 | case .Boolean(let val): 89 | return val 90 | case .DateTime(let val): 91 | return val 92 | case .Comment(let val): 93 | return val 94 | default: 95 | return nil 96 | } 97 | } 98 | 99 | static func == (lhs: Token, rhs: Token) -> Bool { 100 | return lhs.hashValue == rhs.hashValue 101 | } 102 | 103 | } 104 | 105 | typealias TokenGenerator = (String) throws -> Token? 106 | -------------------------------------------------------------------------------- /Sources/Toml/Toml.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016-2018 JD Fergason 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | /** 20 | Error thrown when a TOML syntax error is encountered 21 | 22 | - DuplicateKey: Document contains a duplicate key 23 | - InvalidDateFormat: Date string is not a supported format 24 | - InvalidEscapeSequence: Unsupported escape sequence used in string 25 | - InvalidUnicodeCharacter: Non-existant unicode character specified 26 | - MixedArrayType: Array is composed of multiple types, members must all be the same type 27 | - SyntaxError: Document cannot be parsed due to a syntax error 28 | */ 29 | public enum TomlError: Error { 30 | case DuplicateKey(String) 31 | case InvalidDateFormat(String) 32 | case InvalidEscapeSequence(String) 33 | case InvalidUnicodeCharacter(Int) 34 | case MixedArrayType(String) 35 | case SyntaxError(String) 36 | } 37 | 38 | protocol SetValueProtocol { 39 | mutating func set(value: Any, for key: [String]) 40 | } 41 | 42 | /** 43 | Data parsed from a TOML document 44 | */ 45 | public class Toml: CustomStringConvertible, SetValueProtocol { 46 | private var data: [Path: Any] 47 | private(set) public var keyNames: Set 48 | private(set) public var prefixPath: Path? 49 | private(set) public var tableNames: Set 50 | 51 | public init() { 52 | data = [Path: Any]() 53 | keyNames = Set() 54 | tableNames = Set() 55 | } 56 | 57 | /** 58 | Read the specified TOML file from disk. 59 | 60 | - Parameter contentsOfFile: Path of file to read 61 | - Parameter encoding: Encoding of file 62 | 63 | - Throws: `TomlError.SyntaxError` if the file is invalid 64 | - Throws: `NSError` if the file does not exist 65 | 66 | - Returns: A dictionary with parsed results 67 | */ 68 | public convenience init(contentsOfFile path: String, 69 | encoding: String.Encoding = String.Encoding.utf8) throws { 70 | self.init() 71 | let source = try String(contentsOfFile: path, encoding: encoding) 72 | let parser = Parser(toml: self) 73 | try parser.parse(string: source) 74 | } 75 | 76 | /** 77 | Parse the string `withString` as TOML. 78 | 79 | - Parameter withString: A string with TOML document 80 | 81 | - Throws: `TomlError.SyntaxError` if the file is invalid 82 | 83 | - Returns: A dictionary with parsed results 84 | */ 85 | public convenience init(withString string: String) throws { 86 | self.init() 87 | let parser = Parser(toml: self) 88 | try parser.parse(string: string) 89 | } 90 | 91 | /** 92 | Create an empty Toml object with the specified prefix path 93 | 94 | - Parameter prefixPath: The path to prefix all tables with 95 | */ 96 | private convenience init(prefixPath: Path) { 97 | self.init() 98 | self.prefixPath = prefixPath 99 | } 100 | 101 | /** 102 | Set the value for the the given key path 103 | 104 | - Parameter key: Array of strings 105 | - Parameter value: Value to set 106 | */ 107 | public func set(value: Any, for key: [String]) { 108 | let path = Path(key) 109 | keyNames.insert(path) 110 | data[path] = value 111 | } 112 | 113 | /** 114 | Add a sub-table 115 | 116 | - Parameter key: Array of strings indicating table path 117 | */ 118 | public func setTable(key: [String]) { 119 | tableNames.insert(Path(key)) 120 | } 121 | 122 | /** 123 | Check if the TOML document contains the specified key. 124 | 125 | - Parameter key: Key path to check 126 | - Parameter includeTables: Include tables and inline tables in key path check 127 | 128 | - Returns: True if key exists; false otherwise 129 | */ 130 | public func hasKey(key: [String], includeTables: Bool = true) -> Bool { 131 | var keyExists = data[Path(key)] != nil 132 | if includeTables { 133 | keyExists = keyExists || hasTable(key) 134 | } 135 | return keyExists 136 | } 137 | 138 | /** 139 | Check if the TOML document contains the specified key. 140 | 141 | - Parameter key: Key path to check 142 | 143 | - Returns: True if key exists; false otherwise 144 | */ 145 | public func hasKey(_ key: String...) -> Bool { 146 | return hasKey(key: key) 147 | } 148 | 149 | /** 150 | Check if the TOML document contains the specified table. 151 | 152 | - Parameter key: Key path to check 153 | 154 | - Returns: True if table exists; false otherwise 155 | */ 156 | public func hasTable(_ key: [String]) -> Bool { 157 | return tableNames.contains(Path(key)) 158 | } 159 | 160 | /** 161 | Check if the TOML document contains the specified table. 162 | 163 | - Parameter key: Key path to check 164 | 165 | - Returns: True if key exists; false otherwise 166 | */ 167 | public func hasTable(_ key: String...) -> Bool { 168 | return hasTable(key) 169 | } 170 | 171 | /** 172 | Get an array of type T from the TOML document 173 | 174 | - Parameter path: Key path of array 175 | 176 | - Returns: An array of type [T] 177 | */ 178 | public func array(_ path: [String]) -> [T]? { 179 | if let val = data[Path(path)] { 180 | return val as? [T] 181 | } 182 | 183 | return nil 184 | } 185 | 186 | /** 187 | Get an array of type T from the TOML document 188 | 189 | - Parameter path: Key path of array 190 | 191 | - Returns: An array of type [T] 192 | */ 193 | public func array(_ path: String...) -> [T]? { 194 | return array(path) 195 | } 196 | 197 | /** 198 | Get a boolean value from the specified key path. 199 | 200 | - Parameter path: Key path of value 201 | 202 | - Returns: boolean value of key path 203 | */ 204 | public func bool(_ path: [String]) -> Bool? { 205 | return value(path) 206 | } 207 | 208 | /** 209 | Get a boolean value from the specified key path. 210 | 211 | - Parameter path: Key path of value 212 | 213 | - Returns: boolean value of key path 214 | */ 215 | public func bool(_ path: String...) -> Bool? { 216 | return value(path) 217 | } 218 | 219 | /** 220 | Get a date value from the specified key path. 221 | 222 | - Parameter path: Key path of value 223 | 224 | - Returns: date value of key path 225 | */ 226 | public func date(_ path: [String]) -> Date? { 227 | return value(path) 228 | } 229 | 230 | /** 231 | Get a date value from the specified key path. 232 | 233 | - Parameter path: Key path of value 234 | 235 | - Returns: date value of key path 236 | */ 237 | public func date(_ path: String...) -> Date? { 238 | return value(path) 239 | } 240 | 241 | /** 242 | Get a double value from the specified key path. 243 | 244 | - Parameter path: Key path of value 245 | 246 | - Returns: double value of key path 247 | */ 248 | public func double(_ path: [String]) -> Double? { 249 | return value(path) 250 | } 251 | 252 | /** 253 | Get a double value from the specified key path. 254 | 255 | - Parameter path: Key path of value 256 | 257 | - Returns: double value of key path 258 | */ 259 | public func double(_ path: String...) -> Double? { 260 | return double(path) 261 | } 262 | 263 | /** 264 | Get a int value from the specified key path. 265 | 266 | - Parameter path: Key path of value 267 | 268 | - Returns: int value of key path 269 | */ 270 | public func int(_ path: [String]) -> Int? { 271 | return value(path) 272 | } 273 | 274 | /** 275 | Get a int value from the specified key path. 276 | 277 | - Parameter path: Key path of value 278 | 279 | - Returns: int value of key path 280 | */ 281 | public func int(_ path: String...) -> Int? { 282 | return value(path) 283 | } 284 | 285 | /** 286 | Get a string value from the specified key path. 287 | 288 | - Parameter path: Key path of value 289 | 290 | - Returns: string value of key path 291 | */ 292 | public func string(_ path: [String]) -> String? { 293 | return value(path) 294 | } 295 | 296 | /** 297 | Get a string value from the specified key path. 298 | 299 | - Parameter path: Key path of value 300 | 301 | - Returns: string value of key path 302 | */ 303 | public func string(_ path: String...) -> String? { 304 | return value(path) 305 | } 306 | 307 | /** 308 | Get a dictionary of all tables 1-level down from the given key 309 | path. To get all tables at the root level call with no parameters. 310 | 311 | - Parameter parent: Root key path 312 | 313 | - Returns: Dictionary of key names and tables 314 | */ 315 | public func tables(_ parent: [String]) -> [String: Toml] { 316 | var result = [String: Toml]() 317 | for tableName in tableNames { 318 | var tableParent = tableName 319 | var myTableName = tableName 320 | if let tablePrefix = prefixPath { 321 | myTableName = tablePrefix + tableName 322 | } 323 | 324 | tableParent.components.removeLast() 325 | if parent == tableParent.components { 326 | // this is a table to include 327 | result[myTableName.components.map(quoted).joined(separator: ".")] = table(from: tableName.components) 328 | } 329 | } 330 | return result 331 | } 332 | 333 | /** 334 | Get a dictionary of all tables 1-level down from the given key 335 | path. To get all tables at the root level call with no parameters. 336 | 337 | - Parameter parent: Root key path 338 | 339 | - Returns: Dictionary of key names and tables 340 | */ 341 | public func tables(_ parent: String...) -> [String: Toml] { 342 | return tables(parent) 343 | } 344 | 345 | /** 346 | Return a TOML table that contains everything beneath the specified 347 | path. 348 | 349 | - Parameter from: Key path to create table from 350 | 351 | - Returns: `Toml` table of all keys beneath the specified path 352 | */ 353 | public func table(from path: [String]) -> Toml { 354 | var fullTablePrefix = Path(path) 355 | if let tablePrefix = prefixPath { 356 | fullTablePrefix = tablePrefix + Path(path) 357 | } 358 | 359 | let constructedTable = Toml(prefixPath: fullTablePrefix) 360 | 361 | // add values 362 | for keyName in keyNames { 363 | var keyArray = keyName.components 364 | if keyName.begins(with: path) { 365 | keyArray.removeSubrange(0.. Toml? { 392 | return table(from: path) 393 | } 394 | 395 | /** 396 | Get a value of type T from the specified key path. 397 | 398 | - Parameter path: Key path of value 399 | 400 | - Returns: value of key path 401 | */ 402 | public func value(_ path: [String]) -> T? { 403 | if let val = data[Path(path)] { 404 | return val as? T 405 | } 406 | 407 | return nil 408 | } 409 | 410 | /** 411 | Get a value of type T from the specified key path. 412 | 413 | - Parameter path: Key path of value 414 | 415 | - Returns: value of key path 416 | */ 417 | public func value(_ path: String...) throws -> T? { 418 | return value(path) 419 | } 420 | 421 | /** 422 | Get the value specified by path as a string 423 | 424 | - Parameter path: Key path of value 425 | 426 | - Returns: value of key path as a string 427 | */ 428 | public func valueDescription(_ path: [String]) -> String? { 429 | if let check = data[Path(path)] { 430 | if let intVal = check as? Int { 431 | return String(describing: intVal) 432 | } else if let doubleVal = check as? Double { 433 | return String(describing: doubleVal) 434 | } else if let stringVal = check as? String { 435 | return "\"\(escape(string: stringVal))\"" 436 | } else if let boolVal = check as? Bool { 437 | return String(describing: boolVal) 438 | } else if let dateVal = check as? Date { 439 | return dateVal.rfc3339String() 440 | } else if let tableArray = check as? [Toml] { 441 | return serializeArrayOfTables(tables: tableArray) 442 | } 443 | 444 | return String(describing: check) 445 | } 446 | 447 | return nil 448 | } 449 | 450 | /** 451 | Get a string representation of the TOML document 452 | 453 | - Returns: String version of TOML document 454 | */ 455 | public var description: String { 456 | return serialize(toml: self) 457 | } 458 | } 459 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import TomlTests 3 | 4 | XCTMain([ 5 | testCase(TomlTests.allTests), 6 | ]) 7 | -------------------------------------------------------------------------------- /Tests/TomlTests/TomlTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016-2018 JD Fergason 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import XCTest 18 | @testable import Toml 19 | 20 | private func changeWorkingDirectoryToPackageRoot() { 21 | let packageRootPath = URL(fileURLWithPath: #file) 22 | .deletingLastPathComponent().deletingLastPathComponent().deletingLastPathComponent().path 23 | 24 | if !FileManager.default.changeCurrentDirectoryPath(packageRootPath) { 25 | fatalError("Couldn't change working directory to \(packageRootPath)") 26 | } 27 | } 28 | 29 | class TomlTests: XCTestCase { 30 | override func setUp() { 31 | changeWorkingDirectoryToPackageRoot() 32 | } 33 | 34 | func testSimple() { 35 | let actual = try! Toml(contentsOfFile: "Tests/TomlTests/simple.toml") 36 | XCTAssertEqual(actual.string("string"), "value") 37 | XCTAssertEqual(actual.string("literal_string"), "lite\\ral") 38 | XCTAssertEqual(actual.int("int"), 1) 39 | XCTAssertEqual(actual.double("float"), 3.14) 40 | XCTAssertEqual(actual.bool("bool"), true) 41 | XCTAssertEqual(actual.date("date"), Date(rfc3339String: "1982-07-27T12:00:00.0Z")) 42 | XCTAssertEqual(actual.string("inline_table", "1"), "one") 43 | XCTAssertEqual(actual.string("inline_table", "3"), "three") 44 | 45 | // check hasKey and hasTable 46 | XCTAssertTrue(actual.hasTable("inline_table")) 47 | XCTAssertTrue(actual.hasKey("inline_table", "1")) 48 | XCTAssertFalse(actual.hasTable("non-existant-table")) 49 | XCTAssertFalse(actual.hasKey("inline_table", "4")) 50 | 51 | XCTAssertEqual(actual.array("array") ?? [], [1, 2, 3]) 52 | } 53 | 54 | func testSerialize() { 55 | let actual = try! Toml(contentsOfFile: "Tests/TomlTests/serialize.toml") 56 | let expected = try! String(contentsOfFile: "Tests/TomlTests/expected-serialize.toml", encoding: .utf8) 57 | XCTAssertEqual(String(describing: actual), expected.trim()) 58 | } 59 | 60 | func testImplicitlyDefinedTable() { 61 | let actual = try! Toml(contentsOfFile: "Tests/TomlTests/nested-tables.toml") 62 | XCTAssertTrue(actual.hasTable("table2")) 63 | } 64 | 65 | func testNestedTables() { 66 | let actual = try! Toml(contentsOfFile: "Tests/TomlTests/nested-tables.toml") 67 | // All tables 68 | var expectedKeys = ["table1", "table2"] 69 | var actualKeys: [String] = [] 70 | for (key, _) in actual.tables() { 71 | actualKeys.append(key) 72 | } 73 | expectedKeys.sort() 74 | actualKeys.sort() 75 | 76 | XCTAssertEqual(String(describing: expectedKeys), String(describing: actualKeys)) 77 | let expectedTables = try! String(contentsOfFile: "Tests/TomlTests/expected-nested-tables.toml", encoding: .utf8) 78 | 79 | XCTAssertEqual(expectedTables.trim(), String(describing: actual).trim()) 80 | } 81 | 82 | /* This test fails in TravisCI for some reason ... it passes on my local machine; disable until we figure out what's going on. 83 | func testDateFormat() { 84 | let actual = try! Toml(contentsOfFile: "Tests/TomlTests/date-format.toml") 85 | XCTAssertEqual(actual.date("date1"), Date(rfc3339String: "1979-05-27T07:32:00.0Z")!) 86 | XCTAssertEqual(actual.date("date2"), Date(rfc3339String: "1979-05-27T07:32:00.5Z")!) 87 | XCTAssertEqual(actual.date("date3"), Date(rfc3339String: "1979-05-27T00:32:00.6-07:00")!) 88 | XCTAssertEqual(actual.date("date4"), Date(rfc3339String: "1979-05-27T00:32:00.999999+07:00")!) 89 | XCTAssertEqual(actual.date("date5"), Date(rfc3339String: "1979-05-27T07:32:00.0", localTime: true)!) 90 | XCTAssertEqual(actual.date("date6"), Date(rfc3339String: "1979-05-27T07:32:00.5", localTime: true)!) 91 | XCTAssertEqual(actual.date("date7"), Date(rfc3339String: "1979-05-27T00:00:00.0", localTime: true)!) 92 | } 93 | */ 94 | 95 | // Tests from TOML repo 96 | 97 | func testTomlExample() { 98 | let actual = try! Toml(contentsOfFile: "Tests/TomlTests/toml-example.toml") 99 | 100 | // owner 101 | XCTAssertEqual(actual.string("owner", "name"), "Tom Preston-Werner") 102 | XCTAssertEqual(actual.string("owner", "organization"), "GitHub") 103 | XCTAssertEqual(actual.string("owner", "bio"), "GitHub Cofounder & CEO\nLikes tater tots and beer.") 104 | XCTAssertEqual(actual.date("owner", "dob"), Date(rfc3339String: "1979-05-27T07:32:00.0Z")) 105 | 106 | // database 107 | XCTAssertEqual(actual.string("database", "server"), "192.168.1.1") 108 | let ports: [Int] = actual.array("database", "ports")! 109 | XCTAssertEqual(ports, [8001, 8001, 8002]) 110 | XCTAssertEqual(actual.int("database", "connection_max"), 5000) 111 | XCTAssertEqual(actual.bool("database", "enabled"), true) 112 | 113 | // servers.alpha 114 | XCTAssertEqual(actual.string("servers", "alpha", "ip"), "10.0.0.1") 115 | XCTAssertEqual(actual.string("servers", "alpha", "dc"), "eqdc10") 116 | 117 | // servers.beta 118 | XCTAssertEqual(actual.string("servers", "beta", "ip"), "10.0.0.2") 119 | XCTAssertEqual(actual.string("servers", "beta", "dc"), "eqdc10") 120 | XCTAssertEqual(actual.string("servers", "beta", "country"), "中国") 121 | 122 | // clients 123 | let data: [Any] = actual.array("clients", "data")! 124 | XCTAssertEqual(data[0] as! [String], ["gamma", "delta"]) 125 | XCTAssertEqual(data[1] as! [Int], [1, 2]) 126 | let hosts: [String] = actual.array("clients", "hosts")! 127 | XCTAssertEqual(hosts, ["alpha", "omega"]) 128 | 129 | // products array 130 | let products: [Toml] = actual.array("products")! 131 | XCTAssertEqual(products[0].string("name"), "Hammer") 132 | XCTAssertEqual(products[0].int("sku"), 738594937) 133 | XCTAssertEqual(products[1].string("name"), "Nail") 134 | XCTAssertEqual(products[1].int("sku"), 284758393) 135 | XCTAssertEqual(products[1].string("color"), "gray") 136 | } 137 | 138 | func testHardExample() { 139 | let actual = try! Toml(contentsOfFile: "Tests/TomlTests/hard_example.toml") 140 | XCTAssertEqual(actual.string("the", "test_string"), "You'll hate me after this - #") 141 | let test_array: [String] = actual.array("the", "hard", "test_array")! 142 | XCTAssertEqual(test_array, ["] ", " # "]) 143 | let test_array2: [String] = actual.array("the", "hard", "test_array2")! 144 | XCTAssertEqual(test_array2, ["Test #11 ]proved that", "Experiment #9 was a success"]) 145 | XCTAssertEqual(actual.string("the", "hard", "another_test_string"), " Same thing, but with a string #") 146 | XCTAssertEqual(actual.string("the", "hard", "harder_test_string"), " And when \"'s are in the string, along with # \"") 147 | XCTAssertEqual(actual.string("the", "hard", "bit#", "what?"), "You don't think some user won't do that?") 148 | let multi_line_array: [String] = actual.array("the", "hard", "bit#", "multi_line_array")! 149 | XCTAssertEqual(multi_line_array, ["]"]) 150 | } 151 | 152 | func testHardExampleUnicode() { 153 | let actual = try! Toml(contentsOfFile: "Tests/TomlTests/hard_example_unicode.toml") 154 | XCTAssertEqual(actual.string("the", "test_string"), "Ýôú'ℓℓ λáƭè ₥è áƒƭèř ƭλïƨ - #") 155 | let test_array: [String] = actual.array("the", "hard", "test_array")! 156 | XCTAssertEqual(test_array, ["] ", " # "]) 157 | let test_array2: [String] = actual.array("the", "hard", "test_array2")! 158 | XCTAssertEqual(test_array2, ["Tèƨƭ #11 ]ƥřôƲèδ ƭλáƭ", "Éжƥèřï₥èñƭ #9 ωáƨ á ƨúççèƨƨ"]) 159 | XCTAssertEqual(actual.string("the", "hard", "another_test_string"), "§á₥è ƭλïñϱ, βúƭ ωïƭλ á ƨƭřïñϱ #") 160 | XCTAssertEqual(actual.string("the", "hard", "harder_test_string"), " Âñδ ωλèñ \"'ƨ ářè ïñ ƭλè ƨƭřïñϱ, áℓôñϱ ωïƭλ # \"") 161 | XCTAssertEqual(actual.string("the", "hard", "βïƭ#", "ωλáƭ?"), "Ýôú δôñ'ƭ ƭλïñƙ ƨô₥è úƨèř ωôñ'ƭ δô ƭλáƭ?") 162 | let multi_line_array: [String] = actual.array("the", "hard", "βïƭ#", "multi_line_array")! 163 | XCTAssertEqual(multi_line_array, ["]"]) 164 | } 165 | 166 | // MARK: toml-tests 167 | 168 | func testArrayEmpty() { 169 | // thevoid = [[[[[]]]]] 170 | let actual = try! Toml(contentsOfFile: "Tests/TomlTests/array-empty.toml") 171 | // there should be 5 sub-arrays 172 | let arr0: [Any] = actual.array("thevoid")! 173 | let arr1: [Any] = arr0[0] as! [Any] 174 | let arr2: [Any] = arr1[0] as! [Any] 175 | let arr3: [Any] = arr2[0] as! [Any] 176 | let arr4: [Any] = arr3[0] as! [Any] 177 | 178 | XCTAssertEqual(arr4.count, 0) 179 | } 180 | 181 | func testArrayNospaces() { 182 | // ints = [1,2,3] 183 | let actual = try! Toml(contentsOfFile: "Tests/TomlTests/array-nospaces.toml") 184 | let arr: [Int] = actual.array("ints")! 185 | XCTAssertEqual(arr, [1,2,3]) 186 | } 187 | 188 | func testArraysHetergeneous() { 189 | // mixed = [[1, 2], ["a", "b"], [1.1, 2.1]] 190 | let actual = try! Toml(contentsOfFile: "Tests/TomlTests/arrays-hetergeneous.toml") 191 | let arr: [Any] = actual.array("mixed")! 192 | let arr0: [Int] = arr[0] as! [Int] 193 | let arr1: [String] = arr[1] as! [String] 194 | let arr2: [Double] = arr[2] as! [Double] 195 | 196 | XCTAssertEqual(arr0, [1, 2]) 197 | XCTAssertEqual(arr1, ["a", "b"]) 198 | XCTAssertEqual(arr2, [1.1, 2.1]) 199 | } 200 | 201 | func testArraysNested() { 202 | // nest = [["a"], ["b"]] 203 | let actual = try! Toml(contentsOfFile: "Tests/TomlTests/arrays-nested.toml") 204 | let arr: [Any] = actual.array("nest")! 205 | XCTAssertEqual(arr[0] as! [String], ["a"]) 206 | XCTAssertEqual(arr[1] as! [String], ["b"]) 207 | } 208 | 209 | func testArrays() { 210 | let actual = try! Toml(contentsOfFile: "Tests/TomlTests/arrays.toml") 211 | 212 | let ints: [Int] = actual.array("ints")! 213 | XCTAssertEqual(ints, [1, 2, 3]) 214 | 215 | let floats: [Double] = actual.array("floats")! 216 | XCTAssertEqual(floats, [1.1, 2.1, 3.1]) 217 | 218 | let strings: [String] = actual.array("strings")! 219 | XCTAssertEqual(strings, ["a", "b", "c"]) 220 | 221 | let dates: [Date] = actual.array("dates")! 222 | XCTAssertEqual(dates, [Date(rfc3339String: "1987-07-05T17:45:00.0Z")!, Date(rfc3339String: "1979-05-27T07:32:00.0Z")!, Date(rfc3339String: "2006-06-01T11:00:00.0Z")!]) 223 | } 224 | 225 | func testBool() { 226 | let actual = try! Toml(contentsOfFile: "Tests/TomlTests/bool.toml") 227 | XCTAssertEqual(actual.bool("t"), true) 228 | XCTAssertEqual(actual.bool("f"), false) 229 | } 230 | 231 | func testCommentsEverywhere() { 232 | let actual = try! Toml(contentsOfFile: "Tests/TomlTests/comments-everywhere.toml") 233 | XCTAssertEqual(actual.int("group", "answer"), 42) 234 | XCTAssertEqual(actual.array("group", "more") ?? [], [42, 42]) 235 | } 236 | 237 | func testDatetime() { 238 | let actual = try! Toml(contentsOfFile: "Tests/TomlTests/datetime.toml") 239 | XCTAssertEqual(actual.date("bestdayever"), Date(rfc3339String: "1987-07-05T17:45:00.0Z")!) 240 | } 241 | 242 | func testEmpty() { 243 | let _ = try! Toml(contentsOfFile: "Tests/TomlTests/empty.toml") 244 | } 245 | 246 | func testExample() { 247 | let actual = try! Toml(contentsOfFile: "Tests/TomlTests/example.toml") 248 | XCTAssertEqual(actual.date("best-day-ever"), Date(rfc3339String: "1987-07-05T17:45:00.0Z")!) 249 | XCTAssertFalse(actual.bool("numtheory", "boring")!) 250 | XCTAssertEqual(actual.array("numtheory", "perfection") ?? [], [6, 28, 496]) 251 | } 252 | 253 | func testFloat() { 254 | let actual = try! Toml(contentsOfFile: "Tests/TomlTests/float.toml") 255 | XCTAssertEqual(actual.double("pi"), 3.14) 256 | XCTAssertEqual(actual.double("negpi"), -3.14) 257 | } 258 | 259 | func testImplicitAndExplicitAfter() { 260 | let actual = try! Toml(contentsOfFile: "Tests/TomlTests/implicit-and-explicit-after.toml") 261 | XCTAssertEqual(actual.int("a", "b", "c", "answer"), 42) 262 | XCTAssertEqual(actual.int("a", "better"), 43) 263 | } 264 | 265 | func testImplicitAndExplicitBefore() { 266 | let actual = try! Toml(contentsOfFile: "Tests/TomlTests/implicit-and-explicit-before.toml") 267 | XCTAssertEqual(actual.int("a", "b", "c", "answer"), 42) 268 | XCTAssertEqual(actual.int("a", "better"), 43) 269 | } 270 | 271 | func testImplicitGroups() { 272 | let actual = try! Toml(contentsOfFile: "Tests/TomlTests/implicit-groups.toml") 273 | XCTAssertEqual(actual.int("a", "b", "c", "answer"), 42) 274 | } 275 | 276 | func testInteger() { 277 | let actual = try! Toml(contentsOfFile: "Tests/TomlTests/integer.toml") 278 | XCTAssertEqual(actual.int("answer"), 42) 279 | XCTAssertEqual(actual.int("neganswer"), -42) 280 | } 281 | 282 | func testKeyEqualsNospace() { 283 | let actual = try! Toml(contentsOfFile: "Tests/TomlTests/key-equals-nospace.toml") 284 | XCTAssertEqual(actual.int("answer"), 42) 285 | } 286 | 287 | func testKeySpace() { 288 | let actual = try! Toml(contentsOfFile: "Tests/TomlTests/key-space.toml") 289 | XCTAssertEqual(actual.int("a b"), 1) 290 | } 291 | 292 | func testKeySpecialChars() { 293 | let actual = try! Toml(contentsOfFile: "Tests/TomlTests/key-special-chars.toml") 294 | XCTAssertEqual(actual.int("~!@$^&*()_+-`1234567890[]|/?><.,;:'"), 1) 295 | } 296 | 297 | func testLongFloat() { 298 | let actual = try! Toml(contentsOfFile: "Tests/TomlTests/long-float.toml") 299 | XCTAssertEqual(actual.double("longpi"), 3.141592653589793) 300 | XCTAssertEqual(actual.double("neglongpi"), -3.141592653589793) 301 | } 302 | 303 | func testLongInteger() { 304 | let actual = try! Toml(contentsOfFile: "Tests/TomlTests/long-integer.toml") 305 | XCTAssertEqual(actual.int("answer"), 9223372036854775807) 306 | XCTAssertEqual(actual.int("neganswer"), -9223372036854775808) 307 | } 308 | 309 | func testMultilineString() { 310 | let actual = try! Toml(contentsOfFile: "Tests/TomlTests/multiline-string.toml") 311 | XCTAssertEqual(actual.string("multiline_empty_one"), "") 312 | XCTAssertEqual(actual.string("multiline_empty_two"), "") 313 | XCTAssertEqual(actual.string("multiline_empty_three"), "") 314 | XCTAssertEqual(actual.string("multiline_empty_four"), "") 315 | let expected = "The quick brown fox jumps over the lazy dog." 316 | XCTAssertEqual(actual.string("equivalent_one"), expected) 317 | XCTAssertEqual(actual.string("equivalent_two"), expected) 318 | XCTAssertEqual(actual.string("equivalent_three"), expected) 319 | } 320 | 321 | func testRawMultilineString() { 322 | let actual = try! Toml(contentsOfFile: "Tests/TomlTests/raw-multiline-string.toml") 323 | XCTAssertEqual(actual.string("oneline"), "This string has a ' quote character.") 324 | XCTAssertEqual(actual.string("firstnl"), "This string has a ' quote character.") 325 | XCTAssertEqual(actual.string("multiline"), "This string\nhas ' a quote character\nand more than\none newline\nin it.") 326 | } 327 | 328 | func testRawString() { 329 | let actual = try! Toml(contentsOfFile: "Tests/TomlTests/raw-string.toml") 330 | XCTAssertEqual(actual.string("backspace"), "This string has a \\b backspace character.") 331 | XCTAssertEqual(actual.string("tab"), "This string has a \\t tab character.") 332 | XCTAssertEqual(actual.string("newline"), "This string has a \\n new line character.") 333 | XCTAssertEqual(actual.string("formfeed"), "This string has a \\f form feed character.") 334 | XCTAssertEqual(actual.string("carriage"), "This string has a \\r carriage return character.") 335 | XCTAssertEqual(actual.string("slash"), "This string has a \\/ slash character.") 336 | XCTAssertEqual(actual.string("backslash"), "This string has a \\\\ backslash character.") 337 | } 338 | 339 | func testStringEmpty() { 340 | let actual = try! Toml(contentsOfFile: "Tests/TomlTests/string-empty.toml") 341 | XCTAssertEqual(actual.string("answer"), "") 342 | } 343 | 344 | func testStringEscapes() { 345 | let actual = try! Toml(contentsOfFile: "Tests/TomlTests/string-escapes.toml") 346 | XCTAssertEqual(actual.string("backspace"), "This string has a \u{0008} backspace character.") 347 | XCTAssertEqual(actual.string("tab"), "This string has a \t tab character.") 348 | XCTAssertEqual(actual.string("newline"), "This string has a \n new line character.") 349 | XCTAssertEqual(actual.string("formfeed"), "This string has a \u{000C} form feed character.") 350 | XCTAssertEqual(actual.string("carriage"), "This string has a \r carriage return character.") 351 | XCTAssertEqual(actual.string("quote"), "This string has a \" quote character.") 352 | XCTAssertEqual(actual.string("backslash"), "This string has a \\ backslash character.") 353 | XCTAssertEqual(actual.string("notunicode1"), "This string does not have a unicode \\u escape.") 354 | XCTAssertEqual(actual.string("notunicode2"), "This string does not have a unicode \u{005C}u escape.") 355 | XCTAssertEqual(actual.string("notunicode3"), "This string does not have a unicode \\u0075 escape.") 356 | XCTAssertEqual(actual.string("notunicode4"), "This string does not have a unicode \\\u{0075} escape.") 357 | } 358 | 359 | func testStringSimple() { 360 | let actual = try! Toml(contentsOfFile: "Tests/TomlTests/string-simple.toml") 361 | XCTAssertEqual(actual.string("answer"), "You are not drinking enough whisky.") 362 | } 363 | 364 | func testStringWithPound() { 365 | let actual = try! Toml(contentsOfFile: "Tests/TomlTests/string-with-pound.toml") 366 | XCTAssertEqual(actual.string("pound"), "We see no # comments here.") 367 | XCTAssertEqual(actual.string("poundcomment"), "But there are # some comments here.") 368 | } 369 | 370 | func testTableArrayImplicit() { 371 | let actual = try! Toml(contentsOfFile: "Tests/TomlTests/table-array-implicit.toml") 372 | let array: [Toml] = actual.array("albums", "songs")! 373 | XCTAssertEqual(array[0].string("name"), "Glory Days") 374 | } 375 | 376 | func testTableArrayMany() { 377 | let actual = try! Toml(contentsOfFile: "Tests/TomlTests/table-array-many.toml") 378 | let array: [Toml] = actual.array("people")! 379 | XCTAssertEqual(array.count, 3) 380 | XCTAssertEqual(array[0].string("first_name"), "Bruce") 381 | XCTAssertEqual(array[0].string("last_name"), "Springsteen") 382 | 383 | XCTAssertEqual(array[1].string("first_name"), "Eric") 384 | XCTAssertEqual(array[1].string("last_name"), "Clapton") 385 | 386 | XCTAssertEqual(array[2].string("first_name"), "Bob") 387 | XCTAssertEqual(array[2].string("last_name"), "Seger") 388 | } 389 | 390 | func testTableArrayNest() { 391 | let actual = try! Toml(contentsOfFile: "Tests/TomlTests/table-array-nest.toml") 392 | XCTAssertEqual(actual.keyNames.count, 1) 393 | let array: [Toml] = actual.array("albums")! 394 | XCTAssertEqual(array.count, 2) 395 | 396 | // First one 397 | let first = array[0] 398 | XCTAssertEqual(first.string("name"), "Born to Run") 399 | let first_songs: [Toml] = first.array("songs")! 400 | XCTAssertEqual(first_songs.count, 2) 401 | XCTAssertEqual(first_songs[0].string("name"), "Jungleland") 402 | XCTAssertEqual(first_songs[1].string("name"), "Meeting Across the River") 403 | 404 | // Second one 405 | let second = array[1] 406 | XCTAssertEqual(second.string("name"), "Born in the USA") 407 | let second_songs: [Toml] = second.array("songs")! 408 | XCTAssertEqual(second_songs.count, 2) 409 | XCTAssertEqual(second_songs[0].string("name"), "Glory Days") 410 | XCTAssertEqual(second_songs[1].string("name"), "Dancing in the Dark") 411 | } 412 | 413 | func testTableArrayOne() { 414 | let actual = try! Toml(contentsOfFile: "Tests/TomlTests/table-array-one.toml") 415 | XCTAssertEqual(actual.keyNames.count, 1) 416 | let array: [Toml] = actual.array("people")! 417 | XCTAssertEqual(array[0].string("first_name"), "Bruce") 418 | XCTAssertEqual(array[0].string("last_name"), "Springsteen") 419 | } 420 | 421 | func testTableEmpty() { 422 | let _ = try! Toml(contentsOfFile: "Tests/TomlTests/table-empty.toml") 423 | } 424 | 425 | func testTableSubEmpty() { 426 | let _ = try! Toml(contentsOfFile: "Tests/TomlTests/table-sub-empty.toml") 427 | } 428 | 429 | func testTableWhitespace() { 430 | let _ = try! Toml(contentsOfFile: "Tests/TomlTests/table-whitespace.toml") 431 | } 432 | 433 | func testTableWithPound() { 434 | let actual = try! Toml(contentsOfFile: "Tests/TomlTests/table-with-pound.toml") 435 | XCTAssertEqual(actual.int("key#group", "answer"), 42) 436 | } 437 | 438 | func testUnicodeEscape() { 439 | let actual = try! Toml(contentsOfFile: "Tests/TomlTests/unicode-escape.toml") 440 | XCTAssertEqual(actual.string("answer4"), "\u{03B4}") 441 | XCTAssertEqual(actual.string("answer8"), "\u{000003B4}") 442 | } 443 | 444 | func testUnicodeLiteral() { 445 | let actual = try! Toml(contentsOfFile: "Tests/TomlTests/unicode-literal.toml") 446 | XCTAssertEqual(actual.string("answer"), "\u{03B4}") 447 | } 448 | 449 | // MARK: invalid tests 450 | 451 | func testParseErrorExample1() { 452 | XCTAssertThrowsError(try Toml(contentsOfFile: "Tests/TomlTests/hard_example-error1.toml")) 453 | } 454 | 455 | func testParseErrorExample2() { 456 | XCTAssertThrowsError(try Toml(contentsOfFile: "Tests/TomlTests/hard_example-error2.toml")) 457 | } 458 | 459 | func testParseErrorExample3() { 460 | XCTAssertThrowsError(try Toml(contentsOfFile: "Tests/TomlTests/hard_example-error3.toml")) 461 | } 462 | 463 | func testParseErrorExample4() { 464 | XCTAssertThrowsError(try Toml(contentsOfFile: "Tests/TomlTests/hard_example-error4.toml")) 465 | } 466 | 467 | func testInvalidArrayMixedTypesArraysAndInts() { 468 | XCTAssertThrowsError(try Toml(contentsOfFile: "Tests/TomlTests/array-mixed-types-arrays-and-ints.toml")) 469 | } 470 | 471 | func testInvalidArrayMixedTypesIntsAndFloats() { 472 | XCTAssertThrowsError(try Toml(contentsOfFile: "Tests/TomlTests/array-mixed-types-ints-and-floats.toml")) 473 | } 474 | 475 | func testInvalidArrayMixedTypesStringsAndInts() { 476 | XCTAssertThrowsError(try Toml(contentsOfFile: "Tests/TomlTests/array-mixed-types-strings-and-ints.toml")) 477 | } 478 | 479 | func testInvalidDatetimeMalformedNoLeads() { 480 | XCTAssertThrowsError(try Toml(contentsOfFile: "Tests/TomlTests/datetime-malformed-no-leads.toml")) 481 | } 482 | 483 | func testInvalidDatetimeMalformedNoSecs() { 484 | XCTAssertThrowsError(try Toml(contentsOfFile: "Tests/TomlTests/datetime-malformed-no-secs.toml")) 485 | } 486 | 487 | func testInvalidDatetimeMalformedNoT() { 488 | XCTAssertThrowsError(try Toml(contentsOfFile: "Tests/TomlTests/datetime-malformed-no-t.toml")) 489 | } 490 | 491 | func testInvalidDatetimeMalformedWithMilli() { 492 | XCTAssertThrowsError(try Toml(contentsOfFile: "Tests/TomlTests/datetime-malformed-with-milli.toml")) 493 | } 494 | 495 | func testInvalidDuplicateKeyTable() { 496 | XCTAssertThrowsError(try Toml(contentsOfFile: "Tests/TomlTests/duplicate-key-table.toml")) 497 | } 498 | 499 | func testInvalidDuplicateKeys() { 500 | XCTAssertThrowsError(try Toml(contentsOfFile: "Tests/TomlTests/duplicate-keys.toml")) 501 | } 502 | 503 | func testInvalidDuplicateTables() { 504 | XCTAssertThrowsError(try Toml(contentsOfFile: "Tests/TomlTests/duplicate-tables.toml")) 505 | } 506 | 507 | func testInvalidEmptyImplicitTable() { 508 | XCTAssertThrowsError(try Toml(contentsOfFile: "Tests/TomlTests/empty-implicit-table.toml")) 509 | } 510 | 511 | func testInvalidEmptyTable() { 512 | XCTAssertThrowsError(try Toml(contentsOfFile: "Tests/TomlTests/empty-table.toml")) 513 | } 514 | 515 | func testInvalidFloatNoLeadingZero() { 516 | XCTAssertThrowsError(try Toml(contentsOfFile: "Tests/TomlTests/float-no-leading-zero.toml")) 517 | } 518 | 519 | func testInvalidFloatNoTrailingDigits() { 520 | XCTAssertThrowsError(try Toml(contentsOfFile: "Tests/TomlTests/float-no-trailing-digits.toml")) 521 | } 522 | 523 | func testInvalidKeyEmpty() { 524 | XCTAssertThrowsError(try Toml(contentsOfFile: "Tests/TomlTests/key-empty.toml")) 525 | } 526 | 527 | func testInvalidKeyHash() { 528 | XCTAssertThrowsError(try Toml(contentsOfFile: "Tests/TomlTests/key-hash.toml")) 529 | } 530 | 531 | func testInvalidKeyNewline() { 532 | XCTAssertThrowsError(try Toml(contentsOfFile: "Tests/TomlTests/key-newline.toml")) 533 | } 534 | 535 | func testInvalidKeyOpenBracket() { 536 | XCTAssertThrowsError(try Toml(contentsOfFile: "Tests/TomlTests/key-open-bracket.toml")) 537 | } 538 | 539 | func testInvalidKeySingleOpenBracket() { 540 | XCTAssertThrowsError(try Toml(contentsOfFile: "Tests/TomlTests/key-single-open-bracket.toml")) 541 | } 542 | 543 | func testInvalidKeySpace() { 544 | XCTAssertThrowsError(try Toml(contentsOfFile: "Tests/TomlTests/key-space-error.toml")) 545 | } 546 | 547 | func testInvalidKeyStartBracket() { 548 | XCTAssertThrowsError(try Toml(contentsOfFile: "Tests/TomlTests/key-start-bracket.toml")) 549 | } 550 | 551 | func testInvalidKeyTwoEquals() { 552 | XCTAssertThrowsError(try Toml(contentsOfFile: "Tests/TomlTests/key-two-equals.toml")) 553 | } 554 | 555 | func testInvalidStringBadByteEscape() { 556 | XCTAssertThrowsError(try Toml(contentsOfFile: "Tests/TomlTests/string-bad-byte-escape.toml")) 557 | } 558 | 559 | func testInvalidStringBadEscape() { 560 | XCTAssertThrowsError(try Toml(contentsOfFile: "Tests/TomlTests/string-bad-escape.toml")) 561 | } 562 | 563 | func testInvalidStringByteEscapes() { 564 | XCTAssertThrowsError(try Toml(contentsOfFile: "Tests/TomlTests/string-byte-escapes.toml")) 565 | } 566 | 567 | func testInvalidStringNoClose() { 568 | XCTAssertThrowsError(try Toml(contentsOfFile: "Tests/TomlTests/string-no-close.toml")) 569 | } 570 | 571 | func testInvalidTableArrayMalformedBracket() { 572 | XCTAssertThrowsError(try Toml(contentsOfFile: "Tests/TomlTests/table-array-malformed-bracket.toml")) 573 | } 574 | 575 | func testInvalidTableArrayMalformedEmpty() { 576 | XCTAssertThrowsError(try Toml(contentsOfFile: "Tests/TomlTests/table-array-malformed-empty.toml")) 577 | } 578 | 579 | func testInvalidTableEmpty() { 580 | XCTAssertThrowsError(try Toml(contentsOfFile: "Tests/TomlTests/table-empty-invalid.toml")) 581 | } 582 | 583 | func testInvalidTableNestedBracketsClose() { 584 | XCTAssertThrowsError(try Toml(contentsOfFile: "Tests/TomlTests/table-nested-brackets-close.toml")) 585 | } 586 | 587 | func testInvalidTableNestedBracketsOpen() { 588 | XCTAssertThrowsError(try Toml(contentsOfFile: "Tests/TomlTests/table-nested-brackets-open.toml")) 589 | } 590 | 591 | func testInvalidTableWhitespace() { 592 | XCTAssertThrowsError(try Toml(contentsOfFile: "Tests/TomlTests/table-whitespace-invalid.toml")) 593 | } 594 | 595 | func testInvalidTableWithPound() { 596 | XCTAssertThrowsError(try Toml(contentsOfFile: "Tests/TomlTests/table-with-pound-invalid.toml")) 597 | } 598 | 599 | func testInvalidTextAfterArrayEntries() { 600 | XCTAssertThrowsError(try Toml(contentsOfFile: "Tests/TomlTests/text-after-array-entries.toml")) 601 | } 602 | 603 | func testInvalidTextAfterInteger() { 604 | XCTAssertThrowsError(try Toml(contentsOfFile: "Tests/TomlTests/text-after-integer.toml")) 605 | } 606 | 607 | func testInvalidTextAfterString() { 608 | XCTAssertThrowsError(try Toml(contentsOfFile: "Tests/TomlTests/text-after-string.toml")) 609 | } 610 | 611 | func testInvalidTextAfterTable() { 612 | XCTAssertThrowsError(try Toml(contentsOfFile: "Tests/TomlTests/text-after-table.toml")) 613 | } 614 | 615 | func testInvalidTextBeforeArraySeparator() { 616 | XCTAssertThrowsError(try Toml(contentsOfFile: "Tests/TomlTests/text-before-array-separator.toml")) 617 | } 618 | 619 | func testInvalidTextInArray() { 620 | XCTAssertThrowsError(try Toml(contentsOfFile: "Tests/TomlTests/text-in-array.toml")) 621 | } 622 | 623 | // MARK: allTests 624 | 625 | static var allTests : [(String, (TomlTests) -> () throws -> Void)] { 626 | return [ 627 | ("testSimple", testSimple), 628 | // ("testDateFormat", testDateFormat), // see comment on function 629 | ("testNestedTables", testNestedTables), 630 | ("testImplicitlyDefinedTable", testImplicitlyDefinedTable), 631 | // failed tests 632 | ("testParseErrorExample1", testParseErrorExample1), 633 | ("testParseErrorExample2", testParseErrorExample2), 634 | ("testParseErrorExample3", testParseErrorExample3), 635 | ("testParseErrorExample4", testParseErrorExample4), 636 | // tests from the main toml repository @ https://github.com/toml-lang/toml 637 | ("testTomlExample", testTomlExample), 638 | ("testHardExample", testHardExample), 639 | ("testHardExampleUnicode", testHardExampleUnicode), 640 | ("testInvalidArrayMixedTypesArraysAndInts", testInvalidArrayMixedTypesArraysAndInts), 641 | ("testInvalidArrayMixedTypesIntsAndFloats", testInvalidArrayMixedTypesIntsAndFloats), 642 | ("testInvalidArrayMixedTypesStringsAndInts", testInvalidArrayMixedTypesStringsAndInts), 643 | ("testInvalidDatetimeMalformedNoLeads", testInvalidDatetimeMalformedNoLeads), 644 | ("testInvalidDatetimeMalformedNoSecs", testInvalidDatetimeMalformedNoSecs), 645 | ("testInvalidDatetimeMalformedNoT", testInvalidDatetimeMalformedNoT), 646 | ("testInvalidDatetimeMalformedWithMilli", testInvalidDatetimeMalformedWithMilli), 647 | ("testInvalidDuplicateKeyTable", testInvalidDuplicateKeyTable), 648 | ("testInvalidDuplicateKeys", testInvalidDuplicateKeys), 649 | ("testInvalidDuplicateTables", testInvalidDuplicateTables), 650 | ("testInvalidEmptyImplicitTable", testInvalidEmptyImplicitTable), 651 | ("testInvalidEmptyTable", testInvalidEmptyTable), 652 | ("testInvalidFloatNoLeadingZero", testInvalidFloatNoLeadingZero), 653 | ("testInvalidFloatNoTrailingDigits", testInvalidFloatNoTrailingDigits), 654 | ("testInvalidKeyEmpty", testInvalidKeyEmpty), 655 | ("testInvalidKeyHash", testInvalidKeyHash), 656 | ("testInvalidKeyNewline", testInvalidKeyNewline), 657 | ("testInvalidKeyOpenBracket", testInvalidKeyOpenBracket), 658 | ("testInvalidKeySingleOpenBracket", testInvalidKeySingleOpenBracket), 659 | ("testInvalidKeySpace", testInvalidKeySpace), 660 | ("testInvalidKeyStartBracket", testInvalidKeyStartBracket), 661 | ("testInvalidKeyTwoEquals", testInvalidKeyTwoEquals), 662 | ("testInvalidStringBadByteEscape", testInvalidStringBadByteEscape), 663 | ("testInvalidStringBadEscape", testInvalidStringBadEscape), 664 | ("testInvalidStringByteEscapes", testInvalidStringByteEscapes), 665 | ("testInvalidStringNoClose", testInvalidStringNoClose), 666 | ("testInvalidTableArrayMalformedBracket", testInvalidTableArrayMalformedBracket), 667 | ("testInvalidTableArrayMalformedEmpty", testInvalidTableArrayMalformedEmpty), 668 | ("testInvalidTableEmpty", testInvalidTableEmpty), 669 | ("testInvalidTableNestedBracketsClose", testInvalidTableNestedBracketsClose), 670 | ("testInvalidTableNestedBracketsOpen", testInvalidTableNestedBracketsOpen), 671 | ("testInvalidTableWhitespace", testInvalidTableWhitespace), 672 | ("testInvalidTableWithPound", testInvalidTableWithPound), 673 | ("testInvalidTextAfterArrayEntries", testInvalidTextAfterArrayEntries), 674 | ("testInvalidTextAfterInteger", testInvalidTextAfterInteger), 675 | ("testInvalidTextAfterString", testInvalidTextAfterString), 676 | ("testInvalidTextAfterTable", testInvalidTextAfterTable), 677 | ("testInvalidTextBeforeArraySeparator", testInvalidTextBeforeArraySeparator), 678 | ("testInvalidTextInArray", testInvalidTextInArray), 679 | // tests from https://github.com/BurntSushi/toml-test 680 | ("testArrayEmpty", testArrayEmpty), 681 | ("testArrayNospaces", testArrayNospaces), 682 | ("testArraysHetergeneous", testArraysHetergeneous), 683 | ("testArraysNested", testArraysNested), 684 | ("testArrays", testArrays), 685 | ("testBool", testBool), 686 | ("testCommentsEverywhere", testCommentsEverywhere), 687 | ("testDatetime", testDatetime), 688 | ("testEmpty", testEmpty), 689 | ("testExample", testExample), 690 | ("testFloat", testFloat), 691 | ("testImplicitAndExplicitAfter", testImplicitAndExplicitAfter), 692 | ("testImplicitAndExplicitBefore", testImplicitAndExplicitBefore), 693 | ("testImplicitGroups", testImplicitGroups), 694 | ("testInteger", testInteger), 695 | ("testKeyEqualsNospace", testKeyEqualsNospace), 696 | ("testKeySpace", testKeySpace), 697 | ("testKeySpecialChars", testKeySpecialChars), 698 | ("testLongFloat", testLongFloat), 699 | ("testLongInteger", testLongInteger), 700 | ("testMultilineString", testMultilineString), 701 | ("testRawMultilineString", testRawMultilineString), 702 | ("testRawString", testRawString), 703 | ("testStringEmpty", testStringEmpty), 704 | ("testStringEscapes", testStringEscapes), 705 | ("testStringSimple", testStringSimple), 706 | ("testStringWithPound", testStringWithPound), 707 | ("testTableArrayImplicit", testTableArrayImplicit), 708 | ("testTableArrayMany", testTableArrayMany), 709 | ("testTableArrayNest", testTableArrayNest), 710 | ("testTableArrayOne", testTableArrayOne), 711 | ("testTableEmpty", testTableEmpty), 712 | ("testTableSubEmpty", testTableSubEmpty), 713 | ("testTableWhitespace", testTableWhitespace), 714 | ("testTableWithPound", testTableWithPound), 715 | ("testUnicodeEscape", testUnicodeEscape), 716 | ("testUnicodeLiteral", testUnicodeLiteral), 717 | ] 718 | } 719 | } 720 | -------------------------------------------------------------------------------- /Tests/TomlTests/array-empty.toml: -------------------------------------------------------------------------------- 1 | thevoid = [[[[[]]]]] 2 | -------------------------------------------------------------------------------- /Tests/TomlTests/array-mixed-types-arrays-and-ints.toml: -------------------------------------------------------------------------------- 1 | arrays-and-ints = [1, ["Arrays are not integers."]] 2 | -------------------------------------------------------------------------------- /Tests/TomlTests/array-mixed-types-ints-and-floats.toml: -------------------------------------------------------------------------------- 1 | ints-and-floats = [1, 1.1] 2 | -------------------------------------------------------------------------------- /Tests/TomlTests/array-mixed-types-strings-and-ints.toml: -------------------------------------------------------------------------------- 1 | strings-and-ints = ["hi", 42] 2 | -------------------------------------------------------------------------------- /Tests/TomlTests/array-nospaces.toml: -------------------------------------------------------------------------------- 1 | ints = [1,2,3] 2 | -------------------------------------------------------------------------------- /Tests/TomlTests/arrays-hetergeneous.toml: -------------------------------------------------------------------------------- 1 | mixed = [[1, 2], ["a", "b"], [1.1, 2.1]] 2 | -------------------------------------------------------------------------------- /Tests/TomlTests/arrays-nested.toml: -------------------------------------------------------------------------------- 1 | nest = [["a"], ["b"]] 2 | -------------------------------------------------------------------------------- /Tests/TomlTests/arrays.toml: -------------------------------------------------------------------------------- 1 | ints = [1, 2, 3] 2 | floats = [1.1, 2.1, 3.1] 3 | strings = ["a", "b", "c"] 4 | dates = [ 5 | 1987-07-05T17:45:00Z, 6 | 1979-05-27T07:32:00Z, 7 | 2006-06-01T11:00:00Z, 8 | ] 9 | -------------------------------------------------------------------------------- /Tests/TomlTests/bool.toml: -------------------------------------------------------------------------------- 1 | t = true 2 | f = false 3 | -------------------------------------------------------------------------------- /Tests/TomlTests/comments-everywhere.toml: -------------------------------------------------------------------------------- 1 | # Top comment. 2 | # Top comment. 3 | # Top comment. 4 | 5 | # [no-extraneous-groups-please] 6 | 7 | [group] # Comment 8 | answer = 42 # Comment 9 | # no-extraneous-keys-please = 999 10 | # Inbetween comment. 11 | more = [ # Comment 12 | # What about multiple # comments? 13 | # Can you handle it? 14 | # 15 | # Evil. 16 | # Evil. 17 | 42, 42, # Comments within arrays are fun. 18 | # What about multiple # comments? 19 | # Can you handle it? 20 | # 21 | # Evil. 22 | # Evil. 23 | # ] Did I fool you? 24 | ] # Hopefully not. 25 | -------------------------------------------------------------------------------- /Tests/TomlTests/date-format.toml: -------------------------------------------------------------------------------- 1 | date1 = 1979-05-27T07:32:00Z 2 | date2 = 1979-05-27T07:32:00.5Z 3 | date3 = 1979-05-27T00:32:00.6-07:00 4 | date4 = 1979-05-27T00:32:00.999999+07:00 5 | date5 = 1979-05-27T07:32:00 6 | date6 = 1979-05-27T07:32:00.5 7 | date7 = 1979-05-27 8 | -------------------------------------------------------------------------------- /Tests/TomlTests/datetime-malformed-no-leads.toml: -------------------------------------------------------------------------------- 1 | no-leads = 1987-7-05T17:45:00Z 2 | -------------------------------------------------------------------------------- /Tests/TomlTests/datetime-malformed-no-secs.toml: -------------------------------------------------------------------------------- 1 | no-secs = 1987-07-05T17:45Z 2 | -------------------------------------------------------------------------------- /Tests/TomlTests/datetime-malformed-no-t.toml: -------------------------------------------------------------------------------- 1 | no-t = 1987-07-0517:45:00Z 2 | -------------------------------------------------------------------------------- /Tests/TomlTests/datetime-malformed-with-milli.toml: -------------------------------------------------------------------------------- 1 | with-milli = 1987-07-5T17:45:00.12Z 2 | -------------------------------------------------------------------------------- /Tests/TomlTests/datetime.toml: -------------------------------------------------------------------------------- 1 | bestdayever = 1987-07-05T17:45:00Z 2 | -------------------------------------------------------------------------------- /Tests/TomlTests/duplicate-key-table.toml: -------------------------------------------------------------------------------- 1 | [fruit] 2 | type = "apple" 3 | 4 | [fruit.type] 5 | apple = "yes" 6 | -------------------------------------------------------------------------------- /Tests/TomlTests/duplicate-keys.toml: -------------------------------------------------------------------------------- 1 | dupe = false 2 | dupe = true 3 | -------------------------------------------------------------------------------- /Tests/TomlTests/duplicate-tables.toml: -------------------------------------------------------------------------------- 1 | [a] 2 | [a] 3 | -------------------------------------------------------------------------------- /Tests/TomlTests/empty-implicit-table.toml: -------------------------------------------------------------------------------- 1 | [naughty..naughty] 2 | -------------------------------------------------------------------------------- /Tests/TomlTests/empty-table.toml: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /Tests/TomlTests/empty.toml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdfergason/swift-toml/f26573bd49534f68ac11c2f7467c1d50cdb2afce/Tests/TomlTests/empty.toml -------------------------------------------------------------------------------- /Tests/TomlTests/example.toml: -------------------------------------------------------------------------------- 1 | best-day-ever = 1987-07-05T17:45:00Z 2 | 3 | [numtheory] 4 | boring = false 5 | perfection = [6, 28, 496] 6 | -------------------------------------------------------------------------------- /Tests/TomlTests/expected-nested-tables.toml: -------------------------------------------------------------------------------- 1 | [table1] 2 | key = "yes" 3 | 4 | [table1.sub] 5 | safety = "gravel" 6 | 7 | [table1.sub2] 8 | hello = true 9 | 10 | [table2.sub] 11 | yes = 3.14 12 | 13 | -------------------------------------------------------------------------------- /Tests/TomlTests/expected-serialize.toml: -------------------------------------------------------------------------------- 1 | array-of-arrays = [[1, 2], ["a", "b"]] 2 | date = 2016-08-24T12:00:32.000000Z 3 | double = 3.14 4 | double-array = [1.1000000000000001, 2.2000000000000002, 3.2999999999999998] 5 | int = 32 6 | int-array = [1, 2, 3] 7 | literal = "hello" 8 | multi = "Yes this\nis a multi-line string" 9 | multi_literal = "\\Hello'\nSecond multi-line\n/end/" 10 | string = "Hello" 11 | string-array = ["yes", "no"] 12 | table-array = [{name = "tom"}, 13 | {name = "lucinda"}, 14 | {last = "lujo", name = "greg"}] 15 | 16 | [atable] 17 | a_number = 41 18 | name = "the table" 19 | tough = "not really" 20 | 21 | [inline-table] 22 | age = 32 23 | name = "Jack" 24 | starts = 4.9 25 | 26 | [table1.nested] 27 | description = "table1 is implicitly created; but it should not show up in the serialization" 28 | 29 | [table3] 30 | created = "after" 31 | 32 | [table3.nested-inline] 33 | n0t = "will th#is work?" 34 | roen = "marks" 35 | 36 | [table3.someother.really.nested] 37 | movealong = 5 38 | -------------------------------------------------------------------------------- /Tests/TomlTests/float-no-leading-zero.toml: -------------------------------------------------------------------------------- 1 | answer = .12345 2 | neganswer = -.12345 3 | -------------------------------------------------------------------------------- /Tests/TomlTests/float-no-trailing-digits.toml: -------------------------------------------------------------------------------- 1 | answer = 1. 2 | neganswer = -1. 3 | -------------------------------------------------------------------------------- /Tests/TomlTests/float.toml: -------------------------------------------------------------------------------- 1 | pi = 3.14 2 | negpi = -3.14 3 | -------------------------------------------------------------------------------- /Tests/TomlTests/hard_example-error1.toml: -------------------------------------------------------------------------------- 1 | # Test file for TOML 2 | # Only this one tries to emulate a TOML file written by a user of the kind of parser writers probably hate 3 | # This part you'll really hate 4 | 5 | [the] 6 | test_string = "You'll hate me after this - #" # " Annoying, isn't it? 7 | 8 | [the.hard] 9 | test_array = [ "] ", " # "] # ] There you go, parse this! 10 | test_array2 = [ "Test #11 ]proved that", "Experiment #9 was a success" ] 11 | # You didn't think it'd as easy as chucking out the last #, did you? 12 | another_test_string = " Same thing, but with a string #" 13 | harder_test_string = " And when \"'s are in the string, along with # \"" # "and comments are there too" 14 | # Things will get harder 15 | 16 | [the.hard."bit#"] 17 | "what?" = "You don't think some user won't do that?" 18 | multi_line_array = [ 19 | "]", 20 | # ] Oh yes I did 21 | ] 22 | 23 | # Each of the following keygroups/key value pairs should produce an error. Uncomment to them to test 24 | 25 | [error] if you didn't catch this, your parser is broken 26 | #string = "Anything other than tabs, spaces and newline after a keygroup or key value pair has ended should produce an error unless it is a comment" like this 27 | #array = [ 28 | # "This might most likely happen in multiline arrays", 29 | # Like here, 30 | # "or here, 31 | # and here" 32 | # ] End of array comment, forgot the # 33 | #number = 3.14 pi <--again forgot the # -------------------------------------------------------------------------------- /Tests/TomlTests/hard_example-error2.toml: -------------------------------------------------------------------------------- 1 | # Test file for TOML 2 | # Only this one tries to emulate a TOML file written by a user of the kind of parser writers probably hate 3 | # This part you'll really hate 4 | 5 | [the] 6 | test_string = "You'll hate me after this - #" # " Annoying, isn't it? 7 | 8 | [the.hard] 9 | test_array = [ "] ", " # "] # ] There you go, parse this! 10 | test_array2 = [ "Test #11 ]proved that", "Experiment #9 was a success" ] 11 | # You didn't think it'd as easy as chucking out the last #, did you? 12 | another_test_string = " Same thing, but with a string #" 13 | harder_test_string = " And when \"'s are in the string, along with # \"" # "and comments are there too" 14 | # Things will get harder 15 | 16 | [the.hard."bit#"] 17 | "what?" = "You don't think some user won't do that?" 18 | multi_line_array = [ 19 | "]", 20 | # ] Oh yes I did 21 | ] 22 | 23 | # Each of the following keygroups/key value pairs should produce an error. Uncomment to them to test 24 | 25 | array = [ 26 | "This might most likely happen in multiline arrays", 27 | Like here, 28 | "or here, 29 | and here" 30 | ] End of array comment, forgot the # 31 | #number = 3.14 pi <--again forgot the # -------------------------------------------------------------------------------- /Tests/TomlTests/hard_example-error3.toml: -------------------------------------------------------------------------------- 1 | # Test file for TOML 2 | # Only this one tries to emulate a TOML file written by a user of the kind of parser writers probably hate 3 | # This part you'll really hate 4 | 5 | [the] 6 | test_string = "You'll hate me after this - #" # " Annoying, isn't it? 7 | 8 | [the.hard] 9 | test_array = [ "] ", " # "] # ] There you go, parse this! 10 | test_array2 = [ "Test #11 ]proved that", "Experiment #9 was a success" ] 11 | # You didn't think it'd as easy as chucking out the last #, did you? 12 | another_test_string = " Same thing, but with a string #" 13 | harder_test_string = " And when \"'s are in the string, along with # \"" # "and comments are there too" 14 | # Things will get harder 15 | 16 | [the.hard."bit#"] 17 | "what?" = "You don't think some user won't do that?" 18 | multi_line_array = [ 19 | "]", 20 | # ] Oh yes I did 21 | ] 22 | 23 | # Each of the following keygroups/key value pairs should produce an error. Uncomment to them to test 24 | 25 | array = [ 26 | "This might most likely happen in multiline arrays", 27 | Like here, 28 | "or here, 29 | and here" 30 | ] End of array comment, forgot the # 31 | #number = 3.14 pi <--again forgot the # -------------------------------------------------------------------------------- /Tests/TomlTests/hard_example-error4.toml: -------------------------------------------------------------------------------- 1 | # Test file for TOML 2 | # Only this one tries to emulate a TOML file written by a user of the kind of parser writers probably hate 3 | # This part you'll really hate 4 | 5 | [the] 6 | test_string = "You'll hate me after this - #" # " Annoying, isn't it? 7 | 8 | [the.hard] 9 | test_array = [ "] ", " # "] # ] There you go, parse this! 10 | test_array2 = [ "Test #11 ]proved that", "Experiment #9 was a success" ] 11 | # You didn't think it'd as easy as chucking out the last #, did you? 12 | another_test_string = " Same thing, but with a string #" 13 | harder_test_string = " And when \"'s are in the string, along with # \"" # "and comments are there too" 14 | # Things will get harder 15 | 16 | [the.hard."bit#"] 17 | "what?" = "You don't think some user won't do that?" 18 | multi_line_array = [ 19 | "]", 20 | # ] Oh yes I did 21 | ] 22 | 23 | # Each of the following keygroups/key value pairs should produce an error. Uncomment to them to test 24 | 25 | number = 3.14 pi <--again forgot the # -------------------------------------------------------------------------------- /Tests/TomlTests/hard_example.toml: -------------------------------------------------------------------------------- 1 | # Test file for TOML 2 | # Only this one tries to emulate a TOML file written by a user of the kind of parser writers probably hate 3 | # This part you'll really hate 4 | 5 | [the] 6 | test_string = "You'll hate me after this - #" # " Annoying, isn't it? 7 | 8 | [the.hard] 9 | test_array = [ "] ", " # "] # ] There you go, parse this! 10 | test_array2 = [ "Test #11 ]proved that", "Experiment #9 was a success" ] 11 | # You didn't think it'd as easy as chucking out the last #, did you? 12 | another_test_string = " Same thing, but with a string #" 13 | harder_test_string = " And when \"'s are in the string, along with # \"" # "and comments are there too" 14 | # Things will get harder 15 | 16 | [the.hard."bit#"] 17 | "what?" = "You don't think some user won't do that?" 18 | multi_line_array = [ 19 | "]", 20 | # ] Oh yes I did 21 | ] 22 | 23 | # Each of the following keygroups/key value pairs should produce an error. Uncomment to them to test 24 | 25 | #[error] if you didn't catch this, your parser is broken 26 | #string = "Anything other than tabs, spaces and newline after a keygroup or key value pair has ended should produce an error unless it is a comment" like this 27 | #array = [ 28 | # "This might most likely happen in multiline arrays", 29 | # Like here, 30 | # "or here, 31 | # and here" 32 | # ] End of array comment, forgot the # 33 | #number = 3.14 pi <--again forgot the # -------------------------------------------------------------------------------- /Tests/TomlTests/hard_example_unicode.toml: -------------------------------------------------------------------------------- 1 | # Tèƨƭ ƒïℓè ƒôř TÓM£ 2 | 3 | # Óñℓ¥ ƭλïƨ ôñè ƭřïèƨ ƭô è₥úℓáƭè á TÓM£ ƒïℓè ωřïƭƭèñ β¥ á úƨèř ôƒ ƭλè ƙïñδ ôƒ ƥářƨèř ωřïƭèřƨ ƥřôβáβℓ¥ λáƭè 4 | # Tλïƨ ƥářƭ ¥ôú'ℓℓ řèáℓℓ¥ λáƭè 5 | 6 | [the] 7 | test_string = "Ýôú'ℓℓ λáƭè ₥è áƒƭèř ƭλïƨ - #" # " Âññô¥ïñϱ, ïƨñ'ƭ ïƭ? 8 | 9 | 10 | [the.hard] 11 | test_array = [ "] ", " # "] # ] Tλèřè ¥ôú ϱô, ƥářƨè ƭλïƨ! 12 | test_array2 = [ "Tèƨƭ #11 ]ƥřôƲèδ ƭλáƭ", "Éжƥèřï₥èñƭ #9 ωáƨ á ƨúççèƨƨ" ] 13 | # Ýôú δïδñ'ƭ ƭλïñƙ ïƭ'δ áƨ èáƨ¥ áƨ çλúçƙïñϱ ôúƭ ƭλè ℓáƨƭ #, δïδ ¥ôú? 14 | another_test_string = "§á₥è ƭλïñϱ, βúƭ ωïƭλ á ƨƭřïñϱ #" 15 | harder_test_string = " Âñδ ωλèñ \"'ƨ ářè ïñ ƭλè ƨƭřïñϱ, áℓôñϱ ωïƭλ # \"" # "áñδ çô₥₥èñƭƨ ářè ƭλèřè ƭôô" 16 | # Tλïñϱƨ ωïℓℓ ϱèƭ λářδèř 17 | 18 | [the.hard."βïƭ#"] 19 | "ωλáƭ?" = "Ýôú δôñ'ƭ ƭλïñƙ ƨô₥è úƨèř ωôñ'ƭ δô ƭλáƭ?" 20 | multi_line_array = [ 21 | "]", 22 | # ] Óλ ¥èƨ Ì δïδ 23 | ] 24 | 25 | # Each of the following keygroups/key value pairs should produce an error. Uncomment to them to test 26 | 27 | #[error] ïƒ ¥ôú δïδñ'ƭ çáƭçλ ƭλïƨ, ¥ôúř ƥářƨèř ïƨ βřôƙèñ 28 | #string = "Âñ¥ƭλïñϱ ôƭλèř ƭλáñ ƭáβƨ, ƨƥáçèƨ áñδ ñèωℓïñè áƒƭèř á ƙè¥ϱřôúƥ ôř ƙè¥ Ʋáℓúè ƥáïř λáƨ èñδèδ ƨλôúℓδ ƥřôδúçè áñ èřřôř úñℓèƨƨ ïƭ ïƨ á çô₥₥èñƭ" ℓïƙè ƭλïƨ 29 | 30 | #array = [ 31 | # "Tλïƨ ₥ïϱλƭ ₥ôƨƭ ℓïƙèℓ¥ λáƥƥèñ ïñ ₥úℓƭïℓïñè ářřá¥ƨ", 32 | # £ïƙè λèřè, 33 | # "ôř λèřè, 34 | # áñδ λèřè" 35 | # ] Éñδ ôƒ ářřᥠçô₥₥èñƭ, ƒôřϱôƭ ƭλè # 36 | #number = 3.14 ƥï <--áϱáïñ ƒôřϱôƭ ƭλè # -------------------------------------------------------------------------------- /Tests/TomlTests/implicit-and-explicit-after.toml: -------------------------------------------------------------------------------- 1 | [a.b.c] 2 | answer = 42 3 | 4 | [a] 5 | better = 43 6 | -------------------------------------------------------------------------------- /Tests/TomlTests/implicit-and-explicit-before.toml: -------------------------------------------------------------------------------- 1 | [a] 2 | better = 43 3 | 4 | [a.b.c] 5 | answer = 42 6 | -------------------------------------------------------------------------------- /Tests/TomlTests/implicit-groups.toml: -------------------------------------------------------------------------------- 1 | [a.b.c] 2 | answer = 42 3 | -------------------------------------------------------------------------------- /Tests/TomlTests/integer.toml: -------------------------------------------------------------------------------- 1 | answer = 42 2 | neganswer = -42 3 | -------------------------------------------------------------------------------- /Tests/TomlTests/key-empty.toml: -------------------------------------------------------------------------------- 1 | = 1 2 | -------------------------------------------------------------------------------- /Tests/TomlTests/key-equals-nospace.toml: -------------------------------------------------------------------------------- 1 | answer=42 2 | -------------------------------------------------------------------------------- /Tests/TomlTests/key-hash.toml: -------------------------------------------------------------------------------- 1 | a# = 1 2 | -------------------------------------------------------------------------------- /Tests/TomlTests/key-newline.toml: -------------------------------------------------------------------------------- 1 | a 2 | = 1 3 | -------------------------------------------------------------------------------- /Tests/TomlTests/key-open-bracket.toml: -------------------------------------------------------------------------------- 1 | [abc = 1 2 | -------------------------------------------------------------------------------- /Tests/TomlTests/key-single-open-bracket.toml: -------------------------------------------------------------------------------- 1 | [ -------------------------------------------------------------------------------- /Tests/TomlTests/key-space-error.toml: -------------------------------------------------------------------------------- 1 | a b = 1 -------------------------------------------------------------------------------- /Tests/TomlTests/key-space.toml: -------------------------------------------------------------------------------- 1 | "a b" = 1 -------------------------------------------------------------------------------- /Tests/TomlTests/key-special-chars.toml: -------------------------------------------------------------------------------- 1 | "~!@$^&*()_+-`1234567890[]|/?><.,;:'" = 1 2 | -------------------------------------------------------------------------------- /Tests/TomlTests/key-start-bracket.toml: -------------------------------------------------------------------------------- 1 | [a] 2 | [xyz = 5 3 | [b] 4 | -------------------------------------------------------------------------------- /Tests/TomlTests/key-two-equals.toml: -------------------------------------------------------------------------------- 1 | key= = 1 2 | -------------------------------------------------------------------------------- /Tests/TomlTests/long-float.toml: -------------------------------------------------------------------------------- 1 | longpi = 3.141592653589793 2 | neglongpi = -3.141592653589793 3 | -------------------------------------------------------------------------------- /Tests/TomlTests/long-integer.toml: -------------------------------------------------------------------------------- 1 | answer = 9223372036854775807 2 | neganswer = -9223372036854775808 3 | -------------------------------------------------------------------------------- /Tests/TomlTests/multiline-string.toml: -------------------------------------------------------------------------------- 1 | multiline_empty_one = """""" 2 | multiline_empty_two = """ 3 | """ 4 | multiline_empty_three = """\ 5 | """ 6 | multiline_empty_four = """\ 7 | \ 8 | \ 9 | """ 10 | 11 | equivalent_one = "The quick brown fox jumps over the lazy dog." 12 | equivalent_two = """ 13 | The quick brown \ 14 | 15 | 16 | fox jumps over \ 17 | the lazy dog.""" 18 | 19 | equivalent_three = """\ 20 | The quick brown \ 21 | fox jumps over \ 22 | the lazy dog.\ 23 | """ 24 | -------------------------------------------------------------------------------- /Tests/TomlTests/nested-tables.toml: -------------------------------------------------------------------------------- 1 | [table1] 2 | key = "yes" 3 | 4 | [table1.sub] 5 | safety = "gravel" 6 | 7 | [table1.sub2] 8 | hello = true 9 | 10 | [table2.sub] 11 | yes = 3.14 -------------------------------------------------------------------------------- /Tests/TomlTests/raw-multiline-string.toml: -------------------------------------------------------------------------------- 1 | oneline = '''This string has a ' quote character.''' 2 | firstnl = ''' 3 | This string has a ' quote character.''' 4 | multiline = ''' 5 | This string 6 | has ' a quote character 7 | and more than 8 | one newline 9 | in it.''' 10 | -------------------------------------------------------------------------------- /Tests/TomlTests/raw-string.toml: -------------------------------------------------------------------------------- 1 | backspace = 'This string has a \b backspace character.' 2 | tab = 'This string has a \t tab character.' 3 | newline = 'This string has a \n new line character.' 4 | formfeed = 'This string has a \f form feed character.' 5 | carriage = 'This string has a \r carriage return character.' 6 | slash = 'This string has a \/ slash character.' 7 | backslash = 'This string has a \\ backslash character.' 8 | -------------------------------------------------------------------------------- /Tests/TomlTests/serialize.toml: -------------------------------------------------------------------------------- 1 | # basic key value serialization 2 | int = 32 3 | double = 3.14 4 | date = 2016-08-24T12:00:32.00Z 5 | string = "Hello" 6 | literal = 'hello' 7 | multi = """ 8 | Yes this 9 | is a multi-line string""" 10 | multi_literal = '''\Hello' 11 | Second multi-line 12 | /end/''' 13 | int-array = [1, 2 ,3] # intentional white-space change 14 | double-array = [1.1, 2.2, 3.3] 15 | string-array = ["yes", "no"] 16 | array-of-arrays = [[1, 2], ["a", "b"]] 17 | inline-table = {name = "Jack", age = 32, starts = 4.9} 18 | 19 | # table 20 | [atable] 21 | name = "the table" 22 | tough = "not really" 23 | a_number = 41 24 | 25 | # nested-table 26 | [table1.nested] 27 | description = "table1 is implicitly created; but it should not show up in the serialization" 28 | 29 | [table3.someother.really.nested] 30 | movealong = 5 31 | 32 | [table3] 33 | created = "after" 34 | nested-inline = {roen = "marks", n0t = "will th\u0023is work?"} 35 | 36 | # nested-table no parent 37 | 38 | # array of tables 39 | [[table-array]] 40 | name = "tom" 41 | 42 | [[table-array]] 43 | name = "lucinda" 44 | 45 | [[table-array]] 46 | name = "greg" 47 | last = "lujo" 48 | -------------------------------------------------------------------------------- /Tests/TomlTests/simple.toml: -------------------------------------------------------------------------------- 1 | string="value" 2 | literal_string='lite\ral' 3 | int=1 4 | float=3.14 5 | date=1982-07-27T12:00:00Z 6 | array=[1,2,3] 7 | inline_table={1="one", 3="three"} 8 | bool=true -------------------------------------------------------------------------------- /Tests/TomlTests/string-bad-byte-escape.toml: -------------------------------------------------------------------------------- 1 | naughty = "\xAg" 2 | -------------------------------------------------------------------------------- /Tests/TomlTests/string-bad-escape.toml: -------------------------------------------------------------------------------- 1 | invalid-escape = "This string has a bad \a escape character." 2 | -------------------------------------------------------------------------------- /Tests/TomlTests/string-byte-escapes.toml: -------------------------------------------------------------------------------- 1 | answer = "\x33" 2 | -------------------------------------------------------------------------------- /Tests/TomlTests/string-empty.toml: -------------------------------------------------------------------------------- 1 | answer = "" 2 | -------------------------------------------------------------------------------- /Tests/TomlTests/string-escapes.toml: -------------------------------------------------------------------------------- 1 | backspace = "This string has a \b backspace character." 2 | tab = "This string has a \t tab character." 3 | newline = "This string has a \n new line character." 4 | formfeed = "This string has a \f form feed character." 5 | carriage = "This string has a \r carriage return character." 6 | quote = "This string has a \" quote character." 7 | backslash = "This string has a \\ backslash character." 8 | notunicode1 = "This string does not have a unicode \\u escape." 9 | notunicode2 = "This string does not have a unicode \u005Cu escape." 10 | notunicode3 = "This string does not have a unicode \\u0075 escape." 11 | notunicode4 = "This string does not have a unicode \\\u0075 escape." 12 | -------------------------------------------------------------------------------- /Tests/TomlTests/string-no-close.toml: -------------------------------------------------------------------------------- 1 | no-ending-quote = "One time, at band camp 2 | -------------------------------------------------------------------------------- /Tests/TomlTests/string-simple.toml: -------------------------------------------------------------------------------- 1 | answer = "You are not drinking enough whisky." 2 | -------------------------------------------------------------------------------- /Tests/TomlTests/string-with-pound.toml: -------------------------------------------------------------------------------- 1 | pound = "We see no # comments here." 2 | poundcomment = "But there are # some comments here." # Did I # mess you up? 3 | -------------------------------------------------------------------------------- /Tests/TomlTests/table-array-implicit.toml: -------------------------------------------------------------------------------- 1 | [[albums.songs]] 2 | name = "Glory Days" 3 | -------------------------------------------------------------------------------- /Tests/TomlTests/table-array-malformed-bracket.toml: -------------------------------------------------------------------------------- 1 | [[albums] 2 | name = "Born to Run" 3 | -------------------------------------------------------------------------------- /Tests/TomlTests/table-array-malformed-empty.toml: -------------------------------------------------------------------------------- 1 | [[]] 2 | name = "Born to Run" 3 | -------------------------------------------------------------------------------- /Tests/TomlTests/table-array-many.toml: -------------------------------------------------------------------------------- 1 | [[people]] 2 | first_name = "Bruce" 3 | last_name = "Springsteen" 4 | 5 | [[people]] 6 | first_name = "Eric" 7 | last_name = "Clapton" 8 | 9 | [[people]] 10 | first_name = "Bob" 11 | last_name = "Seger" 12 | -------------------------------------------------------------------------------- /Tests/TomlTests/table-array-nest.toml: -------------------------------------------------------------------------------- 1 | [[albums]] 2 | name = "Born to Run" 3 | 4 | [[albums.songs]] 5 | name = "Jungleland" 6 | 7 | [[albums.songs]] 8 | name = "Meeting Across the River" 9 | 10 | [[albums]] 11 | name = "Born in the USA" 12 | 13 | [[albums.songs]] 14 | name = "Glory Days" 15 | 16 | [[albums.songs]] 17 | name = "Dancing in the Dark" 18 | -------------------------------------------------------------------------------- /Tests/TomlTests/table-array-one.toml: -------------------------------------------------------------------------------- 1 | [[people]] 2 | first_name = "Bruce" 3 | last_name = "Springsteen" 4 | -------------------------------------------------------------------------------- /Tests/TomlTests/table-empty-invalid.toml: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /Tests/TomlTests/table-empty.toml: -------------------------------------------------------------------------------- 1 | [a] 2 | -------------------------------------------------------------------------------- /Tests/TomlTests/table-nested-brackets-close.toml: -------------------------------------------------------------------------------- 1 | [a]b] 2 | zyx = 42 3 | -------------------------------------------------------------------------------- /Tests/TomlTests/table-nested-brackets-open.toml: -------------------------------------------------------------------------------- 1 | [a[b] 2 | zyx = 42 3 | -------------------------------------------------------------------------------- /Tests/TomlTests/table-sub-empty.toml: -------------------------------------------------------------------------------- 1 | [a] 2 | [a.b] 3 | -------------------------------------------------------------------------------- /Tests/TomlTests/table-whitespace-invalid.toml: -------------------------------------------------------------------------------- 1 | [invalid key] -------------------------------------------------------------------------------- /Tests/TomlTests/table-whitespace.toml: -------------------------------------------------------------------------------- 1 | ["valid key"] 2 | -------------------------------------------------------------------------------- /Tests/TomlTests/table-with-pound-invalid.toml: -------------------------------------------------------------------------------- 1 | [key#group] 2 | answer = 42 -------------------------------------------------------------------------------- /Tests/TomlTests/table-with-pound.toml: -------------------------------------------------------------------------------- 1 | ["key#group"] 2 | answer = 42 3 | -------------------------------------------------------------------------------- /Tests/TomlTests/text-after-array-entries.toml: -------------------------------------------------------------------------------- 1 | array = [ 2 | "Is there life after an array separator?", No 3 | "Entry" 4 | ] 5 | -------------------------------------------------------------------------------- /Tests/TomlTests/text-after-integer.toml: -------------------------------------------------------------------------------- 1 | answer = 42 the ultimate answer? 2 | -------------------------------------------------------------------------------- /Tests/TomlTests/text-after-string.toml: -------------------------------------------------------------------------------- 1 | string = "Is there life after strings?" No. 2 | -------------------------------------------------------------------------------- /Tests/TomlTests/text-after-table.toml: -------------------------------------------------------------------------------- 1 | [error] this shouldn't be here 2 | -------------------------------------------------------------------------------- /Tests/TomlTests/text-before-array-separator.toml: -------------------------------------------------------------------------------- 1 | array = [ 2 | "Is there life before an array separator?" No, 3 | "Entry" 4 | ] 5 | -------------------------------------------------------------------------------- /Tests/TomlTests/text-in-array.toml: -------------------------------------------------------------------------------- 1 | array = [ 2 | "Entry 1", 3 | I don't belong, 4 | "Entry 2", 5 | ] 6 | -------------------------------------------------------------------------------- /Tests/TomlTests/toml-example.toml: -------------------------------------------------------------------------------- 1 | # This is a TOML document. Boom. 2 | 3 | title = "TOML Example" 4 | 5 | [owner] 6 | name = "Tom Preston-Werner" 7 | organization = "GitHub" 8 | bio = "GitHub Cofounder & CEO\nLikes tater tots and beer." 9 | dob = 1979-05-27T07:32:00Z # First class dates? Why not? 10 | 11 | [database] 12 | server = "192.168.1.1" 13 | ports = [ 8001, 8001, 8002 ] 14 | connection_max = 5000 15 | enabled = true 16 | 17 | [servers] 18 | 19 | # You can indent as you please. Tabs or spaces. TOML don't care. 20 | [servers.alpha] 21 | ip = "10.0.0.1" 22 | dc = "eqdc10" 23 | 24 | [servers.beta] 25 | ip = "10.0.0.2" 26 | dc = "eqdc10" 27 | country = "中国" # This should be parsed as UTF-8 28 | 29 | [clients] 30 | data = [ ["gamma", "delta"], [1, 2] ] # just an update to make sure parsers support it 31 | 32 | # Line breaks are OK when inside arrays 33 | hosts = [ 34 | "alpha", 35 | "omega" 36 | ] 37 | 38 | # Products 39 | 40 | [[products]] 41 | name = "Hammer" 42 | sku = 738594937 43 | 44 | [[products]] 45 | name = "Nail" 46 | sku = 284758393 47 | color = "gray" -------------------------------------------------------------------------------- /Tests/TomlTests/unicode-escape.toml: -------------------------------------------------------------------------------- 1 | answer4 = "\u03B4" 2 | answer8 = "\U000003B4" 3 | -------------------------------------------------------------------------------- /Tests/TomlTests/unicode-literal.toml: -------------------------------------------------------------------------------- 1 | answer = "δ" 2 | --------------------------------------------------------------------------------