{"id":17822182,"url":"https://github.com/samuelschlesinger/commander-cli","last_synced_at":"2025-03-18T12:31:40.872Z","repository":{"id":55123347,"uuid":"233656583","full_name":"SamuelSchlesinger/commander-cli","owner":"SamuelSchlesinger","description":"A simple library I wrote to allow me to quickly and easily construct command line interfaces.","archived":false,"fork":false,"pushed_at":"2023-01-20T23:17:32.000Z","size":71,"stargazers_count":28,"open_issues_count":5,"forks_count":2,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-02-28T09:21:07.021Z","etag":null,"topics":["cli","hacktoberfest"],"latest_commit_sha":null,"homepage":"","language":"Haskell","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/SamuelSchlesinger.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-01-13T17:46:12.000Z","updated_at":"2024-11-15T23:54:45.000Z","dependencies_parsed_at":"2023-02-12T06:31:50.043Z","dependency_job_id":null,"html_url":"https://github.com/SamuelSchlesinger/commander-cli","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SamuelSchlesinger%2Fcommander-cli","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SamuelSchlesinger%2Fcommander-cli/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SamuelSchlesinger%2Fcommander-cli/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SamuelSchlesinger%2Fcommander-cli/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/SamuelSchlesinger","download_url":"https://codeload.github.com/SamuelSchlesinger/commander-cli/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243926065,"owners_count":20369911,"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","hacktoberfest"],"created_at":"2024-10-27T17:36:31.173Z","updated_at":"2025-03-18T12:31:40.865Z","avatar_url":"https://github.com/SamuelSchlesinger.png","language":"Haskell","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Commander CLI\n\n[![Hackage](https://img.shields.io/hackage/v/commander-cli.svg)](https://hackage.haskell.org/package/commander-cli)\n[![Build Status](https://travis-ci.org/SamuelSchlesinger/commander-cli.svg?branch=master)](https://travis-ci.org/SamuelSchlesinger/commander-cli)\n\nThis library is meant to allow Haskell programmers to quickly and easily construct\ncommand line interfaces with decent documentation.\n\nOne extension I use in these examples is `-XTypeApplications`. This extension allows us to use the `@param`\nsyntax to apply an type-level argument explicitly to a function with a `forall x ...` in its\ntype. This is as opposed to implicitly applying type-level arguments, as we do when we write\n`fmap (+ 1) [1, 2, 3]`, applying the type `[]` to `fmap`. It's because of type inference in Haskell\nthat we don't always have to apply our types explicitly, as many other languages force you to do using\na syntax typically like `fmap\u003c[], Int\u003e (+ 1) [1, 2, 3]`.`.\n\nWe can go to the command line and try out this example:\n\n```\n\u003e :set -XTypeApplications\n\u003e :t fmap @[]\nfmap @[] :: (a -\u003e b) -\u003e [a] -\u003e [b]\n\u003e :t fmap @[] @Int\nfmap @[] @Int :: (Int -\u003e b) -\u003e [Int] -\u003e [b]\n\u003e :t fmap @[] @Int @Bool\nfmap @[] @Int @Bool :: (Int -\u003e Bool) -\u003e [Int] -\u003e [Bool]\n```\n\nThe API of `commander-cli` allows for very profitable usage of type\napplications, because the description of our command line program will live\nat the type level. \n\nAnother extension we will use is `-XDataKinds`, which is only for the ability\nto use strings, or the kind `Symbol`, at the type level. Kinds are just the\ntype of types, and so `-XDataKinds` allows us to have kinds which are actually\ndata in their own right, like lists, strings, numbers, and custom Haskell\ndata types. For us, we will use strings to represent the documentation of our\nprogram at the type level, as well as the names of options, flags, and arguments\nwe want to parse. This allows us to generate documentation programs simply from\nthe type signature of the CLI program we build.\n\nOur first example will show a basic command line application,\ncomplete with help messages that display reasonable messages to the user.\n\n```haskell\nmain = command_\n  . toplevel @\"argument-taker\"\n  . arg @\"example-argument\" $ \\arg -\u003e\n    raw $ do\n      putStrLn arg\n```\n\nWhen you run this program with `argument-taker help`, you will see:\n\n```\nusage:\nname: argument-taker\n|\n+- subprogram: help\n|\n`- argument: example-argument :: [Char]\n```\n\nThe meaning of this documentation is that every path in the tree is a unique command.\nThe one we've used is the help command. If we run this program with `argument-taker hello`\nwe will see:\n\n```\nhello\n```\n\nNaturally, we might want to expand on the documentation of this program, as its not quite\nobvious enough what it does.\n\n```haskell\nmain = command_\n  . toplevel @\"argument-taker\"\n  . arg @\"example-argument\" $ \\arg -\u003e\n    description @\"Takes the argument and prints it\"\n  . raw $ do\n      putStrLn arg\n```\n\nPrinting out the documentation again with `argument-taker help`, we see:\n\n```haskell\nusage:\nname: argument-taker\n|\n+- subprogram: help\n|\n`- argument: example-argument :: [Char]\n   |\n   `- description: Takes the argument and prints it\n```\n\nOkay, so we can expand the documentation. But what if I have an option to pass to the same program? Well, we can pass an option like so:\n\n```haskell\nmain = command_\n  . toplevel @\"argument-taker\"\n  . optDef @\"m\" @\"mode\" \"Print\" $ \\mode -\u003e\n    arg @\"example-argument\" $ \\arg -\u003e\n    description @\"Takes the argument and prints it or not, depending on the mode\" \n  . raw $ do\n      if mode == \"Print\" then putStrLn arg else pure ()\n```\n\nNow, when we run `argument-taker help` we will see:\n\n```\nusage:\nname: argument-taker\n|\n+- subprogram: help\n|\n`- option: -m \u003cmode :: [Char]\u003e\n   |\n   `- argument: example-argument :: [Char]\n      |\n      `- description: Takes the argument and prints it or not, depending on the mode\n```\n\nOkay! So we can now create programs which take arguments and options, so what\nelse do we want in a command line program? Flags! Lets add a flag to our\nexample program:\n\n```haskell\nmain = command_\n  . toplevel @\"argument-taker\"\n  . optDef @\"m\" @\"mode\" \"Print\" $ \\mode -\u003e\n    arg @\"example-argument\" $ \\arg -\u003e\n    flag @\"loud\" $ \\loud -\u003e\n    description @\"Takes the argument and prints it or not, depending on the mode and possibly loudly\" \n  . raw $ do\n      let msg = if loud then map toUpper arg \u003c\u003e \"!\" else arg\n      if mode == \"Print\" then putStrLn msg else pure ()\n```\n\nRunning this with `argument-taker help`, we see:\n\n```\nusage:\nname: argument-taker\n|\n+- subprogram: help\n|\n`- option: -m \u003cmode :: [Char]\u003e\n   |\n   `- argument: example-argument :: [Char]\n      |\n      `- flag: ~loud\n         |\n         `- description: Takes the argument and prints it or not, depending on the mode and possibly loudly\n```\n\nOkay, so we've added all of the normal command line things, but we haven't yet shown how to add a new command\nto our program, so lets do that. To do this, we can write:\n\n```haskell\nmain = command_\n  . toplevel @\"argument-taker\"\n  $ defaultProgram \u003c+\u003e sub @\"shriek\" (raw (putStrLn \"AHHHHH!!\"))\n  where\n  defaultProgram = \n      optDef @\"m\" @\"mode\" \"Print\" $ \\mode -\u003e\n      arg @\"example-argument\" $ \\arg -\u003e\n      flag @\"loud\" $ \\loud -\u003e\n      description @\"Takes the argument and prints it or not, depending on the mode and possibly loudly\" \n    . raw $ do\n        let msg = if loud then map toUpper arg \u003c\u003e \"!\" else arg\n        if mode == \"Print\" then putStrLn msg else pure ()\n```\n\nRunning this program with `argument-taker help`, we can see the docs yet again:\n\n```\nusage:\nname: argument-taker\n|\n+- subprogram: help\n|\n+- option: -m \u003cmode :: [Char]\u003e\n|  |\n|  `- argument: example-argument :: [Char]\n|     |\n|     `- flag: ~loud\n|        |\n|        `- description: Takes the argument and prints it or not, depending on the mode and possibly loudly\n|\n`- subprogram: shriek\n```\n\nAwesome! So we have now shown how to use the primitives of CLI programs, as well as how to\nadd new subprograms. One more thing I would like to show that is different from normal CLI\nlibraries is that I added the ability to automatically search for environment variables and\npass them to your program. I just liked this, as sometimes when I use a CLI program I forget\nthis or that environment variable, and the documentation generation makes this self documenting\nin commander-cli. We can add this to our program by writing:\n\n```haskell\nmain = command_\n  . toplevel @\"argument-taker\"\n  $ env @\"ARGUMENT_TAKER_DIRECTORY\" \\argumentTakerDirectory -\u003e\n      defaultProgram argumentTakerDirectory\n  \u003c+\u003e sub @\"shriek\" (raw $ do\n        setCurrentDirectory argumentTakerDirectory \n        putStrLn \"AHHH!\"\n      )\n  where\n  defaultProgram argumentTakerDirectory = \n      optDef @\"m\" @\"mode\" \"Print\" $ \\mode -\u003e\n      arg @\"example-argument\" $ \\arg -\u003e\n      flag @\"loud\" $ \\loud -\u003e\n      description @\"Takes the argument and prints it or not, depending on the mode and possibly loudly\" \n    . raw $ do\n        setCurrentDirectory argumentTakerDirectory\n        let msg = if loud then map toUpper arg \u003c\u003e \"!\" else arg\n        if mode == \"Print\" then putStrLn msg else pure ()\n```\n\nNow, we will see `argument-taker help` as:\n\n```\nusage:\nname: argument-taker\n|\n+- subprogram: help\n|\n`- required env: ARGUMENT_TAKER_DIRECTORY :: [Char]\n   |\n   +- option: -m \u003cmode :: [Char]\u003e\n   |  |\n   |  `- argument: example-argument :: [Char]\n   |     |\n   |     `- flag: ~loud\n   |        |\n   |        `- description: Takes the argument and prints it or not, depending on the mode and possibly loudly\n   |\n   `- subprogram: shriek\n```\n\nWe can see that it documents the usage of this environment variable in a\nreasonable way, but its not clear where exactly what it does exactly. First,\nyou might think to use the `description` combinator, but it isn't exactly made\nfor describing an input, but for documenting a path of a program. We can fix this\nusing the `annotated` combinator, which was made for describing inputs to our\nprogram:\n\n```haskell\nmain :: IO ()\nmain = command_\n  . toplevel @\"argument-taker\"\n  . annotated @\"the directory we will go to for the program\"\n  $ env @\"ARGUMENT_TAKER_DIRECTORY\" \\argumentTakerDirectory -\u003e\n      defaultProgram argumentTakerDirectory\n  \u003c+\u003e sub @\"shriek\" (raw $ do\n        setCurrentDirectory argumentTakerDirectory \n        putStrLn \"AHHH!\"\n      )\n  where\n  defaultProgram argumentTakerDirectory = \n      optDef @\"m\" @\"mode\" \"Print\" $ \\mode -\u003e\n      arg @\"example-argument\" $ \\arg -\u003e\n      flag @\"loud\" $ \\loud -\u003e\n      description @\"Takes the argument and prints it or not, depending on the mode\" \n    . raw $ do\n        setCurrentDirectory argumentTakerDirectory\n        let msg = if loud then map toUpper arg \u003c\u003e \"!\" else arg\n        if mode == \"Print\" then putStrLn msg else pure ()\n```\n\nRunning `argument-taker help` will result in:\n\n```\nusage:\nname: argument-taker\n|\n+- subprogram: help\n|\n`- required env: ARGUMENT_TAKER_DIRECTORY :: [Char], the directory we will go to for the program\n   |\n   +- option: -m \u003cmode :: [Char]\u003e\n   |  |\n   |  `- argument: example-argument :: [Char]\n   |     |\n   |     `- flag: ~loud\n   |        |\n   |        `- description: Takes the argument and prints it or not, depending on the mode\n   |\n   `- subprogram: shriek\n```\n\n## Design\n\nThe library is based around the following classes:\n\n```haskell\nclass Unrender r where\n  unrender :: Text -\u003e Maybe r\n```\n\nThis class is what you will use to define the parsing of a type from text and\ncan use any parsing library or whatever you want. Next, we have the class\n\n```haskell\nclass HasProgram p where\n  data ProgramT p m a\n  run :: ProgramT p IO a -\u003e CommanderT State IO a\n  hoist :: (forall x. m x -\u003e n x) -\u003e ProgramT p m a -\u003e ProgramT p n a\n  documentation :: Forest String\n```\n\nInstances of this class will define a syntactic element, a new instance of the\ndata family ProgramT, as well as its semantics in terms of the CommanderT monad,\nwhich is something like a free backtracking monad. Users should not have to make\ninstances of this class, as the common CLI elements are already defined as\ninstances. Of course, you can if you want to, and it can be profitable to do so.\n\n## Similar Projects\n\n### Recommended Alternatives\n- [cmdargs](https://hackage.haskell.org/package/cmdargs) Command line argument processing\n- [docopt](https://hackage.haskell.org/package/docopt) A command-line interface parser that will make you smile\n- [getopt-generics](https://hackage.haskell.org/package/getopt-generics) Create command line interfaces with ease\n- [optparse-applicative](https://hackage.haskell.org/package/optparse-applicative) Utilities and combinators for parsing command line options\n\n### Other CLI Packages\n- [ReadArgs](https://hackage.haskell.org/package/ReadArgs) Simple command line argument parsing\n- [argparser](https://hackage.haskell.org/package/argparser) Command line parsing framework for console applications\n- [cli-extras](https://hackage.haskell.org/package/cli-extras) Miscellaneous utilities for building and working with command line interfaces\n- [cli](https://hackage.haskell.org/package/cli) CLI\n- [cmdtheline](https://hackage.haskell.org/package/cmdtheline) Declarative command-line option parsing and documentation library.\n- [configifier](https://hackage.haskell.org/package/configifier) parser for config files, shell variables, command line args.\n- [configuration-tools](https://hackage.haskell.org/package/configuration-tools) Tools for specifying and parsing configurations\n- [console-program](https://hackage.haskell.org/package/console-program) Interpret the command line and a config file as commands and options\n- [hflags](https://hackage.haskell.org/package/hflags) Command line flag parser, very similar to Google's gflags\n- [multiarg](https://hackage.haskell.org/package/multiarg) Command lines for options that take multiple arguments\n- [options](https://hackage.haskell.org/package/options) A powerful and easy-to-use command-line option parser.\n- [parseargs](https://hackage.haskell.org/package/parseargs) Parse command-line arguments\n- [quickterm](https://hackage.haskell.org/package/quickterm) An interface for describing and executing terminal applications\n- [shell-utility](https://hackage.haskell.org/package/shell-utility) Utility functions for writing command-line programs\n- [symantic-cli](https://hackage.haskell.org/package/symantic-cli) Symantics for parsing and documenting a CLI\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsamuelschlesinger%2Fcommander-cli","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsamuelschlesinger%2Fcommander-cli","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsamuelschlesinger%2Fcommander-cli/lists"}