use std::env;
use std::fs;
use std::io::{self, BufRead};
use std::path;
use std::process;
use std::thread;
use anyhow::{anyhow, Context, Result};
fn parse_yml_line(line: String) -> Result<(String, String)> {
let parsed: Vec<&str> = line.split(':').collect();
let name = String::from(parsed[0]);
let repo = parsed[1..].join(":").split_whitespace().collect();
Ok((name, repo))
}
fn write_sources(s_repo: &str, t_repo: &str, file: &path::Path) -> Result<()> {
let text = format!("schemes: {}\ntemplates: {}", s_repo, t_repo);
fs::write(file, text).with_context(|| format!("Couldn't write {:?}", file))?;
Ok(())
}
fn get_sources(file: &path::Path) -> Result<(String, String)> {
let default_s_repo = "https://github.com/chriskempson/base16-schemes-source.git";
let default_t_repo = "https://github.com/chriskempson/base16-templates-source.git";
let sources_file = match fs::File::open(file) {
Ok(contents) => contents,
Err(_) => {
write_sources(default_s_repo, default_t_repo, file)?;
fs::File::open(file).with_context(|| format!("Couldn't access {:?}", file))?
}
};
let mut s_repo = String::from(default_s_repo);
let mut t_repo = String::from(default_t_repo);
let reader = io::BufReader::new(sources_file);
for line in reader.lines() {
let (name, repo) = parse_yml_line(line?)?;
if name == "schemes" {
s_repo = repo;
} else if name == "templates" {
t_repo = repo;
}
}
write_sources(&s_repo, &t_repo, file)?;
Ok((s_repo, t_repo))
}
fn get_repo_list(file: &path::Path) -> Result<Vec<(String, String)>> {
let sources_file = fs::File::open(file).with_context(|| {
format!(
"Failed to read repository list {:?}. Try 'update lists' first?",
file
)
})?;
let mut result = Vec::new();
let reader = io::BufReader::new(sources_file);
for line in reader.lines() {
let (name, repo) = parse_yml_line(line?)?;
let first = name.chars().next();
if first != Some('#') && first != None {
result.push((name, repo));
}
}
Ok(result)
}
fn git_clone(path: &path::Path, repo: String, verbose: bool) -> Result<()> {
let _ = fs::remove_dir_all(path);
env::set_var("GIT_TERMINAL_PROMPT", "0");
let command = if verbose {
process::Command::new("git")
.arg("clone")
.arg("--depth")
.arg("1")
.arg(&repo)
.arg(path)
.status()
.with_context(|| "Failed to run git, is it installed?")?
} else {
process::Command::new("git")
.arg("clone")
.arg("--depth")
.arg("1")
.arg("--quiet")
.arg(&repo)
.arg(path)
.status()
.with_context(|| "Failed to run git, is it installed?")?
};
match command.code() {
Some(0) => Ok(()),
Some(_) | None => Err(anyhow!(
"Git clone failed on repo '{}', check if your repository lists are correct.",
&repo
)),
}
}
fn update_lists(dir: &path::Path, verbose: bool) -> Result<()> {
let sources_dir = &dir.join("sources");
if verbose {
println!("Updating sources list from sources.yaml")
}
let (schemes_source, templates_source) = get_sources(&dir.join("sources.yaml"))?;
if verbose {
println!("Schemes source: {}", schemes_source);
println!("Templates source: {}", templates_source);
}
let schemes_source_dir = sources_dir.join("schemes");
let templates_source_dir = sources_dir.join("templates");
let s_child = thread::spawn(move || git_clone(&schemes_source_dir, schemes_source, verbose));
let t_child =
thread::spawn(move || git_clone(&templates_source_dir, templates_source, verbose));
s_child.join().unwrap()?;
t_child.join().unwrap()?;
Ok(())
}
fn update_schemes(dir: &path::Path, verbose: bool) -> Result<()> {
let schemes_dir = &dir.join("schemes");
let scheme_list = &dir.join("sources").join("schemes").join("list.yaml");
if verbose {
println!("Updating schemes from source")
}
let schemes = get_repo_list(scheme_list)?;
let mut children = vec![];
for scheme in schemes {
let (name, repo) = scheme;
let current_dir = schemes_dir.join(name);
children.push(thread::spawn(move || {
git_clone(¤t_dir, repo, verbose)
}));
}
for child in children {
child.join().unwrap()?;
}
Ok(())
}
fn update_templates(dir: &path::Path, verbose: bool) -> Result<()> {
let templates_dir = &dir.join("templates");
let template_list = &dir.join("sources").join("templates").join("list.yaml");
if verbose {
println!("Updating templates from source")
}
let templates = get_repo_list(template_list)?;
let mut children = vec![];
for template in templates {
let (name, repo) = template;
let current_dir = templates_dir.join(name);
children.push(thread::spawn(move || {
git_clone(¤t_dir, repo, verbose)
}));
}
for child in children {
child.join().unwrap()?;
}
Ok(())
}
pub fn update(operation: &str, dir: &path::Path, verbose: bool) -> Result<()> {
let base16_dir = &dir.join("base16");
fs::create_dir_all(base16_dir)?;
match operation {
"lists" => update_lists(base16_dir, verbose),
"schemes" => update_schemes(base16_dir, verbose),
"templates" => update_templates(base16_dir, verbose),
"all" => {
update_lists(base16_dir, verbose)?;
update_schemes(base16_dir, verbose)?;
update_templates(base16_dir, verbose)
}
_ => Err(anyhow!("Invalid operation")),
}
}