{"id":18697374,"url":"https://github.com/hejki/commandlineapi","last_synced_at":"2025-11-08T16:30:34.164Z","repository":{"id":66547571,"uuid":"220664415","full_name":"Hejki/CommandLineAPI","owner":"Hejki","description":"API for command line tools in Swift.","archived":false,"fork":false,"pushed_at":"2019-12-05T18:07:59.000Z","size":675,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2024-12-28T03:44:28.864Z","etag":null,"topics":["cli","command-line","filesystem","framework","swift","swift-script"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Hejki.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"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":"2019-11-09T15:44:57.000Z","updated_at":"2023-02-13T19:30:38.000Z","dependencies_parsed_at":"2023-03-01T12:30:46.252Z","dependency_job_id":null,"html_url":"https://github.com/Hejki/CommandLineAPI","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Hejki%2FCommandLineAPI","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Hejki%2FCommandLineAPI/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Hejki%2FCommandLineAPI/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Hejki%2FCommandLineAPI/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Hejki","download_url":"https://codeload.github.com/Hejki/CommandLineAPI/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":239558928,"owners_count":19658929,"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","filesystem","framework","swift","swift-script"],"created_at":"2024-11-07T11:24:04.922Z","updated_at":"2025-11-08T16:30:34.110Z","avatar_url":"https://github.com/Hejki.png","language":"Swift","readme":"\n# CommandLineAPI\n![badge-swift][] ![badge-platforms][] [![badge-spm][]][spm-link] [![badge-ci][]][ci] [![badge-docs][]][docs] [![badge-licence][]][licence]\n\nThe library that can help you create a command line applications. This library is inspired by [Swiftline][swiftline], [Path.swift][pathswift] and [ShellOut][shellout].\n\n## Features\n\n* [Path struct](#path) for handling files and directories.\n* [String styles](#string-styles) which helps styling the strings before print them to the terminal.\n* [Prompt functions](#prompt-functions) for process input and output in the terminal.\n* [Functions for run](#run) an external commands and read its standard output.\n* Read and write [environment](#env) variables.\n* Parse command line [arguments](#args).\n\n## Path\n\nPath is a simple way for accessing, reading and writing files and directories.\n\nCrate a Path instance:\n```swift\n// Path are always absolute\nlet path = try Path(\"/Users/hejki/tools/README\") // absolute path from root /\nlet pathFromURL = try Path(url: URL(fileURLWithPath: \"/Users/hejki/tools/README\"))\n\ntry Path(\"~/Downloads\") // path relative to current user home\ntry Path(\"~tom/Downloads\") // path relative to another user home\ntry Path(\"Package.swift\") // path relative to current working directory\nPath(\".stool/config.yaml\", relativeTo: .home) // relative path to another path\n```\n\nShortcut paths for system directories:\n```swift\nPath.root      // File system root\nPath.home      // Current user's home\nPath.current   // Current working directory\nPath.temporary // Path to temporary directory\n```\n\nPath components and path chaining:\n```swift\n// Paths can be joined with appending function or + operator\nlet readme = Path.home.appending(\"tools\") + \"README.md\"\n\nreadme.url // URL representation\nreadme.path // Absolute path string\nreadme.pathComponents // [\"Users\", \"hejki\", \"tools\", \"README.md\"]\nreadme.extension // \"md\"\nreadme.basename // \"README.md\"\nreadme.basenameWithoutExtension // \"README\"\nreadme.parent // Path(\"/Users/hejki/tools\")\nreadme.path(relativeTo: Path.home + \"Downloads\") // \"../tools/README.md\"\n```\n\nIterate over the directory content:\n```swift\n// Sequence with a shallow search of the specified directory, without hidden files\nfor path in Path.current.children { \n    print(path)\n}\n\n// Use recursive to deep search and/or includingHidden for hidden files\nPath.current.recursive.includingHidden.forEach { print($0) }\n```\n\nAccess to file and directory attributes:\n```swift\nreadme.exist // `true` if this path represents an actual filesystem entry\nreadme.type // The type of filesystem entry. Can be .file, .directory, .symlink and .pipe\n\nlet attributes = readme.attributes\n\nattributes.creationDate     // item's creation date\nattributes.modificationDate // item's last modify date\nattributes.extensionHidden  // item's extension is hidden\nattributes.userName         // item's owner user name\nattributes.groupName        // item's owner group name\nattributes.permissions      // item's permissions\nattributes.size             // item's size in bytes (read-only)\n```\n\nCreate, copy, move and delete filesystem items:\n```swift\nlet downloads = Path(\"Downloads/stool\", relativeTo: .home)\nlet documents = try Path.home.createDirectory(\"Projects\") // Creates a directory\n\ntry downloads.touch() // Creates an empty file, or updates its modification time\n    .copy(to: documents, overwrite: true) // Copy that file to documents directory\n    .rename(to: \"stool.todo\") // Rename that file\ntry downloads.delete(useTrash: false) // Delete original file, or move to trash\n```\n\nRead and write filesystem item's content:\n```swift\n// Read content functions\nlet string = String(contentsOf: readme) // File content as String\nlet data = Data(contentsOf: readme) // File content as Data\n\n// Write functions on Data and String\ntry \"Hi!\".write(to: path, append: false, atomically: false, encoding: .utf8)\ntry Data().write(to: path, append: false, atomically: false)\n\n// Write functions on Path\ntry path.write(text: \"README\", append: false, encoding: .utf8)\ntry path.write(data: Data(), append: false)\n```\n\n## String Styles\n\nString styles helps styling the strings before printing them to the terminal. You can change the text color, the text background color and the text style. String styles works in string interpolation and for implementations of `StringProtocol`.\n\nChange style of string part using string interpolation extension:\n```swift\nprint(\"Result is \\(result.exitCode, styled: .fgRed)\")\nprint(\"Result is \\(result.exitCode, styled: .fgRed, .italic)\") // multiple styles at once\n```\n\nThe types that conforming `StringProtocol` can use styles directly:\n```swift\nprint(\"Init...\".styled(.bgMagenta, .bold, .fg(r: 12, g: 42, b: 0)))\n```\n\nThe string style interpolation can be globaly disabled by setting `CLI.enableStringStyles` to `false`, the interpolation is enabled by default.\n\n## Prompt Functions\n\n### Print\n\nFunctions for print strings to standard output/error or for read input from standard input.\n```swift\nCLI.print(\"Print text to console without \\n at end.\")\nCLI.println(\"Print text to console with terminating newline.\")\nCLI.print(error: \"Print error to console without \\n at end.\")\nCLI.println(error: \"Print error to console with terminating newline.\")\n\n// read user input\nlet fileName = CLI.read()\n```\n\nHandler for this functions can be changed by setting `CLI.prompt` variable. This can be handle for tests, for example:\n```swift\nclass TestPromptHandler: PromptHandler {\n    func print(_ string: String) {\n        XCTAssertEqual(\"test print\", string)\n    }\n    ...\n}\n\nCLI.prompt = TestPromptHandler()\n```\n\n### Ask\n\nAsk presents the user with a prompt and waits for the user input.\n```swift\nlet toolName = CLI.ask(\"Enter tool name: \")\n```\n\nTypes that confirms ExpressibleByStringArgument can be returned from ask.\n```swift\nlet timeout = CLI.ask(\"Enter timeout: \", type: Int.self)\n\n// If user enters something that cannot be converted to Int, a new prompt is displayed,\n// this prompt will keep displaying until the user enters an Int:\n// $ Enter timeout: No\n// $ Please enter a valid Int.\n// \u003e 2.3\n// $ Please enter a valid Int.\n// \u003e 2\n```\n\nPrompt can be customized througt ask options.\n```swift\n// to specify default value which is used if the user only press Enter key\nCLI.ask(\"Output path [/tmp]?\\n \", options: .default(\"/tmp\"))\n\n// use .confirm() if you require value confirmation\nCLI.ask(\"Remove file? \", type: Bool.self, options: .confirm())\n.confirm(message: \"Use this value?\\n \") // to specify custom message\n.confirm(block: { \"Use \\($0) value? \" }) // to specify custom message with entered value\n\n// add some .validator() to validate an entered value\nlet positive = AskOption\u003cInt\u003e.validator(\"Value must be positive.\") { $0 \u003e 0 }\nlet maxVal = AskOption\u003cInt\u003e.validator(\"Max value is 100.\") { $0 \u003c= 100 }\n\nCLI.ask(\"Requested value: \", options: positive, maxVal)\n\n// you can use some predefined validators\n.notEmptyValidator() // for Strings\n.rangeValidator(0...5) // for Comparable instances\n\n// options can be combined together\nlet i: Int = CLI.ask(\"Value: \", options: .default(3), .confirm(), positive)\n```\n\n### Choose\n\nChoose is used to prompt the user to select an item between several possible items.\n```swift\nlet user = CLI.choose(\"Select user: \", choices: [\"hejki\", \"guest\"])\n\n// This will print:\n// $ 1. hejki\n// $ 2. guest\n// $ Select user: \n```\n\nThe user must choose one item from the list by entering its number. If the user enters a wrong input, a prompt will keep showing until the user makes a correct choice.\n\nChoices can be supplied with dictionary, to display a different value than you later get as a result.\n```swift\nlet difficulty = CLI.choose(\"Select difficulty: \", choices: [\n    \"Easy\": 0, \"Hard\": 1, \"Extreme\": 10\n])\n```\n\n## Run\n\nRun provides a quick way to run an external command and read its standard output.\n\n```swift\nlet files = try CLI.run(\"ls -al\")\nprint(files)\n\n// Complex example with pipes different commands together\ntry CLI.run(\"ps aux | grep php | awk '{print $2}' | xargs kill\")\n```\n\nIn case of error, `run` will automatically read `stderr` and format it into a typed Swift error:\n```swift\ndo {\n    try CLI.run(\"swift\", \"build\")\n} catch let error as CLI.CommandExecutionError {\n    print(error.terminationStatus) // Prints termination status code\n    print(error.stderr) // Prints error output\n    print(error.stdout) // Prints standard output\n}\n```\n\nEach command can be run with one of available executors. The executor defines how to run command.\n\n* `.default` executor is dedicated to non-interactive, short running tasks. This executor runs the command and consumes all outputs. Command standard output will be returned after task execution. This executor is default for `CLI.run` functions.\n* `.dummy` executor that only prints command to `CLI.println`. You can specify returned stdout string, stderr and exitCode.\n* `.interactive` executor runs command with redirected standard/error outputs to system outputs. This executor can handle user's inputs from system standard input. The command output will not be recorded.\n\nFor more complex executions use `Command` type directly:\n```swift\nlet command = CLI.Command(\n    [\"swift\", \"build\"],\n    executor: .interactive,\n    workingDirectory: Path.home + \"/Projects/CommandLineAPI\",\n    environment: [\"PATH\": \"/usr/bin/\"]\n)\n\nlet result = try command.execute()\n```\n\nCommands are executed within context of some shell. If you want change the default `zsh` shell for command execution, see the documentation of variable `CLI.processBuilder` for more informations.\n\n## Env\n\nRead and write the environment variables passed to the script:\n```swift\n// Array with all envirnoment keys\nCLI.env.keys\n\n// Get environment variable\nCLI.env[\"PATH\"]\n\n// Set environment variable\nCLI.env[\"PATH\"] = \"~/bin\"\n```\n\n## Args\n\nReturns the arguments passed to the script.\n```swift\n// For example when calling `stool init -s default -q -- tool`\n// CLI.args contains following results\n\nCLI.args.all == [\"stool\", \"init\", \"-s\", \"default\", \"-q\", \"--\", \"tool\"]\nCLI.args.command == \"stool\"\nCLI.args.flags == [\"s\": \"default\", \"q\": \"\"]\nCLI.args.parameters == [\"init\", \"tool\"]\n```\n\n## Instalation\n\nTo install CommandLineAPI for use in a Swift Package Manager powered tool, add CommandLineAPI as a dependency to your `Package.swift` file. For more information, please see the [Swift Package Manager documentation](https://github.com/apple/swift-package-manager/tree/master/Documentation).\n```swift\n.package(url: \"https://github.com/Hejki/CommandLineAPI\", from: \"0.3.0\")\n```\n\n## Alternatives\n\n#### for path handling\n* [Path.swift][pathswift] by Max Howell\n* [Pathos](https://github.com/dduan/Pathos) by Daniel Duan\n* [PathKit](https://github.com/kylef/PathKit) by Kyle Fuller\n* [Files](https://github.com/JohnSundell/Files) by John Sundell\n* [Utility](https://github.com/apple/swift-package-manager) by Apple\n\n#### for command line tools\n\n* [Swiftline][swiftline] by Omar Abdelhafith\n* [ShellOut][shellout] by John Sundell\n* [Commander](https://github.com/kylef/Commander) by Kyle Fuller \n\n## Questions or feedback?\n\nFeel free to [open an issue][new-issue], or find me [@hejki on Twitter](https://twitter.com/hejki).\n\n[badge-swift]: https://img.shields.io/badge/Swift-5.1-orange.svg?logo=swift?style=flat\n[badge-spm]: https://img.shields.io/badge/spm-compatible-brightgreen.svg?style=flat\n[spm-link]: https://swift.org/package-manager\n[badge-platforms]: https://img.shields.io/badge/platform-mac+linux-lightgray.svg?style=flat\n[badge-ci]: https://travis-ci.com/Hejki/CommandLineAPI.svg\n[ci]: https://travis-ci.com/Hejki/CommandLineAPI\n[badge-licence]: https://img.shields.io/badge/license-MIT-black.svg?style=flat\n[licence]: https://github.com/Hejki/CommandLineAPI/blob/master/LICENSE\n[docs]: https://hejki.github.io/CommandLineAPI\n[badge-docs]: https://hejki.github.io/CommandLineAPI/badge.svg?sanitize=true\n[new-issue]: https://github.com/Hejki/CommandLineAPI/issues/new\n[swiftline]: https://github.com/nsomar/Swiftline\n[shellout]: https://github.com/JohnSundell/ShellOut\n[pathswift]: https://github.com/mxcl/Path.swift","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhejki%2Fcommandlineapi","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhejki%2Fcommandlineapi","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhejki%2Fcommandlineapi/lists"}