diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..481deedcf0f43938452ca0d8a20a399dcf41de49 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) [2020] Alexis Bridoux + +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. \ No newline at end of file diff --git a/Sources/Scout/Definitions/PathExplorer+Extensions.swift b/Sources/Scout/Definitions/PathExplorer+Extensions.swift new file mode 100644 index 0000000000000000000000000000000000000000..10bbd6f03121ba80c9079442f0d1cef14312f407 --- /dev/null +++ b/Sources/Scout/Definitions/PathExplorer+Extensions.swift @@ -0,0 +1,61 @@ +// MARK: Literal types extensions + +extension PathExplorer { + public init(stringLiteral value: Self.StringLiteralType) { + self.init(value: value) + } +} + +extension PathExplorer { + public init(booleanLiteral value: Self.BooleanLiteralType) { + self.init(value: value) + } +} + +extension PathExplorer { + public init(integerLiteral value: Self.IntegerLiteralType) { + self.init(value: value) + } +} + +extension PathExplorer { + public init(floatLiteral value: Self.FloatLiteralType) { + self.init(value: value) + } +} + +// MARK: Data validation + +extension PathExplorer { + + /// Ensure a value as a correct type + /// - Parameter value: The value to convert + /// - Parameter type: The type to use to convert the value. Use `automatic` to let the function try the available types + /// - Throws: PathExplorerError.valueConversionError when the value is not convertible to the type or to be automatically converted + /// - Returns: The value converted to the optimal type + func convert<Type: KeyAllowedType>(_ value: Any, to type: KeyType<Type>) throws -> Type { + + if !(type is AutomaticType) { + // avoid to try to infer the type if it's specified + return try Type(value: value) + } + + // try to infer the type + + // handle the case when value is a string + if let stringValue = (value as? CustomStringConvertible)?.description { + if let bool = Bool(stringValue) { + return try Type(value: bool) + } else if let int = Int(stringValue) { + return try Type(value: int) + } else if let double = Double(stringValue) { + return try Type(value: double) + } else { + return try Type(value: stringValue) + } + } + + // otherwise, try to return the type as it is + return try Type(value: value) + } +} diff --git a/Sources/Scout/Definitions/PathExplorer.swift b/Sources/Scout/Definitions/PathExplorer.swift index 3075f33422cefa9468871ef654664c9fc67d3d47..c64f6d6095028c0309d44a27a9701eada26c17f8 100644 --- a/Sources/Scout/Definitions/PathExplorer.swift +++ b/Sources/Scout/Definitions/PathExplorer.swift @@ -352,65 +352,3 @@ where func exportData() throws -> Data func exportString() throws -> String } - -// MARK: Literal types extensions - -extension PathExplorer { - public init(stringLiteral value: Self.StringLiteralType) { - self.init(value: value) - } -} - -extension PathExplorer { - public init(booleanLiteral value: Self.BooleanLiteralType) { - self.init(value: value) - } -} - -extension PathExplorer { - public init(integerLiteral value: Self.IntegerLiteralType) { - self.init(value: value) - } -} - -extension PathExplorer { - public init(floatLiteral value: Self.FloatLiteralType) { - self.init(value: value) - } -} - -// MARK: Data validation - -extension PathExplorer { - - /// Ensure a value as a correct type - /// - Parameter value: The value to convert - /// - Parameter type: The type to use to convert the value. Use `automatic` to let the function try the available types - /// - Throws: PathExplorerError.valueConversionError when the value is not convertible to the type or to be automatically converted - /// - Returns: The value converted to the optimal type - func convert<Type: KeyAllowedType>(_ value: Any, to type: KeyType<Type>) throws -> Type { - - if !(type is AutomaticType) { - // avoid to try to infer the type if it's specified - return try Type(value: value) - } - - // try to infer the type - - // handle the case when value is a string - if let stringValue = (value as? CustomStringConvertible)?.description { - if let bool = Bool(stringValue) { - return try Type(value: bool) - } else if let int = Int(stringValue) { - return try Type(value: int) - } else if let double = Double(stringValue) { - return try Type(value: double) - } else { - return try Type(value: stringValue) - } - } - - // otherwise, try to return the type as it is - return try Type(value: value) - } -} diff --git a/Sources/Scout/Definitions/SerializationFormat.swift b/Sources/Scout/Definitions/SerializationFormat.swift index ef55a156ad5916dde52292febd736fe73a2b01ae..62eafd530daea320ccc59fc462b31fa4b6108bcc 100644 --- a/Sources/Scout/Definitions/SerializationFormat.swift +++ b/Sources/Scout/Definitions/SerializationFormat.swift @@ -22,6 +22,11 @@ public struct JsonFormat: SerializationFormat { } public static func serialize(value: Any) throws -> Data { - try JSONSerialization.data(withJSONObject: value, options: [.prettyPrinted]) + if #available(OSX 10.15, *) { + return try JSONSerialization.data(withJSONObject: value, options: [.prettyPrinted, .withoutEscapingSlashes]) + } else { + return try JSONSerialization.data(withJSONObject: value, options: [.prettyPrinted]) + } + } } diff --git a/Sources/Scout/Implementations/PathExplorerSerialization+PathExplorer.swift b/Sources/Scout/Implementations/PathExplorerSerialization+PathExplorer.swift new file mode 100644 index 0000000000000000000000000000000000000000..4e776e2a7ec4c5d5122473178e7945a084ef36c2 --- /dev/null +++ b/Sources/Scout/Implementations/PathExplorerSerialization+PathExplorer.swift @@ -0,0 +1,120 @@ +import Foundation + +extension PathExplorerSerialization: PathExplorer { + + public var string: String? { value as? String } + public var bool: Bool? { value as? Bool } + public var int: Int? { value as? Int } + public var real: Double? { value as? Double } + + public var stringValue: String { + switch value { + case let bool as Bool: + return bool.description + case let int as Int: + return String(int) + case let double as Double: + return String(double) + case let string as String: + return string + default: + return "" + } + } + + public var description: String { + if let description = try? exportString() { + return description + } else { + return "Unable to convert \(String(describing: self)) to a String. The serialization has thrown an error. Try the `exportString()` function" + } + } + + public var format: DataFormat { + if F.self == JsonFormat.self { + return .json + } else if F.self == PlistFormat.self { + return .plist + } else { + fatalError("Serialiation format not recognized. Allowed: Jsonformat and PlistFormat") + } + + } + + // MARK: Get + + public func get(_ pathElements: PathElement...) throws -> Self { + try get(pathElements) + } + + public func get<T: KeyAllowedType>(_ path: Path, as type: KeyType<T>) throws -> T { + try T(value: get(path).value) + } + + public func get<T: KeyAllowedType>(_ pathElements: PathElement..., as type: KeyType<T>) throws -> T { + try T(value: get(pathElements).value) + } + + // MARK: Set + + public mutating func set(_ path: [PathElement], to newValue: Any) throws { + try set(path, to: newValue, as: .automatic) + } + + public mutating func set<Type: KeyAllowedType>(_ pathElements: PathElement..., to newValue: Any, as type: KeyType<Type>) throws { + try set(pathElements, to: newValue, as: type) + } + + public mutating func set(_ pathElements: PathElement..., to newValue: Any) throws { + try set(pathElements, to: newValue, as: .automatic) + } + + // -- Set key name + + public mutating func set(_ pathElements: PathElement..., keyNameTo newKeyName: String) throws { + try set(pathElements, keyNameTo: newKeyName) + } + + // MARK: Delete + + public mutating func delete(_ pathElements: PathElement...) throws { + try delete(pathElements) + } + + // MARK: Add + + public mutating func add(_ newValue: Any, at path: Path) throws { + try add(newValue, at: path, as: .automatic) + } + + public mutating func add(_ newValue: Any, at pathElements: PathElement...) throws { + try add(newValue, at: pathElements, as: .automatic) + } + + public mutating func add<Type>(_ newValue: Any, at pathElements: PathElement..., as type: KeyType<Type>) throws where Type: KeyAllowedType { + try add(newValue, at: pathElements, as: type) + } + + // MARK: Export + + public func exportData() throws -> Data { + try F.serialize(value: value) + } + + public func exportString() throws -> String { + let data = try exportData() + guard let string = String(data: data, encoding: .utf8) else { + throw PathExplorerError.stringToDataConversionError + } + + guard F.self == JsonFormat.self else { return string } + + if #available(OSX 10.15, *) { + // the without backslash option is available + return string + } else { + // we have to remvove the back slashes + return string.replacingOccurrences(of: "\\", with: "") + } + } +} diff --git a/Sources/Scout/Implementations/PathExplorerSerialization.swift b/Sources/Scout/Implementations/PathExplorerSerialization.swift index 7e466c8a639a739dd454845162f0f6050bcdb716..d363e37b898a026f8a7a1ee30fdae3388c81c566 100644 --- a/Sources/Scout/Implementations/PathExplorerSerialization.swift +++ b/Sources/Scout/Implementations/PathExplorerSerialization.swift @@ -374,114 +374,3 @@ public struct PathExplorerSerialization<F: SerializationFormat> { value = currentPathExplorer.value } } - -extension PathExplorerSerialization: PathExplorer { - - public var string: String? { value as? String } - public var bool: Bool? { value as? Bool } - public var int: Int? { value as? Int } - public var real: Double? { value as? Double } - - public var stringValue: String { - switch value { - case let bool as Bool: - return bool.description - case let int as Int: - return String(int) - case let double as Double: - return String(double) - case let string as String: - return string - default: - return "" - } - } - - public var description: String { - if let description = try? exportString() { - return description - } else { - return "Unable to convert \(String(describing: self)) to a String. The serialization has thrown an error. Try the `exportString()` function" - } - } - - public var format: DataFormat { - if F.self == JsonFormat.self { - return .json - } else if F.self == PlistFormat.self { - return .plist - } else { - fatalError("Serialiation format not recognized. Allowed: Jsonformat and PlistFormat") - } - - } - - // MARK: Get - - public func get(_ pathElements: PathElement...) throws -> Self { - try get(pathElements) - } - - public func get<T: KeyAllowedType>(_ path: Path, as type: KeyType<T>) throws -> T { - try T(value: get(path).value) - } - - public func get<T: KeyAllowedType>(_ pathElements: PathElement..., as type: KeyType<T>) throws -> T { - try T(value: get(pathElements).value) - } - - // MARK: Set - - public mutating func set(_ path: [PathElement], to newValue: Any) throws { - try set(path, to: newValue, as: .automatic) - } - - public mutating func set<Type: KeyAllowedType>(_ pathElements: PathElement..., to newValue: Any, as type: KeyType<Type>) throws { - try set(pathElements, to: newValue, as: type) - } - - public mutating func set(_ pathElements: PathElement..., to newValue: Any) throws { - try set(pathElements, to: newValue, as: .automatic) - } - - // -- Set key name - - public mutating func set(_ pathElements: PathElement..., keyNameTo newKeyName: String) throws { - try set(pathElements, keyNameTo: newKeyName) - } - - // MARK: Delete - - public mutating func delete(_ pathElements: PathElement...) throws { - try delete(pathElements) - } - - // MARK: Add - - public mutating func add(_ newValue: Any, at path: Path) throws { - try add(newValue, at: path, as: .automatic) - } - - public mutating func add(_ newValue: Any, at pathElements: PathElement...) throws { - try add(newValue, at: pathElements, as: .automatic) - } - - public mutating func add<Type>(_ newValue: Any, at pathElements: PathElement..., as type: KeyType<Type>) throws where Type: KeyAllowedType { - try add(newValue, at: pathElements, as: type) - } - - // MARK: Export - - public func exportData() throws -> Data { - try F.serialize(value: value) - } - - public func exportString() throws -> String { - let data = try exportData() - guard let string = String(data: data, encoding: .utf8) else { - throw PathExplorerError.stringToDataConversionError - } - - return string - } -} diff --git a/Sources/Scout/Implementations/PathExplorerXML+PathExplorer.swift b/Sources/Scout/Implementations/PathExplorerXML+PathExplorer.swift new file mode 100644 index 0000000000000000000000000000000000000000..3c1d237f6f87fc8f817396a5612ca461246dcdce --- /dev/null +++ b/Sources/Scout/Implementations/PathExplorerXML+PathExplorer.swift @@ -0,0 +1,71 @@ +extension PathExplorerXML: PathExplorer { + public var string: String? { element.string.trimmingCharacters(in: .whitespacesAndNewlines) == "" ? nil : element.string } + public var bool: Bool? { element.bool } + public var int: Int? { element.int } + public var real: Double? { element.double } + + public var stringValue: String { element.string } + + public var description: String { element.xml } + + public var format: DataFormat { .xml } + + // MARK: Get + + public func get(_ pathElements: PathElement...) throws -> Self { + try get(pathElements) + } + + public func get(_ pathElements: Path) throws -> Self { + var currentPathExplorer = self + + try pathElements.forEach { + currentPathExplorer = try currentPathExplorer.get(pathElement: $0) + } + + return currentPathExplorer + } + + public func get<T>(_ path: Path, as type: KeyType<T>) throws -> T where T: KeyAllowedType { + let explorer = try get(path) + + guard let value = explorer.element.value else { + throw PathExplorerError.underlyingError("Program error. No value at '\(path.description)' although the path is valid.") + } + return try T(value: value) + } + + public func get<T>(_ pathElements: PathElement..., as type: KeyType<T>) throws -> T where T: KeyAllowedType { + try get(pathElements, as: type) + } + + // MARK: Set + + public mutating func set<Type>(_ path: [PathElement], to newValue: Any, as type: KeyType<Type>) throws where Type: KeyAllowedType { + try set(path, to: newValue) + } + + public mutating func set(_ pathElements: PathElement..., to newValue: Any) throws { + try set(pathElements, to: newValue) + } + + public mutating func set<Type>(_ pathElements: PathElement..., to newValue: Any, as type: KeyType<Type>) throws where Type: KeyAllowedType { + try set(pathElements, to: newValue) + } + + // -- Set key name + + public mutating func set(_ pathElements: PathElement..., keyNameTo newKeyName: String) throws { + try set(pathElements, keyNameTo: newKeyName) + } + + // MARK: Add + + public mutating func add<Type>(_ newValue: Any, at path: Path, as type: KeyType<Type>) throws where Type: KeyAllowedType { + try add(newValue, at: path) + } + + public mutating func add<Type>(_ newValue: Any, at pathElements: PathElement..., as type: KeyType<Type>) throws where Type: KeyAllowedType { + try add(newValue, at: pathElements) + } +} diff --git a/Sources/Scout/Implementations/PathExplorerXML.swift b/Sources/Scout/Implementations/PathExplorerXML.swift index f0465b21896a51624206f3957ec399102c310176..7653eeaf717e4a42e2cefd35b645057639859a7a 100644 --- a/Sources/Scout/Implementations/PathExplorerXML.swift +++ b/Sources/Scout/Implementations/PathExplorerXML.swift @@ -239,76 +239,3 @@ public struct PathExplorerXML { AEXMLDocument(root: element, options: .init()).xml } } - -extension PathExplorerXML: PathExplorer { - public var string: String? { element.string } - public var bool: Bool? { element.bool } - public var int: Int? { element.int } - public var real: Double? { element.double } - - public var stringValue: String { element.string } - - public var description: String { element.xml } - - public var format: DataFormat { .xml } - - // MARK: Get - - public func get(_ pathElements: PathElement...) throws -> Self { - try get(pathElements) - } - - public func get(_ pathElements: Path) throws -> Self { - var currentPathExplorer = self - - try pathElements.forEach { - currentPathExplorer = try currentPathExplorer.get(pathElement: $0) - } - - return currentPathExplorer - } - - public func get<T>(_ path: Path, as type: KeyType<T>) throws -> T where T: KeyAllowedType { - let explorer = try get(path) - - guard let value = explorer.element.value else { - throw PathExplorerError.underlyingError("Program error. No value at '\(path.description)' although the path is valid.") - } - return try T(value: value) - } - - public func get<T>(_ pathElements: PathElement..., as type: KeyType<T>) throws -> T where T: KeyAllowedType { - try get(pathElements, as: type) - } - - // MARK: Set - - public mutating func set<Type>(_ path: [PathElement], to newValue: Any, as type: KeyType<Type>) throws where Type: KeyAllowedType { - try set(path, to: newValue) - } - - public mutating func set(_ pathElements: PathElement..., to newValue: Any) throws { - try set(pathElements, to: newValue) - } - - public mutating func set<Type>(_ pathElements: PathElement..., to newValue: Any, as type: KeyType<Type>) throws where Type: KeyAllowedType { - try set(pathElements, to: newValue) - } - - // -- Set key name - - public mutating func set(_ pathElements: PathElement..., keyNameTo newKeyName: String) throws { - try set(pathElements, keyNameTo: newKeyName) - } - - // MARK: Add - - public mutating func add<Type>(_ newValue: Any, at path: Path, as type: KeyType<Type>) throws where Type: KeyAllowedType { - try add(newValue, at: path) - } - - public mutating func add<Type>(_ newValue: Any, at pathElements: PathElement..., as type: KeyType<Type>) throws where Type: KeyAllowedType { - try add(newValue, at: pathElements) - } - -} diff --git a/Sources/ScoutCLT/ReadCommand.swift b/Sources/ScoutCLT/ReadCommand.swift index 9f8898e6b7c56def87ccacba493b57da20c1fd9b..22798520c9f5c54d969979f3f3a3e01e7225883f 100644 --- a/Sources/ScoutCLT/ReadCommand.swift +++ b/Sources/ScoutCLT/ReadCommand.swift @@ -83,11 +83,14 @@ struct ReadCommand: ParsableCommand { var value: String if let json = try? PathExplorerFactory.make(Json.self, from: data) { - value = try json.get(readingPath).stringValue + let key = try json.get(readingPath) + value = key.string ?? key.description } else if let plist = try? PathExplorerFactory.make(Plist.self, from: data) { - value = try plist.get(readingPath).stringValue + let key = try plist.get(readingPath) + value = key.string ?? key.description } else if let xml = try? PathExplorerFactory.make(Xml.self, from: data) { - value = try xml.get(readingPath).stringValue + let key = try xml.get(readingPath) + value = key.string ?? key.description } else { if let filePath = inputFilePath { throw RuntimeError.unknownFormat("The format of the file at \(filePath) is not recognized") diff --git a/Sources/ScoutCLT/RuntimeError.swift b/Sources/ScoutCLT/RuntimeError.swift index 6a8e6e1ca34d8b99d316b138521d25d1e8dee7a7..13d81b310cc324c8002be518c2afad93ff294793 100644 --- a/Sources/ScoutCLT/RuntimeError.swift +++ b/Sources/ScoutCLT/RuntimeError.swift @@ -8,7 +8,7 @@ enum RuntimeError: LocalizedError { var errorDescription: String? { switch self { case .invalidData(let description): return description - case .noValueAt(let path): return "No single value at '\(path)'. Either Dictionary or Array to subscript." + case .noValueAt(let path): return "No value at '\(path)'" case .unknownFormat(let description): return description } }