├── .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 |
4 |
5 | Options
6 |
7 | More powerful options for `Enum` and `OptionSet` types.
8 |
9 | [](https://swift.org)
10 | [](http://twitter.com/brightdigit)
11 | 
12 | 
13 | 
14 |
15 | [](https://swiftpackageindex.com/brightdigit/Options)
16 | [](https://swiftpackageindex.com/brightdigit/Options)
17 |
18 | [](https://codecov.io/gh/brightdigit/Options)
19 | [](https://www.codefactor.io/repository/github/brightdigit/Options)
20 | [](https://codebeat.co/projects/github-com-brightdigit-mistkit-main)
21 | [](https://codeclimate.com/github/brightdigit/Options)
22 | [](https://codeclimate.com/github/brightdigit/Options)
23 | [](https://codeclimate.com/github/brightdigit/Options)
24 | [](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: {}
--------------------------------------------------------------------------------