Skip to content

Commit

Permalink
Merge pull request #10 from thebarndog/feature/environment-manipulation
Browse files Browse the repository at this point in the history
[Feature] - Adding and Removing Values
  • Loading branch information
thebarndog authored Feb 6, 2022
2 parents d1fcf5b + 2e71994 commit 0a7249c
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 7 deletions.
4 changes: 2 additions & 2 deletions Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
]
Expand Down
1 change: 1 addition & 0 deletions Sources/Dotenv.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
64 changes: 59 additions & 5 deletions Sources/Environment.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, Value>
public private(set) var values: OrderedDictionary<String, Value>

/// Process info instance.
private let processInfo: ProcessInfo
Expand Down Expand Up @@ -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<String, Value>, processInfo: processInfo)
}

/// Create an environment from the contents of a file.
/// - Parameter contents: File contents.
Expand All @@ -188,10 +194,46 @@ 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
}

/// 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, force: true)
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
Expand All @@ -202,17 +244,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
Expand Down
60 changes: 60 additions & 0 deletions Tests/EnvironmentTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}

0 comments on commit 0a7249c

Please sign in to comment.