From 2553f8c44daed1f6e7e3a624e7144cb602760e28 Mon Sep 17 00:00:00 2001 From: Brendan Conron Date: Sat, 5 Feb 2022 17:10:07 -0800 Subject: [PATCH 1/4] updated to latest collections package --- Package.resolved | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Package.resolved b/Package.resolved index 8cef17f..e56059c 100644 --- a/Package.resolved +++ b/Package.resolved @@ -6,8 +6,8 @@ "repositoryURL": "https://github.com/apple/swift-collections.git", "state": { "branch": null, - "revision": "2d33a0ea89c961dcb2b3da2157963d9c0370347e", - "version": "1.0.1" + "revision": "48254824bb4248676bf7ce56014ff57b142b77eb", + "version": "1.0.2" } } ] From 99b3b9e4dc05affb4d3f40547b36f485e5f1207a Mon Sep 17 00:00:00 2001 From: Brendan Conron Date: Sat, 5 Feb 2022 17:27:29 -0800 Subject: [PATCH 2/4] exposed values and added mutating functions --- Sources/Environment.swift | 48 +++++++++++++++++++++++++++++++++++---- 1 file changed, 43 insertions(+), 5 deletions(-) diff --git a/Sources/Environment.swift b/Sources/Environment.swift index 82aac0a..f418c3e 100644 --- a/Sources/Environment.swift +++ b/Sources/Environment.swift @@ -121,10 +121,10 @@ public struct Environment { /// Environment fallback strategy. public static var fallbackStrategy: FallbackStrategy = .init(query: .configuration, fallback: .process) - // MARK: - Private + // MARK: - Storage /// Backing environment values. - private let values: OrderedDictionary + public private(set) var values: OrderedDictionary /// Process info instance. private let processInfo: ProcessInfo @@ -188,10 +188,36 @@ public struct Environment { self.values = values self.processInfo = processInfo } + + // MARK: - Modifying the Environment + + /// Set a new value in the environment for the given key, optionally specifiying if the value should overwrite an existing value, if any exists. + /// - Parameters: + /// - value: Value to set in the environment. + /// - key: Key to set value for. + /// - force: Flag that indicates if the value should be forced if a value with the same key exists, defaults to `false`. + /// - Important: If the environment is set to read from the process, this method is a no-op as the process' environment is read-only. + public mutating func setValue(_ value: Value?, forKey key :String, force: Bool = false) { + guard values[key] == nil || force else { + return + } + values[key] = value + } + + /// Remove a value for the given key, returning the old value, if any exsts. + /// - Parameter key: Key to remove. + /// - Returns: Old value that was removed, if any. + @discardableResult + public mutating func removeValue(forKey key: String) -> Value? { + let oldValue = queryValue(forKey: key) + setValue(nil, forKey: key) + return oldValue + } // MARK: - Serialization /// Transform the environment into a string representation that can be written to disk. + /// - Important: If the environment is backed by the current process, this method will **not** serialize those values to disk, only those manually set via `setValue(_:forKey:force:)`. /// - Returns: File contents. func serialize() throws -> String { values.enumerated().reduce(into: "") { accumulated, current in @@ -202,17 +228,29 @@ public struct Environment { // MARK: - Subscript public subscript(key: String) -> Value? { - queryValue(forKey: key) + get { + queryValue(forKey: key) + } set { + setValue(newValue, forKey: key) + } } public subscript(key: String, default defaultValue: @autoclosure () -> Value) -> Value { - queryValue(forKey: key) ?? defaultValue() + get { + queryValue(forKey: key) ?? defaultValue() + } set { + setValue(newValue, forKey: key) + } } // MARK: Dynamic Member Lookup public subscript(dynamicMember member: String) -> Value? { - queryValue(forKey: member) + get { + queryValue(forKey: member) + } set { + setValue(newValue, forKey: member) + } } // MARK: - Helpers From 378f1ccb185b4af45cd83fd663aed34c4d13f435 Mon Sep 17 00:00:00 2001 From: Brendan Conron Date: Sat, 5 Feb 2022 17:41:55 -0800 Subject: [PATCH 3/4] added tests --- Sources/Environment.swift | 20 ++++++++++-- Tests/EnvironmentTests.swift | 60 ++++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+), 2 deletions(-) diff --git a/Sources/Environment.swift b/Sources/Environment.swift index f418c3e..0f34c8e 100644 --- a/Sources/Environment.swift +++ b/Sources/Environment.swift @@ -163,6 +163,12 @@ public struct Environment { } self.processInfo = processInfo } + + /// Create an empty environment. + /// - Parameter processInfo: Process info to seed the environment with. + public init(processInfo: ProcessInfo = ProcessInfo.processInfo) throws { + try self.init(values: [:] as OrderedDictionary, processInfo: processInfo) + } /// Create an environment from the contents of a file. /// - Parameter contents: File contents. @@ -197,20 +203,30 @@ public struct Environment { /// - key: Key to set value for. /// - force: Flag that indicates if the value should be forced if a value with the same key exists, defaults to `false`. /// - Important: If the environment is set to read from the process, this method is a no-op as the process' environment is read-only. - public mutating func setValue(_ value: Value?, forKey key :String, force: Bool = false) { + public mutating func setValue(_ value: Value?, forKey key: String, force: Bool = false) { guard values[key] == nil || force else { return } values[key] = value } + /// Set a new value in the environment for the given key, optionally specifiying if the value should overwrite an existing value, if any exists. + /// - Parameters: + /// - value: Value to set in the environment. + /// - key: Key to set value for. + /// - force: Flag that indicates if the value should be forced if a value with the same key exists, defaults to `false`. + /// - Important: If the environment is set to read from the process, this method is a no-op as the process' environment is read-only. + public mutating func setValue(_ value: String, forKey key: String, force: Bool = false) { + setValue(Value(value), forKey: key, force: force) + } + /// Remove a value for the given key, returning the old value, if any exsts. /// - Parameter key: Key to remove. /// - Returns: Old value that was removed, if any. @discardableResult public mutating func removeValue(forKey key: String) -> Value? { let oldValue = queryValue(forKey: key) - setValue(nil, forKey: key) + setValue(nil, forKey: key, force: true) return oldValue } diff --git a/Tests/EnvironmentTests.swift b/Tests/EnvironmentTests.swift index 5bbb5d9..a62acaf 100644 --- a/Tests/EnvironmentTests.swift +++ b/Tests/EnvironmentTests.swift @@ -39,4 +39,64 @@ final class EnvironmentTests: XCTestCase { XCTAssertEqual(env.networkRetries, .integer(3)) XCTAssertEqual(env.networkTimeout, .double(10.5)) } + + // MARK: - Adding and Removing Values + + func testAddingValueForNonexistantKey() throws { + var environment = try Environment() + + XCTAssertNil(environment.key) + + environment.setValue(.integer(1), forKey: "key") + + XCTAssertEqual(environment.key, .integer(1)) + } + + func testAddingValueForExistingKeyWithoutForcing() throws { + var environment = try Environment(values: [ + "key": .integer(1) + ]) + + XCTAssertEqual(environment.key, .integer(1)) + + environment.setValue(.integer(2), forKey: "key") + + XCTAssertEqual(environment.key, .integer(1)) + } + + func testAddingValueForExistingValueWithForcing() throws { + var environment = try Environment(values: [ + "key": .integer(1) + ]) + + XCTAssertEqual(environment.key, .integer(1)) + + environment.setValue(.integer(2), forKey: "key", force: true) + + XCTAssertEqual(environment.key, .integer(2)) + } + + func testRemovingNonexistantValue() throws { + var environment = try Environment() + + XCTAssertNil(environment.key) + + let oldValue = environment.removeValue(forKey: "key") + + XCTAssertNil(oldValue) + } + + + func testRemovingValue() throws { + var environment = try Environment(values: [ + "key": .integer(1) + ]) + + XCTAssertNotNil(environment.key) + + let oldValue = environment.removeValue(forKey: "key") + + XCTAssertEqual(oldValue, .integer(1)) + XCTAssertNil(environment.key) + } } From 2e71994fa5f0ede7c2258e2cf9c27cf78799d82f Mon Sep 17 00:00:00 2001 From: Brendan Conron Date: Sat, 5 Feb 2022 17:42:56 -0800 Subject: [PATCH 4/4] added clarifying comment --- Sources/Dotenv.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/Dotenv.swift b/Sources/Dotenv.swift index e586b0c..dc10056 100644 --- a/Sources/Dotenv.swift +++ b/Sources/Dotenv.swift @@ -49,6 +49,7 @@ public struct Dotenv { } /// Save an environment object to a file. + /// - Important: This method does not serialize process values to disk. /// - Parameters: /// - environment: Environment to serialize. /// - path: Path to save the environment to.