Merge pull request #246 from ABridoux/release/4.0.1

Release 4.0.1
This commit is contained in:
Alexis Bridoux 2021-05-02 18:26:39 +02:00 committed by GitHub
commit 5b94054841
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 281 additions and 53 deletions

View File

@ -6,6 +6,20 @@
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "Scout"
BuildableName = "Scout"
BlueprintName = "Scout"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
@ -48,20 +62,6 @@
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "Scout"
BuildableName = "Scout"
BlueprintName = "Scout"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction

View File

@ -3,7 +3,12 @@
All notable changes to this project will be documented in this file. `Scout` adheres to [Semantic Versioning](http://semver.org).
---
## [4.0.0](https://github.com/ABridoux/scout/tree/3.0.3) (28/04/2021)
## [4.0.1](https://github.com/ABridoux/scout/tree/4.0.1) (02/05/2021)
### Fixed
- Date decoding and `PathExplorer.date` [#239]
- `ExplorerXML` add and set features works with `ExplroerXML` values [#241]
## [4.0.0](https://github.com/ABridoux/scout/tree/4.0.0) (28/04/2021)
### Added
- Conversion from CSV input to one of the available formats [#181]
- `Data` and `Date` values support [#197]

View File

@ -101,3 +101,16 @@ infix operator <|>: SequencePrecedence
public func <|><A>(lhs: Parser<A>, rhs: Parser<A>) -> Parser<A> {
lhs.or(rhs)
}
infix operator <+>: SequencePrecedence
public func <+><A>(lhs: Parser<A>, rhs: Parser<A>) -> Parser<[A]> {
curry { [$0, $1] } <^> lhs <*> rhs
}
public func <+><A>(lhs: Parser<[A]>, rhs: Parser<A>) -> Parser<[A]> {
curry { $0 + [$1] } <^> lhs <*> rhs
}
public func <+><A>(lhs: Parser<[A]>, rhs: Parser<[A]>) -> Parser<[A]> {
curry { $0 + $1 } <^> lhs <*> rhs
}

View File

@ -94,6 +94,23 @@ public extension Parser {
}
}
/// Match characters until one of the forbidden ones is encountered
static func string(forbiddenCharacters isForbidden: @escaping (Character) -> Bool) -> Parser<String> {
Parser<String> { input in
guard !input.isEmpty else { return nil }
var remainder = input
var currentString = ""
while let char = remainder.first {
if isForbidden(char) { break }
currentString += String(char)
remainder = remainder.dropFirst()
}
return currentString.isEmpty ? nil : (currentString, remainder)
}
}
/// Match characters until the last ones equal the forbidden string, or if the character matches one of the forbidden ones
///
/// ### Examples

View File

@ -4,5 +4,5 @@
// MIT license, see LICENSE file for details
public enum ScoutVersion {
public static let current = "4.0.0"
public static let current = "4.0.1"
}

View File

@ -15,7 +15,6 @@ extension ExplorerValue {
// MARK: General function
/// - parameter detailedName: When `true`, the key name after a filter will be composed of the parent key followed by the child key
private func _get(path: SlicePath) throws -> Self {
guard let (head, tail) = path.headAndTail() else { return self }

View File

@ -121,10 +121,12 @@ extension ExplorerValue: Codable {
return .bool(bool)
} else if let data = try? container.decode(Data.self) {
return .data(data)
} else if let date = try? container.decode(Date.self) {
return .date(date)
} else if container.decodeNil() {
return .string("null")
} else {
throw ExplorerError(description: "Unable to decode single value in data. \(container.codingPath)")
throw ExplorerError(description: "Unable to decode single value in data. \(container.codingPath.pathDescription)")
}
}
@ -190,7 +192,12 @@ extension ExplorerValue {
}
static func singleFrom(string: String) -> ExplorerValue {
if let int = Int(string) { return .int(int) } else if let double = Double(string) { return .double(double) } else if let bool = Bool(string) { return .bool(bool) } else { return .string(string) }
// swiftlint:disable statement_position
if let int = Int(string) { return .int(int) }
else if let double = Double(string) { return .double(double) }
else if let bool = Bool(string) { return .bool(bool) }
else { return .string(string) }
// swiftlint:enable statement_position
}
}
@ -277,6 +284,11 @@ extension ExplorerValue {
return data
}
public var date: Date? {
guard case let .date(date) = self else { return nil }
return date
}
public var array: ArrayValue? {
switch self {
case .array(let array): return array

View File

@ -42,14 +42,50 @@ public extension CodableFormats {
+ #"|(?<=\{)\s*"\#(foldedKey)"\s*:\s*"\#(foldedMark)"\s*(?=\})"# // dict
}
public static func encode<E: Encodable>(_ value: E, rootName: String?) throws -> Data {
private static let encoder: JSONEncoder = {
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
return try encoder.encode(value)
return encoder
}()
private static let decoder: JSONDecoder = JSONDecoder()
public static func encode<E: Encodable>(_ value: E, rootName: String?) throws -> Data {
try encoder.encode(value)
}
public static func decode<D>(_ type: D.Type, from data: Data) throws -> D where D: Decodable {
try JSONDecoder().decode(type, from: data)
try decoder.decode(type, from: data)
}
}
}
extension CodableFormats {
public enum JsonDateIso8601: CodableFormat {
public static let dataFormat: DataFormat = .json
public static var foldedRegexPattern: String { JsonDefault.foldedRegexPattern }
private static let decoder: JSONDecoder = {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
return decoder
}()
private static let encoder: JSONEncoder = {
let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .iso8601
encoder.outputFormatting = .prettyPrinted
return encoder
}()
public static func decode<D>(_ type: D.Type, from data: Data) throws -> D where D: Decodable {
try decoder.decode(type, from: data)
}
public static func encode<E>(_ value: E, rootName: String?) throws -> Data where E: Encodable {
try encoder.encode(value)
}
}
}
@ -65,14 +101,20 @@ public extension CodableFormats {
+ #"|(?<=<dict>)\s*<key>\#(foldedKey)</key>\s*<string>\#(foldedMark)</string>\s*(?=</dict>)"# // dict
}
public static func encode<E>(_ value: E, rootName: String?) throws -> Data where E: Encodable {
private static let encoder: PropertyListEncoder = {
let encoder = PropertyListEncoder()
encoder.outputFormat = .xml
return try encoder.encode(value)
return encoder
}()
private static let decoder: PropertyListDecoder = PropertyListDecoder()
public static func encode<E>(_ value: E, rootName: String?) throws -> Data where E: Encodable {
try encoder.encode(value)
}
public static func decode<D>(_ type: D.Type, from data: Data) throws -> D where D: Decodable {
try PropertyListDecoder().decode(type, from: data)
try decoder.decode(type, from: data)
}
}
}
@ -88,12 +130,15 @@ public extension CodableFormats {
+ #"|\#(foldedKey)\s*:\s*\#(foldedMark)\s*(?=\n)"# // dict
}
private static let encoder = YAMLEncoder()
private static let decoder = YAMLDecoder()
public static func encode<E>(_ value: E, rootName: String?) throws -> Data where E: Encodable {
try YAMLEncoder().encode(value).data(using: .utf8).unwrapOrThrow(.stringToData)
try encoder.encode(value).data(using: .utf8).unwrapOrThrow(.stringToData)
}
public static func decode<D>(_ type: D.Type, from data: Data) throws -> D where D: Decodable {
try YAMLDecoder().decode(type, from: data)
try decoder.decode(type, from: data)
}
}
}

View File

@ -126,6 +126,12 @@ extension ExplorerValueDecoder {
.unwrapOrThrow(.typeMismatch(Data.self, codingPath: codingPath + [key]))
}
func decode(_ type: Date.Type, forKey key: Key) throws -> Date {
try valueFor(key: key)
.date
.unwrapOrThrow(.typeMismatch(Date.self, codingPath: codingPath + [key]))
}
func decode<T>(_ type: T.Type, forKey key: Key) throws -> T where T: Decodable {
let value = try valueFor(key: key)
@ -133,6 +139,10 @@ extension ExplorerValueDecoder {
return try decode(Data.self, forKey: key) as! T
}
if T.self == Date.self {
return try decode(Date.self, forKey: key) as! T
}
let decoder = ExplorerValueDecoder(value, codingPath: codingPath + [key])
return try T(from: decoder)
}

View File

@ -74,11 +74,23 @@ extension ExplorerValueDecoder {
try value.data.unwrapOrThrow(.typeMismatch(Data.self, codingPath: codingPath))
}
func decode(_ type: Date.Type) throws -> Date {
try value.date.unwrapOrThrow(.typeMismatch(Date.self, codingPath: codingPath))
}
func decode<T>(_ type: T.Type) throws -> T where T: Decodable {
if T.self == Data.self {
return try decode(Data.self) as! T
}
if T.self == Date.self {
return try decode(Date.self) as! T
}
if T.self == Date.self {
return try decode(Date.self) as! T
}
let decoder = ExplorerValueDecoder(value, codingPath: codingPath)
return try T(from: decoder)
}

View File

@ -107,11 +107,21 @@ extension ExplorerValueDecoder {
return value
}
mutating func decode(_ type: Date.Type) throws -> Date {
let value = try array[currentIndex].date.unwrapOrThrow(.typeMismatch(Date.self, codingPath: codingPath))
currentIndex += 1
return value
}
mutating func decode<T>(_ type: T.Type) throws -> T where T: Decodable {
if T.self == Data.self {
return try decode(Data.self) as! T
}
if T.self == Date.self {
return try decode(Date.self) as! T
}
let decoder = ExplorerValueDecoder(array[currentIndex], codingPath: codingPath)
let decoded = try T(from: decoder)
currentIndex += 1

View File

@ -74,12 +74,21 @@ extension ExplorerValueEncoder {
try encoder.value.add(.data(value), at: path.appending(key.stringValue))
}
mutating func encode(_ value: Date, forKey key: Key) throws {
try encoder.value.add(.date(value), at: path.appending(key.stringValue))
}
mutating func encode<T>(_ value: T, forKey key: Key) throws where T: Encodable {
if let data = value as? Data {
try encode(data, forKey: key)
return
}
if let data = value as? Date {
try encode(data, forKey: key)
return
}
let newEncoder = ExplorerValueEncoder()
try value.encode(to: newEncoder)
try encoder.value.add(newEncoder.value, at: path.appending(key.stringValue))

View File

@ -74,12 +74,21 @@ extension ExplorerValueEncoder {
try encoder.value.set(path, to: .data(value))
}
mutating func encode(_ value: Date) throws {
try encoder.value.set(path, to: .date(value))
}
mutating func encode<T>(_ value: T) throws where T: Encodable {
if let data = value as? Data {
try encode(data)
return
}
if let date = value as? Date {
try encode(date)
return
}
let newEncoder = ExplorerValueEncoder(codingPath: codingPath)
try value.encode(to: encoder)
try encoder.value.set(path, to: newEncoder.value)

View File

@ -91,12 +91,22 @@ extension ExplorerValueEncoder {
count += 1
}
mutating func encode(_ value: Date) throws {
try encoder.value.add(.date(value), at: path.appending(.count))
count += 1
}
mutating func encode<T>(_ value: T) throws where T: Encodable {
if let data = value as? Data {
try encode(data)
return
}
if let date = value as? Date {
try encode(date)
return
}
let newEncoder = ExplorerValueEncoder(codingPath: codingPath)
try value.encode(to: newEncoder)
try encoder.value.add(newEncoder.value, at: path.appending(.count))

View File

@ -14,12 +14,38 @@ extension ExplorerXML {
try _add(value: .explorerValue(value), at: Slice(path))
}
/// Add the given `AEXMLElement` value rather than an `ExplorerValue`
public mutating func add(_ element: Element, at path: Path) throws {
if referenceIsShared() { self = copy() }
try _add(value: .explorerXML(ExplorerXML(element: element)), at: Slice(path))
}
/// Add the given `ExplorerXML` value rather than an `ExplorerValue`
public mutating func add(_ explorer: ExplorerXML, at path: Path) throws {
if referenceIsShared() { self = copy() }
try _add(value: .explorerXML(explorer), at: Slice(path))
}
public func adding(_ value: ExplorerValue, at path: Path) throws -> ExplorerXML {
var copy = self.copy()
try copy.add(value, at: path)
return copy
}
/// Add the given `AEXMLElement` value rather than an `ExplorerValue`
public func adding(_ element: Element, at path: Path) throws -> ExplorerXML {
var copy = self.copy()
try copy.add(element, at: path)
return copy
}
/// Add the given `ExplorerXML` value rather than an `ExplorerValue`
public func adding(_ explorerXML: ExplorerXML, at path: Path) throws -> ExplorerXML {
var copy = self.copy()
try copy.add(explorerXML, at: path)
return copy
}
// MARK: General function
/// Return the value if it should be added to the parent

View File

@ -16,21 +16,33 @@ extension ExplorerXML {
try _set(path: Slice(path), to: .explorerValue(newValue))
}
/// Set the path to the given `AEXMLElement` value rather than an `ExplorerValue`
public mutating func set(_ path: Path, to element: Element) throws {
try _set(path: Slice(path), to: .explorerXML(ExplorerXML(element: element)))
}
/// Set the path to the given `ExplorerXML` value rather than an `ExplorerValue`
public mutating func set(_ path: Path, to explorer: ExplorerXML) throws {
try _set(path: Slice(path), to: .explorerXML(explorer))
}
public func setting(_ path: Path, to newValue: ExplorerValue) throws -> ExplorerXML {
var modified = copy()
try modified.set(path, to: newValue)
return modified
}
/// Set the path to the given AEXMLElement rather than an `ExplorerValue`
public mutating func set(_ path: Path, to newElement: Element) throws {
try _set(path: Slice(path), to: .xmlElement(newElement))
/// Set the path to the given `AEXMLElement` value rather than an `ExplorerValue`
public func setting(_ path: Path, to element: Element) throws -> ExplorerXML {
var modified = copy()
try modified.set(path, to: element)
return modified
}
/// Set the path to the given AEXMLElement rather than an `ExplorerValue`
public func setting(_ path: Path, to newElement: Element) throws -> ExplorerXML {
/// Set the path to the given `ExplorerXML` value rather than an `ExplorerValue`
public func setting(_ path: Path, to explorer: ExplorerXML) throws -> ExplorerXML {
var modified = copy()
try modified.set(path, to: newElement)
try modified.set(path, to: explorer)
return modified
}

View File

@ -31,7 +31,7 @@ public struct ExplorerXML: PathExplorer {
public var int: Int? { element.int }
public var double: Double? { element.double }
@available(*, deprecated)
@available(*, deprecated, renamed: "double")
public var real: Double? { element.double }
/// Always `nil` on XML
@ -259,7 +259,7 @@ extension ExplorerXML {
func set(value: ValueSetter) {
switch value {
case .explorerValue(let value): set(newValue: value)
case .xmlElement(let element): set(newElement: element)
case .explorerXML(let explorer): set(newElement: explorer.element)
}
}
@ -306,7 +306,7 @@ extension ExplorerXML {
/// Wrapper to more easily handle setting an ExplorerValue or Element
enum ValueSetter {
case explorerValue(ExplorerValue)
case xmlElement(Element)
case explorerXML(ExplorerXML)
}
}

View File

@ -0,0 +1,34 @@
//
// Scout
// Copyright (c) 2020-present Alexis Bridoux
// MIT license, see LICENSE file for details
import Foundation
extension CodingKey {
/// Path element string description
var pathDescription: String {
let split = stringValue.components(separatedBy: " ")
if split.count == 2, split[0] == "Index", let index = Int(split[1]) {
return "[\(index)]"
} else {
return stringValue
}
}
}
extension Array where Element == CodingKey {
/// String description of the coding path
var pathDescription: String {
var path = reduce("") { "\($0)\($1.pathDescription)" }
if path.hasPrefix(".") {
path.removeFirst()
}
return path
}
}

View File

@ -15,7 +15,9 @@ public struct CodablePathExplorer<Format: CodableFormat>: PathExplorer {
public var string: String? { value.string }
public var bool: Bool? { value.bool }
public var int: Int? { value.int }
@available(*, deprecated, renamed: "double")
public var real: Double? { value.real }
public var double: Double? { value.real }
public var data: Data? { value.data }
public func array<T>(of type: T.Type) throws -> [T] where T: ExplorerValueCreatable { try value.array(of: type) }
public func dictionary<T>(of type: T.Type) throws -> [String: T] where T: ExplorerValueCreatable { try value.dictionary(of: type) }

View File

@ -31,6 +31,9 @@ where
/// Non `nil` if the key is of the `Double` type
var real: Double? { get }
/// Non `nil` if the key is of the `Double` type
var double: Double? { get }
/// Non `nil` if the key is of the `Data` type
var data: Data? { get }

View File

@ -71,8 +71,8 @@ struct CSVCommand: ParsableCommand {
} else {
var output = try explorer.exportString()
let colorInjector = try self.colorInjector(for: dataFormat)
output = colorise ? colorInjector.inject(in: output) : output
let highlight = try self.colorInjector(for: dataFormat)
output = colorise ? highlight(output) : output
print(output)
}
}

View File

@ -43,7 +43,7 @@ extension ParsableCommand {
extension ParsableCommand {
func colorInjector(for format: Scout.DataFormat) throws -> TextInjector {
func colorInjector(for format: Scout.DataFormat) throws -> (String) -> String {
switch format {
case .json:
@ -51,28 +51,28 @@ extension ParsableCommand {
if let colors = try getColorFile()?.json {
jsonInjector.delegate = JSONInjectorColorDelegate(colors: colors)
}
return jsonInjector
return jsonInjector.inject
case .plist:
let plistInjector = PlistInjector(type: .terminal)
if let colors = try getColorFile()?.plist {
plistInjector.delegate = PlistInjectorColorDelegate(colors: colors)
}
return plistInjector
return plistInjector.inject
case .yaml:
let yamlInjector = YAMLInjector(type: .terminal)
if let colors = try getColorFile()?.yaml {
yamlInjector.delegate = YAMLInjectorColorDelegate(colors: colors)
}
return yamlInjector
return yamlInjector.inject
case .xml:
let xmlInjector = XMLEnhancedInjector(type: .terminal)
if let colors = try getColorFile()?.xml {
xmlInjector.delegate = XMLInjectorColorDelegate(colors: colors)
}
return xmlInjector
return xmlInjector.inject
}
}

View File

@ -7,7 +7,7 @@ import Foundation
import Scout
import ArgumentParser
/// Try to get a PathExplorer from the input
/// Try to get a `PathExplorer` from the input
protocol PathExplorerInputCommand: ParsableCommand {
/// A file path from which to read the data
@ -34,8 +34,8 @@ extension PathExplorerInputCommand {
func run() throws {
var filePath: String?
switch (inputFilePath?.replacingTilde, modifyFilePath?.replacingTilde) {
case (.some(let path), nil): filePath = path
case (nil, .some(let path)): filePath = path
case (let path?, nil): filePath = path
case (nil, let path?): filePath = path
case (nil, nil): break
case (.some, .some): throw RuntimeError.invalidArgumentsCombination(description: "Combining (-i|--input) with (-m|--modify) is not allowed")
}

View File

@ -6,6 +6,6 @@
import Scout
typealias Xml = PathExplorers.Xml
typealias Json = PathExplorers.Json
typealias Json = CodablePathExplorer<CodableFormats.JsonDateIso8601>
typealias Plist = PathExplorers.Plist
typealias Yaml = PathExplorers.Yaml

View File

@ -126,8 +126,8 @@ extension SADCommand {
func printOutput(output: String, with format: Scout.DataFormat) throws {
if colorise {
let injector = try colorInjector(for: format)
print(injector.inject(in: output))
let highlight = try colorInjector(for: format)
print(highlight(output))
} else {
print(output)
}

View File

@ -75,13 +75,13 @@ struct ReadCommand: PathExplorerInputCommand, ExportCommand {
print(value)
case .noExport:
let colorInjector = try self.colorInjector(for: P.format)
let output = colorise ? colorInjector.inject(in: value) : value
let highlight = try self.colorInjector(for: P.format)
let output = colorise ? highlight(value) : value
print(output)
case .dataFormat(let format):
let colorInjector = try self.colorInjector(for: format)
let output = colorise ? colorInjector.inject(in: value) : value
let highlight = try self.colorInjector(for: format)
let output = colorise ? highlight(value) : value
print(output)
}
}