├── .gitignore ├── .slather.yml ├── .travis.yml ├── Config.podspec ├── Config.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ └── xcschemes │ └── Config.xcscheme ├── LICENSE ├── Package.swift ├── README.md ├── Sources └── Config │ ├── Config.swift │ ├── Extensions.swift │ ├── Info.plist │ ├── JSON.swift │ ├── JSONValue.swift │ └── Messages.swift └── Tests └── ConfigTests ├── ConfigFiles ├── invalidConfig.json └── mainConfig.json ├── Info.plist ├── Keys.swift ├── LiveConfigTests.swift ├── LocalConfigInvalidTests.swift └── LocalConfigTests.swift /.gitignore: -------------------------------------------------------------------------------- 1 | ### CocoaPods ### 2 | ## CocoaPods GitIgnore Template 3 | 4 | # CocoaPods - Only use to conserve bandwidth / Save time on Pushing 5 | # - Also handy if you have a large number of dependant pods 6 | # - AS PER https://guides.cocoapods.org/using/using-cocoapods.html NEVER IGNORE THE LOCK FILE 7 | Pods/ 8 | 9 | ### macOS ### 10 | # General 11 | .DS_Store 12 | .AppleDouble 13 | .LSOverride 14 | 15 | # Icon must end with two \r 16 | Icon 17 | 18 | 19 | # Thumbnails 20 | ._* 21 | 22 | # Files that might appear in the root of a volume 23 | .DocumentRevisions-V100 24 | .fseventsd 25 | .Spotlight-V100 26 | .TemporaryItems 27 | .Trashes 28 | .VolumeIcon.icns 29 | .com.apple.timemachine.donotpresent 30 | 31 | # Directories potentially created on remote AFP share 32 | .AppleDB 33 | .AppleDesktop 34 | Network Trash Folder 35 | Temporary Items 36 | .apdisk 37 | 38 | ### Swift ### 39 | # Xcode 40 | # 41 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 42 | 43 | ## User settings 44 | xcuserdata/ 45 | 46 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 47 | *.xcscmblueprint 48 | *.xccheckout 49 | 50 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 51 | build/ 52 | DerivedData/ 53 | *.moved-aside 54 | *.pbxuser 55 | !default.pbxuser 56 | *.mode1v3 57 | !default.mode1v3 58 | *.mode2v3 59 | !default.mode2v3 60 | *.perspectivev3 61 | !default.perspectivev3 62 | 63 | ## Obj-C/Swift specific 64 | *.hmap 65 | 66 | ## App packaging 67 | *.ipa 68 | *.dSYM.zip 69 | *.dSYM 70 | 71 | ## Playgrounds 72 | timeline.xctimeline 73 | playground.xcworkspace 74 | 75 | # Swift Package Manager 76 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 77 | # Packages/ 78 | # Package.pins 79 | # Package.resolved 80 | # *.xcodeproj 81 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 82 | # hence it is not needed unless you have added a package configuration file to your project 83 | # .swiftpm 84 | 85 | .build/ 86 | 87 | # CocoaPods 88 | # We recommend against adding the Pods directory to your .gitignore. However 89 | # you should judge for yourself, the pros and cons are mentioned at: 90 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 91 | # Pods/ 92 | # Add this line if you want to avoid checking in source code from the Xcode workspace 93 | # *.xcworkspace 94 | 95 | # Carthage 96 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 97 | # Carthage/Checkouts 98 | 99 | Carthage/Build/ 100 | 101 | # Accio dependency management 102 | Dependencies/ 103 | .accio/ 104 | 105 | # fastlane 106 | # It is recommended to not store the screenshots in the git repo. 107 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 108 | # For more information about the recommended setup visit: 109 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 110 | 111 | fastlane/report.xml 112 | fastlane/Preview.html 113 | fastlane/screenshots/**/*.png 114 | fastlane/test_output 115 | 116 | # Code Injection 117 | # After new code Injection tools there's a generated folder /iOSInjectionProject 118 | # https://github.com/johnno1962/injectionforxcode 119 | 120 | iOSInjectionProject/ 121 | 122 | ### SwiftPackageManager ### 123 | Packages 124 | xcuserdata 125 | 126 | 127 | ### Xcode ### 128 | # Xcode 129 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 130 | 131 | 132 | 133 | 134 | ## Gcc Patch 135 | /*.gcno 136 | 137 | # End of https://www.toptal.com/developers/gitignore/api/swift,swiftpackagemanager,xcode,macos,cocoapods -------------------------------------------------------------------------------- /.slather.yml: -------------------------------------------------------------------------------- 1 | coverage_service: cobertura_xml 2 | xcodeproj: Config.xcodeproj 3 | scheme: Config 4 | output_directory: reports 5 | ignore: 6 | - ConfigTests/* -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: swift 2 | osx_image: xcode13.1 3 | xcode_workspace: Config.xcworkspace 4 | xcode_scheme: Config 5 | 6 | script: 7 | - xcodebuild clean build -sdk iphonesimulator -workspace Config.xcworkspace -scheme Config CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO 8 | 9 | -------------------------------------------------------------------------------- /Config.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |spec| 2 | 3 | spec.name = "Config" 4 | spec.version = "2.0.0" 5 | spec.summary = "Use a JSON file as an app configuration, reach keys with dot notation, written in Swift" 6 | 7 | spec.description = <<-DESC 8 | Config is a framework written in Swift that makes it easy for you to use JSON file as a configuration, with JSON keys in dot notation in your application. Also supports SwiftUI! 9 | DESC 10 | 11 | spec.homepage = "https://github.com/mustafakarakus/Config" 12 | spec.license = { :type => "MIT", :file => "LICENSE" } 13 | spec.author = { "Mustafa" => "karakusmustafa@gmail.com" } 14 | 15 | spec.ios.deployment_target = "15.0" 16 | spec.swift_version = "5.5" 17 | 18 | spec.source = { :git => "https://github.com/mustafakarakus/Config.git", :tag => "#{spec.version}" } 19 | spec.source_files = "Sources/Config/**/*" 20 | spec.exclude_files = "Sources/Config/*.plist" 21 | end -------------------------------------------------------------------------------- /Config.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 452455B9238B2E4F00B18151 /* JSONValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 452455B8238B2E4F00B18151 /* JSONValue.swift */; }; 11 | 45424D3F234691C80096337E /* Config.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 45424D35234691C80096337E /* Config.framework */; }; 12 | 45424D672346A20C0096337E /* Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45424D662346A20B0096337E /* Config.swift */; }; 13 | 45424D692346A2840096337E /* JSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45424D682346A2840096337E /* JSON.swift */; }; 14 | 45424D6B2346A3880096337E /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45424D6A2346A3880096337E /* Extensions.swift */; }; 15 | 458BE1F4234786F400920DEA /* mainConfig.json in Resources */ = {isa = PBXBuildFile; fileRef = 458BE1F3234786F400920DEA /* mainConfig.json */; }; 16 | 458BE21C2347FF9300920DEA /* Messages.swift in Sources */ = {isa = PBXBuildFile; fileRef = 458BE21B2347FF9300920DEA /* Messages.swift */; }; 17 | 45E890F7234F016D009F0CCA /* LocalConfigInvalidTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45E890F3234F016C009F0CCA /* LocalConfigInvalidTests.swift */; }; 18 | 45E890F9234F016D009F0CCA /* LiveConfigTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45E890F5234F016D009F0CCA /* LiveConfigTests.swift */; }; 19 | 45E890FA234F016D009F0CCA /* LocalConfigTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45E890F6234F016D009F0CCA /* LocalConfigTests.swift */; }; 20 | 45E890FC234F0182009F0CCA /* invalidConfig.json in Resources */ = {isa = PBXBuildFile; fileRef = 45E890FB234F0182009F0CCA /* invalidConfig.json */; }; 21 | 45E890FE234F0E62009F0CCA /* Keys.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45E890FD234F0E62009F0CCA /* Keys.swift */; }; 22 | /* End PBXBuildFile section */ 23 | 24 | /* Begin PBXContainerItemProxy section */ 25 | 45424D40234691C80096337E /* PBXContainerItemProxy */ = { 26 | isa = PBXContainerItemProxy; 27 | containerPortal = 45424D2C234691C80096337E /* Project object */; 28 | proxyType = 1; 29 | remoteGlobalIDString = 45424D34234691C80096337E; 30 | remoteInfo = Config; 31 | }; 32 | /* End PBXContainerItemProxy section */ 33 | 34 | /* Begin PBXFileReference section */ 35 | 452455B8238B2E4F00B18151 /* JSONValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSONValue.swift; sourceTree = ""; }; 36 | 45424D35234691C80096337E /* Config.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Config.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 37 | 45424D39234691C80096337E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 38 | 45424D3E234691C80096337E /* ConfigTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ConfigTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 39 | 45424D45234691C80096337E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 40 | 45424D662346A20B0096337E /* Config.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Config.swift; sourceTree = ""; }; 41 | 45424D682346A2840096337E /* JSON.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSON.swift; sourceTree = ""; }; 42 | 45424D6A2346A3880096337E /* Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = ""; }; 43 | 458BE1F3234786F400920DEA /* mainConfig.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = mainConfig.json; sourceTree = ""; }; 44 | 458BE21B2347FF9300920DEA /* Messages.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Messages.swift; sourceTree = ""; }; 45 | 45E890F3234F016C009F0CCA /* LocalConfigInvalidTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalConfigInvalidTests.swift; sourceTree = ""; }; 46 | 45E890F5234F016D009F0CCA /* LiveConfigTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LiveConfigTests.swift; sourceTree = ""; }; 47 | 45E890F6234F016D009F0CCA /* LocalConfigTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalConfigTests.swift; sourceTree = ""; }; 48 | 45E890FB234F0182009F0CCA /* invalidConfig.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = invalidConfig.json; sourceTree = ""; }; 49 | 45E890FD234F0E62009F0CCA /* Keys.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Keys.swift; sourceTree = ""; }; 50 | /* End PBXFileReference section */ 51 | 52 | /* Begin PBXFrameworksBuildPhase section */ 53 | 45424D32234691C80096337E /* Frameworks */ = { 54 | isa = PBXFrameworksBuildPhase; 55 | buildActionMask = 2147483647; 56 | files = ( 57 | ); 58 | runOnlyForDeploymentPostprocessing = 0; 59 | }; 60 | 45424D3B234691C80096337E /* Frameworks */ = { 61 | isa = PBXFrameworksBuildPhase; 62 | buildActionMask = 2147483647; 63 | files = ( 64 | 45424D3F234691C80096337E /* Config.framework in Frameworks */, 65 | ); 66 | runOnlyForDeploymentPostprocessing = 0; 67 | }; 68 | /* End PBXFrameworksBuildPhase section */ 69 | 70 | /* Begin PBXGroup section */ 71 | 45424D2B234691C80096337E = { 72 | isa = PBXGroup; 73 | children = ( 74 | 45424D37234691C80096337E /* Config */, 75 | 45424D42234691C80096337E /* ConfigTests */, 76 | 45424D36234691C80096337E /* Products */, 77 | ); 78 | sourceTree = ""; 79 | }; 80 | 45424D36234691C80096337E /* Products */ = { 81 | isa = PBXGroup; 82 | children = ( 83 | 45424D35234691C80096337E /* Config.framework */, 84 | 45424D3E234691C80096337E /* ConfigTests.xctest */, 85 | ); 86 | name = Products; 87 | sourceTree = ""; 88 | }; 89 | 45424D37234691C80096337E /* Config */ = { 90 | isa = PBXGroup; 91 | children = ( 92 | 45424D39234691C80096337E /* Info.plist */, 93 | 45424D662346A20B0096337E /* Config.swift */, 94 | 45424D682346A2840096337E /* JSON.swift */, 95 | 45424D6A2346A3880096337E /* Extensions.swift */, 96 | 458BE21B2347FF9300920DEA /* Messages.swift */, 97 | 452455B8238B2E4F00B18151 /* JSONValue.swift */, 98 | ); 99 | name = Config; 100 | path = Sources/Config; 101 | sourceTree = ""; 102 | }; 103 | 45424D42234691C80096337E /* ConfigTests */ = { 104 | isa = PBXGroup; 105 | children = ( 106 | 458BE1F02347867E00920DEA /* ConfigFiles */, 107 | 45E890F5234F016D009F0CCA /* LiveConfigTests.swift */, 108 | 45E890F3234F016C009F0CCA /* LocalConfigInvalidTests.swift */, 109 | 45E890F6234F016D009F0CCA /* LocalConfigTests.swift */, 110 | 45424D45234691C80096337E /* Info.plist */, 111 | 45E890FD234F0E62009F0CCA /* Keys.swift */, 112 | ); 113 | name = ConfigTests; 114 | path = Tests/ConfigTests; 115 | sourceTree = ""; 116 | }; 117 | 458BE1F02347867E00920DEA /* ConfigFiles */ = { 118 | isa = PBXGroup; 119 | children = ( 120 | 458BE1F3234786F400920DEA /* mainConfig.json */, 121 | 45E890FB234F0182009F0CCA /* invalidConfig.json */, 122 | ); 123 | path = ConfigFiles; 124 | sourceTree = ""; 125 | }; 126 | /* End PBXGroup section */ 127 | 128 | /* Begin PBXHeadersBuildPhase section */ 129 | 45424D30234691C80096337E /* Headers */ = { 130 | isa = PBXHeadersBuildPhase; 131 | buildActionMask = 2147483647; 132 | files = ( 133 | ); 134 | runOnlyForDeploymentPostprocessing = 0; 135 | }; 136 | /* End PBXHeadersBuildPhase section */ 137 | 138 | /* Begin PBXNativeTarget section */ 139 | 45424D34234691C80096337E /* Config */ = { 140 | isa = PBXNativeTarget; 141 | buildConfigurationList = 45424D49234691C80096337E /* Build configuration list for PBXNativeTarget "Config" */; 142 | buildPhases = ( 143 | 45424D30234691C80096337E /* Headers */, 144 | 45424D31234691C80096337E /* Sources */, 145 | 45424D32234691C80096337E /* Frameworks */, 146 | 45424D33234691C80096337E /* Resources */, 147 | ); 148 | buildRules = ( 149 | ); 150 | dependencies = ( 151 | ); 152 | name = Config; 153 | productName = Config; 154 | productReference = 45424D35234691C80096337E /* Config.framework */; 155 | productType = "com.apple.product-type.framework"; 156 | }; 157 | 45424D3D234691C80096337E /* ConfigTests */ = { 158 | isa = PBXNativeTarget; 159 | buildConfigurationList = 45424D4C234691C80096337E /* Build configuration list for PBXNativeTarget "ConfigTests" */; 160 | buildPhases = ( 161 | 45424D3A234691C80096337E /* Sources */, 162 | 45424D3B234691C80096337E /* Frameworks */, 163 | 45424D3C234691C80096337E /* Resources */, 164 | ); 165 | buildRules = ( 166 | ); 167 | dependencies = ( 168 | 45424D41234691C80096337E /* PBXTargetDependency */, 169 | ); 170 | name = ConfigTests; 171 | productName = ConfigTests; 172 | productReference = 45424D3E234691C80096337E /* ConfigTests.xctest */; 173 | productType = "com.apple.product-type.bundle.unit-test"; 174 | }; 175 | /* End PBXNativeTarget section */ 176 | 177 | /* Begin PBXProject section */ 178 | 45424D2C234691C80096337E /* Project object */ = { 179 | isa = PBXProject; 180 | attributes = { 181 | LastSwiftUpdateCheck = 1010; 182 | LastUpgradeCheck = 1100; 183 | ORGANIZATIONNAME = "Mustafa Karakus"; 184 | TargetAttributes = { 185 | 45424D34234691C80096337E = { 186 | CreatedOnToolsVersion = 10.1; 187 | LastSwiftMigration = 1100; 188 | }; 189 | 45424D3D234691C80096337E = { 190 | CreatedOnToolsVersion = 10.1; 191 | LastSwiftMigration = 1100; 192 | }; 193 | }; 194 | }; 195 | buildConfigurationList = 45424D2F234691C80096337E /* Build configuration list for PBXProject "Config" */; 196 | compatibilityVersion = "Xcode 9.3"; 197 | developmentRegion = en; 198 | hasScannedForEncodings = 0; 199 | knownRegions = ( 200 | en, 201 | Base, 202 | ); 203 | mainGroup = 45424D2B234691C80096337E; 204 | productRefGroup = 45424D36234691C80096337E /* Products */; 205 | projectDirPath = ""; 206 | projectRoot = ""; 207 | targets = ( 208 | 45424D34234691C80096337E /* Config */, 209 | 45424D3D234691C80096337E /* ConfigTests */, 210 | ); 211 | }; 212 | /* End PBXProject section */ 213 | 214 | /* Begin PBXResourcesBuildPhase section */ 215 | 45424D33234691C80096337E /* Resources */ = { 216 | isa = PBXResourcesBuildPhase; 217 | buildActionMask = 2147483647; 218 | files = ( 219 | ); 220 | runOnlyForDeploymentPostprocessing = 0; 221 | }; 222 | 45424D3C234691C80096337E /* Resources */ = { 223 | isa = PBXResourcesBuildPhase; 224 | buildActionMask = 2147483647; 225 | files = ( 226 | 458BE1F4234786F400920DEA /* mainConfig.json in Resources */, 227 | 45E890FC234F0182009F0CCA /* invalidConfig.json in Resources */, 228 | ); 229 | runOnlyForDeploymentPostprocessing = 0; 230 | }; 231 | /* End PBXResourcesBuildPhase section */ 232 | 233 | /* Begin PBXSourcesBuildPhase section */ 234 | 45424D31234691C80096337E /* Sources */ = { 235 | isa = PBXSourcesBuildPhase; 236 | buildActionMask = 2147483647; 237 | files = ( 238 | 45424D672346A20C0096337E /* Config.swift in Sources */, 239 | 458BE21C2347FF9300920DEA /* Messages.swift in Sources */, 240 | 45424D6B2346A3880096337E /* Extensions.swift in Sources */, 241 | 45424D692346A2840096337E /* JSON.swift in Sources */, 242 | 452455B9238B2E4F00B18151 /* JSONValue.swift in Sources */, 243 | ); 244 | runOnlyForDeploymentPostprocessing = 0; 245 | }; 246 | 45424D3A234691C80096337E /* Sources */ = { 247 | isa = PBXSourcesBuildPhase; 248 | buildActionMask = 2147483647; 249 | files = ( 250 | 45E890F9234F016D009F0CCA /* LiveConfigTests.swift in Sources */, 251 | 45E890FA234F016D009F0CCA /* LocalConfigTests.swift in Sources */, 252 | 45E890F7234F016D009F0CCA /* LocalConfigInvalidTests.swift in Sources */, 253 | 45E890FE234F0E62009F0CCA /* Keys.swift in Sources */, 254 | ); 255 | runOnlyForDeploymentPostprocessing = 0; 256 | }; 257 | /* End PBXSourcesBuildPhase section */ 258 | 259 | /* Begin PBXTargetDependency section */ 260 | 45424D41234691C80096337E /* PBXTargetDependency */ = { 261 | isa = PBXTargetDependency; 262 | target = 45424D34234691C80096337E /* Config */; 263 | targetProxy = 45424D40234691C80096337E /* PBXContainerItemProxy */; 264 | }; 265 | /* End PBXTargetDependency section */ 266 | 267 | /* Begin XCBuildConfiguration section */ 268 | 45424D47234691C80096337E /* Debug */ = { 269 | isa = XCBuildConfiguration; 270 | buildSettings = { 271 | ALWAYS_SEARCH_USER_PATHS = NO; 272 | CLANG_ANALYZER_NONNULL = YES; 273 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 274 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 275 | CLANG_CXX_LIBRARY = "libc++"; 276 | CLANG_ENABLE_MODULES = YES; 277 | CLANG_ENABLE_OBJC_ARC = YES; 278 | CLANG_ENABLE_OBJC_WEAK = YES; 279 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 280 | CLANG_WARN_BOOL_CONVERSION = YES; 281 | CLANG_WARN_COMMA = YES; 282 | CLANG_WARN_CONSTANT_CONVERSION = YES; 283 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 284 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 285 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 286 | CLANG_WARN_EMPTY_BODY = YES; 287 | CLANG_WARN_ENUM_CONVERSION = YES; 288 | CLANG_WARN_INFINITE_RECURSION = YES; 289 | CLANG_WARN_INT_CONVERSION = YES; 290 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 291 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 292 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 293 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 294 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 295 | CLANG_WARN_STRICT_PROTOTYPES = YES; 296 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 297 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 298 | CLANG_WARN_UNREACHABLE_CODE = YES; 299 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 300 | CODE_SIGN_IDENTITY = "iPhone Developer"; 301 | COPY_PHASE_STRIP = NO; 302 | CURRENT_PROJECT_VERSION = 1; 303 | DEBUG_INFORMATION_FORMAT = dwarf; 304 | ENABLE_STRICT_OBJC_MSGSEND = YES; 305 | ENABLE_TESTABILITY = YES; 306 | GCC_C_LANGUAGE_STANDARD = gnu11; 307 | GCC_DYNAMIC_NO_PIC = NO; 308 | GCC_NO_COMMON_BLOCKS = YES; 309 | GCC_OPTIMIZATION_LEVEL = 0; 310 | GCC_PREPROCESSOR_DEFINITIONS = ( 311 | "DEBUG=1", 312 | "$(inherited)", 313 | ); 314 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 315 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 316 | GCC_WARN_UNDECLARED_SELECTOR = YES; 317 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 318 | GCC_WARN_UNUSED_FUNCTION = YES; 319 | GCC_WARN_UNUSED_VARIABLE = YES; 320 | IPHONEOS_DEPLOYMENT_TARGET = 12.1; 321 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 322 | MTL_FAST_MATH = YES; 323 | ONLY_ACTIVE_ARCH = YES; 324 | SDKROOT = iphoneos; 325 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 326 | SWIFT_OBJC_INTERFACE_HEADER_NAME = "Config-Swift.h"; 327 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 328 | SWIFT_VERSION = 5.0; 329 | VERSIONING_SYSTEM = "apple-generic"; 330 | VERSION_INFO_PREFIX = ""; 331 | }; 332 | name = Debug; 333 | }; 334 | 45424D48234691C80096337E /* Release */ = { 335 | isa = XCBuildConfiguration; 336 | buildSettings = { 337 | ALWAYS_SEARCH_USER_PATHS = NO; 338 | CLANG_ANALYZER_NONNULL = YES; 339 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 340 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 341 | CLANG_CXX_LIBRARY = "libc++"; 342 | CLANG_ENABLE_MODULES = YES; 343 | CLANG_ENABLE_OBJC_ARC = YES; 344 | CLANG_ENABLE_OBJC_WEAK = YES; 345 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 346 | CLANG_WARN_BOOL_CONVERSION = YES; 347 | CLANG_WARN_COMMA = YES; 348 | CLANG_WARN_CONSTANT_CONVERSION = YES; 349 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 350 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 351 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 352 | CLANG_WARN_EMPTY_BODY = YES; 353 | CLANG_WARN_ENUM_CONVERSION = YES; 354 | CLANG_WARN_INFINITE_RECURSION = YES; 355 | CLANG_WARN_INT_CONVERSION = YES; 356 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 357 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 358 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 359 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 360 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 361 | CLANG_WARN_STRICT_PROTOTYPES = YES; 362 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 363 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 364 | CLANG_WARN_UNREACHABLE_CODE = YES; 365 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 366 | CODE_SIGN_IDENTITY = "iPhone Developer"; 367 | COPY_PHASE_STRIP = NO; 368 | CURRENT_PROJECT_VERSION = 1; 369 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 370 | ENABLE_NS_ASSERTIONS = NO; 371 | ENABLE_STRICT_OBJC_MSGSEND = YES; 372 | GCC_C_LANGUAGE_STANDARD = gnu11; 373 | GCC_NO_COMMON_BLOCKS = YES; 374 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 375 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 376 | GCC_WARN_UNDECLARED_SELECTOR = YES; 377 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 378 | GCC_WARN_UNUSED_FUNCTION = YES; 379 | GCC_WARN_UNUSED_VARIABLE = YES; 380 | IPHONEOS_DEPLOYMENT_TARGET = 12.1; 381 | MTL_ENABLE_DEBUG_INFO = NO; 382 | MTL_FAST_MATH = YES; 383 | SDKROOT = iphoneos; 384 | SWIFT_COMPILATION_MODE = wholemodule; 385 | SWIFT_OBJC_INTERFACE_HEADER_NAME = "Config-Swift.h"; 386 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 387 | SWIFT_VERSION = 5.0; 388 | VALIDATE_PRODUCT = YES; 389 | VERSIONING_SYSTEM = "apple-generic"; 390 | VERSION_INFO_PREFIX = ""; 391 | }; 392 | name = Release; 393 | }; 394 | 45424D4A234691C80096337E /* Debug */ = { 395 | isa = XCBuildConfiguration; 396 | buildSettings = { 397 | BUILD_LIBRARY_FOR_DISTRIBUTION = YES; 398 | CLANG_ENABLE_MODULES = YES; 399 | CODE_SIGN_IDENTITY = ""; 400 | CODE_SIGN_STYLE = Manual; 401 | DEFINES_MODULE = YES; 402 | DEVELOPMENT_TEAM = ""; 403 | DYLIB_COMPATIBILITY_VERSION = 1; 404 | DYLIB_CURRENT_VERSION = 1; 405 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 406 | INFOPLIST_FILE = Sources/Config/Info.plist; 407 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 408 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 409 | LD_RUNPATH_SEARCH_PATHS = ( 410 | "$(inherited)", 411 | "@executable_path/Frameworks", 412 | "@loader_path/Frameworks", 413 | ); 414 | PRODUCT_BUNDLE_IDENTIFIER = com.mustafakarakus.apps.Config; 415 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 416 | PROVISIONING_PROFILE_SPECIFIER = ""; 417 | "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = ""; 418 | SKIP_INSTALL = YES; 419 | SWIFT_INSTALL_OBJC_HEADER = YES; 420 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 421 | SWIFT_VERSION = 5.0; 422 | TARGETED_DEVICE_FAMILY = "1,2"; 423 | }; 424 | name = Debug; 425 | }; 426 | 45424D4B234691C80096337E /* Release */ = { 427 | isa = XCBuildConfiguration; 428 | buildSettings = { 429 | BUILD_LIBRARY_FOR_DISTRIBUTION = YES; 430 | CLANG_ENABLE_MODULES = YES; 431 | CODE_SIGN_IDENTITY = ""; 432 | CODE_SIGN_STYLE = Manual; 433 | DEFINES_MODULE = YES; 434 | DEVELOPMENT_TEAM = ""; 435 | DYLIB_COMPATIBILITY_VERSION = 1; 436 | DYLIB_CURRENT_VERSION = 1; 437 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 438 | INFOPLIST_FILE = Sources/Config/Info.plist; 439 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 440 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 441 | LD_RUNPATH_SEARCH_PATHS = ( 442 | "$(inherited)", 443 | "@executable_path/Frameworks", 444 | "@loader_path/Frameworks", 445 | ); 446 | PRODUCT_BUNDLE_IDENTIFIER = com.mustafakarakus.apps.Config; 447 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 448 | PROVISIONING_PROFILE_SPECIFIER = ""; 449 | "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = ""; 450 | SKIP_INSTALL = YES; 451 | SWIFT_INSTALL_OBJC_HEADER = YES; 452 | SWIFT_VERSION = 5.0; 453 | TARGETED_DEVICE_FAMILY = "1,2"; 454 | }; 455 | name = Release; 456 | }; 457 | 45424D4D234691C80096337E /* Debug */ = { 458 | isa = XCBuildConfiguration; 459 | buildSettings = { 460 | "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; 461 | CODE_SIGN_STYLE = Manual; 462 | DEVELOPMENT_TEAM = ""; 463 | INFOPLIST_FILE = Tests/ConfigTests/Info.plist; 464 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 465 | LD_RUNPATH_SEARCH_PATHS = ( 466 | "$(inherited)", 467 | "@executable_path/Frameworks", 468 | "@loader_path/Frameworks", 469 | ); 470 | PRODUCT_BUNDLE_IDENTIFIER = com.mustafakarakus.apps.ConfigTests; 471 | PRODUCT_NAME = "$(TARGET_NAME)"; 472 | PROVISIONING_PROFILE_SPECIFIER = ""; 473 | "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = ""; 474 | SWIFT_VERSION = 5.0; 475 | TARGETED_DEVICE_FAMILY = "1,2"; 476 | }; 477 | name = Debug; 478 | }; 479 | 45424D4E234691C80096337E /* Release */ = { 480 | isa = XCBuildConfiguration; 481 | buildSettings = { 482 | "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; 483 | CODE_SIGN_STYLE = Manual; 484 | DEVELOPMENT_TEAM = ""; 485 | INFOPLIST_FILE = Tests/ConfigTests/Info.plist; 486 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 487 | LD_RUNPATH_SEARCH_PATHS = ( 488 | "$(inherited)", 489 | "@executable_path/Frameworks", 490 | "@loader_path/Frameworks", 491 | ); 492 | PRODUCT_BUNDLE_IDENTIFIER = com.mustafakarakus.apps.ConfigTests; 493 | PRODUCT_NAME = "$(TARGET_NAME)"; 494 | PROVISIONING_PROFILE_SPECIFIER = ""; 495 | "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = ""; 496 | SWIFT_VERSION = 5.0; 497 | TARGETED_DEVICE_FAMILY = "1,2"; 498 | }; 499 | name = Release; 500 | }; 501 | /* End XCBuildConfiguration section */ 502 | 503 | /* Begin XCConfigurationList section */ 504 | 45424D2F234691C80096337E /* Build configuration list for PBXProject "Config" */ = { 505 | isa = XCConfigurationList; 506 | buildConfigurations = ( 507 | 45424D47234691C80096337E /* Debug */, 508 | 45424D48234691C80096337E /* Release */, 509 | ); 510 | defaultConfigurationIsVisible = 0; 511 | defaultConfigurationName = Release; 512 | }; 513 | 45424D49234691C80096337E /* Build configuration list for PBXNativeTarget "Config" */ = { 514 | isa = XCConfigurationList; 515 | buildConfigurations = ( 516 | 45424D4A234691C80096337E /* Debug */, 517 | 45424D4B234691C80096337E /* Release */, 518 | ); 519 | defaultConfigurationIsVisible = 0; 520 | defaultConfigurationName = Release; 521 | }; 522 | 45424D4C234691C80096337E /* Build configuration list for PBXNativeTarget "ConfigTests" */ = { 523 | isa = XCConfigurationList; 524 | buildConfigurations = ( 525 | 45424D4D234691C80096337E /* Debug */, 526 | 45424D4E234691C80096337E /* Release */, 527 | ); 528 | defaultConfigurationIsVisible = 0; 529 | defaultConfigurationName = Release; 530 | }; 531 | /* End XCConfigurationList section */ 532 | }; 533 | rootObject = 45424D2C234691C80096337E /* Project object */; 534 | } 535 | -------------------------------------------------------------------------------- /Config.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Config.xcodeproj/xcshareddata/xcschemes/Config.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 57 | 58 | 59 | 60 | 62 | 63 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 mustafakarakus 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. 22 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.5 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "Config", 7 | platforms: [ 8 | .macOS(.v12), 9 | .iOS(.v15), 10 | .tvOS(.v15), 11 | .watchOS(.v8) 12 | ], 13 | products: [ 14 | .library( 15 | name: "Config", 16 | targets: ["Config"]), 17 | ], 18 | dependencies: [], 19 | targets: [ 20 | .target( 21 | name: "Config", 22 | dependencies: [], 23 | path: "Sources/Config", 24 | exclude: [ 25 | "Info.plist" 26 | ] 27 | ), 28 | .testTarget( 29 | name: "ConfigTests", 30 | dependencies: ["Config"], 31 | path: "Tests/ConfigTests", 32 | exclude: [ 33 | "Info.plist" 34 | ] 35 | ), 36 | ] 37 | ) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Config 2 | 3 | [![Build Status](https://travis-ci.org/mustafakarakus/Config.svg?branch=master)](https://travis-ci.org/mustafakarakus/Config) 4 | [![Codebeat](https://codebeat.co/badges/7ab7f216-59bb-430b-9791-fab438fb3c05)](https://codebeat.co/projects/github-com-mustafakarakus-config-master) 5 | [![SwiftUI](https://img.shields.io/badge/SwiftUI-blue.svg?style=flat)](https://developer.apple.com/swiftui/) 6 | [![Swift 5](https://img.shields.io/badge/Swift-5.5-orange.svg?style=flat)](https://developer.apple.com/swift/) 7 | [![CocoaPods](https://img.shields.io/cocoapods/v/Config.svg)](https://cocoapods.org/pods/Config) 8 | [![Swift Package Manager](https://img.shields.io/badge/SPM-compatible-brightgreen.svg)](https://github.com/apple/swift-package-manager) 9 | [![Carthage](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) 10 | [![License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](LICENSE) 11 | 12 | Config is a framework written in Swift that makes it easy for you to use JSON file as a configuration, with JSON keys in dot notation in your application. Also supports SwiftUI! 13 | 14 | 15 | ## Table of contents 16 | - [Requirements](#requirements) 17 | - [Integration](#integration) 18 | - [Swift Package Manager](#swift-package-manager) 19 | - [CocoaPods](#cocoapods) 20 | - [Usage](#usage) 21 | - [Instance](#usage-instance) 22 | - [Property Wrapper](#usage-property-wrapper) 23 | - [Pattern Matching](#usage-pattern-matching) 24 | - [SwiftUI](#swiftui) 25 | - [What is next?](#what-is-next) 26 | - [Contribution](#contribution) 27 | - [License](#license) 28 | 29 | --- 30 | 31 | 32 | 33 | ## Requirements 34 | 35 | * iOS 15.0 36 | * Xcode 13.1 37 | * Swift 5.5 38 | 39 | 40 | 41 | ## Integration 42 | 43 | 44 | 45 | ### Swift Package Manager 46 | 47 | - In Xcode, open `File > Add Packages`. 48 | - Search **https://github.com/mustafakarakus/Config.git** 49 | - Config should be listed. Click `Add Package` 50 | 51 | 52 | 53 | ### CocoaPods 54 | 55 | You can use CocoaPods to install **Config** by adding it to your Podfile: 56 | 57 | ```Pod 58 | use_frameworks! 59 | 60 | target 'MyApp' do 61 | pod 'Config' 62 | end 63 | ``` 64 | 65 | 66 | 67 | ## Usage 68 | 69 | Create a desired config file in **JSON** format in your Xcode project or use a web url that returns a **JSON** 70 | 71 | ```JSON 72 | 73 | { 74 | "development": { 75 | "debug": true 76 | }, 77 | "application": { 78 | "type":5, 79 | "version":1.2, 80 | "appKey":"ABCD-EFGH-IJKLMNOPR", 81 | "security":{ 82 | "OAuth2":{ 83 | "credentials":{ 84 | "username":"mustafakarakus", 85 | "password":"password" 86 | }, 87 | "groups":[1,9,0,5], 88 | "chain":false 89 | }, 90 | "domainExceptions":[ 91 | "https://www.google.com", 92 | "https://www.twitter.com" 93 | ] 94 | } 95 | } 96 | } 97 | 98 | ``` 99 | 100 | You have 3 options to read your **JSON** values, Choose the best way for you 101 | 102 | * Instance 103 | * Property Wrapper 104 | * Pattern Matching 105 | 106 | 107 | 108 | ### Instance 109 | 110 | * Initialize **Config** with the JSON file that exists in your directories. Or Use a JSON URL while initializing. 111 | 112 | **Local File** 113 | 114 | Add `my-config.json` file in the desired project directory 115 | 116 | ```swift 117 | 118 | let config = Config(with: "my-config") 119 | 120 | ``` 121 | 122 | **Remote File** 123 | 124 | Use a URL for your JSON file that exists on remote. 125 | 126 | ```swift 127 | 128 | let config = Config(with: "https://raw.githubusercontent.com/mustafakarakus/Config/master/Tests/ConfigTests/ConfigFiles/mainConfig.json") 129 | 130 | ``` 131 | 132 | * Type your JSON keys with **dot notation** that described in JSON file. Last thing is call **.value()** function. *value()* function is type inferred. That means you need to specify your data type in your variable/constant. 133 | 134 | ```swift 135 | 136 | let myIntegerValue:Int = config.myKey.myIntegerValue.value() 137 | 138 | ``` 139 | 140 | ```swift 141 | 142 | if let development:[String:Any] = config.development.value(){ 143 | print(development) 144 | } 145 | 146 | ``` 147 | #### Examples 148 | 149 | ```swift 150 | 151 | import Config 152 | 153 | class ViewController: UIViewController { 154 | 155 | override func viewDidLoad() { 156 | super.viewDidLoad() 157 | 158 | let config = Config(with: "my-config") 159 | 160 | if let development:[String:Any] = config.development.value() { 161 | print(development) 162 | } 163 | 164 | if let debug:Bool = config.development.debug.value(){ 165 | print(debug) 166 | } 167 | 168 | if let application:[String:Any] = config.application.value(){ 169 | print(application) 170 | } 171 | 172 | if let version:Double = config.application.version.value(){ 173 | print(version) 174 | } 175 | 176 | if let applicationType:Int = config.application.type.value(){ 177 | print(applicationType) 178 | } 179 | 180 | if let appKey:String = config.application.appKey.value(){ 181 | print(appKey) 182 | } 183 | 184 | if let username:String = config.application.security.OAuth2.credentials.username.value(){ 185 | print(username) 186 | } 187 | 188 | if let groups:[Int] = config.application.security.OAuth2.groups.value(){ 189 | print(groups) 190 | } 191 | 192 | if let domainExceptions:[String] = config.application.security.domainExceptions.value(){ 193 | print(domainExceptions) 194 | } 195 | } 196 | } 197 | ``` 198 | 199 | 200 | 201 | ### JSONValue Property Wrapper 202 | 203 | * Mark your variable with Local JSON file name; **@JSONValue("my-json-file-name", "JSONKey")** 204 | 205 | or 206 | 207 | * Mark your variable with Remote JSON URL; **@JSONValue("http://www.mydomain.com/files/my-config.json", "JSONKey")** 208 | 209 | *In this case 'application.security.OAuth2.groups' data type is an Int array in 'my-config.json' file (read the sample JSON file above)* 210 | 211 | ```swift 212 | 213 | @JSONValue("my-config", "application.security.OAuth2.groups") 214 | var groups: [Int] = [] 215 | 216 | ``` 217 | 218 | ```swift 219 | 220 | @JSONValue(url: "https://raw.githubusercontent.com/mustafakarakus/Config/master/Tests/ConfigTests/ConfigFiles/mainConfig.json", "application.appKey") 221 | var appKey: String = "" 222 | 223 | ``` 224 | 225 | #### Examples 226 | 227 | ```swift 228 | 229 | import Config 230 | 231 | class PropertyWrapperViewController: UIViewController { 232 | 233 | @JSONValue("my-config", "application.security.OAuth2.groups") 234 | var groups: [Int] = [] 235 | 236 | @JSONValue("my-config1", "development") 237 | var development: [String:Any] = [:] 238 | 239 | @JSONValue("my-config", "development.debug") 240 | var isDebug: Bool = false 241 | 242 | @JSONValue("my-config", "application.security.OAuth2.credentials.username") 243 | var username: String = "" 244 | 245 | @JSONValue(url: "https://raw.githubusercontent.com/mustafakarakus/Config/master/Tests/ConfigTests/ConfigFiles/mainConfig.json", "application.appKey") 246 | var appKey: String = "" 247 | 248 | override func viewDidLoad() { 249 | super.viewDidLoad() 250 | 251 | print("groups: \(groups)") 252 | print("development: \(development)") 253 | print("debug mode: \(isDebug)") 254 | print("OAuth2 username: \(username)") 255 | print("App Key: \(appKey)") 256 | } 257 | } 258 | ``` 259 | 260 | 261 | 262 | ### Pattern Matching 263 | 264 | * Create an instance of **Config** then type your JSON keys with **dot notation** that described in JSON file. 265 | * Use Swift's Pattern matching power with the following cases 266 | 267 | ``` 268 | .string(val) 269 | .int(val) 270 | .double(val) 271 | .bool(val) 272 | .object(val) 273 | .array(val) 274 | ``` 275 | 276 | ```swift 277 | private var config: Config? { 278 | let sampleUrl = "https://raw.githubusercontent.com/mustafakarakus/Config/master/Tests/ConfigTests/ConfigFiles/mainConfig.json" 279 | guard let url = URL(string: sampleUrl) else { return nil } 280 | return Config(with: url) 281 | } 282 | 283 | let string = config?.application.appKey 284 | if case let .string(val) = string { 285 | print("app Key \(val)") 286 | } 287 | 288 | ``` 289 | 290 | #### Examples 291 | 292 | ```swift 293 | 294 | import Config 295 | 296 | class PatternMatchingViewController: UIViewController { 297 | 298 | private var config: Config? { 299 | let sampleUrl = "https://raw.githubusercontent.com/mustafakarakus/Config/master/ConfigExamples/config.json" 300 | guard let url = URL(string: sampleUrl) else { return nil } 301 | return Config(with: url) 302 | } 303 | 304 | override func viewDidLoad() { 305 | super.viewDidLoad() 306 | 307 | let string = config?.application.appKey 308 | if case let .string(val) = string { 309 | print("app Key \(val)") 310 | } 311 | 312 | let double = config?.application.version 313 | if case let .double(val) = double { 314 | print("version: \(val)") 315 | } 316 | 317 | let intArray = config?.application.security.OAuth2.groups 318 | if case let .array(val) = intArray { 319 | for item in val { 320 | print("groups: \(String(describing: item.intValue))") 321 | } 322 | } 323 | 324 | let dictionary = config?.application 325 | if case let .object(val) = dictionary { 326 | print("application: \(val)") 327 | } 328 | } 329 | } 330 | 331 | ``` 332 | 333 | 334 | 335 | ### SwiftUI 336 | 337 | Config now supports SwiftUI. Using the **JSONValue** Property Wrapper in the SwiftUI variable will bind the data from your JSON source, local or remote. 338 | 339 | ```swift 340 | import SwiftUI 341 | import Config 342 | 343 | struct ContentViewLocal: View { 344 | 345 | @JSONValue("my-config", "application.security.OAuth2.credentials.username") 346 | var name: String = "" 347 | 348 | var body: some View { 349 | Text(name) 350 | .padding() 351 | } 352 | } 353 | ``` 354 | 355 | ```swift 356 | import SwiftUI 357 | import Config 358 | 359 | struct ContentViewRemote: View { 360 | 361 | @JSONValue(url: "https://raw.githubusercontent.com/mustafakarakus/Config/master/Tests/ConfigTests/ConfigFiles/mainConfig.json", "application.appKey") 362 | var appKey: String = "" 363 | 364 | var body: some View { 365 | Text(appKey) 366 | .padding() 367 | } 368 | } 369 | ``` 370 | 371 | 372 | 373 | ## What is next? 374 | 375 | - [x] Carthage 376 | - [x] Swift Package Manager 377 | - [x] Remote JSON 378 | - [x] Swift 5.x 379 | - [x] Property Wrappers 380 | - [x] Use instance instead of Singleton 381 | - [x] SwiftUI 382 | 383 | 384 | 385 | ## Contribution 386 | 387 | Anyone who would like to contribute to the project is more than welcome. 388 | 389 | * Fork this repo 390 | * Make your changes 391 | * Submit pull request 392 | 393 | 394 | 395 | ## License 396 | 397 | MIT License 398 | 399 | Copyright (c) 2021 mustafakarakus 400 | 401 | Permission is hereby granted, free of charge, to any person obtaining a copy 402 | of this software and associated documentation files (the "Software"), to deal 403 | in the Software without restriction, including without limitation the rights 404 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 405 | copies of the Software, and to permit persons to whom the Software is 406 | furnished to do so, subject to the following conditions: 407 | 408 | The above copyright notice and this permission notice shall be included in all 409 | copies or substantial portions of the Software. 410 | 411 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 412 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 413 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 414 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 415 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 416 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 417 | SOFTWARE. -------------------------------------------------------------------------------- /Sources/Config/Config.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Config.swift 3 | // Config 4 | // 5 | // Created by Mustafa Karakus on 02/10/2019. 6 | // Copyright © 2019 Mustafa Karakus. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | @dynamicMemberLookup 12 | public class Config { 13 | private var url:URL? 14 | private var properties:JSON? 15 | 16 | public init(with fileName:String) { 17 | Bundle.allBundles.forEach { [weak self] bundle in 18 | guard let path = bundle.path(forResource: fileName, ofType: "json") else { 19 | print("\(Messages.notFound) \(fileName)") 20 | return 21 | } 22 | self?.url = URL(fileURLWithPath: path) 23 | self?.readConfig() 24 | } 25 | } 26 | 27 | public init(with url:URL) { 28 | self.url = url 29 | self.readConfig() 30 | } 31 | 32 | public subscript(dynamicMember member: String) -> JSON { 33 | if let properties = self.properties { 34 | let val = properties[member] 35 | return val 36 | } 37 | return JSON.error(Messages.checkJSONFile) 38 | } 39 | 40 | public subscript(key: String) -> T? { 41 | let splitChar = "." 42 | var subscripts:[String] = [] 43 | if key.contains(splitChar) { 44 | subscripts = key.components(separatedBy: splitChar) 45 | }else{ 46 | subscripts.append(key) 47 | } 48 | var data:JSON = self[dynamicMember: subscripts[0]] 49 | for i in 1.. Optional { 11 | switch self { 12 | case .none: return other 13 | case .some: return self 14 | } 15 | } 16 | 17 | func resolve(with error: @autoclosure () -> Error) throws -> Wrapped { 18 | switch self { 19 | case .none: throw error() 20 | case .some(let wrapped): return wrapped 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Sources/Config/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | $(MARKETING_VERSION) 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | 22 | 23 | -------------------------------------------------------------------------------- /Sources/Config/JSON.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JSON.swift 3 | // Config 4 | // 5 | // Created by Mustafa Karakus on 03/10/2019. 6 | // Copyright © 2019 Mustafa Karakus. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | @dynamicMemberLookup 12 | public enum JSON { 13 | case string(String) 14 | case int(Int) 15 | case double(Double) 16 | case bool(Bool) 17 | case object([String: JSON]) 18 | case array([JSON]) 19 | case error(String) 20 | } 21 | 22 | extension JSON : Decodable { 23 | public init(from decoder: Decoder) throws { 24 | let container = try decoder.singleValueContainer() 25 | self = try ((try? container.decode(String.self)).map(JSON.string)) 26 | .or((try? container.decode(Int.self)).map(JSON.int)) 27 | .or((try? container.decode(Double.self)).map(JSON.double)) 28 | .or((try? container.decode(Bool.self)).map(JSON.bool)) 29 | .or((try? container.decode([String: JSON].self)).map(JSON.object)) 30 | .or((try? container.decode([JSON].self)).map(JSON.array)) 31 | .resolve(with: DecodingError.typeMismatch(JSON.self, DecodingError.Context(codingPath: container.codingPath, debugDescription: Messages.notValidJSON))) 32 | } 33 | 34 | public func value() -> T? { 35 | return getValue(json: self) as? T 36 | } 37 | 38 | public subscript(key: String) -> JSON { 39 | if case .object(let dict) = self { 40 | return dict[key] ?? JSON.error("\(Messages.keyDoesNotExists) - '\(key)'") 41 | } 42 | return JSON.error(Messages.notValidJSON) 43 | } 44 | 45 | public subscript(dynamicMember member: String) -> JSON { 46 | if case .object(let dict) = self { 47 | return dict[member] ?? JSON.error("\(Messages.keyDoesNotExists) - '\(member)'") 48 | } 49 | return JSON.error("\(Messages.cannotReach) - '\(member)'") 50 | } 51 | 52 | func getValue(json:JSON) -> Any { 53 | switch json { 54 | case .int(let intValue): 55 | return intValue 56 | case .string(let stringValue): 57 | return stringValue 58 | case .bool(let boolValue): 59 | return boolValue 60 | case .double(let doubleValue): 61 | return doubleValue 62 | case .object(let dictionaryValue): 63 | return dictionaryValue.mapValues { getValue(json: $0 )} 64 | case .array(let arrayValue): 65 | return arrayValue.map { getValue(json: $0 )} 66 | default: 67 | return JSON.error 68 | } 69 | } 70 | } 71 | 72 | extension JSON { 73 | public var intValue: Int? { 74 | return getValue(json: self) as? Int 75 | } 76 | public var stringValue: String? { 77 | return getValue(json: self) as? String 78 | } 79 | public var boolValue: Bool? { 80 | return getValue(json: self) as? Bool 81 | } 82 | public var doubleValue: Double? { 83 | return getValue(json: self) as? Double 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /Sources/Config/JSONValue.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JSONValue.swift 3 | // Config 4 | // 5 | // Created by Mustafa Karakus on 23/11/2019. 6 | // Copyright © 2019 Mustafa Karakus. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | @propertyWrapper 12 | public struct JSONValue { 13 | private let key: String 14 | private var value: Value 15 | 16 | public var wrappedValue: Value { 17 | get { Config.shared?[key] ?? value } 18 | set { value = newValue } 19 | } 20 | } 21 | 22 | extension JSONValue { 23 | public init(wrappedValue: Value, _ configFileName: String, _ key: String) { 24 | self.value = wrappedValue 25 | self.key = key 26 | Config.initialize(name: configFileName) 27 | } 28 | 29 | public init(wrappedValue: Value, url: String, _ key: String) { 30 | self.value = wrappedValue 31 | self.key = key 32 | guard let url = URL(string: url) else { return } 33 | Config.initialize(url: url) 34 | } 35 | } 36 | 37 | extension Config { 38 | internal static var shared: Config? = nil 39 | internal static func initialize(name: String) { 40 | if shared == nil { 41 | shared = Config(with: name) 42 | } 43 | } 44 | internal static func initialize(url: URL) { 45 | if shared == nil { 46 | shared = Config(with: url) 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Sources/Config/Messages.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Messages.swift 3 | // Config 4 | // 5 | // Created by Mustafa Karakus on 05/10/2019. 6 | // Copyright © 2019 Mustafa Karakus. All rights reserved. 7 | // 8 | 9 | struct Messages { 10 | static let logTag = "Config.framework.log ===>" 11 | static let checkJSONFile = "\(logTag) Please check your config source, not exists or not valid JSON. Maybe you forgot to initialize?"; 12 | static let notValidJSON = "\(logTag) Not valid JSON."; 13 | static let notFound = "\(logTag) File not found."; 14 | static let keyDoesNotExists = "\(logTag) Key does not exists"; 15 | static let cannotReach = "\(logTag) Can not reach"; 16 | } 17 | -------------------------------------------------------------------------------- /Tests/ConfigTests/ConfigFiles/invalidConfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "development": { 3 | "debug": true 4 | }, 5 | "application": { 6 | "type":5, 7 | "version": 1.2, 8 | "appKey": "ABCD-EFGH-IJKLMNOPR", 9 | "security":{ 10 | "OAuth2":{ 11 | "credentials":{ 12 | "username":"mustafakarakus", 13 | "password":"password" 14 | }, 15 | "groups":[1,9,0,5], 16 | "cha 17 | -------------------------------------------------------------------------------- /Tests/ConfigTests/ConfigFiles/mainConfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "development": { 3 | "debug": true 4 | }, 5 | "application": { 6 | "type":5, 7 | "version": 1.2, 8 | "appKey": "ABCD-EFGH-IJKLMNOPR", 9 | "security":{ 10 | "OAuth2":{ 11 | "credentials":{ 12 | "username":"mustafakarakus", 13 | "password":"password" 14 | }, 15 | "groups":[1,9,0,5], 16 | "chain":false 17 | }, 18 | "domainExceptions":[ 19 | "https://www.google.com", 20 | "https://www.twitter.com" 21 | ] 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Tests/ConfigTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Tests/ConfigTests/Keys.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Keys.swift 3 | // ConfigTests 4 | // 5 | // Created by Mustafa Karakus on 10/10/2019. 6 | // Copyright © 2019 Mustafa Karakus. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct Keys { 12 | public static let SampleEndpointUrl = "https://raw.githubusercontent.com/mustafakarakus/Config/master/Tests/ConfigTests/ConfigFiles/mainConfig.json" 13 | public static let GoogleUrl = "https://www.google.com" 14 | } 15 | -------------------------------------------------------------------------------- /Tests/ConfigTests/LiveConfigTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LiveConfigTests.swift 3 | // ConfigTests 4 | // 5 | // Created by Mustafa Karakus on 09/10/2019. 6 | // Copyright © 2019 Mustafa Karakus. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import Config 11 | 12 | class LiveConfigTests: XCTestCase { 13 | 14 | private var config = Config(with: URL(string: Keys.SampleEndpointUrl)!) 15 | 16 | func testApplicationTypeIsInteger() { 17 | let data:Int? = config.application.type.value() 18 | XCTAssertNotNil(data) 19 | XCTAssertEqual(data, 5) 20 | } 21 | 22 | func testApplicationVersionIsDouble() { 23 | let data:Double? = config.application.version.value() 24 | XCTAssertNotNil(data) 25 | XCTAssertEqual(data, 1.2) 26 | } 27 | 28 | func testApplicationKeyIsString() { 29 | let data:String? = config.application.appKey.value() 30 | XCTAssertNotNil(data) 31 | XCTAssertEqual(data, "ABCD-EFGH-IJKLMNOPR") 32 | } 33 | 34 | func testApplicationSecurityGroupsAreArray() { 35 | let data:[Int]? = config.application.security.OAuth2.groups.value() 36 | XCTAssertNotNil(data) 37 | XCTAssertEqual(data, [1,9,0,5]) 38 | } 39 | 40 | func testMainKeyIsNotExists() { 41 | let data:String? = config.app.debug.value() 42 | XCTAssertNil(data) 43 | } 44 | 45 | func testSubKeyIsNotExists() { 46 | let data:String? = config.development.version.value() 47 | XCTAssertNil(data) 48 | } 49 | 50 | func testParseWrongDataType() { 51 | let wrongDataType:Int? = config.development.debug.value() 52 | let correctDataType:Bool? = config.development.debug.value() 53 | XCTAssertNil(wrongDataType) 54 | XCTAssertNotNil(correctDataType) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Tests/ConfigTests/LocalConfigInvalidTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ConfigTests.swift 3 | // ConfigTests 4 | // 5 | // Created by Mustafa Karakus on 03/10/2019. 6 | // Copyright © 2019 Mustafa Karakus. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import Config 11 | 12 | class LocalConfigInvalidTests: XCTestCase { 13 | 14 | private var config = Config(with: "invalidConfig") 15 | 16 | func testConfigIsInitialized() { 17 | let isDebug:Bool? = config.development.debug.value() 18 | XCTAssertNil(isDebug) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Tests/ConfigTests/LocalConfigTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ConfigTests.swift 3 | // ConfigTests 4 | // 5 | // Created by Mustafa Karakus on 03/10/2019. 6 | // Copyright © 2019 Mustafa Karakus. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import Config 11 | 12 | class LocalConfigTests: XCTestCase { 13 | 14 | private var config = Config(with: "mainConfig") 15 | 16 | func testDevelopmentKeyIsObject() { 17 | let data:[String:Bool]? = config.development.value() 18 | XCTAssertEqual(data, ["debug":true]) 19 | } 20 | 21 | func testDebugKeyIsBoolean() { 22 | let data:Bool? = config.development.debug.value() 23 | XCTAssertEqual(data, true) 24 | } 25 | 26 | func testApplicationTypeIsInteger() { 27 | let data:Int? = config.application.type.value() 28 | XCTAssertEqual(data, 5) 29 | } 30 | 31 | func testApplicationVersionIsDouble() { 32 | let data:Double? = config.application.version.value() 33 | XCTAssertEqual(data, 1.2) 34 | } 35 | 36 | func testApplicationKeyIsString() { 37 | let data:String? = config.application.appKey.value() 38 | XCTAssertEqual(data, "ABCD-EFGH-IJKLMNOPR") 39 | } 40 | 41 | func testApplicationSecurityGroupsAreArray() { 42 | let data:[Int]? = config.application.security.OAuth2.groups.value() 43 | XCTAssertEqual(data, [1,9,0,5]) 44 | } 45 | 46 | func testMainKeyIsNotExists() { 47 | let data:String? = config.app.debug.value() 48 | XCTAssertNil(data) 49 | } 50 | 51 | func testSubKeyIsNotExists() { 52 | let data:String? = config.development.version.value() 53 | XCTAssertNil(data) 54 | } 55 | 56 | func testTryParseWrongDataTypeShouldBeNil() { 57 | let wrongDataType:Int? = config.development.debug.value() 58 | let correctDataType:Bool? = config.development.debug.value() 59 | XCTAssertNil(wrongDataType) 60 | XCTAssertNotNil(correctDataType) 61 | } 62 | 63 | func testStringSubscriptsWithDotNotationShouldHaveValidValue() { 64 | let key = "application.security.OAuth2.groups" 65 | let data:[Int]? = config[key] 66 | XCTAssertEqual(data, [1,9,0,5]) 67 | } 68 | 69 | func testStringSubscriptsWithDotNotationWithWrongDatatypeShouldBeNil() { 70 | let key = "application.security.OAuth2.groups" 71 | let wrongDataType:Int? = config[key] 72 | XCTAssertNil(wrongDataType) 73 | } 74 | 75 | func testStringSubscriptsWithWrongNotationShouldBeNil() { 76 | let key = "application-security-OAuth2-groups" 77 | let data:[Int]? = config[key] 78 | XCTAssertNil(data) 79 | } 80 | 81 | func testStringSubscriptsWithEmptyStringShouldBeNil() { 82 | let key = "" 83 | let data:[Int]? = config[key] 84 | XCTAssertNil(data) 85 | } 86 | 87 | func testStringSubscriptsWithSpaceShouldBeNil() { 88 | let key = " " 89 | let data:[Int]? = config[key] 90 | XCTAssertNil(data) 91 | } 92 | func testStringSubscriptsWithSingleDotWithoutKeysShouldBeNil() { 93 | let key = "." 94 | let data:[Int]? = config[key] 95 | XCTAssertNil(data) 96 | } 97 | } 98 | --------------------------------------------------------------------------------