{"id":21585365,"url":"https://github.com/objecthub/swift-commandlinekit","last_synced_at":"2025-07-11T02:41:36.033Z","repository":{"id":63919470,"uuid":"135120937","full_name":"objecthub/swift-commandlinekit","owner":"objecthub","description":"Framework supporting the development of command-line tools in Swift on macOS and Linux. The framework supports managing command-line arguments, provides lightweight functions to deal with escape sequences, and defines an API for reading strings from the terminal.","archived":false,"fork":false,"pushed_at":"2023-10-26T21:25:36.000Z","size":169,"stargazers_count":52,"open_issues_count":0,"forks_count":5,"subscribers_count":5,"default_branch":"master","last_synced_at":"2024-10-30T00:32:20.198Z","etag":null,"topics":["cli","command-line-arguments","command-line-tool","escape-sequences","flags","readline","swift"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/objecthub.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-05-28T06:49:39.000Z","updated_at":"2024-08-28T05:15:09.000Z","dependencies_parsed_at":"2023-02-17T23:31:06.063Z","dependency_job_id":"609541a5-339d-4f9e-b660-ecd96e0d7e1c","html_url":"https://github.com/objecthub/swift-commandlinekit","commit_stats":{"total_commits":29,"total_committers":2,"mean_commits":14.5,"dds":0.1724137931034483,"last_synced_commit":"c66994d36c654fdd4a27863e5f8aaadc101099eb"},"previous_names":[],"tags_count":13,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/objecthub%2Fswift-commandlinekit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/objecthub%2Fswift-commandlinekit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/objecthub%2Fswift-commandlinekit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/objecthub%2Fswift-commandlinekit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/objecthub","download_url":"https://codeload.github.com/objecthub/swift-commandlinekit/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248288346,"owners_count":21078903,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cli","command-line-arguments","command-line-tool","escape-sequences","flags","readline","swift"],"created_at":"2024-11-24T15:10:23.234Z","updated_at":"2025-04-10T20:06:56.257Z","avatar_url":"https://github.com/objecthub.png","language":"Swift","readme":"# Swift CommandLineKit\n\n[![Platform: macOS](https://img.shields.io/badge/Platform-macOS-blue.svg?style=flat)](https://developer.apple.com/osx/)\n[![Platform: Linux](https://img.shields.io/badge/Platform-Linux-blue.svg?style=flat)](https://www.ubuntu.com/)\n[![Language: Swift 5.8](https://img.shields.io/badge/Language-Swift%205.8-green.svg?style=flat)](https://developer.apple.com/swift/)\n[![IDE: Xcode 14](https://img.shields.io/badge/IDE-Xcode%2014-orange.svg?style=flat)](https://developer.apple.com/xcode/)\n[![Carthage: compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)\n[![License: BSD](https://img.shields.io/badge/License-BSD-lightgrey.svg?style=flat)](https://developers.google.com/open-source/licenses/bsd)\n\n## Overview\n\nThis is a library supporting the development of command-line tools in\nthe programming language Swift on macOS. It also compiles under Linux.\nThe library provides the following functionality:\n\n   - Management of command-line arguments,\n   - Usage of escape sequences on terminals, and\n   - Reading strings on terminals using a lineread-inspired implementation\n     based on the library [Linenoise-Swift](https://github.com/andybest/linenoise-swift),\n     but supporting unicode input, multiple lines, and styled text.\n\n## Command-line arguments\n\n### Basics\n\nCommandLineKit handles command-line arguments with the following protocol:\n\n   1. A new [Flags](https://github.com/objecthub/swift-commandlinekit/blob/master/Sources/CommandLineKit/Flags.swift)\n      object gets created either for the system-provided command-line arguments or for a\n      custom sequence of arguments.\n   2. For every flag, a [Flag](https://github.com/objecthub/swift-commandlinekit/blob/master/Sources/CommandLineKit/Flag.swift)\n       object is being created and registered in the `Flags` object.\n   3. Once all flag objects are declared and registered, the command-line gets parsed. After parsing\n      is complete, the flag objects can be used to access the extracted options and arguments.\n\nCommandLineKit defines different types of\n[Flag](https://github.com/objecthub/swift-commandlinekit/blob/master/Sources/CommandLineKit/Flag.swift)\nsubclasses for handling _options_ (i.e. flags without\nparameters) and _arguments_ (i.e. flags with parameters). Arguments are either _singleton arguments_ (i.e. they\nhave exactly one value) or they are _repeated arguments_ (i.e. they have many values). Arguments are\nparameterized with a type which defines how to parse values. The framework natively supports _int_,\n_double_, _string_, and _enum_ types, which means that in practice, just using the built-in flag classes\nare almost always sufficient. Nevertheless,\n[the framework is extensible](https://github.com/objecthub/swift-commandlinekit/tree/master/Sources/CommandLineKit)\nand supports arbitrary argument types.\n\nA flag is identified by a _short name_ character and a _long name_ string. At least one of the two needs to be\ndefined. For instance, the \"help\" option could be defined by the short name \"h\" and the long name \"help\".\nOn the command-line, a user could either use `-h` or `--help` to refer to this option; i.e. short names are\nprefixed with a single dash, long names are prefixed with a double dash.\n\nAn argument is a parameterized flag. The parameters follow directly the flag identifier (typically separated by\na space). For instance, an integer argument with long name \"size\" could be defined as: `--size 64`. If the\nargument is repeated, then multiple parameters may follow the flag identifier, as in this\nexample: `--size 2 4 8 16`. The sequence is terminated by either the end of the command-line arguments,\nanother flag, or the terminator \"---\". All command-line arguments following the terminator are not being parsed\nand are returned in the `parameters` field of the `Flags` object.\n\n### Programmatic API\n\nHere is an [example](https://github.com/objecthub/swift-lispkit/blob/master/Sources/LispKitRepl/main.swift)\nfrom the [LispKit](https://github.com/objecthub/swift-lispkit) project. It uses factory methods (like `flags.string`,\n`flags.int`, `flags.option`, `flags.strings`, etc.) provided by the\n[Flags](https://github.com/objecthub/swift-commandlinekit/blob/master/Sources/CommandLineKit/Flags.swift)\nclass to create and register individual flags.\n\n```swift\n// Create a new flags object for the system-provided command-line arguments\nvar flags = Flags()\n\n// Define the various flags\nlet filePaths  = flags.strings(\"f\", \"filepath\",\n                               description: \"Adds file path in which programs are searched for.\")\nlet libPaths   = flags.strings(\"l\", \"libpath\",\n                               description: \"Adds file path in which libraries are searched for.\")\nlet heapSize   = flags.int(\"x\", \"heapsize\",\n                           description: \"Initial capacity of the heap\", value: 1000)\nlet importLibs = flags.strings(\"i\", \"import\",\n                               description: \"Imports library automatically after startup.\")\nlet prelude    = flags.string(\"p\", \"prelude\",\n                              description: \"Path to prelude file which gets executed after \" +\n                                           \"loading all provided libraries.\")\nlet prompt     = flags.string(\"r\", \"prompt\",\n                              description: \"String used as prompt in REPL.\", value: \"\u003e \")\nlet quiet      = flags.option(\"q\", \"quiet\",\n                              description: \"In quiet mode, optional messages are not printed.\")\nlet help       = flags.option(\"h\", \"help\",\n                              description: \"Show description of usage and options of this tools.\")\n\n// Parse the command-line arguments and return error message if parsing fails\nif let failure = flags.parsingFailure() {\n  print(failure)\n  exit(1)\n}\n```\n\nThe framework supports printing the supported options via the `Flags.usageDescription` function. For the\ncommand-line flags as defined above, this function returns the following usage description:\n\n```\nusage: LispKitRepl [\u003coption\u003e ...] [---] [\u003cprogram\u003e \u003carg\u003e ...]\noptions:\n  -f, --filepath \u003cvalue\u003e ...\n      Adds file path in which programs are searched for.\n  -l, --libpath \u003cvalue\u003e ...\n      Adds file path in which libraries are searched for.\n  -h, --heapsize \u003cvalue\u003e\n      Initial capacity of the heap\n  -i, --import \u003cvalue\u003e ...\n      Imports library automatically after startup.\n  -p, --prelude \u003cvalue\u003e\n      Path to prelude file which gets executed after loading all provided libraries.\n  -r, --prompt \u003cvalue\u003e\n      String used as prompt in REPL.\n  -q, --quiet\n      In quiet mode, optional messages are not printed.\n  -h, --help\n      Show description of usage and options of this tools.\n```\n\nCommand-line tools can inspect whether a flag was set via the `Flag.wasSet` field. For flags with\nparameters, the parameters are stored in the `Flag.value` field. The type of this field is dependent on the\nflag type. For repeated flags, an array is used.\n\nHere is an example how the flags defined by the code snippet above could be used:\n\n```swift\n// If help flag was provided, print usage description and exit tool\nif help.wasSet {\n  print(flags.usageDescription(usageName: TextStyle.bold.properties.apply(to: \"usage:\"),\n                               synopsis: \"[\u003coption\u003e ...] [---] [\u003cprogram\u003e \u003carg\u003e ...]\",\n                               usageStyle: TextProperties.none,\n                               optionsName: TextStyle.bold.properties.apply(to: \"options:\"),\n                               flagStyle: TextStyle.italic.properties),\n        terminator: \"\")\n  exit(0)\n}\n...\n// Define how optional messages and errors are printed\nfunc printOpt(_ message: String) {\n  if !quiet.wasSet {\n    print(message)\n  }\n}\n...\n// Set heap size (assuming 1234 is the default if the flag is not set)\nvirtualMachine.setHeapSize(heapSize.value ?? 1234)\n...\n// Register all file paths\nfor path in filePaths.value {\n  virtualMachine.fileHandler.register(path)\n}\n...\n// Load prelude file if it was provided via flag `prelude`\nif let file = prelude.value {\n  virtualMachine.load(file)\n}\n```\n\n### Declarative API\n\nThe code below illustrates how to combine the `Command` protocol with property wrappers\ndeclaring the various command-line flags. The whole lifecycle of a command-line tool that\nis declared like this will be managed automatically. After flags are being parsed, either\nmethods `run()` or `fail(with:)` are being called (depending on whether flag parsing\nsucceeds or fails).\n\n```swift\n@main struct LispKitRepl: Command {\n  @CommandArguments(short: \"f\", description: \"Adds file path in which programs are searched for.\")\n  var filePath: [String]\n  @CommandArguments(short: \"l\", description: \"Adds file path in which libraries are searched for.\")\n  var libPaths: [String]\n  @CommandArgument(short: \"x\", description: \"Initial capacity of the heap\")\n  var heapSize: Int = 1234\n  ...\n  @CommandOption(short: \"h\", description: \"Show description of usage and options of this tools.\")\n  var help: Bool\n  @CommandParameters // Inject the unparsed parameters\n  var params: [String]\n  @CommandFlags // Inject the flags object\n  var flags: Flags\n  \n  mutating func fail(with reason: String) throws {\n    print(reason)\n    exit(1)\n  }\n  \n  mutating func run() throws {\n    // If help flag was provided, print usage description and exit tool\n    if help {\n      print(flags.usageDescription(usageName: TextStyle.bold.properties.apply(to: \"usage:\"),\n                                   synopsis: \"[\u003coption\u003e ...] [---] [\u003cprogram\u003e \u003carg\u003e ...]\",\n                                   usageStyle: TextProperties.none,\n                                   optionsName: TextStyle.bold.properties.apply(to: \"options:\"),\n                                   flagStyle: TextStyle.italic.properties),\n            terminator: \"\")\n      exit(0)\n    }\n    ...\n    // Define how optional messages and errors are printed\n    func printOpt(_ message: String) {\n      if !quiet {\n        print(message)\n      }\n    }\n    ...\n    // Set heap size\n    virtualMachine.setHeapSize(heapSize)\n    ...\n    // Register all file paths\n    for path in filePaths {\n      virtualMachine.fileHandler.register(path)\n    }\n    ...\n    // Load prelude file if it was provided via flag `prelude`\n    if let file = prelude {\n      virtualMachine.load(file)\n    }\n  }\n}\n```\n\n## Text style and colors\n\nCommandLineKit provides a\n[TextProperties](https://github.com/objecthub/swift-commandlinekit/blob/master/Sources/CommandLineKit/TextProperties.swift)\nstructure for bundling a text color, a background color, and a text style in a single object. Text properties can be\nmerged with the `with(:)` functions and applied to a string with the `apply(to:)` function.\n\nIndividual enumerations for\n[TextColor](https://github.com/objecthub/swift-commandlinekit/blob/master/Sources/CommandLineKit/TextColor.swift),\n[BackgroundColor](https://github.com/objecthub/swift-commandlinekit/blob/master/Sources/CommandLineKit/BackgroundColor.swift), and\n[TextStyle](https://github.com/objecthub/swift-commandlinekit/blob/master/Sources/CommandLineKit/TextStyle.swift)\ndefine the individual properties.\n\n## Reading strings\n\nCommandLineKit includes a significantly improved version of the \"readline\" API originally defined by the library\n[Linenoise-Swift](https://github.com/andybest/linenoise-swift). It supports unicode text, multi-line text entry, and\nstyled text. It supports all the existing features such as _advanced keyboard support_, _history_,\n_text completion_, and _hints_.\n\nThe following code illustrates the usage of the\n[LineReader](https://github.com/objecthub/swift-commandlinekit/blob/master/Sources/CommandLineKit/LineReader.swift) API:\n\n```swift\nif let ln = LineReader() {\n  ln.setCompletionCallback { currentBuffer in\n    let completions = [\n      \"Hello!\",\n      \"Hello Google\",\n      \"Scheme is awesome!\"\n    ]\n    return completions.filter { $0.hasPrefix(currentBuffer) }\n  }\n  ln.setHintsCallback { currentBuffer in\n    let hints = [\n      \"Foo\",\n      \"Lorem Ipsum\",\n      \"Scheme is awesome!\"\n    ]\n    let filtered = hints.filter { $0.hasPrefix(currentBuffer) }\n    if let hint = filtered.first {\n      let hintText = String(hint.dropFirst(currentBuffer.count))\n      return (hintText, TextColor.grey.properties)\n    } else {\n      return nil\n    }\n  }\n  print(\"Type 'exit' to quit\")\n  var done = false\n  while !done {\n    do {\n      let output = try ln.readLine(prompt: \"\u003e \",\n                                   maxCount: 200,\n                                   strippingNewline: true,\n                                   promptProperties: TextProperties(.green, nil, .bold),\n                                   readProperties: TextProperties(.blue, nil),\n                                   parenProperties: TextProperties(.red, nil, .bold))\n      print(\"Entered: \\(output)\")\n      ln.addHistory(output)\n      if output == \"exit\" {\n        break\n      }\n    } catch LineReaderError.CTRLC {\n      print(\"\\nCaptured CTRL+C. Quitting.\")\n      done = true\n    } catch {\n      print(error)\n    }\n  }\n}\n```\n\n## Requirements\n\n- [Xcode 14](https://developer.apple.com/xcode/)\n- [Swift 5.8](https://developer.apple.com/swift/)\n- [Carthage](https://github.com/Carthage/Carthage)\n- [Swift Package Manager](https://swift.org/package-manager/)\n\n## Copyright\n\nAuthor: Matthias Zenger (\u003cmatthias@objecthub.com\u003e)  \nCopyright © 2018-2023 Google LLC.  \n_Please note: This is not an official Google product._\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fobjecthub%2Fswift-commandlinekit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fobjecthub%2Fswift-commandlinekit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fobjecthub%2Fswift-commandlinekit/lists"}