use std::borrow::Cow;
use std::collections::HashMap;
use std::fs;
use std::path::PathBuf;
use clap;
use open;
use regex::Regex;
use consts;
use error::{Blame, MainError, Result, ResultExt};
use platform;
lazy_static! {
static ref RE_SUB: Regex = Regex::new(r#"#\{([A-Za-z_][A-Za-z0-9_]*)}"#).unwrap();
}
#[derive(Debug)]
pub enum Args {
Dump { name: String },
List,
Show { path: bool },
}
impl Args {
pub fn subcommand() -> clap::App<'static, 'static> {
use clap::{AppSettings, Arg, SubCommand};
SubCommand::with_name("templates")
.about("Manage Cargo Script expression templates.")
.setting(AppSettings::SubcommandRequiredElseHelp)
.subcommand(SubCommand::with_name("dump")
.about("Outputs the contents of a template to standard output.")
.arg(Arg::with_name("template")
.help("Name of template to dump.")
.index(1)
.required(true)
)
)
.subcommand(SubCommand::with_name("list")
.about("List the available templates.")
)
.subcommand(SubCommand::with_name("show")
.about("Open the template folder in a file browser.")
.arg(Arg::with_name("show_path")
.help("Output the path to the template folder to standard output instead.")
.long("path")
)
)
}
pub fn parse(m: &clap::ArgMatches) -> Self {
match m.subcommand() {
("dump", Some(m)) => {
Args::Dump {
name: m.value_of("template").unwrap().into(),
}
},
("list", _) => Args::List,
("show", Some(m)) => {
Args::Show {
path: m.is_present("show_path"),
}
},
(name, _) => panic!("bad subcommand: {:?}", name)
}
}
}
pub fn try_main(args: Args) -> Result<i32> {
match args {
Args::Dump { name } => try!(dump(&name)),
Args::List => try!(list()),
Args::Show { path } => try!(show(path)),
}
Ok(0)
}
pub fn expand(src: &str, subs: &HashMap<&str, &str>) -> Result<String> {
let sub_size = subs
.iter()
.map(|(_, v)| v.len())
.sum::<usize>();
let est_size = src.len() + sub_size;
let mut anchor = 0;
let mut result = String::with_capacity(est_size);
for m in RE_SUB.captures_iter(src) {
let (m_start, m_end) = {
let m_0 = m.get(0).unwrap();
(m_0.start(), m_0.end())
};
let prior_slice = anchor..m_start;
anchor = m_end;
result.push_str(&src[prior_slice]);
let sub_name = m.get(1).unwrap().as_str();
match subs.get(sub_name) {
Some(s) => result.push_str(s),
None => return Err(MainError::OtherOwned(Blame::Human, format!("substitution `{}` in template is unknown", sub_name))),
}
}
result.push_str(&src[anchor..]);
Ok(result)
}
pub fn get_template_path() -> Result<PathBuf> {
if cfg!(debug_assertions) {
use std::env;
if let Ok(path) = env::var("CARGO_SCRIPT_DEBUG_TEMPLATE_PATH") {
return Ok(path.into());
}
}
let cache_path = try!(platform::get_config_dir());
Ok(cache_path.join("script-templates"))
}
pub fn get_template(name: &str) -> Result<Cow<'static, str>> {
use std::io::Read;
let base = try!(get_template_path());
let file = fs::File::open(base.join(format!("{}.rs", name)))
.map_err(MainError::from)
.err_tag(format!("template file `{}.rs` does not exist in {}",
name,
base.display()))
.shift_blame(Blame::Human);
if file.is_err() {
if let Some(text) = builtin_template(name) {
return Ok(text.into())
}
}
let mut file = try!(file);
let mut text = String::new();
try!(file.read_to_string(&mut text));
Ok(text.into())
}
fn builtin_template(name: &str) -> Option<&'static str> {
Some(match name {
"expr" => consts::EXPR_TEMPLATE,
"file" => consts::FILE_TEMPLATE,
"loop" => consts::LOOP_TEMPLATE,
"loop-count" => consts::LOOP_COUNT_TEMPLATE,
_ => return None,
})
}
fn dump(name: &str) -> Result<()> {
let text = try!(get_template(name));
print!("{}", text);
Ok(())
}
fn list() -> Result<()> {
use std::ffi::OsStr;
let t_path = try!(get_template_path());
if !t_path.exists() {
return Err(format!("cannot list template directory `{}`: it does not exist", t_path.display()).into());
}
if !t_path.is_dir() {
return Err(format!("cannot list template directory `{}`: it is not a directory", t_path.display()).into());
}
for entry in try!(fs::read_dir(&t_path)) {
let entry = try!(entry);
if !try!(entry.file_type()).is_file() {
continue;
}
let f_path = entry.path();
if f_path.extension() != Some(OsStr::new("rs")) {
continue;
}
if let Some(stem) = f_path.file_stem() {
println!("{}", stem.to_string_lossy());
}
}
Ok(())
}
fn show(path: bool) -> Result<()> {
let t_path = try!(get_template_path());
if path {
println!("{}", t_path.display());
Ok(())
} else {
if !t_path.exists() {
try!(fs::create_dir_all(&t_path));
}
if t_path.is_dir() {
try!(open::that(&t_path));
} else {
return Err(format!("cannot open directory `{}`; it isn't a directory", t_path.display()).into());
}
Ok(())
}
}