Skip to content

Commit 21bd964

Browse files
committed
refactor: migrate MistDemo from ArgumentParser to ConfigKeyKit
Implemented complete migration to centralized configuration system while preserving all authentication functionality from v1.0.0-alpha.4. ARCHITECTURE: - Created ConfigKeyKit library for hierarchical configuration management - Implemented MistDemoConfig with CLI → ENV → defaults precedence - Added AuthenticationHelper for intelligent auth method selection NEW FILES: - Sources/ConfigKeyKit/ConfigKey.swift - Configuration key with defaults - Sources/ConfigKeyKit/OptionalConfigKey.swift - Optional configuration keys - Sources/ConfigKeyKit/ConfigurationKey.swift - Base protocol and naming styles - Sources/MistDemo/Configuration/MistDemoConfig.swift - Centralized config - Sources/MistDemo/Utilities/AuthenticationHelper.swift - Auth setup logic MODIFIED FILES: - Package.swift: Added ConfigKeyKit target, removed ArgumentParser dependency - Sources/MistDemo/MistDemo.swift: Replaced ArgumentParser with ConfigKeyKit - Package.resolved: Updated for dependency changes PRESERVED FUNCTIONALITY: - All authentication methods (API-only, web auth, server-to-server, adaptive) - All test commands (--test-all-auth, --test-api-only, etc.) - Authentication server with browser-based sign-in - CloudKit demo with user fetch, zone listing, record queries - All environment variable support (CLOUDKIT_API_TOKEN, etc.) CONFIGURATION HIERARCHY: 1. Command-line arguments: --api-token, --container-identifier, etc. 2. Environment variables: CLOUDKIT_API_TOKEN, CLOUDKIT_CONTAINER_ID, etc. 3. Default values: Sensible fallbacks for optional parameters BREAKING CHANGES: - ArgumentParser removed; command-line interface unchanged functionally - Configuration now uses hierarchical resolution (CLI → ENV → defaults) Based on v1.0.0-alpha.4 (commit aee13f2)
1 parent aee13f2 commit 21bd964

File tree

8 files changed

+1079
-135
lines changed

8 files changed

+1079
-135
lines changed

Examples/MistDemo/Package.resolved

