{"id":16964982,"url":"https://github.com/propensive/exoskeleton","last_synced_at":"2025-03-22T14:31:01.418Z","repository":{"id":41080180,"uuid":"86938815","full_name":"propensive/exoskeleton","owner":"propensive","description":"Interfaces for interacting with the shell from Scala","archived":false,"fork":false,"pushed_at":"2025-02-11T21:16:48.000Z","size":4872,"stargazers_count":14,"open_issues_count":6,"forks_count":2,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-03-18T11:52:02.815Z","etag":null,"topics":["bash","scala","shell","zsh"],"latest_commit_sha":null,"homepage":"https://soundness.dev/exoskeleton/","language":"Scala","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/propensive.png","metadata":{"files":{"readme":".github/readme.md","changelog":null,"contributing":".github/contributing.md","funding":null,"license":null,"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":"2017-04-01T19:54:01.000Z","updated_at":"2025-02-11T21:16:52.000Z","dependencies_parsed_at":"2023-10-10T19:13:39.754Z","dependency_job_id":"cb441003-6d82-472b-bf59-87d7a105a760","html_url":"https://github.com/propensive/exoskeleton","commit_stats":null,"previous_names":["propensive/testaceous"],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/propensive%2Fexoskeleton","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/propensive%2Fexoskeleton/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/propensive%2Fexoskeleton/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/propensive%2Fexoskeleton/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/propensive","download_url":"https://codeload.github.com/propensive/exoskeleton/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244971783,"owners_count":20540855,"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":["bash","scala","shell","zsh"],"created_at":"2024-10-13T23:44:46.321Z","updated_at":"2025-03-22T14:31:01.411Z","avatar_url":"https://github.com/propensive.png","language":"Scala","funding_links":[],"categories":[],"sub_categories":[],"readme":"[\u003cimg alt=\"GitHub Workflow\" src=\"https://img.shields.io/github/actions/workflow/status/propensive/exoskeleton/main.yml?style=for-the-badge\" height=\"24\"\u003e](https://github.com/propensive/exoskeleton/actions)\n[\u003cimg src=\"https://img.shields.io/discord/633198088311537684?color=8899f7\u0026label=DISCORD\u0026style=for-the-badge\" height=\"24\"\u003e](https://discord.com/invite/MBUrkTgMnA)\n\u003cimg src=\"/doc/images/github.png\" valign=\"middle\"\u003e\n\n# Exoskeleton\n\n__Interfaces for interacting with the shell__\n\nExoskeleton is a Scala library to make it easy to write command-line applications, in particular those which\nneed argument parsing, and tab completions, making it trivial to write interactive command-line applications in\nScala. Tab completions are automatically provided for [`bash`](https://www.gnu.org/software/bash/),\n[`zsh`](http://zsh.sourceforge.net/) and [`fish`](https://fishshell.com/).\n\n## Features\n\n- POSIX-style parameter parsing\n- unified programmatic tab-completions for `bash`, `zsh` and `fish`\n- easy to specify conditional tab-completions\n- functional API for application entry points\n- simple stream access to POSIX interrupts (signals)\n- easy installation of completion scripts for each shell\n\n\n## Availability\n\n\n\n\n\n\n\n## Getting Started\n\nExoskeleton has a modular design, so you can use as much or as little of it as\nyou like. The modules include:\n - `args` for parsing command-line arguments\n - `core` to provide an enhanced entry-point into an application (requires\n   `args`)\n - `completions` to provide tab-completions for an application (requires\n   `core`)\n\n### Application Entry Point\n\nIn its most trivial usage, Exoskeleton provides an `application` wrapper to\nimplement a main method:\n\n```scala\nimport exoskeleton.*\nimport anticipation.*, turbulence.*, rudiments.*, gossamer.*\n\n@main\ndef myapp(args: IArray[Text]): Unit = application(args):\n  Out.println(t\"Hello world!\")\n  ExitStatus.Ok\n```\n\nPackaging this as a fat JAR, with its `Main-Class` specified as `myapp` will\nproduce an executable JAR which can be invoked with,\n```bash\njava -jar myapp.jar\n```\n\nThe body of the `application` method makes certain contextual values available.\nFor example, `arguments` provides access to the application's arguments. Note\nthat the return value is `ExitStatus.Ok`, though different exit statuses can be\nspecified with `ExitStatus.Fail(1)` or higher numbers; but the value must be\nspecified.\n\n### Argument Parsing\n\nWe make the following distinction between _arguments_ and _parameters_: the\nword \"arguments\" is used to describe a linear sequence of textual values, while\nwe use \"parameters\" to describe the interpreted meaning of arguments. So, for\nexample, the arguments of `grep -rA4 pattern` would be {`-rA4`, `pattern`}\nwhile its parameters would be some representation of {\"search recursively\", \"4\nlines of trailing content\", \"search for pattern\"}.\n\nExoskeleton interprets arguments as parameters. Different interpreters may be\nused, though most users will want to use the POSIX parameters interpreter,\n`parameterInterpretation.posix` to get a `PosixParameters`. With the contextual\n`parameterInterpretation.posix` in scope, the `parameters` method can be used\nto get an instance of `PosixParameters` instance.\n\nAn instance of `PosixParameters` distinguishes between _flags_, which are\narguments beginning with `-` or `--`, and _operands_—all other arguments, and\nconsists of the following:\n1. a list of positional operands appearing before the first flag, often\n   considered as \"subcommands\"\n2. a map from flags to a list of operands (being all the arguments up to the\n   next flag)\n3. a list of unparsed positional arguments which appear after a `--` argument\n\nWhile each argument is clearly a string, each is represented by the type\n`Argument` which encapsulates not just the argument's `Text` value, but its\nposition on the command line. (The first argument after the command is numbered\n`0`, whereas in scripting languages it's typically `1`.) Retaining the argument\nnumber turns out to be useful later, when providing completions.\n\n### Tab-completions\n\n#### The State of Completions\n\nThe ability to press the `tab` key with the cursor at some position in an\nincomplete command line is an extremely useful feature of modern command-line\napplications, and offers users a highly interactive experience, allowing them\nto discover new features, preview possible flags and operand values and avoid\ntypographic errors by having values completed without typing.\n\nTab completions are widely available in Bash, ZSH and Fish, though the support\navailable in each shell differs greatly. The support in bash is most\nsimplistic, with no more information than the completion text itself provided\nto the user, whereas Fish an ZSH both allow a description to be associated with\neach completion value.\n\nEach shell implements completions differently, so most command-line\napplications which provide completions do so through a different script for\neach shell, often written by different users and potentially having subtle\ndifferences from each other or from the command itself.\n\nCompletion scripts also differ in how dynamic their completions are. Most try\nto duplicate the behavior of the application by hardcoding subcommands, flags\nand some completion values, or try to approximate operand values by calling\nother shell commands. Many go further to ensure that flags remain self\nconsistent; for example, if the `-t` flag and the `-x` flags cannot be used\ntogether, then `-x` would not be suggested as a completion of `-` if `-t`\nalready appears on the command line.\n\nThis work is admirable, but it's difficult to maintain, and liable to mistakes\nor compromises.\n\n#### Exoskeleton's Completions\n\nExoskeleton takes the view that the application itself should be the only\nsource of truth for the completions of arguments to a command, and that to the\ngreatest extent possible, the same code that interprets a command when it is\ninvoked should be used to evaluate what completions should be provided.\n\nThis makes it possible for the complex logic of determining completions to be\nmoved out of scripts written in Bash, and have it evaluated entirely in the\napplication itself—as long as the application has access to all the information\nthat would have been available to the completions script.\n\nThis greatly simplifies the functionality of completions scripts for Bash, ZSH\nand Fish. Exoskeleton provides a _generic_ completion script for each, which is\ngreatly simplified. It's sufficient for each script to capture the current\n(incomplete) arguments of the command line, the position of the cursor, and the\ntype of shell, and to invoke the application in \"completions mode\", passing it\nthis information so that it can produce output which can provide the\ncompletions for just the _current_ argument.\n\nWithin the Scala code, a further innovation is employed to reuse as much logic\nas possible from interpreting the arguments in producing the completions, while\nmaking an important distinction between the part of the program which runs when\ncompletions are requested, and the part which runs when the user presses\n`enter` and the command is invoked.\n\nAn application with tab-completions must delimit the side-effecting part of the\nprogram in a special `execute` block which will be run for an invocation, but\nnot for tab-completions. However, code which processes the parameters will be\nexecuted in both cases. This ensures that requesting completions will not cause\nunwanted side-effects, but offers an opportunity to have completions inferred\nfrom the \"prelude\" code which runs in both modes. This is described in detail\nin the next section.\n\nIn the prelude, the act of checking for a flag is sufficient to infer that flag\nas a possible completion for any \"free\" argument. Checking the value of a\nparameter may, of course, be performed only conditionally in a branch which is\ndependent on other parameters, and this would cause that parameter to be\nproposed only conditionally too, on _precisely the same condition_!\n\nFor example, imagine a command which has a `-v` flag to make it produce output,\nand a `--verbosity` flag which can be used _only_ if `-v` is present. The\nprelude would check for `-v`, and if it's present, would check for\n`--verbosity`. The whole expression could return a value such as\n`Maybe[Verbosity]` which would determine the verbosity level for the rest of\nthe program.\n\nIf a user pressed `tab` after a single `-` character at the end of the command\nline, it would invoke the prelude, checking first for `-v`, and (as a\nside-effect) adding it to the list of flag completions. If there is no `-v`\nalready on the command-line, then it would be proposed for the completion of\n`-`. But if there is a `-v` already present, the branch which checks for\n`--verbosity` would also run, and (as a side-effect) it would be added to the\nset of known flags, and would be proposed as a completion of `-`. (Since it\nonly makes sense for `-v` to appear once on the command-line, it would _not_ be\nproposed as a completion in this instance.)\n\nThis simple example illustrates how the exact same logic could be used in both\nscenarios. But that logic can be as complex as necessary, and depend on any\nnumber of variables. But in all cases, completions will be guaranteed to mirror\nexecution.\n\n#### Using Tab Completions\n\nA standalone application or daemon will not process tab completions by default,\nbut they can be \"turned on\" by importing the contextual value:\n```scala\nimport executives.completions\n```\n\nHaving this in scope when `application` of `daemon` is invoked will replace the\ndefault _executive_ with the _completions executive_. The role of the executive\nis to determine the return type of that invocation's execution block, as well\nas the types of contextual values that are available within the block.\n\nThis distinction is important for keeping the simple æsthetics of the\nentry-point API, while imposing necessary requirements on the structure of the\nimplementation.\n\nExecutives are powerful idea, but in practice, only one difference is crucial:\nwith the standard executive, the return value must be an `ExitStatus` instance,\nwhile for the completions executive, the return value must be an instance of\n`Execution`, which can be constructed with a delimited `execute` block; and an\n`execute` block must\nreturn an `ExitStatus`.\n\nHere is an illustration of the difference in code. Compare the standard\nexecutive,\n\n```scala\n@main\ndef myapp(): Unit = daemon:\n  // side-effecting code goes here\n  ExitStatus.Ok\n```\nwith the completions executive:\n```scala\nimport executives.completions\n\n@main\ndef myapp(): Unit = daemon:\n  // parameter processing code goes here\n  execute:\n    // side-effecting code goes here\n    ExitStatus.Ok\n```\nBecause the only way to construct an `Execution` is with an `execute` block,\nand an `execute` block can only be written by returning an `ExitStatus` value,\nthe structure of a completions application (standalone or daemon) will always\nfollow this structure. It's not easy to forget the execution block or an exit\nstatus because doing so will produce a complie error.\n\nThe completions executive also applies some restrictions in the \"prelude\" code\nwhich do not exist for the standard executive: standard output is not\npermitted, except inside an `execute` block. Under the completions executive, a\n`Stdio` instance, which is required by calls to `Out.println` and\n`Err.println`, is not made available in the context of `daemon` or\n`application`, since there is nowhere for output to be sent during evaluation\nof completions. The `execute` block provides an `Stdio`, though.\n\nOne further contextual value is provided in an `execute` block: an instance of\n`Effectful`. This type is defined in Exoskeleton, but is not used anywhere. But\nany user-defined method can require it as a `using` parameter, and it will\nensure that that method can _only_ be called from within an `execute` block,\nsince the only way to obtain an `Effectful` instance is from within an\n`execute` block. This makes it very difficult to invoke an \"effectful\" method\nelsewhere, by accident.\n\n\n## Status\n\nExoskeleton is classified as __fledgling__. For reference, Soundness projects are\ncategorized into one of the following five stability levels:\n\n- _embryonic_: for experimental or demonstrative purposes only, without any guarantees of longevity\n- _fledgling_: of proven utility, seeking contributions, but liable to significant redesigns\n- _maturescent_: major design decisions broady settled, seeking probatory adoption and refinement\n- _dependable_: production-ready, subject to controlled ongoing maintenance and enhancement; tagged as version `1.0.0` or later\n- _adamantine_: proven, reliable and production-ready, with no further breaking changes ever anticipated\n\nProjects at any stability level, even _embryonic_ projects, can still be used,\nas long as caution is taken to avoid a mismatch between the project's stability\nlevel and the required stability and maintainability of your own project.\n\nExoskeleton is designed to be _small_. Its entire source code currently consists\nof 777 lines of code.\n\n## Building\n\nExoskeleton will ultimately be built by Fury, when it is published. In the\nmeantime, two possibilities are offered, however they are acknowledged to be\nfragile, inadequately tested, and unsuitable for anything more than\nexperimentation. They are provided only for the necessity of providing _some_\nanswer to the question, \"how can I try Exoskeleton?\".\n\n1. *Copy the sources into your own project*\n   \n   Read the `fury` file in the repository root to understand Exoskeleton's build\n   structure, dependencies and source location; the file format should be short\n   and quite intuitive. Copy the sources into a source directory in your own\n   project, then repeat (recursively) for each of the dependencies.\n\n   The sources are compiled against the latest nightly release of Scala 3.\n   There should be no problem to compile the project together with all of its\n   dependencies in a single compilation.\n\n2. *Build with [Wrath](https://github.com/propensive/wrath/)*\n\n   Wrath is a bootstrapping script for building Exoskeleton and other projects in\n   the absence of a fully-featured build tool. It is designed to read the `fury`\n   file in the project directory, and produce a collection of JAR files which can\n   be added to a classpath, by compiling the project and all of its dependencies,\n   including the Scala compiler itself.\n   \n   Download the latest version of\n   [`wrath`](https://github.com/propensive/wrath/releases/latest), make it\n   executable, and add it to your path, for example by copying it to\n   `/usr/local/bin/`.\n\n   Clone this repository inside an empty directory, so that the build can\n   safely make clones of repositories it depends on as _peers_ of `exoskeleton`.\n   Run `wrath -F` in the repository root. This will download and compile the\n   latest version of Scala, as well as all of Exoskeleton's dependencies.\n\n   If the build was successful, the compiled JAR files can be found in the\n   `.wrath/dist` directory.\n\n## Contributing\n\nContributors to Exoskeleton are welcome and encouraged. New contributors may like\nto look for issues marked\n[beginner](https://github.com/propensive/exoskeleton/labels/beginner).\n\nWe suggest that all contributors read the [Contributing\nGuide](/contributing.md) to make the process of contributing to Exoskeleton\neasier.\n\nPlease __do not__ contact project maintainers privately with questions unless\nthere is a good reason to keep them private. While it can be tempting to\nrepsond to such questions, private answers cannot be shared with a wider\naudience, and it can result in duplication of effort.\n\n## Author\n\nExoskeleton was designed and developed by Jon Pretty, and commercial support and\ntraining on all aspects of Scala 3 is available from [Propensive\nO\u0026Uuml;](https://propensive.com/).\n\n\n\n## Name\n\nExoskeleton is a library for interacting with shells, which are their exterior skeletons—or Exoskeletons.\n\nIn general, Soundness project names are always chosen with some rationale,\nhowever it is usually frivolous. Each name is chosen for more for its\n_uniqueness_ and _intrigue_ than its concision or catchiness, and there is no\nbias towards names with positive or \"nice\" meanings—since many of the libraries\nperform some quite unpleasant tasks.\n\nNames should be English words, though many are obscure or archaic, and it\nshould be noted how willingly English adopts foreign words. Names are generally\nof Greek or Latin origin, and have often arrived in English via a romance\nlanguage.\n\n## Logo\n\nThe logo shows a simplistic and imaginary arthropod, with a pair of wings on each side; and an exoskeleton.\n\n## License\n\nExoskeleton is copyright \u0026copy; 2025 Jon Pretty \u0026 Propensive O\u0026Uuml;, and\nis made available under the [Apache 2.0 License](/license.md).\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpropensive%2Fexoskeleton","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpropensive%2Fexoskeleton","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpropensive%2Fexoskeleton/lists"}