├── .gitignore ├── .gitmodules ├── LICENSE.txt ├── README.md ├── toml-examples ├── example-v0.3.0.toml ├── example-v0.4.0.toml └── wrong.toml ├── tomlutil.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ └── xcschemes │ └── tomlutil.xcscheme └── tomlutil ├── Info.plist ├── LMPTOMLSerialization.h ├── LMPTOMLSerialization.mm ├── LMP_toml_visitors.h └── main.m /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | build/ 3 | *.pbxuser 4 | !default.pbxuser 5 | *.mode1v3 6 | !default.mode1v3 7 | *.mode2v3 8 | !default.mode2v3 9 | *.perspectivev3 10 | !default.perspectivev3 11 | xcuserdata 12 | *.xccheckout 13 | *.moved-aside 14 | DerivedData 15 | *.hmap 16 | *.ipa 17 | *.xcuserstate 18 | 19 | #OS X Stuff 20 | .localized 21 | .DS_Store 22 | *.zip 23 | 24 | # Pods 25 | Pods/ 26 | 27 | # Editors 28 | .idea 29 | *.swp 30 | 31 | # Keys 32 | *.pem 33 | *.pkey 34 | *.p12 35 | 36 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "submodules/toml11"] 2 | path = submodules/toml11 3 | url = git@github.com:ToruNiina/toml11.git 4 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Dominik Wagner. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ObjectiveTOML 2 | 3 | ObjectiveTOML is a clean and nice Objective-C API to read and write [TOML](https://github.com/toml-lang/toml) files. It is utilizing [toml11](https://github.com/ToruNiina/toml11) and therefore on par with its TOML compliance. At time of writing this is [TOML 1.0.0](https://toml.io/en/v1.0.0) 4 | 5 | The main target is a small command line utility `tomlutil` to convert between TOML, JSON and the xml and binary plist format on macOS. 6 | 7 | The API is aligned with `NSJSONSerialization`: 8 | 9 | ```objectivec 10 | #import "LMPTOMLSerialization.h" 11 | 12 | NSData *inputData = [NSData dataWithContentsOfURL:fileURL]; 13 | NSDictionary * tomlObject = 14 | [LMPTOMLSerialization TOMLObjectWithData:inputData error:&error]; 15 | // now tomlObject holds the contents of the TOML file. 16 | 17 | ``` 18 | 19 | The `tomlutil` has nice parsing error reporting (if you only want this, you can use the `-lint` option: 20 | 21 | ``` 22 | $> ./tomlutil -lint toml-examples/wrong.toml 23 | 🚫 Input TOML could not be parsed 24 | [error] toml::parse_key_value_pair: invalid format for key 25 | --> toml-examples/wrong.toml 26 | | 27 | 14 | asdfpoin1!@ j= ;alskjfasdf 28 | | ^--- invalid character in key 29 | | 30 | Hint: Did you forget '.' to separate dotted-key? 31 | Hint: Allowed characters for bare key are [0-9a-zA-Z_-]. 32 | ``` 33 | 34 | Current usage output: 35 | 36 | ``` 37 | tomlutil v2.0.0 (toml11 v3.7.1) 38 | 39 | Usage: tomlutil [-f json|xml1|binary1|toml] file [outputfile] 40 | 41 | A file of '-' reads from stdin. Can read json, plists and toml. Output defaults to stdout. 42 | -f format Output format. One of json, xml1, binary1, toml. Defaults to toml. 43 | -lint Just lint with toml11, no output. 44 | ``` 45 | 46 | Simple conversion of the Mojave News.app plist to TOML: 47 | 48 | ```toml 49 | $> ./tomlutil /System/Applications/News.app/Contents/Info.plist 50 | SBAppUsesLocalNotifications = true 51 | DTSDKBuild = "22D40" 52 | UILaunchStoryboardName = "LaunchScreen" 53 | CFBundleHelpBookName = "com.apple.News.help" 54 | NSSupportsSuddenTermination = true 55 | DTPlatformVersion = "13.2" 56 | UIViewGroupOpacity = false 57 | CFBundleDisplayName = "News" 58 | CFBundleName = "News" 59 | HPDHelpProjectIdentifier = "news" 60 | UIDeviceFamily = [6] 61 | CFBundlePackageType = "APPL" 62 | UIRequiredDeviceCapabilities = ["armv7"] 63 | CFBundleSignature = "????" 64 | UIMenuBarItemTitleQuit = "Quit News" 65 | CFBundleVersion = "3270.0.1" 66 | LSSupportedRegions = ["US","GB","AU","CA"] 67 | NSLocationDefaultAccuracyReduced = true 68 | UIMenuBarItemTitleHelp = "News Help" 69 | NSLocationUsageDescription = """ 70 | Get top local news and weather, and locally relevant search results an\ 71 | d ads.\ 72 | """ 73 | UIMenuBarItemTitleAbout = "About News" 74 | UIMenuBarItemTitleHide = "Hide News" 75 | NSCalendarsUsageDescription = "This will let you add events from News to your calendar." 76 | CFBundleSupportedPlatforms = ["MacOSX"] 77 | NSLocationWhenInUseUsageDescription = """ 78 | Get top local news and weather, and locally relevant search r\ 79 | esults and ads.\ 80 | """ 81 | CFBundleInfoDictionaryVersion = "6.0" 82 | LSMinimumSystemVersion = "13.2" 83 | CFBundleIdentifier = "com.apple.news" 84 | NSHumanReadableCopyright = "Copyright © 2022 Apple Inc. All rights reserved." 85 | NSPhotoLibraryAddUsageDescription = "" 86 | UIWhitePointAdaptivityStyle = "UIWhitePointAdaptivityStyleReading" 87 | NSContactsUsageDescription = "" 88 | NSSupportsAutomaticTermination = true 89 | CFBundleShortVersionString = "8.2.1" 90 | CFBundleIconFile = "AppIcon" 91 | CTIgnoreUserFonts = true 92 | UIAppFonts = [] 93 | UIUserInterfaceStyle = "Automatic" 94 | DTXcodeBuild = "14A6270d" 95 | CFBundleExecutable = "News" 96 | DTCompiler = "com.apple.compilers.llvm.clang.1_0" 97 | CFBundleIconName = "AppIcon" 98 | UIStatusBarHidden = false 99 | "UISupportedInterfaceOrientations~ipad" = [ 100 | "UIInterfaceOrientationPortrait", 101 | "UIInterfaceOrientationPortraitUpsideDown", 102 | "UIInterfaceOrientationLandscapeLeft", 103 | "UIInterfaceOrientationLandscapeRight", 104 | ] 105 | BuildMachineOSBuild = "20A241133" 106 | UIViewControllerBasedStatusBarAppearance = true 107 | UIBackgroundModes = ["audio","fetch","remote-notification"] 108 | NSAccentColorName = "NewsAccentColor" 109 | UIViewEdgeAntialiasing = false 110 | DTPlatformName = "macosx" 111 | SBMatchingApplicationGenres = [ 112 | "News","Reference","Entertainment","Productivity","Education", 113 | "Business", 114 | ] 115 | NSUserActivityTypes = [ 116 | "TagIntent","TodayIntent","com.apple.news.articleViewing", 117 | "com.apple.news.feedBrowsing","com.apple.news.feedBackCatalog", 118 | "com.apple.news.forYou","com.apple.news.history","com.apple.news.saved", 119 | "com.apple.news.magazineSections","com.apple.news.link", 120 | ] 121 | UIApplicationShortcutWidget = "com.apple.news.widget" 122 | CFBundleDevelopmentRegion = "en" 123 | UISupportedInterfaceOrientations = ["UIInterfaceOrientationPortrait"] 124 | CFBundleHelpBookFolder = "News.help" 125 | BGTaskSchedulerPermittedIdentifiers = ["com.apple.news.backgroundFetchManager"] 126 | _LSSupportsRemoval = true 127 | DTSDKName = "macosx13.2.internal" 128 | DTPlatformBuild = "22D40" 129 | NSPhotoLibraryUsageDescription = "" 130 | DTXcode = "1400" 131 | LSCounterpartIdentifiers = ["com.apple.nanonews"] 132 | 133 | [[UIApplicationShortcutItems]] 134 | UIApplicationShortcutItemTitle = "ApplicationShortcutItemForYou" 135 | UIApplicationShortcutItemIconFile = "ios_for_you_icon_large" 136 | UIApplicationShortcutItemType = "com.apple.news.openforyou" 137 | 138 | [UNUserNotificationCenter] 139 | UNSuppressUserAuthorizationPrompt = false 140 | 141 | [UIApplicationSceneManifest] 142 | UIApplicationSupportsMultipleScenes = "1" 143 | 144 | [UIApplicationSceneManifest.UISceneConfigurations] 145 | 146 | [[UIApplicationSceneManifest.UISceneConfigurations.UIWindowSceneSessionRoleApplication]] 147 | UISceneConfigurationName = "Default Configuration" 148 | UISceneDelegateClassName = "NewsUI2.SceneDelegate" 149 | UISceneClassName = "TeaUI.WindowScene" 150 | 151 | 152 | [[CFBundleURLTypes]] 153 | CFBundleURLSchemes = ["applenews","applenewss"] 154 | CFBundleURLName = "com.apple.NewsCustomScheme" 155 | CFBundleTypeRole = "Editor" 156 | ``` 157 | 158 | Full header: 159 | 160 | ```objectivec 161 | @interface LMPTOMLSerialization : NSObject 162 | 163 | /** 164 | Generate a Foundation Dictionary from TOML data. 165 | 166 | @param data NSData representing a TOML file 167 | @param error helpful information if the parsing fails 168 | @return NSDictionary representing the contents of the TOML file. Note that given dates will be represented as NSDateComponents, use +serializationObjectWtihTOMLObject: to convert those to RFC3339 strings that can be used in JSON or PropertyList serializations. 169 | */ 170 | + (NSDictionary *)TOMLObjectWithData:(NSData *)data error:(NSError **)error; 171 | 172 | /** 173 | Generates NSData representation of the TOMLObject. The representation is UTF8 and can be stored directly as a TOML file. 174 | 175 | Note that roundtripping is a lossy opreation, as all comments are stripped, the allowed number formats are reduced to canonical ones and doubles might lose or gain unwanted precision. 176 | 177 | @param tomlObject Foundation Object consisting of TOML serializable objects. In addition to plist objects this contains NSDateComponent objects with y-m-d filled, h-m-s-[nanoseconds] filled, all fields filled, or all fields + timezone filled. 178 | @param error helpful information if generation fails 179 | @return NSData representing the object. 180 | */ 181 | + (NSData *)dataWithTOMLObject:(NSDictionary *)tomlObject error:(NSError **)error; 182 | 183 | /** 184 | Takes a Dictionary representing a TOML file and translates the NSDateComponents into RFC339 strings to be able to be serialized in JSON or PropertyLists 185 | 186 | @param tomlObject foundation dictionary consisting of TOML serializable objects. 187 | @return NSDictionary containing property list serializable objects. 188 | */ 189 | + (NSDictionary *)serializableObjectWithTOMLObject:(NSDictionary *)tomlObject; 190 | 191 | @end 192 | ``` 193 | 194 | ## Changelog 195 | 196 | * v2.0.0 197 | * switched to [toml11] (https://github.com/ToruNiina/toml11 198 | 199 | * v1.1.0 200 | * Updated to cpptoml v0.1.1 201 | * Added more arguments, help and error reporting to the `tomlutil` 202 | 203 | * v1.0.1 204 | * Fixed an issue with cpptoml with trailing whitespace and comments in dates as well as allowing for empty inline tables now. 205 | 206 | * v1.0 207 | * Reading and writing works. Dates are handeled. Conversion between json, plists and toml works as expected. 208 | 209 | * v0.1.0 210 | * Basic reading works, no support of Date format yet, or of the switches promised. 211 | 212 | ## Authors 213 | 214 | * **Dominik Wagner** - [monkeydom](https://github.com/monkeydom) - [@monkeydom@mastodon.social](https://mastodon.social/@monkeydom) 215 | 216 | ## License 217 | 218 | Distributed under the MIT License - see [LICENSE.txt](LICENSE.txt) file for details 219 | 220 | ## Acknowledgments 221 | 222 | * ObjectiveTOML relies on the excellent [cpptoml](https://github.com/skystrife/cpptoml) 223 | 224 | -------------------------------------------------------------------------------- /toml-examples/example-v0.3.0.toml: -------------------------------------------------------------------------------- 1 | # Comment 2 | # I am a comment. Hear me roar. Roar. 3 | 4 | # Table 5 | # Tables (also known as hash tables or dictionaries) are collections of key/value pairs. 6 | # They appear in square brackets on a line by themselves. 7 | 8 | [Table] 9 | 10 | key = "value" # Yeah, you can do this. 11 | 12 | # Nested tables are denoted by table names with dots in them. Name your tables whatever crap you please, just don't use #, ., [ or ]. 13 | 14 | [dog.tater] 15 | type = "pug" 16 | 17 | # You don't need to specify all the super-tables if you don't want to. TOML knows how to do it for you. 18 | 19 | # [x] you 20 | # [x.y] don't 21 | # [x.y.z] need these 22 | [x.y.z.w] # for this to work 23 | 24 | # String 25 | # There are four ways to express strings: basic, multi-line basic, literal, and multi-line literal. 26 | # All strings must contain only valid UTF-8 characters. 27 | 28 | [String] 29 | basic = "I'm a string. \"You can quote me\". Name\tJos\u00E9\nLocation\tSF." 30 | 31 | [String.Multiline] 32 | 33 | # The following strings are byte-for-byte equivalent: 34 | key1 = "One\nTwo" 35 | key2 = """One\nTwo""" 36 | key3 = """ 37 | One 38 | Two""" 39 | 40 | [String.Multilined.Singleline] 41 | 42 | # The following strings are byte-for-byte equivalent: 43 | key1 = "The quick brown fox jumps over the lazy dog." 44 | 45 | key2 = """ 46 | The quick brown \ 47 | 48 | 49 | fox jumps over \ 50 | the lazy dog.""" 51 | 52 | key3 = """\ 53 | The quick brown \ 54 | fox jumps over \ 55 | the lazy dog.\ 56 | """ 57 | 58 | [String.Literal] 59 | 60 | # What you see is what you get. 61 | winpath = 'C:\Users\nodejs\templates' 62 | winpath2 = '\\ServerX\admin$\system32\' 63 | quoted = 'Tom "Dubs" Preston-Werner' 64 | regex = '<\i\c*\s*>' 65 | 66 | 67 | [String.Literal.Multiline] 68 | 69 | regex2 = '''I [dw]on't need \d{2} apples''' 70 | lines = ''' 71 | The first newline is 72 | trimmed in raw strings. 73 | All other whitespace 74 | is preserved. 75 | ''' 76 | 77 | # Integer 78 | # Integers are whole numbers. Positive numbers may be prefixed with a plus sign. 79 | # Negative numbers are prefixed with a minus sign. 80 | 81 | [Integer] 82 | key1 = +99 83 | key2 = 42 84 | key3 = 0 85 | key4 = -17 86 | 87 | # Float 88 | # A float consists of an integer part (which may be prefixed with a plus or minus sign) 89 | # followed by a fractional part and/or an exponent part. 90 | 91 | [Float.fractional] 92 | 93 | # fractional 94 | key1 = +1.0 95 | key2 = 3.1415 96 | key3 = -0.01 97 | 98 | [Float.exponent] 99 | 100 | # exponent 101 | key1 = 5e+22 102 | key2 = 1e6 103 | key3 = -2E-2 104 | 105 | [Float.both] 106 | 107 | # both 108 | key = 6.626e-34 109 | 110 | # Boolean 111 | # Booleans are just the tokens you're used to. Always lowercase. 112 | 113 | [Booleans] 114 | True = true 115 | False = false 116 | 117 | # Datetime 118 | # Datetimes are RFC 3339 dates. 119 | 120 | [Datetime] 121 | key1 = 1979-05-27T07:32:00Z 122 | key2 = 1979-05-27T00:32:00-07:00 123 | key3 = 1979-05-27T00:32:00.999999-07:00 124 | 125 | # Array 126 | # Arrays are square brackets with other primitives inside. Whitespace is ignored. Elements are separated by commas. Data types may not be mixed. 127 | 128 | [Array] 129 | key1 = [ 1, 2, 3 ] 130 | key2 = [ "red", "yellow", "green" ] 131 | key3 = [ [ 1, 2 ], [3, 4, 5] ] 132 | key4 = [ [ 1, 2 ], ["a", "b", "c"] ] # this is ok 133 | 134 | #Arrays can also be multiline. So in addition to ignoring whitespace, arrays also ignore newlines between the brackets. 135 | # Terminating commas are ok before the closing bracket. 136 | 137 | key5 = [ 138 | 1, 2, 3 139 | ] 140 | key6 = [ 141 | 1, 142 | 2, # this is ok 143 | ] 144 | 145 | # Array of Tables 146 | # These can be expressed by using a table name in double brackets. 147 | # Each table with the same double bracketed name will be an element in the array. 148 | # The tables are inserted in the order encountered. 149 | 150 | [[products]] 151 | name = "Hammer" 152 | sku = 738594937 153 | 154 | [[products]] 155 | 156 | [[products]] 157 | name = "Nail" 158 | sku = 284758393 159 | color = "gray" 160 | 161 | 162 | # You can create nested arrays of tables as well. 163 | 164 | [[fruit]] 165 | name = "apple" 166 | 167 | [fruit.physical] 168 | color = "red" 169 | shape = "round" 170 | 171 | [[fruit.variety]] 172 | name = "red delicious" 173 | 174 | [[fruit.variety]] 175 | name = "granny smith" 176 | 177 | [[fruit]] 178 | name = "banana" 179 | 180 | [[fruit.variety]] 181 | name = "plantain" 182 | 183 | -------------------------------------------------------------------------------- /toml-examples/example-v0.4.0.toml: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | ## Comment 3 | 4 | # Speak your mind with the hash symbol. They go from the symbol to the end of 5 | # the line. 6 | 7 | 8 | ################################################################################ 9 | ## Table 10 | 11 | # Tables (also known as hash tables or dictionaries) are collections of 12 | # key/value pairs. They appear in square brackets on a line by themselves. 13 | 14 | [table] 15 | 16 | key = "value" # Yeah, you can do this. 17 | 18 | # Nested tables are denoted by table names with dots in them. Name your tables 19 | # whatever crap you please, just don't use #, ., [ or ]. 20 | 21 | [table.subtable] 22 | 23 | key = "another value" 24 | 25 | # You don't need to specify all the super-tables if you don't want to. TOML 26 | # knows how to do it for you. 27 | 28 | # [x] you 29 | # [x.y] don't 30 | # [x.y.z] need these 31 | [x.y.z.w] # for this to work 32 | 33 | 34 | ################################################################################ 35 | ## Inline Table 36 | 37 | # Inline tables provide a more compact syntax for expressing tables. They are 38 | # especially useful for grouped data that can otherwise quickly become verbose. 39 | # Inline tables are enclosed in curly braces `{` and `}`. No newlines are 40 | # allowed between the curly braces unless they are valid within a value. 41 | 42 | [table.inline] 43 | 44 | name = { first = "Tom", last = "Preston-Werner" } 45 | point = { x = 1, y = 2 } 46 | 47 | 48 | ################################################################################ 49 | ## String 50 | 51 | # There are four ways to express strings: basic, multi-line basic, literal, and 52 | # multi-line literal. All strings must contain only valid UTF-8 characters. 53 | 54 | [string] 55 | 56 | basic = "I'm a string. \"You can quote me\". Name\tJos\u00E9\nLocation\tSF." 57 | 58 | [string.multiline] 59 | 60 | # The following strings are byte-for-byte equivalent: 61 | key1 = "One\nTwo" 62 | key2 = """One\nTwo""" 63 | key3 = """ 64 | One 65 | Two""" 66 | 67 | [string.multiline.continued] 68 | 69 | # The following strings are byte-for-byte equivalent: 70 | key1 = "The quick brown fox jumps over the lazy dog." 71 | 72 | key2 = """ 73 | The quick brown \ 74 | 75 | 76 | fox jumps over \ 77 | the lazy dog.""" 78 | 79 | key3 = """\ 80 | The quick brown \ 81 | fox jumps over \ 82 | the lazy dog.\ 83 | """ 84 | 85 | [string.literal] 86 | 87 | # What you see is what you get. 88 | winpath = 'C:\Users\nodejs\templates' 89 | winpath2 = '\\ServerX\admin$\system32\' 90 | quoted = 'Tom "Dubs" Preston-Werner' 91 | regex = '<\i\c*\s*>' 92 | 93 | 94 | [string.literal.multiline] 95 | 96 | regex2 = '''I [dw]on't need \d{2} apples''' 97 | lines = ''' 98 | The first newline is 99 | trimmed in raw strings. 100 | All other whitespace 101 | is preserved. 102 | ''' 103 | 104 | 105 | ################################################################################ 106 | ## Integer 107 | 108 | # Integers are whole numbers. Positive numbers may be prefixed with a plus sign. 109 | # Negative numbers are prefixed with a minus sign. 110 | 111 | [integer] 112 | 113 | key1 = +99 114 | key2 = 42 115 | key3 = 0 116 | key4 = -17 117 | 118 | [integer.underscores] 119 | 120 | # For large numbers, you may use underscores to enhance readability. Each 121 | # underscore must be surrounded by at least one digit. 122 | key1 = 1_000 123 | key2 = 5_349_221 124 | key3 = 1_2_3_4_5 # valid but inadvisable 125 | 126 | 127 | ################################################################################ 128 | ## Float 129 | 130 | # A float consists of an integer part (which may be prefixed with a plus or 131 | # minus sign) followed by a fractional part and/or an exponent part. 132 | 133 | [float.fractional] 134 | 135 | key1 = +1.0 136 | key2 = 3.1415 137 | key3 = -0.01 138 | 139 | [float.exponent] 140 | 141 | key1 = 5e+22 142 | key2 = 1e6 143 | key3 = -2E-2 144 | 145 | [float.both] 146 | 147 | key = 6.626e-34 148 | 149 | [float.underscores] 150 | 151 | key1 = 9_224_617.445_991_228_313 152 | key2 = 1e1_00 153 | 154 | 155 | ################################################################################ 156 | ## Boolean 157 | 158 | # Booleans are just the tokens you're used to. Always lowercase. 159 | 160 | [boolean] 161 | 162 | True = true 163 | False = false 164 | 165 | 166 | ################################################################################ 167 | ## Datetime 168 | 169 | # Datetimes are RFC 3339 dates. 170 | 171 | [datetime] 172 | 173 | key1 = 1979-05-27T07:32:00Z # asdf 174 | key2 = 1979-05-27T00:32:00-07:00 175 | key3 = 1979-05-27T00:32:00.999999-07:00 176 | key4 = 1979-05-27 00:32:00+07:10 177 | time = 12:32:11.123 178 | date = 1979-05-27 179 | 180 | 181 | ################################################################################ 182 | ## Array 183 | 184 | # Arrays are square brackets with other primitives inside. Whitespace is 185 | # ignored. Elements are separated by commas. Data types may not be mixed. 186 | 187 | [array] 188 | 189 | key1 = [ 1, 2, 3 ] 190 | key2 = [ "red", "yellow", "green" ] 191 | key3 = [ [ 1, 2 ], [3, 4, 5] ] 192 | key4 = [ [ 1, 2 ], ["a", "b", "c"] ] # this is ok 193 | 194 | # Arrays can also be multiline. So in addition to ignoring whitespace, arrays 195 | # also ignore newlines between the brackets. Terminating commas are ok before 196 | # the closing bracket. 197 | 198 | key5 = [ 199 | 1, 2, 3 200 | ] 201 | key6 = [ 202 | 1, 203 | 2, # this is ok 204 | ] 205 | 206 | 207 | ################################################################################ 208 | ## Array of Tables 209 | 210 | # These can be expressed by using a table name in double brackets. Each table 211 | # with the same double bracketed name will be an element in the array. The 212 | # tables are inserted in the order encountered. 213 | 214 | [[products]] 215 | 216 | name = "Hammer" 217 | sku = 738594937 218 | 219 | [[products]] 220 | 221 | [[products]] 222 | 223 | name = "Nail" 224 | sku = 284758393 225 | color = "gray" 226 | 227 | 228 | # You can create nested arrays of tables as well. 229 | 230 | [[fruit]] 231 | name = "apple" 232 | 233 | [fruit.physical] 234 | color = "red" 235 | shape = "round" 236 | 237 | [[fruit.variety]] 238 | name = "red delicious" 239 | 240 | [[fruit.variety]] 241 | name = "granny smith" 242 | 243 | [[fruit]] 244 | name = "banana" 245 | 246 | [[fruit.variety]] 247 | name = "plantain" 248 | -------------------------------------------------------------------------------- /toml-examples/wrong.toml: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | ## Comment 3 | 4 | # Speak your mind with the hash symbol. They go from the symbol to the end of 5 | # the line. 6 | 7 | 8 | ################################################################################ 9 | ## Table 10 | 11 | # Tables (also known as hash tables or dictionaries) are collections of 12 | # key/value pairs. They appear in square brackets on a line by themselves. 13 | 14 | asdfpoin1!@ j= ;alskjfasdf 15 | 16 | [table] 17 | 18 | key = "value" # Yeah, you can do this. 19 | -------------------------------------------------------------------------------- /tomlutil.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | F22CB70B217B1FD600D1565D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = F22CB70A217B1FD600D1565D /* main.m */; }; 11 | F22CB714217B239300D1565D /* LMPTOMLSerialization.mm in Sources */ = {isa = PBXBuildFile; fileRef = F22CB713217B239300D1565D /* LMPTOMLSerialization.mm */; }; 12 | /* End PBXBuildFile section */ 13 | 14 | /* Begin PBXCopyFilesBuildPhase section */ 15 | F22CB705217B1FD600D1565D /* CopyFiles */ = { 16 | isa = PBXCopyFilesBuildPhase; 17 | buildActionMask = 2147483647; 18 | dstPath = /usr/share/man/man1/; 19 | dstSubfolderSpec = 0; 20 | files = ( 21 | ); 22 | runOnlyForDeploymentPostprocessing = 1; 23 | }; 24 | /* End PBXCopyFilesBuildPhase section */ 25 | 26 | /* Begin PBXFileReference section */ 27 | F22CB707217B1FD600D1565D /* tomlutil */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = tomlutil; sourceTree = BUILT_PRODUCTS_DIR; }; 28 | F22CB70A217B1FD600D1565D /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 29 | F22CB712217B239300D1565D /* LMPTOMLSerialization.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LMPTOMLSerialization.h; sourceTree = ""; }; 30 | F22CB713217B239300D1565D /* LMPTOMLSerialization.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = LMPTOMLSerialization.mm; sourceTree = ""; }; 31 | F23131842663608400AFE2BA /* toml.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = toml.hpp; path = submodules/toml11/toml.hpp; sourceTree = SOURCE_ROOT; }; 32 | F2657CF126F4D26B00C279B9 /* tomlutil */ = {isa = PBXFileReference; lastKnownFileType = text; path = tomlutil; sourceTree = BUILT_PRODUCTS_DIR; }; 33 | F2962202217F303B00C44751 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 34 | F2BC7534217B44D10056FAED /* LMP_toml_visitors.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LMP_toml_visitors.h; sourceTree = ""; }; 35 | F2FAE3732663A3DC0080F81A /* region.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = region.hpp; sourceTree = ""; }; 36 | F2FAE3742663A3DC0080F81A /* datetime.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = datetime.hpp; sourceTree = ""; }; 37 | F2FAE3752663A3DC0080F81A /* serializer.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = serializer.hpp; sourceTree = ""; }; 38 | F2FAE3762663A3DC0080F81A /* utility.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = utility.hpp; sourceTree = ""; }; 39 | F2FAE3772663A3DC0080F81A /* get.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = get.hpp; sourceTree = ""; }; 40 | F2FAE3782663A3DC0080F81A /* parser.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = parser.hpp; sourceTree = ""; }; 41 | F2FAE3792663A3DC0080F81A /* literal.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = literal.hpp; sourceTree = ""; }; 42 | F2FAE37A2663A3DC0080F81A /* macros.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = macros.hpp; sourceTree = ""; }; 43 | F2FAE37B2663A3DC0080F81A /* traits.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = traits.hpp; sourceTree = ""; }; 44 | F2FAE37C2663A3DC0080F81A /* comments.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = comments.hpp; sourceTree = ""; }; 45 | F2FAE37D2663A3DC0080F81A /* source_location.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = source_location.hpp; sourceTree = ""; }; 46 | F2FAE37E2663A3DC0080F81A /* combinator.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = combinator.hpp; sourceTree = ""; }; 47 | F2FAE37F2663A3DC0080F81A /* value.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = value.hpp; sourceTree = ""; }; 48 | F2FAE3802663A3DC0080F81A /* from.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = from.hpp; sourceTree = ""; }; 49 | F2FAE3812663A3DC0080F81A /* result.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = result.hpp; sourceTree = ""; }; 50 | F2FAE3822663A3DC0080F81A /* into.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = into.hpp; sourceTree = ""; }; 51 | F2FAE3832663A3DC0080F81A /* lexer.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = lexer.hpp; sourceTree = ""; }; 52 | F2FAE3842663A3DC0080F81A /* string.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = string.hpp; sourceTree = ""; }; 53 | F2FAE3852663A3DC0080F81A /* storage.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = storage.hpp; sourceTree = ""; }; 54 | F2FAE3862663A3DC0080F81A /* color.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = color.hpp; sourceTree = ""; }; 55 | F2FAE3872663A3DC0080F81A /* types.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = types.hpp; sourceTree = ""; }; 56 | F2FAE3882663A3DC0080F81A /* exception.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = exception.hpp; sourceTree = ""; }; 57 | /* End PBXFileReference section */ 58 | 59 | /* Begin PBXFrameworksBuildPhase section */ 60 | F22CB704217B1FD600D1565D /* Frameworks */ = { 61 | isa = PBXFrameworksBuildPhase; 62 | buildActionMask = 2147483647; 63 | files = ( 64 | ); 65 | runOnlyForDeploymentPostprocessing = 0; 66 | }; 67 | /* End PBXFrameworksBuildPhase section */ 68 | 69 | /* Begin PBXGroup section */ 70 | F22CB6FE217B1FD600D1565D = { 71 | isa = PBXGroup; 72 | children = ( 73 | F22CB709217B1FD600D1565D /* tomlutil */, 74 | F22CB708217B1FD600D1565D /* Products */, 75 | ); 76 | sourceTree = ""; 77 | }; 78 | F22CB708217B1FD600D1565D /* Products */ = { 79 | isa = PBXGroup; 80 | children = ( 81 | F22CB707217B1FD600D1565D /* tomlutil */, 82 | ); 83 | name = Products; 84 | sourceTree = ""; 85 | }; 86 | F22CB709217B1FD600D1565D /* tomlutil */ = { 87 | isa = PBXGroup; 88 | children = ( 89 | F22CB70A217B1FD600D1565D /* main.m */, 90 | F22CB712217B239300D1565D /* LMPTOMLSerialization.h */, 91 | F22CB713217B239300D1565D /* LMPTOMLSerialization.mm */, 92 | F2BC7534217B44D10056FAED /* LMP_toml_visitors.h */, 93 | F23131842663608400AFE2BA /* toml.hpp */, 94 | F2FAE3722663A3DC0080F81A /* toml */, 95 | F2962202217F303B00C44751 /* Info.plist */, 96 | F2657CF026F4D24E00C279B9 /* Products */, 97 | ); 98 | path = tomlutil; 99 | sourceTree = ""; 100 | }; 101 | F2657CF026F4D24E00C279B9 /* Products */ = { 102 | isa = PBXGroup; 103 | children = ( 104 | F2657CF126F4D26B00C279B9 /* tomlutil */, 105 | ); 106 | name = Products; 107 | sourceTree = ""; 108 | }; 109 | F2FAE3722663A3DC0080F81A /* toml */ = { 110 | isa = PBXGroup; 111 | children = ( 112 | F2FAE3732663A3DC0080F81A /* region.hpp */, 113 | F2FAE3742663A3DC0080F81A /* datetime.hpp */, 114 | F2FAE3752663A3DC0080F81A /* serializer.hpp */, 115 | F2FAE3762663A3DC0080F81A /* utility.hpp */, 116 | F2FAE3772663A3DC0080F81A /* get.hpp */, 117 | F2FAE3782663A3DC0080F81A /* parser.hpp */, 118 | F2FAE3792663A3DC0080F81A /* literal.hpp */, 119 | F2FAE37A2663A3DC0080F81A /* macros.hpp */, 120 | F2FAE37B2663A3DC0080F81A /* traits.hpp */, 121 | F2FAE37C2663A3DC0080F81A /* comments.hpp */, 122 | F2FAE37D2663A3DC0080F81A /* source_location.hpp */, 123 | F2FAE37E2663A3DC0080F81A /* combinator.hpp */, 124 | F2FAE37F2663A3DC0080F81A /* value.hpp */, 125 | F2FAE3802663A3DC0080F81A /* from.hpp */, 126 | F2FAE3812663A3DC0080F81A /* result.hpp */, 127 | F2FAE3822663A3DC0080F81A /* into.hpp */, 128 | F2FAE3832663A3DC0080F81A /* lexer.hpp */, 129 | F2FAE3842663A3DC0080F81A /* string.hpp */, 130 | F2FAE3852663A3DC0080F81A /* storage.hpp */, 131 | F2FAE3862663A3DC0080F81A /* color.hpp */, 132 | F2FAE3872663A3DC0080F81A /* types.hpp */, 133 | F2FAE3882663A3DC0080F81A /* exception.hpp */, 134 | ); 135 | name = toml; 136 | path = submodules/toml11/toml; 137 | sourceTree = SOURCE_ROOT; 138 | }; 139 | /* End PBXGroup section */ 140 | 141 | /* Begin PBXNativeTarget section */ 142 | F22CB706217B1FD600D1565D /* tomlutil */ = { 143 | isa = PBXNativeTarget; 144 | buildConfigurationList = F22CB70E217B1FD600D1565D /* Build configuration list for PBXNativeTarget "tomlutil" */; 145 | buildPhases = ( 146 | F22CB703217B1FD600D1565D /* Sources */, 147 | F22CB704217B1FD600D1565D /* Frameworks */, 148 | F22CB705217B1FD600D1565D /* CopyFiles */, 149 | ); 150 | buildRules = ( 151 | ); 152 | dependencies = ( 153 | ); 154 | name = tomlutil; 155 | productName = tomlutil; 156 | productReference = F22CB707217B1FD600D1565D /* tomlutil */; 157 | productType = "com.apple.product-type.tool"; 158 | }; 159 | /* End PBXNativeTarget section */ 160 | 161 | /* Begin PBXProject section */ 162 | F22CB6FF217B1FD600D1565D /* Project object */ = { 163 | isa = PBXProject; 164 | attributes = { 165 | LastUpgradeCheck = 1300; 166 | ORGANIZATIONNAME = "Lone Monkey Productions"; 167 | TargetAttributes = { 168 | F22CB706217B1FD600D1565D = { 169 | CreatedOnToolsVersion = 10.0; 170 | }; 171 | }; 172 | }; 173 | buildConfigurationList = F22CB702217B1FD600D1565D /* Build configuration list for PBXProject "tomlutil" */; 174 | compatibilityVersion = "Xcode 9.3"; 175 | developmentRegion = en; 176 | hasScannedForEncodings = 0; 177 | knownRegions = ( 178 | en, 179 | ); 180 | mainGroup = F22CB6FE217B1FD600D1565D; 181 | productRefGroup = F22CB708217B1FD600D1565D /* Products */; 182 | projectDirPath = ""; 183 | projectRoot = ""; 184 | targets = ( 185 | F22CB706217B1FD600D1565D /* tomlutil */, 186 | ); 187 | }; 188 | /* End PBXProject section */ 189 | 190 | /* Begin PBXSourcesBuildPhase section */ 191 | F22CB703217B1FD600D1565D /* Sources */ = { 192 | isa = PBXSourcesBuildPhase; 193 | buildActionMask = 2147483647; 194 | files = ( 195 | F22CB714217B239300D1565D /* LMPTOMLSerialization.mm in Sources */, 196 | F22CB70B217B1FD600D1565D /* main.m in Sources */, 197 | ); 198 | runOnlyForDeploymentPostprocessing = 0; 199 | }; 200 | /* End PBXSourcesBuildPhase section */ 201 | 202 | /* Begin XCBuildConfiguration section */ 203 | F22CB70C217B1FD600D1565D /* Debug */ = { 204 | isa = XCBuildConfiguration; 205 | buildSettings = { 206 | ALWAYS_SEARCH_USER_PATHS = NO; 207 | CLANG_ANALYZER_NONNULL = YES; 208 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 209 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; 210 | CLANG_CXX_LIBRARY = "libc++"; 211 | CLANG_ENABLE_MODULES = YES; 212 | CLANG_ENABLE_OBJC_ARC = YES; 213 | CLANG_ENABLE_OBJC_WEAK = YES; 214 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 215 | CLANG_WARN_BOOL_CONVERSION = YES; 216 | CLANG_WARN_COMMA = YES; 217 | CLANG_WARN_CONSTANT_CONVERSION = YES; 218 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 219 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 220 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 221 | CLANG_WARN_EMPTY_BODY = YES; 222 | CLANG_WARN_ENUM_CONVERSION = YES; 223 | CLANG_WARN_INFINITE_RECURSION = YES; 224 | CLANG_WARN_INT_CONVERSION = YES; 225 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 226 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 227 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 228 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 229 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 230 | CLANG_WARN_STRICT_PROTOTYPES = YES; 231 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 232 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 233 | CLANG_WARN_UNREACHABLE_CODE = YES; 234 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 235 | CODE_SIGN_IDENTITY = "-"; 236 | COPY_PHASE_STRIP = NO; 237 | DEBUG_INFORMATION_FORMAT = dwarf; 238 | ENABLE_STRICT_OBJC_MSGSEND = YES; 239 | ENABLE_TESTABILITY = YES; 240 | GCC_C_LANGUAGE_STANDARD = gnu11; 241 | GCC_DYNAMIC_NO_PIC = NO; 242 | GCC_NO_COMMON_BLOCKS = YES; 243 | GCC_OPTIMIZATION_LEVEL = 0; 244 | GCC_PREPROCESSOR_DEFINITIONS = ( 245 | "DEBUG=1", 246 | "$(inherited)", 247 | ); 248 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 249 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 250 | GCC_WARN_UNDECLARED_SELECTOR = YES; 251 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 252 | GCC_WARN_UNUSED_FUNCTION = YES; 253 | GCC_WARN_UNUSED_VARIABLE = YES; 254 | MACOSX_DEPLOYMENT_TARGET = 10.15; 255 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 256 | MTL_FAST_MATH = YES; 257 | ONLY_ACTIVE_ARCH = YES; 258 | SDKROOT = macosx; 259 | }; 260 | name = Debug; 261 | }; 262 | F22CB70D217B1FD600D1565D /* Release */ = { 263 | isa = XCBuildConfiguration; 264 | buildSettings = { 265 | ALWAYS_SEARCH_USER_PATHS = NO; 266 | CLANG_ANALYZER_NONNULL = YES; 267 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 268 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; 269 | CLANG_CXX_LIBRARY = "libc++"; 270 | CLANG_ENABLE_MODULES = YES; 271 | CLANG_ENABLE_OBJC_ARC = YES; 272 | CLANG_ENABLE_OBJC_WEAK = YES; 273 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 274 | CLANG_WARN_BOOL_CONVERSION = YES; 275 | CLANG_WARN_COMMA = YES; 276 | CLANG_WARN_CONSTANT_CONVERSION = YES; 277 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 278 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 279 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 280 | CLANG_WARN_EMPTY_BODY = YES; 281 | CLANG_WARN_ENUM_CONVERSION = YES; 282 | CLANG_WARN_INFINITE_RECURSION = YES; 283 | CLANG_WARN_INT_CONVERSION = YES; 284 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 285 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 286 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 287 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 288 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 289 | CLANG_WARN_STRICT_PROTOTYPES = YES; 290 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 291 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 292 | CLANG_WARN_UNREACHABLE_CODE = YES; 293 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 294 | CODE_SIGN_IDENTITY = "-"; 295 | COPY_PHASE_STRIP = NO; 296 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 297 | ENABLE_NS_ASSERTIONS = NO; 298 | ENABLE_STRICT_OBJC_MSGSEND = YES; 299 | GCC_C_LANGUAGE_STANDARD = gnu11; 300 | GCC_NO_COMMON_BLOCKS = YES; 301 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 302 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 303 | GCC_WARN_UNDECLARED_SELECTOR = YES; 304 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 305 | GCC_WARN_UNUSED_FUNCTION = YES; 306 | GCC_WARN_UNUSED_VARIABLE = YES; 307 | MACOSX_DEPLOYMENT_TARGET = 10.15; 308 | MTL_ENABLE_DEBUG_INFO = NO; 309 | MTL_FAST_MATH = YES; 310 | SDKROOT = macosx; 311 | }; 312 | name = Release; 313 | }; 314 | F22CB70F217B1FD600D1565D /* Debug */ = { 315 | isa = XCBuildConfiguration; 316 | buildSettings = { 317 | CODE_SIGN_STYLE = Automatic; 318 | CREATE_INFOPLIST_SECTION_IN_BINARY = YES; 319 | INFOPLIST_FILE = tomlutil/Info.plist; 320 | PRODUCT_BUNDLE_IDENTIFIER = productions.monkey.lone.tomlutil; 321 | PRODUCT_NAME = "$(TARGET_NAME)"; 322 | }; 323 | name = Debug; 324 | }; 325 | F22CB710217B1FD600D1565D /* Release */ = { 326 | isa = XCBuildConfiguration; 327 | buildSettings = { 328 | CODE_SIGN_STYLE = Automatic; 329 | CREATE_INFOPLIST_SECTION_IN_BINARY = YES; 330 | INFOPLIST_FILE = tomlutil/Info.plist; 331 | PRODUCT_BUNDLE_IDENTIFIER = productions.monkey.lone.tomlutil; 332 | PRODUCT_NAME = "$(TARGET_NAME)"; 333 | }; 334 | name = Release; 335 | }; 336 | /* End XCBuildConfiguration section */ 337 | 338 | /* Begin XCConfigurationList section */ 339 | F22CB702217B1FD600D1565D /* Build configuration list for PBXProject "tomlutil" */ = { 340 | isa = XCConfigurationList; 341 | buildConfigurations = ( 342 | F22CB70C217B1FD600D1565D /* Debug */, 343 | F22CB70D217B1FD600D1565D /* Release */, 344 | ); 345 | defaultConfigurationIsVisible = 0; 346 | defaultConfigurationName = Release; 347 | }; 348 | F22CB70E217B1FD600D1565D /* Build configuration list for PBXNativeTarget "tomlutil" */ = { 349 | isa = XCConfigurationList; 350 | buildConfigurations = ( 351 | F22CB70F217B1FD600D1565D /* Debug */, 352 | F22CB710217B1FD600D1565D /* Release */, 353 | ); 354 | defaultConfigurationIsVisible = 0; 355 | defaultConfigurationName = Release; 356 | }; 357 | /* End XCConfigurationList section */ 358 | }; 359 | rootObject = F22CB6FF217B1FD600D1565D /* Project object */; 360 | } 361 | -------------------------------------------------------------------------------- /tomlutil.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /tomlutil.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /tomlutil.xcodeproj/xcshareddata/xcschemes/tomlutil.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 66 | 67 | 70 | 71 | 74 | 75 | 78 | 79 | 82 | 83 | 86 | 87 | 88 | 89 | 95 | 97 | 103 | 104 | 105 | 106 | 108 | 109 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /tomlutil/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleIdentifier 6 | $(PRODUCT_BUNDLE_IDENTIFIER) 7 | CFBundleShortVersionString 8 | 2.0.0 9 | LMPToml11Version 10 | 3.7.1 11 | LSMinimumSystemVersion 12 | ${MACOSX_DEPLOYMENT_TARGET} 13 | 14 | 15 | -------------------------------------------------------------------------------- /tomlutil/LMPTOMLSerialization.h: -------------------------------------------------------------------------------- 1 | // LMPTOMLSerialization.h 2 | // 3 | // Created by dom on 10/20/18. 4 | // Copyright © 2018 Lone Monkey Productions. All rights reserved. 5 | // 6 | 7 | #import 8 | 9 | extern NSErrorDomain const LMPTOMLErrorDomain; 10 | 11 | extern NSString * const LMPTOMLErrorInfoKeyColorizedReason; 12 | 13 | extern NSString * const LMPTOMLOptionKeySourceFileURL; 14 | 15 | @interface LMPTOMLSerialization : NSObject 16 | 17 | /** 18 | Generate a Foundation Dictionary from TOML data. 19 | 20 | @param data NSData representing a TOML file 21 | @param options NSDictionary with options for the toml parsing 22 | @param error helpful information if the parsing fails 23 | @return NSDictionary representing the contents of the TOML file. Note that given dates will be represented as NSDateComponents, use +serializableObjectWithTOMLObject: to convert those to RFC3339 strings that can be used in JSON or PropertyList serializations. 24 | */ 25 | + (NSDictionary *)TOMLObjectWithData:(NSData *)data options:(NSDictionary *)options error:(NSError **)error; 26 | 27 | + (NSDictionary *)TOMLObjectWithData:(NSData *)data error:(NSError **)error; 28 | 29 | 30 | /** 31 | Generates NSData representation of the TOMLObject. The representation is UTF8 and can be stored directly as a TOML file. 32 | 33 | Note that roundtripping is a lossy opreation, as all comments are stripped, the allowed number formats are reduced to canonical ones and doubles might lose or gain unwanted precision. 34 | 35 | @param tomlObject Foundation Object consisting of TOML serializable objects. In addition to plist objects this contains NSDateComponent objects with y-m-d filled, h-m-s-[nanoseconds] filled, all fields filled, or all fields + timezone filled. 36 | @param error helpful information if generation fails 37 | @return NSData representing the object. 38 | */ 39 | + (NSData *)dataWithTOMLObject:(NSDictionary *)tomlObject error:(NSError **)error; 40 | 41 | /** 42 | Takes a Dictionary representing a TOML file and translates the NSDateComponents into RFC339 strings to be able to be serialized in JSON or PropertyLists 43 | 44 | @param tomlObject foundation dictionary consisting of TOML serializable objects. 45 | @return NSDictionary containing property list serializable objects. 46 | */ 47 | + (NSDictionary *)serializableObjectWithTOMLObject:(NSDictionary *)tomlObject; 48 | 49 | @end 50 | -------------------------------------------------------------------------------- /tomlutil/LMPTOMLSerialization.mm: -------------------------------------------------------------------------------- 1 | // LMPTOMLSerialization.m 2 | // 3 | // Created by dom on 10/20/18. 4 | // Copyright © 2018 Lone Monkey Productions. All rights reserved. 5 | 6 | #import "LMPTOMLSerialization.h" 7 | 8 | #define TOML11_COLORIZE_ERROR_MESSAGE 9 | 10 | // #define TOML11_PRESERVE_COMMENTS_BY_DEFAULT 11 | #include "toml.hpp" 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #include "LMP_toml_visitors.h" 20 | 21 | 22 | NSErrorDomain const LMPTOMLErrorDomain = @"productions.monkey.lone.TOML"; 23 | static NSInteger const LMPTOMLParseErrorCode = 7031; 24 | static NSInteger const LMPTOMLWriteErrorCode = 7001; 25 | 26 | NSString * const LMPTOMLErrorInfoKeyColorizedReason = @"ColoredFailureReason"; 27 | 28 | extern NSString * const LMPTOMLOptionKeySourceFileURL = @"sourceFileURL"; 29 | 30 | 31 | 32 | @implementation LMPTOMLSerialization 33 | 34 | + (NSDictionary *)TOMLObjectWithData:(NSData *)data error:(NSError **)error { 35 | return [self TOMLObjectWithData:data options:nil error:error]; 36 | } 37 | 38 | + (NSDictionary *)TOMLObjectWithData:(NSData *)data options:(NSDictionary *)options error:(NSError **)error { 39 | 40 | try { 41 | char *bytes = (char *)data.bytes; 42 | 43 | // deprecated but seems to do what I want, the implementation previously was missing seek capabilities. 44 | // see https://stackoverflow.com/questions/13059091/creating-an-input-stream-from-constant-memory#comment115305688_13059195 45 | std::strstreambuf sbuf(bytes, data.length); 46 | std::istream stream(&sbuf); 47 | 48 | // this works as well, probably does more copying than we need 49 | // std::istringstream stream(std::string(bytes, data.length)); 50 | 51 | NSURL *fileSourceURL = options[LMPTOMLOptionKeySourceFileURL]; 52 | 53 | NSString *filePath = fileSourceURL ? fileSourceURL.relativePath : @"anonymous input"; 54 | 55 | // parse 56 | const auto data = toml::parse(stream, filePath.UTF8String); 57 | 58 | // convert table to standard Objective-C objects 59 | toml_nsdictionary_writer dw; 60 | dw.visit(toml::get(data)); 61 | 62 | // std::cout << " --- " << data << " --- \n"; 63 | 64 | NSDictionary *result = dw.dictionary(); 65 | return result; 66 | 67 | } catch (const toml::exception& e) { 68 | if (error) { 69 | NSString *coloredWhat = @(e.what()); 70 | NSRegularExpression *regEx = [NSRegularExpression regularExpressionWithPattern:@"\033\\[\\d+m" options:0 error:nil]; 71 | NSString *cleanWhat = [regEx stringByReplacingMatchesInString:coloredWhat options:0 range:NSMakeRange(0, coloredWhat.length) withTemplate:@""]; 72 | 73 | *error = [NSError errorWithDomain:LMPTOMLErrorDomain 74 | code:LMPTOMLParseErrorCode 75 | userInfo:@{ 76 | NSLocalizedDescriptionKey : @"Input TOML could not be parsed", 77 | NSLocalizedFailureReasonErrorKey : cleanWhat, 78 | LMPTOMLErrorInfoKeyColorizedReason : coloredWhat, 79 | }]; 80 | } 81 | return nil; 82 | } 83 | } 84 | 85 | static toml::value DictionaryToTable(NSDictionary *dict) { 86 | toml::table table; 87 | for (NSString *key in dict.keyEnumerator) { 88 | auto cppKey = std::string(key.UTF8String); 89 | auto cppValue = ObjectToValue(dict[key]); 90 | table.insert(std::make_pair(cppKey, cppValue)); 91 | } 92 | return toml::value(table); 93 | } 94 | 95 | static toml::value ArrayToArray(NSArray *array) { 96 | toml::array result; 97 | for (id value in array) { 98 | result.push_back(ObjectToValue(value)); 99 | } 100 | return toml::value(result); 101 | } 102 | 103 | static toml::value ObjectToValue(id objectValue) { 104 | if ([objectValue isKindOfClass:[NSString class]]) { 105 | return toml::value(std::string([objectValue UTF8String])); 106 | } else if ([objectValue isKindOfClass:[NSNumber class]]) { 107 | if ((__bridge CFBooleanRef)objectValue == kCFBooleanTrue || 108 | (__bridge CFBooleanRef)objectValue == kCFBooleanFalse) { 109 | return toml::value((__bridge CFBooleanRef)objectValue == kCFBooleanTrue); 110 | } else if (CFNumberIsFloatType((__bridge CFNumberRef)objectValue)) { 111 | return toml::value([objectValue doubleValue]); 112 | } else { 113 | return toml::value([objectValue longLongValue]); 114 | } 115 | } else if ([objectValue isKindOfClass:[NSDictionary class]]) { 116 | return DictionaryToTable(objectValue); 117 | } else if ([objectValue isKindOfClass:[NSArray class]]) { 118 | return ArrayToArray(objectValue); 119 | } else if ([objectValue isKindOfClass:[NSDateComponents class]]) { 120 | NSDateComponents *dc = objectValue; 121 | 122 | if (dc.year == NSDateComponentUndefined) { 123 | toml::local_time lt; 124 | if (dc.hour != NSDateComponentUndefined) { 125 | lt.hour = (int)dc.hour; 126 | } 127 | if (dc.minute != NSDateComponentUndefined) { 128 | lt.minute = (int)dc.minute; 129 | } 130 | if (dc.second != NSDateComponentUndefined) { 131 | lt.second = (int)dc.second; 132 | } 133 | if (dc.nanosecond != NSDateComponentUndefined) { 134 | lt.nanosecond = (int)(dc.nanosecond % 1000); 135 | long ms = dc.nanosecond / 1000; 136 | lt.microsecond = (int)(ms % 1000); 137 | lt.millisecond = (int)ms / 1000; 138 | } 139 | return toml::value(lt); 140 | } else { 141 | toml::local_date ld((int16_t)dc.year, (toml::month_t)(dc.month - 1), (uint8_t)dc.day); 142 | if (dc.second == NSDateComponentUndefined) { 143 | return toml::value(ld); 144 | } 145 | else { 146 | toml::local_time lt; 147 | if (dc.hour != NSDateComponentUndefined) { 148 | lt.hour = (int)dc.hour; 149 | } 150 | if (dc.minute != NSDateComponentUndefined) { 151 | lt.minute = (int)dc.minute; 152 | } 153 | if (dc.second != NSDateComponentUndefined) { 154 | lt.second = (int)dc.second; 155 | } 156 | if (dc.nanosecond != NSDateComponentUndefined) { 157 | lt.nanosecond = (int)(dc.nanosecond % 1000); 158 | long ms = dc.nanosecond / 1000; 159 | lt.microsecond = (int)(ms % 1000); 160 | lt.millisecond = (int)ms / 1000; 161 | } 162 | 163 | toml::local_datetime ldt(ld, lt); 164 | if (!dc.timeZone) { 165 | return toml::value(ldt); 166 | } else { 167 | int minutesFromGMT = (int)[dc.timeZone secondsFromGMT] / 60; 168 | toml::offset_datetime dt(ldt, toml::time_offset(minutesFromGMT / 60, minutesFromGMT % 60)); 169 | return toml::value(dt); 170 | } 171 | } 172 | } 173 | 174 | } else { 175 | return toml::value("--- unkonwn ---"); 176 | } 177 | } 178 | 179 | + (NSData *)dataWithTOMLObject:(NSDictionary *)tomlObject error:(NSError **)error { 180 | try { 181 | // __auto_type dict = @{@"test1" : @1, @"testBool" : @YES, @"testString" : @"some string", @"test3.14" : @(M_PI), 182 | // @"subtable" : @{ @"two" : @"zwo"}, 183 | // @"some array" : @[@1, @2, @3], 184 | // @"some other array" : @[@"oans", @"zwoa"], 185 | // }; 186 | 187 | auto root = DictionaryToTable(tomlObject); 188 | std::stringstream s(""); 189 | // setw makes inline tables if they fit 190 | // std::setprecision(7) e.g. would also set the float precision. we might want that for the future 191 | s << std::setw(100) << root << std::endl; 192 | std::string str = s.str(); 193 | NSData *result = [NSData dataWithBytes:str.data() length:str.length()]; 194 | return result; 195 | } catch (const toml::exception& e) { 196 | if (error) { 197 | NSString *coloredWhat = @(e.what()); 198 | NSRegularExpression *regEx = [NSRegularExpression regularExpressionWithPattern:@"\033\\[\\d+m" options:0 error:nil]; 199 | NSString *cleanWhat = [regEx stringByReplacingMatchesInString:coloredWhat options:0 range:NSMakeRange(0, coloredWhat.length) withTemplate:@""]; 200 | 201 | *error = [NSError errorWithDomain:LMPTOMLErrorDomain 202 | code:LMPTOMLWriteErrorCode 203 | userInfo:@{ 204 | NSLocalizedDescriptionKey : @"Input objects could not be converted to TOML", 205 | NSLocalizedFailureReasonErrorKey : cleanWhat, 206 | LMPTOMLErrorInfoKeyColorizedReason : coloredWhat, 207 | }]; 208 | } 209 | return nil; 210 | } 211 | } 212 | 213 | static NSString *TOMLTimeStringFromComponents(NSDateComponents *dc) { 214 | NSMutableString *result = [NSMutableString new]; 215 | if (dc.year != NSDateComponentUndefined) { 216 | [result appendFormat:@"%04d-%02d-%02d", (int)dc.year, (int)dc.month, (int)dc.day]; 217 | } 218 | if (dc.minute != NSDateComponentUndefined) { 219 | if (result.length > 0) { 220 | [result appendString:@"T"]; 221 | } 222 | [result appendFormat:@"%02d:%02d:%02d", (int)dc.hour, (int)dc.minute, (int)dc.second]; 223 | if (dc.nanosecond != NSDateComponentUndefined && dc.nanosecond > 0) { 224 | [result appendFormat:@".%06d", (int)(dc.nanosecond / 1000)]; 225 | } 226 | if (dc.timeZone) { 227 | int minuteOffset = (int)dc.timeZone.secondsFromGMT / 60; 228 | if (minuteOffset == 0) { 229 | [result appendString:@"Z"]; 230 | } else { 231 | [result appendString:minuteOffset > 0 ? @"+" : @"-"]; 232 | [result appendFormat:@"%02d:%02d", ABS(minuteOffset) / 60, ABS(minuteOffset) % 60]; 233 | } 234 | } 235 | } 236 | return result; 237 | } 238 | 239 | static NSArray *serializableArray(NSArray *array) { 240 | NSMutableArray *result = [array mutableCopy]; 241 | NSUInteger index = result.count; 242 | while (index-- != 0) { 243 | id value = result[index]; 244 | if ([value isKindOfClass:[NSArray class]]) { 245 | result[index] = serializableArray(value); 246 | } else if ([value isKindOfClass:[NSDictionary class]]) { 247 | result[index] = [LMPTOMLSerialization serializableObjectWithTOMLObject:value]; 248 | } else if ([value isKindOfClass:[NSDateComponents class]]) { 249 | result[index] = TOMLTimeStringFromComponents(value); 250 | } 251 | } 252 | return result; 253 | } 254 | 255 | + (NSDictionary *)serializableObjectWithTOMLObject:(NSDictionary *)tomlObject { 256 | NSMutableDictionary *result = [tomlObject mutableCopy]; 257 | for (NSString *key in result.allKeys) { 258 | id value = result[key]; 259 | if ([value isKindOfClass:[NSDictionary class]]) { 260 | result[key] = [self serializableObjectWithTOMLObject:value]; 261 | } else if ([value isKindOfClass:[NSArray class]]) { 262 | result[key] = serializableArray(value); 263 | } else if ([value isKindOfClass:[NSDateComponents class]]) { 264 | result[key] = TOMLTimeStringFromComponents(value); 265 | } 266 | } 267 | return result; 268 | } 269 | 270 | @end 271 | -------------------------------------------------------------------------------- /tomlutil/LMP_toml_visitors.h: -------------------------------------------------------------------------------- 1 | // 2 | // LMP_cpptoml_visitors.h 3 | // tomlutil 4 | // 5 | // Created by dom on 10/20/18. 6 | // Copyright © 2018 Lone Monkey Productions. All rights reserved. 7 | // 8 | 9 | #ifndef LMP_toml_visitors_h 10 | #define LMP_toml_visitors_h 11 | #include "toml.hpp" 12 | /** 13 | * A visitor for toml objects that writes to an NSMutableDictionary 14 | */ 15 | class toml_nsdictionary_writer { 16 | public: 17 | toml_nsdictionary_writer() { 18 | currentDictionary_ = resultContainer_ = [NSMutableDictionary new]; 19 | currentKey_ = @"TOMLRoot"; 20 | } 21 | 22 | NSDictionary *dictionary() { 23 | return [resultContainer_[@"TOMLRoot"] copy]; 24 | } 25 | 26 | void visit(const toml::string& v) { 27 | id value = [NSString stringWithUTF8String: static_cast(v).c_str()]; 28 | writeValue(value); 29 | } 30 | 31 | void visit(const toml::local_date& v) { 32 | id value = ({ 33 | NSDateComponents *result = [NSDateComponents new]; 34 | result.year = v.year; 35 | result.month = v.month + 1; 36 | result.day = v.day; 37 | result; 38 | }); 39 | writeValue(value); 40 | } 41 | 42 | void visit(const toml::local_time& v) { 43 | id value = ({ 44 | NSDateComponents *result = [NSDateComponents new]; 45 | result.hour = v.hour; 46 | result.minute = v.minute; 47 | result.second = v.second; 48 | result.nanosecond = v.millisecond * 1000000 + v.microsecond * 1000 + v.nanosecond; 49 | result; 50 | }); 51 | // NSLog(@"%@", [value debugDescription]); 52 | writeValue(value); 53 | } 54 | 55 | // untested, need to be looked again to have proper roundtrip I guess. 56 | void visit(const toml::local_datetime& v) { 57 | id value = ({ 58 | NSDateComponents *result = [NSDateComponents new]; 59 | result.year = v.date.year; 60 | result.month = v.date.month + 1; 61 | result.day = v.date.day; 62 | result.hour = v.time.hour; 63 | result.minute = v.time.minute; 64 | result.second = v.time.second; 65 | result.nanosecond = v.time.millisecond * 1000000 + v.time.microsecond * 1000 + v.time.nanosecond; 66 | result; 67 | }); 68 | writeValue(value); 69 | } 70 | 71 | void visit(const toml::offset_datetime& v) { 72 | id value = ({ 73 | NSDateComponents *result = [NSDateComponents new]; 74 | result.year = v.date.year; 75 | result.month = v.date.month + 1; 76 | result.day = v.date.day; 77 | result.hour = v.time.hour; 78 | result.minute = v.time.minute; 79 | result.second = v.time.second; 80 | result.nanosecond = v.time.millisecond * 1000000 + v.time.microsecond * 1000 + v.time.nanosecond; 81 | result.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:(v.offset.hour * 60 + v.offset.minute) * 60]; 82 | result; 83 | }); 84 | writeValue(value); 85 | } 86 | 87 | 88 | void visit(const toml::array& t) { 89 | withContainerIsDictionary( NO, ^{ 90 | for (auto it = t.begin(); it != t.end(); it++) { 91 | visit(*it); 92 | } 93 | }); 94 | } 95 | 96 | 97 | void visit(const toml::table& t) { 98 | withContainerIsDictionary( YES, ^{ 99 | for (auto it = t.begin(); it != t.end(); it++) { 100 | currentKey_ = [NSString stringWithUTF8String: it->first.c_str()]; 101 | visit(it->second); 102 | } 103 | }); 104 | } 105 | 106 | void visit(const toml::value& dunno) { 107 | switch (dunno.type()) { 108 | case toml::value_t::boolean: 109 | writeValue(dunno.as_boolean() ? @YES : @NO); 110 | break; 111 | case toml::value_t::integer: 112 | writeValue(@(dunno.as_integer())); 113 | break; 114 | case toml::value_t::floating: 115 | writeValue(@(dunno.as_floating())); 116 | break; 117 | case toml::value_t::string : 118 | visit(toml::get(dunno)); 119 | break; 120 | case toml::value_t::array : 121 | visit(toml::get(dunno)); 122 | break; 123 | case toml::value_t::table : 124 | visit(toml::get(dunno)); 125 | break; 126 | case toml::value_t::local_date : 127 | visit(toml::get(dunno)); 128 | break; 129 | case toml::value_t::local_time : 130 | visit(toml::get(dunno)); 131 | break; 132 | case toml::value_t::local_datetime : 133 | visit(toml::get(dunno)); 134 | break; 135 | case toml::value_t::offset_datetime : 136 | visit(toml::get(dunno)); 137 | break; 138 | default: 139 | // have all types now, should not happen anymore™ 140 | std::cout << "- '" << dunno << "' --------- not yet implemented" << std::endl; 141 | break; 142 | } 143 | } 144 | 145 | private: 146 | 147 | void withContainerIsDictionary(BOOL isDictionary, dispatch_block_t stuff) { 148 | NSMutableArray *array = currentArray_; 149 | NSMutableDictionary *dict = currentDictionary_; 150 | NSString *key = currentKey_; 151 | 152 | if (isDictionary) { 153 | NSMutableDictionary *contextDictionary = [NSMutableDictionary new]; 154 | writeValue(contextDictionary); 155 | 156 | currentDictionary_ = contextDictionary; 157 | currentArray_ = nil; 158 | } else { 159 | NSMutableArray *contextArray = [NSMutableArray new]; 160 | writeValue(contextArray); 161 | 162 | currentArray_ = contextArray; 163 | } 164 | 165 | stuff(); 166 | 167 | currentKey_ = key; 168 | currentArray_ = array; 169 | currentDictionary_ = dict; 170 | } 171 | 172 | void writeValue(id value) { 173 | if (currentArray_) { 174 | [currentArray_ addObject:value]; 175 | } else { 176 | currentDictionary_[currentKey_] = value; 177 | } 178 | } 179 | 180 | NSMutableDictionary *resultContainer_; 181 | NSMutableDictionary *currentDictionary_; 182 | NSString *currentKey_; 183 | NSMutableArray *currentArray_; 184 | }; 185 | 186 | #endif /* LMP_toml_visitors_h */ 187 | -------------------------------------------------------------------------------- /tomlutil/main.m: -------------------------------------------------------------------------------- 1 | // main.m 2 | // tomlutil 3 | // 4 | // Created by dom on 10/20/18. 5 | // Copyright © 2018 Lone Monkey Productions. All rights reserved. 6 | 7 | @import Foundation; 8 | #import "LMPTOMLSerialization.h" 9 | 10 | typedef CF_ENUM(int, LMPFileFormat) { 11 | FileFormatUnknown = 0, 12 | FileFormatPlistBinary = kCFPropertyListBinaryFormat_v1_0, 13 | FileFormatPlistXML = kCFPropertyListXMLFormat_v1_0, 14 | // FileFormatPlistOpenStep = kCFPropertyListOpenStepFormat, cannot write anymore anyways 15 | FileFormatJSON = 801, 16 | FileFormatTOML = 901, 17 | FileFormatNone = 99991, 18 | }; 19 | 20 | void showErrorAndHalt(NSError *error, NSData *inputData) { 21 | if (!error) { 22 | return; 23 | } 24 | char *bold=""; 25 | char *stopBold=""; 26 | 27 | BOOL supportsAnsiColor = isatty([[NSFileHandle fileHandleWithStandardError] fileDescriptor]); 28 | 29 | if (supportsAnsiColor) { 30 | NSString *term = [[NSProcessInfo processInfo] environment][@"TERM"] ; 31 | if (term && ( 32 | [term rangeOfString:@"color" options:NSCaseInsensitiveSearch].location != NSNotFound || 33 | [term rangeOfString:@"ansi" options:NSCaseInsensitiveSearch].location != NSNotFound 34 | ) 35 | ){ 36 | } else { 37 | supportsAnsiColor = NO; 38 | } 39 | 40 | } 41 | if (supportsAnsiColor) { 42 | bold="\033[1m"; 43 | stopBold="\033[0m"; 44 | } 45 | fputs(bold, stderr); 46 | fputs("🚫 ",stderr); 47 | fputs(error.localizedDescription.UTF8String, stderr); 48 | fputs(stopBold, stderr); 49 | fputs("\n", stderr); 50 | 51 | if (supportsAnsiColor && error.userInfo[LMPTOMLErrorInfoKeyColorizedReason]) { 52 | fputs([error.userInfo[LMPTOMLErrorInfoKeyColorizedReason] UTF8String], stderr); 53 | } else { 54 | fputs(error.localizedFailureReason.UTF8String, stderr); 55 | } 56 | fputs("\n", stderr); 57 | 58 | exit(EXIT_FAILURE); 59 | } 60 | 61 | static LMPFileFormat formatFromFilename(NSString *filename) { 62 | // Extensions 63 | NSString *extension = [filename.pathExtension lowercaseString]; 64 | LMPFileFormat result = [@{ 65 | @".toml" : @(FileFormatTOML), 66 | @".json" : @(FileFormatJSON), 67 | @".js" : @(FileFormatJSON), 68 | @".plist" : @(FileFormatPlistXML), 69 | }[extension] intValue]; 70 | return result; 71 | } 72 | 73 | static NSString *version_string(void) { 74 | NSString *shortVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"]; 75 | // NSString *bundleVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]; 76 | NSString *toml11Version = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"LMPToml11Version"]; 77 | 78 | NSString *versionLineString = [NSString stringWithFormat:@"tomlutil v%@ (toml11 v%@)", shortVersion, toml11Version]; 79 | return versionLineString; 80 | } 81 | 82 | static void show_help(BOOL versionOnly) { 83 | 84 | puts(version_string().UTF8String); 85 | 86 | if (versionOnly) { 87 | return; 88 | } 89 | 90 | puts(""); 91 | puts("Usage: tomlutil [-f json|xml1|binary1|toml] file [outputfile]\n\n" 92 | "A file of '-' reads from stdin. Can read json, plists and toml. Output defaults to stdout.\n" 93 | "-f format Output format. One of json, xml1, binary1, toml. Defaults to toml.\n" 94 | "-lint Just lint with toml11, no output."); 95 | } 96 | 97 | int main(int argc, const char * argv[]) { 98 | @autoreleasepool { 99 | 100 | BOOL showHelp = argc <= 1; 101 | 102 | if (!showHelp && argc == 2) { 103 | NSString *argument = [[NSProcessInfo processInfo].arguments lastObject]; 104 | NSArray *helpArgs = @[@"-h",@"-help",@"--help"]; 105 | if ([helpArgs containsObject:argument]) { 106 | showHelp = YES; 107 | } else if ([@[@"-v",@"-version",@"--version"] containsObject:argument]) { 108 | show_help(YES); 109 | return EXIT_SUCCESS; 110 | } 111 | } 112 | 113 | if (showHelp) { 114 | show_help(NO); 115 | return EXIT_SUCCESS; 116 | } 117 | 118 | if (argc > 1) { 119 | __block LMPFileFormat inputFormat = FileFormatUnknown; 120 | __block LMPFileFormat outputFormat = FileFormatUnknown; // default to toml 121 | __block NSString *filenameString; 122 | __block NSString *outputFilenameString; 123 | 124 | __block NSString *flag = nil; 125 | 126 | NSMutableArray *argumentsArray = [[NSProcessInfo processInfo].arguments mutableCopy]; 127 | argumentsArray[0] = @"tomlutil"; 128 | 129 | [[NSProcessInfo processInfo].arguments enumerateObjectsUsingBlock:^(NSString *argument, NSUInteger index, BOOL *stop) { 130 | if (index > 0) { 131 | if (flag) { 132 | if ([flag isEqualToString:@"-f"]) { 133 | if (outputFormat != FileFormatNone) { 134 | outputFormat = [@{ 135 | @"toml" : @(FileFormatTOML), 136 | 137 | @"json" : @(FileFormatJSON), 138 | 139 | @"plist" : @(FileFormatPlistXML), 140 | @"xml" : @(FileFormatPlistXML), 141 | @"binary" : @(FileFormatPlistBinary), 142 | @"xml1" : @(FileFormatPlistXML), 143 | @"binary1" : @(FileFormatPlistBinary), 144 | }[argument] intValue] ?: outputFormat; 145 | } 146 | } 147 | flag = nil; 148 | } else { 149 | if ([argument hasPrefix:@"-f"]) { 150 | flag = argument; 151 | } else if ([argument isEqualToString:@"-lint"]) { 152 | outputFormat = FileFormatNone; 153 | inputFormat = FileFormatTOML; 154 | } else { 155 | if (!filenameString) { 156 | filenameString = argument; 157 | } else if (!outputFilenameString) { 158 | outputFilenameString = argument; 159 | } else { 160 | showErrorAndHalt([NSError errorWithDomain:NSPOSIXErrorDomain code:999 userInfo:@{ 161 | NSLocalizedDescriptionKey : @"Too many file arguments.", 162 | NSLocalizedFailureReasonErrorKey : [argumentsArray componentsJoinedByString:@" "], 163 | }], nil); 164 | } 165 | } 166 | } 167 | } 168 | }]; 169 | 170 | NSData *inputData = (!filenameString || [filenameString isEqualToString:@"-"]) ? ({ 171 | [[NSFileHandle fileHandleWithStandardInput] readDataToEndOfFile]; 172 | }) : ({ 173 | NSURL *fileURL =[NSURL fileURLWithPath:[filenameString stringByStandardizingPath]]; 174 | [NSData dataWithContentsOfURL:fileURL]; 175 | }); 176 | 177 | 178 | NSError *error; 179 | NSDictionary *tomlObject; 180 | NSDictionary *dictionaryObject; 181 | 182 | if (inputData.length > 0) { 183 | 184 | // determine input format 185 | if (inputFormat == FileFormatUnknown) { 186 | inputFormat = formatFromFilename(filenameString); 187 | } 188 | 189 | if (inputFormat == FileFormatUnknown) { 190 | // check first bytes 191 | char *bytes = (char *)inputData.bytes; 192 | if (bytes[0] == '<' && 193 | bytes[1] == '?') { 194 | inputFormat = FileFormatPlistXML; 195 | } else if ([inputData length] > 6 && [[[NSString alloc] initWithBytes:bytes length:6 encoding:NSASCIIStringEncoding] isEqualToString:@"bplist"]) { 196 | inputFormat = FileFormatPlistBinary; 197 | } else if (bytes[0] == '{') { 198 | inputFormat = FileFormatJSON; 199 | } 200 | } 201 | 202 | if (inputFormat == FileFormatUnknown) { 203 | inputFormat = FileFormatTOML; // default to TOML if nothing else was found 204 | } 205 | 206 | if (inputFormat == FileFormatTOML) { 207 | tomlObject = [LMPTOMLSerialization TOMLObjectWithData:inputData options:filenameString ? @{LMPTOMLOptionKeySourceFileURL : [NSURL fileURLWithPath:filenameString]} : nil error:&error]; 208 | showErrorAndHalt(error, inputData); 209 | } else if (inputFormat == FileFormatJSON) { 210 | NSJSONReadingOptions opts = NSJSONReadingFragmentsAllowed; 211 | if (@available(macOS 12.0, *)) { 212 | opts |= NSJSONReadingJSON5Allowed; 213 | } 214 | dictionaryObject = [NSJSONSerialization JSONObjectWithData:inputData options:opts error:&error]; 215 | showErrorAndHalt(error, inputData); 216 | } else { 217 | dictionaryObject = [NSPropertyListSerialization propertyListWithData:inputData options:0 format:nil error:&error]; 218 | showErrorAndHalt(error, inputData); 219 | } 220 | } else if (inputData) { // empty input produces empty output 221 | dictionaryObject = @{}; 222 | } else { 223 | showErrorAndHalt([NSError errorWithDomain:NSPOSIXErrorDomain code:999 userInfo:@{ 224 | NSLocalizedDescriptionKey : [NSString stringWithFormat:@"Failure to read input (%@)", filenameString ?: @"stdin"], 225 | NSLocalizedFailureReasonErrorKey : [argumentsArray componentsJoinedByString:@" "], 226 | }], nil); 227 | } 228 | 229 | if (outputFormat == FileFormatNone) { 230 | exit(EXIT_SUCCESS); 231 | } else if (outputFormat == FileFormatUnknown) { 232 | if (outputFilenameString) { 233 | outputFormat = formatFromFilename(outputFilenameString); 234 | } 235 | if (outputFormat == FileFormatUnknown) { 236 | outputFormat = FileFormatTOML; 237 | } 238 | } 239 | 240 | NSData *outputData; 241 | if (outputFormat == FileFormatTOML) { 242 | outputData = [LMPTOMLSerialization dataWithTOMLObject:tomlObject ?: dictionaryObject error:&error]; 243 | showErrorAndHalt(error, inputData); 244 | } else { 245 | if (!dictionaryObject) { 246 | dictionaryObject = [LMPTOMLSerialization serializableObjectWithTOMLObject:tomlObject]; 247 | } 248 | @try { 249 | if (outputFormat == FileFormatJSON) { 250 | NSJSONWritingOptions options = NSJSONWritingPrettyPrinted; 251 | if (@available(macOS 10.13, *)) { 252 | options |= NSJSONWritingSortedKeys; 253 | } 254 | options |= NSJSONWritingWithoutEscapingSlashes; 255 | 256 | outputData = [NSJSONSerialization dataWithJSONObject:dictionaryObject options:options error:&error]; 257 | showErrorAndHalt(error, inputData); 258 | } else { 259 | outputData = [NSPropertyListSerialization dataWithPropertyList:dictionaryObject format:(NSPropertyListFormat)outputFormat options:0 error:&error]; 260 | showErrorAndHalt(error, inputData); 261 | } 262 | } 263 | @catch (NSException *exception) { 264 | error = [NSError errorWithDomain:NSCocoaErrorDomain code:1234 userInfo:@{ 265 | NSLocalizedDescriptionKey : [NSString stringWithFormat:@"Exception in Foundation while serializing: %@", exception.name], 266 | NSLocalizedFailureReasonErrorKey : exception.reason, 267 | } 268 | ]; 269 | showErrorAndHalt(error, nil); 270 | } 271 | } 272 | 273 | if (outputFilenameString) { 274 | NSError *error; 275 | [outputData writeToURL:[NSURL fileURLWithPath:[outputFilenameString stringByStandardizingPath]] options:NSDataWritingAtomic error:&error]; 276 | showErrorAndHalt(error, nil); 277 | } else { 278 | [[NSFileHandle fileHandleWithStandardOutput] writeData:outputData]; 279 | } 280 | } 281 | } 282 | return EXIT_SUCCESS; 283 | } 284 | --------------------------------------------------------------------------------