Skip to content

Commit e45eb05

Browse files
committed
fix(CLI): Display for Errors + refactor
* refactored errors into a hierarchy * implemented `Display` trait for all error types, including some 'hierarchy-aware' printing. Fixes #54
1 parent 4548644 commit e45eb05

5 files changed

Lines changed: 89 additions & 22 deletions

File tree

src/mako/cli/lib/cli.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
OUTPUT_FLAG = 'o'
1616
VALUE_ARG = 'v'
1717
SCOPE_FLAG = 'scope'
18+
CONFIG_DIR_FLAG = 'config-dir'
1819

1920
FILE_ARG = '<file>'
2021
MIME_ARG = '<mime>'

src/mako/cli/lib/docopt.mako

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
<%!
33
from util import (put_and, supports_scopes)
44
from cli import (mangle_subcommand, new_method_context, PARAM_FLAG, STRUCT_FLAG, UPLOAD_FLAG, OUTPUT_FLAG, VALUE_ARG,
5-
CONFIG_DIR, SCOPE_FLAG, is_request_value_property, FIELD_SEP, docopt_mode, FILE_ARG, MIME_ARG, OUT_ARG)
5+
CONFIG_DIR, SCOPE_FLAG, is_request_value_property, FIELD_SEP, docopt_mode, FILE_ARG, MIME_ARG, OUT_ARG,
6+
CONFIG_DIR_FLAG)
67
78
v_arg = '<%s>' % VALUE_ARG
89
%>\
@@ -63,7 +64,7 @@ Configuration:
6364
the user to grant this application permission to use it.
6465
If unset, it defaults to the shortest scope url for a particular method.
6566
% endif scopes
66-
--config-dir <folder>
67+
--${CONFIG_DIR_FLAG} <folder>
6768
A directory into which we will store our persistent data. Defaults to a user-writable
6869
directory that we will create during the first invocation.
6970
[default: ${CONFIG_DIR}]

src/mako/cli/lib/engine.mako

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
%>\
88
<%def name="new(c)">\
99
mod cmn;
10-
use cmn::{InvalidOptionsError};
10+
use cmn::InvalidOptionsError;
1111
1212
use oauth2::ApplicationSecret;
1313

