Files
cli/cli.carp
2020-01-29 11:23:00 +01:00

205 lines
6.5 KiB
Plaintext

(defmodule Maybe
(defn zero [] (Maybe.Nothing))
)
(defmodule CLI
(deftype Option [
long String
short String
description String
required? Bool
default (Maybe String)
options (Maybe (Array String))
])
(deftype Parser [
description String
options (Array Option)
])
(deftype CmdMap [
values (Array (Pair (Pair String String) (Maybe String)))
])
(defmodule CmdMap
(defn new [] (init []))
(defn put [m o v]
(update-values m
&(fn [vs]
(Array.push-back vs
(Pair.init
(Pair.init @(CLI.Option.long o) @(CLI.Option.short o))
@v)))))
(defn put-empty [m o] (put m o (CLI.Option.default o)))
(defn contains? [m s]
(let-do [found false]
(foreach [e (values m)]
(let [k (Pair.a e)]
(when (or (= (Pair.a k) s) (= (Pair.b k) s))
(do
(set! found true)
(break)))))
found))
(defn set? [m s]
(let-do [found false]
(foreach [e (values m)]
(let [k (Pair.a e)
v (Pair.b e)]
(when (or (= (Pair.a k) s) (= (Pair.b k) s))
(do
(set! found (Maybe.just? v))
(break)))))
found))
(defn get [m s]
(let-do [res @""]
(foreach [e (values m)]
(let [k (Pair.a e)
v (Pair.b e)]
(when (or (= (Pair.a k) s) (= (Pair.b k) s))
(do
(set! res (Maybe.unsafe-from @v))
(break)))))
res))
(defn in? [m s vs]
(let-do [found true]
(foreach [e (values m)]
(let [k (Pair.a e)
v (Pair.b e)]
(when (or (= (Pair.a k) s) (= (Pair.b k) s))
(match @v
(Maybe.Just value)
(do
(set! found (Array.contains? vs &value))
(break))
(Maybe.Nothing) (break)))))
found))
(defn put! [m s v]
(let [vs (values m)]
(for [i 0 (Array.length vs)]
(let [p (Array.unsafe-nth vs i)
k (Pair.a p)]
(when (or (= (Pair.a k) s) (= (Pair.b k) s))
(do
(Pair.set-b! p (Maybe.Just @v))
(break)))))))
(defn to-map [m]
(Array.reduce
&(fn [a v] (Map.put a (Pair.a (Pair.a v)) (Pair.b v)))
{}
(values m)))
)
(defmodule Parser
(defn values [p]
(Array.reduce &CLI.CmdMap.put-empty (CLI.CmdMap.new) (options p)))
)
(defn new [descr] (Parser.init descr []))
(defn add [p opt]
(Parser.update-options p &(fn [options] (Array.push-back options @opt))))
(defndynamic option- [long short description required default-options]
(if (= (length default-options) 0)
(list 'CLI.Option.init
(list 'copy long) (list 'copy short) (list 'copy description)
required '(Maybe.Nothing) '(Maybe.Nothing))
(if (= (length default-options) 1)
(list 'CLI.Option.init
(list 'copy long) (list 'copy short) (list 'copy description)
required (list 'Maybe.Just (car default-options)) '(Maybe.Nothing))
(list 'CLI.Option.init
(list 'copy long) (list 'copy short) (list 'copy description)
required (list 'Maybe.Just (car default-options))
(list 'Maybe.Just (cadr default-options))))))
(defmacro option [long short description required :rest default-options]
(CLI.option- long short description required default-options))
(defn options-str [p]
(join
" "
&(Array.copy-map
&(fn [o] (fmt "[-%s | --%s]" (Option.short o) (Option.long o)))
(Parser.options p))))
(defn usage [p]
(do
(IO.println
&(fmt "usage: %s %s\n%s\nOptions:"
(System.get-arg 0) &(options-str p) (Parser.description p)))
(foreach [arg (Parser.options p)]
(do
(IO.print
&(fmt " --%s|-%s: %s"
(Option.long arg) (Option.short arg) (Option.description arg)))
(when @(Option.required? arg) (IO.print " REQUIRED"))
(when (Maybe.just? (Option.default arg))
(IO.print &(fmt " (default: %s)" &(Maybe.unsafe-from @(Option.default arg)))))
(match @(Option.options arg)
(Maybe.Just o)
(IO.print &(fmt " (options: %s)" &(join ", " &o)))
(Maybe.Nothing) ())
(IO.println "")))
(IO.println " --help|-h: print this help message and exit.")))
(defn parse [p]
(let-do [values (Parser.values p)
res (Result.Success @p)
options (Parser.options p)]
(for [i 1 (System.get-args-len)]
(let [x (System.get-arg i)]
(if (or (String.starts-with? x "--") (String.starts-with? x "-"))
(let [flag (Pattern.substitute #"^\-\-?" x "" 1)]
(cond
(CmdMap.contains? &values &flag)
(do
(set! i (Int.inc i))
(if (< i (System.get-args-len))
(CmdMap.put! &values &flag (System.get-arg i))
(do
(set! res (Result.Error (fmt "No value for: %s" &flag)))
(break))))
(or (= &flag "help") (= &flag "h"))
(do
(usage p)
(System.exit 0))
(do
(set! res (Result.Error (fmt "Unknown option: %s" x)))
(break))))
(do
(set! res (Result.Error (fmt "Unexpected argument: %s" x)))
(break)))))
(foreach [o options]
(cond
(and @(Option.required? o)
(not (CmdMap.set? &values (Option.long o))))
(do
(set! res (Result.Error (fmt "Required option missing: --%s" (Option.long o))))
(break))
(Maybe.just? (Option.options o))
(let-do [opts (Maybe.unsafe-from @(Option.options o))]
(when (not (CmdMap.in? &values (Option.long o) &opts))
(do
(set! res
(Result.Error
(fmt
"Option %s received an invalid option %s (Options are %s)"
(Option.long o)
&(CmdMap.get &values (Option.long o))
&(join ", " &opts))))
(break))))
()))
(match res
(Result.Success _) (Result.Success (CmdMap.to-map &values))
(Result.Error x) (Result.Error x))))
)