| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279 |
- //===----------------------------------------------------------------------===//
- //
- // This source file is part of the Swift Argument Parser open source project
- //
- // Copyright (c) 2020 Apple Inc. and the Swift project authors
- // Licensed under Apache License v2.0 with Runtime Library Exception
- //
- // See https://swift.org/LICENSE.txt for license information
- //
- //===----------------------------------------------------------------------===//
- import ArgumentParser
- @main
- struct Math: ParsableCommand {
- // Customize your command's help and subcommands by implementing the
- // `configuration` property.
- static let configuration = CommandConfiguration(
- // Optional abstracts and discussions are used for help output.
- abstract: "A utility for performing maths.",
- // Commands can define a version for automatic '--version' support.
- version: "1.0.0",
- // Pass an array to `subcommands` to set up a nested tree of subcommands.
- // With language support for type-level introspection, this could be
- // provided by automatically finding nested `ParsableCommand` types.
- subcommands: [Add.self, Multiply.self, Statistics.self],
- // A default subcommand, when provided, is automatically selected if a
- // subcommand is not given on the command line.
- defaultSubcommand: Add.self)
- }
- struct Options: ParsableArguments {
- @Flag(
- name: [.customLong("hex-output"), .customShort("x")],
- help: "Use hexadecimal notation for the result.")
- var hexadecimalOutput = false
- @Argument(
- help: "A group of integers to operate on.")
- var values: [Int] = []
- }
- extension Math {
- static func format(_ result: Int, usingHex: Bool) -> String {
- usingHex
- ? String(result, radix: 16)
- : String(result)
- }
- struct Add: ParsableCommand {
- static let configuration =
- CommandConfiguration(abstract: "Print the sum of the values.")
- // The `@OptionGroup` attribute includes the flags, options, and
- // arguments defined by another `ParsableArguments` type.
- @OptionGroup var options: Options
- mutating func run() {
- let result = options.values.reduce(0, +)
- print(format(result, usingHex: options.hexadecimalOutput))
- }
- }
- struct Multiply: ParsableCommand {
- static let configuration = CommandConfiguration(
- abstract: "Print the product of the values.",
- aliases: ["mul"])
- @OptionGroup var options: Options
- mutating func run() {
- let result = options.values.reduce(1, *)
- print(format(result, usingHex: options.hexadecimalOutput))
- }
- }
- }
- // In practice, these nested types could be broken out into different files.
- extension Math {
- struct Statistics: ParsableCommand {
- static let configuration = CommandConfiguration(
- // Command names are automatically generated from the type name
- // by default; you can specify an override here.
- commandName: "stats",
- abstract: "Calculate descriptive statistics.",
- subcommands: [Average.self, StandardDeviation.self, Quantiles.self])
- }
- }
- extension Math.Statistics {
- struct Average: ParsableCommand {
- static let configuration = CommandConfiguration(
- abstract: "Print the average of the values.",
- version: "1.5.0-alpha",
- aliases: ["avg"])
- enum Kind: String, ExpressibleByArgument, CaseIterable {
- case mean, median, mode
- }
- @Option(help: "The kind of average to provide.")
- var kind: Kind = .mean
- @Argument(help: "A group of floating-point values to operate on.")
- var values: [Double] = []
- func validate() throws {
- if (kind == .median || kind == .mode) && values.isEmpty {
- throw ValidationError(
- "Please provide at least one value to calculate the \(kind).")
- }
- }
- func calculateMean() -> Double {
- guard !values.isEmpty else {
- return 0
- }
- let sum = values.reduce(0, +)
- return sum / Double(values.count)
- }
- func calculateMedian() -> Double {
- guard !values.isEmpty else {
- return 0
- }
- let sorted = values.sorted()
- let mid = sorted.count / 2
- if sorted.count.isMultiple(of: 2) {
- return (sorted[mid - 1] + sorted[mid]) / 2
- } else {
- return sorted[mid]
- }
- }
- func calculateMode() -> [Double] {
- guard !values.isEmpty else {
- return []
- }
- let grouped = Dictionary(grouping: values, by: { $0 })
- let highestFrequency = grouped.lazy.map { $0.value.count }.max() ?? 0
- return grouped.filter { _, v in v.count == highestFrequency }
- .map { k, _ in k }
- }
- mutating func run() {
- switch kind {
- case .mean:
- print(calculateMean())
- case .median:
- print(calculateMedian())
- case .mode:
- let result = calculateMode()
- .map(String.init(describing:))
- .joined(separator: " ")
- print(result)
- }
- }
- }
- struct StandardDeviation: ParsableCommand {
- static let configuration = CommandConfiguration(
- commandName: "stdev",
- abstract: "Print the standard deviation of the values.")
- @Argument(help: "A group of floating-point values to operate on.")
- var values: [Double] = []
- mutating func run() {
- if values.isEmpty {
- print(0.0)
- } else {
- let sum = values.reduce(0, +)
- let mean = sum / Double(values.count)
- let squaredErrors =
- values
- .map { $0 - mean }
- .map { $0 * $0 }
- let variance = squaredErrors.reduce(0, +) / Double(values.count)
- let result = variance.squareRoot()
- print(result)
- }
- }
- }
- struct Quantiles: ParsableCommand {
- static let configuration = CommandConfiguration(
- abstract: "Print the quantiles of the values (TBD).")
- @Argument(
- completion: .list(["alphabet", "alligator", "branch", "braggart"]))
- var oneOfFour: String?
- @Argument(
- completion: .custom { _, _, _ in
- ["alabaster", "breakfast", "crunch", "crash"]
- }
- )
- var customArg: String?
- @available(
- *, deprecated,
- message: "Deprecated use of custom completion for @Argument"
- )
- @Argument(
- completion: .custom { _ in ["alabaster", "breakfast", "crunch", "crash"] }
- )
- var customDeprecatedArg: String?
- @Argument(help: "A group of floating-point values to operate on.")
- var values: [Double] = []
- // These args and the validation method are for testing exit codes:
- @Flag(help: .hidden)
- var testSuccessExitCode = false
- @Flag(help: .hidden)
- var testFailureExitCode = false
- @Flag(help: .hidden)
- var testValidationExitCode = false
- @Option(help: .hidden)
- var testCustomExitCode: Int32?
- // These args are for testing custom completion scripts:
- @Option(completion: .file(extensions: ["txt", "md"]))
- var file: String?
- @Option(completion: .directory)
- var directory: String?
- @Option(
- completion: .shellCommand("head -100 '/usr/share/dict/words' | tail -50")
- )
- var shell: String?
- @Option(completion: .custom(customCompletion))
- var custom: String?
- @available(
- *, deprecated, message: "Deprecated use of custom completion for @Option"
- )
- @Option(completion: .custom(customDeprecatedCompletion))
- var customDeprecated: String?
- func validate() throws {
- if testSuccessExitCode {
- throw ExitCode.success
- }
- if testFailureExitCode {
- throw ExitCode.failure
- }
- if testValidationExitCode {
- throw ExitCode.validationFailure
- }
- if let exitCode = testCustomExitCode {
- throw ExitCode(exitCode)
- }
- }
- }
- }
- func customCompletion(_ s: [String], _: Int, _: String) -> [String] {
- (s.last ?? "").starts(with: "a")
- ? ["aardvark", "aaaaalbert"]
- : ["hello", "helicopter", "heliotrope"]
- }
- func customDeprecatedCompletion(_ s: [String]) -> [String] {
- (s.last ?? "").starts(with: "a")
- ? ["aardvark", "aaaaalbert"]
- : ["hello", "helicopter", "heliotrope"]
- }
|