Code Monkey home page Code Monkey logo

cliche's Introduction

CLIche

  • AutoMagic CLI argument parsing is so Cliché.

Examples

import cliche
# Use https://nim-lang.github.io/Nim/os.html#commandLineParams
# let real = commandLineParams()
let fake = @["--a=1", "--v_1=9.9", "--v2=1", "--v3=2", "--v4=X", "--v5=t", "--v6=z", "--help"]
fake.getOpt (a: int.high, v_1: 3.14, v2: 9'u64, v3: -9'i64, v4: "a", v5: '4', v6: cstring"b", missing: 42)
doAssert a == 1
doAssert v_1 == 9.9
doAssert v2 == 1'u64
doAssert v3 == 2'i64
doAssert v4 == "X"
doAssert v5 == 't'
doAssert v6 == cstring"z"
doAssert missing == 42  ## missing is not in fake, fallback to default value 42.
  • Auto-Generated --help (Can be parsed as TSV)
$ example --help
 key     type    default
 --a=    int     int.high
 --v_1=  float   3.14
 --v2=   uint    9'u64
 --v3=   int     -9'i64
 --v4=   string  "a"
 --v5=   char    '4'
 --v6=   string  cstring"b"
 --missing=      int     42
 --help  ?       Some Help Message Here!

$

Cligen Vs Cliche

Imagine you just need foo to be 'z', with default to 'x'.

Lets use Cligen (I dont really need a func but Cligen API needs it):

import std/macros, cligen
func x(foo = 'x') = doAssert foo == 'z'
expandMacros:
  dispatch x

Expands to:

