Skip to content

Commit

Permalink
fix(CLI): Display for Errors + refactor
Browse files Browse the repository at this point in the history
* refactored errors into a hierarchy
* implemented `Display` trait for all error types, including some
  'hierarchy-aware' printing.

Fixes #54
  • Loading branch information
Byron committed Apr 14, 2015
1 parent 4548644 commit e45eb05
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 22 deletions.
1 change: 1 addition & 0 deletions src/mako/cli/lib/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
OUTPUT_FLAG = 'o'
VALUE_ARG = 'v'
SCOPE_FLAG = 'scope'
CONFIG_DIR_FLAG = 'config-dir'

FILE_ARG = '<file>'
MIME_ARG = '<mime>'
Expand Down
5 changes: 3 additions & 2 deletions src/mako/cli/lib/docopt.mako
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
<%!
from util import (put_and, supports_scopes)
from cli import (mangle_subcommand, new_method_context, PARAM_FLAG, STRUCT_FLAG, UPLOAD_FLAG, OUTPUT_FLAG, VALUE_ARG,
CONFIG_DIR, SCOPE_FLAG, is_request_value_property, FIELD_SEP, docopt_mode, FILE_ARG, MIME_ARG, OUT_ARG)
CONFIG_DIR, SCOPE_FLAG, is_request_value_property, FIELD_SEP, docopt_mode, FILE_ARG, MIME_ARG, OUT_ARG,
CONFIG_DIR_FLAG)
v_arg = '<%s>' % VALUE_ARG
%>\
Expand Down Expand Up @@ -63,7 +64,7 @@ Configuration:
the user to grant this application permission to use it.
If unset, it defaults to the shortest scope url for a particular method.
% endif scopes
--config-dir <folder>
--${CONFIG_DIR_FLAG} <folder>
A directory into which we will store our persistent data. Defaults to a user-writable
directory that we will create during the first invocation.
[default: ${CONFIG_DIR}]
Expand Down
2 changes: 1 addition & 1 deletion src/mako/cli/lib/engine.mako
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
%>\
<%def name="new(c)">\
mod cmn;
use cmn::{InvalidOptionsError};
use cmn::InvalidOptionsError;
use oauth2::ApplicationSecret;
Expand Down
2 changes: 1 addition & 1 deletion src/mako/cli/main.rs.mako
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ fn main() {
println!("{:?}", opts);
match Engine::new(opts) {
Err(e) => {
write!(io::stderr(), "{:?}", e).ok();
write!(io::stderr(), "{}", e).ok();
env::set_exit_status(e.exit_code);
},
Ok(mut engine) => {
Expand Down
101 changes: 83 additions & 18 deletions src/rust/cli/cmn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,70 @@ use rustc_serialize::json;
use std::fs;
use std::env;
use std::io;
use std::fmt;
use std::path::Path;
use std::cell::RefCell;

use std::io::{Write, Read};

use std::default::Default;

#[derive(Debug)]
pub enum ArgumentError {
ConfigurationDirectoryInaccessible((String, io::Error)),
ConfigurationDirectoryUnset,
UsernameExpansionFailed(String),
pub enum ApplicationSecretError {
DecoderError((String, json::DecoderError)),
FormatError(String),
}

impl fmt::Display for ApplicationSecretError {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
match *self {
ApplicationSecretError::DecoderError((ref path, ref err))
=> writeln!(f, "Could not decode file at '{}' with error: {}"
, path, err),
ApplicationSecretError::FormatError(ref path)
=> writeln!(f, "'installed' field is unset in secret file at '{}'"
, path),
}
}
}

#[derive(Debug)]
pub enum ConfigurationError {
DirectoryCreationFailed((String, io::Error)),
DirectoryUnset,
HomeExpansionFailed(String),
Secret(ApplicationSecretError),
IOError((String, io::Error)),
SecretDecoderError((String, json::DecoderError)),
SecretFormatError(String),
}

impl fmt::Display for ConfigurationError {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
match *self {
ConfigurationError::DirectoryCreationFailed((ref dir, ref err))
=> writeln!(f, "Directory '{}' could not be created with error: {}", dir, err),
ConfigurationError::DirectoryUnset
=> writeln!(f, "--config-dir was unset or empty"),
ConfigurationError::HomeExpansionFailed(ref dir)
=> writeln!(f, "Couldn't find HOME directory of current user, failed to expand '{}'", dir),
ConfigurationError::Secret(ref err)
=> writeln!(f, "Secret -> {}", err),
ConfigurationError::IOError((ref path, ref err))
=> writeln!(f, "IO operation failed on path '{}' with error: {}", path, err),
}
}
}

#[derive(Debug)]
pub enum ArgumentError {
Configuration(ConfigurationError),
}

impl fmt::Display for ArgumentError {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
match *self {
ArgumentError::Configuration(ref err) => writeln!(f, "Configuration -> {}", err)
}
}
}

#[derive(Debug)]
Expand All @@ -26,6 +76,15 @@ pub struct InvalidOptionsError {
pub exit_code: i32,
}

impl fmt::Display for InvalidOptionsError {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
for issue in &self.issues {
try!(issue.fmt(f));
}
Ok(())
}
}

