Skip to content

Commit

Permalink
switch cli parsing from clap to docopt
Browse files Browse the repository at this point in the history
  • Loading branch information
dherman committed Jan 17, 2018
1 parent 7c8b78c commit f0f8d8f
Show file tree
Hide file tree
Showing 12 changed files with 688 additions and 449 deletions.
584 changes: 291 additions & 293 deletions Cargo.lock

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ name = "launchscript"
path = "src/launchscript.rs"

[dependencies]
clap = "2.26.2"
docopt = "0.8"
notion-core = { path = "crates/notion-core" }
serde = "1.0"
serde_derive = "1.0"
console = "0.5"

[workspace]
2 changes: 1 addition & 1 deletion crates/node-archive/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ version = "0.1.0"
authors = ["David Herman <david.herman@gmail.com>"]

[dependencies]
flate2 = "0.2.20"
flate2 = "1.0"
tar = "0.4.13"
zip = "0.2.6"
reqwest = "0.7.3"
Expand Down
4 changes: 2 additions & 2 deletions crates/node-archive/src/tarball.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,13 +106,13 @@ impl<S: Source, F: FnMut(&(), usize)> ProgressSource<S, F> {
fn new(source: S, callback: F) -> io::Result<ProgressSource<S, F>> {
match source.uncompressed_size() {
Some(size) => {
let decoded = GzDecoder::new(source)?;
let decoded = GzDecoder::new(source);
Ok(ProgressSource::Uncompressed(size, ProgressRead::new(decoded, (), callback)))
}
None => {
let size = source.compressed_size();
let progress = ProgressRead::new(source, (), callback);
Ok(ProgressSource::Compressed(size, GzDecoder::new(progress)?))
Ok(ProgressSource::Compressed(size, GzDecoder::new(progress)))
}
}
}
Expand Down
44 changes: 44 additions & 0 deletions src/command/activate.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
use docopt::{self, Docopt};
use notion_core::{global, die};
use notion_core::version::Version;
use std::process::exit;

pub const USAGE: &'static str = "
Activate a particular toolchain version
Usage:
notion use [options] <version>
notion use -h | --help
Options:
-h, --help Display this message
-g, --global Activate the toolchain globally
";

#[derive(Debug, Deserialize)]
struct Args {
arg_version: Option<String>,
flag_global: bool
}

pub fn run(mut args: Vec<String>, _verbose: bool) -> Result<(), docopt::Error> {
let mut argv = vec![String::from("notion"), String::from("use")];
argv.append(&mut args);

let args: Args = Docopt::new(USAGE)
.and_then(|d| d.argv(argv).deserialize())?;

if args.flag_global {
// FIXME: compute the default
let version = args.arg_version.unwrap();
match global::set(Version::Public(version)) {
Ok(_) => { }
Err(err) => { die(err); }
}
} else {
println!("not yet implemented; in the meantime you can modify your package.json.");
exit(1);
}

Ok(())
}
63 changes: 63 additions & 0 deletions src/command/current.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
use docopt::{self, Docopt};
use std::process::exit;
use notion_core::{current, die};

pub const USAGE: &'static str = "
Display the currently activated toolchain
Usage:
notion current [options]
Options:
-h, --help Display this message
-l, --local Display local toolchain
-g, --global Display global toolchain
";

#[derive(Debug, Deserialize)]
struct Args {
flag_local: bool,
flag_global: bool
}

pub fn run(mut args: Vec<String>) -> Result<(), docopt::Error> {
let mut argv = vec![String::from("notion"), String::from("current")];
argv.append(&mut args);

let args: Args = Docopt::new(USAGE)
.and_then(|d| d.argv(argv).deserialize())?;

if args.flag_local && !args.flag_global {
match current::local() {
Ok(Some(version)) => { println!("v{}", version); }
Ok(None) => { exit(1); }
Err(err) => { die(err); }
}
} else if args.flag_global && !args.flag_local {
match current::global() {
Ok(Some(version)) => { println!("v{}", version); }
Ok(None) => { exit(1); }
Err(err) => { die(err); }
}
} else {
match current::both() {
Ok((local, global)) => {
let global_active = local.is_none() && global.is_some();
let none = local.is_none() && global.is_none();
// FIXME: abstract this
for version in local {
println!("local: v{} (active)", version);
}
for version in global {
println!("global: v{}{}", version, if global_active { " (active)" } else { "" });
}
if none {
exit(1);
}
}
Err(err) => { die(err); }
}
}

Ok(())
}
62 changes: 62 additions & 0 deletions src/command/help.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
use docopt::{self, Docopt};
use std::process::exit;
use console::style;

const USAGE: &'static str = "
Get some help with a notion command
Usage:
notion help [<command>]
notion help -h | --help
Options:
-h, --help Display this message
";

#[derive(Debug, Deserialize)]
struct Args {
arg_command: Option<String>
}

pub fn run(mut args: Vec<String>) -> Result<(), docopt::Error> {
let mut argv = vec![String::from("notion"), String::from("help")];
argv.append(&mut args);

let args: Args = Docopt::new(USAGE)
.and_then(|d| d.argv(argv).deserialize())?;

let command = match args.arg_command {
Some(command) => command,
None => {
super::throw_help(super::USAGE)?;
panic!("can't happen")
}
};

match &command[..] {
"use" => {
super::throw_help(super::activate::USAGE)?;
}
"current" => {
super::throw_help(super::current::USAGE)?;
}
"help" => {
super::throw_help(USAGE)?;
}
"version" => {
super::throw_help(super::version::USAGE)?;
}
"install" => {
super::throw_help(super::install::USAGE)?;
}
"uninstall" => {
super::throw_help(super::uninstall::USAGE)?;
}
_ => {
eprintln!("{} Unknown subcommand: '{}'", style("error:").red().bold(), command);
exit(1);
}
}

Ok(())
}
34 changes: 34 additions & 0 deletions src/command/install.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
use docopt::{self, Docopt};
use std::process::exit;
use notion_core;

pub const USAGE: &'static str = "
Install a toolchain to the local machine
Usage:
notion install <version>
notion install -h | --help
Options:
-h, --help Display this message
";

#[derive(Debug, Deserialize)]
struct Args {
arg_version: String
}

pub fn run(mut args: Vec<String>, _verbose: bool) -> Result<(), docopt::Error> {
let mut argv = vec![String::from("notion"), String::from("install")];
argv.append(&mut args);

let args: Args = Docopt::new(USAGE)
.and_then(|d| d.argv(argv).deserialize())?;

if let Err(err) = notion_core::install::by_version(&args.arg_version) {
notion_core::display_error(err);
exit(1);
}

Ok(())
}
122 changes: 122 additions & 0 deletions src/command/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
pub mod install;
pub mod uninstall;
pub mod current;
pub mod activate;
pub mod help;
pub mod version;

use docopt::{self, Docopt};
use console::style;
use std::process::exit;

pub const VERSION: &'static str = env!("CARGO_PKG_VERSION");

pub const USAGE: &'static str = "
Notion: the hassle-free Node.js manager
Usage:
notion [-v | --verbose] <command> [<args> ...]
notion -h | --help
notion -V | --version
Options:
-h, --help Display this message
-V, --version Print version info and exit
-v, --verbose Use verbose output
Some common notion commands are:
install Install a toolchain to the local machine
uninstall Uninstall a toolchain from the local machine
use Activate a particular toolchain version
current Display the currently activated toolchain version
help Display this message
version Print version info and exit
See 'notion help <command>' for more information on a specific command.
";

#[derive(Debug, Deserialize)]
struct Args {
arg_command: Option<Command>,
arg_args: Vec<String>,
flag_version: bool,
flag_verbose: bool
}

#[derive(Debug, Deserialize)]
enum Command {
Install,
Uninstall,
Use,
Current,
Help,
Version
}

fn docopt_help_error(e: &docopt::Error) -> Option<&str> {
match e {
&docopt::Error::WithProgramUsage(ref err, ref usage) => {
if let &docopt::Error::Help = &**err {
Some(usage)
} else {
None
}
}
&docopt::Error::Help => {
Some(USAGE)
}
_ => None
}
}

fn exit_with(e: docopt::Error) -> ! {
// Docopt prints help messages to stdout but stderr is more traditional.
if let Some(usage) = docopt_help_error(&e) {
eprintln!("{}", usage);
exit(0);
}

// Prefix fatal errors with a red, bold "error: " prefix.
if e.fatal() {
eprint!("{} ", style("error:").red().bold());
}

// Now let docopt do the rest.
e.exit()
}

pub fn throw_help(usage: &str) -> Result<(), docopt::Error> {
Err(docopt::Error::WithProgramUsage(Box::new(docopt::Error::Help), String::from(usage.trim())))
}

pub fn run() {
Docopt::new(USAGE)
.and_then(|d| d.options_first(true).version(Some(String::from(VERSION))).deserialize())
.and_then(|args: Args| {
match args.arg_command {
None => {
throw_help(USAGE)
}
Some(Command::Install) => {
install::run(args.arg_args, args.flag_verbose)
}
Some(Command::Uninstall) => {
uninstall::run(args.arg_args, args.flag_verbose)
}
Some(Command::Use) => {
activate::run(args.arg_args, args.flag_verbose)
}
Some(Command::Current) => {
current::run(args.arg_args)
}
Some(Command::Help) => {
help::run(args.arg_args)
}
Some(Command::Version) => {
version::run(args.arg_args)
}
}
})
.unwrap_or_else(|e| exit_with(e));

}

0 comments on commit f0f8d8f

Please sign in to comment.