Lines changed: 1 addition & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Examples/MistDemo/Package.swift

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -86,16 +86,20 @@ let package = Package(
8686
],
8787
dependencies: [
8888
.package(path: "../.."), // MistKit
89-
.package(url: "https://github.com/hummingbird-project/hummingbird.git", from: "2.0.0"),
90-
.package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.5.0")
89+
.package(url: "https://github.com/hummingbird-project/hummingbird.git", from: "2.0.0")
9190
],
9291
targets: [
92+
.target(
93+
name: "ConfigKeyKit",
94+
dependencies: [],
95+
swiftSettings: swiftSettings
96+
),
9397
.executableTarget(
9498
name: "MistDemo",
9599
dependencies: [
100+
"ConfigKeyKit",
96101
.product(name: "MistKit", package: "MistKit"),
97-
.product(name: "Hummingbird", package: "hummingbird"),
98-
.product(name: "ArgumentParser", package: "swift-argument-parser")
102+
.product(name: "Hummingbird", package: "hummingbird")
99103
],
100104
resources: [
101105
.copy("Resources")
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
//
2+
// ConfigKey.swift
3+
// MistDemo
4+
//
5+
// Created by Leo Dion.
6+
// Copyright © 2026 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+
// MARK: - Generic Configuration Key
33+
34+
/// Configuration key for values with default fallbacks
35+
///
36+
/// Use `ConfigKey` when a configuration value has a sensible default
37+
/// that should be used when not provided by the user. The `read()` method
38+
/// will always return a non-optional value.
39+
///
40+
/// Example:
41+
/// ```swift
42+
/// let containerID = ConfigKey<String>(
43+
/// base: "cloudkit.container_id",
44+
/// default: "iCloud.com.brightdigit.Bushel"
45+
/// )
46+
/// // read(containerID) returns String (non-optional)
47+
/// ```
48+
public struct ConfigKey<Value: Sendable>: ConfigurationKey, Sendable {
49+
private let baseKey: String?
50+
private let styles: [ConfigKeySource: any NamingStyle]
51+
private let explicitKeys: [ConfigKeySource: String]
52+
public let defaultValue: Value // Non-optional!
53+
54+
/// Initialize with explicit CLI and ENV keys and required default
55+
public init(cli: String? = nil, env: String? = nil, default defaultVal: Value) {
56+
self.baseKey = nil
57+
self.styles = [:]
58+
var keys: [ConfigKeySource: String] = [:]
59+
if let cli = cli { keys[.commandLine] = cli }
60+
if let env = env { keys[.environment] = env }
61+
self.explicitKeys = keys
62+
self.defaultValue = defaultVal
63+
}
64+
65+
/// Initialize from a base key string with naming styles and required default
66+
/// - Parameters:
67+
/// - base: Base key string (e.g., "cloudkit.container_id")
68+
/// - styles: Dictionary mapping sources to naming styles
69+
/// - defaultVal: Required default value
70+
public init(
71+
base: String,
72+
styles: [ConfigKeySource: any NamingStyle],
73+
default defaultVal: Value
74+
) {
75+
self.baseKey = base
76+
self.styles = styles
77+
self.explicitKeys = [:]
78+
self.defaultValue = defaultVal
79+
}
80+
81+
/// Convenience initializer with standard naming conventions and required default
82+
/// - Parameters:
83+
/// - base: Base key string (e.g., "cloudkit.container_id")
84+
/// - envPrefix: Prefix for environment variable (defaults to nil)
85+
/// - defaultVal: Required default value
86+
public init(_ base: String, envPrefix: String? = nil, default defaultVal: Value) {
87+
self.baseKey = base
88+
self.styles = [
89+
.commandLine: StandardNamingStyle.dotSeparated,
90+
.environment: StandardNamingStyle.screamingSnakeCase(prefix: envPrefix),
91+
]
92+
self.explicitKeys = [:]
93+
self.defaultValue = defaultVal
94+
}
95+
96+
public func key(for source: ConfigKeySource) -> String? {
97+
// Check for explicit key first
98+
if let explicit = explicitKeys[source] {
99+
return explicit
100+
}
101+
102+
// Generate from base key and style
103+
guard let base = baseKey, let style = styles[source] else {
104+
return nil
105+
}
106+
107+
return style.transform(base)
108+
}
109+
}
110+
111+
extension ConfigKey: CustomDebugStringConvertible {
112+
public var debugDescription: String {
113+
let cliKey = key(for: .commandLine) ?? "nil"
114+
let envKey = key(for: .environment) ?? "nil"
115+
return "ConfigKey(cli: \(cliKey), env: \(envKey), default: \(defaultValue))"
116+
}
117+
}
118+
119+
// MARK: - Convenience Initializers for BUSHEL Prefix
120+
121+
extension ConfigKey {
122+
/// Convenience initializer for keys with BUSHEL prefix
123+
/// - Parameters:
124+
/// - base: Base key string (e.g., "sync.dry_run")
125+
/// - defaultVal: Required default value
126+
public init(bushelPrefixed base: String, default defaultVal: Value) {
127+
self.init(base, envPrefix: "BUSHEL", default: defaultVal)
128+
}
129+
}
130+
131+
// MARK: - Specialized Initializers for Booleans
132+
133+
extension ConfigKey where Value == Bool {
134+
/// Non-optional default value accessor for booleans
135+
@available(*, deprecated, message: "Use defaultValue directly instead")
136+
public var boolDefault: Bool {
137+
defaultValue // Already non-optional!
138+
}
139+
140+
/// Initialize a boolean configuration key with non-optional default
141+
/// - Parameters:
142+
/// - cli: Command-line argument name
143+
/// - env: Environment variable name
144+
/// - defaultVal: Default value (defaults to false)
145+
public init(cli: String, env: String, default defaultVal: Bool = false) {
146+
self.baseKey = nil
147+
self.styles = [:]
148+
var keys: [ConfigKeySource: String] = [:]
149+
keys[.commandLine] = cli
150+
keys[.environment] = env
151+
self.explicitKeys = keys
152+
self.defaultValue = defaultVal
153+
}
154+
155+
/// Initialize a boolean configuration key from base string
156+
/// - Parameters:
157+
/// - base: Base key string (e.g., "sync.verbose")
158+
/// - envPrefix: Prefix for environment variable (defaults to nil)
159+
/// - defaultVal: Default value (defaults to false)
160+
public init(_ base: String, envPrefix: String? = nil, default defaultVal: Bool = false) {
161+
self.baseKey = base
162+
self.styles = [
163+
.commandLine: StandardNamingStyle.dotSeparated,
164+
.environment: StandardNamingStyle.screamingSnakeCase(prefix: envPrefix),
165+
]
166+
self.explicitKeys = [:]
167+
self.defaultValue = defaultVal
168+
}
169+
}
170+
171+
// MARK: - BUSHEL Prefix Convenience
172+
173+
extension ConfigKey where Value == Bool {
174+
/// Convenience initializer for boolean keys with BUSHEL prefix
175+
/// - Parameters:
176+
/// - base: Base key string (e.g., "sync.verbose")
177+
/// - defaultVal: Default value (defaults to false)
178+
public init(bushelPrefixed base: String, default defaultVal: Bool = false) {
179+
self.init(base, envPrefix: "BUSHEL", default: defaultVal)
180+
}
181+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
//
2+
// ConfigurationKey.swift
3+
// MistDemo
4+
//
5+
// Created by Leo Dion.
6+
// Copyright © 2026 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+
// MARK: - Configuration Key Source
33+
34+
/// Source for configuration keys (CLI arguments or environment variables)
35+
public enum ConfigKeySource: CaseIterable, Sendable {
36+
/// Command-line arguments (e.g., --cloudkit-container-id)
37+
case commandLine
38+
39+
/// Environment variables (e.g., CLOUDKIT_CONTAINER_ID)
40+
case environment
41+
}
42+
43+
// MARK: - Naming Style
44+
45+
/// Protocol for transforming base key strings into different naming conventions
46+
public protocol NamingStyle: Sendable {
47+
/// Transform a base key string according to this naming style
48+
/// - Parameter base: Base key string (e.g., "cloudkit.container_id")
49+
/// - Returns: Transformed key string
50+
func transform(_ base: String) -> String
51+
}
52+
53+
/// Common naming styles for configuration keys
54+
public enum StandardNamingStyle: NamingStyle, Sendable {
55+
/// Dot-separated lowercase (e.g., "cloudkit.container_id")
56+
case dotSeparated
57+
58+
/// Screaming snake case with prefix (e.g., "BUSHEL_CLOUDKIT_CONTAINER_ID")
59+
case screamingSnakeCase(prefix: String?)
60+
61+
public func transform(_ base: String) -> String {
62+
switch self {
63+
case .dotSeparated:
64+
return base
65+
66+
case .screamingSnakeCase(let prefix):
67+
let snakeCase = base.uppercased().replacingOccurrences(of: ".", with: "_")
68+
if let prefix = prefix {
69+
return "\(prefix)_\(snakeCase)"
70+
}
71+
return snakeCase
72+
}
73+
}
74+
}
75+
76+
// MARK: - Configuration Key Protocol
77+
78+
/// Protocol for configuration keys that support multiple sources
79+
public protocol ConfigurationKey: Sendable {
80+
/// Get the key string for a specific source
81+
/// - Parameter source: The configuration source (CLI or ENV)
82+
/// - Returns: The key string for that source, or nil if the key doesn't support that source
83+
func key(for source: ConfigKeySource) -> String?
84+
}

0 commit comments

Comments
 (0)