proc cligenScope(cf`gensym2: ClCfg) =
  case cf`gensym2.sigPIPE
  of spRaise:
    discard
  of spPass:
    SIGPIPE_pass()
  of spIsOk:
    SIGPIPE_isOk()
  if (
    not (cast[pointer](cgVarSeqStrNil) == cgVarSeqStrNil)):
  proc dispatchx(cmdline: seq[string] = mergeParams(@["x"]); usage = clUse;
                 prefix = ""; parseOnly = false; skipHelp = false; noHdr = false) =
    {.push, hint[XDeclaredButNotUsed]: false.}
    var ap: ArgcvtParams
    ap.val4req = cf`gensym2.hTabVal4req
    let shortH`gensym4 = "h"
    var allParams: seq[string] = if (
      0 < len(cf`gensym2.helpSyntax)): @["help", "help-syntax"] else: @["help"]
    var crbt: CritBitTree[string]
    incl(crbt, optionNormalize("help", "_-"), "help")
    if (
      0 < len(cf`gensym2.helpSyntax)):
      incl(crbt, optionNormalize("help-syntax", "_-"), "help-syntax")
    var mand: seq[string]
    var tab: TextTab = @[]
    let helpHelpRow`gensym4 = @["-" & shortH`gensym4 & ", --help", "", "",
                                ("help", "print this cligen-erated help")[1]]
    if skipHelp:
      if
        not (shortH`gensym4 == "h") and
        not (("help", "print this cligen-erated help")[1] ==
            cf`gensym2.hTabSuppress):
        add(tab, helpHelpRow`gensym4)
    elif
      not (("help", "print this cligen-erated help")[1] ==
          cf`gensym2.hTabSuppress):
      add(tab, helpHelpRow`gensym4)
    if
      0 < len(cf`gensym2.helpSyntax) and
      not (("help-syntax", "advanced: prepend,plurals,..")[1] ==
          cf`gensym2.hTabSuppress) and
        not skipHelp:
      add(tab, @["--help-syntax", "", "",
                 ("help-syntax", "advanced: prepend,plurals,..")[1]])
    ap.shortNoVal = {shortH`gensym4[0]}
    ap.longNoVal = @["help", "help-syntax"]
    let setByP: ptr seq[ClParse] = cgSetByParseNil
    proc mayRend(x`gensym4: string): string =
      result = if (
        not (cf`gensym2.render == nil)): cf`gensym2.render(x`gensym4)
      else:
        x`gensym4

    if (
      0 < len(cf`gensym2.version)):
      add(allParams, "version")
      incl(crbt, optionNormalize("version", "_-"), "version")
      ap.parNm = "version"
      ap.parSh = "\x00"
      ap.parReq = 0
      ap.parRend = ("version", "print version")[0]
      if (
        not (("version", "print version")[1] == cf`gensym2.hTabSuppress)):
        add(tab, argHelp(false, ap) & ("version", "print version")[1])
    var fooParamDispatch = 'x'
    var fooParamDefault = fooParamDispatch
    ap.parNm = "foo"
    ap.parSh = "f"
    ap.parReq = 0
    ap.parRend = if (
      false): "" else: helpCase("foo", clLongOpt)
    let descr`gensym6 = getDescription(fooParamDispatch, "foo", "")
    if (
      not (descr`gensym6 == cf`gensym2.hTabSuppress)):
      add(tab, argHelp(fooParamDispatch, ap) & mayRend(descr`gensym6))
    if (
      not (ap.parReq == 0)):
      tab[BackwardsIndex(1)][2] = ap.val4req
    incl(crbt, optionNormalize("foo", "_-"), move(ap.parRend))
    add(allParams, helpCase("foo", clLongOpt))
    let ww`gensym7 = wrapWidth(cf`gensym2.widthEnv, ttyWidth)
    let indentDoc`gensym7 = addPrefix(prefix, wrap(mayRend(""), ww`gensym7, 3,
        len(prefix)))
    proc hl(tag`gensym7, val`gensym7: string): string =
      result = getOrDefault(cf`gensym2.helpAttr, tag`gensym7, "") & val`gensym7 &
          getOrDefault(cf`gensym2.helpAttrOff, tag`gensym7, "")

    let use`gensym7 = if noHdr: if (
      0 < len(cf`gensym2.use)): cf`gensym2.use
    else:
      usage
     else: if (
      0 < len(cf`gensym2.useHdr)): cf`gensym2.useHdr else: clUseHdr &
        if (
      0 < len(cf`gensym2.use)): cf`gensym2.use else: usage
    ap.help = use`gensym7 %
        ["doc", hl("doc", indentDoc`gensym7), "command", hl("cmd", "x"), "args",
         hl("args", "[optional-params] "), "options", addPrefix(prefix & "  ", alignTable(
        tab, 2 * len(prefix) + 2, cf`gensym2.hTabColGap, cf`gensym2.hTabMinLast,
        cf`gensym2.hTabRowSep, toInts(cf`gensym2.hTabCols), onCols(cf`gensym2),
        offCols(cf`gensym2), "", ww`gensym7, printedLen))]
    if
      0 < len(ap.help) and
      not (ap.help[BackwardsIndex(1)] == '\n'):
      ap.help &= "\n"
    if (
      0 < len(prefix)):
      ap.help = addPrefix(prefix, ap.help)
    var keyCount = initCountTable(32)
    proc parser(args`gensym17 = cmdline; provideDflAlias = true) =
      var posNo = 0
      var p = initOptParser(args`gensym17, ap.shortNoVal, ap.longNoVal,
                            cf`gensym2.reqSep, cf`gensym2.sepChars,
                            cf`gensym2.opChars, @[], cf`gensym2.longPfxOk,
                            cf`gensym2.stopPfxOk)
      while true:
        next(p)
        if p.kind == cmdEnd:
          break
        if p.kind == cmdError:
          if (
            not (cast[pointer](setByP) == cgSetByParseNil)):
            add(setByP[], ("", "", move(p.message), clParseOptErr))
          if not parseOnly:
            write(stderr, [p.message, "\n"])
          break
        case p.kind
        of cmdLongOption, cmdShortOption:
          case if p.kind == cmdLongOption: lengthen(crbt, p.key,
              cf`gensym2.longPfxOk)
          else:
            p.key
          of "help", "h":
            if (
              not (cast[pointer](setByP) == cgSetByParseNil)):
              add(setByP[], ("help", "", ap.help, clHelpOnly))
            if not parseOnly:
              write(stdout, ap.help)
              raise
                (ref HelpOnly)(msg: "", parent: nil)
          of "helpsyntax":
            if (
              not (cast[pointer](setByP) == cgSetByParseNil)):
              add(setByP[],
                  ("helpsyntax", "", cf`gensym2.helpSyntax, clHelpOnly))
            if not parseOnly:
              write(stdout, cf`gensym2.helpSyntax)
              raise
                (ref HelpOnly)(msg: "", parent: nil)
          of "version", "\x00":
            if (
              not (cast[pointer](setByP) == cgSetByParseNil)):
              add(setByP[], ("version", "", cf`gensym2.version, clVersionOnly))
            if not parseOnly:
              if (
                0 < len(cf`gensym2.version)):
                write(stdout, [cf`gensym2.version, "\n"])
                raise
                  (ref VersionOnly)(msg: "", parent: nil)
              else:
                write(stdout, "Unknown version\n")
                raise
                  (ref VersionOnly)(msg: "", parent: nil)
          of "foo", "f":
            ap.key = p.key
            ap.val = p.val
            ap.sep = p.sep
            ap.parNm = "foo"
            ap.parRend = helpCase("foo", clLongOpt)
            inc(keyCount, "foo", 1)
            ap.parCount = keyCount["foo"]
            if (
              not (cast[pointer](setByP) == cgSetByParseNil)):
              if argParse(fooParamDispatch, fooParamDefault, ap):
                add(setByP[], ("foo", move(p.val), "", clOk))
              else:
                add(setByP[], ("foo", move(p.val),
                               "Cannot parse arg to " & ap.key, clBadVal))
            if not parseOnly:
              if not argParse(fooParamDispatch, fooParamDefault, ap):
                write(stderr, ap.msg)
                raise
                  (ref ParseError)(msg: "Cannot parse arg to " & ap.key,
                                   parent: nil)
            discard delItem(mand, "foo")
          of "":
            let ks`gensym13 = valsWithPfx(crbt, p.key)
            let msg`gensym13 = "Ambiguous long option prefix \"$1\" matches:\n  $2 " %
                [p.key, join(ks`gensym13, "\n  ")] &
                "\nRun with --help for more details.\n"
            if (
              not (cast[pointer](setByP) == cgSetByParseNil)):
              add(setByP[], (move(p.key), move(p.val), msg`gensym13, clBadKey))
            if not parseOnly:
              write(stderr, msg`gensym13)
              raise
                (ref ParseError)(msg: "Unknown option", parent: nil)
          else:
            var
              mb`gensym14: string
              k`gensym14: string
            k`gensym14 = "short"
            if p.kind == cmdLongOption:
              k`gensym14 = "long"
              var idNorm`gensym14: seq[string]
              for id`gensym14 in items(allParams):
                add(idNorm`gensym14, optionNormalize(id`gensym14, "_-"))
              let sugg`gensym14 = suggestions(optionNormalize(p.key, "_-"),
                  idNorm`gensym14, allParams, 3, C(4))
              if (
                0 < len(sugg`gensym14)):
                mb`gensym14 &=
                    "Maybe you meant one of:\n\t" & join(sugg`gensym14, " ") &
                    "\n\n"
            let msg`gensym14 = "Unknown " & k`gensym14 & " option: \"" & p.key &
                "\"\n\n" &
                mb`gensym14 &
                "Run with --help for full usage.\n"
            if (
              not (cast[pointer](setByP) == cgSetByParseNil)):
              add(setByP[], (move(p.key), move(p.val), msg`gensym14, clBadKey))
            if not parseOnly:
              write(stderr, msg`gensym14)
              raise
                (ref ParseError)(msg: "Unknown option", parent: nil)
        else:
          let msg`gensym15 = "Unexpected non-option " & $p.key
          if (
            not (cast[pointer](setByP) == cgSetByParseNil)):
            add(setByP[],
                (move(ap.key), move(p.val), msg`gensym15, clNonOption))
          if not parseOnly:
            write(stderr, "x does not expect non-option arguments at \"" &
                $p.key &
                "\".\nRun with --help for full usage.\n")
            raise
              (ref ParseError)(msg: msg`gensym15, parent: nil)

    {.pop.}
    parser(cmdline, true)
    if (
      0 < len(mand)):
      if (
        not (cast[pointer](setByP) == cgSetByParseNil)):
        for m`gensym17 in items(mand):
          add(setByP[], (m`gensym17, "", "Missing " & m`gensym17, clMissing))
      if not parseOnly:
        write(stderr, "Missing these required parameters:\n")
        for m`gensym17 in items(mand):
          write(stderr, ["  ", m`gensym17, "\n"])
        write(stderr, "Run command with --help for more details.\n")
        raise
          (ref ParseError)(msg: "Missing one/some mandatory args", parent: nil)
    if parseOnly or
      not (cast[pointer](setByP) == cgSetByParseNil) and
      0 < numOfStatus(setByP[], ClNoCall):
      return
    x(fooParamDispatch)

  try:
    dispatchx(mergeParams([""], commandLineParams()), clUse, "", false, false,
              false)
    quit(0)
  except HelpOnly, VersionOnly:
    quit(0)
  except ParseError:
    quit(cgParseErrorExitCode)


cligenScope(clCfg)
  • Cligen can not compile for JavaScript targets.
  • Uses cast, ref, nil, addr, unsafe code, etc.
  • Run-time string concatenations &.
  • Several for loops.
  • Several import and export from stdlib, several public symbols.
  • Wont work with template, macro, anonimous func.
  • Abbreviated CLI arguments (--bar is duplicated as -b too?).
  • C Compilation warnings.
  • API enforces to use proc.

Lets use Cliche:

import std/[macros, os, strutils], cliche
expandMacros:
  commandLineParams().getOpt (foo: 'x')
doAssert foo == 'z'

Expands to:

var foo = 'x'
for v in commandLineParams():
  var sepPos: int
  var k, b: string
  if not(v.len > 3) or v[0] != '-'  or v[1] != '-': continue
  if v.len == 6 and v[0] == 'h' and v[1] == 'e' and v[2] == 'l' and v[3] == 'p':
    quit(apiExplained, 0)
  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':
    quit(debuginfos, 0)
  for x in 2 .. v.len:
    if v[x] == '=':
      sepPos = x
      break
  k = v[2 ..< sepPos]
  b = v[sepPos .. ^1]
  if k.len == 3 and k[0] == 'f' and k[1] == 'o' and k[2] == 'o':
    foo = char(b[0])
  • Cliche works for JavaScript targets (Use with nodejs package).
  • No cast, no ref, no nil, no addr, no unsafe code.
  • Compile-time string ops.
  • 1 for loop.
  • 2 import, 0 export from stdlib, 1 public symbol.
  • Works with template, macro, etc.
  • No support for abbreviated CLI arguments (--foo in CLI is foo in code).
  • API just uses variables.
  • Supports enum, Positive, Natural, BiggestUInt, BiggestInt, BiggestFloat, byte, cint, cfloat.
  • Fallbacks for values: static default ➡️ env var ➡️ command line

Stars

@juancarlospaco@nikitavoloboev@zargot@artemklevtsov@AndrielFR@jaco-codexorbis@nonimportant@hamidb80@bichanna@hugosenari@Infinitybeond1@kamilchm@pedrovhb@foxoman@pietroppeter@srozb@xilicode@ccamateur@ThomasTJdev@dsrw@sirredbeard@mrgaturus@maleyva1@jacob-2@sevagh@elcritch@teroz@Kesefon@svercl@AlexandrosKap@qununc@rauny-brandao@Mycsina@KlausEverWalkingDev@nikhilsimha@DeanHnter@yaroslav-gwit@hydralisk98@slicklash@GabrielLasso@ippaveln@seipy@krisskross@noahehall@Notiee@linwaytin@insolitum@adokitkat@doongjohn@roclimber1@mimame@sgmihai@sincspecv@Tim-Hartmann@lucidrains@tuxcanfly@kyegomez@Yuan-ManX@xchanmolx@ShalokShalom@marcusramberg@fartek@peterhil@nschlemm@KolyaRS@caden-parajuli@vosscodes@arkanoid87@vosscodes@vosscodes@vosscodes@vosscodes@vosscodes@vosscodes@vosscodes@vosscodes@vosscodes@xjzh123@Expector-Hutch@Pent@succumbs@Lulzx@tuanductran@armed@NahuelUTNPigui@planetis-m@SpotlightKid@am-zed

cliche's People

Contributors

adokitkat avatar alexandroskap avatar am-zed avatar andrielfr avatar arkanoid87 avatar armed avatar artemklevtsov avatar bichanna avatar caden-parajuli avatar ccamateur avatar deanhnter avatar expector-hutch avatar gabriellasso avatar hochata avatar juancarlospaco avatar kesefon avatar klauseverwalkingdev avatar kolyars avatar luisacosta828 avatar lulzx avatar mycsina avatar nahuelutnpigui avatar notiee avatar pent avatar shalokshalom avatar spotlightkid avatar thomastjdev avatar tim-hartmann avatar vosscodes avatar yuan-manx avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

cliche's Issues

Can´t parse `uint64` and `float32`

For some types the parsing procedures are wrong.

Examples

uint64

It is parsed as an int, so the following code

@["--a=18446744073709551615"].getOpt (a: 0u64)

results in Parsed integer outside of valid range [ValueError]

float32

It is parsed as an int,

@["--b=1.0"].getOpt (b: 0f32)

results in invalid integer: 1.0 [ValueError]

export `func explainHelp`, or inject `apiExplained`

how about exporting func explainHelp or inject the resulting string in getOpt caller scope (just like vars)?
This way devs can quit the program with nice formatted help screen if the parameters are not correct.

I'm currently using this, but it's just a workaround

@["--help"].getOpt (myvar: 42)

[Feature request] Parse bool

I've switched from cligen to cliche and all is good, except that I had to switch from --enableFeature=true to --enableFeature=yes|true|Yes|True adding some boilerplate code

Would be nice to add bools too!

Can't use constants as values

It seems values are limited to literal values, even though constant are also static values

Example

The following code

import std/strutils
import cliche

const val = 0

@["--a=1"].getOpt (a: val)

echo a

results in Error: undeclared identifier: 'a'

Proposal default > ini > env > cmd

it would be nice to extend current behaviour to ini file > env variables > cmdline (usually in this order) like many Go programs do, and also many modern containerizable application

for example if I would have

cliche variables

commandLineParams().getOpt (myoption: 12)

myproject.ini file with

myoption=22

env set with

MYPROJECT_MYOPTION=32

cmdline with

--myoption=42

At runtime I'd get

echo myoption
# 42

due to default > ini > env > cmd order

Basically given existing variables block, it can be re-used to read ini file and environment variables (using custom prefix) and finally set the variable according to a certain order

Boolean literals aren't valid values

Using true or false as tuple value results in undeclared variables.

Example

import std/strutils
import cliche

@["--a=true"].getOpt (a: false)

echo a

results in a compilation error Error: undeclared indentifier 'a'

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.