use std::env::set_var;
use std::fs::{create_dir_all, remove_dir_all, write, File};
use std::io::{BufRead, BufReader};
use std::path::Path;
use std::process::Command;
use std::thread::spawn;
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) -> Result<()> {
let text = format!("schemes: {}\ntemplates: {}", s_repo, t_repo);
write(file, text).with_context(|| format!("Couldn't write {:?}", file))?;
Ok(())
}
fn get_sources(file: &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 File::open(file) {
Ok(contents) => contents,
Err(_) => {
write_sources(default_s_repo, default_t_repo, file)?;
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 = 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) -> Result<Vec<(String, String)>> {
let sources_file = File::open(file).with_context(|| {
format!(
"Failed to read repository list {:?}. Try 'update lists' first?",
file
)
})?;
let mut result = Vec::new();
let reader = 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)
}
enum CloneType {
Scheme,
Template,
List,
}
fn git_clone(path: &Path, repo: String, verbose: bool, clone_type: CloneType) -> Result<()> {
let _ = remove_dir_all(path);
set_var("GIT_TERMINAL_PROMPT", "0");
let command_clone = if verbose {
Command::new("git")
.arg("clone")
.arg("-n")
.arg(&repo)
.arg(path)
.arg("--depth")
.arg("1")
.status()
.context("Couldn't run git (is it installed?)")?
} else {
Command::new("git")
.arg("clone")
.arg("--quiet")
.arg("-n")
.arg(&repo)
.arg(path)
.arg("--depth")
.arg("1")
.status()
.context("Couldn't run git (is it installed?)")?
};
if verbose {
println!("checking out on {:?}", path)
}
let command_checkout = match clone_type {
CloneType::Scheme => Command::new("git")
.arg("-C")
.arg(path)
.arg("checkout")
.arg("--quiet")
.arg("HEAD")
.arg("*.y*ml")
.status()
.context("Couldn't run git (is it installed?)")?,
CloneType::Template => Command::new("git")
.arg("-C")
.arg(path)
.arg("checkout")
.arg("--quiet")
.arg("HEAD")
.arg("templates")
.status()
.context("Couldn't run git (is it installed?)")?,
CloneType::List => Command::new("git")
.arg("-C")
.arg(path)
.arg("checkout")
.arg("--quiet")
.arg("HEAD")
.arg("list.yaml")
.status()
.context("Couldn't run git (is it installed?)")?,
};
let command_clone = command_clone
.code()
.ok_or_else(|| anyhow!("Failed to clone with git (is it installed?)"))?;
let command_checkout = command_checkout
.code()
.ok_or_else(|| anyhow!("Failed to checkout files with git"))?;
let _ = remove_dir_all(path.join(".git"));
if command_clone != 0 || command_checkout != 0 {
Err(anyhow!(
"Git failed to run on repository '{}'. Check if your repo list is valid.",
repo
))
} else {
Ok(())
}
}
fn update_lists(dir: &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 = spawn(move || {
git_clone(
&schemes_source_dir,
schemes_source,
verbose,
CloneType::List,
)
});
let t_child = spawn(move || {
git_clone(
&templates_source_dir,
templates_source,
verbose,
CloneType::List,
)
});
s_child.join().unwrap()?;
t_child.join().unwrap()?;
Ok(())
}
fn update_schemes(dir: &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(spawn(move || {
git_clone(¤t_dir, repo, verbose, CloneType::Scheme)
}));
}
for child in children {
child.join().unwrap()?;
}
Ok(())
}
fn update_templates(dir: &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(spawn(move || {
git_clone(¤t_dir, repo, verbose, CloneType::Template)
}));
}
for child in children {
child.join().unwrap()?;
}
Ok(())
}
pub fn update(operation: &str, dir: &Path, verbose: bool) -> Result<()> {
let base16_dir = &dir.join("base16");
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")),
}
}