src/mako/cli/main.rs.mako

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ fn main() {
3030
println!("{:?}", opts);
3131
match Engine::new(opts) {
3232
Err(e) => {
33-
write!(io::stderr(), "{:?}", e).ok();
33+
write!(io::stderr(), "{}", e).ok();
3434
env::set_exit_status(e.exit_code);
3535
},
3636
Ok(mut engine) => {

src/rust/cli/cmn.rs

Lines changed: 83 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,70 @@ use rustc_serialize::json;
44
use std::fs;
55
use std::env;
66
use std::io;
7+
use std::fmt;
78
use std::path::Path;
9+
use std::cell::RefCell;
810

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

1113
use std::default::Default;
1214

1315
#[derive(Debug)]
14-
pub enum ArgumentError {
15-
ConfigurationDirectoryInaccessible((String, io::Error)),
16-
ConfigurationDirectoryUnset,
17-
UsernameExpansionFailed(String),
16+
pub enum ApplicationSecretError {
17+
DecoderError((String, json::DecoderError)),
18+
FormatError(String),
19+
}
20+
21+
impl fmt::Display for ApplicationSecretError {
22+
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
23+
match *self {
24+
ApplicationSecretError::DecoderError((ref path, ref err))
25+
=> writeln!(f, "Could not decode file at '{}' with error: {}"
26+
, path, err),
27+
ApplicationSecretError::FormatError(ref path)
28+
=> writeln!(f, "'installed' field is unset in secret file at '{}'"
29+
, path),
30+
}
31+
}
32+
}
33+
34+
#[derive(Debug)]
35+
pub enum ConfigurationError {
36+
DirectoryCreationFailed((String, io::Error)),
37+
DirectoryUnset,
38+
HomeExpansionFailed(String),
39+
Secret(ApplicationSecretError),
1840
IOError((String, io::Error)),
19-
SecretDecoderError((String, json::DecoderError)),
20-
SecretFormatError(String),
41+
}
42+
43+
impl fmt::Display for ConfigurationError {
44+
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
45+
match *self {
46+
ConfigurationError::DirectoryCreationFailed((ref dir, ref err))
47+
=> writeln!(f, "Directory '{}' could not be created with error: {}", dir, err),
48+
ConfigurationError::DirectoryUnset
49+
=> writeln!(f, "--config-dir was unset or empty"),
50+
ConfigurationError::HomeExpansionFailed(ref dir)
51+
=> writeln!(f, "Couldn't find HOME directory of current user, failed to expand '{}'", dir),
52+
ConfigurationError::Secret(ref err)
53+
=> writeln!(f, "Secret -> {}", err),
54+
ConfigurationError::IOError((ref path, ref err))
55+
=> writeln!(f, "IO operation failed on path '{}' with error: {}", path, err),
56+
}
57+
}
58+
}
59+
60+
#[derive(Debug)]
61+
pub enum ArgumentError {
62+
Configuration(ConfigurationError),
63+
}
64+
65+
impl fmt::Display for ArgumentError {
66+
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
67+
match *self {
68+
ArgumentError::Configuration(ref err) => writeln!(f, "Configuration -> {}", err)
69+
}
70+
}
2171
}
2272

2373
#[derive(Debug)]
@@ -26,6 +76,15 @@ pub struct InvalidOptionsError {
2676
pub exit_code: i32,
2777
}
2878

79+
impl fmt::Display for InvalidOptionsError {
80+
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
81+
for issue in &self.issues {
82+
try!(issue.fmt(f));
83+
}
84+
Ok(())
85+
}
86+
}
87+
2988
impl InvalidOptionsError {
3089
pub fn single(err: ArgumentError, exit_code: i32) -> InvalidOptionsError {
3190
InvalidOptionsError {
@@ -38,13 +97,13 @@ impl InvalidOptionsError {
3897
pub fn assure_config_dir_exists(dir: &str) -> Result<String, ArgumentError> {
3998
let trdir = dir.trim();
4099
if trdir.len() == 0 {
41-
return Err(ArgumentError::ConfigurationDirectoryUnset)
100+
return Err(ArgumentError::Configuration(ConfigurationError::DirectoryUnset))
42101
}
43102

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

57116
if let Err(err) = fs::create_dir(&expanded_config_dir) {
58117
if err.kind() != io::ErrorKind::AlreadyExists {
59-
return Err(ArgumentError::ConfigurationDirectoryInaccessible((expanded_config_dir, err)))
118+
return Err(ArgumentError::Configuration(
119+
ConfigurationError::DirectoryCreationFailed((expanded_config_dir, err))))
60120
}
61121
}
62122

@@ -67,15 +127,15 @@ pub fn application_secret_from_directory(dir: &str, secret_basename: &str) -> Re
67127
let secret_path = Path::new(dir).join(secret_basename);
68128
let secret_str = || secret_path.as_path().to_str().unwrap().to_string();
69129
let secret_io_error = |io_err: io::Error| {
70-
Err(ArgumentError::IOError(
130+
Err(ArgumentError::Configuration(ConfigurationError::IOError(
71131
(secret_str(), io_err)
72-
))
132+
)))
73133
};
74134

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

96156
let json_enocded_secret = json::encode(&app_secret).unwrap();
97-
e = match fs::OpenOptions::new().create(true).write(true).open(&secret_path) {
157+
err = match fs::OpenOptions::new().create(true).write(true).open(&secret_path) {
98158
Err(cfe) => cfe,
99159
Ok(mut f) => {
100160
match f.write(json_enocded_secret.as_bytes()) {
@@ -105,20 +165,25 @@ pub fn application_secret_from_directory(dir: &str, secret_basename: &str) -> Re
105165
};
106166
// fall through to IO error handling
107167
}
108-
return secret_io_error(e)
168+
return secret_io_error(err)
109169
},
110170
Ok(mut f) => {
111171
let mut json_encoded_secret = String::new();
112172
if let Err(io_err) = f.read_to_string(&mut json_encoded_secret) {
113173
return secret_io_error(io_err)
114174
}
115175
match json::decode::<ConsoleApplicationSecret>(&json_encoded_secret) {
116-
Err(json_decode_error) => return Err(ArgumentError::SecretDecoderError(
176+
Err(json_decode_error) => return Err(ArgumentError::Configuration(
177+
ConfigurationError::Secret(ApplicationSecretError::DecoderError(
117178
(secret_str(), json_decode_error)
118-
)),
179+
)))),
119180
Ok(console_secret) => match console_secret.installed {
120181
Some(secret) => return Ok(secret),
121-
None => return Err(ArgumentError::SecretFormatError(secret_str()))
182+
None => return Err(
183+
ArgumentError::Configuration(
184+
ConfigurationError::Secret(
185+
ApplicationSecretError::FormatError(secret_str())
186+
)))
122187
},
123188
}
124189
}

0 commit comments

Comments
 (0)