impl InvalidOptionsError {
pub fn single(err: ArgumentError, exit_code: i32) -> InvalidOptionsError {
InvalidOptionsError {
Expand All @@ -38,13 +97,13 @@ impl InvalidOptionsError {
pub fn assure_config_dir_exists(dir: &str) -> Result<String, ArgumentError> {
let trdir = dir.trim();
if trdir.len() == 0 {
return Err(ArgumentError::ConfigurationDirectoryUnset)
return Err(ArgumentError::Configuration(ConfigurationError::DirectoryUnset))
}

let expanded_config_dir =
if trdir.as_bytes()[0] == b'~' {
match env::var("HOME").ok().or(env::var("UserProfile").ok()) {
None => return Err(ArgumentError::UsernameExpansionFailed(trdir.to_string())),
None => return Err(ArgumentError::Configuration(ConfigurationError::HomeExpansionFailed(trdir.to_string()))),
Some(mut user) => {
user.push_str(&trdir[1..]);
user
Expand All @@ -56,7 +115,8 @@ pub fn assure_config_dir_exists(dir: &str) -> Result<String, ArgumentError> {

if let Err(err) = fs::create_dir(&expanded_config_dir) {
if err.kind() != io::ErrorKind::AlreadyExists {
return Err(ArgumentError::ConfigurationDirectoryInaccessible((expanded_config_dir, err)))
return Err(ArgumentError::Configuration(
ConfigurationError::DirectoryCreationFailed((expanded_config_dir, err))))
}
}

Expand All @@ -67,15 +127,15 @@ pub fn application_secret_from_directory(dir: &str, secret_basename: &str) -> Re
let secret_path = Path::new(dir).join(secret_basename);
let secret_str = || secret_path.as_path().to_str().unwrap().to_string();
let secret_io_error = |io_err: io::Error| {
Err(ArgumentError::IOError(
Err(ArgumentError::Configuration(ConfigurationError::IOError(
(secret_str(), io_err)
))
)))
};

for _ in 0..2 {
match fs::File::open(&secret_path) {
Err(mut e) => {
if e.kind() == io::ErrorKind::NotFound {
Err(mut err) => {
if err.kind() == io::ErrorKind::NotFound {
// Write our built-in one - user may adjust the written file at will
let secret = ApplicationSecret {
client_id: "14070749909-vgip2f1okm7bkvajhi9jugan6126io9v.apps.googleusercontent.com".to_string(),
Expand All @@ -94,7 +154,7 @@ pub fn application_secret_from_directory(dir: &str, secret_basename: &str) -> Re
};

let json_enocded_secret = json::encode(&app_secret).unwrap();
e = match fs::OpenOptions::new().create(true).write(true).open(&secret_path) {
err = match fs::OpenOptions::new().create(true).write(true).open(&secret_path) {
Err(cfe) => cfe,
Ok(mut f) => {
match f.write(json_enocded_secret.as_bytes()) {
Expand All @@ -105,20 +165,25 @@ pub fn application_secret_from_directory(dir: &str, secret_basename: &str) -> Re
};
// fall through to IO error handling
}
return secret_io_error(e)
return secret_io_error(err)
},
Ok(mut f) => {
let mut json_encoded_secret = String::new();
if let Err(io_err) = f.read_to_string(&mut json_encoded_secret) {
return secret_io_error(io_err)
}
match json::decode::<ConsoleApplicationSecret>(&json_encoded_secret) {
Err(json_decode_error) => return Err(ArgumentError::SecretDecoderError(
Err(json_decode_error) => return Err(ArgumentError::Configuration(
ConfigurationError::Secret(ApplicationSecretError::DecoderError(
(secret_str(), json_decode_error)
)),
)))),
Ok(console_secret) => match console_secret.installed {
Some(secret) => return Ok(secret),
None => return Err(ArgumentError::SecretFormatError(secret_str()))
None => return Err(
ArgumentError::Configuration(
ConfigurationError::Secret(
ApplicationSecretError::FormatError(secret_str())
)))
},
}
}
Expand Down

0 comments on commit e45eb05

Please sign in to comment.