{"id":20768502,"url":"https://github.com/papierkorb/toka","last_synced_at":"2025-05-11T09:32:02.494Z","repository":{"id":53559410,"uuid":"100050976","full_name":"Papierkorb/toka","owner":"Papierkorb","description":"A type-safe, object-oriented option parser","archived":false,"fork":false,"pushed_at":"2023-07-08T20:44:26.000Z","size":41,"stargazers_count":9,"open_issues_count":2,"forks_count":3,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-30T12:15:15.719Z","etag":null,"topics":["cli","crystal","mapping","options","options-parsing","type-safe"],"latest_commit_sha":null,"homepage":null,"language":"Crystal","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/Papierkorb.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":"2017-08-11T16:27:48.000Z","updated_at":"2024-08-04T19:12:05.000Z","dependencies_parsed_at":"2024-11-17T23:47:40.476Z","dependency_job_id":null,"html_url":"https://github.com/Papierkorb/toka","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/Papierkorb%2Ftoka","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Papierkorb%2Ftoka/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Papierkorb%2Ftoka/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Papierkorb%2Ftoka/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Papierkorb","download_url":"https://codeload.github.com/Papierkorb/toka/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253544980,"owners_count":21925315,"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","crystal","mapping","options","options-parsing","type-safe"],"created_at":"2024-11-17T11:39:14.439Z","updated_at":"2025-05-11T09:32:02.227Z","avatar_url":"https://github.com/Papierkorb.png","language":"Crystal","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Toːka [![Build Status](https://travis-ci.org/Papierkorb/toka.svg?branch=master)](https://travis-ci.org/Papierkorb/toka)\n\nA type-safe, object-oriented option parser using the mapping-pattern.\n\n## Simple usage\n\n**Note**: If you're unfamiliar with UNIX-style argument passing, see the\n[explanation below](#explanation-of-argument-passing).\n\nDoesn't get much simpler than this:\n\n```crystal\nrequire \"toka\"\n\nclass MyOptions # Create a container class\n  Toka.mapping({ # Don't forget the opening braces!\n    name: String, # Mandatory option\n    last_name: String?, # Optional option\n    verbose: Bool, # Mandatory option\n  })\nend\n\n# Now, create an instance:\nopts = MyOptions.new # It will use `ARGV` by default!\n\n# And access the fields.  Bool-type fields have an question-mark getter too!\nputs \"Hello, #{opts.name} #{opts.last_name}\" if opts.verbose?\n```\n\nWith this, the user can pass something like `--name Alice --no-verbose`, or\njust `--help` (or `-h`) to print a help page.\n\n**Hint** You can find usage-examples in `samples/` !\n\n**Note**: The default help page renderer will `exit()` the process after\nprinting its output!\n\n### A note on running sample code\n\nIf you're running the sample code directly through `crystal` like this:\n`$ crystal samples/simple.cr --name=Me --verbose`\nyou'll get a nasty error message from `crystal` that it doesn't know \"--name\".\n\nFor this to work, you have to tell crystal to stop processing arguments by\nadding a \"--\" between the arguments for the sample program and the source file:\n\n`$ crystal samples/simple.cr -- --name=Me --verbose`\n\nIf this is news to you, consider taking a refresher on [UNIX-style argument passing](#explanation-of-argument-passing).\n\n## Advanced usage\n\n`Toka` can do much more than that.  To adjust further settings, you can pass\na `NamedTuple` as value too!\n\nFirst, an example:\n\n```crystal\nclass MyOptions\n  Toka.mapping({ # Still don't forget the opening braces!\n    name: { # This is the settings (named-)tuple\n      type: String, # Still a String\n      default: \"World\", # But greet the World if none was given\n      description: \"Whom to greet\", # For the help page\n      value_name: \"NAME\", # Same ^\n    },\n    last_name: {\n      type: String?,\n      # nilable: true, # Alternatively, write `type: String` and this\n    },\n    verbose: {\n      type: Bool?, # Trick to detect explicit activation and deactivation\n    },\n  }, { # Optionally, the info tuple\n    banner: \"Usage: my_cool_tool [--name]\",\n    footer: \"I'm at the bottom!\",\n  })\nend\n\n# Now, create an instance:\nopts = MyOptions.new # It will use `ARGV` by default!\n\n# And access the fields.  Bool-type fields have an question-mark getter too!\nputs \"Hello, #{opts.name} #{opts.last_name}\" if opts.verbose?\n```\n\nThe settings tuple supports the following options:\n\n* `type` The type.  Examples: `String`, `Int32?`, `Array(String)`\n* `nilable` If the type is optional (\"nil-able\").  You can also make the `type` nilable for the same effect.\n* `default` The default value.\n* `long` Allows to manually configure long-options.  Auto-generated from the name otherwise.\n* `short` Allows to manually configure short-options.  Auto-generated otherwise.  Set to `false` to disable.\n* `converter` Converter to use for the value.  See below.\n* `value_converter` Alias for `converter`.\n* `key_converter` Converter for the key to use for a `Hash` type.\n* `description` The human-readable description.  Can be multi-line.\n* `value_name` Human-readable value name, shown next to the option name like: `--foo=HERE`\n* `category` Human-readable category name, for grouping in the help page. Optional.\n\n**Note**: The `long` and `short` fields can take a single string, or an\narray of strings.  Do *not* prepend dashes yourself, `Toka` will do that!\n\nThe *info* argument allows the following options:\n\n* `banner` The banner string.  Displayed as first line(s) in the help page.\n* `footer` The footer string.  Displayed as last line(s) in the help page.\n* `help` If to auto-generate the `--help`/`-h` option.  Defaults to `true`.\n* `colors` If the help page shall be colorized.  Defaults to `true`.\n\n## Positional options\n\nAll positional options are collected into a the `#positional_options` array.\n\nThis includes bare words, argument-looking words with a leading back-slash,\nand everything after a stand-alone double-dash \"argument\":\n\nThe line `--one \\--two three -- --four -five six` would activate the \"one\"\nswitch, and collect the positional options like this:\n`[ \"--two\", \"three\", \"--four\", \"-five\", \"six\" ]`.\n\n## Converters\n\nA converter is a module or class, responding to `.read(raw_value : String) : T?`.\nOn success, this method returns an instance of `T`, otherwise, it can\neither return `nil` to prompt a default error message, or raise a more\ndescriptive one itself.\n\n```crystal\nmodule IpV4Converter # Sample only!  Please do more error checking in your converters!\n  def self.read(input : String) : Int32? # You can also just write `Int32`\n    input.split(\".\", 4).map(\u0026.to_u32).reduce(0u32){|x, a| (a \u003c\u003c 8) | x}\n  end\nend\n\nclass MyOptions\n  Toka.mapping({\n    addr: { # Reacts to `--addr`\n      type: UInt32, # The result will be a UInt32\n      converter: IpV4Converter # And we want to use this converter\n    },\n  })\nend\n```\n## Input verification\n\nIt is possible to verify input data before it's being used.  To do this,\npass a `Proc` through the `verifier` (or `value_verifier`) field setting.\nFor the key of a `Hash` type, `key_verifier` is what you're looking for.\n\nThis `Proc` gets passed the already converted value, and is then expected to\nreturn either a `false` or a `String` to signal an error, or anything else\nto signal success.\n\nIf the verifier responds with a `false`, the user will receive a generic\n`Toka::VerificationError`.  If the response is a `String`, it will be\nappeneded to its message for further context.\n\n```crystal\nclass MyOptions\n  Toka.mapping({\n    name: {\n      type: String, #  vvvvvv Type is required for Crystal!\n      verifier: -\u003e(x : String){ x == \"Bob\" } # Only accepts \"Bob\" as input\n    },\n    age: {\n      type: Int32, # Simple age restriction with additional message:\n      verifier: -\u003e(x : Int32){ x \u003e= 18 || \"Must be an adult\" }\n    }\n  })\nend\n```\n\n**Note**: The verifier can be anything that responds to `#call()`, behaving\nlike a `Proc`.  You could also have a module which responds to\n`self.call(x)`, and pass in that module.\n\n## Error handling\n\nWhen an error is encountered while parsing the input, a sub-class of\n`Toka::Error` is raised.  All error classes provide additional data to\nthe error handler by carrying additional fields, next to the standard\nmessage.\n\n**Note**: A converter or verifier raising a custom error are not handled\nby Toka.  They're passed through.  Albeit losing the additional information\nToka errors provide, this is supported.\n\n## Sequential and associative options\n\nBy using `Array(T)` or `Hash(K, V)` as type, you allow the user to pass in\nmultiple values for a single option.  They're added in the order they're\nread: The left-most value will be the first one to be added, and so on.\n\nFor `Array(T)`, the user has to repeat the option for each element to be\nadded. If you have an option called `many` of type `Array(String)`, the user\npasses `--many one --many two --many three` to generate\n`[ \"one\", \"two\", \"three\" ]`.\n\nFor `Hash(K, V)`, the user repeats the option for each key-value pair,\nseparating the key from the value using an equal-sign (\"=\") like this:\n`--many foo=bar --many one=two` will generate\n`{ \"foo\" =\u003e \"bar\", \"one\" =\u003e \"two\" }`.\n\nYou're not restricted to using `String`.  You can use whichever type you\nwant.  The built-in ones will just work, for others, use a custom converter.\nConverters will be invoked with one value each, and thus work out of the box.\n\n**Note**: These containers are not nil-able.  Instead, you'll get an empty\ncontainer.  You can still pass a default one though!\n\n```crystal\nclass MyOptions\n  Toka.mapping({\n    name: Array(String), # Simple usage\n    ints: Array(Int32), # Works too\n    streets: {\n      type: Array(String),\n      default: [ \"Foo st\", \"Bar st\" ], # Will only be used if none are given.\n    },\n    birthday: {\n      type: Hash(String, Time), # Associative data\n      key_converter: TitelizeName, # Converter for the key\n      value_converter: TimeConverter, # Converter for the value\n      # converter: TimeConverter, # Alias, equivalent to the one above\n    },\n  })\nend\n```\n\n**Note**: The parser is restricted to `Array` and `Hash`.  You can't use other\ngeneric types.\n\n## Boolean behaviour\n\n`Bool` is somewhat special.  Switches of this type don't require a value.\nIf the user wants to explicitly set one, `--switch=false` has to be used.\nA following `true` or `false` will **not** be detected.\n\nFurther, a `Bool` switch automatically gets two versions:  The active\nversion, and the inactive one.  The long-name for the inactive one is the\nactive name with a \"no-\" prepended: `--verbose` gets turned into\n`--no-verbose`.  For the short-name, the existing short-names are taken and,\nif no collisions are detected, inversed by uppercasing the character.  This\nalso works, if the short-name was auto-generated in the first place.  In\nour example, the `--verbose` switch would be assigned `-v` as short switch,\nand it would be uppercased to negate: From `-v` to `-V`.\n\nAs already noted, the user has to follow a value immediately if one is\npassed.  Only the long-name supports this, the short-name **does not**.\nSo, this will work: `--verbose=true`, but this **will not**: `-vtrue`.\n\nThe following inputs will be turned into `true`: `true`, `yes`, `t` and `y`.\nFor `false`: `false`, `no`, `f`, `n`.  Other inputs will **raise an error**.\n\n### Bool in containers\n\nIt's possible to mix containers with Bool, like `Array(Bool)` or\n`Hash(String, Bool)`.  No automatic (de-)activation switch is generated\nfor these cases, meaning the user has to explicitly set the value.\n\nSample for an array: `-ayes -ayes -ano` gives `[ true, true, false ]`.\nHash sample: `-afoo=yes -abar=no` gives `{ \"foo\" =\u003e true, \"bar\" =\u003e false }`.\n\n## Generation behaviour\n\nThe macro tries to do as much for you as possible, so here's what's possible:\n\n* The long-name is generated from the dasherized name: `foo_bar: String` will be turned into `--foo-bar`.\n* The short-names are generated from the long-name, prefering the first character of each word of each\n  long-name, and using any following characters afterwards.  Done until an unique one is found, or none at all.\n* `Bool` type options get both a getter with question-mark and one without: `#verbose` is the same as `#verbose?`\n* A `--help` page is generated.  Upon activation through the user, the process is exited!\n\n## Installation\n\nAdd this to your application's `shard.yml`:\n\n```yaml\ndependencies:\n  toka:\n    github: Papierkorb/toka\n```\n\n## Explanation of argument passing\n\nThis just covers the common UNIX-style option-passing.  You can skip this if\nyou're familiar with it - `Toka` implements it!\n\n**Note**: Windows-style passing, using a leading slash (e.g. `/f`) instead\nof dashes, are not supported.\n\nFirst, options are split word-wise (According to the program calling another\nprogram, usually your shell).  A \"word\" may actually consist out of multiple\nreadable words separated by a space (\" \"), so don't get confused.\n\nSecond, there are two kinds of options: Switches, and positional options.\nThe first kind are those which are \"named\", and are accessed directly by\none of their names.  Positional options are not: All options which do not\nlook like an option (\"bare words\") are positional options.\n\nExample: `wc -l foo` This invokes the `wc` utility, passing the `-l` switch,\nand the `foo` positional option.\n\n### Long- and short-options\n\nMany options have a long-name, and a short-name. (They may have further\naliases).  Long-names are longer than their short-name counterparts.  They\nare distinguished by the leading count of dashes: Two (\"--\") for a\nlong-name, and one (\"-\") for a short-name.\n\nLong-names for an option are usually whole words, but can span multiple\nwords.  It is common to separate words using a single dash:\n`--street-number`.  It is not possible to define multiple long-names at\nonce.  Sometimes long-names are case-sensitive, other times they're not.\n`Toka` implements long-names case-sensitively.\n\nShort-names are commonly one character only.  You can combine multiple\nshort-name switches into a single word: `-abc` will flip the switches\nfor `a`, `b` and `c`.  This is equivalent to doing the following: `-a -b -c`.\n\n### Value passing\n\nIn many cases it's desirable to pass specific values to a switch for further\nconfiguration.\n\nFor long-names, the value can either follow in the same word by separating\nthe value from the long-name using an equal-sign (\"=\"): `--foo=bar` would\npass the value \"bar\" to the \"foo\" switch.  If no equal-sign is found while\nrequiring a value, the following word is used: `--foo bar` is equivalent.\n\nFor short-names, the value *immediately* follows the short option: `-fBar`\nwould pass \"Bar\" to the \"f\" switch.  This is a common source of confusion\nand mistakes: Say we have the switch \"a\" not taking a value, and \"b\" taking\none.  Now, you want to invoke both, and pass a value to \"b\". So you write\n`-ba foo` - And suddenly, you passed \"a\" to the \"b\" switch as value, and\n\"foo\" is treated as positional option.  Correct is this: `-ab foo` - This\nactivates the \"a\" switch, and passes \"foo\" to the \"b\" switch.  You can also\ncombine non-value-taking with value-taking short-names into the same word:\n`-abfoo` will activate \"a\", abd pass \"foo\" to \"b\".\n\n### Cancelling option parsing\n\nSometimes it may be useful to tell the option parser that some options are\nto be treated as positional options.  There are two solutions to this:\n\n1. Escape it by prepending a back-slash: `\\--foo`\n2. Ignore everything following by stand-alone double-dash: `--foo -- --bar`\n   will activate the \"foo\" switch, but pass \"--bar\" as positional option.\n\n## Contributing\n\n1. Fork it ( https://github.com/Papierkorb/toka/fork )\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- [Papierkorb](https://github.com/Papierkorb) Stefan Merettig - creator, maintainer\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpapierkorb%2Ftoka","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpapierkorb%2Ftoka","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpapierkorb%2Ftoka/lists"}