{"id":13804686,"url":"https://github.com/devnote-dev/cling","last_synced_at":"2025-04-15T22:50:54.636Z","repository":{"id":129810950,"uuid":"524840537","full_name":"devnote-dev/cling","owner":"devnote-dev","description":"A modular, non-macro-based command line interface library","archived":false,"fork":false,"pushed_at":"2024-09-28T16:46:05.000Z","size":283,"stargazers_count":25,"open_issues_count":2,"forks_count":2,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-03-29T02:12:12.706Z","etag":null,"topics":["cli","cling","command-line","crystal","crystal-lang"],"latest_commit_sha":null,"homepage":"https://crystaldoc.info/github/devnote-dev/cling/v3.0.0/index.html","language":"Crystal","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mpl-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/devnote-dev.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":"2022-08-15T03:12:27.000Z","updated_at":"2025-02-05T11:35:41.000Z","dependencies_parsed_at":"2023-11-18T01:00:34.384Z","dependency_job_id":"9e1a9152-a1fa-4af0-9757-c99f05129180","html_url":"https://github.com/devnote-dev/cling","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devnote-dev%2Fcling","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devnote-dev%2Fcling/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devnote-dev%2Fcling/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devnote-dev%2Fcling/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/devnote-dev","download_url":"https://codeload.github.com/devnote-dev/cling/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249167434,"owners_count":21223505,"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","cling","command-line","crystal","crystal-lang"],"created_at":"2024-08-04T01:00:52.592Z","updated_at":"2025-04-15T22:50:54.619Z","avatar_url":"https://github.com/devnote-dev.png","language":"Crystal","funding_links":[],"categories":["CLI Builders"],"sub_categories":[],"readme":"# Cling\n\nBased on [spf13/cobra](https://github.com/spf13/cobra), Cling is built to be almost entirely modular, giving you absolute control over almost everything without the need for embedded macros - there isn't even a default help command or flag!\n\n## Contents\n\n- [Installation](#installation)\n- [Basic Usage](#basic-usage)\n- [Commands](#commands)\n- [Arguments and Options](#arguments-and-options)\n- [Customising](#customising)\n- [Extensions](#extensions)\n- [Motivation](#motivation)\n- [Projects using Cling](#projects-using-cling)\n- [Contributing](#contributing)\n- [Contributors](#contributors)\n\n## Installation\n\n1. Add the dependency to your `shard.yml`:\n\n```yaml\ndependencies:\n  cling:\n    github: devnote-dev/cling\n    version: \"\u003e= 3.0.0\"\n```\n\n2. Run `shards install`\n\n## Basic Usage\n\n```crystal\nrequire \"cling\"\n\nclass MainCommand \u003c Cling::Command\n  def setup : Nil\n    @name = \"greet\"\n    @description = \"Greets a person\"\n    add_argument \"name\", description: \"the name of the person to greet\", required: true\n    add_option 'c', \"caps\", description: \"greet with capitals\"\n    add_option 'h', \"help\", description: \"sends help information\"\n  end\n\n  def pre_run(arguments : Cling::Arguments, options : Cling::Options) : Bool\n    if options.has? \"help\"\n      puts help_template # generated using Cling::Formatter\n\n      false\n    else\n      true\n    end\n  end\n\n  def run(arguments : Cling::Arguments, options : Cling::Options) : Nil\n    message = \"Hello, #{arguments.get(\"name\")}!\"\n\n    if options.has? \"caps\"\n      puts message.upcase\n    else\n      puts message\n    end\n  end\nend\n\nmain = MainCommand.new\nmain.execute ARGV\n```\n\n```\n$ crystal greet.cr -h\nUsage:\n        greet \u003carguments\u003e [options]\n\nArguments:\n        name    the name of the person to greet (required)\n\nOptions:\n        -c, --caps  greet with capitals\n        -h, --help  sends help information\n\n$ crystal greet.cr Dev\nHello, Dev!\n\n$ crystal greet.cr -c Dev\nHELLO, DEV!\n```\n\n## Commands\n\nBy default, the `Command` class is initialized with almost no values. All information about the command must be defined in the `setup` method.\n\n```crystal\nclass MainCommand \u003c Cling::Command\n  def setup : Nil\n    @name = \"greet\"\n    @description = \"Greets a person\"\n    # defines an argument\n    add_argument \"name\", description: \"the name of the person to greet\", required: true\n    # defines a flag option\n    add_option 'c', \"caps\", description: \"greet with capitals\"\n    add_option 'h', \"help\", description: \"sends help information\"\n  end\nend\n```\n\n\u003e [!NOTE]\n\u003e See [command.cr](/src/cling/command.cr) for the full list of options.\n\nCommands can also contain children, or subcommands:\n\n```crystal\nrequire \"cling\"\n# import our subcommand here\nrequire \"./welcome_command\"\n\n# using the `MainCommand` created earlier\nmain = MainCommand.new\nmain.add_command WelcomeCommand.new\n# there is also the `add_commands` method for adding multiple\n# subcommands at one time\n\n# run the command\nmain.execute ARGV\n```\n\n```\n$ crystal greet.cr -h\nUsage:\n        greet \u003carguments\u003e [options]\n\nCommands:\n        welcome    sends a friendly welcome message\n\nArguments:\n        name    the name of person to greet (required)\n\nOptions:\n        -c, --caps  greet with capitals\n        -h, --help  sends help information\n\n$ crystal greet.cr welcome Dev\nWelcome to the CLI world, Dev!\n```\n\nAs well as being able to have subcommands, they can also inherit certain properties from the parent command:\n\n```crystal\n# in welcome_command.cr ...\nclass WelcomeCommand \u003c Cling::Command\n  def setup : Nil\n    # ...\n\n    # this will inherit the header and footer properties\n    @inherit_borders = true\n    # this will NOT inherit the parent flag options\n    @inherit_options = false\n    # this will inherit the input, output and error IO streams\n    @inherit_streams = true\n  end\nend\n```\n\n## Arguments and Options\n\nArguments and flag options can be defined in the `setup` method of a command using the `add_argument` and `add_option` methods respectively.\n\n```crystal\nclass MainCommand \u003c Cling::Command\n  def setup : Nil\n    add_argument \"name\",\n      # sets a description for it\n      description: \"the name of the person to greet\",\n      # set it as a required or optional argument\n      required: true,\n      # allow multiple values for the argument\n      multiple: false,\n      # make it hidden from the help template\n      hidden: false\n\n    # define an option with a short flag using chars\n    add_option 'c', \"caps\",\n      # sets a description for it\n      description: \"greet with capitals\",\n      # set it as a required or optional flag\n      required: false,\n      # the type of option it is, can be:\n      # :none to take no arguments\n      # :single to take one argument\n      # or :multiple to take multiple arguments\n      type: :none,\n      # optionally set a default value\n      default: nil,\n      # make it hidden from the help template\n      hidden: false\n  end\nend\n```\n\n\u003e [!WARNING]\n\u003e You can only have **one** argument with the `multiple` option which will include all the remaining input values (or unknown arguments). See the [example command](/examples/cat/cat.cr) for usage.\n\nThese arguments and options can then be accessed at execution time via the `arguments` and `options` parameters in the `pre_run`, `run` and `post_run` methods of a command:\n\n```crystal\nclass MainCommand \u003c Cling::Command\n  # ...\n\n  def pre_run(arguments : Cling::Arguments, options : Cling::Options) : Bool # can also be `Nil`\n    if arguments.get(\"name\").as_s.blank?\n      stderr.puts \"Your name can't be blank!\"\n\n      false\n    else\n      true\n    end\n  end\nend\n```\n\nThe `pre_run` method is slightly different to the other run methods: it allows returning a boolean to the command executor, which will determine whether the command should continue running – `false` will stop the command, `true` will continue. Explicitly returning `nil` or not specifying a return type is the same as returning `true`; the command will continue to run.\n\nIf you try to access the value of an argument or option that isn't set, it will raise a `ValueNotFound` exception. To avoid this, use the `get?` method and check accordingly:\n\n```crystal\n# ...\n\ndef run(arguments : Cling::Arguments, options : Cling::Options) : Nil\n  caps = options.get?(\"caps\").try(\u0026.as_bool) || false\n  stdout.puts caps # =\u003e false\nend\n```\n\n\u003e [!NOTE]\n\u003e See [argument.cr](/src/cling/argument.cr#L34) and [option.cr](/src/cling/option.cr#L51) for more information on parameter methods, and [value.cr](/src/cling/value.cr) for value methods.\n\n## Customising\n\nThe help template is divided into the following sections:\n\n```\n[HEADER]\n\n[DESCRIPTION]\n\n[USAGE]\n    \u003cNAME\u003e \u003cUSE | \"[\u003carguments\u003e]\" \"[\u003coptions\u003e]\"\u003e\n\n[COMMANDS]\n    [ALIASES] \u003cNAME\u003e \u003cSUMMARY\u003e\n\n[ARGUMENTS]\n    \u003cNAME\u003e \u003cDESCRIPTION\u003e [\"(required)\"]\n\n[OPTIONS]\n    [SHORT] \u003cLONG\u003e \u003cDESCRIPTION\u003e [\"(required)\"] [\"(default: ...)\"]\n\n[FOOTER]\n```\n\nSections in `\u003c\u003e` will always be present, and ones in `[]` are optional depending on whether they are defined. Because of Cling's modularity, this means that you could essentially have a blank help template (wouldn't recommend it though).\n\nYou can customise the following options for the help template formatter:\n\n```crystal\nclass Cling::Formatter::Options\n  # The character to use for flag option delimiters (default is `-`).\n  property option_delim : Char\n\n  # Whether to show the `default` tag for options with default values (default is `true`).\n  property show_defaults : Bool\n\n  # Whether to show the `required` tag for required arguments/options (default is `true`).\n  property show_required : Bool\nend\n```\n\nAnd pass it to the command like so:\n\n```crystal\nrequire \"cling\"\n\noptions = Cling::Formatter::Options.new option_delim: '+', show_defaults: false\n# we can re-use this in multiple commands\nformatter = Cling::Formatter.new options\n\nclass MainCommand \u003c Cling::Command\n  # ...\n\n  def help_template : String\n    formatter.generate self\n  end\nend\n```\n\nAlternatively, if you want a completely custom design, you can pass a string directly:\n\n```crystal\ndef help_template : String\n  \u003c\u003c-TEXT\n    My custom command help text!\n\n    Use:\n        greet \u003cname\u003e [-c | --caps] [-h | --help]\n    TEXT\nend\n```\n\n## Extensions\n\nCling comes with a few useful extension methods for handling argument and option values:\n\n```crystal\nrequire \"cling\"\nrequire \"cling/ext\"\n\nclass StatCommand \u003c Cling::MainCommand\n  def setup : Nil\n    super\n\n    @name = \"stat\"\n    @description = \"Gets the stat information of a file\"\n\n    add_argument \"path\", description: \"the path of the file to stat\", required: true\n  end\n\n  def run(arguments : Cling::Arguments, options : Cling::Options) : Nil\n    path = arguments.get(\"path\").as_path\n\n    if File.exists? path\n      info = File.info path\n      stdout.puts \u003c\u003c-INFO\n        name:        #{path.basename}\n        size:        #{info.size}\n        directory:   #{info.directory?}\n        symlink:     #{info.symlink?}\n        permissions: #{info.permissions}\n        INFO\n    else\n      stderr.puts \"No file found at that path\"\n    end\n  end\nend\n\nStatCommand.new.execute ARGV\n```\n\n```\n$ crystal stat.cr ./shard.yml\nname:        shard.yml\nsize:        272\ndirectory:   false\nsymlink:     false\npermissions: rwxrwxrwx (0o777)\n```\n\n\u003e [!NOTE]\n\u003e See [ext.cr](/src/cling/ext.cr) for the full list of extension methods.\n\nAdditionally, you can define your own extension methods on the `Value` struct like so:\n\n```crystal\nrequire \"cling\"\n\nmodule Cling\n  struct Value\n    def as_chars : Array(Char)\n      @raw.to_s.chars\n    end\n  end\nend\n\nclass StatCommand\n  # ...\n\n  def pre_run(arguments : Cling::Arguments, options : Cling::Options) : Nil\n    puts arguments.get(\"path\").as_chars\n    # =\u003e ['.', '/', 's', 'h', 'a', 'r', 'd', '.', 'y', 'm', 'l']\n  end\nend\n```\n\n## Motivation\n\nMost Crystal CLI builders/DSLs are opinionated with limited customisation available. Cling aims to be entirely modular so that you have the freedom to change whatever you want without having to write tons of boilerplate or monkey-patch code. Macro-based CLI shards can also be quite restrictive as they are not scalable, meaning that you may eventually have to refactor your application to another CLI shard. This is not meant to discourage you from using macro-based CLI shards, they are still useful for short and simple applications with a general template, but if you are looking for something to handle larger applications with guaranteed stability and scalability, Cling is the library for you.\n\n## Projects using Cling\n\nInformation made available thanks to [shards.info](https://shards.info/github/devnote-dev/cling/).\n\n- [Docr](https://github.com/devnote-dev/docr) - A CLI tool for searching Crystal documentation\n- [Fossil](https://github.com/PteroPackages/Fossil) - 📦 Pterodactyl Archive Manager\n- [Geode](https://github.com/devnote-dev/geode) - An alternative Crystal package manager\n- [Crimson](https://github.com/crimson-crystal/crimson) - A Crystal Version Manager\n- [tanda_cli](https://github.com/DanielGilchrist/tanda_cli) - A CLI application for people using Tanda/Workforce.com\n\n## Contributing\n\n1. Fork it (\u003chttps://github.com/devnote-dev/cling/fork\u003e)\n2. Create your feature branch (`git checkout -b my-new-feature`)\n3. Commit your changes (`git commit -am 'Add some feature'`)\n4. Push to the branch (`git push origin my-new-feature`)\n5. Create a new Pull Request\n\n## Contributors\n\n- [Devonte W](https://github.com/devnote-dev) - creator and maintainer\n\nThis repository is managed under the Mozilla Public License v2.\n\n© 2022-present devnote-dev\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdevnote-dev%2Fcling","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdevnote-dev%2Fcling","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdevnote-dev%2Fcling/lists"}