From a2fd9e31d5fdf1a0e9d61fe76ab5a4461d10b08a Mon Sep 17 00:00:00 2001
From: leogdion
Date: Mon, 29 Apr 2024 16:43:39 -0400
Subject: [PATCH] Updated Documentation and API (#13)
---
.gitignore | 5 +-
.swiftlint.yml | 1 -
Documentation/Reference/Options/README.md | 29 --
.../enums/MappedValueRepresentableError.md | 17 -
.../Reference/Options/extensions/EnumSet.md | 46 ---
.../MappedValueCollectionRepresented.md | 48 ---
.../MappedValueDictionaryRepresented.md | 48 ---
.../extensions/MappedValueRepresentable.md | 29 --
.../MappedValueCollectionRepresented.md | 10 -
.../MappedValueDictionaryRepresented.md | 10 -
.../protocols/MappedValueRepresentable.md | 40 ---
.../Reference/Options/structs/EnumSet.md | 50 ---
.../Reference/Options/structs/MappedEnum.md | 65 ----
.../Options/typealiases/EnumSet.RawValue.md | 7 -
README.md | 290 ++++++------------
Scripts/gh-md-toc | 76 +++--
Scripts/lint.sh | 2 +-
Sources/Options/Array.swift | 18 +-
Sources/Options/CodingOptions.swift | 49 +++
Sources/Options/Dictionary.swift | 8 +-
.../Documentation.docc/Documentation.md | 162 ++++++++++
Sources/Options/EnumSet.swift | 56 +++-
Sources/Options/Macro.swift | 4 +-
Sources/Options/MappedEnum.swift | 12 +-
.../MappedValueRepresentable+Codable.swift | 97 ++++++
.../Options/MappedValueRepresentable.swift | 18 +-
.../MappedValueRepresentableError.swift | 1 +
...ted.swift => MappedValueRepresented.swift} | 11 +-
Sources/Options/MappedValues.swift | 10 +-
Tests/OptionsTests/EnumSetTests.swift | 6 +-
...appedValueCollectionRepresentedTests.swift | 80 +++++
.../Mocks/MockCollectionEnum.swift | 9 +-
.../Mocks/MockDictionaryEnum.swift | 4 +-
33 files changed, 647 insertions(+), 671 deletions(-)
delete mode 100644 Documentation/Reference/Options/README.md
delete mode 100644 Documentation/Reference/Options/enums/MappedValueRepresentableError.md
delete mode 100644 Documentation/Reference/Options/extensions/EnumSet.md
delete mode 100644 Documentation/Reference/Options/extensions/MappedValueCollectionRepresented.md
delete mode 100644 Documentation/Reference/Options/extensions/MappedValueDictionaryRepresented.md
delete mode 100644 Documentation/Reference/Options/extensions/MappedValueRepresentable.md
delete mode 100644 Documentation/Reference/Options/protocols/MappedValueCollectionRepresented.md
delete mode 100644 Documentation/Reference/Options/protocols/MappedValueDictionaryRepresented.md
delete mode 100644 Documentation/Reference/Options/protocols/MappedValueRepresentable.md
delete mode 100644 Documentation/Reference/Options/structs/EnumSet.md
delete mode 100644 Documentation/Reference/Options/structs/MappedEnum.md
delete mode 100644 Documentation/Reference/Options/typealiases/EnumSet.RawValue.md
create mode 100644 Sources/Options/CodingOptions.swift
create mode 100644 Sources/Options/Documentation.docc/Documentation.md
create mode 100644 Sources/Options/MappedValueRepresentable+Codable.swift
rename Sources/Options/{MappedValueGenericRepresented.swift => MappedValueRepresented.swift} (89%)
diff --git a/.gitignore b/.gitignore
index bd44408..008465e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -127,4 +127,7 @@ fastlane/test_output
iOSInjectionProject/
.mint
-Output
\ No newline at end of file
+Output
+
+# Due to support for 5.10 and below
+Package.resolved
\ No newline at end of file
diff --git a/.swiftlint.yml b/.swiftlint.yml
index aebc975..6be46e8 100644
--- a/.swiftlint.yml
+++ b/.swiftlint.yml
@@ -76,7 +76,6 @@ opt_in_rules:
- sorted_first_last
- sorted_imports
- static_operator
- - strict_fileprivate
- strong_iboutlet
- toggle_bool
- trailing_closure
diff --git a/Documentation/Reference/Options/README.md b/Documentation/Reference/Options/README.md
deleted file mode 100644
index f7afffc..0000000
--- a/Documentation/Reference/Options/README.md
+++ /dev/null
@@ -1,29 +0,0 @@
-# Reference Documentation
-
-## Protocols
-
-- [MappedValueCollectionRepresented](protocols/MappedValueCollectionRepresented.md)
-- [MappedValueDictionaryRepresented](protocols/MappedValueDictionaryRepresented.md)
-- [MappedValueRepresentable](protocols/MappedValueRepresentable.md)
-
-## Structs
-
-- [EnumSet](structs/EnumSet.md)
-- [MappedEnum](structs/MappedEnum.md)
-
-## Enums
-
-- [MappedValueRepresentableError](enums/MappedValueRepresentableError.md)
-
-## Extensions
-
-- [EnumSet](extensions/EnumSet.md)
-- [MappedValueCollectionRepresented](extensions/MappedValueCollectionRepresented.md)
-- [MappedValueDictionaryRepresented](extensions/MappedValueDictionaryRepresented.md)
-- [MappedValueRepresentable](extensions/MappedValueRepresentable.md)
-
-## Typealiases
-
-- [EnumSet.RawValue](typealiases/EnumSet.RawValue.md)
-
-This file was generated by [SourceDocs](https://github.com/eneko/SourceDocs)
\ No newline at end of file
diff --git a/Documentation/Reference/Options/enums/MappedValueRepresentableError.md b/Documentation/Reference/Options/enums/MappedValueRepresentableError.md
deleted file mode 100644
index 6c4a2a9..0000000
--- a/Documentation/Reference/Options/enums/MappedValueRepresentableError.md
+++ /dev/null
@@ -1,17 +0,0 @@
-**ENUM**
-
-# `MappedValueRepresentableError`
-
-```swift
-public enum MappedValueRepresentableError: Error
-```
-
-An Error thrown when the `MappedType` value or `RawType` value
-are invalid for an `Enum`.
-
-## Cases
-### `valueNotFound`
-
-```swift
-case valueNotFound
-```
diff --git a/Documentation/Reference/Options/extensions/EnumSet.md b/Documentation/Reference/Options/extensions/EnumSet.md
deleted file mode 100644
index e8af1f1..0000000
--- a/Documentation/Reference/Options/extensions/EnumSet.md
+++ /dev/null
@@ -1,46 +0,0 @@
-**EXTENSION**
-
-# `EnumSet`
-```swift
-extension EnumSet where EnumType: CaseIterable
-```
-
-## Methods
-### `array()`
-
-```swift
-public func array() -> [EnumType]
-```
-
-Returns an array of the enum values based on the OptionSet
-- Returns: Array for each value represented by the enum.
-
-### `init(from:)`
-
-```swift
-public init(from decoder: Decoder) throws
-```
-
-Decodes the EnumSet based on an Array of MappedTypes.
-- Parameter decoder: Decoder which contains info as an array of MappedTypes.
-
-#### Parameters
-
-| Name | Description |
-| ---- | ----------- |
-| decoder | Decoder which contains info as an array of MappedTypes. |
-
-### `encode(to:)`
-
-```swift
-public func encode(to encoder: Encoder) throws
-```
-
-Encodes the EnumSet based on an Array of MappedTypes.
-- Parameter encoder: Encoder which will contain info as an array of MappedTypes.
-
-#### Parameters
-
-| Name | Description |
-| ---- | ----------- |
-| encoder | Encoder which will contain info as an array of MappedTypes. |
\ No newline at end of file
diff --git a/Documentation/Reference/Options/extensions/MappedValueCollectionRepresented.md b/Documentation/Reference/Options/extensions/MappedValueCollectionRepresented.md
deleted file mode 100644
index 7c43682..0000000
--- a/Documentation/Reference/Options/extensions/MappedValueCollectionRepresented.md
+++ /dev/null
@@ -1,48 +0,0 @@
-**EXTENSION**
-
-# `MappedValueCollectionRepresented`
-```swift
-public extension MappedValueCollectionRepresented
-```
-
-## Methods
-### `rawValue(basedOn:)`
-
-```swift
-static func rawValue(basedOn value: MappedType) throws -> RawValue
-```
-
-Gets the raw value based on the MappedType by finding the index of the mapped value.
-- Parameter value: MappedType value.
-- Throws: `MappedValueCollectionRepresentedError.valueNotFound`
- If the value was not found in the array
-- Returns:
- The raw value of the enumeration
- based on the index the MappedType value was found at.
-
-#### Parameters
-
-| Name | Description |
-| ---- | ----------- |
-| value | MappedType value. |
-
-### `mappedValue(basedOn:)`
-
-```swift
-static func mappedValue(basedOn rawValue: RawValue) throws -> MappedType
-```
-
-Gets the mapped value based on the rawValue
-by access the array at the raw value subscript.
-- Parameter rawValue: The raw value of the enumeration
- which pretains to its index in the `mappedValues` Array.
-- Throws: `MappedValueCollectionRepresentedError.valueNotFound`
- if the raw value (i.e. index) is outside the range of the `mappedValues` array.
-- Returns:
- The Mapped Type value based on the value in the array at the raw value index.
-
-#### Parameters
-
-| Name | Description |
-| ---- | ----------- |
-| rawValue | The raw value of the enumeration which pretains to its index in the `mappedValues` Array. |
\ No newline at end of file
diff --git a/Documentation/Reference/Options/extensions/MappedValueDictionaryRepresented.md b/Documentation/Reference/Options/extensions/MappedValueDictionaryRepresented.md
deleted file mode 100644
index 2dbb3d5..0000000
--- a/Documentation/Reference/Options/extensions/MappedValueDictionaryRepresented.md
+++ /dev/null
@@ -1,48 +0,0 @@
-**EXTENSION**
-
-# `MappedValueDictionaryRepresented`
-```swift
-public extension MappedValueDictionaryRepresented
-```
-
-## Methods
-### `rawValue(basedOn:)`
-
-```swift
-static func rawValue(basedOn value: MappedType) throws -> RawValue
-```
-
-Gets the raw value based on the MappedType by finding the key of the mapped value.
-- Parameter value: MappedType value.
-- Throws: `MappedValueCollectionRepresentedError.valueNotFound`
- If the value was not found in the dictionary
-- Returns:
- The raw value of the enumeration
- based on the key the MappedType value.
-
-#### Parameters
-
-| Name | Description |
-| ---- | ----------- |
-| value | MappedType value. |
-
-### `mappedValue(basedOn:)`
-
-```swift
-static func mappedValue(basedOn rawValue: RawValue) throws -> MappedType
-```
-
-Gets the mapped value based on the rawValue
-by access the dictionary at the raw value key.
-- Parameter rawValue: The raw value of the enumeration
- which pretains to its index in the `mappedValues` Array.
-- Throws: `MappedValueCollectionRepresentedError.valueNotFound`
- if the raw value (i.e. key) is not in the dictionary.
-- Returns:
- The Mapped Type value based on the raw value key.
-
-#### Parameters
-
-| Name | Description |
-| ---- | ----------- |
-| rawValue | The raw value of the enumeration which pretains to its index in the `mappedValues` Array. |
\ No newline at end of file
diff --git a/Documentation/Reference/Options/extensions/MappedValueRepresentable.md b/Documentation/Reference/Options/extensions/MappedValueRepresentable.md
deleted file mode 100644
index d60f114..0000000
--- a/Documentation/Reference/Options/extensions/MappedValueRepresentable.md
+++ /dev/null
@@ -1,29 +0,0 @@
-**EXTENSION**
-
-# `MappedValueRepresentable`
-```swift
-public extension MappedValueRepresentable
-```
-
-## Methods
-### `mappedValue()`
-
-```swift
-func mappedValue() throws -> MappedType
-```
-
-Gets the mapped value of the enumeration.
-- Parameter rawValue: The raw value of the enumeration
- which pretains to its index in the `mappedValues` Array.
-- Throws: `MappedValueCollectionRepresentedError.valueNotFound`
- if the raw value (i.e. index) is outside the range of the `mappedValues` array.
-- Returns:
- The Mapped Type value based on the value in the array at the raw value index.
-Gets the mapped value of the enumeration.
-- Returns: The `MappedType` value
-
-#### Parameters
-
-| Name | Description |
-| ---- | ----------- |
-| rawValue | The raw value of the enumeration which pretains to its index in the `mappedValues` Array. |
\ No newline at end of file
diff --git a/Documentation/Reference/Options/protocols/MappedValueCollectionRepresented.md b/Documentation/Reference/Options/protocols/MappedValueCollectionRepresented.md
deleted file mode 100644
index eccfc19..0000000
--- a/Documentation/Reference/Options/protocols/MappedValueCollectionRepresented.md
+++ /dev/null
@@ -1,10 +0,0 @@
-**PROTOCOL**
-
-# `MappedValueCollectionRepresented`
-
-```swift
-public protocol MappedValueCollectionRepresented: MappedValueRepresentable
- where RawValue == Int, MappedType: Equatable
-```
-
-Protocol which simplifies mapped value by using an ordered Array of values.
diff --git a/Documentation/Reference/Options/protocols/MappedValueDictionaryRepresented.md b/Documentation/Reference/Options/protocols/MappedValueDictionaryRepresented.md
deleted file mode 100644
index d43d620..0000000
--- a/Documentation/Reference/Options/protocols/MappedValueDictionaryRepresented.md
+++ /dev/null
@@ -1,10 +0,0 @@
-**PROTOCOL**
-
-# `MappedValueDictionaryRepresented`
-
-```swift
-public protocol MappedValueDictionaryRepresented: MappedValueRepresentable
- where RawValue == Int, MappedType: Equatable
-```
-
-Protocol which simplifies mapped value by using a dictionary.
diff --git a/Documentation/Reference/Options/protocols/MappedValueRepresentable.md b/Documentation/Reference/Options/protocols/MappedValueRepresentable.md
deleted file mode 100644
index d2d54cf..0000000
--- a/Documentation/Reference/Options/protocols/MappedValueRepresentable.md
+++ /dev/null
@@ -1,40 +0,0 @@
-**PROTOCOL**
-
-# `MappedValueRepresentable`
-
-```swift
-public protocol MappedValueRepresentable: RawRepresentable, CaseIterable
-```
-
-## Methods
-### `rawValue(basedOn:)`
-
-```swift
-static func rawValue(basedOn string: MappedType) throws -> RawValue
-```
-
-Gets the raw value based on the MappedType.
-- Parameter value: MappedType value.
-- Returns: The raw value of the enumeration based on the `MappedType `value.
-
-#### Parameters
-
-| Name | Description |
-| ---- | ----------- |
-| value | MappedType value. |
-
-### `mappedValue(basedOn:)`
-
-```swift
-static func mappedValue(basedOn rawValue: RawValue) throws -> MappedType
-```
-
-Gets the `MappedType` value based on the `rawValue`.
-- Parameter rawValue: The raw value of the enumeration.
-- Returns: The Mapped Type value based on the `rawValue`.
-
-#### Parameters
-
-| Name | Description |
-| ---- | ----------- |
-| rawValue | The raw value of the enumeration. |
\ No newline at end of file
diff --git a/Documentation/Reference/Options/structs/EnumSet.md b/Documentation/Reference/Options/structs/EnumSet.md
deleted file mode 100644
index 36f9ca9..0000000
--- a/Documentation/Reference/Options/structs/EnumSet.md
+++ /dev/null
@@ -1,50 +0,0 @@
-**STRUCT**
-
-# `EnumSet`
-
-```swift
-public struct EnumSet: OptionSet
- where EnumType.RawValue == Int
-```
-
-Generic struct for using Enums with RawValue type of Int as an Optionset
-
-## Properties
-### `rawValue`
-
-```swift
-public let rawValue: Int
-```
-
-Raw Value of the OptionSet
-
-## Methods
-### `init(rawValue:)`
-
-```swift
-public init(rawValue: Int)
-```
-
-Creates the EnumSet based on the `rawValue`
-- Parameter rawValue: Integer raw value of the OptionSet
-
-#### Parameters
-
-| Name | Description |
-| ---- | ----------- |
-| rawValue | Integer raw value of the OptionSet |
-
-### `init(values:)`
-
-```swift
-public init(values: [EnumType])
-```
-
-Creates the EnumSet based on the values in the array.
-- Parameter values: Array of enum values.
-
-#### Parameters
-
-| Name | Description |
-| ---- | ----------- |
-| values | Array of enum values. |
\ No newline at end of file
diff --git a/Documentation/Reference/Options/structs/MappedEnum.md b/Documentation/Reference/Options/structs/MappedEnum.md
deleted file mode 100644
index 18695d7..0000000
--- a/Documentation/Reference/Options/structs/MappedEnum.md
+++ /dev/null
@@ -1,65 +0,0 @@
-**STRUCT**
-
-# `MappedEnum`
-
-```swift
-public struct MappedEnum: Codable
- where EnumType.MappedType: Codable
-```
-
-A generic struct for enumerations which allow for additional values attached.
-
-## Properties
-### `value`
-
-```swift
-public let value: EnumType
-```
-
-Base Enumeraion value.
-
-## Methods
-### `init(value:)`
-
-```swift
-public init(value: EnumType)
-```
-
-Creates an instance based on the base enumeration value.
-- Parameter value: Base Enumeration value.
-
-#### Parameters
-
-| Name | Description |
-| ---- | ----------- |
-| value | Base Enumeration value. |
-
-### `init(from:)`
-
-```swift
-public init(from decoder: Decoder) throws
-```
-
-Decodes the value based on the mapped value.
-- Parameter decoder: Decoder.
-
-#### Parameters
-
-| Name | Description |
-| ---- | ----------- |
-| decoder | Decoder. |
-
-### `encode(to:)`
-
-```swift
-public func encode(to encoder: Encoder) throws
-```
-
-Encodes the value based on the mapped value.
-- Parameter encoder: Encoder.
-
-#### Parameters
-
-| Name | Description |
-| ---- | ----------- |
-| encoder | Encoder. |
\ No newline at end of file
diff --git a/Documentation/Reference/Options/typealiases/EnumSet.RawValue.md b/Documentation/Reference/Options/typealiases/EnumSet.RawValue.md
deleted file mode 100644
index b818eba..0000000
--- a/Documentation/Reference/Options/typealiases/EnumSet.RawValue.md
+++ /dev/null
@@ -1,7 +0,0 @@
-**TYPEALIAS**
-
-# `EnumSet.RawValue`
-
-```swift
-public typealias RawValue = EnumType.RawValue
-```
diff --git a/README.md b/README.md
index 074e3f1..40ed2ed 100644
--- a/README.md
+++ b/README.md
@@ -4,23 +4,17 @@
Options
-Swift Package for more powerful `Enum` types.
+More powerful options for `Enum` and `OptionSet` types.
[![SwiftPM](https://img.shields.io/badge/SPM-Linux%20%7C%20iOS%20%7C%20macOS%20%7C%20watchOS%20%7C%20tvOS-success?logo=swift)](https://swift.org)
[![Twitter](https://img.shields.io/badge/twitter-@brightdigit-blue.svg?style=flat)](http://twitter.com/brightdigit)
![GitHub](https://img.shields.io/github/license/brightdigit/Options)
![GitHub issues](https://img.shields.io/github/issues/brightdigit/Options)
-
-[![macOS](https://github.com/brightdigit/Options/workflows/macOS/badge.svg)](https://github.com/brightdigit/Options/actions?query=workflow%3AmacOS)
-[![ubuntu](https://github.com/brightdigit/Options/workflows/ubuntu/badge.svg)](https://github.com/brightdigit/Options/actions?query=workflow%3Aubuntu)
-[![Travis (.com)](https://img.shields.io/travis/com/brightdigit/Options?logo=travis&?label=travis-ci)](https://travis-ci.com/brightdigit/Options)
-[![Bitrise](https://img.shields.io/bitrise/1c35b4466fb0a529?logo=bitrise&?label=bitrise&token=ryjPpLP4dkC5v-RV1fzKaw)](https://app.bitrise.io/app/1c35b4466fb0a529)
-[![CircleCI](https://img.shields.io/circleci/build/github/brightdigit/Options?logo=circleci&?label=circle-ci&token=a7ad8ba0bfc08f6c9c0ce786ac9c1ddfac871993)](https://app.circleci.com/pipelines/github/brightdigit/Options)
+![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/brightdigit/Options/Options.yml?label=actions&logo=github&?branch=main)
[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fbrightdigit%2FOptions%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/brightdigit/Options)
[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fbrightdigit%2FOptions%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/brightdigit/Options)
-
[![Codecov](https://img.shields.io/codecov/c/github/brightdigit/Options)](https://codecov.io/gh/brightdigit/Options)
[![CodeFactor Grade](https://img.shields.io/codefactor/grade/github/brightdigit/Options)](https://www.codefactor.io/repository/github/brightdigit/Options)
[![codebeat badge](https://codebeat.co/badges/c47b7e58-867c-410b-80c5-57e10140ba0f)](https://codebeat.co/projects/github-com-brightdigit-mistkit-main)
@@ -32,254 +26,156 @@ Swift Package for more powerful `Enum` types.
# Table of Contents
- * [**Introduction**](#introduction)
- * [**Features**](#features)
- * [**Installation**](#installation)
- * [**Usage**](#usage)
- * [Setting up a **`MappedValueRepresentable`** Enum](#setting-up-a-mappedvaluerepresentable-enum)
- * [Using **`MappedValueCollectionRepresented`**](#using-mappedvaluecollectionrepresented)
- * [Codable Enums using a **`MappedEnum`** Type](#codable-enums-using-a-mappedenum-type)
- * [Using Enums in OptionSets with **`EnumSet`**](#using-enums-in-optionsets-with-enumset)
- * [Converting **`EnumSet`** to Enum Array](#converting-enumset-to-enum-array)
- * [Codable **`EnumSet`** using a **`MappedValueRepresentable`** Enum](#codable-enumset-using-a-mappedvaluerepresentable-enum)
- * [Further Code Documentation](#further-code-documentation)
- * [**License**](#license)
+ * [Introduction](#introduction)
+ * [Requirements](#requirements)
+ * [Installation](#installation)
+ * [Usage](#usage)
+ * [Versatile Options with Enums and OptionSets](#versatile-options-with-enums-and-optionsets)
+ * [Multiple Value Types](#multiple-value-types)
+ * [Creating an OptionSet](#creating-an-optionset)
+ * [Further Code Documentation](#further-code-documentation)
+ * [License](#license)
# Introduction
-**Options** provides a features to `Enum` and `OptionSet` types such as:
-
-* Providing additional value types besides the `RawType rawValue`
-* Being able to interchange between `Enum` and `OptionSet` types
-* Using an additional value type for a `Codable` `OptionSet`
+**Options** provides a powerful set of features for `Enum` and `OptionSet` types:
-## Example
+- Providing additional representations for `Enum` types besides the `RawType rawValue`
+- Being able to interchange between `Enum` and `OptionSet` types
+- Using an additional value type for a `Codable` `OptionSet`
-Let's say you have `Enum` type:
+# Requirements
-```swift
-enum ContinuousIntegrationSystem {
- case github
- case travisci
- case circleci
- case bitrise
-}
-```
+**Apple Platforms**
-We want two things:
-
-* Use it as an `OptionSet` so we can store multiple CI sytems
-* Store and parse it via a `String`
-
-If `OptionSet` requires an `Int` `RawType`, how can we parse and store as `String`?
-
-With **Options** we can enable `ContinuousIntegrationSystem` to do both:
-
-```swift
-enum ContinuousIntegrationSystem: Int, MappedValueCollectionRepresented {
- case github
- case travisci
- case circleci
- case bitrise
-
- typealias MappedType = String
-
- static let mappedValues = [
- "github",
- "travisci",
- "circleci",
- "bitrise"
- ]
-}
+- Xcode 14.1 or later
+- Swift 5.7.1 or later
+- iOS 16 / watchOS 9 / tvOS 16 / macOS 12 or later deployment targets
-typealias ContinuousIntegrationSystemSet = EnumSet
+**Linux**
-let systems = ContinuousIntegrationSystemSet([.travisci, .github])
-```
+- Ubuntu 20.04 or later
+- Swift 5.7.1 or later
# Installation
-Swift Package Manager is Apple's decentralized dependency manager to integrate libraries to your Swift projects. It is now fully integrated with Xcode 11.
-
-To integrate **Options** into your project using SPM, specify it in your Package.swift file:
-
-```swift
-let package = Package(
- ...
- dependencies: [
- .package(url: "https://github.com/brightdigit/Options", from: "0.1.0")
- ],
- targets: [
- .target(
- name: "YourTarget",
- dependencies: ["Options", ...]),
- ...
- ]
-)
+Use the Swift Package Manager to install this library via the repository url:
+
+```
+https://github.com/brightdigit/Options.git
```
+Use version up to `1.0`.
+
# Usage
-## Setting up a [MappedValueRepresentable](/Documentation/Reference/Options/protocols/MappedValueRepresentable.md) Enum
+## Versatile Options
-So let's say we our `enum`:
+Let's say we are using an `Enum` for a list of popular social media networks:
```swift
-enum ContinuousIntegrationSystem: Int {
- case github
- case travisci
- case circleci
- case bitrise
+enum SocialNetwork : Int {
+ case digg
+ case aim
+ case bebo
+ case delicious
+ case eworld
+ case googleplus
+ case itunesping
+ case jaiku
+ case miiverse
+ case musically
+ case orkut
+ case posterous
+ case stumbleupon
+ case windowslive
+ case yahoo
}
```
-We want to be able to make it available as an `OptionSet` so it needs an `RawType` of `Int`.
-However we want to decode and encode it via `Codable` as a `String`.
-
-**Options** has a protocol `MappedValueRepresentable` which allows to do that by implementing it.
+We'll be using this as a way to define a particular social handle:
```swift
-enum ContinuousIntegrationSystem: Int, MappedValueRepresentable {
- case github
- case travisci
- case circleci
- case bitrise
-
- static func rawValue(basedOn string: String) throws -> Int {
- if (string == "github") {
- return 0
- } else {
- ...
- } else {
- throw ...
- }
- }
-
- static func mappedValue(basedOn rawValue: Int) throws -> String {
- if (rawValue == 0) {
- return "github"
- } else {
- ...
- } else {
- throw ...
- }
- }
+struct SocialHandle {
+ let name : String
+ let network : SocialNetwork
}
```
-This can be simplified further by using [`MappedValueCollectionRepresented`](/Documentation/Reference/Options/protocols/MappedValueCollectionRepresented.md).
-
-## Using [MappedValueCollectionRepresented](/Documentation/Reference/Options/protocols/MappedValueCollectionRepresented.md)
-
-By using `MappedValueCollectionRepresented`, you can simplify implementing [`MappedValueRepresentable`](/Documentation/Reference/Options/protocols/MappedValueRepresentable.md):
+However we also want to provide a way to have a unique set of social networks available:
```swift
-enum ContinuousIntegrationSystem: Int, MappedValueCollectionRepresented {
- case github
- case travisci
- case circleci
- case bitrise
-
- static let mappedValues = [
- "github",
- "travisci",
- "circleci",
- "bitrise"
- ]
+struct SocialNetworkSet : Int, OptionSet {
+...
}
-```
-Now we we've made it simplifies implementing [`MappedValueRepresentable`](/Documentation/Reference/Options/protocols/MappedValueRepresentable.md) so let's look how to use it with `Codable`.
-
-## Codable Enums using a [MappedEnum](/Documentation/Reference/Options/structs/MappedEnum.md) Type
-
-So you've setup a `MappedValueRepresentable` `enum`, the next part is having the `MappedType` which in this case is `String` the part that's used in `Codable`.
+let user : User
+let networks : SocialNetworkSet = user.availableNetworks()
+```
-This is where [`MappedEnum`](/Documentation/Reference/Options/structs/MappedEnum.md) is used:
+We can then simply use ``Options()`` macro to generate both these types:
```swift
-struct BuildSetup : Codable {
- let ci: MappedEnum
+@Options
+enum SocialNetwork : Int {
+ case digg
+ case aim
+ case bebo
+ case delicious
+ case eworld
+ case googleplus
+ case itunesping
+ case jaiku
+ case miiverse
+ case musically
+ case orkut
+ case posterous
+ case stumbleupon
+ case windowslive
+ case yahoo
}
```
-Now if the `String` can be used in encoding and decoding the value rather than the `RawType` `Int`:
+Now we can use the newly create `SocialNetworkSet` type to store a set of values:
-```json
-{
- "ci" : "github"
-}
+```swift
+let networks : SocialNetworkSet
+networks = [.aim, .delicious, .googleplus, .windowslive]
```
-Next, let's take a look how we could use `ContinuousIntegrationSystem` in an `OptionSet`.
+## Multiple Value Types
-## Using Enums in OptionSets with [EnumSet](/Documentation/Reference/Options/structs/EnumSet.md)
+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.
-[`EnumSet`](/Documentation/Reference/Options/structs/EnumSet.md) allows you to interchangeably use `Enum` with an `OptionSet`. [`EnumSet`](/Documentation/Reference/Options/structs/EnumSet.md) is a Generic `struct` while takes any `Enum` type with a `RawType`. So we can create an `OptionSet` instance which uses out `ContinuousIntegrationSystem`:
+For instance, with a type like `SocialNetwork` we need need to store the value as an Integer:
-```swift
-let systems = EnumSet([.travisci, .github])
+```json
+5
```
-## Converting EnumSet to Enum Array
-
-If your `Enum` implements `CaseIterable`, then you can extract the individual `ContinuousIntegrationSystem` enum values with `.array()`:
-
-```swift
-enum ContinuousIntegrationSystem: Int, CaseIterable {
- case github
- case travisci
- case circleci
- case bitrise
-}
-
-let systems = EnumSet([.travisci, .github])
+However by adding the ``Options()`` macro we can also decode from a String:
-print(systems.array())
+```
+"googleplus"
```
-Lastly, let's put all this together.
-
-## Codable EnumSet using a MappedValueRepresentable Enum
+## Creating an OptionSet
-If your `enum` implements `MappedValueRepresentable` and you use it in an [`EnumSet`](/Documentation/Reference/Options/structs/EnumSet.md), then you can allow for your `OptionSet` to be `Codable` as an `Array` of values rather than the cumulative `rawValue`:
+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:
```swift
-enum ContinuousIntegrationSystem: Int, MappedValueCollectionRepresented, CaseIterable {
-case github
-case travisci
-case circleci
-case bitrise
-
-static let mappedValues = [
- "github",
- "travisci",
- "circleci",
- "bitrise"
-]
-}
-
-
-struct BuildSetup : Codable {
- let systems: EnumSet
-}
-
-let systems = BuildSetup(systems: EnumSet(values: [.travisci, .github]))
+[.aim, .delicious, .googleplus, .windowslive]
```
-For our `systems` variable, our `Codable` data would be:
+is encoded as:
```json
-{
- "systems" : ["travisci", "github"]
-}
+["aim", "delicious", "googleplus", "windowslive"]
```
-This will make it easier for making our data human-readable instead of using the `rawValue` of `3`.
-
# Further Code Documentation
-[Documentation Here](/Documentation/Reference/Options/README.md)
+[Documentation Here](https://swiftpackageindex.com/brightdigit/Options/main/documentation/options)
# License
diff --git a/Scripts/gh-md-toc b/Scripts/gh-md-toc
index 8d35839..03b5ddd 100755
--- a/Scripts/gh-md-toc
+++ b/Scripts/gh-md-toc
@@ -23,7 +23,7 @@
# substr($0, match($0, "href=\"[^\"]+?\" ")+6, RLENGTH-8)
#
-gh_toc_version="0.8.0"
+gh_toc_version="0.10.0"
gh_user_agent="gh-md-toc v$gh_toc_version"
@@ -55,24 +55,24 @@ gh_toc_md2html() {
URL=https://api.github.com/markdown/raw
- if [ ! -z "$GH_TOC_TOKEN" ]; then
+ if [ -n "$GH_TOC_TOKEN" ]; then
TOKEN=$GH_TOC_TOKEN
else
TOKEN_FILE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/token.txt"
if [ -f "$TOKEN_FILE" ]; then
- TOKEN="$(cat $TOKEN_FILE)"
+ TOKEN="$(cat "$TOKEN_FILE")"
fi
fi
- if [ ! -z "${TOKEN}" ]; then
+ if [ -n "${TOKEN}" ]; then
AUTHORIZATION="Authorization: token ${TOKEN}"
fi
local gh_tmp_file_md=$gh_file_md
if [ "$skip_header" = "yes" ]; then
- if grep -Fxq "" $gh_src; then
+ if grep -Fxq "" "$gh_src"; then
# cut everything before the toc
gh_tmp_file_md=$gh_file_md~~
- sed '1,//d' $gh_file_md > $gh_tmp_file_md
+ sed '1,//d' "$gh_file_md" > "$gh_tmp_file_md"
fi
fi
@@ -84,7 +84,7 @@ gh_toc_md2html() {
-H "$AUTHORIZATION" \
"$URL")
- rm -f $gh_file_md~~
+ rm -f "${gh_file_md}~~"
if [ "$?" != "0" ]; then
echo "XXNetworkErrorXX"
@@ -152,7 +152,8 @@ gh_toc(){
echo
fi
else
- local rawhtml=$(gh_toc_md2html "$gh_src" "$skip_header")
+ local rawhtml
+ rawhtml=$(gh_toc_md2html "$gh_src" "$skip_header")
if [ "$rawhtml" == "XXNetworkErrorXX" ]; then
echo "Parsing local markdown file requires access to github API"
echo "Please make sure curl is installed and check your network connectivity"
@@ -165,10 +166,11 @@ gh_toc(){
echo "or place GitHub auth token here: ${TOKEN_FILE}"
exit 1
fi
- local toc=`echo "$rawhtml" | gh_toc_grab "$gh_src_copy" "$indent"`
+ local toc
+ toc=`echo "$rawhtml" | gh_toc_grab "$gh_src_copy" "$indent"`
echo "$toc"
if [ "$need_replace" = "yes" ]; then
- if grep -Fxq "" $gh_src && grep -Fxq "" $gh_src; then
+ if grep -Fxq "" "$gh_src" && grep -Fxq "" "$gh_src"; then
echo "Found markers"
else
echo "You don't have or in your file...exiting"
@@ -176,14 +178,16 @@ gh_toc(){
fi
local ts="<\!--ts-->"
local te="<\!--te-->"
- local dt=`date +'%F_%H%M%S'`
+ local dt
+ dt=$(date +'%F_%H%M%S')
local ext=".orig.${dt}"
local toc_path="${gh_src}.toc.${dt}"
local toc_createdby=""
- local toc_footer=""
+ local toc_footer
+ toc_footer=""
# http://fahdshariff.blogspot.ru/2012/12/sed-mutli-line-replacement-between-two.html
# clear old TOC
- sed -i${ext} "/${ts}/,/${te}/{//!d;}" "$gh_src"
+ sed -i"${ext}" "/${ts}/,/${te}/{//!d;}" "$gh_src"
# create toc file
echo "${toc}" > "${toc_path}"
if [ "${no_footer}" != "yes" ]; then
@@ -198,7 +202,7 @@ gh_toc(){
fi
echo
if [ "${no_backup}" = "yes" ]; then
- rm ${toc_path} ${gh_src}${ext}
+ rm "$toc_path" "$gh_src$ext"
fi
echo "!! TOC was added into: '$gh_src'"
if [ -z "${no_backup}" ]; then
@@ -218,6 +222,8 @@ gh_toc(){
# $2 - number of spaces used to indent.
#
gh_toc_grab() {
+
+ href_regex="/href=\"[^\"]+?\"/"
common_awk_script='
modified_href = ""
split(href, chars, "")
@@ -237,26 +243,25 @@ gh_toc_grab() {
}
print sprintf("%*s", (level-1)*'"$2"', "") "* [" text "](" gh_url modified_href ")"
'
- if [ `uname -s` == "OS/390" ]; then
+ if [ "`uname -s`" == "OS/390" ]; then
grepcmd="pcregrep -o"
echoargs=""
awkscript='{
- level = substr($0, length($0), 1)
- text = substr($0, match($0, /a>.*<\/h/)+2, RLENGTH-5)
- href = substr($0, match($0, "href=\"([^\"]+)?\"")+6, RLENGTH-7)
+ level = substr($0, 3, 1)
+ text = substr($0, match($0, /<\/span><\/a>[^<]*<\/h/)+11, RLENGTH-14)
+ href = substr($0, match($0, '$href_regex')+6, RLENGTH-7)
'"$common_awk_script"'
}'
else
grepcmd="grep -Eo"
echoargs="-e"
awkscript='{
- level = substr($0, length($0), 1)
- text = substr($0, match($0, /a>.*<\/h/)+2, RLENGTH-5)
- href = substr($0, match($0, "href=\"[^\"]+?\"")+6, RLENGTH-7)
+ level = substr($0, 3, 1)
+ text = substr($0, match($0, /">.*<\/h/)+2, RLENGTH-5)
+ href = substr($0, match($0, '$href_regex')+6, RLENGTH-7)
'"$common_awk_script"'
}'
fi
- href_regex='href=\"[^\"]+?\"'
# if closed is on the new line, then move it on the prev line
# for example:
@@ -265,8 +270,11 @@ gh_toc_grab() {
# became: The command foo1
sed -e ':a' -e 'N' -e '$!ba' -e 's/\n<\/h/<\/h/g' |
+ # Sometimes a line can start with . Fix that.
+ sed -e ':a' -e 'N' -e '$!ba' -e 's/\n//g' | sed 's/<\/code>//g' |
@@ -275,7 +283,7 @@ gh_toc_grab() {
sed 's/]*[^<]*<\/g-emoji> //g' |
# now all rows are like:
- # ... title..
# format result line
# * $0 - whole string
# * last element of each row: "/dev/null`; then
- echo `$tool --version | head -n 1`
+ if type $tool &>/dev/null; then
+ $tool --version | head -n 1
else
echo "not installed"
fi
@@ -310,7 +318,8 @@ show_version() {
}
show_help() {
- local app_name=$(basename "$0")
+ local app_name
+ app_name=$(basename "$0")
echo "GitHub TOC generator ($app_name): $gh_toc_version"
echo ""
echo "Usage:"
@@ -355,17 +364,18 @@ gh_toc_app() {
if [ "$1" = "-" ]; then
if [ -z "$TMPDIR" ]; then
TMPDIR="/tmp"
- elif [ -n "$TMPDIR" -a ! -d "$TMPDIR" ]; then
+ elif [ -n "$TMPDIR" ] && [ ! -d "$TMPDIR" ]; then
mkdir -p "$TMPDIR"
fi
local gh_tmp_md
- if [ `uname -s` == "OS/390" ]; then
- local timestamp=$(date +%m%d%Y%H%M%S)
+ if [ "`uname -s`" == "OS/390" ]; then
+ local timestamp
+ timestamp=$(date +%m%d%Y%H%M%S)
gh_tmp_md="$TMPDIR/tmp.$timestamp"
else
- gh_tmp_md=$(mktemp $TMPDIR/tmp.XXXXXX)
+ gh_tmp_md=$(mktemp "$TMPDIR/tmp.XXXXXX")
fi
- while read input; do
+ while read -r input; do
echo "$input" >> "$gh_tmp_md"
done
gh_toc_md2html "$gh_tmp_md" | gh_toc_grab "" "$indent"
@@ -408,4 +418,4 @@ gh_toc_app() {
#
# Entry point
#
-gh_toc_app "$@"
+gh_toc_app "$@"
\ No newline at end of file
diff --git a/Scripts/lint.sh b/Scripts/lint.sh
index fd0a0e3..31c3fa9 100755
--- a/Scripts/lint.sh
+++ b/Scripts/lint.sh
@@ -35,7 +35,7 @@ pushd $PACKAGE_DIR
if [ -z "$CI" ]; then
$MINT_RUN swiftformat .
- $MINT_RUN swiftlint autocorrect
+ $MINT_RUN swiftlint --fix
fi
$MINT_RUN swiftformat --lint $SWIFTFORMAT_OPTIONS .
diff --git a/Sources/Options/Array.swift b/Sources/Options/Array.swift
index e192aa1..0c4db74 100644
--- a/Sources/Options/Array.swift
+++ b/Sources/Options/Array.swift
@@ -29,11 +29,16 @@
// swiftlint:disable:next line_length
@available(*, deprecated, renamed: "MappedValueGenericRepresented", message: "Use MappedValueGenericRepresented instead.")
-public protocol MappedValueCollectionRepresented: MappedValueGenericRepresented
+public protocol MappedValueCollectionRepresented: MappedValueRepresented
where MappedValueType: Sequence {}
-extension Array: MappedValues where Element: Equatable {
- public func key(value: Element) throws -> Int {
+extension Array: MappedValues where Element: Equatable {}
+
+extension Collection where Element: Equatable, Self: MappedValues {
+ /// Get the index based on the value passed.
+ /// - Parameter value: Value to search.
+ /// - Returns: Index found.
+ public func key(value: Element) throws -> Self.Index {
guard let index = firstIndex(of: value) else {
throw MappedValueRepresentableError.valueNotFound
}
@@ -41,8 +46,11 @@ extension Array: MappedValues where Element: Equatable {
return index
}
- public func value(key: Int) throws -> Element {
- guard key < count, key >= 0 else {
+ /// Gets the value based on the index.
+ /// - Parameter key: The index.
+ /// - Returns: The value at index.
+ public func value(key: Self.Index) throws -> Element {
+ guard key < endIndex, key >= startIndex else {
throw MappedValueRepresentableError.valueNotFound
}
return self[key]
diff --git a/Sources/Options/CodingOptions.swift b/Sources/Options/CodingOptions.swift
new file mode 100644
index 0000000..e26ad68
--- /dev/null
+++ b/Sources/Options/CodingOptions.swift
@@ -0,0 +1,49 @@
+//
+// CodingOptions.swift
+// SimulatorServices
+//
+// Created by Leo Dion.
+// Copyright © 2024 BrightDigit.
+//
+// Permission is hereby granted, free of charge, to any person
+// obtaining a copy of this software and associated documentation
+// files (the “Software”), to deal in the Software without
+// restriction, including without limitation the rights to use,
+// copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following
+// conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+// OTHER DEALINGS IN THE SOFTWARE.
+//
+
+import Foundation
+
+/// Options for how a ``MappedValueRepresentable`` type is encoding and decoded.
+public struct CodingOptions: OptionSet, Sendable {
+ /// Allow decoding from String
+ public static let allowMappedValueDecoding: CodingOptions = .init(rawValue: 1)
+
+ /// Encode the value as a String.
+ public static let encodeAsMappedValue: CodingOptions = .init(rawValue: 2)
+
+ /// Default options.
+ public static let `default`: CodingOptions =
+ [.allowMappedValueDecoding, encodeAsMappedValue]
+
+ public let rawValue: Int
+
+ public init(rawValue: Int) {
+ self.rawValue = rawValue
+ }
+}
diff --git a/Sources/Options/Dictionary.swift b/Sources/Options/Dictionary.swift
index 79e2b8e..a7a3b31 100644
--- a/Sources/Options/Dictionary.swift
+++ b/Sources/Options/Dictionary.swift
@@ -29,11 +29,11 @@
// swiftlint:disable:next line_length
@available(*, deprecated, renamed: "MappedValueGenericRepresented", message: "Use MappedValueGenericRepresented instead.")
-public protocol MappedValueDictionaryRepresented: MappedValueGenericRepresented
+public protocol MappedValueDictionaryRepresented: MappedValueRepresented
where MappedValueType == [Int: MappedType] {}
-extension Dictionary: MappedValues where Key == Int, Value: Equatable {
- public func key(value: Value) throws -> Int {
+extension Dictionary: MappedValues where Value: Equatable {
+ public func key(value: Value) throws -> Key {
let pair = first { $0.value == value }
guard let key = pair?.key else {
throw MappedValueRepresentableError.valueNotFound
@@ -42,7 +42,7 @@ extension Dictionary: MappedValues where Key == Int, Value: Equatable {
return key
}
- public func value(key: Int) throws -> Value {
+ public func value(key: Key) throws -> Value {
guard let value = self[key] else {
throw MappedValueRepresentableError.valueNotFound
}
diff --git a/Sources/Options/Documentation.docc/Documentation.md b/Sources/Options/Documentation.docc/Documentation.md
new file mode 100644
index 0000000..994798d
--- /dev/null
+++ b/Sources/Options/Documentation.docc/Documentation.md
@@ -0,0 +1,162 @@
+# ``Options``
+
+More powerful options for `Enum` and `OptionSet` types.
+
+## Overview
+
+**Options** provides a powerful set of features for `Enum` and `OptionSet` types:
+
+- Providing additional representations for `Enum` types besides the `RawType rawValue`
+- Being able to interchange between `Enum` and `OptionSet` types
+- Using an additional value type for a `Codable` `OptionSet`
+
+### Requirements
+
+**Apple Platforms**
+
+- Xcode 14.1 or later
+- Swift 5.7.1 or later
+- iOS 16 / watchOS 9 / tvOS 16 / macOS 12 or later deployment targets
+
+**Linux**
+
+- Ubuntu 20.04 or later
+- Swift 5.7.1 or later
+
+### Installation
+
+Use the Swift Package Manager to install this library via the repository url:
+
+```
+https://github.com/brightdigit/Options.git
+```
+
+Use version up to `1.0`.
+
+### Versatile Options
+
+Let's say we are using an `Enum` for a list of popular social media networks:
+
+```swift
+enum SocialNetwork : Int {
+ case digg
+ case aim
+ case bebo
+ case delicious
+ case eworld
+ case googleplus
+ case itunesping
+ case jaiku
+ case miiverse
+ case musically
+ case orkut
+ case posterous
+ case stumbleupon
+ case windowslive
+ case yahoo
+}
+```
+
+We'll be using this as a way to define a particular social handle:
+
+```swift
+struct SocialHandle {
+ let name : String
+ let network : SocialNetwork
+}
+```
+
+However we also want to provide a way to have a unique set of social networks available:
+
+```swift
+struct SocialNetworkSet : Int, OptionSet {
+...
+}
+
+let user : User
+let networks : SocialNetworkSet = user.availableNetworks()
+```
+
+We can then simply use ``Options()`` macro to generate both these types:
+
+```swift
+@Options
+enum SocialNetwork : Int {
+ case digg
+ case aim
+ case bebo
+ case delicious
+ case eworld
+ case googleplus
+ case itunesping
+ case jaiku
+ case miiverse
+ case musically
+ case orkut
+ case posterous
+ case stumbleupon
+ case windowslive
+ case yahoo
+}
+```
+
+Now we can use the newly create `SocialNetworkSet` type to store a set of values:
+
+```swift
+let networks : SocialNetworkSet
+networks = [.aim, .delicious, .googleplus, .windowslive]
+```
+
+### Multiple Value Types
+
+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.
+
+For instance, with a type like `SocialNetwork` we need need to store the value as an Integer:
+
+```json
+5
+```
+
+However by adding the ``Options()`` macro we can also decode from a String:
+
+```
+"googleplus"
+```
+
+### Creating an OptionSet
+
+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:
+
+```swift
+[.aim, .delicious, .googleplus, .windowslive]
+```
+
+is encoded as:
+
+```json
+["aim", "delicious", "googleplus", "windowslive"]
+```
+
+## Topics
+
+### Options conformance
+
+- ``Options()``
+- ``MappedValueRepresentable``
+- ``MappedValueRepresented``
+- ``EnumSet``
+
+### Advanced customization
+
+- ``CodingOptions``
+- ``MappedValues``
+
+### Errors
+
+- ``MappedValueRepresentableError-2k4ki``
+
+### Deprecated
+
+- ``MappedValueCollectionRepresented``
+- ``MappedValueDictionaryRepresented``
+- ``MappedEnum``
diff --git a/Sources/Options/EnumSet.swift b/Sources/Options/EnumSet.swift
index 69a4b3b..c2e447e 100644
--- a/Sources/Options/EnumSet.swift
+++ b/Sources/Options/EnumSet.swift
@@ -1,17 +1,49 @@
-/// Generic struct for using Enums with RawValue type of Int as an Optionset
-public struct EnumSet: OptionSet, Sendable
- where EnumType.RawValue == Int {
+/// Generic struct for using Enums with `RawValue`.
+///
+/// If you have an `enum` such as:
+/// ```swift
+/// @Options
+/// enum SocialNetwork : Int {
+/// case digg
+/// case aim
+/// case bebo
+/// case delicious
+/// case eworld
+/// case googleplus
+/// case itunesping
+/// case jaiku
+/// case miiverse
+/// case musically
+/// case orkut
+/// case posterous
+/// case stumbleupon
+/// case windowslive
+/// case yahoo
+/// }
+/// ```
+/// An ``EnumSet`` could be used to store multiple values as an `OptionSet`:
+/// ```swift
+/// let socialNetworks : EnumSet =
+/// [.digg, .aim, .yahoo, .miiverse]
+/// ```
+public struct EnumSet:
+ OptionSet, Sendable, ExpressibleByArrayLiteral
+ where EnumType.RawValue: FixedWidthInteger & Sendable {
public typealias RawValue = EnumType.RawValue
/// Raw Value of the OptionSet
- public let rawValue: Int
+ public let rawValue: RawValue
/// Creates the EnumSet based on the `rawValue`
/// - Parameter rawValue: Integer raw value of the OptionSet
- public init(rawValue: Int) {
+ public init(rawValue: RawValue) {
self.rawValue = rawValue
}
+ public init(arrayLiteral elements: EnumType...) {
+ self.init(values: elements)
+ }
+
/// Creates the EnumSet based on the values in the array.
/// - Parameter values: Array of enum values.
public init(values: [EnumType]) {
@@ -20,13 +52,19 @@ public struct EnumSet: OptionSet, Sendable
}
internal static func cumulativeValue(
- basedOnRawValues rawValues: Set) -> Int {
+ basedOnRawValues rawValues: Set) -> RawValue {
rawValues.map { 1 << $0 }.reduce(0, |)
}
}
+extension FixedWidthInteger {
+ fileprivate static var one: Self {
+ 1
+ }
+}
+
extension EnumSet where EnumType: CaseIterable {
- internal static func enums(basedOnRawValue rawValue: Int) -> [EnumType] {
+ internal static func enums(basedOnRawValue rawValue: RawValue) -> [EnumType] {
let cases = EnumType.allCases.sorted { $0.rawValue < $1.rawValue }
var values = [EnumType]()
var current = rawValue
@@ -34,8 +72,8 @@ extension EnumSet where EnumType: CaseIterable {
guard current > 0 else {
break
}
- let rawValue = 1 << item.rawValue
- if current & rawValue != 0 {
+ let rawValue = RawValue.one << item.rawValue
+ if current & rawValue != .zero {
values.append(item)
current -= rawValue
}
diff --git a/Sources/Options/Macro.swift b/Sources/Options/Macro.swift
index e120423..ab7ff7b 100644
--- a/Sources/Options/Macro.swift
+++ b/Sources/Options/Macro.swift
@@ -30,9 +30,11 @@
import Foundation
#if swift(>=5.10)
+ /// Sets an enumeration up to implement
+ /// ``MappedValueRepresentable`` and ``MappedValueRepresented``.
@attached(
extension,
- conformances: MappedValueRepresentable, MappedValueGenericRepresented,
+ conformances: MappedValueRepresentable, MappedValueRepresented,
names: named(MappedType), named(mappedValues)
)
@attached(peer, names: suffixed(Set))
diff --git a/Sources/Options/MappedEnum.swift b/Sources/Options/MappedEnum.swift
index 1e651d5..9298ada 100644
--- a/Sources/Options/MappedEnum.swift
+++ b/Sources/Options/MappedEnum.swift
@@ -1,4 +1,10 @@
/// A generic struct for enumerations which allow for additional values attached.
+@available(
+ *,
+ deprecated,
+ renamed: "MappedValueRepresentable",
+ message: "Use `MappedValueRepresentable` with `CodingOptions`."
+)
public struct MappedEnum: Codable, Sendable
where EnumType.MappedType: Codable {
/// Base Enumeraion value.
@@ -20,7 +26,8 @@ public struct MappedEnum: Codable, Sendable
let label = try container.decode(EnumType.MappedType.self)
let rawValue = try EnumType.rawValue(basedOn: label)
guard let value = EnumType(rawValue: rawValue) else {
- preconditionFailure("Invalid Raw Value.")
+ assertionFailure("Every mappedValue should always return a valid rawValue.")
+ throw DecodingError.invalidRawValue(rawValue)
}
self.value = value
}
@@ -42,7 +49,8 @@ public struct MappedEnum: Codable, Sendable
let label = try container.decode(EnumType.MappedType.self)
let rawValue = try EnumType.rawValue(basedOn: label)
guard let value = EnumType(rawValue: rawValue) else {
- preconditionFailure("Invalid Raw Value.")
+ assertionFailure("Every mappedValue should always return a valid rawValue.")
+ throw DecodingError.invalidRawValue(rawValue)
}
self.value = value
}
diff --git a/Sources/Options/MappedValueRepresentable+Codable.swift b/Sources/Options/MappedValueRepresentable+Codable.swift
new file mode 100644
index 0000000..550f7c6
--- /dev/null
+++ b/Sources/Options/MappedValueRepresentable+Codable.swift
@@ -0,0 +1,97 @@
+//
+// MappedValueRepresentable+Codable.swift
+// SimulatorServices
+//
+// Created by Leo Dion.
+// Copyright © 2024 BrightDigit.
+//
+// Permission is hereby granted, free of charge, to any person
+// obtaining a copy of this software and associated documentation
+// files (the “Software”), to deal in the Software without
+// restriction, including without limitation the rights to use,
+// copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following
+// conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+// OTHER DEALINGS IN THE SOFTWARE.
+//
+
+import Foundation
+
+extension DecodingError {
+ internal static func invalidRawValue(_ rawValue: some Any) -> DecodingError {
+ .dataCorrupted(
+ .init(codingPath: [], debugDescription: "Raw Value \(rawValue) is invalid.")
+ )
+ }
+}
+
+extension SingleValueDecodingContainer {
+ fileprivate func decodeAsRawValue() throws -> T
+ where T.RawValue: Decodable {
+ let rawValue = try decode(T.RawValue.self)
+ guard let value = T(rawValue: rawValue) else {
+ throw DecodingError.invalidRawValue(rawValue)
+ }
+ return value
+ }
+
+ fileprivate func decodeAsMappedType() throws -> T
+ where T.RawValue: Decodable, T.MappedType: Decodable {
+ let mappedValues: T.MappedType
+ do {
+ mappedValues = try decode(T.MappedType.self)
+ } catch {
+ return try decodeAsRawValue()
+ }
+
+ let rawValue = try T.rawValue(basedOn: mappedValues)
+
+ guard let value = T(rawValue: rawValue) else {
+ assertionFailure("Every mappedValue should always return a valid rawValue.")
+ throw DecodingError.invalidRawValue(rawValue)
+ }
+
+ return value
+ }
+}
+
+extension MappedValueRepresentable
+ where Self: Decodable, MappedType: Decodable, RawValue: Decodable {
+ /// Decodes the type.
+ /// - Parameter decoder: Decoder.
+ public init(from decoder: any Decoder) throws {
+ let container = try decoder.singleValueContainer()
+
+ if Self.codingOptions.contains(.allowMappedValueDecoding) {
+ self = try container.decodeAsMappedType()
+ } else {
+ self = try container.decodeAsRawValue()
+ }
+ }
+}
+
+extension MappedValueRepresentable
+ where Self: Encodable, MappedType: Encodable, RawValue: Encodable {
+ /// Encoding the type.
+ /// - Parameter decoder: Encodes.
+ public func encode(to encoder: any Encoder) throws {
+ var container = encoder.singleValueContainer()
+ if Self.codingOptions.contains(.encodeAsMappedValue) {
+ try container.encode(mappedValue())
+ } else {
+ try container.encode(rawValue)
+ }
+ }
+}
diff --git a/Sources/Options/MappedValueRepresentable.swift b/Sources/Options/MappedValueRepresentable.swift
index 415ca82..896f4a9 100644
--- a/Sources/Options/MappedValueRepresentable.swift
+++ b/Sources/Options/MappedValueRepresentable.swift
@@ -27,8 +27,17 @@
// OTHER DEALINGS IN THE SOFTWARE.
//
+/// An enum which has an additional value attached.
+/// - Note: ``Options()`` macro will automatically set this up for you.
public protocol MappedValueRepresentable: RawRepresentable, CaseIterable, Sendable {
+ /// The additional value type.
associatedtype MappedType = String
+
+ /// Options for how the enum should be decoded or encoded.
+ static var codingOptions: CodingOptions {
+ get
+ }
+
/// Gets the raw value based on the MappedType.
/// - Parameter value: MappedType value.
/// - Returns: The raw value of the enumeration based on the `MappedType `value.
@@ -41,6 +50,11 @@ public protocol MappedValueRepresentable: RawRepresentable, CaseIterable, Sendab
}
extension MappedValueRepresentable {
+ /// Options regarding how the type can be decoded or encoded.
+ public static var codingOptions: CodingOptions {
+ .default
+ }
+
/// Gets the mapped value of the enumeration.
/// - Parameter rawValue: The raw value of the enumeration
/// which pretains to its index in the `mappedValues` Array.
@@ -48,10 +62,6 @@ extension MappedValueRepresentable {
/// if the raw value (i.e. index) is outside the range of the `mappedValues` array.
/// - Returns:
/// The Mapped Type value based on the value in the array at the raw value index.
-
- /// Gets the mapped value of the enumeration.
-
- /// - Returns: The `MappedType` value
public func mappedValue() throws -> MappedType {
try Self.mappedValue(basedOn: rawValue)
}
diff --git a/Sources/Options/MappedValueRepresentableError.swift b/Sources/Options/MappedValueRepresentableError.swift
index 3d1389f..d303174 100644
--- a/Sources/Options/MappedValueRepresentableError.swift
+++ b/Sources/Options/MappedValueRepresentableError.swift
@@ -34,6 +34,7 @@ import Foundation
/// An Error thrown when the `MappedType` value or `RawType` value
/// are invalid for an `Enum`.
public enum MappedValueRepresentableError: Error, Sendable {
+ /// Whenever a value or key cannot be found.
case valueNotFound
}
#else
diff --git a/Sources/Options/MappedValueGenericRepresented.swift b/Sources/Options/MappedValueRepresented.swift
similarity index 89%
rename from Sources/Options/MappedValueGenericRepresented.swift
rename to Sources/Options/MappedValueRepresented.swift
index 271510b..26ef4e5 100644
--- a/Sources/Options/MappedValueGenericRepresented.swift
+++ b/Sources/Options/MappedValueRepresented.swift
@@ -1,5 +1,5 @@
//
-// MappedValueGenericRepresented.swift
+// MappedValueRepresented.swift
// SimulatorServices
//
// Created by Leo Dion.
@@ -28,14 +28,15 @@
//
/// Protocol which simplifies ``MappedValueRepresentable``by using a ``MappedValues``.
-public protocol MappedValueGenericRepresented: MappedValueRepresentable
- where RawValue == Int, MappedType: Equatable {
- associatedtype MappedValueType: MappedValues
+public protocol MappedValueRepresented: MappedValueRepresentable
+ where MappedType: Equatable {
+ /// A object to lookup values and keys for mapped values.
+ associatedtype MappedValueType: MappedValues
/// An array of the mapped values which lines up with each case.
static var mappedValues: MappedValueType { get }
}
-extension MappedValueGenericRepresented {
+extension MappedValueRepresented {
/// Gets the raw value based on the MappedType by finding the index of the mapped value.
/// - Parameter value: MappedType value.
/// - Throws: `MappedValueCollectionRepresentedError.valueNotFound`
diff --git a/Sources/Options/MappedValues.swift b/Sources/Options/MappedValues.swift
index 8d067eb..67d0a45 100644
--- a/Sources/Options/MappedValues.swift
+++ b/Sources/Options/MappedValues.swift
@@ -29,12 +29,14 @@
import Foundation
-/// Protocol which provides a method for ``MappedValueGenericRepresented`` to pull values.
-public protocol MappedValues {
+/// Protocol which provides a method for ``MappedValueRepresented`` to pull values.
+public protocol MappedValues {
/// Raw Value Type
associatedtype Value: Equatable
+ /// Key Value Type
+ associatedtype Key: Equatable
/// get the key vased on the value.
- func key(value: Value) throws -> Int
+ func key(value: Value) throws -> Key
/// get the value based on the key/index.
- func value(key: Int) throws -> Value
+ func value(key: Key) throws -> Value
}
diff --git a/Tests/OptionsTests/EnumSetTests.swift b/Tests/OptionsTests/EnumSetTests.swift
index c741779..5e55439 100644
--- a/Tests/OptionsTests/EnumSetTests.swift
+++ b/Tests/OptionsTests/EnumSetTests.swift
@@ -75,8 +75,10 @@ internal final class EnumSetTests: XCTestCase {
internal func testInitValues() {
let values: [MockCollectionEnum] = [.a, .b, .c]
- let set = EnumSet(values: values)
- XCTAssertEqual(set.rawValue, 7)
+ let setA = EnumSet(values: values)
+ XCTAssertEqual(setA.rawValue, 7)
+ let setB: MockCollectionEnumSet = [.a, .b, .c]
+ XCTAssertEqual(setB.rawValue, 7)
}
internal func testArray() {
diff --git a/Tests/OptionsTests/MappedValueCollectionRepresentedTests.swift b/Tests/OptionsTests/MappedValueCollectionRepresentedTests.swift
index 8719c74..98565ea 100644
--- a/Tests/OptionsTests/MappedValueCollectionRepresentedTests.swift
+++ b/Tests/OptionsTests/MappedValueCollectionRepresentedTests.swift
@@ -74,4 +74,84 @@ internal final class MappedValueCollectionRepresentedTests: XCTestCase {
XCTAssertEqual(caughtError, .valueNotFound)
}
+
+ internal func testCodingOptions() {
+ XCTAssertEqual(MockDictionaryEnum.codingOptions, .default)
+ }
+
+ internal func testInvalidRaw() throws {
+ let rawValue = Int.random(in: 5 ... 1_000)
+
+ let rawValueJSON = "\(rawValue)"
+
+ let rawValueJSONData = rawValueJSON.data(using: .utf8)!
+
+ let decodingError: DecodingError
+ do {
+ let value = try Self.decoder.decode(MockCollectionEnum.self, from: rawValueJSONData)
+ XCTAssertNil(value)
+ return
+ } catch let error as DecodingError {
+ decodingError = error
+ }
+
+ XCTAssertNotNil(decodingError)
+ }
+
+ internal func testCodable() throws {
+ let argumentSets = MockCollectionEnum.allCases.flatMap {
+ [($0, true), ($0, false)]
+ }.flatMap {
+ [($0.0, $0.1, true), ($0.0, $0.1, false)]
+ }
+
+ for arguments in argumentSets {
+ try codableTest(value: arguments.0, allowMappedValue: arguments.1, encodeAsMappedValue: arguments.2)
+ }
+ }
+
+ static let encoder = JSONEncoder()
+ static let decoder = JSONDecoder()
+
+ private func codableTest(value: MockCollectionEnum, allowMappedValue: Bool, encodeAsMappedValue: Bool) throws {
+ let mappedValue = try value.mappedValue()
+ let rawValue = value.rawValue
+
+ let mappedValueJSON = "\"\(mappedValue)\""
+ let rawValueJSON = "\(rawValue)"
+
+ let mappedValueJSONData = mappedValueJSON.data(using: .utf8)!
+ let rawValueJSONData = rawValueJSON.data(using: .utf8)!
+
+ let oldOptions = MockCollectionEnum.codingOptions
+ MockCollectionEnum.codingOptions = .init([
+ allowMappedValue ? CodingOptions.allowMappedValueDecoding : nil,
+ encodeAsMappedValue ? CodingOptions.encodeAsMappedValue : nil
+ ].compactMap { $0 })
+
+ defer {
+ MockCollectionEnum.codingOptions = oldOptions
+ }
+
+ let mappedDecodeResult = Result {
+ try Self.decoder.decode(MockCollectionEnum.self, from: mappedValueJSONData)
+ }
+
+ let actualRawValueDecoded = try Self.decoder.decode(MockCollectionEnum.self, from: rawValueJSONData)
+
+ let actualEncodedJSON = try Self.encoder.encode(value)
+
+ switch (allowMappedValue, mappedDecodeResult) {
+ case (true, let .success(actualMappedDecodedValue)):
+ XCTAssertEqual(actualMappedDecodedValue, value)
+ case (false, let .failure(error)):
+ XCTAssert(error is DecodingError)
+ default:
+ XCTFail("Unmatched situation \(allowMappedValue): \(mappedDecodeResult)")
+ }
+
+ XCTAssertEqual(actualRawValueDecoded, value)
+
+ XCTAssertEqual(actualEncodedJSON, encodeAsMappedValue ? mappedValueJSONData : rawValueJSONData)
+ }
}
diff --git a/Tests/OptionsTests/Mocks/MockCollectionEnum.swift b/Tests/OptionsTests/Mocks/MockCollectionEnum.swift
index c588f4f..45a530a 100644
--- a/Tests/OptionsTests/Mocks/MockCollectionEnum.swift
+++ b/Tests/OptionsTests/Mocks/MockCollectionEnum.swift
@@ -32,15 +32,17 @@ import Options
#if swift(>=5.10)
// swiftlint:disable identifier_name
@Options
- internal enum MockCollectionEnum: Int, Sendable {
+ internal enum MockCollectionEnum: Int, Sendable, Codable {
case a
case b
case c
case d
+
+ static var codingOptions: CodingOptions = .default
}
#else
// swiftlint:disable identifier_name
- internal enum MockCollectionEnum: Int, MappedValueCollectionRepresented {
+ internal enum MockCollectionEnum: Int, MappedValueCollectionRepresented, Codable {
case a
case b
case c
@@ -52,5 +54,8 @@ import Options
"c",
"d"
]
+ static var codingOptions: CodingOptions = .default
}
+
+ typealias MockCollectionEnumSet = EnumSet
#endif
diff --git a/Tests/OptionsTests/Mocks/MockDictionaryEnum.swift b/Tests/OptionsTests/Mocks/MockDictionaryEnum.swift
index 77be088..0cb80da 100644
--- a/Tests/OptionsTests/Mocks/MockDictionaryEnum.swift
+++ b/Tests/OptionsTests/Mocks/MockDictionaryEnum.swift
@@ -40,7 +40,7 @@ import Options
}
#else
// swiftlint:disable identifier_name
- internal enum MockDictionaryEnum: Int, MappedValueDictionaryRepresented {
+ internal enum MockDictionaryEnum: Int, MappedValueDictionaryRepresented, Codable {
case a = 2
case b = 5
case c = 6
@@ -53,4 +53,6 @@ import Options
12: "d"
]
}
+
+ typealias MockDictionaryEnumSet = EnumSet
#endif