├── .github └── workflows │ └── Options.yml ├── .gitignore ├── .hound.yml ├── .periphery.yml ├── .spi.yml ├── .swift-version ├── .swiftformat ├── .swiftlint.yml ├── LICENSE ├── Mintfile ├── Package.swift ├── Package@swift-5.10.swift ├── README.md ├── Scripts ├── docc.sh ├── gh-md-toc └── lint.sh ├── Sources ├── Options │ ├── Array.swift │ ├── CodingOptions.swift │ ├── Dictionary.swift │ ├── Documentation.docc │ │ └── Documentation.md │ ├── EnumSet.swift │ ├── Macro.swift │ ├── MappedEnum.swift │ ├── MappedValueRepresentable+Codable.swift │ ├── MappedValueRepresentable.swift │ ├── MappedValueRepresentableError.swift │ ├── MappedValueRepresented.swift │ └── MappedValues.swift └── OptionsMacros │ ├── Extensions │ ├── Array.swift │ ├── ArrayExprSyntax.swift │ ├── DeclModifierListSyntax.swift │ ├── DeclModifierSyntax.swift │ ├── DictionaryElementSyntax.swift │ ├── DictionaryExprSyntax.swift │ ├── EnumDeclSyntax.swift │ ├── ExtensionDeclSyntax.swift │ ├── InheritanceClauseSyntax.swift │ ├── KeyValues.swift │ ├── TypeAliasDeclSyntax.swift │ └── VariableDeclSyntax.swift │ ├── InvalidDeclError.swift │ ├── MacrosPlugin.swift │ └── OptionsMacro.swift ├── Tests └── OptionsTests │ ├── EnumSetTests.swift │ ├── MappedEnumTests.swift │ ├── MappedValueCollectionRepresentedTests.swift │ ├── MappedValueDictionaryRepresentedTests.swift │ ├── MappedValueRepresentableTests.swift │ └── Mocks │ ├── MockCollectionEnum.swift │ ├── MockDictionaryEnum.swift │ └── MockError.swift ├── codecov.yml ├── logo.png ├── logo.svg └── project.yml /.github/workflows/Options.yml: -------------------------------------------------------------------------------- 1 | name: macOS 2 | on: 3 | push: 4 | branches-ignore: 5 | - '*WIP' 6 | env: 7 | PACKAGE_NAME: Options 8 | jobs: 9 | build-ubuntu: 10 | name: Build on Ubuntu 11 | env: 12 | PACKAGE_NAME: Options 13 | SWIFT_VER: ${{ matrix.swift-version }} 14 | runs-on: ${{ matrix.runs-on }} 15 | if: "!contains(github.event.head_commit.message, 'ci skip')" 16 | strategy: 17 | matrix: 18 | runs-on: [ubuntu-20.04, ubuntu-22.04] 19 | swift-version: ["5.7.1", "5.8.1", "5.9", "5.9.2", "5.10"] 20 | steps: 21 | - uses: actions/checkout@v4 22 | - name: Set Ubuntu Release DOT 23 | run: echo "RELEASE_DOT=$(lsb_release -sr)" >> $GITHUB_ENV 24 | - name: Set Ubuntu Release NUM 25 | run: echo "RELEASE_NUM=${RELEASE_DOT//[-._]/}" >> $GITHUB_ENV 26 | - name: Set Ubuntu Codename 27 | run: echo "RELEASE_NAME=$(lsb_release -sc)" >> $GITHUB_ENV 28 | - name: Cache swift package modules 29 | id: cache-spm-linux 30 | uses: actions/cache@v4 31 | env: 32 | cache-name: cache-spm 33 | with: 34 | path: .build 35 | key: ${{ runner.os }}-${{ env.RELEASE_DOT }}-${{ env.cache-name }}-${{ matrix.swift-version }}-${{ hashFiles('Package.resolved') }} 36 | restore-keys: | 37 | ${{ runner.os }}-${{ env.RELEASE_DOT }}-${{ env.cache-name }}-${{ matrix.swift-version }}- 38 | ${{ runner.os }}-${{ env.RELEASE_DOT }}-${{ env.cache-name }}- 39 | - name: Cache swift 40 | id: cache-swift-linux 41 | uses: actions/cache@v4 42 | env: 43 | cache-name: cache-swift 44 | with: 45 | path: swift-${{ env.SWIFT_VER }}-RELEASE-ubuntu${{ env.RELEASE_DOT }} 46 | key: ${{ runner.os }}-${{ env.cache-name }}-${{ matrix.swift-version }}-${{ env.RELEASE_DOT }} 47 | restore-keys: | 48 | ${{ runner.os }}-${{ env.cache-name }}-${{ matrix.swift-version }}- 49 | - name: Download Swift 50 | if: steps.cache-swift-linux.outputs.cache-hit != 'true' 51 | run: curl -O https://download.swift.org/swift-${SWIFT_VER}-release/ubuntu${RELEASE_NUM}/swift-${SWIFT_VER}-RELEASE/swift-${SWIFT_VER}-RELEASE-ubuntu${RELEASE_DOT}.tar.gz 52 | - name: Extract Swift 53 | if: steps.cache-swift-linux.outputs.cache-hit != 'true' 54 | run: tar xzf swift-${SWIFT_VER}-RELEASE-ubuntu${RELEASE_DOT}.tar.gz 55 | - name: Add Path 56 | run: echo "$GITHUB_WORKSPACE/swift-${SWIFT_VER}-RELEASE-ubuntu${RELEASE_DOT}/usr/bin" >> $GITHUB_PATH 57 | - name: Test 58 | run: swift test --enable-code-coverage 59 | - uses: sersoft-gmbh/swift-coverage-action@v4 60 | id: coverage-files 61 | with: 62 | fail-on-empty-output: true 63 | - name: Upload coverage to Codecov 64 | uses: codecov/codecov-action@v4 65 | with: 66 | fail_ci_if_error: true 67 | flags: swift-${{ matrix.swift-version }},ubuntu-${{ matrix.RELEASE_DOT }} 68 | verbose: true 69 | token: ${{ secrets.CODECOV_TOKEN }} 70 | files: ${{ join(fromJSON(steps.coverage-files.outputs.files), ',') }} 71 | build-macos: 72 | name: Build on macOS 73 | runs-on: ${{ matrix.os }} 74 | if: "!contains(github.event.head_commit.message, 'ci skip')" 75 | env: 76 | PACKAGE_NAME: Options 77 | strategy: 78 | matrix: 79 | include: 80 | - xcode: "/Applications/Xcode_14.1.app" 81 | os: macos-12 82 | iOSVersion: "16.1" 83 | watchOSVersion: "9.0" 84 | watchName: "Apple Watch Series 5 - 40mm" 85 | iPhoneName: "iPhone 12 mini" 86 | - xcode: "/Applications/Xcode_14.2.app" 87 | os: macos-12 88 | iOSVersion: "16.2" 89 | watchOSVersion: "9.1" 90 | watchName: "Apple Watch Ultra (49mm)" 91 | iPhoneName: "iPhone 14" 92 | - xcode: "/Applications/Xcode_15.0.1.app" 93 | os: macos-13 94 | iOSVersion: "17.0.1" 95 | watchOSVersion: "10.0" 96 | watchName: "Apple Watch Series 9 (41mm)" 97 | iPhoneName: "iPhone 15" 98 | - xcode: "/Applications/Xcode_15.1.app" 99 | os: macos-13 100 | iOSVersion: "17.2" 101 | watchOSVersion: "10.2" 102 | watchName: "Apple Watch Series 9 (45mm)" 103 | iPhoneName: "iPhone 15 Plus" 104 | - xcode: "/Applications/Xcode_15.2.app" 105 | os: macos-14 106 | iOSVersion: "17.2" 107 | watchOSVersion: "10.2" 108 | watchName: "Apple Watch Ultra (49mm)" 109 | iPhoneName: "iPhone 15 Pro" 110 | - xcode: "/Applications/Xcode_15.3.app" 111 | os: macos-14 112 | iOSVersion: "17.4" 113 | watchOSVersion: "10.4" 114 | watchName: "Apple Watch Ultra 2 (49mm)" 115 | iPhoneName: "iPhone 15 Pro Max" 116 | steps: 117 | - uses: actions/checkout@v4 118 | - name: Cache swift package modules 119 | id: cache-spm-macos 120 | uses: actions/cache@v4 121 | env: 122 | cache-name: cache-spm 123 | with: 124 | path: .build 125 | key: ${{ matrix.os }}-build-${{ env.cache-name }}-${{ matrix.xcode }}-${{ hashFiles('Package.resolved') }} 126 | restore-keys: | 127 | ${{ matrix.os }}-build-${{ env.cache-name }}-${{ matrix.xcode }}- 128 | - name: Cache mint 129 | if: startsWith(matrix.xcode,'/Applications/Xcode_15.3') 130 | id: cache-mint 131 | uses: actions/cache@v4 132 | env: 133 | cache-name: cache-mint 134 | with: 135 | path: .mint 136 | key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('Mintfile') }} 137 | restore-keys: | 138 | ${{ runner.os }}-build-${{ env.cache-name }}- 139 | ${{ runner.os }}-build- 140 | ${{ runner.os }}- 141 | - name: Set Xcode Name 142 | run: echo "XCODE_NAME=$(basename -- ${{ matrix.xcode }} | sed 's/\.[^.]*$//' | cut -d'_' -f2)" >> $GITHUB_ENV 143 | - name: Setup Xcode 144 | run: sudo xcode-select -s ${{ matrix.xcode }}/Contents/Developer 145 | - name: Install mint 146 | if: startsWith(matrix.xcode,'/Applications/Xcode_15.3') 147 | run: | 148 | brew update 149 | brew install mint 150 | - name: Build 151 | run: swift build 152 | - name: Run Swift Package tests 153 | run: swift test --enable-code-coverage 154 | - uses: sersoft-gmbh/swift-coverage-action@v4 155 | id: coverage-files-spm 156 | with: 157 | fail-on-empty-output: true 158 | - name: Upload coverage reports to Codecov 159 | uses: codecov/codecov-action@v4 160 | with: 161 | files: ${{ join(fromJSON(steps.coverage-files-spm.outputs.files), ',') }} 162 | token: ${{ secrets.CODECOV_TOKEN }} 163 | flags: macOS,${{ env.XCODE_NAME }},${{ matrix.runs-on }} 164 | - name: Clean up spm build directory 165 | run: rm -rf .build 166 | - name: Lint 167 | run: ./scripts/lint.sh 168 | if: startsWith(matrix.xcode,'/Applications/Xcode_15.3') 169 | # - name: Run iOS target tests 170 | # run: xcodebuild test -scheme ${{ env.PACKAGE_NAME }} -sdk "iphonesimulator" -destination 'platform=iOS Simulator,name=${{ matrix.iPhoneName }},OS=${{ matrix.iOSVersion }}' -enableCodeCoverage YES build test 171 | # - uses: sersoft-gmbh/swift-coverage-action@v4 172 | # id: coverage-files-iOS 173 | # with: 174 | # fail-on-empty-output: true 175 | # - name: Upload coverage to Codecov 176 | # uses: codecov/codecov-action@v4 177 | # with: 178 | # fail_ci_if_error: true 179 | # verbose: true 180 | # token: ${{ secrets.CODECOV_TOKEN }} 181 | # files: ${{ join(fromJSON(steps.coverage-files-iOS.outputs.files), ',') }} 182 | # flags: iOS,iOS${{ matrix.iOSVersion }},macOS,${{ env.XCODE_NAME }} 183 | # - name: Run watchOS target tests 184 | # run: xcodebuild test -scheme ${{ env.PACKAGE_NAME }} -sdk "watchsimulator" -destination 'platform=watchOS Simulator,name=${{ matrix.watchName }},OS=${{ matrix.watchOSVersion }}' -enableCodeCoverage YES build test 185 | # - uses: sersoft-gmbh/swift-coverage-action@v4 186 | # id: coverage-files-watchOS 187 | # with: 188 | # fail-on-empty-output: true 189 | # - name: Upload coverage to Codecov 190 | # uses: codecov/codecov-action@v4 191 | # with: 192 | # fail_ci_if_error: true 193 | # verbose: true 194 | # token: ${{ secrets.CODECOV_TOKEN }} 195 | # files: ${{ join(fromJSON(steps.coverage-files-watchOS.outputs.files), ',') }} 196 | # flags: watchOS,watchOS${{ matrix.watchOSVersion }},macOS,${{ env.XCODE_NAME }} 197 | build-self: 198 | name: Build on Self-Hosting macOS 199 | runs-on: [self-hosted, macOS] 200 | if: github.event.repository.owner.login == github.event.organization.login && !contains(github.event.head_commit.message, 'ci skip') 201 | steps: 202 | - uses: actions/checkout@v4 203 | - name: Cache swift package modules 204 | id: cache-spm-macos 205 | uses: actions/cache@v4 206 | env: 207 | cache-name: cache-spm 208 | with: 209 | path: .build 210 | key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('Package.resolved') }} 211 | restore-keys: | 212 | ${{ runner.os }}-build-${{ env.cache-name }}- 213 | ${{ runner.os }}-build- 214 | ${{ runner.os }}- 215 | - name: Cache mint 216 | id: cache-mint 217 | uses: actions/cache@v4 218 | env: 219 | cache-name: cache-mint 220 | with: 221 | path: .mint 222 | key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('Mintfile') }} 223 | restore-keys: | 224 | ${{ runner.os }}-build-${{ env.cache-name }}- 225 | ${{ runner.os }}-build- 226 | ${{ runner.os }}- 227 | - name: Build 228 | run: swift build 229 | - name: Run Swift Package tests 230 | run: swift test --enable-code-coverage 231 | - uses: sersoft-gmbh/swift-coverage-action@v4 232 | with: 233 | fail-on-empty-output: true 234 | - name: Upload coverage reports to Codecov 235 | uses: codecov/codecov-action@v4 236 | with: 237 | token: ${{ secrets.CODECOV_TOKEN }} 238 | flags: macOS,${{ env.XCODE_NAME }} 239 | - name: Clean up spm build directory 240 | run: rm -rf .build 241 | - name: Lint 242 | run: ./scripts/lint.sh 243 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/swift,swiftpm,swiftpackagemanager,xcode,macos 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=swift,swiftpm,swiftpackagemanager,xcode,macos 3 | 4 | ### macOS ### 5 | # General 6 | .DS_Store 7 | .AppleDouble 8 | .LSOverride 9 | 10 | # Icon must end with two \r 11 | Icon 12 | 13 | 14 | # Thumbnails 15 | ._* 16 | 17 | # Files that might appear in the root of a volume 18 | .DocumentRevisions-V100 19 | .fseventsd 20 | .Spotlight-V100 21 | .TemporaryItems 22 | .Trashes 23 | .VolumeIcon.icns 24 | .com.apple.timemachine.donotpresent 25 | 26 | # Directories potentially created on remote AFP share 27 | .AppleDB 28 | .AppleDesktop 29 | Network Trash Folder 30 | Temporary Items 31 | .apdisk 32 | 33 | ### macOS Patch ### 34 | # iCloud generated files 35 | *.icloud 36 | 37 | ### Swift ### 38 | # Xcode 39 | # 40 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 41 | 42 | ## User settings 43 | xcuserdata/ 44 | 45 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 46 | *.xcscmblueprint 47 | *.xccheckout 48 | 49 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 50 | build/ 51 | DerivedData/ 52 | *.moved-aside 53 | *.pbxuser 54 | !default.pbxuser 55 | *.mode1v3 56 | !default.mode1v3 57 | *.mode2v3 58 | !default.mode2v3 59 | *.perspectivev3 60 | !default.perspectivev3 61 | 62 | ## Obj-C/Swift specific 63 | *.hmap 64 | 65 | ## App packaging 66 | *.ipa 67 | *.dSYM.zip 68 | *.dSYM 69 | 70 | ## Playgrounds 71 | timeline.xctimeline 72 | playground.xcworkspace 73 | 74 | # Swift Package Manager 75 | # 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 | # 82 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 83 | # hence it is not needed unless you have added a package configuration file to your project 84 | .swiftpm 85 | 86 | .build/ 87 | 88 | # CocoaPods 89 | # 90 | # We recommend against adding the Pods directory to your .gitignore. However 91 | # you should judge for yourself, the pros and cons are mentioned at: 92 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 93 | # 94 | # Pods/ 95 | # 96 | # Add this line if you want to avoid checking in source code from the Xcode workspace 97 | # *.xcworkspace 98 | 99 | # Carthage 100 | # 101 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 102 | # Carthage/Checkouts 103 | 104 | Carthage/Build/ 105 | 106 | # Accio dependency management 107 | Dependencies/ 108 | .accio/ 109 | 110 | # fastlane 111 | # 112 | # It is recommended to not store the screenshots in the git repo. 113 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 114 | # For more information about the recommended setup visit: 115 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 116 | 117 | fastlane/report.xml 118 | fastlane/Preview.html 119 | fastlane/screenshots/**/*.png 120 | fastlane/test_output 121 | 122 | # Code Injection 123 | # 124 | # After new code Injection tools there's a generated folder /iOSInjectionProject 125 | # https://github.com/johnno1962/injectionforxcode 126 | 127 | iOSInjectionProject/ 128 | 129 | .mint 130 | Output 131 | 132 | # Due to support for 5.10 and below 133 | Package.resolved -------------------------------------------------------------------------------- /.hound.yml: -------------------------------------------------------------------------------- 1 | swiftlint: 2 | config_file: .swiftlint.yml 3 | -------------------------------------------------------------------------------- /.periphery.yml: -------------------------------------------------------------------------------- 1 | retain_public: true 2 | -------------------------------------------------------------------------------- /.spi.yml: -------------------------------------------------------------------------------- 1 | version: 1 2 | builder: 3 | configs: 4 | - documentation_targets: [Options] 5 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 5.7 2 | -------------------------------------------------------------------------------- /.swiftformat: -------------------------------------------------------------------------------- 1 | --indent 2 2 | --header "\n .*?\.swift\n SimulatorServices\n\n Created by Leo Dion.\n Copyright © {year} BrightDigit.\n\n Permission is hereby granted, free of charge, to any person\n obtaining a copy of this software and associated documentation\n files (the “Software”), to deal in the Software without\n restriction, including without limitation the rights to use,\n copy, modify, merge, publish, distribute, sublicense, and/or\n sell copies of the Software, and to permit persons to whom the\n Software is furnished to do so, subject to the following\n conditions:\n \n The above copyright notice and this permission notice shall be\n included in all copies or substantial portions of the Software.\n\n THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,\n EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\n OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n OTHER DEALINGS IN THE SOFTWARE.\n" 3 | --commas inline 4 | --disable wrapMultilineStatementBraces, redundantInternal 5 | --extensionacl on-declarations 6 | --decimalgrouping 3,4 7 | --exclude .build, DerivedData, .swiftpm 8 | -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | opt_in_rules: 2 | - array_init 3 | - attributes 4 | - closure_body_length 5 | - closure_end_indentation 6 | - closure_spacing 7 | - collection_alignment 8 | - conditional_returns_on_newline 9 | - contains_over_filter_count 10 | - contains_over_filter_is_empty 11 | - contains_over_first_not_nil 12 | - contains_over_range_nil_comparison 13 | - convenience_type 14 | - discouraged_object_literal 15 | - discouraged_optional_boolean 16 | - empty_collection_literal 17 | - empty_count 18 | - empty_string 19 | - empty_xctest_method 20 | - enum_case_associated_values_count 21 | - expiring_todo 22 | - explicit_acl 23 | - explicit_init 24 | - explicit_top_level_acl 25 | - fallthrough 26 | - fatal_error_message 27 | - file_name 28 | - file_name_no_space 29 | - file_types_order 30 | - first_where 31 | - flatmap_over_map_reduce 32 | - force_unwrapping 33 | - function_default_parameter_at_end 34 | - ibinspectable_in_extension 35 | - identical_operands 36 | - implicit_return 37 | - implicitly_unwrapped_optional 38 | - indentation_width 39 | - joined_default_parameter 40 | - last_where 41 | - legacy_multiple 42 | - legacy_random 43 | - literal_expression_end_indentation 44 | - lower_acl_than_parent 45 | - missing_docs 46 | - modifier_order 47 | - multiline_arguments 48 | - multiline_arguments_brackets 49 | - multiline_function_chains 50 | - multiline_literal_brackets 51 | - multiline_parameters 52 | - nimble_operator 53 | - nslocalizedstring_key 54 | - nslocalizedstring_require_bundle 55 | - number_separator 56 | - object_literal 57 | - operator_usage_whitespace 58 | - optional_enum_case_matching 59 | - overridden_super_call 60 | - override_in_extension 61 | - pattern_matching_keywords 62 | - prefer_self_type_over_type_of_self 63 | - prefer_zero_over_explicit_init 64 | - private_action 65 | - private_outlet 66 | - prohibited_interface_builder 67 | - prohibited_super_call 68 | - quick_discouraged_call 69 | - quick_discouraged_focused_test 70 | - quick_discouraged_pending_test 71 | - reduce_into 72 | - redundant_nil_coalescing 73 | - redundant_type_annotation 74 | - required_enum_case 75 | - single_test_class 76 | - sorted_first_last 77 | - sorted_imports 78 | - static_operator 79 | - strong_iboutlet 80 | - toggle_bool 81 | - trailing_closure 82 | - type_contents_order 83 | - unavailable_function 84 | - unneeded_parentheses_in_closure_argument 85 | - unowned_variable_capture 86 | - untyped_error_in_catch 87 | - vertical_parameter_alignment_on_call 88 | - vertical_whitespace_closing_braces 89 | - vertical_whitespace_opening_braces 90 | - xct_specific_matcher 91 | - yoda_condition 92 | analyzer_rules: 93 | - explicit_self 94 | - unused_declaration 95 | - unused_import 96 | type_body_length: 97 | - 100 98 | - 200 99 | file_length: 100 | - 200 101 | - 300 102 | function_body_length: 103 | - 18 104 | - 40 105 | function_parameter_count: 8 106 | line_length: 107 | - 90 108 | - 90 109 | identifier_name: 110 | excluded: 111 | - id 112 | excluded: 113 | - Tests 114 | - DerivedData 115 | - .build 116 | - .swiftpm 117 | indentation_width: 118 | indentation_width: 2 119 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Bright Digit, LLC 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 | 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /Mintfile: -------------------------------------------------------------------------------- 1 | nicklockwood/SwiftFormat@0.53.5 2 | realm/SwiftLint@0.54.0 -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.7.1 2 | 3 | // swiftlint:disable explicit_top_level_acl 4 | // swiftlint:disable prefixed_toplevel_constant 5 | // swiftlint:disable explicit_acl 6 | 7 | import PackageDescription 8 | 9 | let package = Package( 10 | name: "Options", 11 | products: [ 12 | .library( 13 | name: "Options", 14 | targets: ["Options"] 15 | ) 16 | ], 17 | dependencies: [ 18 | // Dependencies declare other packages that this package depends on. 19 | ], 20 | targets: [ 21 | .target( 22 | name: "Options", 23 | dependencies: [] 24 | ), 25 | .testTarget( 26 | name: "OptionsTests", 27 | dependencies: ["Options"] 28 | ) 29 | ] 30 | ) 31 | 32 | // swiftlint:enable explicit_top_level_acl 33 | // swiftlint:enable prefixed_toplevel_constant 34 | // swiftlint:enable explicit_acl 35 | -------------------------------------------------------------------------------- /Package@swift-5.10.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.10 2 | 3 | // swiftlint:disable explicit_top_level_acl 4 | // swiftlint:disable prefixed_toplevel_constant 5 | // swiftlint:disable explicit_acl 6 | 7 | import CompilerPluginSupport 8 | import PackageDescription 9 | 10 | let swiftSettings = [ 11 | SwiftSetting.enableUpcomingFeature("BareSlashRegexLiterals"), 12 | SwiftSetting.enableUpcomingFeature("ConciseMagicFile"), 13 | SwiftSetting.enableUpcomingFeature("ExistentialAny"), 14 | SwiftSetting.enableUpcomingFeature("ForwardTrailingClosures"), 15 | SwiftSetting.enableUpcomingFeature("ImplicitOpenExistentials"), 16 | SwiftSetting.enableUpcomingFeature("StrictConcurrency"), 17 | SwiftSetting.enableUpcomingFeature("DisableOutwardActorInference"), 18 | SwiftSetting.enableExperimentalFeature("StrictConcurrency") 19 | ] 20 | 21 | let package = Package( 22 | name: "Options", 23 | platforms: [ 24 | .macOS(.v10_15), 25 | .iOS(.v13), 26 | .tvOS(.v13), 27 | .watchOS(.v6), 28 | .macCatalyst(.v13), 29 | .visionOS(.v1) 30 | ], 31 | products: [ 32 | .library( 33 | name: "Options", 34 | targets: ["Options"] 35 | ) 36 | ], 37 | dependencies: [ 38 | .package(url: "https://github.com/apple/swift-syntax", from: "510.0.0") 39 | // Dependencies declare other packages that this package depends on. 40 | // .package(url: /* package url */, from: "1.0.0") 41 | ], 42 | targets: [ 43 | .target( 44 | name: "Options", 45 | dependencies: ["OptionsMacros"], 46 | swiftSettings: swiftSettings 47 | ), 48 | .macro( 49 | name: "OptionsMacros", 50 | dependencies: [ 51 | .product(name: "SwiftSyntaxMacros", package: "swift-syntax"), 52 | .product(name: "SwiftCompilerPlugin", package: "swift-syntax") 53 | ], 54 | swiftSettings: swiftSettings 55 | ), 56 | .testTarget( 57 | name: "OptionsTests", 58 | dependencies: ["Options"] 59 | ) 60 | ] 61 | ) 62 | 63 | // swiftlint:enable explicit_top_level_acl 64 | // swiftlint:enable prefixed_toplevel_constant 65 | // swiftlint:enable explicit_acl 66 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |

