Merge pull request #262 from ABridoux/feature/Xcode13-docC
Xcode13 docC
This commit is contained in:
commit
123f56c15f
|
@ -20,7 +20,7 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"package": "Lux",
|
||||
"package": "lux",
|
||||
"repositoryURL": "https://github.com/ABridoux/lux",
|
||||
"state": {
|
||||
"branch": null,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// swift-tools-version:5.3
|
||||
// swift-tools-version:5.5
|
||||
// The swift-tools-version declares the minimum version of Swift required to build this package.
|
||||
|
||||
import PackageDescription
|
||||
|
@ -55,7 +55,7 @@ let package = Package(
|
|||
name: "ScoutCLTCore",
|
||||
dependencies: [
|
||||
"Scout", "Parsing"]),
|
||||
.target(
|
||||
.executableTarget(
|
||||
name: "ScoutCLT",
|
||||
dependencies: [
|
||||
"Scout",
|
||||
|
|
|
@ -5,9 +5,9 @@
|
|||
|
||||
enum Folding {
|
||||
|
||||
/// Use to name the single key when folding a dictionary
|
||||
/// Used to name the single key when folding a dictionary
|
||||
static let foldedKey = "Folded"
|
||||
|
||||
/// Use to replace the content of a dictionary or array when folding it
|
||||
/// Used to replace the content of a dictionary or array when folding it
|
||||
static let foldedMark = "~~SCOUT_FOLDED~~"
|
||||
}
|
||||
|
|
|
@ -103,6 +103,7 @@ extension ExplorerValue {
|
|||
}
|
||||
|
||||
/// The headers for the array of dictionaries
|
||||
///
|
||||
/// #### Complexity
|
||||
/// `O(n)` where `n` is the number of elements in the array
|
||||
private func headers(in array: ArrayValue) -> Set<Path>? {
|
||||
|
|
|
@ -62,6 +62,8 @@ extension ExplorerValue {
|
|||
/// Holds the logic to validate a path built during paths listing
|
||||
private struct PathValidation {
|
||||
let filter: PathsFilter
|
||||
|
||||
/// The path leading to the value
|
||||
private(set) var leading: Path
|
||||
private var isInitial = true
|
||||
private var hasOneKeyValidated = false
|
||||
|
|
|
@ -11,6 +11,7 @@ import Foundation
|
|||
/// - note: Default implementation provided for types conforming to `Encodable`
|
||||
public protocol ExplorerValueRepresentable {
|
||||
|
||||
/// Convert `self` to an ``ExplorerValue``
|
||||
func explorerValue() throws -> ExplorerValue
|
||||
}
|
||||
|
||||
|
@ -18,10 +19,11 @@ public protocol ExplorerValueRepresentable {
|
|||
/// - note: Default implementation provided for types conforming to `Decodable`
|
||||
public protocol ExplorerValueCreatable {
|
||||
|
||||
/// Instantiate a new value from an ``ExplorerValue``
|
||||
init(from explorerValue: ExplorerValue) throws
|
||||
}
|
||||
|
||||
/// Can be represented as and instantiated from as `ExplorerValue`
|
||||
/// Can be represented *as* and instantiated *from* an `ExplorerValue`
|
||||
/// - note: Default implementation provided for types conforming to `Codable`
|
||||
public typealias ExplorerValueConvertible = ExplorerValueRepresentable & ExplorerValueCreatable
|
||||
|
||||
|
|
|
@ -30,6 +30,11 @@ public struct ExplorerXML: PathExplorer {
|
|||
public var bool: Bool? { element.bool }
|
||||
public var int: Int? { element.int }
|
||||
public var double: Double? { element.double }
|
||||
|
||||
/// XML `date` element is always `nil`
|
||||
///
|
||||
/// Date types are not natively supported by XML
|
||||
public var date: Date? { nil }
|
||||
|
||||
@available(*, deprecated, renamed: "double")
|
||||
public var real: Double? { element.double }
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
/// Errors that can be thrown when exploring data using a ``PathExplorer``
|
||||
public struct ExplorerError: LocalizedError, Equatable {
|
||||
public private(set) var path: Path
|
||||
let description: String
|
||||
|
@ -58,30 +59,37 @@ extension ExplorerError {
|
|||
|
||||
public extension ExplorerError {
|
||||
|
||||
/// The provided value is not convertible to an ``ExplorerValue``
|
||||
static func invalid(value: Any) -> Self {
|
||||
ExplorerError(description: "The value \(value) is not convertible to ExplorerValue")
|
||||
}
|
||||
|
||||
/// The key used to subscript is missing. A best match in the existing key is provided if one is found.
|
||||
static func missing(key: String, bestMatch: String?) -> Self {
|
||||
ExplorerError(description: "Missing key '\(key)' in dictionary. Best match found: '\(bestMatch ?? "none")'")
|
||||
}
|
||||
|
||||
/// Trying to subscript something with a key although it's not a dictionary
|
||||
static var subscriptKeyNoDict: Self {
|
||||
ExplorerError(description: "The value cannot be subscripted with a string as it is not a dictionary")
|
||||
}
|
||||
|
||||
/// The provided index is out of bounds to subscript the array
|
||||
static func wrong(index: Int, arrayCount: Int) -> Self {
|
||||
ExplorerError(description: "Index \(index) out of bounds to subscript the array with \(arrayCount) elements")
|
||||
}
|
||||
|
||||
/// Trying to subscript something with an index although it's not an array
|
||||
static var subscriptIndexNoArray: Self {
|
||||
ExplorerError(description: "The value cannot be subscripted with an index as it is not an array")
|
||||
}
|
||||
|
||||
/// The ``PathElement`` is misplaced or forbidden for the feature
|
||||
static func wrongUsage(of element: PathElement) -> Self {
|
||||
return ExplorerError(description: "The element \(element.kindDescription) \(element) cannot be used here. \(element.usage)")
|
||||
}
|
||||
|
||||
/// The bounds provided to the ``PathElement/slice(_:)`` element are not valid to slice the array.
|
||||
static func wrong(bounds: Bounds, arrayCount: Int) -> Self {
|
||||
let description =
|
||||
"""
|
||||
|
@ -93,14 +101,19 @@ public extension ExplorerError {
|
|||
return ExplorerError(description: description)
|
||||
}
|
||||
|
||||
/// The regular expression pattern is invalid
|
||||
static func wrong(regexPattern: String) -> Self {
|
||||
ExplorerError(description: "The string '\(regexPattern)' is not a valid regular expression pattern")
|
||||
}
|
||||
|
||||
/// The conversion from an ``ExplorerValue`` to the provided type has failed
|
||||
static func mismatchingType<T>(_ type: T.Type, value: ExplorerValue) -> Self {
|
||||
ExplorerError(description: "ExplorerValue '\(value)' cannot be represented as \(T.self)")
|
||||
}
|
||||
|
||||
/// The predicate in invalid.
|
||||
///
|
||||
/// For instance, a `String` value is evaluated against a predicate taking an `Int` as input
|
||||
static func predicateNotEvaluatable(_ predicate: String, description: String) -> Self {
|
||||
ExplorerError(description: #"Unable to evaluate the predicate "\#(predicate)". \#(description)"#)
|
||||
}
|
||||
|
|
|
@ -5,60 +5,6 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
// MARK: - Storage
|
||||
|
||||
private struct IndexedSlice {
|
||||
var index: Int
|
||||
var lowerBound: Int
|
||||
var upperBound: Int
|
||||
}
|
||||
|
||||
private struct Indexed<Value> {
|
||||
var index: Int
|
||||
var value: Value
|
||||
}
|
||||
|
||||
private struct IndexedCollection<Value>: Collection {
|
||||
typealias Element = Indexed<Value>
|
||||
|
||||
var elements: [Element]
|
||||
|
||||
var startIndex: Int { elements.startIndex }
|
||||
var endIndex: Int { elements.endIndex }
|
||||
|
||||
init() {
|
||||
elements = []
|
||||
}
|
||||
|
||||
subscript(position: Int) -> Element { elements[position] }
|
||||
func index(after i: Int) -> Int { elements.index(after: i) }
|
||||
|
||||
func makeIterator() -> IndexingIterator<[Element]> {
|
||||
elements.makeIterator()
|
||||
}
|
||||
|
||||
mutating func append(index: Int, value: Value) {
|
||||
elements.append(.init(index: index, value: value))
|
||||
}
|
||||
|
||||
mutating func popLast() -> Element? { elements.popLast() }
|
||||
mutating func removeAll() { elements.removeAll() }
|
||||
}
|
||||
|
||||
private struct IndexedElements {
|
||||
var indexes = IndexedCollection<Int>()
|
||||
var slices = [IndexedSlice]()
|
||||
var keys = IndexedCollection<String>()
|
||||
var filters = IndexedCollection<String>()
|
||||
}
|
||||
|
||||
private extension Array where Element == IndexedSlice {
|
||||
|
||||
mutating func append(index: Int, lower: Int, upper: Int) {
|
||||
append(.init(index: index, lowerBound: lower, upperBound: upper))
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Functions
|
||||
|
||||
extension Path {
|
||||
|
@ -66,7 +12,7 @@ extension Path {
|
|||
/// Compute the path by changing the special path elements like slices or filters
|
||||
///
|
||||
/// Filters are changed to the key they correspond to. Slices are changed to indexes.
|
||||
/// #### Complexity
|
||||
/// ### Complexity
|
||||
/// O(n) with `n` the count of elements in the path
|
||||
public func flattened() -> Path {
|
||||
var indexedElements = getIndexedElements()
|
||||
|
@ -89,6 +35,11 @@ extension Path {
|
|||
|
||||
return Path(newPath)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Helpers
|
||||
|
||||
extension Path {
|
||||
|
||||
/// Parse the path and store the relevant elements with their indexes
|
||||
private func getIndexedElements() -> IndexedElements {
|
||||
|
@ -184,3 +135,53 @@ extension Path {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Storage models
|
||||
|
||||
private struct IndexedSlice {
|
||||
var index: Int
|
||||
var lowerBound: Int
|
||||
var upperBound: Int
|
||||
}
|
||||
|
||||
private struct Indexed<Value> {
|
||||
var index: Int
|
||||
var value: Value
|
||||
}
|
||||
|
||||
private struct IndexedCollection<Value>: Collection {
|
||||
typealias Element = Indexed<Value>
|
||||
|
||||
var elements: [Element] = []
|
||||
|
||||
var startIndex: Int { elements.startIndex }
|
||||
var endIndex: Int { elements.endIndex }
|
||||
|
||||
subscript(position: Int) -> Element { elements[position] }
|
||||
func index(after i: Int) -> Int { elements.index(after: i) }
|
||||
|
||||
func makeIterator() -> IndexingIterator<[Element]> {
|
||||
elements.makeIterator()
|
||||
}
|
||||
|
||||
mutating func append(index: Int, value: Value) {
|
||||
elements.append(.init(index: index, value: value))
|
||||
}
|
||||
|
||||
mutating func popLast() -> Element? { elements.popLast() }
|
||||
mutating func removeAll() { elements.removeAll() }
|
||||
}
|
||||
|
||||
private struct IndexedElements {
|
||||
var indexes = IndexedCollection<Int>()
|
||||
var slices = [IndexedSlice]()
|
||||
var keys = IndexedCollection<String>()
|
||||
var filters = IndexedCollection<String>()
|
||||
}
|
||||
|
||||
private extension Array where Element == IndexedSlice {
|
||||
|
||||
mutating func append(index: Int, lower: Int, upper: Int) {
|
||||
append(.init(index: index, lowerBound: lower, upperBound: upper))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,12 +36,15 @@ public extension Collection where Element == PathElement {
|
|||
|
||||
public extension Collection where SubSequence == Slice<Path> {
|
||||
|
||||
/// The greatest prefix that both paths have in common
|
||||
func commonPrefix(with otherPath: Self) -> Slice<Path> {
|
||||
var iterator = makeIterator()
|
||||
var otherIterator = otherPath.makeIterator()
|
||||
var lastIndex = 0
|
||||
|
||||
while let element = iterator.next(), let otherElement = otherIterator.next(), element == otherElement {
|
||||
while
|
||||
let element = iterator.next(), let otherElement = otherIterator.next(),
|
||||
element == otherElement {
|
||||
lastIndex += 1
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,8 @@ import Foundation
|
|||
public extension Collection where Element == PathElement {
|
||||
|
||||
/// Prints all the elements in the path, with the default separator
|
||||
/// #### Complexity
|
||||
///
|
||||
/// ### Complexity
|
||||
/// O(n) where `n`: element's count
|
||||
var description: String {
|
||||
var description = reduce(into: "", newDescription)
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
/// Collection of `PathElement`s to subscript a `PathExplorer`
|
||||
/// Collection of ``PathElement``s to subscript a `PathExplorer`
|
||||
public struct Path: Hashable {
|
||||
|
||||
// MARK: - Constants
|
||||
|
@ -17,28 +17,27 @@ public struct Path: Hashable {
|
|||
|
||||
private var elements: [PathElement] = []
|
||||
|
||||
/// An empty `Path`
|
||||
public static var empty: Path { .init() }
|
||||
|
||||
// MARK: - Initialization
|
||||
|
||||
/// Instantiate a `Path` for a string representing path components separated with the separator.
|
||||
///
|
||||
/// ### Example with default separator '.'
|
||||
/// ## Example with default separator '.'
|
||||
///
|
||||
/// `computers[2].name` will make the path `["computers", 2, "name"]`
|
||||
///
|
||||
/// `computer.general.serial_number` will make the path `["computer", "general", "serial_number"]`
|
||||
///
|
||||
/// `company.computers[#]` will make the path `["company", "computers", PathElement.count]`
|
||||
/// - `computers[2].name` will make the path `["computers", 2, "name"]`
|
||||
/// - `computer.general.serial_number` will make the path `["computer", "general", "serial_number"]`
|
||||
/// - `company.computers[#]` will make the path `["company", "computers", PathElement.count]`
|
||||
///
|
||||
/// - parameter string: The string representing the path
|
||||
/// - parameter separator: The separator used to split the string. Default is ".".
|
||||
///
|
||||
/// ### Brackets
|
||||
/// When enclosed with brackets, a path element will not be parsed. For example ```computer.(general.information).serial_number```
|
||||
/// ## Brackets
|
||||
/// When enclosed with brackets, a path element will not be parsed. For example `computer.(general.information).serial_number`
|
||||
/// will make the path ["computer", "general.information", "serial_number"]
|
||||
///
|
||||
/// ### Excluded separators
|
||||
/// ## Excluded separators
|
||||
/// The following separators will not work: '[', ']', '(', ')'.
|
||||
public init(string: String, separator: String = Self.defaultSeparator) throws {
|
||||
if Self.forbiddenSeparators.contains(separator) { throw PathError.invalidSeparator(separator) }
|
||||
|
|
|
@ -7,6 +7,7 @@ import Foundation
|
|||
|
||||
extension PathElement {
|
||||
|
||||
/// Placed after an array to slice it with a `Bounds` value
|
||||
public static func slice(_ lower: Bounds.Bound, _ upper: Bounds.Bound) -> PathElement {
|
||||
.slice(Bounds(lower: lower, upper: upper))
|
||||
}
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
/// Store the possible elements that can be used to subscript a `PathExplorer`
|
||||
/// The possible elements that can be used to subscript a ``PathExplorer``
|
||||
public enum PathElement: Hashable {
|
||||
|
||||
// MARK: - Constants
|
||||
|
@ -50,7 +50,7 @@ public enum PathElement: Hashable {
|
|||
}
|
||||
}
|
||||
|
||||
public var usage: String {
|
||||
var usage: String {
|
||||
switch self {
|
||||
case .key: return "A key subscript a dictionary and is specified with a dot '.' then the key name like 'dictionary.keyName'"
|
||||
case .index: return "An index subscript an array and is specified as an integer enclosed with square brackets like '[1]'"
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
// Copyright (c) 2020-present Alexis Bridoux
|
||||
// MIT license, see LICENSE file for details
|
||||
|
||||
/// Protocol to allow to subscript a `PathExplorer` without using directly the `PathElement` enum.
|
||||
/// Protocol to allow to subscript a `PathExplorer` without using directly the ``PathElement`` enum.
|
||||
///
|
||||
/// As `PathElement` already conforms to `ExpressibleByStringLiteral` and `ExpressibleByIntegerLiteral`,
|
||||
/// it is possible to instantiate a Path without the need of using the `PathElementRepresentable` protocol:
|
||||
|
@ -12,7 +12,7 @@
|
|||
/// ```
|
||||
/// But the "Expressible" protocols do not allow to do the same with variables.
|
||||
/// Thus, using `PathElementRepresentable` allows to instantiate a Path from a mix of Strings and Integers variables:
|
||||
/// ```
|
||||
/// ```swift
|
||||
/// let tom = "Tom"
|
||||
/// let hobbies = "hobbies"
|
||||
/// let index = 1
|
||||
|
|
|
@ -7,7 +7,7 @@ import Foundation
|
|||
|
||||
/// A collection of paths arranged following their common prefixes.
|
||||
///
|
||||
/// Useful when building a PathExplorer from a list of paths to reuse the last created explorer
|
||||
/// Useful when building a `PathExplorer` from a list of paths to reuse the last created explorer
|
||||
/// to add children to it (rather than starting again from the root each time).
|
||||
final class PathTree<Value: Equatable> {
|
||||
|
||||
|
@ -30,7 +30,7 @@ final class PathTree<Value: Equatable> {
|
|||
return nil
|
||||
}
|
||||
|
||||
// MARK: - Initialisation
|
||||
// MARK: - Initialization
|
||||
|
||||
init(value: ValueType, element: PathElement) {
|
||||
self.value = value
|
||||
|
|
|
@ -5,7 +5,9 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
/// A `PathExplorer` using an `ExplorerValue` and can be encoded/decoded with the provided `CodableFormat`
|
||||
/// A concrete implementation of `PathExplorer` with a specific ``CodableFormat``.
|
||||
///
|
||||
/// - note: Mainly a wrapper around ``ExplorerValue`` to offer a unified interface for all `Codable` `PathExplorer`s
|
||||
public struct CodablePathExplorer<Format: CodableFormat>: PathExplorer {
|
||||
|
||||
// MARK: - Properties
|
||||
|
@ -19,6 +21,7 @@ public struct CodablePathExplorer<Format: CodableFormat>: PathExplorer {
|
|||
public var real: Double? { value.real }
|
||||
public var double: Double? { value.double }
|
||||
public var data: Data? { value.data }
|
||||
public var date: Date? { value.date }
|
||||
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) }
|
||||
|
||||
|
|
|
@ -9,7 +9,8 @@ import Foundation
|
|||
protocol EquatablePathExplorer: PathExplorer {
|
||||
|
||||
/// `true` when self is equal to the provided other element.
|
||||
/// #### Complexity
|
||||
///
|
||||
/// ### Complexity
|
||||
/// Most often `O(n)` where `n` is the children count.
|
||||
func isEqual(to other: Self) -> Bool
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import Foundation
|
|||
|
||||
public extension PathExplorer {
|
||||
|
||||
/// Same as ``init(value:name:)`` with a default `nil` value for `name`
|
||||
init(value: ExplorerValue) {
|
||||
self.init(value: value, name: nil)
|
||||
}
|
||||
|
@ -17,18 +18,10 @@ public extension PathExplorer {
|
|||
public extension PathExplorer {
|
||||
|
||||
/// Get the key at the given path
|
||||
///
|
||||
/// #### Negative index
|
||||
/// It's possible to specify a negative index to target the last nth element of an array.
|
||||
/// For example, -1 targets the last element and -3 the last 3rd element.
|
||||
/// - Throws: If the path is invalid (e.g. a key does not exist in a dictionary, or indicating an index on a non-array key)
|
||||
func get(_ path: [PathElement]) throws -> Self { try get(Path(path)) }
|
||||
|
||||
/// Get the key at the given path
|
||||
///
|
||||
/// #### Negative index
|
||||
/// It's possible to specify a negative index to target the last nth element of an array.
|
||||
/// For example, -1 targets the last element and -3 the last 3rd element.
|
||||
/// - Throws: If the path is invalid (e.g. a key does not exist in a dictionary, or indicating an index on a non-array key)
|
||||
func get(_ path: PathElement...) throws -> Self { try get(path) }
|
||||
}
|
||||
|
@ -40,33 +33,18 @@ public extension PathExplorer {
|
|||
// MARK: Mutating
|
||||
|
||||
/// Set the value of the key at the given path
|
||||
///
|
||||
/// #### Negative index
|
||||
/// It's possible to specify a negative index to target the last nth element of an array.
|
||||
/// For example, -1 targets the last element and -3 the last 3rd element.
|
||||
///
|
||||
/// - Throws: If the path is invalid (e.g. a key does not exist in a dictionary, or indicating an index on a non-array key)
|
||||
mutating func set(_ path: PathElement..., to newValue: ExplorerValue) throws { try set(Path(path), to: newValue) }
|
||||
|
||||
// MARK: Mutating ExplorerValueRepresentable
|
||||
|
||||
/// Set the value of the key at the given path
|
||||
///
|
||||
/// #### Negative index
|
||||
/// It's possible to specify a negative index to target the last nth element of an array.
|
||||
/// For example, -1 targets the last element and -3 the last 3rd element.
|
||||
///
|
||||
/// Set the provided `ExplorerValueRepresentable`value of the key at the given path
|
||||
/// - Throws: If the path is invalid (e.g. a key does not exist in a dictionary, or indicating an index on a non-array key), or if the `newValue.explorerValue()` function fails
|
||||
mutating func set(_ path: Path, to newValue: ExplorerValueRepresentable) throws {
|
||||
try set(path, to: newValue.explorerValue())
|
||||
}
|
||||
|
||||
/// Set the value of the key at the given path
|
||||
///
|
||||
/// #### Negative index
|
||||
/// It's possible to specify a negative index to target the last nth element of an array.
|
||||
/// For example, -1 targets the last element and -3 the last 3rd element.
|
||||
///
|
||||
/// Set the provided `ExplorerValueRepresentable`value of the key at the given path
|
||||
/// - Throws: If the path is invalid (e.g. a key does not exist in a dictionary, or indicating an index on a non-array key), or if the `newValue.explorerValue()` function fails
|
||||
mutating func set(_ path: PathElement..., to newValue: ExplorerValueRepresentable) throws {
|
||||
try set(Path(path), to: newValue.explorerValue())
|
||||
|
@ -75,29 +53,14 @@ public extension PathExplorer {
|
|||
// MARK: Non mutating
|
||||
|
||||
/// Set the value of the key at the given path and return a new modified `PathExplorer`
|
||||
///
|
||||
/// #### Negative index
|
||||
/// It's possible to specify a negative index to target the last nth element of an array.
|
||||
/// For example, -1 targets the last element and -3 the last 3rd element.
|
||||
///
|
||||
/// - Throws: If the path is invalid (e.g. a key does not exist in a dictionary, or indicating an index on a non-array key)
|
||||
func setting(_ path: PathElement..., to newValue: ExplorerValue) throws -> Self { try setting(Path(path), to: newValue) }
|
||||
|
||||
/// Set the value of the key at the given path and return a new modified `PathExplorer`
|
||||
///
|
||||
/// #### Negative index
|
||||
/// It's possible to specify a negative index to target the last nth element of an array.
|
||||
/// For example, -1 targets the last element and -3 the last 3rd element.
|
||||
///
|
||||
/// - Throws: If the path is invalid (e.g. a key does not exist in a dictionary, or indicating an index on a non-array key), or if the `newValue.explorerValue()` function fails
|
||||
func setting(_ path: Path, to newValue: ExplorerValueRepresentable) throws -> Self { try setting(path, to: newValue.explorerValue()) }
|
||||
|
||||
/// Set the value of the key at the given path and return a new modified `PathExplorer`
|
||||
///
|
||||
/// #### Negative index
|
||||
/// It's possible to specify a negative index to target the last nth element of an array.
|
||||
/// For example, -1 targets the last element and -3 the last 3rd element.
|
||||
///
|
||||
/// - Throws: If the path is invalid (e.g. a key does not exist in a dictionary, or indicating an index on a non-array key), or if the `newValue.explorerValue()` function fails
|
||||
func setting(_ path: PathElement..., to newValue: ExplorerValueRepresentable) throws -> Self { try setting(Path(path), to: newValue.explorerValue()) }
|
||||
}
|
||||
|
@ -107,38 +70,18 @@ public extension PathExplorer {
|
|||
public extension PathExplorer {
|
||||
|
||||
/// Set the name of the key at the given path
|
||||
///
|
||||
/// #### Negative index
|
||||
/// It's possible to specify a negative index to target the last nth element of an array.
|
||||
/// For example, -1 targets the last element and -3 the last 3rd element.
|
||||
///
|
||||
/// - Throws: If the path is invalid (e.g. a key does not exist in a dictionary)
|
||||
mutating func set(_ path: [PathElement], keyNameTo newKeyName: String) throws { try set(Path(path), keyNameTo: newKeyName) }
|
||||
|
||||
/// Set the name of the key at the given path
|
||||
///
|
||||
/// #### Negative index
|
||||
/// It's possible to specify a negative index to target the last nth element of an array.
|
||||
/// For example, -1 targets the last element and -3 the last 3rd element.
|
||||
///
|
||||
/// - Throws: If the path is invalid (e.g. a key does not exist in a dictionary)
|
||||
mutating func set(_ path: PathElement..., keyNameTo newKeyName: String) throws { try set(path, keyNameTo: newKeyName) }
|
||||
|
||||
/// Set the name of the key at the given path, and return a new modified `PathExplorer`
|
||||
///
|
||||
/// #### Negative index
|
||||
/// It's possible to specify a negative index to target the last nth element of an array.
|
||||
/// For example, -1 targets the last element and -3 the last 3rd element.
|
||||
///
|
||||
/// - Throws: If the path is invalid (e.g. a key does not exist in a dictionary)
|
||||
func setting(_ path: [PathElement], keyNameTo newKeyName: String) throws -> Self { try setting(Path(path), keyNameTo: newKeyName) }
|
||||
|
||||
/// Set the name of the key at the given path, and return a new modified `PathExplorer`
|
||||
///
|
||||
/// #### Negative index
|
||||
/// It's possible to specify a negative index to target the last nth element of an array.
|
||||
/// For example, -1 targets the last element and -3 the last 3rd element.
|
||||
///
|
||||
/// - Throws: If the path is invalid (e.g. a key does not exist in a dictionary)
|
||||
func setting(_ path: PathElement..., keyNameTo newKeyName: String) throws -> Self { try setting(path, keyNameTo: newKeyName) }
|
||||
}
|
||||
|
@ -148,51 +91,26 @@ public extension PathExplorer {
|
|||
public extension PathExplorer {
|
||||
|
||||
/// Delete the key at the given path.
|
||||
///
|
||||
/// #### Negative index
|
||||
/// It's possible to specify a negative index to target the last nth element of an array.
|
||||
/// For example, -1 targets the last element and -3 the last 3rd element.
|
||||
///
|
||||
/// - parameter deleteIfEmpty: When `true`, the dictionary or array holding the value will be deleted too if empty after the key deletion. Default: `false`
|
||||
/// - Throws: If the path is invalid (e.g. a key does not exist in a dictionary, or indicating an index on a non-array key)
|
||||
mutating func delete(_ path: Path) throws { try delete(Path(path), deleteIfEmpty: false) }
|
||||
|
||||
/// Delete the key at the given path.
|
||||
///
|
||||
/// #### Negative index
|
||||
/// It's possible to specify a negative index to target the last nth element of an array.
|
||||
/// For example, -1 targets the last element and -3 the last 3rd element.
|
||||
///
|
||||
/// - parameter deleteIfEmpty: When `true`, the dictionary or array holding the value will be deleted too if empty after the key deletion. Default: `false`
|
||||
/// - Throws: If the path is invalid (e.g. a key does not exist in a dictionary, or indicating an index on a non-array key)
|
||||
mutating func delete(_ path: [PathElement], deleteIfEmpty: Bool = false) throws { try delete(Path(path), deleteIfEmpty: deleteIfEmpty) }
|
||||
|
||||
/// Delete the key at the given path.
|
||||
///
|
||||
/// #### Negative index
|
||||
/// It's possible to specify a negative index to target the last nth element of an array.
|
||||
/// For example, -1 targets the last element and -3 the last 3rd element.
|
||||
///
|
||||
/// - parameter deleteIfEmpty: When `true`, the dictionary or array holding the value will be deleted too if empty after the key deletion. Default: `false`
|
||||
/// - Throws: If the path is invalid (e.g. a key does not exist in a dictionary, or indicating an index on a non-array key)
|
||||
mutating func delete(_ path: PathElement..., deleteIfEmpty: Bool = false) throws { try delete(path, deleteIfEmpty: deleteIfEmpty) }
|
||||
|
||||
/// Delete the key at the given path and return a new modified `PathExplorer`
|
||||
///
|
||||
/// #### Negative index
|
||||
/// It's possible to specify a negative index to target the last nth element of an array.
|
||||
/// For example, -1 targets the last element and -3 the last 3rd element.
|
||||
///
|
||||
/// - parameter deleteIfEmpty: When `true`, the dictionary or array holding the value will be deleted too if empty after the key deletion. Default: `false`
|
||||
/// - Throws: If the path is invalid (e.g. a key does not exist in a dictionary, or indicating an index on a non-array key)
|
||||
func deleting(_ path: [PathElement], deleteIfEmpty: Bool = false) throws -> Self { try deleting(Path(path), deleteIfEmpty: deleteIfEmpty) }
|
||||
|
||||
/// Delete the key at the given path and return a new modified `PathExplorer`
|
||||
///
|
||||
/// #### Negative index
|
||||
/// It's possible to specify a negative index to target the last nth element of an array.
|
||||
/// For example, -1 targets the last element and -3 the last 3rd element.
|
||||
///
|
||||
/// - parameter deleteIfEmpty: When `true`, the dictionary or array holding the value will be deleted too if empty after the key deletion. Default: `false`
|
||||
/// - Throws: If the path is invalid (e.g. a key does not exist in a dictionary, or indicating an index on a non-array key)
|
||||
func deleting(_ path: PathElement..., deleteIfEmpty: Bool = false) throws -> Self { try deleting(path, deleteIfEmpty: deleteIfEmpty) }
|
||||
|
@ -206,36 +124,21 @@ public extension PathExplorer {
|
|||
|
||||
/// Add a value at the given path.
|
||||
///
|
||||
/// #### Negative index
|
||||
/// It's possible to specify a negative index to target the last nth element.
|
||||
/// For example, -1 targets the last element and -3 the last 3rd element.
|
||||
///
|
||||
/// #### Appending
|
||||
/// To add a key at the end of an array, specify the `PathElement.count`
|
||||
///
|
||||
/// ### Non-existing key
|
||||
/// Any non existing key encountered in the path will be created.
|
||||
/// ### Appending
|
||||
/// To add a key at the end of an array, specify ``PathElement/count``
|
||||
mutating func add(_ value: ExplorerValue, at path: PathElement...) throws { try add(value, at: Path(path)) }
|
||||
|
||||
/// Add a value at the given path.
|
||||
///
|
||||
/// #### Negative index
|
||||
/// It's possible to specify a negative index to target the last nth element.
|
||||
/// For example, -1 targets the last element and -3 the last 3rd element.
|
||||
///
|
||||
/// #### Appending
|
||||
/// To add a key at the end of an array, specify the `PathElement.count`
|
||||
/// ### Appending
|
||||
/// To add a key at the end of an array, specify ``PathElement/count``
|
||||
/// - Throws: If the `newValue.explorerValue` function fails
|
||||
mutating func add(_ value: ExplorerValueRepresentable, at path: Path) throws { try add(value.explorerValue(), at: path) }
|
||||
|
||||
/// Add a value at the given path.
|
||||
///
|
||||
/// #### Negative index
|
||||
/// It's possible to specify a negative index to target the last nth element.
|
||||
/// For example, -1 targets the last element and -3 the last 3rd element.
|
||||
///
|
||||
/// #### Appending
|
||||
/// To add a key at the end of an array, specify the `PathElement.count`
|
||||
/// ### Appending
|
||||
/// To add a key at the end of an array, specify ``PathElement/count``
|
||||
/// - Throws: If the `newValue.explorerValue()` function fails
|
||||
mutating func add(_ value: ExplorerValueRepresentable, at path: PathElement...) throws { try add(value.explorerValue(), at: Path(path)) }
|
||||
|
||||
|
@ -243,36 +146,21 @@ public extension PathExplorer {
|
|||
|
||||
/// Add a value at the given path, and return a new modified `PathExplorer`
|
||||
///
|
||||
/// #### Negative index
|
||||
/// It's possible to specify a negative index to target the last nth element.
|
||||
/// For example, -1 targets the last element and -3 the last 3rd element.
|
||||
///
|
||||
/// #### Appending
|
||||
/// To add a key at the end of an array, specify the `PathElement.count`
|
||||
///
|
||||
/// ### Non-existing key
|
||||
/// Any non existing key encountered in the path will be created.
|
||||
/// ### Appending
|
||||
/// To add a key at the end of an array, specify ``PathElement/count``
|
||||
func adding(_ value: ExplorerValue, at path: PathElement...) throws -> Self { try adding(value, at: Path(path)) }
|
||||
|
||||
/// Add a value at the given path, and return a new modified `PathExplorer`
|
||||
///
|
||||
/// #### Negative index
|
||||
/// It's possible to specify a negative index to target the last nth element.
|
||||
/// For example, -1 targets the last element and -3 the last 3rd element.
|
||||
///
|
||||
/// #### Appending
|
||||
/// To add a key at the end of an array, specify the `PathElement.count`
|
||||
/// ### Appending
|
||||
/// To add a key at the end of an array, specify ``PathElement/count``
|
||||
/// - Throws: If the `newValue.explorerValue()` function fails
|
||||
func adding(_ value: ExplorerValueRepresentable, at path: Path) throws -> Self { try adding(value.explorerValue(), at: path) }
|
||||
|
||||
/// Add a value at the given path, and return a new modified `PathExplorer`
|
||||
///
|
||||
/// #### Negative index
|
||||
/// It's possible to specify a negative index to target the last nth element.
|
||||
/// For example, -1 targets the last element and -3 the last 3rd element.
|
||||
///
|
||||
/// #### Appending
|
||||
/// To add a key at the end of an array, specify the `PathElement.count`
|
||||
/// ### Appending
|
||||
/// To add a key at the end of an array, specify ``PathElement/count``
|
||||
/// - Throws: If the `newValue.explorerValue()` function fails
|
||||
func adding(_ value: ExplorerValueRepresentable, at path: PathElement...) throws -> Self { try adding(value.explorerValue(), at: Path(path)) }
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
/// Wrap different structs to explore several format: Json, Plist and Xml
|
||||
/// Wrap several structs to explore several format: Json, Plist, YAML and Xml
|
||||
public protocol PathExplorer: CustomStringConvertible, CustomDebugStringConvertible,
|
||||
ExpressibleByStringLiteral,
|
||||
ExpressibleByBooleanLiteral,
|
||||
|
@ -38,6 +38,9 @@ where
|
|||
/// Non `nil` if the key is of the `Data` type
|
||||
var data: Data? { get }
|
||||
|
||||
/// Non `nil` if the key is of the `Date` type
|
||||
var date: Date? { get }
|
||||
|
||||
/// An array of the provided type
|
||||
func array<T: ExplorerValueCreatable>(of type: T.Type) throws -> [T]
|
||||
|
||||
|
@ -56,77 +59,43 @@ where
|
|||
|
||||
// MARK: - Initialization
|
||||
|
||||
|
||||
/// - Parameters:
|
||||
/// - value: The value the explorer will take
|
||||
/// - name: Optionally provide a name for a root element with Xml explorers
|
||||
init(value: ExplorerValue, name: String?)
|
||||
|
||||
// MARK: - Get
|
||||
|
||||
/// Get the key at the given path
|
||||
///
|
||||
/// #### Negative index
|
||||
/// It's possible to specify a negative index to target the last nth element of an array.
|
||||
/// For example, -1 targets the last element and -3 the last 3rd element.
|
||||
/// - Throws: If the path is invalid (e.g. a key does not exist in a dictionary, or indicating an index on a non-array key)
|
||||
func get(_ path: Path) throws -> Self
|
||||
|
||||
// MARK: - Set
|
||||
|
||||
/// Set the value of the key at the given path
|
||||
///
|
||||
/// #### Negative index
|
||||
/// It's possible to specify a negative index to target the last nth element of an array.
|
||||
/// For example, -1 targets the last element and -3 the last 3rd element.
|
||||
///
|
||||
/// - Throws: If the path is invalid (e.g. a key does not exist in a dictionary, or indicating an index on a non-array key)
|
||||
mutating func set(_ path: Path, to newValue: ExplorerValue) throws
|
||||
|
||||
/// Set the value of the key at the given path and returns a new modified `PathExplorer`
|
||||
///
|
||||
/// #### Negative index
|
||||
/// It's possible to specify a negative index to target the last nth element of an array.
|
||||
/// For example, -1 targets the last element and -3 the last 3rd element.
|
||||
///
|
||||
/// - Throws: If the path is invalid (e.g. a key does not exist in a dictionary, or indicating an index on a non-array key)
|
||||
/// - note: The type of the `value` parameter will be automatically inferred. To force the `value`type, use the parameter `as type`
|
||||
func setting(_ path: Path, to newValue: ExplorerValue) throws -> Self
|
||||
|
||||
// MARK: - Set key name
|
||||
|
||||
/// Set the name of the key at the given path
|
||||
///
|
||||
/// #### Negative index
|
||||
/// It's possible to specify a negative index to target the last nth element of an array.
|
||||
/// For example, -1 targets the last element and -3 the last 3rd element.
|
||||
///
|
||||
/// - Throws: If the path is invalid (e.g. a key does not exist in a dictionary)
|
||||
mutating func set(_ path: Path, keyNameTo newKeyName: String) throws
|
||||
|
||||
/// Set the name of the key at the given path, and return a new modified `PathExplorer`
|
||||
///
|
||||
/// #### Negative index
|
||||
/// It's possible to specify a negative index to target the last nth element of an array.
|
||||
/// For example, -1 targets the last element and -3 the last 3rd element.
|
||||
///
|
||||
/// - Throws: If the path is invalid (e.g. a key does not exist in a dictionary)
|
||||
func setting(_ path: Path, keyNameTo keyName: String) throws -> Self
|
||||
|
||||
// MARK: - Delete
|
||||
|
||||
/// Delete the key at the given path.
|
||||
///
|
||||
/// #### Negative index
|
||||
/// It's possible to specify a negative index to target the last nth element of an array.
|
||||
/// For example, -1 targets the last element and -3 the last 3rd element.
|
||||
///
|
||||
/// - parameter deleteIfEmpty: When `true`, the dictionary or array holding the value will be deleted too if empty after the key deletion. Default: `false`
|
||||
/// - Throws: If the path is invalid (e.g. a key does not exist in a dictionary, or indicating an index on a non-array key)
|
||||
mutating func delete(_ path: Path, deleteIfEmpty: Bool) throws
|
||||
|
||||
/// Delete the key at the given path and return a new modified `PathExplorer`
|
||||
///
|
||||
/// #### Negative index
|
||||
/// It's possible to specify a negative index to target the last nth element of an array.
|
||||
/// For example, -1 targets the last element and -3 the last 3rd element.
|
||||
///
|
||||
/// - parameter deleteIfEmpty: When `true`, the dictionary or array holding the value will be deleted too if empty after the key deletion. Default: `false`
|
||||
/// - Throws: If the path is invalid (e.g. a key does not exist in a dictionary, or indicating an index on a non-array key)
|
||||
func deleting(_ path: Path, deleteIfEmpty: Bool) throws -> Self
|
||||
|
@ -135,25 +104,14 @@ where
|
|||
|
||||
/// Add a value at the given path.
|
||||
///
|
||||
/// #### Negative index
|
||||
/// It's possible to specify a negative index to target the last nth element.
|
||||
/// For example, -1 targets the last element and -3 the last 3rd element.
|
||||
///
|
||||
/// #### Appending
|
||||
/// To add a key at the end of an array, specify the `PathElement.count`
|
||||
/// To add a key at the end of an array, specify ``PathElement/count``
|
||||
mutating func add(_ value: ExplorerValue, at path: Path) throws
|
||||
|
||||
/// Add a value at the given path, and return a new modified `PathExplorer`
|
||||
///
|
||||
/// #### Negative index
|
||||
/// It's possible to specify a negative index to target the last nth element.
|
||||
/// For example, -1 targets the last element and -3 the last 3rd element.
|
||||
///
|
||||
/// #### Appending
|
||||
/// ### Appending
|
||||
/// To add a key at the end of an array, specify the `PathElement.count`
|
||||
///
|
||||
/// ### Non-existing key
|
||||
/// Any non existing key encountered in the path will be created.
|
||||
func adding(_ value: ExplorerValue, at path: Path) throws -> Self
|
||||
|
||||
// MARK: - Paths listing
|
||||
|
@ -161,6 +119,6 @@ where
|
|||
/// Returns all the paths leading to single or group values
|
||||
/// - Parameters:
|
||||
/// - initialPath: Scope the returned paths with this path as a starting point
|
||||
/// - filter: Optionally provide a filter on the key and/or value. Default is `noFilter`
|
||||
/// - filter: Optionally provide a filter on the key and/or value. Default is ``PathsFilter/noFilter``
|
||||
func listPaths(startingAt initialPath: Path?, filter: PathsFilter) throws -> [Path]
|
||||
}
|
||||
|
|
|
@ -5,7 +5,9 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
/// Namespace to find all PathExplorers in a single place
|
||||
/// Namespace to find all default PathExplorers in a single place
|
||||
///
|
||||
/// Use default explorers for a format: `PathExplorers.Json`, `PathExplorers.Xml`...
|
||||
public enum PathExplorers {
|
||||
|
||||
public typealias Json = CodablePathExplorer<CodableFormats.JsonDefault>
|
||||
|
|
|
@ -8,8 +8,12 @@ import Foundation
|
|||
/// A `PathExplorer` which can be instantiated from data and export itself to another format
|
||||
public protocol SerializablePathExplorer: PathExplorer {
|
||||
|
||||
/// The `DataFormat` of the serializable `PathExplorer`: JSON, Plist, XML, or YAML
|
||||
static var format: DataFormat { get }
|
||||
|
||||
/// Initialize a new ``PathExplorer`` from the `Data`
|
||||
///
|
||||
/// - Throws: If the data cannot be serialized into the format
|
||||
init(data: Data) throws
|
||||
|
||||
/// Export the path explorer value to data
|
||||
|
@ -18,12 +22,14 @@ public protocol SerializablePathExplorer: PathExplorer {
|
|||
/// Export the path explorer value to a String
|
||||
///
|
||||
/// - note: The single values will be exported correspondingly to the data format.
|
||||
/// For instance: `<string>Hello</string>` and not ust `Hello`.
|
||||
/// To get only the value of the `PathExplorer` without the data , use `description`
|
||||
/// For instance: `<string>Hello</string>` and not `Hello`.
|
||||
/// To get only the value of the `PathExplorer` without the format , use `description`
|
||||
/// or the corresponding type (e.g. `pathExplorer.int` or `pathExplorer.bool`)
|
||||
func exportString() throws -> String
|
||||
|
||||
/// Export the path explorer value to a CSV if possible. Use the default separator ';' if none specified
|
||||
/// Export the path explorer value to a CSV if possible, using the provided separator.
|
||||
///
|
||||
/// - note: Not all values are exportable to CSV. For instance, a three dimensions array is not exportable, neither an array of heterogeneous dictionaries.
|
||||
func exportCSV(separator: String?) throws -> String
|
||||
|
||||
/// Export the path explorer value to the specified format data with a default root name "root"
|
||||
|
@ -32,24 +38,38 @@ public protocol SerializablePathExplorer: PathExplorer {
|
|||
/// Export the path explorer value to the specified format string data with a default root name "root"
|
||||
func exportString(to format: DataFormat, rootName: String?) throws -> String
|
||||
|
||||
/// Returns a new explorer from the provided CSV string when it's possible. Throws otherwise.
|
||||
/// Returns a new explorer from the provided CSV string when it's possible.
|
||||
/// - Parameters:
|
||||
/// - string: The CSV as `String`
|
||||
/// - separator: The `Character` used as separator in the CSV string
|
||||
/// - hasHeaders: Specify whether the CSV string has named headers. Named headers can be full ``Path``s to structure the explorer
|
||||
///
|
||||
/// - Returns: A `SerializablePathExplorer` from the provided CSV
|
||||
/// - Throws: If the CSV cannot be converted to Self
|
||||
static func fromCSV(string: String, separator: Character, hasHeaders: Bool) throws -> Self
|
||||
|
||||
/// New explorer replacing the group values (array or dictionaries) sub values by a unique one
|
||||
/// holding a fold mark to be replaced when exporting the string value.
|
||||
/// - note: Use `exportFoldedString(upTo:)` to directly get the string value
|
||||
/// - note: Use ``exportFoldedString(upTo:)`` to directly get the string value
|
||||
func folded(upTo level: Int) -> Self
|
||||
|
||||
/// Folded explored description, replacing the group values (array or dictionaries) sub values by a single string "..."
|
||||
///
|
||||
/// - Important: To be used only for display purpose as the returned string will not have a proper format
|
||||
func exportFoldedString(upTo level: Int) throws -> String
|
||||
}
|
||||
|
||||
extension SerializablePathExplorer {
|
||||
|
||||
var defaultCSVSeparator: String { ";" }
|
||||
var nullCSVValue: String { "NULL" }
|
||||
}
|
||||
|
||||
public extension SerializablePathExplorer {
|
||||
|
||||
var defaultCSVSeparator: String { ";" }
|
||||
var nullCSVValue: String { "NULL" }
|
||||
|
||||
/// Export the path explorer value to a CSV if possible. Use the default separator ';' if none specified
|
||||
/// Export the path explorer value to a CSV if possible. Using the default separator ';'
|
||||
///
|
||||
/// - note: Not all values are exportable to CSV. For instance, a three dimensions array is not exportable, neither an array of heterogeneous dictionaries.
|
||||
func exportCSV() throws -> String {
|
||||
try exportCSV(separator: nil)
|
||||
}
|
||||
|
|
|
@ -63,7 +63,7 @@ extension Path.ElementParsers {
|
|||
|
||||
extension Path {
|
||||
|
||||
/// Parse a `Path`
|
||||
/// Parse a `Path` from a provided `String`
|
||||
/// - Parameters:
|
||||
/// - separator: Separator between keys elements
|
||||
/// - keyForbiddenCharacters: Optionally prevent characters to be parsed in a key name
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
/// Provided to the ``PathExplorer/listPaths(startingAt:filter:)-4tkeq`` function to target specific paths when listing them.
|
||||
public enum PathsFilter {
|
||||
/// No filter on key or value.
|
||||
case targetOnly(ValueTarget)
|
||||
|
|
|
@ -17,13 +17,13 @@ public protocol ValuePredicate {
|
|||
|
||||
extension PathsFilter {
|
||||
|
||||
/// Specify a boolean expression to filter the value
|
||||
/// Specify a `String` boolean expression to filter the value
|
||||
///
|
||||
/// The value is specified as the variable 'value' in the expression.
|
||||
/// - `value > 10`
|
||||
/// - `value hasPrefix 'Lou' && value hasSuffix 'lou'`
|
||||
///
|
||||
/// - note: Public wrapper around BooleanExpressionEvaluation.Expression
|
||||
/// - note: Public wrapper around `BooleanExpressionEvaluation/Expression`
|
||||
public final class ExpressionPredicate: ValuePredicate {
|
||||
private(set) var expression: Expression
|
||||
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
# Custom types with ExplorerValue
|
||||
|
||||
Learn more about the back bone of the serializable ``PathExplorer``s and understand how you can use it to inject your own types when setting or adding values.
|
||||
|
||||
|
||||
## Meet ExplorerValue
|
||||
|
||||
`ExplorerValue` is a type implementing the ``PathExplorer`` protocol. As it implements `Codable` too, it can be used as a `PathExplorer` as long as a coder exists for the data format. Thus, JSON, Plist and YAML `PathExplorer`s can use the `ExplorerValue` type to get simple conformance to the protocol.
|
||||
|
||||
That's why ``CodablePathExplorer`` is mainly a wrapper around `ExplorerValue` to provide a generic structure implementing `PathExplorer`. ``PathExplorers/Json``, ``PathExplorers/Plist`` and ``PathExplorers/Yaml`` are simply type aliases for `CodablePathExplorer`, and differs only by the generic type ``CodableFormat``.
|
||||
|
||||
As `ExplorerValue` conforms to `Codable`, it's possible to provide a custom `Encoder` or `Decoder` rather than using the default ones coming with the ``PathExplorers`` namespace. This allows to specify date coding strategies for example, or to support new data formats in a blink of an eye with a dedicated Encoder/Decoder.
|
||||
|
||||
## ExplorerValueCreatable
|
||||
|
||||
To take things further, it's also possible to convert any type to an `ExplorerValue` with ``ExplorerValueRepresentable``. This protocol's only requirement is a function that returns an `ExplorerValue`. This way, it's possible to set or add a value of a custom type with a `PathExplorer`.
|
||||
|
||||
It's worth to note that making a type conform to `Encodable` is enough to make it `ExplorerValueRepresentable` too. A value of this type will be *encoded* to an `ExplorerValue`. Thus, using the following structure:
|
||||
|
||||
```swift
|
||||
struct Record: Codable, ExplorerValueConvertible {
|
||||
var name: String
|
||||
var score: Int
|
||||
}
|
||||
```
|
||||
|
||||
It's possible to set a `Record` value with any `PathExplorer`
|
||||
|
||||
```swift
|
||||
let record = Record(name: "Riri", score: 20)
|
||||
|
||||
// plist: CodablePathExplorer<PlistFormat>
|
||||
try plist.set("ducks", "records", 0, to: record)
|
||||
```
|
||||
|
||||
> Note: Even if primitive types conform to `Encodable`, it would be less efficient to encode them. A simpler implementation for `ExplorerValueRepresentable` is provided. The same goes for an array of a primitive type and for a dictionary where `Value` is a primitive type.
|
||||
|
||||
|
||||
## ExplorerValueCreatable
|
||||
|
||||
The counterpart of `ExplorerValueRepresentable` is ``ExplorerValueCreatable``. Types conforming to this protocol declare an initialization from an `ExplorerValue`. This allows to export the value of an `ExplorerValue` to the type.
|
||||
|
||||
> Tip: Similarly with `ExplorerValueRepresentable`, a default implementation is provided for primitive types and types conforming to `Decodable`.
|
||||
|
||||
With the `Record` structure from above,
|
||||
|
||||
```swift
|
||||
struct Record: Codable, ExplorerValueConvertible {
|
||||
var name: String
|
||||
var score: Int
|
||||
}
|
||||
```
|
||||
|
||||
it's possible to try to export a value of a `PathExplorer` as an array of `Record`s with ``PathExplorer/array(of:)``
|
||||
|
||||
```swift
|
||||
// plist: CodablePathExplorer<PlistFormat>
|
||||
let records = try plist.get("Riri", "records").array(of: Record.self)
|
||||
```
|
||||
|
||||
## ExplorerValueConvertible
|
||||
|
||||
``ExplorerValueConvertible`` is simply a type alias for both `ExplorerValueRepresentable` and `ExplorerValueCreatable` protocols.
|
|
@ -0,0 +1,127 @@
|
|||
# Getting started with Scout
|
||||
|
||||
Quickly learn how to use Scout's features.
|
||||
|
||||
## Overview
|
||||
Scout uses types conforming to the protocols ``PathExplorer`` and ``SerializablePathExplorer`` to read and manipulate data. If it's possible to define your own types conforming to those protocols, it's also possible to use default implementations if they suit your needs. Those explorers can be found in ``PathExplorers`` and will be used for the examples in this article.
|
||||
|
||||
The provided examples will reference this "People" file (here as YAML).
|
||||
|
||||
> Note: The full "People" files are used to try Scout and can be found in the Playground folder.
|
||||
|
||||
```yaml
|
||||
Robert:
|
||||
age: 23
|
||||
height: 181
|
||||
hobbies:
|
||||
- video games
|
||||
- party
|
||||
- tennis
|
||||
Suzanne:
|
||||
job: actress
|
||||
movies:
|
||||
- awards: Best speech for a silent movie
|
||||
title: Tomorrow is so far
|
||||
- awards: Best title
|
||||
title: Yesterday will never go
|
||||
- title: What about today?
|
||||
Tom:
|
||||
age: 68
|
||||
height: 175
|
||||
hobbies:
|
||||
- cooking
|
||||
- guitar
|
||||
```
|
||||
|
||||
## Create a PathExplorer
|
||||
|
||||
The simplest way to read data in any of the supported format is to use one of the ``PathExplorers`` implementation and to call ``SerializablePathExplorer/init(data:)``.
|
||||
|
||||
For instance, let's imagine that the file is read and converted to a `Data` value. Here's how to make an explorer for the YAML format.
|
||||
|
||||
```swift
|
||||
let yaml = try PathExplorers.Yaml(data: data)
|
||||
```
|
||||
|
||||
Similarly, if the format was Plist:
|
||||
|
||||
```swift
|
||||
let plist = try PathExplorers.Plist(data: data)
|
||||
```
|
||||
|
||||
## Navigate through data
|
||||
|
||||
It's then possible to use the ``PathExplorer/get(_:)-2ghf1`` method to read the "height" value in the "Tom" dictionary.
|
||||
|
||||
```swift
|
||||
let tomHeightYaml = try yaml.get("Tom", "height")
|
||||
```
|
||||
|
||||
This will return a new `PathExplorers.Yaml`. That's the logic of Scout: when reading, setting, deleting or adding values in a `PathExplorer`, the returned value will be another `PathExplorer`. This allows to keep performing operation easily. When the explorer has the right value, use one of the several options to access its value. For instance here to get Tom's height as a `Double`
|
||||
|
||||
```swift
|
||||
let tomHeight = tomHeightYaml.double
|
||||
// tomHeight: Double?
|
||||
```
|
||||
More concisely, if you are only interested into getting Tom's height, you could write
|
||||
|
||||
```swift
|
||||
let tomHeight = try yaml.get("Tom", "height").double
|
||||
```
|
||||
|
||||
> Note: As you might have noticed, calling `get()` can throw an error. This is the case for most `PathExplorer` functions. Whenever an element in the provided path does not exist, for instance an index out of bounds, or a missing key, a relevant error will be thrown.
|
||||
|
||||
As a last example, here's how to read Robert first hobby inside an array:
|
||||
|
||||
```swift
|
||||
let robertFirstHobby = try yaml.get("Robert", "hobbies", 0)
|
||||
```
|
||||
|
||||
> Tip: Use negative indexes to specify an index from the *end* of the array.
|
||||
|
||||
To lean more about ``Path``s and how they can help you targeting specific pieces of data, you can read <doc:mastering-paths>.
|
||||
|
||||
## Manipulate data
|
||||
|
||||
Using the same logic, it's possible to set, delete or add values.
|
||||
|
||||
For instance, to set Robert's age to 45 with the ``PathExplorer/set(_:to:)-9d877`` function:
|
||||
|
||||
```swift
|
||||
var yaml = try PathExplorers.Yaml(data: data)
|
||||
try yaml.set("Robert", "age", to: 45)
|
||||
```
|
||||
|
||||
Or to add a new key named "score" with a value of 25 to Tom's dictionary with the ``PathExplorer/add(_:at:)-2kii6`` function:
|
||||
|
||||
```swift
|
||||
var yaml = try PathExplorers.Yaml(data: data)
|
||||
try yaml.add(25, at: "Tom", "score")
|
||||
```
|
||||
|
||||
Those modifications all have specificities, like the "delete" one that can also delete an array or dictionary when left empty. To get more information about those features, please refer to ``PathExplorer``.
|
||||
|
||||
Also, it's worth mentioning that there are counterparts of those functions that will rather return a modified copy of the explorer. This is useful to chain operations.
|
||||
|
||||
For instance, to set Tom's height to 160, add a new surname key to Robert's dictionary and remove Suzanne second movie:
|
||||
```swift
|
||||
let yaml = try PathExplorers.Yaml(data: data)
|
||||
let result = yaml
|
||||
.setting("Tom", "height", to: 160)
|
||||
.adding("Bob", to: "Robert", "surname")
|
||||
.deleting("Suzanne", "movies", 1)
|
||||
```
|
||||
|
||||
> Note: Using plain strings, numbers and booleans is made possible because ``PathElement`` implements `ExpressibleByStringLiteral` and `ExpressibleByIntLiteral`.
|
||||
|
||||
|
||||
## Export the results
|
||||
|
||||
If ``PathExplorer`` is used to navigate through data, the protocol ``SerializablePathExplorer`` refines it to offer import and export options.
|
||||
|
||||
Once you are satisfied with the resulting `SerializablePathExplorer` - regardless of the operations you performed - it's possible to export the explorer as a `Data` value or to another format.
|
||||
To export it to a `Data` value, use ``SerializablePathExplorer/exportData()`` function.
|
||||
|
||||
When needed, it's possible to specify another format when exporting: for instance, if a plist was decoded from a file and has to be converted to a JSON format. See ``SerializablePathExplorer/exportData(to:)`` for more informations.
|
||||
|
||||
Similarly, other export features are available like export to a `String` or to a CSV string.
|
|
@ -0,0 +1,250 @@
|
|||
# Mastering Paths
|
||||
|
||||
``Path``s are provided to a ``PathExplorer`` to navigate through or manipulate data precisely.
|
||||
|
||||
## Overview
|
||||
|
||||
Basically, a `Path` is a collection of ``PathElement``s in a specific order. The sequence of `PathElement`s lets the explorer know what value to target next. When navigating to a value is not possible, the explorer will throw an error.
|
||||
|
||||
The examples in this article will refer to this json file, stored in a ``PathExplorers/Json`` value referred to as `json`.
|
||||
|
||||
> Note: The full "People" files are used to try Scout and can be found in the Playground folder.
|
||||
|
||||
```json
|
||||
{
|
||||
"Tom" : {
|
||||
"age" : 68,
|
||||
"hobbies" : [
|
||||
"cooking",
|
||||
"guitar"
|
||||
],
|
||||
"height" : 175
|
||||
},
|
||||
"Robert" : {
|
||||
"age" : 23,
|
||||
"hobbies" : [
|
||||
"video games",
|
||||
"party",
|
||||
"tennis"
|
||||
],
|
||||
"running_records" : [
|
||||
[
|
||||
10,
|
||||
12,
|
||||
9,
|
||||
10
|
||||
],
|
||||
[
|
||||
9,
|
||||
12,
|
||||
11
|
||||
]
|
||||
],
|
||||
"height" : 181
|
||||
},
|
||||
"Suzanne" : {
|
||||
"job" : "actress",
|
||||
"movies" : [
|
||||
{
|
||||
"title" : "Tomorrow is so far",
|
||||
"awards" : "Best speech for a silent movie"
|
||||
},
|
||||
{
|
||||
"title" : "Yesterday will never go",
|
||||
"awards" : "Best title"
|
||||
},
|
||||
{
|
||||
"title" : "What about today?"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Basics
|
||||
|
||||
The simplest `PathElement`s are ``PathElement/key(_:)`` and ``PathElement/index(_:)``. As their name suggest, they are used to target a key in a dictionary or an index in an array.
|
||||
|
||||
A `Path` can be instantiated from `PathElement`s in an array or as variadic parameters. Then the path can be provided to the `PathExplorer` to read or modify a value. Here are some examples with variadic parameters.
|
||||
|
||||
- Make a `Path` targeting Robert's second hobby
|
||||
```swift
|
||||
let path = Path(elements: "Robert", "hobbies", 1)
|
||||
let secondHobby = try json.get(path: path).string
|
||||
print(secondHobby)
|
||||
// "party"
|
||||
```
|
||||
|
||||
> The `PathExplorer` functions always offer convenience versions to use `PathElement` directly. This is useful to avoid creating a `Path` when it does not already exist or when having a more "scripting" approach.
|
||||
|
||||
- Make a `Path` targeting Suzanne's first movie title
|
||||
```swift
|
||||
Path(elements: "Suzanne", "movies", 0, "title")
|
||||
```
|
||||
|
||||
With indexes, it's possible to use negative numbers to target indexes *from the end* of the array.
|
||||
For instance to target Suzanne's last movie:
|
||||
|
||||
```swift
|
||||
Path(elements: "Suzanne", "movies", -1)
|
||||
```
|
||||
|
||||
The following `ducks` array shows how negative indexes are handled with `PathElement.index`
|
||||
|
||||
```
|
||||
["Riri", "Fifi", "Loulou", "Donald", "Daisy"]
|
||||
[ 0 , 1 , 2 , 3 , 4 ] (Positive)
|
||||
[ -5 , -4 , -3 , -2 , -1 ] (Negative)
|
||||
```
|
||||
|
||||
- `ducks[1]` targets "Fifi"
|
||||
- `ducks[-2`] targets "Donald"
|
||||
|
||||
## Group informations
|
||||
|
||||
### Count
|
||||
|
||||
Scout offers to get a dictionary or array count with ``PathElement/count``. This element has to be placed when the value is an array or dictionary. The returned `PathExplorer` will be a int single value.
|
||||
|
||||
For instance, to get Robert's hobbies count.
|
||||
```swift
|
||||
let path = Path(elements: "Robert", "hobbies", .count)
|
||||
let count = try json.get(path: path).int
|
||||
print(count) // 3
|
||||
```
|
||||
|
||||
Similarly, to read the keys count in the overall dictionary, the following Path can be used.
|
||||
```swift
|
||||
Path(elements: .count)
|
||||
```
|
||||
|
||||
### List keys
|
||||
|
||||
Another useful feature is to list all the keys in a dictionary. To do so, the ``PathElement/keysList`` can be used.
|
||||
For instance, to list Tom's keys:
|
||||
```swift
|
||||
let path = Path(elements: "Tom", .keysList)
|
||||
let tomKeys = try json.get(path: path).array(of: String.self)
|
||||
print(tomKeys)
|
||||
// ["age", "hobbies", "height"]
|
||||
```
|
||||
|
||||
## Scope groups
|
||||
|
||||
When working with arrays and dictionaries, it might be useful to be able to target a specific part in the values. For instance to exclude the first and last value in an array, or to target only keys starting with a certain prefix in a dictionary.
|
||||
|
||||
Those features are available with ``PathElement/slice(_:)`` to slice an array and ``PathElement/filter(_:)`` to filter keys in a dictionary.
|
||||
|
||||
### Slice arrays
|
||||
|
||||
With ``PathElement/slice(_:)``, it's possible to target a contiguous part of an array. For instance to get Robert's first two hobbies.
|
||||
|
||||
> note: When represented as a `String`, the slice element is specified as two integers separated by a double point and enclosed by squared brackets like `[0:2]` or `[2:-4]`. When the left value is the first index, it is omitted. The same goes for the right value when it's the last valid index.
|
||||
|
||||
```swift
|
||||
let path = Path(elements: "Robert", "hobbies", .slice(0, 1))
|
||||
let robertFirstTwoHobbies = try json.get(path: path).array(of: String.self)
|
||||
print(robertFirstTwoHobbies) // ["video games", "party"]
|
||||
```
|
||||
|
||||
Similarly with the ``PathElement/index(_:)``, it's possible to use negative indexes. Here to get Suzanne last two movies' titles.
|
||||
```swift
|
||||
let path = Path(elements: "Suzanne", "movies", .slice(-2, -1), "title")
|
||||
let titles = try json.get(path: path).array(of: String.self)
|
||||
print(titles)
|
||||
// ["Yesterday will never go", "What about today?"]
|
||||
```
|
||||
|
||||
The following `ducks` array explains how positive and negative indexes are interpreted with `PathElement.slice`
|
||||
```
|
||||
["Riri", "Fifi", "Loulou", "Donald", "Daisy"]
|
||||
[ 0 , 1 , 2 , 3 , 4 ] (Positive)
|
||||
[ -5 , -4 , -3 , -2 , -1 ] (Negative)
|
||||
```
|
||||
|
||||
- `ducks[0:2]` targets `["Riri", "Fifi", "Loulou"]`
|
||||
- `ducks[2:-2]` targets `["Loulou", "Donald"]`
|
||||
- `ducks[-3:-1]` targets `["Loulou", "Donald", "Daisy"]`
|
||||
|
||||
### Filter dictionaries
|
||||
|
||||
``PathElement/filter(_:)`` lets you provide a regular expression to match certain keys in a dictionary. All the keys that do not fully match the expression will be filtered.
|
||||
|
||||
For instance, to get all keys in Tom's dictionary that start with "h".
|
||||
|
||||
```swift
|
||||
let path = Path(elements: "Tom", .filter("h.*"))
|
||||
let filteredTom = try json.get(path: path)
|
||||
print(filteredTom)
|
||||
```
|
||||
```json
|
||||
{
|
||||
"hobbies" : [
|
||||
"cooking",
|
||||
"guitar"
|
||||
],
|
||||
"height" : 175
|
||||
}
|
||||
```
|
||||
|
||||
Or to get Tom and Robert first hobby.
|
||||
|
||||
```swift
|
||||
let path = Path(elements: .filter("Tom|Robert"), "hobbies", 0)
|
||||
let firstHobbies = try json.get(path: path).dictionary(of: String.self)
|
||||
print(firstHobbies)
|
||||
// ["Tom": "cooking", "Robert": "video games"]
|
||||
```
|
||||
|
||||
### Mixing up
|
||||
|
||||
It's possible to mix both array slicing and dictionary filtering in a same path. For instance to get Tom and Robert first two hobbies.
|
||||
|
||||
```swift
|
||||
let path = Path(elements: .filter("Tom|Robert"), "hobbies", .slide(.first, 1))
|
||||
let hobbies = try json.get(path: path)
|
||||
print(hobbies)
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"Tom" : [
|
||||
"cooking",
|
||||
"guitar"
|
||||
],
|
||||
"Robert" : [
|
||||
"video games",
|
||||
"party"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Literals and PathElementRepresentable
|
||||
Using plain strings and numbers is made possible because ``PathElement`` implements `ExpressibleByStringLiteral` and `ExpressibleByIntLiteral`. But when it comes to use variables as `PathElement`, it is required to specify the element.
|
||||
|
||||
For instance with the first example path to target Robert's second hobby.
|
||||
|
||||
```swift
|
||||
let robertKey = "Robert"
|
||||
let hobbiesKey = "hobbies"
|
||||
let hobbyIndex = 1
|
||||
let path = Path(elements: .key(robertKey), .key(hobbiesKey), .index(hobbyIndex))
|
||||
```
|
||||
|
||||
As this syntax might be a bit heavy, it's possible to use ``PathElementRepresentable`` to create the `Path` with ``Path/init(_:)-1b2iy``. With it, the code above can be rewritten like so.
|
||||
|
||||
```swift
|
||||
let robertKey = "Robert"
|
||||
let hobbiesKey = "hobbies"
|
||||
let hobbyIndex = 1
|
||||
let path = Path(robertKey, hobbiesKey, hobbyIndex)
|
||||
```
|
||||
|
||||
The drawback is that this is possible only for `PathElement.index` and `PathElement.key`. When dealing with other elements like ``PathElement/count``, it is required to specify the `PathElement` type.
|
||||
|
||||
```swift
|
||||
Path(robertKey, hobbiesKey, PathElement.count)
|
||||
```
|
||||
|
||||
The convenience overloads for the `PathExplorer` functions similarly works with `PathElement` and `PathElementRepresentable`.
|
|
@ -0,0 +1,180 @@
|
|||
# What's new in Scout 4.0.0
|
||||
|
||||
Learn the new features of Scout 4.0.0 as well as what was broken or deprecated
|
||||
|
||||
## General
|
||||
|
||||
Scout 4.0.0 is a global refactor of the code base. This was a necessary step to offer new features. Also, the code is now more robust, faster and more flexible to welcome new features.
|
||||
|
||||
### New data structure
|
||||
|
||||
To reach this goal, a new data structure has been chosen to represent a ``PathExplorer`` values. The ``ExplorerValue`` in an indirect enum and thus is a purely functional structure. For Scout features, it allows to write less and cleaner code, but also to remove the need to manage the states a PathExplorer had in the previous versions.
|
||||
|
||||
This new data structure also allows to use Codable to encode and decode data, which offers several new possibilities, like customizing a `Coder` to better fit one’s use case, or to set and add Codable types with no effort (more on that).
|
||||
|
||||
> Note: The XML parsing has not changed and still uses AEXML. I tried in several ways to use ExplorerValue with a XML coder but this always led to informations loss or strange behaviors. Thus I rather rewrote the XML features with this new ”functional mindset” and I believe it is clearer. Also, small new features like attributes reading are now offered.
|
||||
|
||||
### New path parsing
|
||||
The Path parsing is now done with a Parser rather than with regular expressions. This is more robust and faster. The same goes for parsing a path and its value when adding or setting a value with the command-line tool.
|
||||
|
||||
### Breaking changes
|
||||
|
||||
- Adding a value to a path with non-existing keys and indexes will not work anymore. Only when an element of the path is the last will it be valid to add a new key. The solution is now to create empty dictionaries or arrays and fill them after.
|
||||
- Setting or adding values will no more work with `Any` values but with ``ExplorerValueRepresentable``
|
||||
|
||||
### New features
|
||||
- Usage of `Codable` for Plist, JSON and YAML rather than serialization types to encode and decode data. This means that any Encoder or Decoder can be used with it.
|
||||
- `Data` and `Date` values are now supported.
|
||||
- It’s now possible to set or add a dictionary or an array value to an explorer. Also, in Swift, a type conforming to ``ExplorerValueRepresentable`` can be set or added. A default implementation is provided for Codable types.
|
||||
- XML attributes can now be read. In Swift, new options offer to keep the attributes, and to specify a strategy to handle single child elements when exporting a XML explorer.
|
||||
- Import a CSV file to one of the available formats with a precisely shaped structure.
|
||||
|
||||
## ExplorerValue
|
||||
|
||||
Serializable PathExplorers like Plist, JSON and YAML or others like XML can use this type to set, add, or read a value.
|
||||
|
||||
The new ``ExplorerValue`` is the following enum.
|
||||
|
||||
```swift
|
||||
public indirect enum ExplorerValue {
|
||||
case int(Int)
|
||||
case double(Double)
|
||||
case string(String)
|
||||
case bool(Bool)
|
||||
case data(Data)
|
||||
case date(Date)
|
||||
case array([ExplorerValue])
|
||||
case dictionary([String: ExplorerValue])
|
||||
}
|
||||
```
|
||||
|
||||
### Expressible
|
||||
|
||||
``ExplorerValue`` implements the "Expressible" protocols when it's possible for the types it deals with. This means that it's possible to define an ExplorerValue like the following examples.
|
||||
|
||||
```swift
|
||||
let string: ExplorerValue = "string"
|
||||
let dict: ExplorerValue = ["foo": "bar"]
|
||||
let array: ExplorerValue = ["Riri", "Fifi", "Loulou"]
|
||||
```
|
||||
|
||||
### Codable
|
||||
``ExplorerValue`` conforms to `Codable`. The new SerializablePathExplorer (used for JSON, Plist and XML) uses this conformance to offer initialization from Data. But this also means that any Coder can be used to read an `ExplorerValue` from Data. This was already possible to use a different serializer than the default one in the previous implementations. But customizing a Coder is much simpler and now more common in Swift. For instance, setting a custom `Date` decoding strategy is always offered in most coders.
|
||||
be
|
||||
### Conversion with ExplorerValueRepresentable
|
||||
|
||||
Setting and adding a value to an explorer now works with ExplorerValue. For instance, to set Tom’s age to 60:
|
||||
|
||||
```swift
|
||||
json.set("Tom", "age", to: .int(60))
|
||||
```
|
||||
|
||||
Of course, convenience types and functions are offered, so the line above can be written like this:
|
||||
|
||||
```swift
|
||||
json.set("Tom", "age", to: 60)
|
||||
```
|
||||
|
||||
This is made possible with the `ExplorerValueRepresentable` protocol. It only requires a function to convert the type to an ExplorerValue.
|
||||
|
||||
```swift
|
||||
protocol ExplorerValueRepresentable {
|
||||
func explorerValue() throws -> ExplorerValue
|
||||
}
|
||||
```
|
||||
|
||||
Default implementations are provided for the values mapped by ExplorerValue like `String`, `Double`, an `Array` if its `Element` conforms to ``ExplorerValueRepresentable`` and a `Dictionary` if its `Value` conforms to ExplorerValueRepresentable.
|
||||
Some examples:
|
||||
|
||||
```swift
|
||||
let stringValue = "toto"
|
||||
try json.set("name", to: stringValue)
|
||||
let dict = ["firstName": "Riri", "lastName": "Duck"]
|
||||
try json.set("profile", to: dict)
|
||||
```
|
||||
|
||||
Also, a default implementation for any `Encodable` type is provided. An Encoder is implemented to encode a type to an `ExplorerValue`. Similarly, a `Decoder` is implemented to decode an ExplorerValue to a Decodable type with the protocol ExplorerValueCreatable. A type alias is provided to group both protocols:
|
||||
|
||||
```swift
|
||||
ExplorerValueConvertible = ExplorerValueRepresentable & ExplorerValueCreatable
|
||||
```
|
||||
|
||||
For instance with a simple struct.
|
||||
|
||||
```swift
|
||||
struct Record: Codable, ExplorerValueConvertible {
|
||||
var name: String
|
||||
var score: Int
|
||||
}
|
||||
|
||||
let record = Record(name: "Riri", score: 20)
|
||||
```
|
||||
|
||||
It’s then possible to set the record value at a specific path.
|
||||
|
||||
```swift
|
||||
plist.set("ducks", "records", 0, to: record)
|
||||
```
|
||||
|
||||
### About XML
|
||||
The new ``ExplorerXML`` can also set and add `ExplorerValues`, as well as be converted to one. Because XML is not serializable, this process might loose informations. Options to keep attributes and single child strategies are offered. This is useful in the conversion features like XML → JSON. Whenever it’s possible, `ExplorerXML` will keep as much information as possible. When it’s not possible, the type will act consistently. For instance, when setting a new `Array` value, the children of the XML element will all be named "Element".
|
||||
|
||||
In Swift, it’s possible to rather set an AEXMLElement to have more freedom on the children. This requires more work, but I believe it’s a good thing to have this possibility. To know how to create and edit AEXMLElements, you can checkout the [repo](https://github.com/tadija/AEXML).
|
||||
|
||||
## CSV import
|
||||
|
||||
A new CSV import feature is available to convert a CSV input as JSON, Plist, YAML or XML. A cool feature when working with named headers is that they will be treated as paths. This can shape very precisely the structure of the converted data. For instance, the following CSV
|
||||
|
||||
```csv
|
||||
name.first;name.last;hobbies[0];hobbies[1]
|
||||
Robert;Roni;acting;driving
|
||||
Suzanne;Calvin;singing;play
|
||||
Tom;Cattle;surfing;watching movies
|
||||
```
|
||||
|
||||
will be converted to the following Json structure.
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"hobbies" : [
|
||||
"acting",
|
||||
"driving"
|
||||
],
|
||||
"name" : {
|
||||
"first" : "Robert",
|
||||
"last" : "Roni"
|
||||
}
|
||||
},
|
||||
{
|
||||
"hobbies" : [
|
||||
"singing",
|
||||
"play"
|
||||
],
|
||||
"name" : {
|
||||
"first" : "Suzanne",
|
||||
"last" : "Calvin"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name" : {
|
||||
"first" : "Tom",
|
||||
"last" : "Cattle"
|
||||
},
|
||||
"hobbies" : [
|
||||
"surfing",
|
||||
"watching movies"
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
When there are no headers, the input will be treated as a one or two dimension(s) array.
|
||||
To create a `PathExplorer` from a CSV string, use ``SerializablePathExplorer/fromCSV(string:separator:hasHeaders:)`` function.
|
||||
|
||||
|
||||
```swift
|
||||
let json = PathExplorers.Json.fromCSV(csvString, separator: ";", hasHeaders: true)
|
||||
```
|
||||
|
||||
The `hasHeaders` boolean is needed to specify whether the CSV string begins with named headers.
|
|
@ -0,0 +1,429 @@
|
|||
# Paths listing
|
||||
|
||||
`PathExplorer` list path features is useful to get all paths leading to a value or a key.
|
||||
|
||||
## Overview
|
||||
|
||||
In this article, learn how to use the paths listing feature to precisely get the paths you want.
|
||||
|
||||
|
||||
The examples will refer to the following JSON file.
|
||||
|
||||
```json
|
||||
{
|
||||
"Tom" : {
|
||||
"age" : 68,
|
||||
"hobbies" : [
|
||||
"cooking",
|
||||
"guitar"
|
||||
],
|
||||
"height" : 175
|
||||
},
|
||||
"Robert" : {
|
||||
"age" : 23,
|
||||
"hobbies" : [
|
||||
"video games",
|
||||
"party",
|
||||
"tennis"
|
||||
],
|
||||
"running_records" : [
|
||||
[
|
||||
10,
|
||||
12,
|
||||
9,
|
||||
10
|
||||
],
|
||||
[ 9,
|
||||
12,
|
||||
11
|
||||
]
|
||||
],
|
||||
"height" : 181
|
||||
},
|
||||
"Suzanne" : {
|
||||
"job" : "actress",
|
||||
"movies" : [
|
||||
{
|
||||
"title" : "Tomorrow is so far",
|
||||
"awards" : "Best speech for a silent movie"
|
||||
},
|
||||
{
|
||||
"title" : "Yesterday will never go",
|
||||
"awards" : "Best title"
|
||||
},
|
||||
{
|
||||
"title" : "What about today?"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The reference to the JSON will be held by a ``PathExplorers/Json`` value.
|
||||
|
||||
```swift
|
||||
let json = try PathExplorers.Json(data: data)
|
||||
```
|
||||
|
||||
## Basics
|
||||
|
||||
Let's first see how we can list *all* the path in the file. The command
|
||||
|
||||
```swift
|
||||
print(json.listPaths())
|
||||
```
|
||||
should output
|
||||
```
|
||||
Robert
|
||||
Robert.age
|
||||
Robert.height
|
||||
Robert.hobbies
|
||||
Robert.hobbies[0]
|
||||
Robert.hobbies[1]
|
||||
Robert.hobbies[2]
|
||||
Robert.running_records
|
||||
Robert.running_records[0]
|
||||
Robert.running_records[0][0]
|
||||
Robert.running_records[0][1]
|
||||
Robert.running_records[0][2]
|
||||
Robert.running_records[0][3]
|
||||
Robert.running_records[1]
|
||||
Robert.running_records[1][0]
|
||||
Robert.running_records[1][1]
|
||||
Robert.running_records[1][2]
|
||||
Suzanne
|
||||
Suzanne.job
|
||||
Suzanne.movies
|
||||
Suzanne.movies[0]
|
||||
Suzanne.movies[0].awards
|
||||
Suzanne.movies[0].title
|
||||
Suzanne.movies[1]
|
||||
Suzanne.movies[1].awards
|
||||
Suzanne.movies[1].title
|
||||
Suzanne.movies[2]
|
||||
Suzanne.movies[2].title
|
||||
Tom
|
||||
Tom.age
|
||||
Tom.height
|
||||
Tom.hobbies
|
||||
Tom.hobbies[0]
|
||||
Tom.hobbies[1]
|
||||
```
|
||||
|
||||
> Note: A `Path` is more clearly represented as a `String` with a separator (default is `'.'`). That's what you get by calling `path.description`. It's also the way paths are outputted in the terminal when using the `scout` command-line tool.
|
||||
|
||||
## Single and group values
|
||||
|
||||
It's possible to target only single values (e.g. string, number...), group values (e.g. array, dictionary) or both using a ``PathsFilter``.
|
||||
The default target is both single and group.
|
||||
|
||||
For instance, to target only single values.
|
||||
```swift
|
||||
let path = json.listPaths(
|
||||
filter: .targetOnly(.single)
|
||||
)
|
||||
print(paths)
|
||||
```
|
||||
|
||||
output:
|
||||
```
|
||||
Robert.age
|
||||
Robert.height
|
||||
Robert.hobbies[0]
|
||||
Robert.hobbies[1]
|
||||
Robert.hobbies[2]
|
||||
Robert.running_records[0][0]
|
||||
Robert.running_records[0][1]
|
||||
Robert.running_records[0][2]
|
||||
Robert.running_records[0][3]
|
||||
Robert.running_records[1][0]
|
||||
Robert.running_records[1][1]
|
||||
Robert.running_records[1][2]
|
||||
Suzanne.job
|
||||
Suzanne.movies[0].awards
|
||||
Suzanne.movies[0].title
|
||||
Suzanne.movies[1].awards
|
||||
Suzanne.movies[1].title
|
||||
Suzanne.movies[2].title
|
||||
Tom.age
|
||||
Tom.height
|
||||
Tom.hobbies[0]
|
||||
Tom.hobbies[1]
|
||||
```
|
||||
|
||||
Similarly, to target only group values.
|
||||
```swift
|
||||
let path = json.listPaths(
|
||||
filter: .targetOnly(.group)
|
||||
)
|
||||
print(paths)
|
||||
```
|
||||
|
||||
outputs:
|
||||
```
|
||||
Robert
|
||||
Robert.hobbies
|
||||
Robert.running_records
|
||||
Robert.running_records[0]
|
||||
Robert.running_records[1]
|
||||
Suzanne
|
||||
Suzanne.movies
|
||||
Suzanne.movies[0]
|
||||
Suzanne.movies[1]
|
||||
Suzanne.movies[2]
|
||||
Tom
|
||||
Tom.hobbies
|
||||
```
|
||||
|
||||
## Initial path
|
||||
|
||||
To avoid listing all paths meeting the requirements, it's possible to target paths having a prefix ``PathExplorer/listPaths(startingAt:)``.
|
||||
|
||||
For instance to target only paths in the "Robert" dictionary".
|
||||
```swift
|
||||
let path = json.listPaths(
|
||||
startingAt: "Robert"
|
||||
)
|
||||
print(paths)
|
||||
```
|
||||
|
||||
outputs:
|
||||
```
|
||||
Robert.age
|
||||
Robert.height
|
||||
Robert.hobbies
|
||||
Robert.hobbies[0]
|
||||
Robert.hobbies[1]
|
||||
Robert.hobbies[2]
|
||||
Robert.running_records
|
||||
Robert.running_records[0]
|
||||
Robert.running_records[0][0]
|
||||
Robert.running_records[0][1]
|
||||
Robert.running_records[0][2]
|
||||
Robert.running_records[0][3]
|
||||
Robert.running_records[1]
|
||||
Robert.running_records[1][0]
|
||||
Robert.running_records[1][1]
|
||||
Robert.running_records[1][2]
|
||||
```
|
||||
|
||||
Do note that it's possible to use any ``PathElement`` in the provided initial path.
|
||||
|
||||
For instance to target paths in the Robert *or* Tom dictionaries.
|
||||
|
||||
```swift
|
||||
let path = json.listPaths(
|
||||
startingAt: .filter("Robert|Tom")
|
||||
)
|
||||
print(paths)
|
||||
```
|
||||
|
||||
outputs:
|
||||
```
|
||||
Robert
|
||||
Robert.age
|
||||
Robert.height
|
||||
Robert.hobbies
|
||||
Robert.hobbies[0]
|
||||
Robert.hobbies[1]
|
||||
Robert.hobbies[2]
|
||||
Robert.running_records
|
||||
Robert.running_records[0]
|
||||
Robert.running_records[0][0]
|
||||
Robert.running_records[0][1]
|
||||
Robert.running_records[0][2]
|
||||
Robert.running_records[0][3]
|
||||
Robert.running_records[1]
|
||||
Robert.running_records[1][0]
|
||||
Robert.running_records[1][1]
|
||||
Robert.running_records[1][2]
|
||||
Tom
|
||||
Tom.age
|
||||
Tom.height
|
||||
Tom.hobbies
|
||||
Tom.hobbies[0]
|
||||
Tom.hobbies[1]
|
||||
```
|
||||
|
||||
A last example with paths leading to Suzanne's movies titles.
|
||||
|
||||
```swift
|
||||
let path = json.listPaths(
|
||||
startingAt: "Suzanne", "movies", .slice(.first, .last), "title"
|
||||
)
|
||||
print(paths)
|
||||
```
|
||||
|
||||
outputs:
|
||||
```
|
||||
Suzanne.movies[0].title
|
||||
Suzanne.movies[1].title
|
||||
Suzanne.movies[2].title
|
||||
```
|
||||
|
||||
## Filter keys
|
||||
|
||||
It's possible to provide a regular expression to filter the paths final key. Only the paths that contain a key validated by the regular expression will be retrieved. It's required to provide a `NSRegularExpression`. Meanwhile, the convenience initialiser ``PathsFilter/key(pattern:target:)`` takes a `String` pattern and tries to convert to a `NSRegularExpression.`
|
||||
|
||||
List all the paths leading to a key "hobbies".
|
||||
|
||||
```swift
|
||||
let path = try json.listPaths(
|
||||
filter: .key(pattern: "hobbies")
|
||||
)
|
||||
print(paths)
|
||||
```
|
||||
|
||||
outputs:
|
||||
```
|
||||
Robert.hobbies
|
||||
Robert.hobbies[0]
|
||||
Robert.hobbies[1]
|
||||
Robert.hobbies[2]
|
||||
Tom.hobbies
|
||||
Tom.hobbies[0]
|
||||
Tom.hobbies[1]
|
||||
```
|
||||
|
||||
List all the paths leading to a key starting with "h".
|
||||
|
||||
```swift
|
||||
let path = try json.listPaths(
|
||||
filter: .key(pattern: "h.*")
|
||||
)
|
||||
print(paths)
|
||||
```
|
||||
|
||||
outputs:
|
||||
```
|
||||
Robert.height
|
||||
Robert.hobbies
|
||||
Robert.hobbies[0]
|
||||
Robert.hobbies[1]
|
||||
Robert.hobbies[2]
|
||||
Tom.height
|
||||
Tom.hobbies
|
||||
Tom.hobbies[0]
|
||||
Tom.hobbies[1]
|
||||
```
|
||||
|
||||
## Filter values
|
||||
|
||||
The values can be filtered with one ore more predicates with ``PathsFilter/value(_:)``. When such a filter is specified, only the single values are targeted.
|
||||
A path whose value is validated by one of the provided predicates is retrieved.
|
||||
|
||||
> note: Two kinds of predicates are offered: ``PathsFilter/ExpressionPredicate`` that takes a `String` boolean expression and ``PathsFilter/FunctionPredicate`` that takes a function to filter the values. Both implement the ``ValuePredicate`` protocol.
|
||||
|
||||
List the paths leading to a value below 70 with a ``PathsFilter/FunctionPredicate``
|
||||
|
||||
```swift
|
||||
let predicate = PathsFilter.FunctionPredicate { value in
|
||||
switch value {
|
||||
case let .int(int):
|
||||
return int < 70
|
||||
case let .double(double):
|
||||
return double < 70
|
||||
default:
|
||||
return false // ignore other values
|
||||
}
|
||||
}
|
||||
|
||||
let path = try json.listPaths(
|
||||
filter: .value(predicate)
|
||||
)
|
||||
print(paths)
|
||||
```
|
||||
|
||||
outputs:
|
||||
```
|
||||
Robert.age
|
||||
Robert.running_records[0][0]
|
||||
Robert.running_records[0][1]
|
||||
Robert.running_records[0][2]
|
||||
Robert.running_records[0][3]
|
||||
Robert.running_records[1][0]
|
||||
Robert.running_records[1][1]
|
||||
Robert.running_records[1][2]
|
||||
Tom.age
|
||||
```
|
||||
|
||||
> note: Returning false or throwing an error when the ``ExplorerValue`` parameter has not a proper type for the predicate depends on your needs.
|
||||
|
||||
To mention it once, the ``PathsFilter/ExpressionPredicate`` is used with a `String` boolean expression. If it's mainly used for the command-line tool, it's possible to use in Swift. The code above could be written like so
|
||||
|
||||
```swift
|
||||
let path = try json.listPaths(
|
||||
filter: .value("value < 70")
|
||||
)
|
||||
```
|
||||
|
||||
The name 'value' is used to specify the value that will be filtered, and is replaced by the value to evaluate at runtime by Scout.
|
||||
|
||||
It's possible to specify several predicates. Doing so, a value will be validated as long as one predicate validates it.
|
||||
|
||||
For instance to get string values starting with 'guit' *and* values that are greater than 20.
|
||||
|
||||
```swift
|
||||
let prefixPredicate = PathsFilter.FunctionPredicate { value in
|
||||
guard case let .string(string) = value else { return false }
|
||||
return string.hasPrefix("guit")
|
||||
}
|
||||
|
||||
let comparisonPredicate = PathsFilter.FunctionPredicate { value in
|
||||
guard case let .int(int) = value else { return false }
|
||||
return int > 20
|
||||
}
|
||||
|
||||
let paths = try json.listPaths(
|
||||
filter: .value(prefixPredicate, comparisonPredicate)
|
||||
)
|
||||
```
|
||||
|
||||
outputs:
|
||||
|
||||
```
|
||||
Robert.age
|
||||
Robert.height
|
||||
Tom.age
|
||||
Tom.height
|
||||
Tom.hobbies[1]
|
||||
```
|
||||
|
||||
## Mixing up
|
||||
|
||||
Finally, it's worthing noting that all features to filter paths can be mixed up.
|
||||
|
||||
For instance to list paths leading to Robert hobbies that contain the word "game".
|
||||
|
||||
```swift
|
||||
let gamePredicate = PathsFilter.FunctionPredicate { value in
|
||||
guard case let .string(string) = value else { return false }
|
||||
return string.contains("games")
|
||||
}
|
||||
|
||||
let paths = try json.listPaths(
|
||||
startingAt: "Robert", "hobbies",
|
||||
filter: .value(gamePredicate)
|
||||
)
|
||||
```
|
||||
|
||||
outputs:
|
||||
|
||||
```
|
||||
Robert.hobbies[0]
|
||||
```
|
||||
|
||||
List paths leading to Robert or Tom hobbies arrays (group values).
|
||||
|
||||
```swift
|
||||
let paths = try json.listPaths(
|
||||
startingAt: .filter("Tom|Robert"),
|
||||
filter: .key(pattern: "hobbies", target: .group)
|
||||
)
|
||||
```
|
||||
|
||||
outputs:
|
||||
```
|
||||
Robert.hobbies
|
||||
Tom.hobbies
|
||||
```
|
|
@ -0,0 +1,11 @@
|
|||
# ``Scout/ExplorerValue``
|
||||
|
||||
@Metadata {
|
||||
@DocumentationExtension(mergeBehavior: append)
|
||||
}
|
||||
|
||||
## Overview
|
||||
|
||||
`ExplorerValue` is the back bone of serializable ``PathExplorer`` (JSON, Plist, YAML). It's the type that implements all the logic to conform to `PathExplorer`. Then ``CodablePathExplorer`` simply interfaces it with the proper data format to conform to ``SerializablePathExplorer``. Also, it's the type that is used to encode and decode to those formats.
|
||||
|
||||
But it also allows to use your own types to inject them in a `PathExplorer`. Read more with <doc:custom-types-explorerValue>
|
|
@ -0,0 +1,23 @@
|
|||
# ``Scout/PathElement``
|
||||
|
||||
@Metadata {
|
||||
@DocumentationExtension(mergeBehavior: append)
|
||||
}
|
||||
|
||||
## Topics
|
||||
|
||||
### Basics
|
||||
|
||||
- ``key(_:)``
|
||||
- ``index(_:)``
|
||||
|
||||
### Getting a group information
|
||||
|
||||
- ``count``
|
||||
- ``keysList``
|
||||
|
||||
### Scope groups
|
||||
|
||||
- ``filter(_:)``
|
||||
- ``slice(_:)``
|
||||
- ``slice(_:_:)``
|
|
@ -0,0 +1,124 @@
|
|||
# ``Scout/PathExplorer``
|
||||
|
||||
@Metadata {
|
||||
@DocumentationExtension(mergeBehavior: append)
|
||||
}
|
||||
|
||||
## Overview
|
||||
|
||||
Unifies the operations that can be performed with an explorer.
|
||||
|
||||
## Topics
|
||||
|
||||
### Initializers
|
||||
|
||||
An explorer takes an ``ExplorerValue`` to be instantiated.
|
||||
|
||||
- ``init(value:name:)``
|
||||
- ``init(value:)``
|
||||
|
||||
### Accessing read values
|
||||
|
||||
- ``string``
|
||||
- ``int``
|
||||
- ``double``
|
||||
- ``bool``
|
||||
- ``data``
|
||||
- ``date``
|
||||
- ``array(of:)``
|
||||
- ``dictionary(of:)``
|
||||
|
||||
### Singularity
|
||||
|
||||
- ``isSingle``
|
||||
- ``isGroup``
|
||||
|
||||
### Reading values
|
||||
|
||||
All functions perform the same operation but offer to work with an array, variadic parameters or a ``Path``.
|
||||
|
||||
- ``get(_:)-8vyte``
|
||||
- ``get(_:)-6pa9h``
|
||||
- ``get(_:)-2ghf1``
|
||||
|
||||
### Setting values
|
||||
|
||||
All functions perform the same operation but offer to work with an array, variadic parameters or a ``Path``.
|
||||
|
||||
- ``set(_:to:)-5fgny``
|
||||
- ``set(_:to:)-6yk0i``
|
||||
- ``set(_:to:)-9d877``
|
||||
- ``set(_:to:)-376n0``
|
||||
|
||||
### Setting values in a new explorer
|
||||
|
||||
The "setting" functions are the counterpart of the "set" ones, but will return a new explorer rather than modify it.
|
||||
|
||||
- ``setting(_:to:)-7q3g``
|
||||
- ``setting(_:to:)-9vtr8``
|
||||
- ``setting(_:to:)-5tdzy``
|
||||
- ``setting(_:to:)-n2ij``
|
||||
|
||||
### Setting key names
|
||||
|
||||
All functions perform the same operation but offer to work with an array, variadic parameters or a ``Path``.
|
||||
|
||||
- ``set(_:keyNameTo:)-9i6hd``
|
||||
- ``set(_:keyNameTo:)-5j60r``
|
||||
- ``set(_:keyNameTo:)-1zwfv``
|
||||
|
||||
### Settings key names in a new explorer
|
||||
|
||||
The "setting key name" functions are the counterpart of the "set" ones, but will return a new explorer rather than modify it.
|
||||
|
||||
- ``setting(_:keyNameTo:)-7ar89``
|
||||
- ``setting(_:keyNameTo:)-1vrh``
|
||||
- ``setting(_:keyNameTo:)-1fmyp``
|
||||
|
||||
### Deleting values
|
||||
|
||||
All functions perform the same operation but offer to work with an array, variadic parameters or a ``Path``.
|
||||
|
||||
- ``delete(_:deleteIfEmpty:)-g45f``
|
||||
- ``delete(_:)``
|
||||
- ``delete(_:deleteIfEmpty:)-40w9g``
|
||||
- ``delete(_:deleteIfEmpty:)-2uxwq``
|
||||
|
||||
### Deleting values in a new explorer
|
||||
|
||||
The "deleting" functions are the counterpart of the "set" ones, but will return a new explorer rather than modify it.
|
||||
|
||||
- ``deleting(_:deleteIfEmpty:)-32ufs``
|
||||
- ``deleting(_:deleteIfEmpty:)-1byw9``
|
||||
- ``deleting(_:deleteIfEmpty:)-2u4ud``
|
||||
|
||||
### Adding values
|
||||
|
||||
All functions perform the same operation but offer to work with an array, variadic parameters or a ``Path``.
|
||||
|
||||
- ``add(_:at:)-861h4``
|
||||
- ``add(_:at:)-6wo3i``
|
||||
- ``add(_:at:)-2kii6``
|
||||
- ``add(_:at:)-2zxor``
|
||||
|
||||
### Adding values in a new explorer
|
||||
|
||||
The "adding" functions are the counterpart of the "set" ones, but will return a new explorer rather than modify it.
|
||||
|
||||
- ``adding(_:at:)-7fd9c``
|
||||
- ``adding(_:at:)-4ju9b``
|
||||
- ``adding(_:at:)-68mxp``
|
||||
- ``adding(_:at:)-5uv86``
|
||||
|
||||
### Listing paths
|
||||
|
||||
List paths listing to keys based on regular expression or values based on filters.
|
||||
|
||||
- ``listPaths(startingAt:filter:)-4tkeq``
|
||||
- ``listPaths(startingAt:)``
|
||||
- ``listPaths(startingAt:filter:)-8y0x2``
|
||||
|
||||
|
||||
### Deprecated
|
||||
|
||||
- ``real``
|
|
@ -0,0 +1,64 @@
|
|||
# ``Scout/Path``
|
||||
|
||||
@Metadata {
|
||||
@DocumentationExtension(mergeBehavior: append)
|
||||
}
|
||||
|
||||
## Overview
|
||||
|
||||
Paths are the way to feed a ``PathExplorer`` to navigate through data. `PathExplorer`'s operations will often take a `Path` (or a collection of ``PathElement``s) to target precisely where to run.
|
||||
|
||||
Basically, a `Path` is a collection of ``PathElement``s in a specific order. The sequence of `PathElement`s lets the explorer know what value to target next. When navigating to a value is not possible, the explorer will throw an error.
|
||||
|
||||
## Topics
|
||||
|
||||
### Instantiate an empty Path
|
||||
|
||||
- ``init()``
|
||||
- ``empty``
|
||||
|
||||
### Instantiate from PathElement values
|
||||
|
||||
- ``init(elements:)-8dch4``
|
||||
- ``init(elements:)-9i64v``
|
||||
|
||||
### Instantiate from PathElementRepresentable values
|
||||
|
||||
``PathElementRepresentable`` is a protocol to erase the `PathElement` type when instantiating a `Path` with non-literal values.
|
||||
|
||||
- ``init(_:)-1b2iy``
|
||||
- ``init(_:)-cgb7``
|
||||
- ``init(arrayLiteral:)``
|
||||
|
||||
### Instantiate from a String
|
||||
|
||||
A `Path` is easily represented as a `String`, which is especially useful when working in the command-line.
|
||||
|
||||
- ``init(string:separator:)``
|
||||
- ``defaultSeparator``
|
||||
- ``parser(separator:keyForbiddenCharacters:)``
|
||||
|
||||
### Appending elements
|
||||
|
||||
`Path` conforms to several collection protocols. Additionally, those convenience functions are offered.
|
||||
|
||||
- ``append(_:)-9l194``
|
||||
- ``appending(_:)-2ptn6``
|
||||
- ``appending(_:)-3mvwq``
|
||||
|
||||
### Flatten a Path
|
||||
|
||||
When a `Path` contains special group scoping elements like ``PathElement/slice(_:)`` or ``PathElement/filter(_:)``, specifying a `PathElement.index` or `PathElement.key` will not refer to an immediate dictionary or array. The "flatten" operation will replace the slices and the filters in the `Path` with the proper values when the path is complete. Mainly used in paths listing ``PathExplorer/listPaths(startingAt:)``.
|
||||
|
||||
- ``flattened()``
|
||||
|
||||
### Map elements (Collection)
|
||||
|
||||
- ``Path/compactMapIndexes``
|
||||
- ``Path/compactMapKeys``
|
||||
- ``Path/compactMapSlices``
|
||||
- ``Path/compactMapFilter``
|
||||
|
||||
### Compare path (Collection)
|
||||
|
||||
- ``Path/commonPrefix(with:)``
|
|
@ -0,0 +1,46 @@
|
|||
# ``Scout/PathsFilter``
|
||||
|
||||
@Metadata {
|
||||
@DocumentationExtension(mergeBehavior: append)
|
||||
}
|
||||
|
||||
## Overview
|
||||
|
||||
Allows to target single or group values, specific keys with regular expressions and values with predicates.
|
||||
|
||||
When filtering keys or values, it's always possible to specify single, group values or both.
|
||||
|
||||
## Topics
|
||||
|
||||
### No filter
|
||||
|
||||
- ``noFilter``
|
||||
- ``targetOnly(_:)``
|
||||
- ``ValueTarget``
|
||||
|
||||
### Filter keys
|
||||
|
||||
- ``key(regex:)``
|
||||
- ``key(regex:target:)``
|
||||
- ``key(pattern:target:)``
|
||||
|
||||
### Filter values
|
||||
|
||||
- ``value(_:)``
|
||||
- ``value(_:_:)-8tfx1``
|
||||
- ``value(_:_:)-2wxh0``
|
||||
|
||||
### Filter keys and values
|
||||
|
||||
- ``keyAndValue(pattern:valuePredicate:)``
|
||||
- ``keyAndValue(keyRegex:valuePredicate:)``
|
||||
- ``keyAndValue(keyRegex:valuePredicates:)``
|
||||
- ``keyAndValue(keyRegex:valuePredicates:_:)``
|
||||
- ``keyAndValue(pattern:valuePredicatesFormat:_:)``
|
||||
|
||||
### Predicates
|
||||
|
||||
- ``ValuePredicate``
|
||||
- ``ExpressionPredicate``
|
||||
- ``FunctionPredicate``
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
# ``Scout/SerializablePathExplorer/exportFoldedString(upTo:)``
|
||||
|
||||
@Metadata {
|
||||
@DocumentationExtension(mergeBehavior: append)
|
||||
}
|
||||
### Examples
|
||||
|
||||
With the following JSON stored in a `SerializablePathExplorer` named `json`.
|
||||
|
||||
```json
|
||||
{
|
||||
"Tom" : {
|
||||
"age" : 68,
|
||||
"hobbies" : [
|
||||
"cooking",
|
||||
"guitar"
|
||||
],
|
||||
"height" : 175
|
||||
},
|
||||
"Robert" : {
|
||||
"age" : 23,
|
||||
"hobbies" : [
|
||||
"video games",
|
||||
"party",
|
||||
"tennis"
|
||||
],
|
||||
"running_records" : [
|
||||
[
|
||||
10,
|
||||
12,
|
||||
9,
|
||||
10
|
||||
],
|
||||
[
|
||||
9,
|
||||
12,
|
||||
11
|
||||
]
|
||||
],
|
||||
"height" : 181
|
||||
},
|
||||
"Suzanne" : {
|
||||
"job" : "actress",
|
||||
"movies" : [
|
||||
{
|
||||
"title" : "Tomorrow is so far",
|
||||
"awards" : "Best speech for a silent movie"
|
||||
},
|
||||
{
|
||||
"title" : "Yesterday will never go",
|
||||
"awards" : "Best title"
|
||||
},
|
||||
{
|
||||
"title" : "What about today?"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The following
|
||||
|
||||
```swift
|
||||
json.exportFoldedString(upTo: 2)
|
||||
```
|
||||
|
||||
will return the string:
|
||||
|
||||
```json
|
||||
{
|
||||
"Suzanne" : {
|
||||
"job" : "actress",
|
||||
"movies" : [...]
|
||||
},
|
||||
"Tom" : {
|
||||
"hobbies" : [...],
|
||||
"age" : 68,
|
||||
"height" : 175
|
||||
},
|
||||
"Robert" : {
|
||||
"running_records" : [...],
|
||||
"age" : 23,
|
||||
"hobbies" : [...],
|
||||
"height" : 181
|
||||
}
|
||||
}
|
||||
```
|
|
@ -0,0 +1,42 @@
|
|||
# ``Scout/SerializablePathExplorer``
|
||||
|
||||
@Metadata {
|
||||
@DocumentationExtension(mergeBehavior: append)
|
||||
}
|
||||
|
||||
## Overview
|
||||
|
||||
Protocol refining ``PathExplorer`` to offer features like conversion to another format or serialization. Explorers in ``PathExplorers`` implement this protocol.
|
||||
|
||||
## Topics
|
||||
|
||||
### Initializers
|
||||
|
||||
- ``init(data:)``
|
||||
- ``fromCSV(string:separator:hasHeaders:)``
|
||||
|
||||
### Get format info
|
||||
|
||||
- ``format``
|
||||
|
||||
### Export as Data
|
||||
|
||||
- ``exportData()``
|
||||
- ``exportData(to:)``
|
||||
- ``exportData(to:rootName:)``
|
||||
|
||||
### Export as String
|
||||
|
||||
- ``exportString()``
|
||||
- ``exportString(to:)``
|
||||
- ``exportString(to:rootName:)``
|
||||
|
||||
### Export as CSV
|
||||
|
||||
- ``exportCSV()``
|
||||
- ``exportCSV(separator:)``
|
||||
|
||||
### Export folded String
|
||||
|
||||
- ``exportFoldedString(upTo:)``
|
||||
- ``folded(upTo:)``
|
|
@ -0,0 +1,56 @@
|
|||
# ``Scout``
|
||||
|
||||
This library aims to make specific formats data values reading and writing simple when the data format is not known at build time.
|
||||
|
||||
## Overview
|
||||
|
||||
Supported formats:
|
||||
- JSON
|
||||
- Plist
|
||||
- YAML
|
||||
- XML
|
||||
|
||||
## Topics
|
||||
|
||||
### Essential
|
||||
- <doc:getting-started>
|
||||
- ``PathExplorer``
|
||||
- ``Path``
|
||||
|
||||
### Explore data
|
||||
|
||||
- ``PathExplorer``
|
||||
- ``PathExplorers``
|
||||
- ``ExplorerError``
|
||||
|
||||
### Manipulate paths
|
||||
|
||||
- <doc:mastering-paths>
|
||||
- <doc:paths-listing>
|
||||
- ``Path``
|
||||
- ``PathElement``
|
||||
- ``PathElementRepresentable``
|
||||
- ``PathsFilter``
|
||||
- ``ValuePredicate``
|
||||
- ``Bounds``
|
||||
|
||||
### Convert and export explorers
|
||||
|
||||
- ``SerializablePathExplorer``
|
||||
- ``DataFormat``
|
||||
- ``CodablePathExplorer``
|
||||
- ``CodableFormat``
|
||||
- ``CodableFormats``
|
||||
- ``ExplorerXML``
|
||||
- ``SerializationError``
|
||||
|
||||
### Set and add custom types
|
||||
|
||||
- <doc:custom-types-explorerValue>
|
||||
- ``ExplorerValue``
|
||||
- ``ExplorerValueCreatable``
|
||||
- ``ExplorerValueRepresentable``
|
||||
- ``ExplorerValueConvertible``
|
||||
|
||||
### Follow updates
|
||||
- <doc:new-4.0.0>
|
|
@ -91,8 +91,10 @@ final class PathExplorerGetTests: XCTestCase {
|
|||
func testGetKey_MissingKeyThrows_BestMatch<P: EquatablePathExplorer>(_ type: P.Type) throws {
|
||||
let explorer = P(value: ["Endo": 2, "toto": true, "Riri": "duck", "score": 12.5])
|
||||
|
||||
XCTAssertErrorsEqual(try explorer.get("tata"),
|
||||
ExplorerError.missing(key: "tata", bestMatch: "toto"))
|
||||
XCTAssertErrorsEqual(
|
||||
try explorer.get("tata"),
|
||||
.missing(key: "tata", bestMatch: "toto")
|
||||
)
|
||||
}
|
||||
|
||||
func testGetKey_NestedKey<P: EquatablePathExplorer>(_ type: P.Type) throws {
|
||||
|
@ -104,8 +106,10 @@ final class PathExplorerGetTests: XCTestCase {
|
|||
func testGet_MissingNestedKeyThrows<P: PathExplorer>(_ type: P.Type) throws {
|
||||
let explorer = P(value: ["firstKey": ["secondKey": 23]])
|
||||
|
||||
XCTAssertErrorsEqual(try explorer.get("firstKey", "kirk"),
|
||||
ExplorerError.missing(key: "kirk", bestMatch: nil).with(path: "firstKey"))
|
||||
XCTAssertErrorsEqual(
|
||||
try explorer.get("firstKey", "kirk"),
|
||||
.missing(key: "kirk", bestMatch: nil).with(path: "firstKey")
|
||||
)
|
||||
}
|
||||
|
||||
func testGetKey_ThrowsOnNoDictionary<P: EquatablePathExplorer>(_ type: P.Type) throws {
|
||||
|
@ -175,8 +179,10 @@ final class PathExplorerGetTests: XCTestCase {
|
|||
func testGetCount_ThrowsOnNonGroup() throws {
|
||||
let array: ExplorerValue = ["Endo", 1, false, 2.5]
|
||||
|
||||
XCTAssertErrorsEqual(try array.get(0, .count),
|
||||
ExplorerError.wrongUsage(of: .count).with(path: 0))
|
||||
XCTAssertErrorsEqual(
|
||||
try array.get(0, .count),
|
||||
.wrongUsage(of: .count).with(path: 0)
|
||||
)
|
||||
}
|
||||
|
||||
// MARK: - Keys list
|
||||
|
@ -193,7 +199,10 @@ final class PathExplorerGetTests: XCTestCase {
|
|||
func testGetKeysList_ThrowsOnNonDictionary<P: EquatablePathExplorer>(_ type: P.Type) throws {
|
||||
let explorer = P(value: ["Endo", 1, false, 2.5])
|
||||
|
||||
XCTAssertErrorsEqual(try explorer.get(.keysList), .wrongUsage(of: .keysList))
|
||||
XCTAssertErrorsEqual(
|
||||
try explorer.get(.keysList),
|
||||
.wrongUsage(of: .keysList)
|
||||
)
|
||||
}
|
||||
|
||||
// MARK: - Filter
|
||||
|
@ -236,6 +245,7 @@ final class PathExplorerGetTests: XCTestCase {
|
|||
|
||||
func testGetFilter_ThrowsOnNonDictionary<P: EquatablePathExplorer>(_ type: P.Type) throws {
|
||||
let explorer = P(value: ["Endo", 1, false, 2.5])
|
||||
|
||||
XCTAssertErrorsEqual(try explorer.get(.filter("toto")),
|
||||
.wrongUsage(of: .filter("toto"))
|
||||
)
|
||||
|
@ -283,7 +293,10 @@ final class PathExplorerGetTests: XCTestCase {
|
|||
func testGetSlice_ThrowsOnNonArray<P: EquatablePathExplorer>(_ type: P.Type) throws {
|
||||
let explorer = P(value: ["Tom": 10, "Robert": true, "Suzanne": "Here"])
|
||||
|
||||
XCTAssertErrorsEqual(try explorer.get(.slice(0, 1)), .wrongUsage(of: .slice(0, 1)))
|
||||
XCTAssertErrorsEqual(
|
||||
try explorer.get(.slice(0, 1)),
|
||||
.wrongUsage(of: .slice(0, 1))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue