{"id":26600781,"url":"https://github.com/xjzh123/clim","last_synced_at":"2026-05-10T19:05:00.877Z","repository":{"id":202696819,"uuid":"707943492","full_name":"xjzh123/clim","owner":"xjzh123","description":"Yet another CLI option parser generator for Nim.","archived":false,"fork":false,"pushed_at":"2023-10-22T06:21:13.000Z","size":29,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2023-10-22T07:21:01.688Z","etag":null,"topics":["cli","macros","nim","nim-lang","option-parser"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","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/xjzh123.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}},"created_at":"2023-10-21T03:44:49.000Z","updated_at":"2023-10-21T10:29:12.000Z","dependencies_parsed_at":null,"dependency_job_id":"ba035e35-4e8b-4e26-8455-b0a37a835b59","html_url":"https://github.com/xjzh123/clim","commit_stats":null,"previous_names":["xjzh123/clim"],"tags_count":0,"template":null,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xjzh123%2Fclim","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xjzh123%2Fclim/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xjzh123%2Fclim/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xjzh123%2Fclim/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/xjzh123","download_url":"https://codeload.github.com/xjzh123/clim/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245151778,"owners_count":20569317,"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","macros","nim","nim-lang","option-parser"],"created_at":"2025-03-23T18:35:09.130Z","updated_at":"2026-05-10T19:04:55.832Z","avatar_url":"https://github.com/xjzh123.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Clim\n\nYet another CLI option parser generator for Nim.\n\nClim has a very simple usage, a feature set enough for (very?) simple CLI tools, and gracefully handles some problems for you.\n\n## Getting started\n\n```nim\nimport os\n\nopt(path, string, [\"--path\", \"-p\"], \".\") # Default value is \".\"\nopt(help, bool, [\"--help\", \"-h\"])\nopt(name, string, [\"--name\"])\nopt(level, int, [\"--level\"])\nopt(definitions, seq[string], [\"--define\", \"-d\"])\nopt(config, JsonNode, [\"--config\"], %*{}) # Must provide default for JsonNode\nopt(output, Option[string], [\"--output\", \"-o\"])\n\ngetOpt(commandLineParams())\n\necho \u0026\"{path=}, {help=}, {name=}, {level=}, {definitions=}, {config=}, {output=}\"\n```\n\nExpands roughly to:\n\n```nim\nstatic :\n  when string isnot CommandParamTypes:\n    error(\"Unsupported option type: \" \u0026 $string)\n  when bool isnot CommandParamTypes:\n    error(\"Unsupported option type: \" \u0026 $bool)\n  when string isnot CommandParamTypes:\n    error(\"Unsupported option type: \" \u0026 $string)\n  when int isnot CommandParamTypes:\n    error(\"Unsupported option type: \" \u0026 $int)\n  when seq[string] isnot CommandParamTypes:\n    error(\"Unsupported option type: \" \u0026 $seq[string])\n  when JsonNode isnot CommandParamTypes:\n    error(\"Unsupported option type: \" \u0026 $JsonNode)\n  when Option[string] isnot CommandParamTypes:\n    error(\"Unsupported option type: \" \u0026 $Option[string])\n\nvar identNamesThatIsSet: seq[string]\nvar\n  path: string = \".\"\n  help: bool\n  name: string\n  level: int\n  definitions: seq[string]\n  config: JsonNode = newJObject()\n  output: Option[string]\nlet src: seq[string] = commandLineParams()\nfor part in src:\n  let (prefix, name, value) = getParam(part)\n  case prefix\n  of \"--path\", \"-p\":\n    try:\n      path = value\n      add(identNamesThatIsSet, \"path\")\n    except ValueError:\n      echo [\"Warning: Option \\\"\", \"path\", \"\\\" is set to \\\"\", value,\n            \"\\\" but can not be parsed to \\\"\", \"string\", \"\\\".\"]\n  of \"--help\", \"-h\":\n    try:\n      help = parseBool(value)\n      add(identNamesThatIsSet, \"help\")\n    except ValueError:\n      echo [\"Warning: Option \\\"\", \"help\", \"\\\" is set to \\\"\", value,\n            \"\\\" but can not be parsed to \\\"\", \"bool\", \"\\\".\"]\n  of \"--name\":\n    try:\n      name = value\n      add(identNamesThatIsSet, \"name\")\n    except ValueError:\n      echo [\"Warning: Option \\\"\", \"name\", \"\\\" is set to \\\"\", value,\n            \"\\\" but can not be parsed to \\\"\", \"string\", \"\\\".\"]\n  of \"--level\":\n    try:\n      level = int(parseInt(value))\n      add(identNamesThatIsSet, \"level\")\n    except ValueError:\n      echo [\"Warning: Option \\\"\", \"level\", \"\\\" is set to \\\"\", value,\n            \"\\\" but can not be parsed to \\\"\", \"int\", \"\\\".\"]\n  of \"--define\", \"-d\":\n    try:\n      definitions.add(value)\n    except ValueError:\n      echo [\"Warning: Option \\\"\", \"definitions\", \"\\\" is set to \\\"\", value,\n            \"\\\" but can not be parsed to \\\"\", \"string\", \"\\\".\"]\n  of \"--config\":\n    try:\n      config = parseJson(value)\n      add(identNamesThatIsSet, \"config\")\n    except ValueError:\n      echo [\"Warning: Option \\\"\", \"config\", \"\\\" is set to \\\"\", value,\n            \"\\\" but can not be parsed to \\\"\", \"JsonNode\", \"\\\".\"]\n  of \"--output\", \"-o\":\n    try:\n      output = some(value)\n      add(identNamesThatIsSet, \"output\")\n    except ValueError:\n      echo [\"Warning: Option \\\"\", \"output\", \"\\\" is set to \\\"\", value,\n            \"\\\" but can not be parsed to \\\"\", \"string\", \"\\\".\"]\n  else:\n    echo [\"Warning: Option \\\"\", name, \"\\\" is not defined, \\\"\", part,\n          \"\\\" is ignored.\"]\nlet deduplicated = deduplicate(identNamesThatIsSet, false)\nif len(identNamesThatIsSet) != len(deduplicated):\n  for ident in deduplicated:\n    identNamesThatIsSet.del binarySearch(identNamesThatIsSet, ident)\n    if ident in identNamesThatIsSet:\n      echo [\"Warning: Option \\\"\", ident,\n            \"\\\" is set for multiple times. Only the last one will be used.\"]\n```\n\nYes! As you can see, the generated code is *very* clear and simple. And it handles parse error, duplicated options and undefined options for you.\n\n`opt` registers a variable that is bound to some command parameters. `getOpt` parses the given `seq[string]` and assigns option values to the varaibles. So, in one program you can only have one set of parameters. You **can't** do so:\n\n```nim\nblock:\n  opt(foo, string, [\"--foo\"])\n  getOpt(@[\"--foo:foo\"])\n\nblock:\n  opt(foo, int, [\"--foo\"])\n  getOpt(@[\"--foo:1\"])\n```\n\nAlthough Clim supports very simple CLI features, there is support for containers of options: `seq[ParseAble]`. This allows you to implement things like Nim compiler's `--define` option. In the example above, `./test -d:foo -d:bar` will let `definitions` to be `@[\"foo\", \"bar\"]`.\n\n## Hooks\n\nYou can define template/macro/procedure `undefinedOptionHook`, `parseErrorHook`, `duplicateOptionHook` to customize how the generated code handles parse error, duplicated options and undefined options. If you want to make a CLI tool in another language than English, it can be helpful to rewrite such warning texts.\n\nExample:\n\n```nim\nimport os\nimport macros\nimport strformat\n\nimport clim\n\necho commandLineParams()\n\nproc undefinedOptionHook(name, part: string) =\n  discard\n\n\ntemplate parseErrorHook(name, value: string, typ: typedesc) =\n  discard\n\n\nmacro duplicateOptionHook(name: string) =\n  discard\n\nexpandMacros:\n\n  opt(path, string, [\"--path\", \"-p\"], \".\")\n  opt(help, bool, [\"--help\", \"-h\"])\n  opt(name, string, [\"--name\"])\n  opt(level, int, [\"--level\"])\n\n  getOpt(commandLineParams())\n\necho \u0026\"{path=}, {help=}, {name=}, {level=}\"\n```\n\n**Note:** Due to implemention of strformat, you can't use strformat to format template parameters here.\n\n## Types for options\n\n```nim\ntype ParseAble = string | cstring | bool | SomeInteger | SomeFloat | enum | JsonNode\n\ntype CommandParamTypes = ParseAble | seq[ParseAble] | Option[ParseAble]\n```\n\nClim provides a rather small set of types that can be parsed from CLI options, because it is reasonable and natural to write code yourself to parse special types in your own way. This also works for files or paths.\n\nFor example, you can do:\n\n```nim\nimport paths\n\nopt(pathString, string, [\"--path\", \"-p\"], \".\")\n\ngetOpt(commandLineParams())\n\nvar path = Path(pathString)\n```\n\nClim supports JSON options, but it is not very sweet to write them in commands, you need to write like `--config:\"{\\\"key\\\": \\\"value\\\"}\"` or `--config:\"{\"\"key\"\": \"\"value\"\"}\"` to escape quotation marks.\n\nClim supports option types: `Option[ParseAble]`.\n\n## Internal\n\nClim uses procedure `getparam` to parse parameters. It works very naturally, returns `(string, string, string)`. `--name:value` will return `(\"--name\", \"name\", \"value\")`. `--name` will return `(\"--name\", \"name\", \"true\")`.\n\nClim generates human readable code with very simple logic. If Clim can't fulfill your needs but you can implement them by editing clim itself (e.g. Customize parsing), and you don't want to learn another option parser library, you can use `expandMacros` to see the generated code and start from it to write your own code. With good helper functions, you mat not need a option parser library.\n\n## Comparison\n\n|                               | Clim | Cliche | Cligen | Docopt |\n|-------------------------------|------|--------|--------|--------|\n| Directly assign to variables  | T    | T      | F      | F      |\n| Generated Code Simplicity     | ++   | +      | F      | F      |\n| Flexibility                   | +    | -      | +      | ++     |\n| Custom Param name[^1]         | T    | F      | /      | F      |\n| User defined hook             | T    | F      | F      | T      |\n| Generated CLI Help            | F    | T      | T      | T      |\n| Advanced features[^2]         | F    | F      | T      | T      |\n\nClim is inspired by [cliche](https://github.com/juancarlospaco/cliche), and created in order to remove its drawbacks, but keep its easy to use.\n\nWith cliche, you are not allowed to get from mutiple param names for one option, or allow `--name:value` and `--name=value` at the same time. You are also not allowed to get from a param name that is different from the variable name.\n\nCliche uses `--name=value` for default, which is different from what is used in Nim official documents! (Cliche doesn't support multiple delimiters. However, for the time being custom delimiters are not supported by Clim, only the default value `{':', '='}`.)\n\nCliche uses no procedures to help its work, it generates what is used, and even compares strings in a low-level way:\n\n```nim\nimport std/[macros, os, strutils], cliche\nexpandMacros:\n  commandLineParams().getOpt (foo: 'x')\ndoAssert foo == 'z'\n```\n\nExpands to:\n\n```nim\nvar foo = 'x'\nfor v in commandLineParams():\n  var sepPos: int\n  var k, b: string\n  if not(v.len \u003e 3) or v[0] != '-'  or v[1] != '-': continue\n  if v.len == 6 and v[0] == 'h' and v[1] == 'e' and v[2] == 'l' and v[3] == 'p':\n    quit(apiExplained, 0)\n  if len(v) == 8 and v[2] == 'x' and v[3] == 'd' and v[4] == 'e' and v[5] == 'b' and v[6] == 'u' and v[7] == 'g':\n    quit(debuginfos, 0)\n  for x in 2 .. v.len:\n    if v[x] == '=':\n      sepPos = x\n      break\n  k = v[2 ..\u003c sepPos]\n  b = v[sepPos .. ^1]\n  if k.len == 3 and k[0] == 'f' and k[1] == 'o' and k[2] == 'o':\n    foo = char(b[0])\n```\n\n(From cliche repo)\n\nBy contrast, Clim generates graceful, human-readable code, and handles edge cases for you.\n\n[^1]: For example, when you want `-O` to equal to `--optimize`, or `-x` to equal to `--checks`.\n[^2]: Arguments, subcommand, etc.\n\n## Todo\n\n- [ ] Now, flags are implemented by parsing `--name` as `--name:true`. Should warn when `name` is not `bool`.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxjzh123%2Fclim","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fxjzh123%2Fclim","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxjzh123%2Fclim/lists"}