diff --git a/README.md b/README.md index 4f18336..278e9b0 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,29 @@ A simple CLI library for Carp. ## Usage +`CLI` should be built using combinators, as in the example above. It has, as of +now, three option types: integrals (longs), floating point numbers (doubles), +and strings. They can be built using `CLI.int`, `CLI.float`, and `CLI.str`, +respectively. Their structure is always the same: + +```clojure +(CLI.int ) +; or +(CLI.int ) +; or +(CLI.int ) +``` + +You’ll have to set a default if you want to specify options, although you can +set it to `(Maybe.Nothing)` if you want to make sure that it has to be set +manually. + +Once you’re done building your flag structure, you can run `CLI.parse`. It +will not abort the program on error, instead it will tell you what went wrong +in a `Result.Error`. If it succeeds, the `Result.Success` contains a `Map` from +the long flag name to the value (the values are `Maybe`s, since they might be +optional arguments. +
Have fun! diff --git a/cli.carp b/cli.carp index 9d052e8..18a3fbc 100644 --- a/cli.carp +++ b/cli.carp @@ -1,8 +1,56 @@ +; TODO: this is temporary until we have this in the stdlib (defmodule Maybe (defn zero [] (Maybe.Nothing)) ) +(doc CLI "is a simple CLI library for Carp. + +```clojure +(load \"https://veitheller.de/git/carpentry/cli@0.0.1\") + +(defn main [] + (let [p (=> (CLI.new @\"My super cool tool!\") + (CLI.add &(CLI.int \"flag\" \"f\" \"my flag\" true)) + (CLI.add &(CLI.str \"thing\" \"t\" \"my thing\" false @\"hi\" &[@\"a\" @\"b\" @\"hi\"])))] + (match (CLI.parse &p) + (Result.Success flags) + (println* &(str &(Map.get &flags \"flag\")) \" \" &(str &(Map.get &flags \"thing\"))) + (Result.Error msg) (do (IO.errorln &msg) (CLI.usage &p))))) +``` + +## Installation + +```clojure +(load \"https://veitheller.de/git/carpentry/cli@0.0.1\") +``` + +## Usage + +`CLI` should be built using combinators, as in the example above. It has, as of +now, three option types: integrals (longs), floating point numbers (doubles), +and strings. They can be built using `CLI.int`, `CLI.float`, and `CLI.str`, +respectively. Their structure is always the same: + +```clojure +(CLI.int ) +; or +(CLI.int ) +; or +(CLI.int ) +``` + +You’ll have to set a default if you want to specify options, although you can +set it to `(Maybe.Nothing)` if you want to make sure that it has to be set +manually. + +Once you’re done building your flag structure, you can run `CLI.parse`. It +will not abort the program on error, instead it will tell you what went wrong +in a `Result.Error`. If it succeeds, the `Result.Success` contains a `Map` from +the long flag name to the value (the values are `Maybe`s, since they might be +optional arguments.") (defmodule CLI + (hidden Type) + (private Type) (deftype Type (Integer [Long]) (Floating [Double]) @@ -32,6 +80,8 @@ (Str s2) (String.format s &s2))) ) + (hidden Tag) + (private Tag) (deftype Tag (Integer []) (Floating []) @@ -46,6 +96,8 @@ (Str) (CLI.Type.Str @s))) ) + (doc Option "is the option type. To construct an `Option`, please use +[`int`](#int), [`float`](#float), or [`str](#str).") (deftype Option [ type- Tag long String @@ -56,11 +108,18 @@ options (Maybe (Array Type)) ]) + (doc Parser "is the parser type. To construct a `Parser`, please use +[`new](#new).") (deftype Parser [ description String options (Array Option) ]) + (private CmdMap) + (hidden CmdMap) + ; this is pretty brutal. It’s a (Pair (Pair ) ( )) + ; we need to make our own map because long or short might match and both are + ; equivalent. This means a lot of manual work. Sorry about that. (deftype CmdMap [ values (Array (Pair (Pair String String) (Pair Tag (Maybe Type)))) ]) @@ -148,11 +207,15 @@ (Array.reduce &CLI.CmdMap.put-empty (CLI.CmdMap.new) (options p))) ) + (doc new "creates a new `Parser` with a program description `descr`.") (defn new [descr] (Parser.init descr [])) + (doc add "adds an `Option` `opt` to the `Parser` `p`.") (defn add [p opt] (Parser.update-options p &(fn [options] (Array.push-back options @opt)))) + (hidden option-) + (private option-) (defndynamic option- [t long short description required default-options] (if (= (length default-options) 0) (list 'CLI.Option.init (list t) @@ -168,18 +231,20 @@ (list 'Maybe.Just (list 'Array.copy-map '(ref (fn [e] (to-cli-type @e))) (cadr default-options))))))) - (defmacro option [long short description required :rest default-options] - (CLI.option- t long short description required default-options)) - + (doc str "creates a string option.") (defmacro str [long short description required :rest default-options] (CLI.option- 'CLI.Tag.Str long short description required default-options)) + (doc int "creates a integer option. The actual type is a `Long`.") (defmacro int [long short description required :rest default-options] (CLI.option- 'CLI.Tag.Integer long short description required default-options)) + (doc float "creates a integer option. The actual type is a `Double`.") (defmacro float [long short description required :rest default-options] (CLI.option- 'CLI.Tag.Floating long short description required default-options)) + (private options-str) + (hidden options-str) (defn options-str [p] (join " " @@ -187,6 +252,7 @@ &(fn [o] (fmt "[-%s | --%s]" (Option.short o) (Option.long o))) (Parser.options p)))) + (doc usage "takes a `Parser` `p` and prints its usage information.") (defn usage [p] (do (IO.println @@ -207,6 +273,15 @@ (IO.println ""))) (IO.println " --help|-h: print this help message and exit."))) + (doc parse "parses the arguments as specified by the parser `p`. + +If everything goes right, it will return a `Success` containing a map from +the long arguments to their values. Because values can be optional, they are +returned as `Maybe`. + +Otherwise it will return an `Error` containing an error message. If that error +mesage is empty, `--help` was requested. If you don’t want to provide a +`--help` feature, you can override that flag.") (defn parse [p] (let-do [values (Parser.values p) res (Result.Success @p) @@ -226,8 +301,8 @@ (break)))) (or (= &flag "help") (= &flag "h")) (do - (usage p) - (System.exit 0)) + (set! res (Result.Error @"")) + (break)) (do (set! res (Result.Error (fmt "Unknown option: %s" x))) (break)))) diff --git a/docs/CLI.html b/docs/CLI.html new file mode 100644 index 0000000..f319900 --- /dev/null +++ b/docs/CLI.html @@ -0,0 +1,258 @@ + + + + + + + + + +
+ +

+ CLI +

+
+

is a simple CLI library for Carp.

+
(load "https://veitheller.de/git/carpentry/cli@0.0.1")
+
+(defn main []
+  (let [p (=> (CLI.new @"My super cool tool!")
+              (CLI.add &(CLI.int "flag" "f" "my flag" true))
+              (CLI.add &(CLI.str "thing" "t" "my thing" false @"hi" &[@"a" @"b" @"hi"])))]
+    (match (CLI.parse &p)
+      (Result.Success flags)
+        (println* &(str &(Map.get &flags "flag")) " " &(str &(Map.get &flags "thing")))
+      (Result.Error msg) (do (IO.errorln &msg) (CLI.usage &p)))))
+
+

Installation

+
(load "https://veitheller.de/git/carpentry/cli@0.0.1")
+
+

Usage

+

CLI should be built using combinators, as in the example above. It has, as of +now, three option types: integrals (longs), floating point numbers (doubles), +and strings. They can be built using CLI.int, CLI.float, and CLI.str, +respectively. Their structure is always the same:

+
(CLI.int <long> <short> <description> <required?>)
+; or
+(CLI.int <long> <short> <description> <required?> <default>)
+; or
+(CLI.int <long> <short> <description> <required?> <default> <options-array>)
+
+

You’ll have to set a default if you want to specify options, although you can +set it to (Maybe.Nothing) if you want to make sure that it has to be set +manually.

+

Once you’re done building your flag structure, you can run CLI.parse. It +will not abort the program on error, instead it will tell you what went wrong +in a Result.Error. If it succeeds, the Result.Success contains a Map from +the long flag name to the value (the values are Maybes, since they might be +optional arguments.

+ +
+
+ +

+ Option +

+
+
+ module +
+

+ Module +

+ + + +

+

is the option type. To construct an Option, please use +int, float, or `str.

+ +

+
+
+ +

+ Parser +

+
+
+ module +
+

+ Module +

+ + + +

+

is the parser type. To construct a Parser, please use +`new.

+ +

+
+
+ +

+ add +

+
+
+ defn +
+

+ (λ [Parser, (Ref Option)] Parser) +

+
+                    (add p opt)
+                
+

+

adds an Option opt to the Parser p.

+ +

+
+
+ +

+ float +

+
+
+ macro +
+

+ Macro +

+
+                    (float long short description required :rest default-options)
+                
+

+

creates a integer option. The actual type is a Double.

+ +

+
+
+ +

+ int +

+
+
+ macro +
+

+ Macro +

+
+                    (int long short description required :rest default-options)
+                
+

+

creates a integer option. The actual type is a Long.

+ +

+
+
+ +

+ new +

+
+
+ defn +
+

+ (λ [String] Parser) +

+
+                    (new descr)
+                
+

+

creates a new Parser with a program description descr.

+ +

+
+
+ +

+ parse +

+
+
+ defn +
+

+ (λ [(Ref Parser)] (Result (Map String (Maybe Type)) String)) +

+
+                    (parse p)
+                
+

+

parses the arguments as specified by the parser p.

+

If everything goes right, it will return a Success containing a map from +the long arguments to their values. Because values can be optional, they are +returned as Maybe.

+

Otherwise it will return an Error containing an error message. If that error +mesage is empty, --help was requested. If you don’t want to provide a +--help feature, you can override that flag.

+ +

+
+
+ +

+ str +

+
+
+ macro +
+

+ Macro +

+
+                    (str long short description required :rest default-options)
+                
+

+

creates a string option.

+ +

+
+
+ +

+ usage +

+
+
+ defn +
+

+ (λ [(Ref Parser)] ()) +

+
+                    (usage p)
+                
+

+

takes a Parser p and prints its usage information.

+ +

+
+
+ + diff --git a/docs/style.css b/docs/style.css new file mode 100644 index 0000000..ed4f963 --- /dev/null +++ b/docs/style.css @@ -0,0 +1,110 @@ +html { + font-family: "Helvetica", sans-serif; + font-size: 16px; +} + +a { + color: #000; +} + +.logo { + display: none; +} + +ul { + list-style-type: none; + font-family: "Hasklig", "Lucida Console", monospace; + line-height: 1.4em; +} + +.module-description { + margin-bottom: 3em; +} + +.content { + margin: 3em auto auto auto; + width: 80%; + max-width: 610px; + min-width: 400px +} + +h1 { + margin-bottom: 1em; + font-weight: 400; +} + +h2 { + font-weight: 400; + margin-bottom: 0em; +} + +h3 { + margin: 0em; + font-weight: 400; +} + +.binder { + margin: 0em 0em 3.5em 0em; +} + +.sig { + font-family: "Hasklig", "Lucida Console", monospace; + margin: 0.5em 0em 0.5em 0em; +} + +.args { + background-color: #eee; + display: inline-block; + white-space: normal; + margin: 0; + margin-bottom: 1em; +} + +code { + background-color: #eee; +} + +pre { + background-color: #eee; + overflow-y: scroll; +} + +.description { + margin-top: 0.3em; + font-size: 0.8em; + color: #aaa; +} + +.huge { + font-size: 15em; + margin: 0em; +} + +/* Smaller screens */ +@media only screen and (max-width: 600px) { + .logo { + margin: 1em; + text-align: left; + float: left; + width: 100%; + } + .logo img { + display: block; + margin-left: auto; + margin-right: auto; + width: 50%; + } + .content { + margin: 0.5em; + } + .binder { + margin: 0em 0em 1.5em 0em; + } + .sig { + font-size: 0.9em; + } + ul { + padding: 0px; + } +} +.title, .index { display: none; } diff --git a/gendocs.carp b/gendocs.carp new file mode 100644 index 0000000..e5b7257 --- /dev/null +++ b/gendocs.carp @@ -0,0 +1,13 @@ +(load "CLI.carp") + +(defndynamic gendocs [] + (do + (Project.config "title" "cli") + (Project.config "docs-directory" "./docs/") + (Project.config "docs-logo" "") + (Project.config "docs-styling" "style.css") + (Project.config "docs-generate-index" false) + (save-docs CLI))) + +(gendocs) +(quit)