Skip to content
Snippets Groups Projects
Commit 6b9d1e17 authored by Alexis Bridoux's avatar Alexis Bridoux
Browse files

Command-line tool output array dictionary

Also refactored PathEplorers and added a license
parent a43fe2e9
Branches
Tags
No related merge requests found
LICENSE 0 → 100644
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
// 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)
}
}
......@@ -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)
}
}
......@@ -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])
}
}
}
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: "")
}
}
}
......@@ -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
}
}
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)
}
}
......@@ -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)
}
}
......@@ -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")
......
......@@ -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
}
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment