diff --git a/Package.resolved b/Package.resolved index 424bb154..8e263abe 100644 --- a/Package.resolved +++ b/Package.resolved @@ -15,8 +15,17 @@ "repositoryURL": "https://github.com/ABridoux/lux", "state": { "branch": null, - "revision": "b97bc3f12e516669ab95e05161b91062f7934a64", - "version": "0.2.1" + "revision": "8174cedcd6542b59b9e88b577ba87f10fa1ab312", + "version": "0.3.5" + } + }, + { + "package": "Splash", + "repositoryURL": "https://github.com/JohnSundell/Splash", + "state": { + "branch": null, + "revision": "ca9a1b7bff35381fe5ae9fe2ec9333cc5f2a586c", + "version": "0.13.0" } }, { @@ -24,8 +33,8 @@ "repositoryURL": "https://github.com/apple/swift-argument-parser", "state": { "branch": null, - "revision": "3d79b2b5a2e5af52c14e462044702ea7728f5770", - "version": "0.1.0" + "revision": "eb51f949cdd0c9d88abba9ce79d37eb7ea1231d0", + "version": "0.2.0" } } ] diff --git a/Sources/ScoutCLT/AddCommand.swift b/Sources/ScoutCLT/Add/AddCommand.swift similarity index 58% rename from Sources/ScoutCLT/AddCommand.swift rename to Sources/ScoutCLT/Add/AddCommand.swift index 8a3cf66b..eccf8b1b 100644 --- a/Sources/ScoutCLT/AddCommand.swift +++ b/Sources/ScoutCLT/Add/AddCommand.swift @@ -2,68 +2,14 @@ import ArgumentParser import Scout import Foundation -private let discussion = -""" -Notes -===== -- All the keys which do not exist in the path will be created -- Enclose the value with slash signs to force the value as a string: /valueAsString/ (Plist, Json) -- Enclose the value with interrogative signs to force the value as a boolean: ?valueToBoolean? (Plist, Json) -- Enclose the value with tilde signs to force the value as a real: ~valueToReal~ (Plist) -- Enclose the value with chevron signs to force the value as a integer: (Plist) -- When adding an element in an array , use the index -1 to add the element at the end of the array - -Examples -======== -Given the following Json (as input stream or file) - -{ - "people": { - "Tom": { - "height": 175, - "age": 68, - "hobbies": [ - "cooking", - "guitar" - ] - }, - "Arnaud": { - "height": 180, - "age": 23, - "hobbies": [ - "video games", - "party", - "tennis" - ] - } - } -} - -`scout add "people.Franklin.height"=165` will create a new dictionary Franklin and add a height key into it with the value 165 - -`scout add "people.Tom.hobbies[-1]"="Playing music"` will add the hobby "Playing music" to Tom hobbies at the end of the array - -`scout add "people.Arnaud.hobbies[1]"=reading` will insert the hobby "reading" to Arnaud hobbies between the hobby "video games" and "party" - -`scout add "people.Franklin.hobbies[0]"=football` will create a new dictionary Franklin, add a hobbies array into it, and insert the value "football" in the array - -`scout add "people.Franklin.height"=/165/` will create a new dictionary Franklin and add a height key into it with the String value "165" - -`scout add "people.Franklin.height"=~165~` will create a new dictionary Franklin and add a height key into it with the Real value 165 (Plist only) - -More -==== -You can find more examples here: https://github.com/ABridoux/scout/tree/master/Playground -""" - struct AddCommand: ParsableCommand { static let configuration = CommandConfiguration( commandName: "add", abstract: "Add value at a given path", - discussion: discussion) + discussion: "To find examples and advanced explanations, please type `scout doc add`") @Argument(help: PathAndValue.help) - var pathsAndValues: [PathAndValue] + var pathsAndValues = [PathAndValue]() @Option(name: [.short, .customLong("input")], help: "A file path from which to read the data") var inputFilePath: String? @@ -74,8 +20,8 @@ struct AddCommand: ParsableCommand { @Option(name: [.short, .customLong("modify")], help: "Read and write the data into the same file at the given path") var modifyFilePath: String? - @Flag(name: [.short, .long], default: false, inversion: .prefixedNo, help: "Output the modified data") - var verbose: Bool + @Flag(name: [.short, .long], inversion: .prefixedNo, help: "Output the modified data") + var verbose = false func run() throws { diff --git a/Sources/ScoutCLT/Add/AddDocumentation.swift b/Sources/ScoutCLT/Add/AddDocumentation.swift new file mode 100644 index 00000000..d083182a --- /dev/null +++ b/Sources/ScoutCLT/Add/AddDocumentation.swift @@ -0,0 +1,70 @@ +import ArgumentParser +import Lux + +struct AddDocumentation: Documentation { + private static let jsonInjector = JSONInjector(type: .terminal) + private static let zshInjector = ZshInjector(type: .terminal) + + private static let jsonExample = + """ + { + "people": { + "Tom": { + "height": 175, + "age": 68, + "hobbies": [ + "cooking", + "guitar" + ] + }, + "Arnaud": { + "height": 180, + "age": 23, + "hobbies": [ + "video games", + "party", + "tennis" + ] + } + } + } + """ + + private static let examples = [(#"`scout add "people.Franklin.height=165"`"#, #"will create a new dictionary Franklin and add a height key into it with the value 165"#), + (#"`scout add "people.Tom.hobbies[-1]"="Playing music"`"#, #"will add the hobby "Playing music" to Tom hobbies at the end of the array"#), + (#"`scout add "people.Arnaud.hobbies[1]=reading"`"#, #"will insert the hobby "reading" to Arnaud hobbies between the hobby "video games" and "party""#), + (#"`scout add "people.Franklin.hobbies[0]"=football`"#, """ + will create a new dictionary Franklin, + add a hobbies array into it, and insert the value "football" in the array + """), + (#"`scout add "people.Franklin.height=/165/"`"#, #"will create a new dictionary Franklin and add a height key into it with the String value "165""#), + (#"`scout add "people.Franklin.height=~165~"`"#, #"will create a new dictionary Franklin and add a height key into it with the Real value 165 (Plist only)"#)] + + static let text = + """ + + Add command + ============ + + Notes + ----- + - You add delete multiple values in one command + - Specify the \(zshInjector.delegate.inject(.optionNameOrFlag, in: .terminal, "-v")) flag to see the modified data + + - All the keys which do not exist in the path will be created + - Enclose the value with slash signs to force the value as a string: /valueAsString/ (Plist, Json) + - Enclose the value with interrogative signs to force the value as a boolean: ?valueToBoolean? (Plist, Json) + - Enclose the value with tilde signs to force the value as a real: ~valueToReal~ (Plist) + - Enclose the value with chevron signs to force the value as a integer: (Plist) + - When adding an element in an array, use the index -1 to add the element at the end of the array + + Examples + -------- + + JSON file + + \(jsonInjector.inject(in: jsonExample)) + + \(examplesText(from: examples)) + """ +} diff --git a/Sources/ScoutCLT/ColorDelegates/ColorFile.swift b/Sources/ScoutCLT/ColorDelegates/ColorFile.swift index 1a5bcd3f..ad91ba7a 100644 --- a/Sources/ScoutCLT/ColorDelegates/ColorFile.swift +++ b/Sources/ScoutCLT/ColorDelegates/ColorFile.swift @@ -1,29 +1,29 @@ struct JsonColors: Codable { - var punctuation: Int? - var keyName: Int? - var keyValue: Int? + var punctuation: Int? = nil + var keyName: Int? = nil + var keyValue: Int? = nil } struct PlistColors: Codable { - var tag: Int? - var keyName: Int? - var keyValue: Int? - var header: Int? - var comment: Int? + var tag: Int? = nil + var keyName: Int? = nil + var keyValue: Int? = nil + var header: Int? = nil + var comment: Int? = nil } struct XmlColors: Codable { - var punctuation: Int? - var openingTag: Int? - var closingTag: Int? - var key: Int? - var header: Int? - var comment: Int? + var punctuation: Int? = nil + var openingTag: Int? = nil + var closingTag: Int? = nil + var key: Int? = nil + var header: Int? = nil + var comment: Int? = nil } /// Plist file to specify custom colors struct ColorFile: Codable { - var json: JsonColors? - var plist: PlistColors? - var xml: XmlColors? + var json: JsonColors? = nil + var plist: PlistColors? = nil + var xml: XmlColors? = nil } diff --git a/Sources/ScoutCLT/ColorDelegates/JSONInjectorDelegate.swift b/Sources/ScoutCLT/ColorDelegates/JSONInjectorDelegate.swift index 191d3d80..ad27aa0f 100644 --- a/Sources/ScoutCLT/ColorDelegates/JSONInjectorDelegate.swift +++ b/Sources/ScoutCLT/ColorDelegates/JSONInjectorDelegate.swift @@ -13,14 +13,16 @@ final class JSONInjectorColorDelegate: JSONDelegate { } required init() { - fatalError("init() has not been implemented") + colors = JsonColors() + super.init() } // MARK: - Functions - override func injection(for category: JSONCategory, type: TextType) -> String { + override func terminalModifier(for category: JSONCategory) -> TerminalModifier { var colorCode: Int? + // retrieve the color code in the colors plist if any switch category { case .punctuation: colorCode = colors.punctuation case .keyName: colorCode = colors.keyName @@ -28,9 +30,9 @@ final class JSONInjectorColorDelegate: JSONDelegate { } if let code = colorCode { - return String.colorPrefix(code) + return TerminalModifier(colorCode: code) } else { - return super.injection(for: category, type: type) + return super.terminalModifier(for: category) } } } diff --git a/Sources/ScoutCLT/ColorDelegates/PlistInjectorColorDelegate.swift b/Sources/ScoutCLT/ColorDelegates/PlistInjectorColorDelegate.swift index 92c24dbb..b02d9744 100644 --- a/Sources/ScoutCLT/ColorDelegates/PlistInjectorColorDelegate.swift +++ b/Sources/ScoutCLT/ColorDelegates/PlistInjectorColorDelegate.swift @@ -13,12 +13,13 @@ final class PlistInjectorColorDelegate: PlistDelegate { } required init() { - fatalError("init() has not been implemented") + colors = PlistColors() + super.init() } // MARK: - Functions - override func injection(for category: PlistCategory, type: TextType) -> String { + override func terminalModifier(for category: PlistCategory) -> TerminalModifier { var colorCode: Int? switch category { @@ -30,9 +31,9 @@ final class PlistInjectorColorDelegate: PlistDelegate { } if let code = colorCode { - return String.colorPrefix(code) + return TerminalModifier(colorCode: code) } else { - return super.injection(for: category, type: type) + return super.terminalModifier(for: category) } } } diff --git a/Sources/ScoutCLT/ColorDelegates/XMLInjectorColorDelegate.swift b/Sources/ScoutCLT/ColorDelegates/XMLInjectorColorDelegate.swift index 4ad1e4d2..3d824d82 100644 --- a/Sources/ScoutCLT/ColorDelegates/XMLInjectorColorDelegate.swift +++ b/Sources/ScoutCLT/ColorDelegates/XMLInjectorColorDelegate.swift @@ -13,12 +13,13 @@ final class XMLInjectorColorDelegate: XMLEnhancedDelegate { } required init() { - fatalError("init() has not been implemented") + colors = XmlColors() + super.init() } // MARK: - Functions - override func injection(for category: XMLEnhancedCategory, type: TextType) -> String { + override func terminalModifier(for category: XMLEnhancedCategory) -> TerminalModifier { var colorCode: Int? switch category { @@ -31,9 +32,9 @@ final class XMLInjectorColorDelegate: XMLEnhancedDelegate { } if let code = colorCode { - return String.colorPrefix(code) + return TerminalModifier(colorCode: code) } else { - return super.injection(for: category, type: type) + return super.terminalModifier(for: category) } } } diff --git a/Sources/ScoutCLT/Command.swift b/Sources/ScoutCLT/Command.swift new file mode 100644 index 00000000..e30a27c1 --- /dev/null +++ b/Sources/ScoutCLT/Command.swift @@ -0,0 +1,5 @@ +import ArgumentParser + +enum Command: String, ExpressibleByArgument { + case read, set, delete, add +} diff --git a/Sources/ScoutCLT/DeleteCommand.swift b/Sources/ScoutCLT/Delete/DeleteCommand.swift similarity index 89% rename from Sources/ScoutCLT/DeleteCommand.swift rename to Sources/ScoutCLT/Delete/DeleteCommand.swift index 8918a42c..bdc647d3 100644 --- a/Sources/ScoutCLT/DeleteCommand.swift +++ b/Sources/ScoutCLT/Delete/DeleteCommand.swift @@ -9,12 +9,12 @@ struct DeleteCommand: ParsableCommand { static let configuration = CommandConfiguration( commandName: "delete", abstract: "Delete a value at a given path", - discussion: "When accessing an array value by its index, use the index -1 to access to the last element") + discussion: "To find examples and advanced explanations, please type `scout doc delete`") // MARK: - Properties @Argument(help: "Paths to indicate the keys to be deleted") - var readingPaths: [Path] + var readingPaths = [Path]() @Option(name: [.short, .customLong("input")], help: "A file path from which to read the data") var inputFilePath: String? @@ -25,8 +25,8 @@ struct DeleteCommand: ParsableCommand { @Option(name: [.short, .customLong("modify")], help: "Read and write the data into the same file at the given path") var modifyFilePath: String? - @Flag(name: [.short, .long], default: false, inversion: .prefixedNo, help: "Output the modified data") - var verbose: Bool + @Flag(name: [.short, .long], inversion: .prefixedNo, help: "Output the modified data") + var verbose = false // MARK: - Functions diff --git a/Sources/ScoutCLT/Delete/DeleteDocumentation.swift b/Sources/ScoutCLT/Delete/DeleteDocumentation.swift new file mode 100644 index 00000000..c0fb555d --- /dev/null +++ b/Sources/ScoutCLT/Delete/DeleteDocumentation.swift @@ -0,0 +1,59 @@ +import ArgumentParser +import Lux + +struct DeleteDocumentation: Documentation { + private static let jsonInjector = JSONInjector(type: .terminal) + private static let zshInjector = ZshInjector(type: .terminal) + + private static let jsonExample = + """ + { + "people": { + "Tom": { + "height": 175, + "age": 68, + "hobbies": [ + "cooking", + "guitar" + ] + }, + "Arnaud": { + "height": 180, + "age": 23, + "hobbies": [ + "video games", + "party", + "tennis" + ] + } + } + } + """ + + private static let examples = [(#"`scout delete "people.Tom.height"`"#, #"will delete Tom height"#), + (#"`scout delete "people.Tom.hobbies[0]"`"#, #"will delete Tom first hobby"#), + (#"`scout delete "people.Tom.hobbies[-1]"`"#, #"will delete Tom last hobby"#)] + + static let text = + """ + + Delete command + ============ + + Notes + ----- + - If the path is invalid, the program will return an error + - You can delete multiple values in one command + - When accessing an array value by its index, use the index -1 to access to the last element + - Specify the \(zshInjector.delegate.inject(.optionNameOrFlag, in: .terminal, "-v")) flag to see the modified data + + Examples + -------- + + JSON file + + \(jsonInjector.inject(in: jsonExample)) + + \(examplesText(from: examples)) + """ +} diff --git a/Sources/ScoutCLT/Documentation.swift b/Sources/ScoutCLT/Documentation.swift new file mode 100644 index 00000000..c70dd1ea --- /dev/null +++ b/Sources/ScoutCLT/Documentation.swift @@ -0,0 +1,20 @@ +import Lux + +/// Piece of documentation used to be printed +protocol Documentation { + static var text: String { get } +} + +extension Documentation { + + /// Get one example per line + /// - Parameters: + /// - examples: Array of (code, output) to display + /// - injector: Injector to use. Default is Zsh. + /// - Returns: All examples with injected Zsh code + static func examplesText(from examples: [(code: String, output: String)], with injector: TextInjector = ZshInjector(type: .terminal)) -> String { + examples.reduce("") { (result, example) in + "\(result) \(injector.inject(in: example.code)) \(example.output)\n" + } + } +} diff --git a/Sources/ScoutCLT/Main/DocCommand.swift b/Sources/ScoutCLT/Main/DocCommand.swift new file mode 100644 index 00000000..de5e40d2 --- /dev/null +++ b/Sources/ScoutCLT/Main/DocCommand.swift @@ -0,0 +1,136 @@ +import ArgumentParser +import Lux + +private let jsonInjector = JSONInjector(type: .terminal) +private let zshInjector = ZshInjector(type: .terminal) + + +private let jsonExample = +""" +{ + "people": { + "Tom": { + "height": 175, + "age": 68, + "hobbies": [ + "cooking", + "guitar" + ] + }, + "Arnaud": { + "height": 180, + "age": 23, + "hobbies": [ + "video games", + "party", + "tennis" + ] + } + } +} +""" + +private let readExamples = [#"`scout "people.Tom.hobbies[0]"`"#, + #"`scout "people.Arnaud.height"`"#, + #"`scout "people.Tom.hobbies[-1]"`"#, + #"`scout "people.Tom`"#] + +private let setExamples = [#"`scout set "people.Tom.hobbies[0]=basket"`"#, + #"`scout set "people.Arnaud.height=160"`"#, + #"`scout set "people.Tom.age=#years#"`"#, + #"`scout set "people.Tom.hobbies[-1]"="playing music""#] + +private let deleteExamples = [#"`scout delete "people.Tom.height"`"#, + #"`scout delete "people.Tom.hobbies[0]"`"#, + #"`scout delete "people.Tom.hobbies[-1]"`"#] + +private let addExamples = [#"`scout add "people.Franklin.height=165"`"#, + #"`scout add "people.Tom.hobbies[-1]"="Playing music"`"#, + #"`scout add "people.Arnaud.hobbies[1]=reading"`"#, + #"`scout add "people.Arnaud.hobbies[-1]=surf"`"#, + #"`scout add "people.Franklin.hobbies[0]=football"`"#] + +private let documentation = +""" +\u{001B}[38;5;88m + ______ ______ ___ _____ _____ _________ + .' ____ \\ .' ___ | .' `.|_ _||_ _|| _ _ | + | (___ \\_|/ .' \\_|/ .-. \\ | | | | |_/ | | \\_| + _.____`. | | | | | | | ' ' | | | + | \\____) |\\ `.___.'\\\\ `-' / \\ \\__/ / _| |_ + \\______.' `.____ .' `.___.' `.__.' |_____| + +\u{001B}[0;0m + +Here is an overview of scout subcommands: read, set, delete and add. +To get more insights for a specifi command, please type `scout doc [command]`. + +To indicate what value to target, a path should be indicated. A path is a serie of key names or indexes separated by '.' to target one value. +It looks like this "firt_key.second_key[second_index].third_key". + +You can find more examples here: \u{001B}[38;5;88mhttps://github.com/ABridoux/scout/blob/master/Playground/Commands.md\u{001B}[0;0m + +Notes +===== +- An index is indicated between squared brackets, like this [5] +- When reading a value, the output is always a string + +Given the following Json (as input stream or file) + +\(jsonInjector.inject(in: jsonExample)) + +Examples +======== + +Reading +------- +\(zshInjector.inject(in: readExamples[0])) will output "cooking" +\(zshInjector.inject(in: readExamples[1])) will output "180" +\(zshInjector.inject(in: readExamples[2])) will output last Tom hobbies: "guitar" +\(zshInjector.inject(in: readExamples[3])) will output Tom dictionary + +Setting +------- +\(zshInjector.inject(in: setExamples[0])) will change Tom first hobby from "cooking" to "basket" +\(zshInjector.inject(in: setExamples[1])) will change Arnaud's height from 180 to 160 +\(zshInjector.inject(in: setExamples[2])) will change Tom age key name from #age# to #years# +\(zshInjector.inject(in: setExamples[3])) will change Tom last hobby from "guitar" to "playing music" + +Deleting +-------- +\(zshInjector.inject(in: deleteExamples[0])) will delete Tom height +\(zshInjector.inject(in: deleteExamples[1])) will delete Tom first hobby +\(zshInjector.inject(in: deleteExamples[2])) will delete Tom last hobby + +Adding +------ +\(zshInjector.inject(in: addExamples[0])) will create a new dictionary Franklin and add a height key into it with the value 165 +\(zshInjector.inject(in: addExamples[1])) will add the hobby "Playing music" to Tom hobbies at the end of the array +\(zshInjector.inject(in: addExamples[2])) will insert the hobby "reading" to Arnaud hobbies between the hobby "video games" and "party" +\(zshInjector.inject(in: addExamples[3])) will add the hobby "surf" to Arnaud hobbies at the end of the array +\(zshInjector.inject(in: addExamples[4])) will create a new dictionary Franklin, add a hobbies array into it, and insert the value "football" in the array + +""" + + +struct DocCommand: ParsableCommand { +static let configuration = CommandConfiguration( + commandName: "doc", + abstract: "Rich examples and advanced explanations") + + @Argument(help: "Command specific documentation") + var command: Command? + + func run() throws { + if let command = command { + switch command { + case .read: print(ReadDocumentation.text) + case .set: print(SetDocumentation.text) + case .delete: print(DeleteDocumentation.text) + case .add: print(AddDocumentation.text) + } + } else { + print(documentation) + } + } +} diff --git a/Sources/ScoutCLT/Main/ScoutCommand.swift b/Sources/ScoutCLT/Main/ScoutCommand.swift new file mode 100644 index 00000000..64cd6680 --- /dev/null +++ b/Sources/ScoutCLT/Main/ScoutCommand.swift @@ -0,0 +1,82 @@ +import Foundation +import ArgumentParser +import Scout +import Lux + +private let abstract = +""" +Read and modify values in specific format file or data. Currently supported: Json, Plist and Xml. +""" + +private let discussion = +""" +To find advanced help and rich examples, please type `scout doc`. + + +Written by Alexis Bridoux. +\u{001B}[38;5;88mhttps://github.com/ABridoux/scout\u{001B}[0;0m +""" + +struct ScoutCommand: ParsableCommand { + + static let configuration = CommandConfiguration( + commandName: "scout", + abstract: abstract, + discussion: discussion, + subcommands: [ + ReadCommand.self, + SetCommand.self, + DeleteCommand.self, + AddCommand.self, + DocCommand.self, + VersionCommand.self], + defaultSubcommand: ReadCommand.self) + + static func output(_ output: String?, dataWith pathExplorer: T, verbose: Bool) throws { + if let output = output?.replacingTilde { + let fm = FileManager.default + try fm.createFile(atPath: output, contents: pathExplorer.exportData(), attributes: nil) + } + + let injector: TextInjector + + switch pathExplorer.format { + + case .json: + let jsonInjector = JSONInjector(type: .terminal) + if let colors = try ScoutCommand.getColorFile()?.json { + jsonInjector.delegate = JSONInjectorColorDelegate(colors: colors) + } + injector = jsonInjector + + case .plist: + + let plistInjector = PlistInjector(type: .terminal) + if let colors = try ScoutCommand.getColorFile()?.plist { + plistInjector.delegate = PlistInjectorColorDelegate(colors: colors) + } + injector = plistInjector + + case .xml: + let xmlInjector = XMLEnhancedInjector(type: .terminal) + if let colors = try ScoutCommand.getColorFile()?.xml { + xmlInjector.delegate = XMLInjectorColorDelegate(colors: colors) + } + injector = xmlInjector + } + + let output = try pathExplorer.exportString() + let highlightedOutput = injector.inject(in: output) + + if verbose { + print(highlightedOutput) + } + } + + static func getColorFile() throws -> ColorFile? { + let colorFileURL = FileManager.default.homeDirectoryForCurrentUser.appendingPathComponent(".scout/Colors.plist") + guard let data = try? Data(contentsOf: colorFileURL) else { return nil } + + return try PropertyListDecoder().decode(ColorFile.self, from: data) + } +} diff --git a/Sources/ScoutCLT/main.swift b/Sources/ScoutCLT/Main/main.swift similarity index 100% rename from Sources/ScoutCLT/main.swift rename to Sources/ScoutCLT/Main/main.swift diff --git a/Sources/ScoutCLT/ReadCommand.swift b/Sources/ScoutCLT/Read/ReadCommand.swift similarity index 70% rename from Sources/ScoutCLT/ReadCommand.swift rename to Sources/ScoutCLT/Read/ReadCommand.swift index a58dad53..7d8cfd43 100644 --- a/Sources/ScoutCLT/ReadCommand.swift +++ b/Sources/ScoutCLT/Read/ReadCommand.swift @@ -3,53 +3,6 @@ import Scout import Foundation import Lux -private let discussion = -""" - -Notes -===== -- If the path is invalid, the program will retrun an error -- Enclose the value with sharp signs to change the key name: #keyName# -- Enclose the value with slash signs to force the value as a string: /valueAsString/ (useless with XML as XML only has string values) -- When accessing an array value by its index, use the index -1 to access to the last element - -Examples -======== - -Given the following Json (as input stream or file): - -{ - "people": { - "Tom": { - "height": 175, - "age": 68, - "hobbies": [ - "cooking", - "guitar" - ] - }, - "Arnaud": { - "height": 180, - "age": 23, - "hobbies": [ - "video games", - "party", - "tennis" - ] - } - } -} - -Examples -======== - -- Tom first hobby: "people.Tom.hobbies[0]" will output "cooking" - -- Arnaud height: "people.Arnaud.height" will output "180" - -- Tom first hobby: "people.Tom.hobbies[-1]" will output Tom last hobby: "guitar" -""" - struct ReadCommand: ParsableCommand { // MARK: - Constants @@ -57,7 +10,7 @@ struct ReadCommand: ParsableCommand { static let configuration = CommandConfiguration( commandName: "read", abstract: "Read a value at a given path", - discussion: discussion) + discussion: "To find examples and advanced explanations, please type `scout doc read`") // MARK: - Properties @@ -100,16 +53,16 @@ struct ReadCommand: ParsableCommand { } } - func readValue(at path: Path, in data: Data) throws -> (value: String, injector: Injector) { + func readValue(at path: Path, in data: Data) throws -> (value: String, injector: TextInjector) { - var injector: Injector + var injector: TextInjector var value: String if let json = try? PathExplorerFactory.make(Json.self, from: data) { let key = try json.get(path) value = key.stringValue != "" ? key.stringValue : key.description - let jsonInjector = JSONInjector(type: .plain) + let jsonInjector = JSONInjector(type: .terminal) if let colors = try ScoutCommand.getColorFile()?.json { jsonInjector.delegate = JSONInjectorColorDelegate(colors: colors) } @@ -119,7 +72,7 @@ struct ReadCommand: ParsableCommand { let key = try plist.get(path) value = key.stringValue != "" ? key.stringValue : key.description - let plistInjector = PlistInjector(type: .plain) + let plistInjector = PlistInjector(type: .terminal) if let colors = try ScoutCommand.getColorFile()?.plist { plistInjector.delegate = PlistInjectorColorDelegate(colors: colors) } @@ -129,7 +82,7 @@ struct ReadCommand: ParsableCommand { let key = try xml.get(path) value = key.stringValue != "" ? key.stringValue : key.description - let xmlInjector = XMLEnhancedInjector(type: .plain) + let xmlInjector = XMLEnhancedInjector(type: .terminal) if let colors = try ScoutCommand.getColorFile()?.xml { xmlInjector.delegate = XMLInjectorColorDelegate(colors: colors) } diff --git a/Sources/ScoutCLT/Read/ReadDocumentation.swift b/Sources/ScoutCLT/Read/ReadDocumentation.swift new file mode 100644 index 00000000..abb26f6c --- /dev/null +++ b/Sources/ScoutCLT/Read/ReadDocumentation.swift @@ -0,0 +1,59 @@ +import ArgumentParser +import Lux + +struct ReadDocumentation: Documentation { + private static let jsonInjector = JSONInjector(type: .terminal) + private static let zshInjector = ZshInjector(type: .terminal) + + private static let jsonExample = + """ + { + "people": { + "Tom": { + "height": 175, + "age": 68, + "hobbies": [ + "cooking", + "guitar" + ] + }, + "Arnaud": { + "height": 180, + "age": 23, + "hobbies": [ + "video games", + "party", + "tennis" + ] + } + } + } + """ + + private static let examples = [(#"`scout "people.Tom.hobbies[0]"`"#, #"will output Tom first hobby "cooking")"#), + (#"`scout "people.Arnaud.height"`"#, #"will output Arnaud's height "180""#), + (#"`scout "people.Tom.hobbies[-1]"`"#, #"will output Tom last hobby: "guitar""#), + (#"`scout "people.Tom`"#, #"will output Tom dictionary"#)] + + static let text = + """ + + Read command + ============ + + Notes + ----- + - If the path is invalid, the program will return an error + - Enclose the value with sharp signs to change the key name: #keyName# + - When accessing an array value by its index, use the index -1 to access to the last element + + Examples + -------- + + JSON file + + \(jsonInjector.inject(in: jsonExample)) + + \(examplesText(from: examples)) + """ +} diff --git a/Sources/ScoutCLT/ScoutCommand.swift b/Sources/ScoutCLT/ScoutCommand.swift deleted file mode 100644 index d0e35aa0..00000000 --- a/Sources/ScoutCLT/ScoutCommand.swift +++ /dev/null @@ -1,140 +0,0 @@ -import Foundation -import ArgumentParser -import Scout -import Lux - -private let abstract = -""" -Read and modify values in specific format file or data. Currently supported: Json, Plist and Xml. - -Written by Alexis Bridoux. -https://github.com/ABridoux/scout -""" - -private let discussion = -""" -To indicate what value to target, a path should be indicated. A path is a serie of key names or indexes separated by '.' to target one value. -It looks like this "firt_key.second_key[second_index].third_key". - -Notes -===== -- An index is indicated between squared brackets, like this [5] -- When reading a value, the output is always a string - -Given the following Json (as input stream or file) - -{ - "people": { - "Tom": { - "height": 175, - "age": 68, - "hobbies": [ - "cooking", - "guitar" - ] - }, - "Arnaud": { - "height": 180, - "age": 23, - "hobbies": [ - "video games", - "party", - "tennis" - ] - } - } -} - -Examples -======== - -Reading -------- -`scout "people.Tom.hobbies[0]"` will output "cooking" -`scout "people.Arnaud.height"` will output "180" -`scout "people.Tom.hobbies[-1]"` will output last Tom hobbies: "guitar" - -Setting -------- -`scout set "people.Tom.hobbies[0]"=basket` will change Tom first hobby from "cooking" to "basket" -`scout set "people.Arnaud.height"=160` will change Arnaud's height from 180 to 160 -`scout set "people.Tom.age"=#years#` will change Tom age key name from #age# to #years# -`scout set "people.Tom.hobbies[-1]"="playing music"` will change Tom last hobby from "guitar" to "playing music" - -Deleting ---------- -`scout delete "people.Tom.height"` will delete Tom height -`scout delete "people.Tom.hobbies[0]"` will delete Tom first hobby -`scout delete "people.Tom.hobbies[-1]"` will delete Tom last hobby - -Adding ------- -`scout add "people.Franklin.height"=165` will create a new dictionary Franklin and add a height key into it with the value 165 -`scout add "people.Tom.hobbies[-1]"=Playing music"` will add the hobby "Playing music" to Tom hobbies at the end of the array -`scout add "people.Arnaud.hobbies[1]"=reading` will insert the hobby "reading" to Arnaud hobbies between the hobby "video games" and "party" -`scout add "people.Arnaud.hobbies[-1]"=surf` will add the hobby "surf" to Arnaud hobbies at the end of the array -`scout add "people.Franklin.hobbies[0]"=football` will create a new dictionary Franklin, add a hobbies array into it, and insert the value "football" in the array -""" - -struct ScoutCommand: ParsableCommand { - - static let configuration = CommandConfiguration( - commandName: "scout", - abstract: abstract, - discussion: discussion, - subcommands: [ - ReadCommand.self, - SetCommand.self, - DeleteCommand.self, - AddCommand.self, - VersionCommand.self], - defaultSubcommand: ReadCommand.self) - - static func output(_ output: String?, dataWith pathExplorer: T, verbose: Bool) throws { - if let output = output?.replacingTilde { - let fm = FileManager.default - try fm.createFile(atPath: output, contents: pathExplorer.exportData(), attributes: nil) - } - - let injector: Injector - - switch pathExplorer.format { - - case .json: - let jsonInjector = JSONInjector(type: .plain) - if let colors = try ScoutCommand.getColorFile()?.json { - jsonInjector.delegate = JSONInjectorColorDelegate(colors: colors) - } - injector = jsonInjector - - case .plist: - - let plistInjector = PlistInjector(type: .plain) - if let colors = try ScoutCommand.getColorFile()?.plist { - plistInjector.delegate = PlistInjectorColorDelegate(colors: colors) - } - injector = plistInjector - - case .xml: - let xmlInjector = XMLEnhancedInjector(type: .plain) - if let colors = try ScoutCommand.getColorFile()?.xml { - xmlInjector.delegate = XMLInjectorColorDelegate(colors: colors) - } - injector = xmlInjector - } - - let output = try pathExplorer.exportString() - let highlightedOutput = injector.inject(in: output) - - if verbose { - print(highlightedOutput) - } - } - - static func getColorFile() throws -> ColorFile? { - let colorFileURL = FileManager.default.homeDirectoryForCurrentUser.appendingPathComponent(".scout/Colors.plist") - guard let data = try? Data(contentsOf: colorFileURL) else { return nil } - - return try PropertyListDecoder().decode(ColorFile.self, from: data) - } -} diff --git a/Sources/ScoutCLT/SetCommand.swift b/Sources/ScoutCLT/Set/SetCommand.swift similarity index 58% rename from Sources/ScoutCLT/SetCommand.swift rename to Sources/ScoutCLT/Set/SetCommand.swift index f542ad78..ab374840 100644 --- a/Sources/ScoutCLT/SetCommand.swift +++ b/Sources/ScoutCLT/Set/SetCommand.swift @@ -2,63 +2,14 @@ import ArgumentParser import Scout import Foundation -private let discussion = -""" -Notes -===== -- If the path is invalid, the program will retrun an error -- Enclose the value with sharp signs to change the key name: #keyName# -- Enclose the value with slash signs to force the value as a string: /valueAsString/ (Plist, Json) -- Enclose the value with interrogative signs to force the value as a boolean: ?valueToBoolean? (Plist, Json) -- Enclose the value with tilde signs to force the value as a real: ~valueToReal~ (Plist) -- Enclose the value with chevron signs to force the value as a integer: (Plist) -- When accessing an array value by its index, use the index -1 to access to the last element - -Examples -======== - -Given the following XML (as input stream or file) - - - - - https://your-website-url.com/posts - daily - 1.0 - 2020-03-10 - - - https://your-website-url.com/posts/first-post - monthly - 0.5 - 2020-03-10 - - - -`scout set "urlset[1].changefreq"=yearly` will change the second url #changefreq# key value to "yearly" - -`scout set "urlset[0].priority"=2.0` will change the first url #priority# key value to 2.0 - -`scout set "urlset[1].changefreq"=yearly` "urlset[0].priority"=2.0` will change both he second url #changefreq# key value to "yearly" and the first url #priority# key value to 2.0 - -`scout set "urlset[-1].priority"=2.0` will change the last url #priority# key value to 2.0 - -`scout set "urlset[0].changefreq"=#frequence#` will change the first url #changefreq# key name to #frequence# - -`scout set "urlset[0].priority"=/2.0/` will change the first url #priority# key value to the String value "2.0" - -`scout set "urlset[0].priority"=~2~` will change the first url #priority# key value to the Real value 2 (Plist only) - -""" - struct SetCommand: ParsableCommand { static let configuration = CommandConfiguration( commandName: "set", abstract: "Change a value at a given path.", - discussion: discussion) + discussion: "To find examples and advanced explanations, please type `scout doc set`") @Argument(help: PathAndValue.help) - var pathsAndValues: [PathAndValue] + var pathsAndValues = [PathAndValue]() @Option(name: [.short, .customLong("input")], help: "A file path from which to read the data") var inputFilePath: String? @@ -69,8 +20,8 @@ struct SetCommand: ParsableCommand { @Option(name: [.short, .customLong("modify")], help: "Read and write the data into the same file at the given path") var modifyFilePath: String? - @Flag(name: [.short, .long], default: false, inversion: .prefixedNo, help: "Output the modified data") - var verbose: Bool + @Flag(name: [.short, .long], inversion: .prefixedNo, help: "Output the modified data") + var verbose = false func run() throws { diff --git a/Sources/ScoutCLT/Set/SetDocumentation.swift b/Sources/ScoutCLT/Set/SetDocumentation.swift new file mode 100644 index 00000000..3d1d515b --- /dev/null +++ b/Sources/ScoutCLT/Set/SetDocumentation.swift @@ -0,0 +1,66 @@ +import ArgumentParser +import Lux + +struct SetDocumentation: Documentation { + private static let xmlInjector = XMLEnhancedInjector(type: .terminal) + private static let zshInjector = ZshInjector(type: .terminal) + + private static let xmlExample = + """ + + + + https://your-website-url.com/posts + daily + 1.0 + 2020-03-10 + + + https://your-website-url.com/posts/first-post + monthly + 0.5 + 2020-03-10 + + + """ + + private static let examples = [(#"`scout set "urlset[1].changefreq=yearly"`"#, #"will change the second url #changefreq# key value to "yearly""#), + (#"`scout set "urlset[0].priority"=2.0`"#, #"will change the first url #priority# key value to 2.0"#), + (#"`scout set "urlset[1].changefreq=yearly"` "urlset[0].priority=2.0"`"#, """ + will change both the second url #changefreq# key value to "yearly" + and the first url #priority# key value to 2.0 + """), + (#"`scout set "urlset[-1].priority=2.0"`"#, #"will change the last url #priority# key value to 2.0"#), + (#"`scout set "urlset[0].changefreq=#frequence#""#, #"will change the first url #changefreq# key name to #frequence#"#), + (#"`scout set "urlset[0].priority=/2.0/"`"#, #"will change the first url #priority# key value to the String value "2.0""#), + (#"`scout set "urlset[0].priority=~2~"`"#, #"will change the first url #priority# key value to the Real value 2 (Plist only)"#)] + + static let text = + """ + + Set command + ============ + + Notes + ----- + - You can set multiple values in one command. + - Specify the \(zshInjector.delegate.inject(.optionNameOrFlag, in: .terminal, "-v")) flag to see the modified data + + - If the path is invalid, the program will return an error + - Enclose the value with sharp signs to change the key name: #keyName# + - Enclose the value with slash signs to force the value as a string: /valueAsString/ (Plist, Json) + - Enclose the value with interrogative signs to force the value as a boolean: ?valueToBoolean? (Plist, Json) + - Enclose the value with tilde signs to force the value as a real: ~valueToReal~ (Plist) + - Enclose the value with chevron signs to force the value as a integer: (Plist) + - When accessing an array value by its index, use the index -1 to access to the last element + + Examples + -------- + + Xml file + + \(xmlInjector.inject(in: xmlExample)) + + \(examplesText(from: examples)) + """ +}