3 | Options 4 |

5 |

Options

6 | 7 | More powerful options for `Enum` and `OptionSet` types. 8 | 9 | [![SwiftPM](https://img.shields.io/badge/SPM-Linux%20%7C%20iOS%20%7C%20macOS%20%7C%20watchOS%20%7C%20tvOS-success?logo=swift)](https://swift.org) 10 | [![Twitter](https://img.shields.io/badge/twitter-@brightdigit-blue.svg?style=flat)](http://twitter.com/brightdigit) 11 | ![GitHub](https://img.shields.io/github/license/brightdigit/Options) 12 | ![GitHub issues](https://img.shields.io/github/issues/brightdigit/Options) 13 | ![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/brightdigit/Options/Options.yml?label=actions&logo=github&?branch=main) 14 | 15 | [![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fbrightdigit%2FOptions%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/brightdigit/Options) 16 | [![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fbrightdigit%2FOptions%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/brightdigit/Options) 17 | 18 | [![Codecov](https://img.shields.io/codecov/c/github/brightdigit/Options)](https://codecov.io/gh/brightdigit/Options) 19 | [![CodeFactor Grade](https://img.shields.io/codefactor/grade/github/brightdigit/Options)](https://www.codefactor.io/repository/github/brightdigit/Options) 20 | [![codebeat badge](https://codebeat.co/badges/c47b7e58-867c-410b-80c5-57e10140ba0f)](https://codebeat.co/projects/github-com-brightdigit-mistkit-main) 21 | [![Code Climate maintainability](https://img.shields.io/codeclimate/maintainability/brightdigit/Options)](https://codeclimate.com/github/brightdigit/Options) 22 | [![Code Climate technical debt](https://img.shields.io/codeclimate/tech-debt/brightdigit/Options?label=debt)](https://codeclimate.com/github/brightdigit/Options) 23 | [![Code Climate issues](https://img.shields.io/codeclimate/issues/brightdigit/Options)](https://codeclimate.com/github/brightdigit/Options) 24 | [![Reviewed by Hound](https://img.shields.io/badge/Reviewed_by-Hound-8E64B0.svg)](https://houndci.com) 25 | 26 | 27 | # Table of Contents 28 | 29 | * [Introduction](#introduction) 30 | * [Requirements](#requirements) 31 | * [Installation](#installation) 32 | * [Usage](#usage) 33 | * [Versatile Options with Enums and OptionSets](#versatile-options-with-enums-and-optionsets) 34 | * [Multiple Value Types](#multiple-value-types) 35 | * [Creating an OptionSet](#creating-an-optionset) 36 | * [Further Code Documentation](#further-code-documentation) 37 | * [License](#license) 38 | 39 | # Introduction 40 | 41 | **Options** provides a powerful set of features for `Enum` and `OptionSet` types: 42 | 43 | - Providing additional representations for `Enum` types besides the `RawType rawValue` 44 | - Being able to interchange between `Enum` and `OptionSet` types 45 | - Using an additional value type for a `Codable` `OptionSet` 46 | 47 | # Requirements 48 | 49 | **Apple Platforms** 50 | 51 | - Xcode 14.1 or later 52 | - Swift 5.7.1 or later 53 | - iOS 16 / watchOS 9 / tvOS 16 / macOS 12 or later deployment targets 54 | 55 | **Linux** 56 | 57 | - Ubuntu 20.04 or later 58 | - Swift 5.7.1 or later 59 | 60 | # Installation 61 | 62 | Use the Swift Package Manager to install this library via the repository url: 63 | 64 | ``` 65 | https://github.com/brightdigit/Options.git 66 | ``` 67 | 68 | Use version up to `1.0`. 69 | 70 | # Usage 71 | 72 | ## Versatile Options 73 | 74 | Let's say we are using an `Enum` for a list of popular social media networks: 75 | 76 | ```swift 77 | enum SocialNetwork : Int { 78 | case digg 79 | case aim 80 | case bebo 81 | case delicious 82 | case eworld 83 | case googleplus 84 | case itunesping 85 | case jaiku 86 | case miiverse 87 | case musically 88 | case orkut 89 | case posterous 90 | case stumbleupon 91 | case windowslive 92 | case yahoo 93 | } 94 | ``` 95 | 96 | We'll be using this as a way to define a particular social handle: 97 | 98 | ```swift 99 | struct SocialHandle { 100 | let name : String 101 | let network : SocialNetwork 102 | } 103 | ``` 104 | 105 | However we also want to provide a way to have a unique set of social networks available: 106 | 107 | ```swift 108 | struct SocialNetworkSet : Int, OptionSet { 109 | ... 110 | } 111 | 112 | let user : User 113 | let networks : SocialNetworkSet = user.availableNetworks() 114 | ``` 115 | 116 | We can then simply use ``Options()`` macro to generate both these types: 117 | 118 | ```swift 119 | @Options 120 | enum SocialNetwork : Int { 121 | case digg 122 | case aim 123 | case bebo 124 | case delicious 125 | case eworld 126 | case googleplus 127 | case itunesping 128 | case jaiku 129 | case miiverse 130 | case musically 131 | case orkut 132 | case posterous 133 | case stumbleupon 134 | case windowslive 135 | case yahoo 136 | } 137 | ``` 138 | 139 | Now we can use the newly create `SocialNetworkSet` type to store a set of values: 140 | 141 | ```swift 142 | let networks : SocialNetworkSet 143 | networks = [.aim, .delicious, .googleplus, .windowslive] 144 | ``` 145 | 146 | ## Multiple Value Types 147 | 148 | With the ``Options()`` macro, we add the ability to encode and decode values not only from their raw value but also from a another type such as a string. This is useful for when you want to store the values in JSON format. 149 | 150 | For instance, with a type like `SocialNetwork` we need need to store the value as an Integer: 151 | 152 | ```json 153 | 5 154 | ``` 155 | 156 | However by adding the ``Options()`` macro we can also decode from a String: 157 | 158 | ``` 159 | "googleplus" 160 | ``` 161 | 162 | ## Creating an OptionSet 163 | 164 | We can also have a new `OptionSet` type created. ``Options()`` create a new `OptionSet` type with the suffix `-Set`. This new `OptionSet` will automatically work with your enum to create a distinct set of values. Additionally it will decode and encode your values as an Array of String. This means the value: 165 | 166 | ```swift 167 | [.aim, .delicious, .googleplus, .windowslive] 168 | ``` 169 | 170 | is encoded as: 171 | 172 | ```json 173 | ["aim", "delicious", "googleplus", "windowslive"] 174 | ``` 175 | 176 | # Further Code Documentation 177 | 178 | [Documentation Here](https://swiftpackageindex.com/brightdigit/Options/main/documentation/options) 179 | 180 | # License 181 | 182 | This code is distributed under the MIT license. See the [LICENSE](LICENSE) file for more info. 183 | -------------------------------------------------------------------------------- /Scripts/docc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | xcodebuild docbuild -scheme SimulatorServices -derivedDataPath DerivedData -destination 'platform=macOS' 3 | $(xcrun --find docc) process-archive transform-for-static-hosting DerivedData/Build/Products/Debug/SimulatorServices.doccarchive --output-path Output -------------------------------------------------------------------------------- /Scripts/gh-md-toc: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # 4 | # Steps: 5 | # 6 | # 1. Download corresponding html file for some README.md: 7 | # curl -s $1 8 | # 9 | # 2. Discard rows where no substring 'user-content-' (github's markup): 10 | # awk '/user-content-/ { ... 11 | # 12 | # 3.1 Get last number in each row like ' ... sitemap.js.*<\/h/)+2, RLENGTH-5) 21 | # 22 | # 5. Find anchor and insert it inside "(...)": 23 | # substr($0, match($0, "href=\"[^\"]+?\" ")+6, RLENGTH-8) 24 | # 25 | 26 | gh_toc_version="0.10.0" 27 | 28 | gh_user_agent="gh-md-toc v$gh_toc_version" 29 | 30 | # 31 | # Download rendered into html README.md by its url. 32 | # 33 | # 34 | gh_toc_load() { 35 | local gh_url=$1 36 | 37 | if type curl &>/dev/null; then 38 | curl --user-agent "$gh_user_agent" -s "$gh_url" 39 | elif type wget &>/dev/null; then 40 | wget --user-agent="$gh_user_agent" -qO- "$gh_url" 41 | else 42 | echo "Please, install 'curl' or 'wget' and try again." 43 | exit 1 44 | fi 45 | } 46 | 47 | # 48 | # Converts local md file into html by GitHub 49 | # 50 | # -> curl -X POST --data '{"text": "Hello world github/linguist#1 **cool**, and #1!"}' https://api.github.com/markdown 51 | #

Hello world github/linguist#1 cool, and #1!

'" 52 | gh_toc_md2html() { 53 | local gh_file_md=$1 54 | local skip_header=$2 55 | 56 | URL=https://api.github.com/markdown/raw 57 | 58 | if [ -n "$GH_TOC_TOKEN" ]; then 59 | TOKEN=$GH_TOC_TOKEN 60 | else 61 | TOKEN_FILE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/token.txt" 62 | if [ -f "$TOKEN_FILE" ]; then 63 | TOKEN="$(cat "$TOKEN_FILE")" 64 | fi 65 | fi 66 | if [ -n "${TOKEN}" ]; then 67 | AUTHORIZATION="Authorization: token ${TOKEN}" 68 | fi 69 | 70 | local gh_tmp_file_md=$gh_file_md 71 | if [ "$skip_header" = "yes" ]; then 72 | if grep -Fxq "" "$gh_src"; then 73 | # cut everything before the toc 74 | gh_tmp_file_md=$gh_file_md~~ 75 | sed '1,//d' "$gh_file_md" > "$gh_tmp_file_md" 76 | fi 77 | fi 78 | 79 | # echo $URL 1>&2 80 | OUTPUT=$(curl -s \ 81 | --user-agent "$gh_user_agent" \ 82 | --data-binary @"$gh_tmp_file_md" \ 83 | -H "Content-Type:text/plain" \ 84 | -H "$AUTHORIZATION" \ 85 | "$URL") 86 | 87 | rm -f "${gh_file_md}~~" 88 | 89 | if [ "$?" != "0" ]; then 90 | echo "XXNetworkErrorXX" 91 | fi 92 | if [ "$(echo "${OUTPUT}" | awk '/API rate limit exceeded/')" != "" ]; then 93 | echo "XXRateLimitXX" 94 | else 95 | echo "${OUTPUT}" 96 | fi 97 | } 98 | 99 | 100 | # 101 | # Is passed string url 102 | # 103 | gh_is_url() { 104 | case $1 in 105 | https* | http*) 106 | echo "yes";; 107 | *) 108 | echo "no";; 109 | esac 110 | } 111 | 112 | # 113 | # TOC generator 114 | # 115 | gh_toc(){ 116 | local gh_src=$1 117 | local gh_src_copy=$1 118 | local gh_ttl_docs=$2 119 | local need_replace=$3 120 | local no_backup=$4 121 | local no_footer=$5 122 | local indent=$6 123 | local skip_header=$7 124 | 125 | if [ "$gh_src" = "" ]; then 126 | echo "Please, enter URL or local path for a README.md" 127 | exit 1 128 | fi 129 | 130 | 131 | # Show "TOC" string only if working with one document 132 | if [ "$gh_ttl_docs" = "1" ]; then 133 | 134 | echo "Table of Contents" 135 | echo "=================" 136 | echo "" 137 | gh_src_copy="" 138 | 139 | fi 140 | 141 | if [ "$(gh_is_url "$gh_src")" == "yes" ]; then 142 | gh_toc_load "$gh_src" | gh_toc_grab "$gh_src_copy" "$indent" 143 | if [ "${PIPESTATUS[0]}" != "0" ]; then 144 | echo "Could not load remote document." 145 | echo "Please check your url or network connectivity" 146 | exit 1 147 | fi 148 | if [ "$need_replace" = "yes" ]; then 149 | echo 150 | echo "!! '$gh_src' is not a local file" 151 | echo "!! Can't insert the TOC into it." 152 | echo 153 | fi 154 | else 155 | local rawhtml 156 | rawhtml=$(gh_toc_md2html "$gh_src" "$skip_header") 157 | if [ "$rawhtml" == "XXNetworkErrorXX" ]; then 158 | echo "Parsing local markdown file requires access to github API" 159 | echo "Please make sure curl is installed and check your network connectivity" 160 | exit 1 161 | fi 162 | if [ "$rawhtml" == "XXRateLimitXX" ]; then 163 | echo "Parsing local markdown file requires access to github API" 164 | echo "Error: You exceeded the hourly limit. See: https://developer.github.com/v3/#rate-limiting" 165 | TOKEN_FILE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/token.txt" 166 | echo "or place GitHub auth token here: ${TOKEN_FILE}" 167 | exit 1 168 | fi 169 | local toc 170 | toc=`echo "$rawhtml" | gh_toc_grab "$gh_src_copy" "$indent"` 171 | echo "$toc" 172 | if [ "$need_replace" = "yes" ]; then 173 | if grep -Fxq "" "$gh_src" && grep -Fxq "" "$gh_src"; then 174 | echo "Found markers" 175 | else 176 | echo "You don't have or in your file...exiting" 177 | exit 1 178 | fi 179 | local ts="<\!--ts-->" 180 | local te="<\!--te-->" 181 | local dt 182 | dt=$(date +'%F_%H%M%S') 183 | local ext=".orig.${dt}" 184 | local toc_path="${gh_src}.toc.${dt}" 185 | local toc_createdby="" 186 | local toc_footer 187 | toc_footer="" 188 | # http://fahdshariff.blogspot.ru/2012/12/sed-mutli-line-replacement-between-two.html 189 | # clear old TOC 190 | sed -i"${ext}" "/${ts}/,/${te}/{//!d;}" "$gh_src" 191 | # create toc file 192 | echo "${toc}" > "${toc_path}" 193 | if [ "${no_footer}" != "yes" ]; then 194 | echo -e "\n${toc_createdby}\n${toc_footer}\n" >> "$toc_path" 195 | fi 196 | 197 | # insert toc file 198 | if ! sed --version > /dev/null 2>&1; then 199 | sed -i "" "/${ts}/r ${toc_path}" "$gh_src" 200 | else 201 | sed -i "/${ts}/r ${toc_path}" "$gh_src" 202 | fi 203 | echo 204 | if [ "${no_backup}" = "yes" ]; then 205 | rm "$toc_path" "$gh_src$ext" 206 | fi 207 | echo "!! TOC was added into: '$gh_src'" 208 | if [ -z "${no_backup}" ]; then 209 | echo "!! Origin version of the file: '${gh_src}${ext}'" 210 | echo "!! TOC added into a separate file: '${toc_path}'" 211 | fi 212 | echo 213 | fi 214 | fi 215 | } 216 | 217 | # 218 | # Grabber of the TOC from rendered html 219 | # 220 | # $1 - a source url of document. 221 | # It's need if TOC is generated for multiple documents. 222 | # $2 - number of spaces used to indent. 223 | # 224 | gh_toc_grab() { 225 | 226 | href_regex="/href=\"[^\"]+?\"/" 227 | common_awk_script=' 228 | modified_href = "" 229 | split(href, chars, "") 230 | for (i=1;i <= length(href); i++) { 231 | c = chars[i] 232 | res = "" 233 | if (c == "+") { 234 | res = " " 235 | } else { 236 | if (c == "%") { 237 | res = "\\x" 238 | } else { 239 | res = c "" 240 | } 241 | } 242 | modified_href = modified_href res 243 | } 244 | print sprintf("%*s", (level-1)*'"$2"', "") "* [" text "](" gh_url modified_href ")" 245 | ' 246 | if [ "`uname -s`" == "OS/390" ]; then 247 | grepcmd="pcregrep -o" 248 | echoargs="" 249 | awkscript='{ 250 | level = substr($0, 3, 1) 251 | text = substr($0, match($0, /<\/span><\/a>[^<]*<\/h/)+11, RLENGTH-14) 252 | href = substr($0, match($0, '$href_regex')+6, RLENGTH-7) 253 | '"$common_awk_script"' 254 | }' 255 | else 256 | grepcmd="grep -Eo" 257 | echoargs="-e" 258 | awkscript='{ 259 | level = substr($0, 3, 1) 260 | text = substr($0, match($0, /">.*<\/h/)+2, RLENGTH-5) 261 | href = substr($0, match($0, '$href_regex')+6, RLENGTH-7) 262 | '"$common_awk_script"' 263 | }' 264 | fi 265 | 266 | # if closed is on the new line, then move it on the prev line 267 | # for example: 268 | # was: The command foo1 269 | # 270 | # became: The command foo1 271 | sed -e ':a' -e 'N' -e '$!ba' -e 's/\n<\/h/<\/h/g' | 272 | 273 | # Sometimes a line can start with . Fix that. 274 | sed -e ':a' -e 'N' -e '$!ba' -e 's/\n//g' | sed 's/<\/code>//g' | 281 | 282 | # remove g-emoji 283 | sed 's/]*[^<]*<\/g-emoji> //g' | 284 | 285 | # now all rows are like: 286 | #

title

.. 287 | # format result line 288 | # * $0 - whole string 289 | # * last element of each row: "/dev/null; then 313 | $tool --version | head -n 1 314 | else 315 | echo "not installed" 316 | fi 317 | done 318 | } 319 | 320 | show_help() { 321 | local app_name 322 | app_name=$(basename "$0") 323 | echo "GitHub TOC generator ($app_name): $gh_toc_version" 324 | echo "" 325 | echo "Usage:" 326 | echo " $app_name [options] src [src] Create TOC for a README file (url or local path)" 327 | echo " $app_name - Create TOC for markdown from STDIN" 328 | echo " $app_name --help Show help" 329 | echo " $app_name --version Show version" 330 | echo "" 331 | echo "Options:" 332 | echo " --indent Set indent size. Default: 3." 333 | echo " --insert Insert new TOC into original file. For local files only. Default: false." 334 | echo " See https://github.com/ekalinin/github-markdown-toc/issues/41 for details." 335 | echo " --no-backup Remove backup file. Set --insert as well. Default: false." 336 | echo " --hide-footer Do not write date & author of the last TOC update. Set --insert as well. Default: false." 337 | echo " --skip-header Hide entry of the topmost headlines. Default: false." 338 | echo " See https://github.com/ekalinin/github-markdown-toc/issues/125 for details." 339 | echo "" 340 | } 341 | 342 | # 343 | # Options handlers 344 | # 345 | gh_toc_app() { 346 | local need_replace="no" 347 | local indent=3 348 | 349 | if [ "$1" = '--help' ] || [ $# -eq 0 ] ; then 350 | show_help 351 | return 352 | fi 353 | 354 | if [ "$1" = '--version' ]; then 355 | show_version 356 | return 357 | fi 358 | 359 | if [ "$1" = '--indent' ]; then 360 | indent="$2" 361 | shift 2 362 | fi 363 | 364 | if [ "$1" = "-" ]; then 365 | if [ -z "$TMPDIR" ]; then 366 | TMPDIR="/tmp" 367 | elif [ -n "$TMPDIR" ] && [ ! -d "$TMPDIR" ]; then 368 | mkdir -p "$TMPDIR" 369 | fi 370 | local gh_tmp_md 371 | if [ "`uname -s`" == "OS/390" ]; then 372 | local timestamp 373 | timestamp=$(date +%m%d%Y%H%M%S) 374 | gh_tmp_md="$TMPDIR/tmp.$timestamp" 375 | else 376 | gh_tmp_md=$(mktemp "$TMPDIR/tmp.XXXXXX") 377 | fi 378 | while read -r input; do 379 | echo "$input" >> "$gh_tmp_md" 380 | done 381 | gh_toc_md2html "$gh_tmp_md" | gh_toc_grab "" "$indent" 382 | return 383 | fi 384 | 385 | if [ "$1" = '--insert' ]; then 386 | need_replace="yes" 387 | shift 388 | fi 389 | 390 | if [ "$1" = '--no-backup' ]; then 391 | need_replace="yes" 392 | no_backup="yes" 393 | shift 394 | fi 395 | 396 | if [ "$1" = '--hide-footer' ]; then 397 | need_replace="yes" 398 | no_footer="yes" 399 | shift 400 | fi 401 | 402 | if [ "$1" = '--skip-header' ]; then 403 | skip_header="yes" 404 | shift 405 | fi 406 | 407 | 408 | for md in "$@" 409 | do 410 | echo "" 411 | gh_toc "$md" "$#" "$need_replace" "$no_backup" "$no_footer" "$indent" "$skip_header" 412 | done 413 | 414 | echo "" 415 | echo "" 416 | } 417 | 418 | # 419 | # Entry point 420 | # 421 | gh_toc_app "$@" -------------------------------------------------------------------------------- /Scripts/lint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ -z "$SRCROOT" ]; then 4 | SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) 5 | PACKAGE_DIR="${SCRIPT_DIR}/.." 6 | else 7 | PACKAGE_DIR="${SRCROOT}" 8 | fi 9 | 10 | if [ -z "$GITHUB_ACTION" ]; then 11 | MINT_CMD="/opt/homebrew/bin/mint" 12 | else 13 | MINT_CMD="mint" 14 | fi 15 | 16 | export MINT_PATH="$PACKAGE_DIR/.mint" 17 | MINT_ARGS="-n -m $PACKAGE_DIR/Mintfile --silent" 18 | MINT_RUN="$MINT_CMD run $MINT_ARGS" 19 | 20 | pushd $PACKAGE_DIR 21 | 22 | $MINT_CMD bootstrap -m Mintfile 23 | 24 | if [ "$LINT_MODE" == "NONE" ]; then 25 | exit 26 | elif [ "$LINT_MODE" == "STRICT" ]; then 27 | SWIFTFORMAT_OPTIONS="" 28 | SWIFTLINT_OPTIONS="--strict" 29 | else 30 | SWIFTFORMAT_OPTIONS="" 31 | SWIFTLINT_OPTIONS="" 32 | fi 33 | 34 | pushd $PACKAGE_DIR 35 | 36 | if [ -z "$CI" ]; then 37 | $MINT_RUN swiftformat . 38 | $MINT_RUN swiftlint --fix 39 | fi 40 | 41 | $MINT_RUN swiftformat --lint $SWIFTFORMAT_OPTIONS . 42 | $MINT_RUN swiftlint lint $SWIFTLINT_OPTIONS 43 | 44 | popd 45 | -------------------------------------------------------------------------------- /Sources/Options/Array.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Array.swift 3 | // SimulatorServices 4 | // 5 | // Created by Leo Dion. 6 | // Copyright © 2024 BrightDigit. 7 | // 8 | // Permission is hereby granted, free of charge, to any person 9 | // obtaining a copy of this software and associated documentation 10 | // files (the “Software”), to deal in the Software without 11 | // restriction, including without limitation the rights to use, 12 | // copy, modify, merge, publish, distribute, sublicense, and/or 13 | // sell copies of the Software, and to permit persons to whom the 14 | // Software is furnished to do so, subject to the following 15 | // conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | // OTHER DEALINGS IN THE SOFTWARE. 28 | // 29 | 30 | // swiftlint:disable:next line_length 31 | @available(*, deprecated, renamed: "MappedValueGenericRepresented", message: "Use MappedValueGenericRepresented instead.") 32 | public protocol MappedValueCollectionRepresented: MappedValueRepresented 33 | where MappedValueType: Sequence {} 34 | 35 | extension Array: MappedValues where Element: Equatable {} 36 | 37 | extension Collection where Element: Equatable, Self: MappedValues { 38 | /// Get the index based on the value passed. 39 | /// - Parameter value: Value to search. 40 | /// - Returns: Index found. 41 | public func key(value: Element) throws -> Self.Index { 42 | guard let index = firstIndex(of: value) else { 43 | throw MappedValueRepresentableError.valueNotFound 44 | } 45 | 46 | return index 47 | } 48 | 49 | /// Gets the value based on the index. 50 | /// - Parameter key: The index. 51 | /// - Returns: The value at index. 52 | public func value(key: Self.Index) throws -> Element { 53 | guard key < endIndex, key >= startIndex else { 54 | throw MappedValueRepresentableError.valueNotFound 55 | } 56 | return self[key] 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Sources/Options/CodingOptions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CodingOptions.swift 3 | // SimulatorServices 4 | // 5 | // Created by Leo Dion. 6 | // Copyright © 2024 BrightDigit. 7 | // 8 | // Permission is hereby granted, free of charge, to any person 9 | // obtaining a copy of this software and associated documentation 10 | // files (the “Software”), to deal in the Software without 11 | // restriction, including without limitation the rights to use, 12 | // copy, modify, merge, publish, distribute, sublicense, and/or 13 | // sell copies of the Software, and to permit persons to whom the 14 | // Software is furnished to do so, subject to the following 15 | // conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | // OTHER DEALINGS IN THE SOFTWARE. 28 | // 29 | 30 | import Foundation 31 | 32 | /// Options for how a ``MappedValueRepresentable`` type is encoding and decoded. 33 | public struct CodingOptions: OptionSet, Sendable { 34 | /// Allow decoding from String 35 | public static let allowMappedValueDecoding: CodingOptions = .init(rawValue: 1) 36 | 37 | /// Encode the value as a String. 38 | public static let encodeAsMappedValue: CodingOptions = .init(rawValue: 2) 39 | 40 | /// Default options. 41 | public static let `default`: CodingOptions = 42 | [.allowMappedValueDecoding, encodeAsMappedValue] 43 | 44 | public let rawValue: Int 45 | 46 | public init(rawValue: Int) { 47 | self.rawValue = rawValue 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Sources/Options/Dictionary.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Dictionary.swift 3 | // SimulatorServices 4 | // 5 | // Created by Leo Dion. 6 | // Copyright © 2024 BrightDigit. 7 | // 8 | // Permission is hereby granted, free of charge, to any person 9 | // obtaining a copy of this software and associated documentation 10 | // files (the “Software”), to deal in the Software without 11 | // restriction, including without limitation the rights to use, 12 | // copy, modify, merge, publish, distribute, sublicense, and/or 13 | // sell copies of the Software, and to permit persons to whom the 14 | // Software is furnished to do so, subject to the following 15 | // conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | // OTHER DEALINGS IN THE SOFTWARE. 28 | // 29 | 30 | // swiftlint:disable:next line_length 31 | @available(*, deprecated, renamed: "MappedValueGenericRepresented", message: "Use MappedValueGenericRepresented instead.") 32 | public protocol MappedValueDictionaryRepresented: MappedValueRepresented 33 | where MappedValueType == [Int: MappedType] {} 34 | 35 | extension Dictionary: MappedValues where Value: Equatable { 36 | public func key(value: Value) throws -> Key { 37 | let pair = first { $0.value == value } 38 | guard let key = pair?.key else { 39 | throw MappedValueRepresentableError.valueNotFound 40 | } 41 | 42 | return key 43 | } 44 | 45 | public func value(key: Key) throws -> Value { 46 | guard let value = self[key] else { 47 | throw MappedValueRepresentableError.valueNotFound 48 | } 49 | return value 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Sources/Options/Documentation.docc/Documentation.md: -------------------------------------------------------------------------------- 1 | # ``Options`` 2 | 3 | More powerful options for `Enum` and `OptionSet` types. 4 | 5 | ## Overview 6 | 7 | **Options** provides a powerful set of features for `Enum` and `OptionSet` types: 8 | 9 | - Providing additional representations for `Enum` types besides the `RawType rawValue` 10 | - Being able to interchange between `Enum` and `OptionSet` types 11 | - Using an additional value type for a `Codable` `OptionSet` 12 | 13 | ### Requirements 14 | 15 | **Apple Platforms** 16 | 17 | - Xcode 14.1 or later 18 | - Swift 5.7.1 or later 19 | - iOS 16 / watchOS 9 / tvOS 16 / macOS 12 or later deployment targets 20 | 21 | **Linux** 22 | 23 | - Ubuntu 20.04 or later 24 | - Swift 5.7.1 or later 25 | 26 | ### Installation 27 | 28 | Use the Swift Package Manager to install this library via the repository url: 29 | 30 | ``` 31 | https://github.com/brightdigit/Options.git 32 | ``` 33 | 34 | Use version up to `1.0`. 35 | 36 | ### Versatile Options 37 | 38 | Let's say we are using an `Enum` for a list of popular social media networks: 39 | 40 | ```swift 41 | enum SocialNetwork : Int { 42 | case digg 43 | case aim 44 | case bebo 45 | case delicious 46 | case eworld 47 | case googleplus 48 | case itunesping 49 | case jaiku 50 | case miiverse 51 | case musically 52 | case orkut 53 | case posterous 54 | case stumbleupon 55 | case windowslive 56 | case yahoo 57 | } 58 | ``` 59 | 60 | We'll be using this as a way to define a particular social handle: 61 | 62 | ```swift 63 | struct SocialHandle { 64 | let name : String 65 | let network : SocialNetwork 66 | } 67 | ``` 68 | 69 | However we also want to provide a way to have a unique set of social networks available: 70 | 71 | ```swift 72 | struct SocialNetworkSet : Int, OptionSet { 73 | ... 74 | } 75 | 76 | let user : User 77 | let networks : SocialNetworkSet = user.availableNetworks() 78 | ``` 79 | 80 | We can then simply use ``Options()`` macro to generate both these types: 81 | 82 | ```swift 83 | @Options 84 | enum SocialNetwork : Int { 85 | case digg 86 | case aim 87 | case bebo 88 | case delicious 89 | case eworld 90 | case googleplus 91 | case itunesping 92 | case jaiku 93 | case miiverse 94 | case musically 95 | case orkut 96 | case posterous 97 | case stumbleupon 98 | case windowslive 99 | case yahoo 100 | } 101 | ``` 102 | 103 | Now we can use the newly create `SocialNetworkSet` type to store a set of values: 104 | 105 | ```swift 106 | let networks : SocialNetworkSet 107 | networks = [.aim, .delicious, .googleplus, .windowslive] 108 | ``` 109 | 110 | ### Multiple Value Types 111 | 112 | With the ``Options()`` macro, we add the ability to encode and decode values not only from their raw value but also from a another type such as a string. This is useful for when you want to store the values in JSON format. 113 | 114 | For instance, with a type like `SocialNetwork` we need need to store the value as an Integer: 115 | 116 | ```json 117 | 5 118 | ``` 119 | 120 | However by adding the ``Options()`` macro we can also decode from a String: 121 | 122 | ``` 123 | "googleplus" 124 | ``` 125 | 126 | ### Creating an OptionSet 127 | 128 | We can also have a new `OptionSet` type created. ``Options()`` create a new `OptionSet` type with the suffix `-Set`. This new `OptionSet` will automatically work with your enum to create a distinct set of values. Additionally it will decode and encode your values as an Array of String. This means the value: 129 | 130 | ```swift 131 | [.aim, .delicious, .googleplus, .windowslive] 132 | ``` 133 | 134 | is encoded as: 135 | 136 | ```json 137 | ["aim", "delicious", "googleplus", "windowslive"] 138 | ``` 139 | 140 | ## Topics 141 | 142 | ### Options conformance 143 | 144 | - ``Options()`` 145 | - ``MappedValueRepresentable`` 146 | - ``MappedValueRepresented`` 147 | - ``EnumSet`` 148 | 149 | ### Advanced customization 150 | 151 | - ``CodingOptions`` 152 | - ``MappedValues`` 153 | 154 | ### Errors 155 | 156 | - ``MappedValueRepresentableError-2k4ki`` 157 | 158 | ### Deprecated 159 | 160 | - ``MappedValueCollectionRepresented`` 161 | - ``MappedValueDictionaryRepresented`` 162 | - ``MappedEnum`` 163 | -------------------------------------------------------------------------------- /Sources/Options/EnumSet.swift: -------------------------------------------------------------------------------- 1 | /// Generic struct for using Enums with `RawValue`. 2 | /// 3 | /// If you have an `enum` such as: 4 | /// ```swift 5 | /// @Options 6 | /// enum SocialNetwork : Int { 7 | /// case digg 8 | /// case aim 9 | /// case bebo 10 | /// case delicious 11 | /// case eworld 12 | /// case googleplus 13 | /// case itunesping 14 | /// case jaiku 15 | /// case miiverse 16 | /// case musically 17 | /// case orkut 18 | /// case posterous 19 | /// case stumbleupon 20 | /// case windowslive 21 | /// case yahoo 22 | /// } 23 | /// ``` 24 | /// An ``EnumSet`` could be used to store multiple values as an `OptionSet`: 25 | /// ```swift 26 | /// let socialNetworks : EnumSet = 27 | /// [.digg, .aim, .yahoo, .miiverse] 28 | /// ``` 29 | public struct EnumSet: 30 | OptionSet, Sendable, ExpressibleByArrayLiteral 31 | where EnumType.RawValue: FixedWidthInteger & Sendable { 32 | public typealias RawValue = EnumType.RawValue 33 | 34 | /// Raw Value of the OptionSet 35 | public let rawValue: RawValue 36 | 37 | /// Creates the EnumSet based on the `rawValue` 38 | /// - Parameter rawValue: Integer raw value of the OptionSet 39 | public init(rawValue: RawValue) { 40 | self.rawValue = rawValue 41 | } 42 | 43 | public init(arrayLiteral elements: EnumType...) { 44 | self.init(values: elements) 45 | } 46 | 47 | /// Creates the EnumSet based on the values in the array. 48 | /// - Parameter values: Array of enum values. 49 | public init(values: [EnumType]) { 50 | let set = Set(values.map(\.rawValue)) 51 | rawValue = Self.cumulativeValue(basedOnRawValues: set) 52 | } 53 | 54 | internal static func cumulativeValue( 55 | basedOnRawValues rawValues: Set) -> RawValue { 56 | rawValues.map { 1 << $0 }.reduce(0, |) 57 | } 58 | } 59 | 60 | extension FixedWidthInteger { 61 | fileprivate static var one: Self { 62 | 1 63 | } 64 | } 65 | 66 | extension EnumSet where EnumType: CaseIterable { 67 | internal static func enums(basedOnRawValue rawValue: RawValue) -> [EnumType] { 68 | let cases = EnumType.allCases.sorted { $0.rawValue < $1.rawValue } 69 | var values = [EnumType]() 70 | var current = rawValue 71 | for item in cases { 72 | guard current > 0 else { 73 | break 74 | } 75 | let rawValue = RawValue.one << item.rawValue 76 | if current & rawValue != .zero { 77 | values.append(item) 78 | current -= rawValue 79 | } 80 | } 81 | return values 82 | } 83 | 84 | /// Returns an array of the enum values based on the OptionSet 85 | /// - Returns: Array for each value represented by the enum. 86 | public func array() -> [EnumType] { 87 | Self.enums(basedOnRawValue: rawValue) 88 | } 89 | } 90 | 91 | #if swift(>=5.9) 92 | extension EnumSet: Codable 93 | where EnumType: MappedValueRepresentable, EnumType.MappedType: Codable { 94 | /// Decodes the EnumSet based on an Array of MappedTypes. 95 | /// - Parameter decoder: Decoder which contains info as an array of MappedTypes. 96 | public init(from decoder: any Decoder) throws { 97 | let container = try decoder.singleValueContainer() 98 | let values = try container.decode([EnumType.MappedType].self) 99 | let rawValues = try values.map(EnumType.rawValue(basedOn:)) 100 | let set = Set(rawValues) 101 | rawValue = Self.cumulativeValue(basedOnRawValues: set) 102 | } 103 | 104 | /// Encodes the EnumSet based on an Array of MappedTypes. 105 | /// - Parameter encoder: Encoder which will contain info as an array of MappedTypes. 106 | public func encode(to encoder: any Encoder) throws { 107 | var container = encoder.singleValueContainer() 108 | let values = Self.enums(basedOnRawValue: rawValue) 109 | let mappedValues = try values 110 | .map(\.rawValue) 111 | .map(EnumType.mappedValue(basedOn:)) 112 | try container.encode(mappedValues) 113 | } 114 | } 115 | #else 116 | extension EnumSet: Codable 117 | where EnumType: MappedValueRepresentable, EnumType.MappedType: Codable { 118 | /// Decodes the EnumSet based on an Array of MappedTypes. 119 | /// - Parameter decoder: Decoder which contains info as an array of MappedTypes. 120 | public init(from decoder: Decoder) throws { 121 | let container = try decoder.singleValueContainer() 122 | let values = try container.decode([EnumType.MappedType].self) 123 | let rawValues = try values.map(EnumType.rawValue(basedOn:)) 124 | let set = Set(rawValues) 125 | rawValue = Self.cumulativeValue(basedOnRawValues: set) 126 | } 127 | 128 | /// Encodes the EnumSet based on an Array of MappedTypes. 129 | /// - Parameter encoder: Encoder which will contain info as an array of MappedTypes. 130 | public func encode(to encoder: Encoder) throws { 131 | var container = encoder.singleValueContainer() 132 | let values = Self.enums(basedOnRawValue: rawValue) 133 | let mappedValues = try values 134 | .map(\.rawValue) 135 | .map(EnumType.mappedValue(basedOn:)) 136 | try container.encode(mappedValues) 137 | } 138 | } 139 | #endif 140 | -------------------------------------------------------------------------------- /Sources/Options/Macro.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Macro.swift 3 | // SimulatorServices 4 | // 5 | // Created by Leo Dion. 6 | // Copyright © 2024 BrightDigit. 7 | // 8 | // Permission is hereby granted, free of charge, to any person 9 | // obtaining a copy of this software and associated documentation 10 | // files (the “Software”), to deal in the Software without 11 | // restriction, including without limitation the rights to use, 12 | // copy, modify, merge, publish, distribute, sublicense, and/or 13 | // sell copies of the Software, and to permit persons to whom the 14 | // Software is furnished to do so, subject to the following 15 | // conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | // OTHER DEALINGS IN THE SOFTWARE. 28 | // 29 | 30 | import Foundation 31 | 32 | #if swift(>=5.10) 33 | /// Sets an enumeration up to implement 34 | /// ``MappedValueRepresentable`` and ``MappedValueRepresented``. 35 | @attached( 36 | extension, 37 | conformances: MappedValueRepresentable, MappedValueRepresented, 38 | names: named(MappedType), named(mappedValues) 39 | ) 40 | @attached(peer, names: suffixed(Set)) 41 | public macro Options() = #externalMacro(module: "OptionsMacros", type: "OptionsMacro") 42 | #endif 43 | -------------------------------------------------------------------------------- /Sources/Options/MappedEnum.swift: -------------------------------------------------------------------------------- 1 | /// A generic struct for enumerations which allow for additional values attached. 2 | @available( 3 | *, 4 | deprecated, 5 | renamed: "MappedValueRepresentable", 6 | message: "Use `MappedValueRepresentable` with `CodingOptions`." 7 | ) 8 | public struct MappedEnum: Codable, Sendable 9 | where EnumType.MappedType: Codable { 10 | /// Base Enumeraion value. 11 | public let value: EnumType 12 | 13 | /// Creates an instance based on the base enumeration value. 14 | /// - Parameter value: Base Enumeration value. 15 | public init(value: EnumType) { 16 | self.value = value 17 | } 18 | } 19 | 20 | #if swift(>=5.9) 21 | extension MappedEnum { 22 | /// Decodes the value based on the mapped value. 23 | /// - Parameter decoder: Decoder. 24 | public init(from decoder: any Decoder) throws { 25 | let container = try decoder.singleValueContainer() 26 | let label = try container.decode(EnumType.MappedType.self) 27 | let rawValue = try EnumType.rawValue(basedOn: label) 28 | guard let value = EnumType(rawValue: rawValue) else { 29 | assertionFailure("Every mappedValue should always return a valid rawValue.") 30 | throw DecodingError.invalidRawValue(rawValue) 31 | } 32 | self.value = value 33 | } 34 | 35 | /// Encodes the value based on the mapped value. 36 | /// - Parameter encoder: Encoder. 37 | public func encode(to encoder: any Encoder) throws { 38 | let string = try EnumType.mappedValue(basedOn: value.rawValue) 39 | var container = encoder.singleValueContainer() 40 | try container.encode(string) 41 | } 42 | } 43 | #else 44 | extension MappedEnum { 45 | /// Decodes the value based on the mapped value. 46 | /// - Parameter decoder: Decoder. 47 | public init(from decoder: Decoder) throws { 48 | let container = try decoder.singleValueContainer() 49 | let label = try container.decode(EnumType.MappedType.self) 50 | let rawValue = try EnumType.rawValue(basedOn: label) 51 | guard let value = EnumType(rawValue: rawValue) else { 52 | assertionFailure("Every mappedValue should always return a valid rawValue.") 53 | throw DecodingError.invalidRawValue(rawValue) 54 | } 55 | self.value = value 56 | } 57 | 58 | /// Encodes the value based on the mapped value. 59 | /// - Parameter encoder: Encoder. 60 | public func encode(to encoder: Encoder) throws { 61 | let string = try EnumType.mappedValue(basedOn: value.rawValue) 62 | var container = encoder.singleValueContainer() 63 | try container.encode(string) 64 | } 65 | } 66 | #endif 67 | -------------------------------------------------------------------------------- /Sources/Options/MappedValueRepresentable+Codable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MappedValueRepresentable+Codable.swift 3 | // SimulatorServices 4 | // 5 | // Created by Leo Dion. 6 | // Copyright © 2024 BrightDigit. 7 | // 8 | // Permission is hereby granted, free of charge, to any person 9 | // obtaining a copy of this software and associated documentation 10 | // files (the “Software”), to deal in the Software without 11 | // restriction, including without limitation the rights to use, 12 | // copy, modify, merge, publish, distribute, sublicense, and/or 13 | // sell copies of the Software, and to permit persons to whom the 14 | // Software is furnished to do so, subject to the following 15 | // conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | // OTHER DEALINGS IN THE SOFTWARE. 28 | // 29 | 30 | import Foundation 31 | 32 | extension DecodingError { 33 | internal static func invalidRawValue(_ rawValue: some Any) -> DecodingError { 34 | .dataCorrupted( 35 | .init(codingPath: [], debugDescription: "Raw Value \(rawValue) is invalid.") 36 | ) 37 | } 38 | } 39 | 40 | extension SingleValueDecodingContainer { 41 | fileprivate func decodeAsRawValue() throws -> T 42 | where T.RawValue: Decodable { 43 | let rawValue = try decode(T.RawValue.self) 44 | guard let value = T(rawValue: rawValue) else { 45 | throw DecodingError.invalidRawValue(rawValue) 46 | } 47 | return value 48 | } 49 | 50 | fileprivate func decodeAsMappedType() throws -> T 51 | where T.RawValue: Decodable, T.MappedType: Decodable { 52 | let mappedValues: T.MappedType 53 | do { 54 | mappedValues = try decode(T.MappedType.self) 55 | } catch { 56 | return try decodeAsRawValue() 57 | } 58 | 59 | let rawValue = try T.rawValue(basedOn: mappedValues) 60 | 61 | guard let value = T(rawValue: rawValue) else { 62 | assertionFailure("Every mappedValue should always return a valid rawValue.") 63 | throw DecodingError.invalidRawValue(rawValue) 64 | } 65 | 66 | return value 67 | } 68 | } 69 | 70 | extension MappedValueRepresentable 71 | where Self: Decodable, MappedType: Decodable, RawValue: Decodable { 72 | /// Decodes the type. 73 | /// - Parameter decoder: Decoder. 74 | public init(from decoder: any Decoder) throws { 75 | let container = try decoder.singleValueContainer() 76 | 77 | if Self.codingOptions.contains(.allowMappedValueDecoding) { 78 | self = try container.decodeAsMappedType() 79 | } else { 80 | self = try container.decodeAsRawValue() 81 | } 82 | } 83 | } 84 | 85 | extension MappedValueRepresentable 86 | where Self: Encodable, MappedType: Encodable, RawValue: Encodable { 87 | /// Encoding the type. 88 | /// - Parameter decoder: Encodes. 89 | public func encode(to encoder: any Encoder) throws { 90 | var container = encoder.singleValueContainer() 91 | if Self.codingOptions.contains(.encodeAsMappedValue) { 92 | try container.encode(mappedValue()) 93 | } else { 94 | try container.encode(rawValue) 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /Sources/Options/MappedValueRepresentable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MappedValueRepresentable.swift 3 | // SimulatorServices 4 | // 5 | // Created by Leo Dion. 6 | // Copyright © 2024 BrightDigit. 7 | // 8 | // Permission is hereby granted, free of charge, to any person 9 | // obtaining a copy of this software and associated documentation 10 | // files (the “Software”), to deal in the Software without 11 | // restriction, including without limitation the rights to use, 12 | // copy, modify, merge, publish, distribute, sublicense, and/or 13 | // sell copies of the Software, and to permit persons to whom the 14 | // Software is furnished to do so, subject to the following 15 | // conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | // OTHER DEALINGS IN THE SOFTWARE. 28 | // 29 | 30 | /// An enum which has an additional value attached. 31 | /// - Note: ``Options()`` macro will automatically set this up for you. 32 | public protocol MappedValueRepresentable: RawRepresentable, CaseIterable, Sendable { 33 | /// The additional value type. 34 | associatedtype MappedType = String 35 | 36 | /// Options for how the enum should be decoded or encoded. 37 | static var codingOptions: CodingOptions { 38 | get 39 | } 40 | 41 | /// Gets the raw value based on the MappedType. 42 | /// - Parameter value: MappedType value. 43 | /// - Returns: The raw value of the enumeration based on the `MappedType `value. 44 | static func rawValue(basedOn string: MappedType) throws -> RawValue 45 | 46 | /// Gets the `MappedType` value based on the `rawValue`. 47 | /// - Parameter rawValue: The raw value of the enumeration. 48 | /// - Returns: The Mapped Type value based on the `rawValue`. 49 | static func mappedValue(basedOn rawValue: RawValue) throws -> MappedType 50 | } 51 | 52 | extension MappedValueRepresentable { 53 | /// Options regarding how the type can be decoded or encoded. 54 | public static var codingOptions: CodingOptions { 55 | .default 56 | } 57 | 58 | /// Gets the mapped value of the enumeration. 59 | /// - Parameter rawValue: The raw value of the enumeration 60 | /// which pretains to its index in the `mappedValues` Array. 61 | /// - Throws: `MappedValueCollectionRepresentedError.valueNotFound` 62 | /// if the raw value (i.e. index) is outside the range of the `mappedValues` array. 63 | /// - Returns: 64 | /// The Mapped Type value based on the value in the array at the raw value index. 65 | public func mappedValue() throws -> MappedType { 66 | try Self.mappedValue(basedOn: rawValue) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Sources/Options/MappedValueRepresentableError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MappedValueRepresentableError.swift 3 | // SimulatorServices 4 | // 5 | // Created by Leo Dion. 6 | // Copyright © 2024 BrightDigit. 7 | // 8 | // Permission is hereby granted, free of charge, to any person 9 | // obtaining a copy of this software and associated documentation 10 | // files (the “Software”), to deal in the Software without 11 | // restriction, including without limitation the rights to use, 12 | // copy, modify, merge, publish, distribute, sublicense, and/or 13 | // sell copies of the Software, and to permit persons to whom the 14 | // Software is furnished to do so, subject to the following 15 | // conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | // OTHER DEALINGS IN THE SOFTWARE. 28 | // 29 | 30 | import Foundation 31 | 32 | // swiftlint:disable file_types_order 33 | #if swift(>=5.10) 34 | /// An Error thrown when the `MappedType` value or `RawType` value 35 | /// are invalid for an `Enum`. 36 | public enum MappedValueRepresentableError: Error, Sendable { 37 | /// Whenever a value or key cannot be found. 38 | case valueNotFound 39 | } 40 | #else 41 | /// An Error thrown when the `MappedType` value or `RawType` value 42 | /// are invalid for an `Enum`. 43 | public enum MappedValueRepresentableError: Error { 44 | case valueNotFound 45 | } 46 | #endif 47 | // swiftlint:enable file_types_order 48 | -------------------------------------------------------------------------------- /Sources/Options/MappedValueRepresented.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MappedValueRepresented.swift 3 | // SimulatorServices 4 | // 5 | // Created by Leo Dion. 6 | // Copyright © 2024 BrightDigit. 7 | // 8 | // Permission is hereby granted, free of charge, to any person 9 | // obtaining a copy of this software and associated documentation 10 | // files (the “Software”), to deal in the Software without 11 | // restriction, including without limitation the rights to use, 12 | // copy, modify, merge, publish, distribute, sublicense, and/or 13 | // sell copies of the Software, and to permit persons to whom the 14 | // Software is furnished to do so, subject to the following 15 | // conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | // OTHER DEALINGS IN THE SOFTWARE. 28 | // 29 | 30 | /// Protocol which simplifies ``MappedValueRepresentable``by using a ``MappedValues``. 31 | public protocol MappedValueRepresented: MappedValueRepresentable 32 | where MappedType: Equatable { 33 | /// A object to lookup values and keys for mapped values. 34 | associatedtype MappedValueType: MappedValues 35 | /// An array of the mapped values which lines up with each case. 36 | static var mappedValues: MappedValueType { get } 37 | } 38 | 39 | extension MappedValueRepresented { 40 | /// Gets the raw value based on the MappedType by finding the index of the mapped value. 41 | /// - Parameter value: MappedType value. 42 | /// - Throws: `MappedValueCollectionRepresentedError.valueNotFound` 43 | /// If the value was not found in the array 44 | /// - Returns: 45 | /// The raw value of the enumeration 46 | /// based on the index the MappedType value was found at. 47 | public static func rawValue(basedOn value: MappedType) throws -> RawValue { 48 | try mappedValues.key(value: value) 49 | } 50 | 51 | /// Gets the mapped value based on the rawValue 52 | /// by access the array at the raw value subscript. 53 | /// - Parameter rawValue: The raw value of the enumeration 54 | /// which pretains to its index in the `mappedValues` Array. 55 | /// - Throws: `MappedValueCollectionRepresentedError.valueNotFound` 56 | /// if the raw value (i.e. index) is outside the range of the `mappedValues` array. 57 | /// - Returns: 58 | /// The Mapped Type value based on the value in the array at the raw value index. 59 | public static func mappedValue(basedOn rawValue: RawValue) throws -> MappedType { 60 | try mappedValues.value(key: rawValue) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Sources/Options/MappedValues.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MappedValues.swift 3 | // SimulatorServices 4 | // 5 | // Created by Leo Dion. 6 | // Copyright © 2024 BrightDigit. 7 | // 8 | // Permission is hereby granted, free of charge, to any person 9 | // obtaining a copy of this software and associated documentation 10 | // files (the “Software”), to deal in the Software without 11 | // restriction, including without limitation the rights to use, 12 | // copy, modify, merge, publish, distribute, sublicense, and/or 13 | // sell copies of the Software, and to permit persons to whom the 14 | // Software is furnished to do so, subject to the following 15 | // conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | // OTHER DEALINGS IN THE SOFTWARE. 28 | // 29 | 30 | import Foundation 31 | 32 | /// Protocol which provides a method for ``MappedValueRepresented`` to pull values. 33 | public protocol MappedValues { 34 | /// Raw Value Type 35 | associatedtype Value: Equatable 36 | /// Key Value Type 37 | associatedtype Key: Equatable 38 | /// get the key vased on the value. 39 | func key(value: Value) throws -> Key 40 | /// get the value based on the key/index. 41 | func value(key: Key) throws -> Value 42 | } 43 | -------------------------------------------------------------------------------- /Sources/OptionsMacros/Extensions/Array.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Array.swift 3 | // SimulatorServices 4 | // 5 | // Created by Leo Dion. 6 | // Copyright © 2024 BrightDigit. 7 | // 8 | // Permission is hereby granted, free of charge, to any person 9 | // obtaining a copy of this software and associated documentation 10 | // files (the “Software”), to deal in the Software without 11 | // restriction, including without limitation the rights to use, 12 | // copy, modify, merge, publish, distribute, sublicense, and/or 13 | // sell copies of the Software, and to permit persons to whom the 14 | // Software is furnished to do so, subject to the following 15 | // conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | // OTHER DEALINGS IN THE SOFTWARE. 28 | // 29 | 30 | import Foundation 31 | 32 | extension Array { 33 | internal init?(keyValues: KeyValues) where Element == String { 34 | self.init() 35 | for key in 0 ..< keyValues.count { 36 | guard let value = keyValues.get(key) else { 37 | return nil 38 | } 39 | append(value) 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Sources/OptionsMacros/Extensions/ArrayExprSyntax.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ArrayExprSyntax.swift 3 | // SimulatorServices 4 | // 5 | // Created by Leo Dion. 6 | // Copyright © 2024 BrightDigit. 7 | // 8 | // Permission is hereby granted, free of charge, to any person 9 | // obtaining a copy of this software and associated documentation 10 | // files (the “Software”), to deal in the Software without 11 | // restriction, including without limitation the rights to use, 12 | // copy, modify, merge, publish, distribute, sublicense, and/or 13 | // sell copies of the Software, and to permit persons to whom the 14 | // Software is furnished to do so, subject to the following 15 | // conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | // OTHER DEALINGS IN THE SOFTWARE. 28 | // 29 | 30 | import SwiftSyntax 31 | 32 | extension ArrayExprSyntax { 33 | internal init( 34 | from items: some Collection, 35 | _ closure: @escaping @Sendable (T) -> some ExprSyntaxProtocol 36 | ) { 37 | let values = items.map(closure).map { ArrayElementSyntax(expression: $0) } 38 | let arrayElement = ArrayElementListSyntax { 39 | .init(values) 40 | } 41 | self.init(elements: arrayElement) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Sources/OptionsMacros/Extensions/DeclModifierListSyntax.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DeclModifierListSyntax.swift 3 | // SimulatorServices 4 | // 5 | // Created by Leo Dion. 6 | // Copyright © 2024 BrightDigit. 7 | // 8 | // Permission is hereby granted, free of charge, to any person 9 | // obtaining a copy of this software and associated documentation 10 | // files (the “Software”), to deal in the Software without 11 | // restriction, including without limitation the rights to use, 12 | // copy, modify, merge, publish, distribute, sublicense, and/or 13 | // sell copies of the Software, and to permit persons to whom the 14 | // Software is furnished to do so, subject to the following 15 | // conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | // OTHER DEALINGS IN THE SOFTWARE. 28 | // 29 | 30 | import SwiftSyntax 31 | 32 | extension DeclModifierListSyntax { 33 | internal init(keywordModifier: Keyword?) { 34 | if let keywordModifier { 35 | self.init { 36 | DeclModifierSyntax(name: .keyword(keywordModifier)) 37 | } 38 | } else { 39 | self.init([]) 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Sources/OptionsMacros/Extensions/DeclModifierSyntax.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DeclModifierSyntax.swift 3 | // SimulatorServices 4 | // 5 | // Created by Leo Dion. 6 | // Copyright © 2024 BrightDigit. 7 | // 8 | // Permission is hereby granted, free of charge, to any person 9 | // obtaining a copy of this software and associated documentation 10 | // files (the “Software”), to deal in the Software without 11 | // restriction, including without limitation the rights to use, 12 | // copy, modify, merge, publish, distribute, sublicense, and/or 13 | // sell copies of the Software, and to permit persons to whom the 14 | // Software is furnished to do so, subject to the following 15 | // conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | // OTHER DEALINGS IN THE SOFTWARE. 28 | // 29 | 30 | import SwiftSyntax 31 | 32 | extension DeclModifierSyntax { 33 | internal var isNeededAccessLevelModifier: Bool { 34 | switch name.tokenKind { 35 | case .keyword(.public): return true 36 | default: return false 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Sources/OptionsMacros/Extensions/DictionaryElementSyntax.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DictionaryElementSyntax.swift 3 | // SimulatorServices 4 | // 5 | // Created by Leo Dion. 6 | // Copyright © 2024 BrightDigit. 7 | // 8 | // Permission is hereby granted, free of charge, to any person 9 | // obtaining a copy of this software and associated documentation 10 | // files (the “Software”), to deal in the Software without 11 | // restriction, including without limitation the rights to use, 12 | // copy, modify, merge, publish, distribute, sublicense, and/or 13 | // sell copies of the Software, and to permit persons to whom the 14 | // Software is furnished to do so, subject to the following 15 | // conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | // OTHER DEALINGS IN THE SOFTWARE. 28 | // 29 | 30 | import SwiftSyntax 31 | 32 | extension DictionaryElementSyntax { 33 | internal init(pair: (key: Int, value: String)) { 34 | self.init(key: pair.key, value: pair.value) 35 | } 36 | 37 | internal init(key: Int, value: String) { 38 | self.init( 39 | key: IntegerLiteralExprSyntax(integerLiteral: key), 40 | value: StringLiteralExprSyntax( 41 | openingQuote: .stringQuoteToken(), 42 | segments: .init([.stringSegment(.init(content: .stringSegment(value)))]), 43 | closingQuote: .stringQuoteToken() 44 | ) 45 | ) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Sources/OptionsMacros/Extensions/DictionaryExprSyntax.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DictionaryExprSyntax.swift 3 | // SimulatorServices 4 | // 5 | // Created by Leo Dion. 6 | // Copyright © 2024 BrightDigit. 7 | // 8 | // Permission is hereby granted, free of charge, to any person 9 | // obtaining a copy of this software and associated documentation 10 | // files (the “Software”), to deal in the Software without 11 | // restriction, including without limitation the rights to use, 12 | // copy, modify, merge, publish, distribute, sublicense, and/or 13 | // sell copies of the Software, and to permit persons to whom the 14 | // Software is furnished to do so, subject to the following 15 | // conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | // OTHER DEALINGS IN THE SOFTWARE. 28 | // 29 | 30 | import SwiftSyntax 31 | 32 | extension DictionaryExprSyntax { 33 | internal init(keyValues: KeyValues) { 34 | let dictionaryElements = keyValues.dictionary.map(DictionaryElementSyntax.init(pair:)) 35 | 36 | let list = DictionaryElementListSyntax { 37 | .init(dictionaryElements) 38 | } 39 | self.init(content: .elements(list)) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Sources/OptionsMacros/Extensions/EnumDeclSyntax.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EnumDeclSyntax.swift 3 | // SimulatorServices 4 | // 5 | // Created by Leo Dion. 6 | // Copyright © 2024 BrightDigit. 7 | // 8 | // Permission is hereby granted, free of charge, to any person 9 | // obtaining a copy of this software and associated documentation 10 | // files (the “Software”), to deal in the Software without 11 | // restriction, including without limitation the rights to use, 12 | // copy, modify, merge, publish, distribute, sublicense, and/or 13 | // sell copies of the Software, and to permit persons to whom the 14 | // Software is furnished to do so, subject to the following 15 | // conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | // OTHER DEALINGS IN THE SOFTWARE. 28 | // 29 | 30 | import SwiftSyntax 31 | 32 | extension EnumDeclSyntax { 33 | internal var caseElements: [EnumCaseElementSyntax] { 34 | memberBlock.members.flatMap { member in 35 | guard let caseDecl = member.decl.as(EnumCaseDeclSyntax.self) else { 36 | return [EnumCaseElementSyntax]() 37 | } 38 | 39 | return Array(caseDecl.elements) 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Sources/OptionsMacros/Extensions/ExtensionDeclSyntax.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExtensionDeclSyntax.swift 3 | // SimulatorServices 4 | // 5 | // Created by Leo Dion. 6 | // Copyright © 2024 BrightDigit. 7 | // 8 | // Permission is hereby granted, free of charge, to any person 9 | // obtaining a copy of this software and associated documentation 10 | // files (the “Software”), to deal in the Software without 11 | // restriction, including without limitation the rights to use, 12 | // copy, modify, merge, publish, distribute, sublicense, and/or 13 | // sell copies of the Software, and to permit persons to whom the 14 | // Software is furnished to do so, subject to the following 15 | // conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | // OTHER DEALINGS IN THE SOFTWARE. 28 | // 29 | 30 | import SwiftSyntax 31 | 32 | extension ExtensionDeclSyntax { 33 | internal init( 34 | enumDecl: EnumDeclSyntax, 35 | conformingTo protocols: [SwiftSyntax.TypeSyntax] 36 | ) throws { 37 | let typeName = enumDecl.name 38 | 39 | let access = enumDecl.modifiers.first(where: \.isNeededAccessLevelModifier) 40 | 41 | let mappedValues = try VariableDeclSyntax.mappedValuesDeclarationForCases( 42 | enumDecl.caseElements 43 | ) 44 | 45 | self.init( 46 | modifiers: DeclModifierListSyntax([access].compactMap { $0 }), 47 | extendedType: IdentifierTypeSyntax(name: typeName), 48 | inheritanceClause: InheritanceClauseSyntax(protocols: protocols), 49 | memberBlock: MemberBlockSyntax( 50 | members: MemberBlockItemListSyntax { 51 | TypeAliasDeclSyntax(name: "MappedType", for: "String") 52 | mappedValues 53 | } 54 | ) 55 | ) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Sources/OptionsMacros/Extensions/InheritanceClauseSyntax.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InheritanceClauseSyntax.swift 3 | // SimulatorServices 4 | // 5 | // Created by Leo Dion. 6 | // Copyright © 2024 BrightDigit. 7 | // 8 | // Permission is hereby granted, free of charge, to any person 9 | // obtaining a copy of this software and associated documentation 10 | // files (the “Software”), to deal in the Software without 11 | // restriction, including without limitation the rights to use, 12 | // copy, modify, merge, publish, distribute, sublicense, and/or 13 | // sell copies of the Software, and to permit persons to whom the 14 | // Software is furnished to do so, subject to the following 15 | // conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | // OTHER DEALINGS IN THE SOFTWARE. 28 | // 29 | 30 | import SwiftSyntax 31 | 32 | extension InheritanceClauseSyntax { 33 | internal init(protocols: [SwiftSyntax.TypeSyntax]) { 34 | self.init( 35 | inheritedTypes: .init { 36 | .init( 37 | protocols.map { typeSyntax in 38 | InheritedTypeSyntax(type: typeSyntax) 39 | } 40 | ) 41 | } 42 | ) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Sources/OptionsMacros/Extensions/KeyValues.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KeyValues.swift 3 | // SimulatorServices 4 | // 5 | // Created by Leo Dion. 6 | // Copyright © 2024 BrightDigit. 7 | // 8 | // Permission is hereby granted, free of charge, to any person 9 | // obtaining a copy of this software and associated documentation 10 | // files (the “Software”), to deal in the Software without 11 | // restriction, including without limitation the rights to use, 12 | // copy, modify, merge, publish, distribute, sublicense, and/or 13 | // sell copies of the Software, and to permit persons to whom the 14 | // Software is furnished to do so, subject to the following 15 | // conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | // OTHER DEALINGS IN THE SOFTWARE. 28 | // 29 | 30 | import SwiftSyntax 31 | 32 | internal struct KeyValues { 33 | internal private(set) var lastKey: Int? 34 | internal private(set) var dictionary = [Int: String]() 35 | 36 | internal var count: Int { 37 | dictionary.count 38 | } 39 | 40 | internal var nextKey: Int { 41 | (lastKey ?? -1) + 1 42 | } 43 | 44 | internal mutating func append(value: String, withKey key: Int? = nil) throws { 45 | let key = key ?? nextKey 46 | guard dictionary[key] == nil else { 47 | throw InvalidDeclError.rawValue(key) 48 | } 49 | lastKey = key 50 | dictionary[key] = value 51 | } 52 | 53 | internal func get(_ key: Int) -> String? { 54 | dictionary[key] 55 | } 56 | } 57 | 58 | extension KeyValues { 59 | internal init(caseElements: [EnumCaseElementSyntax]) throws { 60 | self.init() 61 | for caseElement in caseElements { 62 | let intText = caseElement.rawValue?.value 63 | .as(IntegerLiteralExprSyntax.self)?.literal.text 64 | let key = intText.flatMap { Int($0) } 65 | let value = 66 | caseElement.name.trimmed.text 67 | try append(value: value, withKey: key) 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Sources/OptionsMacros/Extensions/TypeAliasDeclSyntax.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TypeAliasDeclSyntax.swift 3 | // SimulatorServices 4 | // 5 | // Created by Leo Dion. 6 | // Copyright © 2024 BrightDigit. 7 | // 8 | // Permission is hereby granted, free of charge, to any person 9 | // obtaining a copy of this software and associated documentation 10 | // files (the “Software”), to deal in the Software without 11 | // restriction, including without limitation the rights to use, 12 | // copy, modify, merge, publish, distribute, sublicense, and/or 13 | // sell copies of the Software, and to permit persons to whom the 14 | // Software is furnished to do so, subject to the following 15 | // conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | // OTHER DEALINGS IN THE SOFTWARE. 28 | // 29 | 30 | import SwiftSyntax 31 | 32 | extension TypeAliasDeclSyntax { 33 | internal init(name: TokenSyntax, for initializerTypeName: TokenSyntax) { 34 | self.init( 35 | name: name, 36 | initializer: .init(value: IdentifierTypeSyntax(name: initializerTypeName)) 37 | ) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Sources/OptionsMacros/Extensions/VariableDeclSyntax.swift: -------------------------------------------------------------------------------- 1 | // 2 | // VariableDeclSyntax.swift 3 | // SimulatorServices 4 | // 5 | // Created by Leo Dion. 6 | // Copyright © 2024 BrightDigit. 7 | // 8 | // Permission is hereby granted, free of charge, to any person 9 | // obtaining a copy of this software and associated documentation 10 | // files (the “Software”), to deal in the Software without 11 | // restriction, including without limitation the rights to use, 12 | // copy, modify, merge, publish, distribute, sublicense, and/or 13 | // sell copies of the Software, and to permit persons to whom the 14 | // Software is furnished to do so, subject to the following 15 | // conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | // OTHER DEALINGS IN THE SOFTWARE. 28 | // 29 | 30 | import SwiftSyntax 31 | 32 | extension VariableDeclSyntax { 33 | internal init( 34 | keywordModifier: Keyword?, 35 | bindingKeyword: Keyword, 36 | variableName: String, 37 | initializerExpression: (some ExprSyntaxProtocol)? 38 | ) { 39 | let modifiers = DeclModifierListSyntax(keywordModifier: keywordModifier) 40 | 41 | let initializer: InitializerClauseSyntax? = 42 | initializerExpression.map { .init(value: $0) } 43 | 44 | self.init( 45 | modifiers: modifiers, 46 | bindingSpecifier: .keyword(bindingKeyword), 47 | bindings: .init { 48 | PatternBindingSyntax( 49 | pattern: IdentifierPatternSyntax(identifier: .identifier(variableName)), 50 | initializer: initializer 51 | ) 52 | } 53 | ) 54 | } 55 | 56 | internal static func initializerExpression( 57 | from caseElements: [EnumCaseElementSyntax] 58 | ) throws -> any ExprSyntaxProtocol { 59 | let keyValues = try KeyValues(caseElements: caseElements) 60 | if let array = Array(keyValues: keyValues) { 61 | return ArrayExprSyntax(from: array) { value in 62 | StringLiteralExprSyntax(content: value) 63 | } 64 | } else { 65 | return DictionaryExprSyntax(keyValues: keyValues) 66 | } 67 | } 68 | 69 | internal static func mappedValuesDeclarationForCases( 70 | _ caseElements: [EnumCaseElementSyntax] 71 | ) throws -> VariableDeclSyntax { 72 | let arrayExpression = try Self.initializerExpression(from: caseElements) 73 | 74 | return VariableDeclSyntax( 75 | keywordModifier: .static, 76 | bindingKeyword: .let, 77 | variableName: "mappedValues", 78 | initializerExpression: arrayExpression 79 | ) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Sources/OptionsMacros/InvalidDeclError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InvalidDeclError.swift 3 | // SimulatorServices 4 | // 5 | // Created by Leo Dion. 6 | // Copyright © 2024 BrightDigit. 7 | // 8 | // Permission is hereby granted, free of charge, to any person 9 | // obtaining a copy of this software and associated documentation 10 | // files (the “Software”), to deal in the Software without 11 | // restriction, including without limitation the rights to use, 12 | // copy, modify, merge, publish, distribute, sublicense, and/or 13 | // sell copies of the Software, and to permit persons to whom the 14 | // Software is furnished to do so, subject to the following 15 | // conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | // OTHER DEALINGS IN THE SOFTWARE. 28 | // 29 | 30 | @preconcurrency import SwiftSyntax 31 | 32 | internal enum InvalidDeclError: Error, Sendable { 33 | case kind(SyntaxKind) 34 | case rawValue(Int) 35 | } 36 | -------------------------------------------------------------------------------- /Sources/OptionsMacros/MacrosPlugin.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MacrosPlugin.swift 3 | // SimulatorServices 4 | // 5 | // Created by Leo Dion. 6 | // Copyright © 2024 BrightDigit. 7 | // 8 | // Permission is hereby granted, free of charge, to any person 9 | // obtaining a copy of this software and associated documentation 10 | // files (the “Software”), to deal in the Software without 11 | // restriction, including without limitation the rights to use, 12 | // copy, modify, merge, publish, distribute, sublicense, and/or 13 | // sell copies of the Software, and to permit persons to whom the 14 | // Software is furnished to do so, subject to the following 15 | // conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | // OTHER DEALINGS IN THE SOFTWARE. 28 | // 29 | 30 | import SwiftCompilerPlugin 31 | import SwiftSyntax 32 | import SwiftSyntaxMacros 33 | 34 | @main 35 | internal struct MacrosPlugin: CompilerPlugin { 36 | internal let providingMacros: [any Macro.Type] = [ 37 | OptionsMacro.self 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /Sources/OptionsMacros/OptionsMacro.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OptionsMacro.swift 3 | // SimulatorServices 4 | // 5 | // Created by Leo Dion. 6 | // Copyright © 2024 BrightDigit. 7 | // 8 | // Permission is hereby granted, free of charge, to any person 9 | // obtaining a copy of this software and associated documentation 10 | // files (the “Software”), to deal in the Software without 11 | // restriction, including without limitation the rights to use, 12 | // copy, modify, merge, publish, distribute, sublicense, and/or 13 | // sell copies of the Software, and to permit persons to whom the 14 | // Software is furnished to do so, subject to the following 15 | // conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | // OTHER DEALINGS IN THE SOFTWARE. 28 | // 29 | 30 | import SwiftSyntax 31 | import SwiftSyntaxMacros 32 | 33 | public struct OptionsMacro: ExtensionMacro, PeerMacro { 34 | public static func expansion( 35 | of _: SwiftSyntax.AttributeSyntax, 36 | providingPeersOf declaration: some SwiftSyntax.DeclSyntaxProtocol, 37 | in _: some SwiftSyntaxMacros.MacroExpansionContext 38 | ) throws -> [SwiftSyntax.DeclSyntax] { 39 | guard let enumDecl = declaration.as(EnumDeclSyntax.self) else { 40 | throw InvalidDeclError.kind(declaration.kind) 41 | } 42 | let typeName = enumDecl.name 43 | 44 | let aliasName: TokenSyntax = "\(typeName.trimmed)Set" 45 | 46 | let initializerName: TokenSyntax = "EnumSet<\(typeName)>" 47 | 48 | return [ 49 | .init(TypeAliasDeclSyntax(name: aliasName, for: initializerName)) 50 | ] 51 | } 52 | 53 | public static func expansion( 54 | of _: SwiftSyntax.AttributeSyntax, 55 | attachedTo declaration: some SwiftSyntax.DeclGroupSyntax, 56 | providingExtensionsOf _: some SwiftSyntax.TypeSyntaxProtocol, 57 | conformingTo protocols: [SwiftSyntax.TypeSyntax], 58 | in _: some SwiftSyntaxMacros.MacroExpansionContext 59 | ) throws -> [SwiftSyntax.ExtensionDeclSyntax] { 60 | guard let enumDecl = declaration.as(EnumDeclSyntax.self) else { 61 | throw InvalidDeclError.kind(declaration.kind) 62 | } 63 | 64 | let extensionDecl = try ExtensionDeclSyntax( 65 | enumDecl: enumDecl, conformingTo: protocols 66 | ) 67 | return [extensionDecl] 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Tests/OptionsTests/EnumSetTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EnumSetTests.swift 3 | // SimulatorServices 4 | // 5 | // Created by Leo Dion. 6 | // Copyright © 2024 BrightDigit. 7 | // 8 | // Permission is hereby granted, free of charge, to any person 9 | // obtaining a copy of this software and associated documentation 10 | // files (the “Software”), to deal in the Software without 11 | // restriction, including without limitation the rights to use, 12 | // copy, modify, merge, publish, distribute, sublicense, and/or 13 | // sell copies of the Software, and to permit persons to whom the 14 | // Software is furnished to do so, subject to the following 15 | // conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | // OTHER DEALINGS IN THE SOFTWARE. 28 | // 29 | 30 | @testable import Options 31 | import XCTest 32 | 33 | internal final class EnumSetTests: XCTestCase { 34 | private static let text = "[\"a\",\"b\",\"c\"]" 35 | 36 | internal func testDecoder() { 37 | // swiftlint:disable:next force_unwrapping 38 | let data = Self.text.data(using: .utf8)! 39 | let decoder = JSONDecoder() 40 | let actual: EnumSet 41 | do { 42 | actual = try decoder.decode(EnumSet.self, from: data) 43 | } catch { 44 | XCTAssertNil(error) 45 | return 46 | } 47 | XCTAssertEqual(actual.rawValue, 7) 48 | } 49 | 50 | internal func testEncoder() { 51 | let enumSet = EnumSet(values: [.a, .b, .c]) 52 | let encoder = JSONEncoder() 53 | let data: Data 54 | do { 55 | data = try encoder.encode(enumSet) 56 | } catch { 57 | XCTAssertNil(error) 58 | return 59 | } 60 | 61 | let dataText = String(bytes: data, encoding: .utf8) 62 | 63 | guard let text = dataText else { 64 | XCTAssertNotNil(dataText) 65 | return 66 | } 67 | 68 | XCTAssertEqual(text, Self.text) 69 | } 70 | 71 | internal func testInitValue() { 72 | let set = EnumSet(rawValue: 7) 73 | XCTAssertEqual(set.rawValue, 7) 74 | } 75 | 76 | internal func testInitValues() { 77 | let values: [MockCollectionEnum] = [.a, .b, .c] 78 | let setA = EnumSet(values: values) 79 | XCTAssertEqual(setA.rawValue, 7) 80 | let setB: MockCollectionEnumSet = [.a, .b, .c] 81 | XCTAssertEqual(setB.rawValue, 7) 82 | } 83 | 84 | internal func testArray() { 85 | let expected: [MockCollectionEnum] = [.b, .d] 86 | let enumSet = EnumSet(values: expected) 87 | let actual = enumSet.array() 88 | XCTAssertEqual(actual, expected) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /Tests/OptionsTests/MappedEnumTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MappedEnumTests.swift 3 | // SimulatorServices 4 | // 5 | // Created by Leo Dion. 6 | // Copyright © 2024 BrightDigit. 7 | // 8 | // Permission is hereby granted, free of charge, to any person 9 | // obtaining a copy of this software and associated documentation 10 | // files (the “Software”), to deal in the Software without 11 | // restriction, including without limitation the rights to use, 12 | // copy, modify, merge, publish, distribute, sublicense, and/or 13 | // sell copies of the Software, and to permit persons to whom the 14 | // Software is furnished to do so, subject to the following 15 | // conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | // OTHER DEALINGS IN THE SOFTWARE. 28 | // 29 | 30 | @testable import Options 31 | import XCTest 32 | 33 | internal final class MappedEnumTests: XCTestCase { 34 | private static let text = "\"a\"" 35 | internal func testDecoder() throws { 36 | // swiftlint:disable:next force_unwrapping 37 | let data = Self.text.data(using: .utf8)! 38 | let decoder = JSONDecoder() 39 | let actual: MappedEnum 40 | do { 41 | actual = try decoder.decode(MappedEnum.self, from: data) 42 | } catch { 43 | XCTAssertNil(error) 44 | return 45 | } 46 | XCTAssertEqual(actual.value, .a) 47 | } 48 | 49 | internal func testEncoder() throws { 50 | let encoder = JSONEncoder() 51 | let describedEnum: MappedEnum = .init(value: .a) 52 | let data: Data 53 | do { 54 | data = try encoder.encode(describedEnum) 55 | } catch { 56 | XCTAssertNil(error) 57 | return 58 | } 59 | 60 | let dataText = String(bytes: data, encoding: .utf8) 61 | 62 | guard let text = dataText else { 63 | XCTAssertNotNil(dataText) 64 | return 65 | } 66 | 67 | XCTAssertEqual(text, Self.text) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Tests/OptionsTests/MappedValueCollectionRepresentedTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MappedValueCollectionRepresentedTests.swift 3 | // SimulatorServices 4 | // 5 | // Created by Leo Dion. 6 | // Copyright © 2024 BrightDigit. 7 | // 8 | // Permission is hereby granted, free of charge, to any person 9 | // obtaining a copy of this software and associated documentation 10 | // files (the “Software”), to deal in the Software without 11 | // restriction, including without limitation the rights to use, 12 | // copy, modify, merge, publish, distribute, sublicense, and/or 13 | // sell copies of the Software, and to permit persons to whom the 14 | // Software is furnished to do so, subject to the following 15 | // conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | // OTHER DEALINGS IN THE SOFTWARE. 28 | // 29 | 30 | @testable import Options 31 | import XCTest 32 | 33 | internal final class MappedValueCollectionRepresentedTests: XCTestCase { 34 | internal func testRawValue() { 35 | try XCTAssertEqual(MockCollectionEnum.rawValue(basedOn: "a"), 0) 36 | try XCTAssertEqual(MockCollectionEnum.rawValue(basedOn: "b"), 1) 37 | try XCTAssertEqual(MockCollectionEnum.rawValue(basedOn: "c"), 2) 38 | try XCTAssertEqual(MockCollectionEnum.rawValue(basedOn: "d"), 3) 39 | } 40 | 41 | internal func testString() { 42 | try XCTAssertEqual(MockCollectionEnum.mappedValue(basedOn: 0), "a") 43 | try XCTAssertEqual(MockCollectionEnum.mappedValue(basedOn: 1), "b") 44 | try XCTAssertEqual(MockCollectionEnum.mappedValue(basedOn: 2), "c") 45 | try XCTAssertEqual(MockCollectionEnum.mappedValue(basedOn: 3), "d") 46 | } 47 | 48 | internal func testRawValueFailure() { 49 | let caughtError: MappedValueRepresentableError? 50 | do { 51 | _ = try MockCollectionEnum.rawValue(basedOn: "e") 52 | caughtError = nil 53 | } catch let error as MappedValueRepresentableError { 54 | caughtError = error 55 | } catch { 56 | XCTAssertNil(error) 57 | caughtError = nil 58 | } 59 | 60 | XCTAssertEqual(caughtError, .valueNotFound) 61 | } 62 | 63 | internal func testStringFailure() { 64 | let caughtError: MappedValueRepresentableError? 65 | do { 66 | _ = try MockCollectionEnum.mappedValue(basedOn: .max) 67 | caughtError = nil 68 | } catch let error as MappedValueRepresentableError { 69 | caughtError = error 70 | } catch { 71 | XCTAssertNil(error) 72 | caughtError = nil 73 | } 74 | 75 | XCTAssertEqual(caughtError, .valueNotFound) 76 | } 77 | 78 | internal func testCodingOptions() { 79 | XCTAssertEqual(MockDictionaryEnum.codingOptions, .default) 80 | } 81 | 82 | internal func testInvalidRaw() throws { 83 | let rawValue = Int.random(in: 5 ... 1_000) 84 | 85 | let rawValueJSON = "\(rawValue)" 86 | 87 | let rawValueJSONData = rawValueJSON.data(using: .utf8)! 88 | 89 | let decodingError: DecodingError 90 | do { 91 | let value = try Self.decoder.decode(MockCollectionEnum.self, from: rawValueJSONData) 92 | XCTAssertNil(value) 93 | return 94 | } catch let error as DecodingError { 95 | decodingError = error 96 | } 97 | 98 | XCTAssertNotNil(decodingError) 99 | } 100 | 101 | internal func testCodable() throws { 102 | let argumentSets = MockCollectionEnum.allCases.flatMap { 103 | [($0, true), ($0, false)] 104 | }.flatMap { 105 | [($0.0, $0.1, true), ($0.0, $0.1, false)] 106 | } 107 | 108 | for arguments in argumentSets { 109 | try codableTest(value: arguments.0, allowMappedValue: arguments.1, encodeAsMappedValue: arguments.2) 110 | } 111 | } 112 | 113 | static let encoder = JSONEncoder() 114 | static let decoder = JSONDecoder() 115 | 116 | private func codableTest(value: MockCollectionEnum, allowMappedValue: Bool, encodeAsMappedValue: Bool) throws { 117 | let mappedValue = try value.mappedValue() 118 | let rawValue = value.rawValue 119 | 120 | let mappedValueJSON = "\"\(mappedValue)\"" 121 | let rawValueJSON = "\(rawValue)" 122 | 123 | let mappedValueJSONData = mappedValueJSON.data(using: .utf8)! 124 | let rawValueJSONData = rawValueJSON.data(using: .utf8)! 125 | 126 | let oldOptions = MockCollectionEnum.codingOptions 127 | MockCollectionEnum.codingOptions = .init([ 128 | allowMappedValue ? CodingOptions.allowMappedValueDecoding : nil, 129 | encodeAsMappedValue ? CodingOptions.encodeAsMappedValue : nil 130 | ].compactMap { $0 }) 131 | 132 | defer { 133 | MockCollectionEnum.codingOptions = oldOptions 134 | } 135 | 136 | let mappedDecodeResult = Result { 137 | try Self.decoder.decode(MockCollectionEnum.self, from: mappedValueJSONData) 138 | } 139 | 140 | let actualRawValueDecoded = try Self.decoder.decode(MockCollectionEnum.self, from: rawValueJSONData) 141 | 142 | let actualEncodedJSON = try Self.encoder.encode(value) 143 | 144 | switch (allowMappedValue, mappedDecodeResult) { 145 | case (true, let .success(actualMappedDecodedValue)): 146 | XCTAssertEqual(actualMappedDecodedValue, value) 147 | case (false, let .failure(error)): 148 | XCTAssert(error is DecodingError) 149 | default: 150 | XCTFail("Unmatched situation \(allowMappedValue): \(mappedDecodeResult)") 151 | } 152 | 153 | XCTAssertEqual(actualRawValueDecoded, value) 154 | 155 | XCTAssertEqual(actualEncodedJSON, encodeAsMappedValue ? mappedValueJSONData : rawValueJSONData) 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /Tests/OptionsTests/MappedValueDictionaryRepresentedTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MappedValueDictionaryRepresentedTests.swift 3 | // SimulatorServices 4 | // 5 | // Created by Leo Dion. 6 | // Copyright © 2024 BrightDigit. 7 | // 8 | // Permission is hereby granted, free of charge, to any person 9 | // obtaining a copy of this software and associated documentation 10 | // files (the “Software”), to deal in the Software without 11 | // restriction, including without limitation the rights to use, 12 | // copy, modify, merge, publish, distribute, sublicense, and/or 13 | // sell copies of the Software, and to permit persons to whom the 14 | // Software is furnished to do so, subject to the following 15 | // conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | // OTHER DEALINGS IN THE SOFTWARE. 28 | // 29 | 30 | @testable import Options 31 | import XCTest 32 | 33 | internal final class MappedValueDictionaryRepresentedTests: XCTestCase { 34 | internal func testRawValue() { 35 | try XCTAssertEqual(MockDictionaryEnum.rawValue(basedOn: "a"), 2) 36 | try XCTAssertEqual(MockDictionaryEnum.rawValue(basedOn: "b"), 5) 37 | try XCTAssertEqual(MockDictionaryEnum.rawValue(basedOn: "c"), 6) 38 | try XCTAssertEqual(MockDictionaryEnum.rawValue(basedOn: "d"), 12) 39 | } 40 | 41 | internal func testString() { 42 | try XCTAssertEqual(MockDictionaryEnum.mappedValue(basedOn: 2), "a") 43 | try XCTAssertEqual(MockDictionaryEnum.mappedValue(basedOn: 5), "b") 44 | try XCTAssertEqual(MockDictionaryEnum.mappedValue(basedOn: 6), "c") 45 | try XCTAssertEqual(MockDictionaryEnum.mappedValue(basedOn: 12), "d") 46 | } 47 | 48 | internal func testRawValueFailure() { 49 | let caughtError: MappedValueRepresentableError? 50 | do { 51 | _ = try MockDictionaryEnum.rawValue(basedOn: "e") 52 | caughtError = nil 53 | } catch let error as MappedValueRepresentableError { 54 | caughtError = error 55 | } catch { 56 | XCTAssertNil(error) 57 | caughtError = nil 58 | } 59 | 60 | XCTAssertEqual(caughtError, .valueNotFound) 61 | } 62 | 63 | internal func testStringFailure() { 64 | let caughtError: MappedValueRepresentableError? 65 | do { 66 | _ = try MockDictionaryEnum.mappedValue(basedOn: 0) 67 | caughtError = nil 68 | } catch let error as MappedValueRepresentableError { 69 | caughtError = error 70 | } catch { 71 | XCTAssertNil(error) 72 | caughtError = nil 73 | } 74 | 75 | XCTAssertEqual(caughtError, .valueNotFound) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Tests/OptionsTests/MappedValueRepresentableTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MappedValueRepresentableTests.swift 3 | // SimulatorServices 4 | // 5 | // Created by Leo Dion. 6 | // Copyright © 2024 BrightDigit. 7 | // 8 | // Permission is hereby granted, free of charge, to any person 9 | // obtaining a copy of this software and associated documentation 10 | // files (the “Software”), to deal in the Software without 11 | // restriction, including without limitation the rights to use, 12 | // copy, modify, merge, publish, distribute, sublicense, and/or 13 | // sell copies of the Software, and to permit persons to whom the 14 | // Software is furnished to do so, subject to the following 15 | // conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | // OTHER DEALINGS IN THE SOFTWARE. 28 | // 29 | 30 | @testable import Options 31 | import XCTest 32 | 33 | internal final class MappedValueRepresentableTests: XCTestCase { 34 | internal func testStringValue() { 35 | try XCTAssertEqual(MockCollectionEnum.a.mappedValue(), "a") 36 | try XCTAssertEqual(MockCollectionEnum.b.mappedValue(), "b") 37 | try XCTAssertEqual(MockCollectionEnum.c.mappedValue(), "c") 38 | try XCTAssertEqual(MockCollectionEnum.d.mappedValue(), "d") 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Tests/OptionsTests/Mocks/MockCollectionEnum.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockCollectionEnum.swift 3 | // SimulatorServices 4 | // 5 | // Created by Leo Dion. 6 | // Copyright © 2024 BrightDigit. 7 | // 8 | // Permission is hereby granted, free of charge, to any person 9 | // obtaining a copy of this software and associated documentation 10 | // files (the “Software”), to deal in the Software without 11 | // restriction, including without limitation the rights to use, 12 | // copy, modify, merge, publish, distribute, sublicense, and/or 13 | // sell copies of the Software, and to permit persons to whom the 14 | // Software is furnished to do so, subject to the following 15 | // conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | // OTHER DEALINGS IN THE SOFTWARE. 28 | // 29 | 30 | import Options 31 | 32 | #if swift(>=5.10) 33 | // swiftlint:disable identifier_name 34 | @Options 35 | internal enum MockCollectionEnum: Int, Sendable, Codable { 36 | case a 37 | case b 38 | case c 39 | case d 40 | 41 | static var codingOptions: CodingOptions = .default 42 | } 43 | #else 44 | // swiftlint:disable identifier_name 45 | internal enum MockCollectionEnum: Int, MappedValueCollectionRepresented, Codable { 46 | case a 47 | case b 48 | case c 49 | case d 50 | internal typealias MappedType = String 51 | internal static let mappedValues = [ 52 | "a", 53 | "b", 54 | "c", 55 | "d" 56 | ] 57 | static var codingOptions: CodingOptions = .default 58 | } 59 | 60 | typealias MockCollectionEnumSet = EnumSet 61 | #endif 62 | -------------------------------------------------------------------------------- /Tests/OptionsTests/Mocks/MockDictionaryEnum.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockDictionaryEnum.swift 3 | // SimulatorServices 4 | // 5 | // Created by Leo Dion. 6 | // Copyright © 2024 BrightDigit. 7 | // 8 | // Permission is hereby granted, free of charge, to any person 9 | // obtaining a copy of this software and associated documentation 10 | // files (the “Software”), to deal in the Software without 11 | // restriction, including without limitation the rights to use, 12 | // copy, modify, merge, publish, distribute, sublicense, and/or 13 | // sell copies of the Software, and to permit persons to whom the 14 | // Software is furnished to do so, subject to the following 15 | // conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | // OTHER DEALINGS IN THE SOFTWARE. 28 | // 29 | 30 | import Options 31 | 32 | #if swift(>=5.10) 33 | // swiftlint:disable identifier_name 34 | @Options 35 | internal enum MockDictionaryEnum: Int, Sendable { 36 | case a = 2 37 | case b = 5 38 | case c = 6 39 | case d = 12 40 | } 41 | #else 42 | // swiftlint:disable identifier_name 43 | internal enum MockDictionaryEnum: Int, MappedValueDictionaryRepresented, Codable { 44 | case a = 2 45 | case b = 5 46 | case c = 6 47 | case d = 12 48 | internal typealias MappedType = String 49 | internal static var mappedValues = [ 50 | 2: "a", 51 | 5: "b", 52 | 6: "c", 53 | 12: "d" 54 | ] 55 | } 56 | 57 | typealias MockDictionaryEnumSet = EnumSet 58 | #endif 59 | -------------------------------------------------------------------------------- /Tests/OptionsTests/Mocks/MockError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockError.swift 3 | // SimulatorServices 4 | // 5 | // Created by Leo Dion. 6 | // Copyright © 2024 BrightDigit. 7 | // 8 | // Permission is hereby granted, free of charge, to any person 9 | // obtaining a copy of this software and associated documentation 10 | // files (the “Software”), to deal in the Software without 11 | // restriction, including without limitation the rights to use, 12 | // copy, modify, merge, publish, distribute, sublicense, and/or 13 | // sell copies of the Software, and to permit persons to whom the 14 | // Software is furnished to do so, subject to the following 15 | // conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | // OTHER DEALINGS IN THE SOFTWARE. 28 | // 29 | 30 | import Foundation 31 | 32 | internal struct MockError: Error { 33 | internal let value: T 34 | } 35 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | ignore: 2 | - "Tests" 3 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brightdigit/Options/a2fd9e31d5fdf1a0e9d61fe76ab5a4461d10b08a/logo.png -------------------------------------------------------------------------------- /logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | 538 | 539 | 540 | 541 | 542 | 543 | 544 | 545 | 546 | 547 | 548 | 549 | 550 | 551 | 552 | -------------------------------------------------------------------------------- /project.yml: -------------------------------------------------------------------------------- 1 | name: Options 2 | settings: 3 | LINT_MODE: ${LINT_MODE} 4 | packages: 5 | StealthyStash: 6 | path: . 7 | aggregateTargets: 8 | Lint: 9 | buildScripts: 10 | - path: Scripts/lint.sh 11 | name: Lint 12 | basedOnDependencyAnalysis: false 13 | schemes: {} --------------------------------------------------------------------------------