Browse Source

Small but working `cab` binary, w/ docs and README

Getty Ritter 6 years ago
commit
6b83a4f594
4 changed files with 163 additions and 0 deletions
  1. 3 0
      .gitignore
  2. 6 0
      Cargo.toml
  3. 34 0
      README.md
  4. 120 0
      src/main.rs

+ 3 - 0
.gitignore

@@ -0,0 +1,3 @@
+
+/target
+**/*.rs.bk

+ 6 - 0
Cargo.toml

@@ -0,0 +1,6 @@
+[package]
+name = "cab"
+version = "0.1.0"
+authors = ["Getty Ritter <gdritter@galois.com>"]
+
+[dependencies]

+ 34 - 0
README.md

@@ -0,0 +1,34 @@
+In short: write `cab build` instead of `cabal new-build`, and save six
+keystrokes.
+
+The `cab` tool is a tiny wrapper around Haskell's [`cabal`]()
+package-building tool that rewrites the argument list so that the
+"new-style" project commands can be invoked without the `new-`
+prefix. Otherwise, it simply passes on all other arguments to `cabal`
+unmodified, and thus should be mostly a drop-in convenience tool to
+shorten some cabal invocations.
+
+# Example Usages
+
+```bash
+# cab-specific usage help
+$ cab --cab-help
+# standard cabal help
+$ cab --help
+# building a project using a new-style build
+$ cab build
+# configuring a project using a new-style build
+$ cab configure --enable-tests
+# running an application
+$ cab run
+```
+
+# Warning
+
+As of the time of writing, `cab`'s logic is very simplistic: it
+rewrites based on a set of known strings and tries to only rewrite
+values once. Consequently, an invocation like `cab test run` will
+rewrite to `cabal new-test run`, but it does mean that, in rare and
+unusual cases, it might apply this logic incorrectly. In the future,
+`cab` ought to properly replicate (a subset of) Cabal's command-line
+parsing logic, but it does not yet!

+ 120 - 0
src/main.rs

@@ -0,0 +1,120 @@
+use std::env;
+use std::process;
+use std::os::unix::process::CommandExt;
+
+const USAGE_HINT: &'static str =
+    "NOTICE ABOUT CAB:
+  Cabal was invoked using cab, a wrapper script around
+  Cabal. For cab-specific help, use --cab-help.\n";
+
+const USAGE: &'static str =
+    "Command line Cabal wrapper for new-style builds
+
+  Usage: cab [GLOBAL_FLAGS] [COMMAND [FLAGS]]
+
+cab for the most part has the exact same usage as Cabal, but it
+deliberately rewrites certain commands so that they are understood as
+the 'new-style project' commands.
+
+Certain old-style commands cannot be invoked through cab (as running
+`cab test' will always be treated as `cabal new-test` and not `cabal
+test') but any command which does not have a new-style equivalent
+(such as `check') will be run identically.
+
+Commands which differ from Cabal:
+    cab build     => cabal new-build
+    cab configure => cabal new-configure
+    cab conf      => cabal new-configure
+    cab repl      => cabal new-repl
+    cab run       => cabal new-run
+    cab test      => cabal new-test
+    cab bench     => cabal new-bench
+    cab freeze    => cabal new-freeze
+    cab haddock   => cabal new-haddock
+    cab update    => cabal new-update
+    cab install   => cabal new-install
+    cab exec      => cabal new-exec
+";
+
+/// A function which knows all the replaceable arguments. This will
+/// return None if it's not one of the new-style commands it knows
+/// about.
+fn replace(s: &str) -> Option<String> {
+    Some(match s {
+        "build" => "new-build".to_owned(),
+        "configure" => "new-configure".to_owned(),
+        "repl" => "new-repl".to_owned(),
+        "run" => "new-run".to_owned(),
+        "test" => "new-test".to_owned(),
+        "bench" => "new-bench".to_owned(),
+        "freeze" => "new-freeze".to_owned(),
+        "haddock" => "new-haddock".to_owned(),
+        _ => None?,
+    })
+}
+
+/// This is a tiny stateful iterator that has some logic around
+/// whether to rewrite arguments. (Not enough logic yet, I think, but
+/// this is fine for a first pass.)
+struct CabalArgs {
+    seen_cmd: bool,
+    args: env::Args,
+}
+
+impl CabalArgs {
+    fn new() -> CabalArgs {
+        let mut args = env::args();
+        let _ = args.next();
+        CabalArgs {
+            seen_cmd: false,
+            args: args,
+        }
+    }
+}
+
+impl Iterator for CabalArgs {
+    type Item = String;
+
+    fn next(&mut self) -> Option<String> {
+        // Once we've seen a proper command, then we don't want to do
+        // any kind of rewriting of the rest of it, so we just pass
+        // the arguments on
+        if self.seen_cmd {
+            self.args.next()
+        } else {
+            // if we haven't seen a proper command yet, then we can
+            // keep intercepting arguments
+            self.args.next().and_then( |arg| {
+                if &arg == "--help" {
+                    // if it's asking for help, then we should also
+                    // add a short note about how the usage for `cab`
+                    // is a bit different, but otherwise let `cabal`
+                    // print whatever it wants
+                    println!("{}", USAGE_HINT);
+                    Some(arg)
+                } else if &arg == "--cab-help" {
+                    // We totally intercept --cab-help to display our
+                    // own full help string and leave it at that.
+                    println!("{}", USAGE);
+                    process::exit(0);
+                } else if let Some(r) = replace(&arg) {
+                    // If there is a replacement for this fragment,
+                    // then we've now seen a command (and shouldn't do
+                    // anything else) but we should return the
+                    // replacement string (i.e. new-build for build)
+                    self.seen_cmd = true;
+                    Some(r)
+                } else {
+                    // otherwise, keep going!
+                    Some(arg)
+                }
+            })
+        }
+    }
+}
+
+fn main() {
+    process::Command::new("cabal")
+        .args(CabalArgs::new())
+        .exec();
+}