#![allow(dead_code)]
extern crate liquid;
extern crate os_info;
extern crate walkdir;
use std::cmp::Ordering;
use std::fs;
use std::io::prelude::*;
use std::path::{Path, PathBuf};
pub struct SROutput {
pub status: i32,
pub wrapped_status: i32,
pub stdout: Vec<String>,
pub stderr: Vec<String>,
}
pub fn create_component(
target_dir: &Path,
name: String,
source_license: String,
doc_license: String,
) -> SROutput {
let mut output = SROutput {
status: 0,
wrapped_status: 0,
stderr: Vec::new(),
stdout: Vec::new(),
};
let component_dir: PathBuf;
if target_dir.join(".sr").exists() {
component_dir = target_dir.join("components").join(&name);
} else {
component_dir = target_dir.join(&name);
}
match fs::create_dir(&component_dir) {
Ok(_) => (),
Err(e) => {
output.status = 11;
output.stderr.push(format!(
"ERROR: Could not create component directory: {}",
e
));
}
};
if !component_dir.join("components").exists() {
match fs::create_dir(component_dir.join("components")) {
Ok(_) => (),
Err(e) => {
output.status = 12;
output.stderr.push(format!(
"ERROR: Could not create components directory: {}",
e
));
}
};
match fs::File::create(component_dir.join("components").join(".ph")) {
Ok(_) => (),
Err(e) => {
output.status = 21;
output.stderr.push(format!(
"ERROR: Could not create placeholder file in components directory: {}",
e
));
}
};
} else {
output.stdout.push(String::from(
"components directory already exists, using existing directory.",
));
}
if !component_dir.join("dist").exists() {
match fs::create_dir(component_dir.join("dist")) {
Ok(_) => (),
Err(e) => {
output.status = 13;
output
.stderr
.push(format!("ERROR: Could not create dist directory: {}", e));
}
};
match fs::File::create(component_dir.join("dist").join(".ph")) {
Ok(_) => (),
Err(e) => {
output.status = 21;
output.stderr.push(format!(
"ERROR: Could not create placeholder file in dist directory: {}",
e
));
}
};
} else {
output.stdout.push(String::from(
"dist directory already exists, using existing directory.",
));
}
if !component_dir.join("docs").exists() {
match fs::create_dir(component_dir.join("docs")) {
Ok(_) => (),
Err(e) => {
output.status = 14;
output
.stderr
.push(format!("ERROR: Could not create docs directory: {}", e));
}
};
match fs::File::create(component_dir.join("docs").join(".ph")) {
Ok(_) => (),
Err(e) => {
output.status = 21;
output.stderr.push(format!(
"ERROR: Could not create placeholder file in docs directory: {}",
e
));
}
};
} else {
output.stdout.push(String::from(
"docs directory already exists, using existing directory.",
));
}
if !component_dir.join("source").exists() {
match fs::create_dir(component_dir.join("source")) {
Ok(_) => (),
Err(e) => {
output.status = 15;
output
.stderr
.push(format!("ERROR: Could not create source directory: {}", e));
}
};
match fs::File::create(component_dir.join("source").join(".ph")) {
Ok(_) => (),
Err(e) => {
output.status = 21;
output.stderr.push(format!(
"ERROR: Could not create placeholder file in source directory: {}",
e
));
}
};
} else {
output.stdout.push(String::from(
"source directory already exists, using existing directory.",
));
}
let file_output = generate_readme(&component_dir, &name);
output = combine_sroutputs(output, file_output);
let file_output = generate_bom(&component_dir, &name);
output = combine_sroutputs(output, file_output);
let file_output = generate_package_json(&component_dir, &name, &source_license);
output = combine_sroutputs(output, file_output);
let file_output = generate_dot_file(&component_dir, &source_license, &doc_license);
output = combine_sroutputs(output, file_output);
let amal_output = amalgamate_licenses(&component_dir);
output = combine_sroutputs(output, amal_output);
output
.stdout
.push(String::from("Finished setting up component."));
output
}
pub fn remote_login(
target_dir: &Path,
url: Option<String>,
username: Option<String>,
password: Option<String>,
) -> SROutput {
let mut output = SROutput {
status: 0,
wrapped_status: 0,
stderr: Vec::new(),
stdout: Vec::new(),
};
let mut final_url = url.unwrap().to_owned();
if final_url.contains("https") {
final_url = add_user_pass_to_https(final_url, username, password);
}
if !target_dir.join(".git").exists() {
let git_output = git_sr::git_init(target_dir, &final_url);
output = combine_sroutputs(output, git_output);
} else {
let git_output = git_sr::git_set_remote_url(target_dir, &final_url);
output = combine_sroutputs(output, git_output);
}
output
}
pub fn upload_component(
target_dir: &Path,
message: String,
url: String,
username: Option<String>,
password: Option<String>,
) -> SROutput {
let mut output = amalgamate_licenses(&target_dir);
if !target_dir.join(".git").exists() {
let mut final_url = url.to_owned();
if final_url.contains("https") {
final_url = add_user_pass_to_https(final_url, username, password);
}
let git_output = git_sr::git_init(target_dir, &final_url);
output = combine_sroutputs(output, git_output);
}
if !target_dir.join(".gitignore").exists() {
let file_output = generate_gitignore(&target_dir);
output = combine_sroutputs(output, file_output);
}
let git_output = git_sr::git_add_and_commit(target_dir, message);
output = combine_sroutputs(output, git_output);
output
.stdout
.push(String::from("Done uploading component."));
output
}
fn add_user_pass_to_https(
url: String,
username: Option<String>,
password: Option<String>,
) -> String {
let mut userpass = String::new();
let mut final_url = String::new();
if username.is_some() && password.is_some() {
userpass.push_str("https://");
userpass.push_str(&username.unwrap());
userpass.push_str(":");
userpass.push_str(&password.unwrap());
userpass.push_str("@");
final_url = url.replace("https://", &userpass);
}
final_url
}
pub fn refactor(
target_dir: &Path,
name: String,
url: String,
username: Option<String>,
password: Option<String>,
) -> SROutput {
let mut output = SROutput {
status: 0,
wrapped_status: 0,
stderr: Vec::new(),
stdout: Vec::new(),
};
let component_dir = target_dir.join("components").join(&name);
let mut remote_url = String::new();
if url.starts_with("git@") {
remote_url.push_str("git+ssh://");
remote_url.push_str(&url);
} else {
remote_url = url.to_owned();
}
if component_dir.exists() {
output = upload_component(
&component_dir,
String::from("Initial commit, refactoring component"),
url.to_owned(),
username,
password,
);
let remove_output = remove(&target_dir, &name);
output = combine_sroutputs(output, remove_output);
let add_output = add_remote_component(&target_dir, &remote_url, None);
output = combine_sroutputs(output, add_output);
let amal_output = amalgamate_licenses(&target_dir);
output = combine_sroutputs(output, amal_output);
} else {
output.status = 10;
output.stderr.push(String::from(
"ERROR: The component does not exist in the components directory.",
));
return output;
}
output.stdout.push(String::from(
"Finished refactoring local component to remote repository.",
));
output
}
pub fn remove(target_dir: &Path, name: &str) -> SROutput {
let mut output = SROutput {
status: 0,
wrapped_status: 0,
stderr: Vec::new(),
stdout: Vec::new(),
};
let component_dir = target_dir.join("components").join(name);
if component_dir.exists() {
output
.stdout
.push(format!("Deleting component directory {}.", name));
for entry in walkdir::WalkDir::new(&component_dir) {
let entry = match entry {
Ok(ent) => ent,
Err(e) => {
output.status = 6;
output.stderr.push(format!(
"ERROR: Could not handle entry while walking components directory tree: {}",
e
));
return output;
}
};
let md = match entry.path().metadata() {
Ok(m) => m,
Err(e) => {
output.status = 7;
output.stderr.push(format!(
"ERROR: Could not get metadata for a .git directory entry: {}",
e
));
return output;
}
};
let mut perms = md.permissions();
perms.set_readonly(false);
match fs::set_permissions(&entry.path(), perms) {
Ok(_) => (),
Err(e) => {
output.status = 8;
output.stderr.push(format!(
"ERROR: Failed to set permissions on .git directory: {}",
e
));
return output;
}
};
}
match fs::remove_dir_all(component_dir) {
Ok(_) => (),
Err(e) => {
output.status = 9;
output.stderr.push(format!(
"ERROR: not able to delete component directory: {}",
e
));
return output;
}
};
} else {
output = remove_remote_component(&target_dir, name, None);
}
let amal_output = amalgamate_licenses(&target_dir);
let mut output = combine_sroutputs(output, amal_output);
output
.stdout
.push(format!("Component {} was successfully removed.", name));
output
}
pub fn change_licenses(target_dir: &Path, source_license: String, doc_license: String) -> SROutput {
let output = update_yaml_value(&target_dir.join(".sr"), "source_license", &source_license);
let secondary_output = update_yaml_value(
&target_dir.join(".sr"),
"documentation_license",
&doc_license,
);
let output = combine_sroutputs(output, secondary_output);
let amal_output = amalgamate_licenses(&target_dir);
let output = combine_sroutputs(output, amal_output);
output
}
pub fn add_remote_component(target_dir: &Path, url: &str, cache: Option<String>) -> SROutput {
let mut output = npm_sr::npm_install(target_dir, &url, cache);
let amal_output = amalgamate_licenses(&target_dir);
output = combine_sroutputs(output, amal_output);
if output.status != 0 || output.wrapped_status != 0 {
output.stderr.push(String::from(
"ERROR: Remote component was not successfully added",
));
}
if output.status == 0 && output.wrapped_status == 0 {
output
.stdout
.push(String::from("Remote component was added successfully."));
}
output
}
pub fn remove_remote_component(target_dir: &Path, name: &str, cache: Option<String>) -> SROutput {
let mut output = npm_sr::npm_uninstall(target_dir, name, cache);
if output.status != 0 || output.wrapped_status != 0 {
output.stderr.push(String::from(
"ERROR: Component was not successfully removed",
));
}
if output.status == 0 && output.wrapped_status == 0 {
output
.stdout
.push(String::from("Component was removed successfully."));
}
output
}
pub fn download_component(target_dir: &Path, url: &str) -> SROutput {
let mut output = git_sr::git_clone(target_dir, url);
if output.status != 0 || output.wrapped_status != 0 {
output.stderr.push(String::from(
"ERROR: Component was not successfully downloaded",
));
}
if output.status == 0 && output.wrapped_status == 0 {
output
.stdout
.push(String::from("Component was downloaded successfully."));
}
output
}
pub fn update_dependencies(target_dir: &Path) -> SROutput {
let mut output = npm_sr::npm_install(target_dir, "", None);
if output.status != 0 || output.wrapped_status != 0 {
output.stderr.push(String::from(
"ERROR: Dependencies were not successfully updated",
));
}
if output.status == 0 && output.wrapped_status == 0 {
output
.stdout
.push(String::from("Dependencies were updated successfully."));
}
let amal_output = amalgamate_licenses(&target_dir);
output = combine_sroutputs(output, amal_output);
output
}
pub fn update_local_component(target_dir: &Path) -> SROutput {
let mut output = SROutput {
status: 0,
wrapped_status: 0,
stderr: Vec::new(),
stdout: Vec::new(),
};
if target_dir.join(".git").exists() {
output = git_sr::git_pull(target_dir);
let amal_output = amalgamate_licenses(&target_dir);
output = combine_sroutputs(output, amal_output);
if output.status == 0 {
output
.stdout
.push(String::from("Component updated successfully."));
} else {
output
.stdout
.push(String::from("Component not updated successfully."));
}
} else {
output.status = 1;
output.stderr.push(String::from(
"ERROR: Component is not set up as a repository, cannot update it.",
));
}
output
}
pub fn list_all_licenses(target_dir: &Path) -> String {
let nl = get_newline();
let mut license_listing = String::from("Licenses Specified In This Component:");
license_listing.push_str(&nl);
let sr_entries = get_sr_paths(target_dir);
for entry in sr_entries {
let source_value = get_yaml_value(&entry, "source_license");
let doc_value = get_yaml_value(&entry, "documentation_license");
license_listing.push_str(&format!(
"Path: {}, Source License: {}, Documentation License: {}{}",
entry.display(),
source_value,
doc_value,
nl
));
}
license_listing
}
pub fn get_licenses(target_dir: &Path) -> (String, String) {
let sr_file: PathBuf;
let mut source_license = String::from("Unlicense");
let mut doc_license = String::from("CC0-1.0");
sr_file = target_dir.join(".sr");
if sr_file.exists() {
source_license = get_yaml_value(&sr_file, "source_license");
doc_license = get_yaml_value(&sr_file, "documentation_license");
}
(source_license, doc_license)
}
pub fn get_level(target_dir: &Path) -> u8 {
let level: u8;
let current_file = target_dir.join(".sr");
let parent_file = target_dir.join(".sr");
if !parent_file.exists() && !current_file.exists() {
level = 0;
} else if !parent_file.exists() && current_file.exists() {
level = 1;
} else {
level = 2;
}
level
}
fn generate_readme(target_dir: &Path, name: &str) -> SROutput {
let mut output = SROutput {
status: 0,
wrapped_status: 0,
stderr: Vec::new(),
stdout: Vec::new(),
};
if !target_dir.join("README.md").exists() {
let mut globals = liquid::value::Object::new();
globals.insert("name".into(), liquid::value::Value::scalar(name.to_owned()));
let contents = render_template("README.md.liquid", &mut globals);
match fs::write(target_dir.join("README.md"), contents) {
Ok(_) => (),
Err(e) => {
output.status = 16;
output
.stderr
.push(format!("Could not write to README.md file: {}", e));
}
};
} else {
output.stdout.push(String::from(
"README.md already exists, using existing file and refusing to overwrite.",
));
}
output
}
fn generate_bom(target_dir: &Path, name: &str) -> SROutput {
let mut output = SROutput {
status: 0,
wrapped_status: 0,
stderr: Vec::new(),
stdout: Vec::new(),
};
if !target_dir.join("bom_data.yaml").exists() {
let mut globals = liquid::value::Object::new();
globals.insert("name".into(), liquid::value::Value::scalar(name.to_owned()));
let contents = render_template("bom_data.yaml.liquid", &mut globals);
match fs::write(target_dir.join("bom_data.yaml"), contents) {
Ok(_) => (),
Err(e) => {
output.status = 17;
output
.stderr
.push(format!("Could not write to bom_data.yaml: {}", e));
}
};
} else {
output.stdout.push(String::from(
"bom_data.yaml already exists, using existing file and refusing to overwrite.",
));
}
output
}
fn generate_package_json(target_dir: &Path, name: &str, license: &str) -> SROutput {
let mut output = SROutput {
status: 0,
wrapped_status: 0,
stderr: Vec::new(),
stdout: Vec::new(),
};
if !target_dir.join("package.json").exists() {
let mut globals = liquid::value::Object::new();
globals.insert("name".into(), liquid::value::Value::scalar(name.to_owned()));
globals.insert(
"license".into(),
liquid::value::Value::scalar(license.to_owned()),
);
let contents = render_template("package.json.liquid", &mut globals);
match fs::write(target_dir.join("package.json"), contents) {
Ok(_) => (),
Err(e) => {
output.status = 18;
output
.stderr
.push(format!("Could not write to package.json: {}", e));
}
};
} else {
output.stdout.push(String::from(
"package.json already exists, using existing file and refusing to overwrite.",
));
}
output
}
fn generate_gitignore(target_dir: &Path) -> SROutput {
let mut output = SROutput {
status: 0,
wrapped_status: 0,
stderr: Vec::new(),
stdout: Vec::new(),
};
if !target_dir.join(".gitignore").exists() {
let mut globals = liquid::value::Object::new();
let contents = render_template(".gitignore.liquid", &mut globals);
match fs::write(target_dir.join(".gitignore"), contents) {
Ok(_) => (),
Err(e) => {
output.status = 19;
output
.stderr
.push(format!("Could not write to .gitignore: {}", e));
}
};
} else {
output.stdout.push(String::from(
".gitignore already exists, using existing file and refusing to overwrite.",
));
}
output
}
fn generate_dot_file(target_dir: &Path, source_license: &str, doc_license: &str) -> SROutput {
let mut output = SROutput {
status: 0,
wrapped_status: 0,
stderr: Vec::new(),
stdout: Vec::new(),
};
if !target_dir.join(".sr").exists() {
let mut globals = liquid::value::Object::new();
globals.insert(
"source_license".into(),
liquid::value::Value::scalar(source_license.to_owned()),
);
globals.insert(
"doc_license".into(),
liquid::value::Value::scalar(doc_license.to_owned()),
);
let contents = render_template(".sr.liquid", &mut globals);
match fs::write(target_dir.join(".sr"), contents) {
Ok(_) => (),
Err(e) => {
output.status = 20;
output
.stderr
.push(format!("Could not write to .sr file: {}", e));
}
};
} else {
output.stdout.push(String::from(
".sr already exists, using existing file and refusing to overwrite.",
));
}
output
}
fn render_template(template_name: &str, globals: &mut liquid::value::Object) -> String {
let mut contents = String::new();
if template_name == ".sr.liquid" {
contents = templates::sr_file_template();
} else if template_name == ".gitignore.liquid" {
contents = templates::gitignore_template();
} else if template_name == "bom_data.yaml.liquid" {
contents = templates::bom_data_yaml_template();
} else if template_name == "package.json.liquid" {
contents = templates::package_json_template();
} else if template_name == "README.md.liquid" {
contents = templates::readme_template();
}
let template = liquid::ParserBuilder::with_liquid()
.build()
.parse(&contents)
.expect("Could not parse template using Liquid.");
let output = template
.render(globals)
.expect("Could not render template using Liquid.");
output
}
fn amalgamate_licenses(target_dir: &Path) -> SROutput {
let output = SROutput {
status: 0,
wrapped_status: 0,
stdout: Vec::new(),
stderr: Vec::new(),
};
let mut license_str = String::new();
let mut source_licenses: Vec<String> = Vec::new();
let mut doc_licenses: Vec<String> = Vec::new();
let sr_entries = get_sr_paths(target_dir);
for entry in sr_entries {
let source_value = get_yaml_value(&entry, "source_license");
let doc_value = get_yaml_value(&entry, "documentation_license");
if !source_licenses.contains(&source_value) {
source_licenses.push(source_value);
}
if !doc_licenses.contains(&doc_value) {
doc_licenses.push(doc_value);
}
}
license_str.push_str("(");
let mut i = 0;
for lic in source_licenses {
if i > 0 {
license_str.push_str(" AND ")
}
license_str.push_str(&lic);
i = i + 1;
}
if doc_licenses.len() > 0 && i > 0 {
license_str.push_str(" AND ");
}
let mut j = 0;
for lic in doc_licenses {
if j > 0 {
license_str.push_str(" AND ");
}
license_str.push_str(&lic);
j = j + 1;
}
license_str.push_str(")");
update_json_value(&target_dir.join("package.json"), "license", &license_str);
output
}
fn get_sr_paths(target_dir: &Path) -> Vec<PathBuf> {
let mut sr_paths = Vec::new();
let walker = globwalk::GlobWalkerBuilder::from_patterns(target_dir, &[".sr"])
.max_depth(100)
.follow_links(false)
.sort_by(path_cmp)
.build()
.expect("Could not build globwalk directory walker.")
.into_iter()
.filter_map(Result::ok);
for sr_file in walker {
sr_paths.push(sr_file.path().to_path_buf());
}
sr_paths
}
fn path_cmp(a: &walkdir::DirEntry, b: &walkdir::DirEntry) -> Ordering {
let order: Ordering;
if a.to_owned().into_path().to_string_lossy() < b.to_owned().into_path().to_string_lossy() {
order = Ordering::Less;
} else {
order = Ordering::Greater;
}
order
}
fn get_json_value(json_file: &PathBuf, key: &str) -> String {
let mut value = String::new();
if json_file.exists() {
let mut file = fs::File::open(&json_file).expect("Error opening JSON file.");
let mut contents = String::new();
file.read_to_string(&mut contents)
.expect("ERROR: Unable to read the JSON file for this component");
let lines = contents.lines();
for line in lines {
if line.contains(&key) {
let part: Vec<&str> = line.split(":").collect();
value = part[1]
.replace("\"", "")
.replace(",", "")
.trim()
.to_string();
}
}
} else {
panic!(
"JSON file {} not found, cannot extract data from it.",
json_file.display()
);
}
value
}
fn update_json_value(json_file: &PathBuf, key: &str, value: &str) {
if json_file.exists() {
let mut file = fs::File::open(&json_file).expect("Error opening JSON file.");
let mut contents = String::new();
let mut new_contents = String::new();
file.read_to_string(&mut contents)
.expect("ERROR: Unable to read the JSON file for this component");
let lines = contents.lines();
for line in lines {
if line.contains(&key) {
let part: Vec<&str> = line.split(":").collect();
let old_value = part[1]
.replace("\"", "")
.replace(",", "")
.trim()
.to_string();
let new_line = line.replace(&old_value, &value);
new_contents = contents.replace(line, &new_line);
}
}
if !new_contents.is_empty() {
fs::write(json_file, new_contents).expect("Could not write to JSON file.");
}
}
}
fn get_yaml_value(yaml_file: &PathBuf, key: &str) -> String {
let mut value = String::new();
if yaml_file.exists() {
let mut file = fs::File::open(&yaml_file).expect("Error opening yaml file.");
let mut contents = String::new();
file.read_to_string(&mut contents)
.expect("ERROR: Unable to read the yaml file for this component");
let lines = contents.lines();
for line in lines {
if line.contains(&key) {
let part: Vec<&str> = line.split(":").collect();
value = String::from(part[1].replace(",", "").trim());
}
}
} else {
panic!(
"yaml file {} not found, cannot extract data from it.",
yaml_file.display()
);
}
value
}
fn update_yaml_value(yaml_file: &PathBuf, key: &str, value: &str) -> SROutput {
let mut output = SROutput {
status: 0,
wrapped_status: 0,
stdout: Vec::new(),
stderr: Vec::new(),
};
if yaml_file.exists() {
let mut new_contents = String::new();
let contents = match fs::read_to_string(yaml_file) {
Ok(cont) => cont,
Err(e) => {
output.status = 4;
output.stderr.push(format!(
"ERROR: Could not update the contents of the YAML file: {}",
e
));
return output;
}
};
for line in contents.lines() {
if line.contains(&key) {
let part: Vec<&str> = line.split(":").collect();
let old_value = String::from(part[1].replace(",", "").trim());
let new_line = line.replace(&old_value, &value);
new_contents = contents.replace(line, &new_line);
}
}
if !new_contents.is_empty() {
match fs::write(yaml_file, new_contents) {
Ok(_) => (),
Err(e) => {
output.status = 5;
output
.stderr
.push(format!("ERROR: Could not write to the YAML file: {}", e));
return output;
}
};
}
} else {
output.status = 3;
output.stderr.push(String::from(
"ERROR: YAML file to be updated does not exist.",
));
}
output
}
fn get_parent_dir(target_dir: &Path) -> PathBuf {
let parent_dir = target_dir
.parent()
.expect("ERROR: Could not get the parent directory of the target component.");
parent_dir.to_path_buf()
}
fn get_newline() -> String {
let info = os_info::get();
if info.os_type() == os_info::Type::Windows {
String::from("\r\n")
} else {
String::from("\n")
}
}
fn combine_sroutputs(mut dest: SROutput, src: SROutput) -> SROutput {
for line in src.stdout {
dest.stdout.push(line);
}
for line in src.stderr {
dest.stderr.push(line);
}
if dest.status == 0 && src.status != 0 {
dest.status = src.status;
}
dest
}
pub mod git_sr;
pub mod npm_sr;
pub mod templates;
#[cfg(test)]
mod tests {
use std::env;
use std::ffi::OsStr;
use std::fs;
use std::path::{Component, Path};
extern crate git2;
extern crate uuid;
use std::io::prelude::*;
use std::path::PathBuf;
use std::process::Command;
#[test]
fn test_get_parent_dir() {
let temp_dir = env::temp_dir();
let test_dir = set_up(&temp_dir, "toplevel");
assert!(&test_dir.join("toplevel").exists());
assert_eq!(super::get_parent_dir(&test_dir.join("toplevel")), test_dir);
}
#[test]
fn test_yaml_file_handling() {
let temp_dir = env::temp_dir();
let test_dir = set_up(&temp_dir, "toplevel");
let source_license =
super::get_yaml_value(&test_dir.join("toplevel").join(".sr"), "source_license");
assert_eq!(source_license, "Unlicense");
super::update_yaml_value(
&test_dir.join("toplevel").join(".sr"),
"source_license",
"NotASourceLicense",
);
let source_license =
super::get_yaml_value(&test_dir.join("toplevel").join(".sr"), "source_license");
assert_eq!(source_license, "NotASourceLicense");
let value = super::get_yaml_value(&test_dir.join("toplevel").join(".sr"), "not_a_key");
assert_eq!(value, "");
}
#[test]
fn test_json_file_handling() {
let temp_dir = env::temp_dir();
let test_dir = set_up(&temp_dir, "toplevel");
let name = super::get_json_value(&test_dir.join("toplevel").join("package.json"), "name");
assert_eq!(name, "toplevel");
super::update_json_value(
&test_dir.join("toplevel").join("package.json"),
"name",
"NotAName",
);
let name = super::get_json_value(&test_dir.join("toplevel").join("package.json"), "name");
assert_eq!(name, "NotAName");
let name =
super::get_json_value(&test_dir.join("toplevel").join("package.json"), "not_a_key");
assert_eq!(name, "");
}
#[test]
fn test_amalgamate_licenses() {
let temp_dir = env::temp_dir();
let test_dir = set_up(&temp_dir, "toplevel");
super::update_json_value(
&test_dir.join("toplevel").join("package.json"),
"license",
"NotALicense",
);
super::amalgamate_licenses(&test_dir.join("toplevel"));
let license =
super::get_json_value(&test_dir.join("toplevel").join("package.json"), "license");
assert_eq!(
license,
"(Unlicense AND NotASourceLicense AND CC0-1.0 AND NotADocLicense AND CC-BY-4.0)"
);
}
#[test]
fn test_get_licenses() {
let temp_dir = env::temp_dir();
let test_dir = set_up(&temp_dir, "toplevel");
let licenses = super::get_licenses(&test_dir);
assert_eq!(licenses.0, "Unlicense");
assert_eq!(licenses.1, "CC0-1.0");
}
#[test]
fn test_list_all_licenses() {
let temp_dir = env::temp_dir();
let test_dir = set_up(&temp_dir, "toplevel");
let license_listing = super::list_all_licenses(&test_dir.join("toplevel"));
assert!(license_listing.contains("Licenses Specified In This Component:"));
assert!(license_listing.contains("Unlicense"));
assert!(license_listing.contains("CC0-1.0"));
assert!(license_listing.contains("NotASourceLicense"));
assert!(license_listing.contains("NotADocLicense"));
assert!(license_listing.contains("CC-BY-4.0"));
}
#[test]
fn test_gitignore_template() {
let content = super::templates::gitignore_template();
assert!(content.contains("# Dependency directories"));
assert!(content.contains("node_modules/"));
assert!(content.contains("# Distribution directory"));
assert!(content.contains("dist/"));
let mut globals = liquid::value::Object::new();
let render = super::render_template(".gitignore.liquid", &mut globals);
assert!(render.contains("# Dependency directories"));
assert!(render.contains("node_modules/"));
assert!(render.contains("# Distribution directory"));
assert!(render.contains("dist/"));
}
#[test]
fn test_sr_file_template() {
let content = super::templates::sr_file_template();
assert!(content.contains("source_license: {{source_license}},"));
assert!(content.contains("documentation_license: {{doc_license}}"));
let mut globals = liquid::value::Object::new();
globals.insert(
"source_license".into(),
liquid::value::Value::scalar("NotASourceLicense"),
);
globals.insert(
"doc_license".into(),
liquid::value::Value::scalar("NotADocLicense"),
);
let render = super::render_template(".sr.liquid", &mut globals);
assert!(render.contains("source_license: NotASourceLicense,"));
assert!(render.contains("documentation_license: NotADocLicense"));
}
#[test]
fn test_bom_data_yaml_template() {
let content = super::templates::bom_data_yaml_template();
assert!(content.contains("# Bill of Materials Data for {{name}}"));
assert!(content.contains("parts:"));
assert!(content.contains(" - specific_component_variation"));
assert!(content.contains(" notes: ''"));
assert!(content.contains("order:"));
assert!(content.contains(" -component_1"));
let mut globals = liquid::value::Object::new();
globals.insert("name".into(), liquid::value::Value::scalar("TopLevel"));
let render = super::render_template("bom_data.yaml.liquid", &mut globals);
assert!(render.contains("# Bill of Materials Data for TopLevel"));
assert!(render.contains("parts:"));
assert!(render.contains(" - specific_component_variation"));
assert!(render.contains(" notes: ''"));
assert!(render.contains("order:"));
assert!(render.contains(" -component_1"));
}
#[test]
fn test_package_json_template() {
let content = super::templates::package_json_template();
assert!(content.contains(" \"name\": \"{{name}}\","));
assert!(content.contains(" \"license\": \"{{license}}\","));
let mut globals = liquid::value::Object::new();
globals.insert("name".into(), liquid::value::Value::scalar("TopLevel"));
globals.insert(
"license".into(),
liquid::value::Value::scalar("(NotASourceLicense AND NotADocLicense)"),
);
let render = super::render_template("package.json.liquid", &mut globals);
assert!(render.contains(" \"name\": \"TopLevel\","));
assert!(render.contains(" \"license\": \"(NotASourceLicense AND NotADocLicense)\","));
}
#[test]
fn test_readme_template() {
let content = super::templates::readme_template();
assert!(content.contains("# {{name}}"));
assert!(content.contains("Developed in [Sliderule](http://sliderule.io) an implementation of the [Distributed OSHW Framework](http://dof.sliderule.io)."));
let mut globals = liquid::value::Object::new();
globals.insert("name".into(), liquid::value::Value::scalar("TopLevel"));
let render = super::render_template("README.md.liquid", &mut globals);
assert!(render.contains("# TopLevel"));
assert!(render.contains("Developed in [Sliderule](http://sliderule.io) an implementation of the [Distributed OSHW Framework](http://dof.sliderule.io)."));
}
#[test]
fn test_generate_dot_file() {
let temp_dir = env::temp_dir();
let uuid_dir = uuid::Uuid::new_v4();
let test_dir_name = format!("temp_{}", uuid_dir);
let temp_dir = temp_dir.join(test_dir_name);
fs::create_dir(&temp_dir).expect("Could not create temporary directory for test.");
super::generate_dot_file(&temp_dir, "NotASourceLicense", "NotADocLicense");
let mut file = fs::File::open(&temp_dir.join(".sr")).expect("Unable to open the sr file");
let mut contents = String::new();
file.read_to_string(&mut contents)
.expect("Unable to read the sr file");
assert!(contents.contains("source_license: NotASourceLicense,"));
assert!(contents.contains("documentation_license: NotADocLicense"));
}
#[test]
fn test_generate_gitignore() {
let temp_dir = env::temp_dir();
let uuid_dir = uuid::Uuid::new_v4();
let test_dir_name = format!("temp_{}", uuid_dir);
let temp_dir = temp_dir.join(test_dir_name);
fs::create_dir(&temp_dir).expect("Could not create temporary directory for test.");
super::generate_gitignore(&temp_dir);
let mut file = fs::File::open(&temp_dir.join(".gitignore"))
.expect("Unable to open the gitignore file");
let mut contents = String::new();
file.read_to_string(&mut contents)
.expect("Unable to read the gitignore file");
assert!(contents.contains("node_modules/"));
assert!(contents.contains("dist/"));
}
#[test]
fn test_generate_package_json() {
let temp_dir = env::temp_dir();
let uuid_dir = uuid::Uuid::new_v4();
let test_dir_name = format!("temp_{}", uuid_dir);
let temp_dir = temp_dir.join(test_dir_name);
fs::create_dir(&temp_dir).expect("Could not create temporary directory for test.");
super::generate_package_json(&temp_dir, "TopLevel", "NotASourceLicense");
let mut file = fs::File::open(&temp_dir.join("package.json"))
.expect("Unable to open the package.json file");
let mut contents = String::new();
file.read_to_string(&mut contents)
.expect("Unable to read the package.json file");
assert!(contents.contains(" \"name\": \"TopLevel\","));
assert!(contents.contains(" \"license\": \"NotASourceLicense\","));
}
#[test]
fn test_generate_bom() {
let temp_dir = env::temp_dir();
let uuid_dir = uuid::Uuid::new_v4();
let test_dir_name = format!("temp_{}", uuid_dir);
let temp_dir = temp_dir.join(test_dir_name);
fs::create_dir(&temp_dir).expect("Could not create temporary directory for test.");
super::generate_bom(&temp_dir, "TopLevel");
let mut file = fs::File::open(&temp_dir.join("bom_data.yaml"))
.expect("Unable to open the bom_data.yaml file");
let mut contents = String::new();
file.read_to_string(&mut contents)
.expect("Unable to read the package.json file");
assert!(contents.contains("# Bill of Materials Data for TopLevel"));
}
#[test]
fn test_generate_readme() {
let temp_dir = env::temp_dir();
let uuid_dir = uuid::Uuid::new_v4();
let test_dir_name = format!("temp_{}", uuid_dir);
let temp_dir = temp_dir.join(test_dir_name);
fs::create_dir(&temp_dir).expect("Could not create temporary directory for test.");
super::generate_readme(&temp_dir, "TopLevel");
let mut file =
fs::File::open(&temp_dir.join("README.md")).expect("Unable to open the README.md file");
let mut contents = String::new();
file.read_to_string(&mut contents)
.expect("Unable to read the package.json file");
assert!(contents.contains("# TopLevel"));
}
#[test]
fn test_update_local_component() {
let temp_dir = env::temp_dir();
let test_dir = set_up(&temp_dir, "toplevel");
let output = super::update_local_component(&test_dir.join("toplevel"));
assert_eq!(0, output.status);
assert_eq!(output.stdout[0].trim(), "Already up to date.");
assert_eq!(output.stdout[1], "Component updated successfully.");
}
#[test]
fn test_update_dependencies() {
let temp_dir = env::temp_dir();
let test_dir = set_up(&temp_dir, "toplevel");
let output = super::update_dependencies(&test_dir.join("toplevel"));
assert_eq!(0, output.status);
assert!(output.stdout[1].contains("Dependencies were updated successfully."));
}
#[test]
fn test_download_component() {
let temp_dir = env::temp_dir();
let test_dir = set_up(&temp_dir, "toplevel");
let output = super::download_component(
&test_dir.join("toplevel"),
"https://github.com/jmwright/toplevel.git",
);
assert_eq!(0, output.status);
assert!(output.stdout[1].contains("Component was downloaded successfully."));
}
#[test]
fn test_remove_remote_component() {
let temp_dir = env::temp_dir();
let test_dir = set_up(&temp_dir, "toplevel");
let cache_dir = temp_dir.join(format!("cache_{}", uuid::Uuid::new_v4()));
let output = super::remove_remote_component(
&test_dir.join("toplevel"),
"blink_firmware",
Some(cache_dir.to_string_lossy().to_string()),
);
assert_eq!(0, output.status);
assert!(!test_dir
.join("toplevel")
.join("node_modules")
.join("blink_firmware")
.exists());
}
#[test]
fn test_add_remote_component() {
let temp_dir = env::temp_dir();
let test_dir = set_up(&temp_dir, "toplevel");
let cache_dir = temp_dir.join(format!("cache_{}", uuid::Uuid::new_v4()));
let output = super::add_remote_component(
&test_dir.join("toplevel"),
"https://github.com/jmwright/arduino-sr.git",
Some(cache_dir.to_string_lossy().to_string()),
);
let component_path = test_dir
.join("toplevel")
.join("node_modules")
.join("arduino-sr");
assert_eq!(0, output.status);
assert!(component_path.exists());
assert!(is_valid_component(
&component_path,
"arduino-sr",
"Unlicense",
"CC0-1.0"
));
}
#[test]
fn test_change_licenses() {
let temp_dir = env::temp_dir();
let test_dir = set_up(&temp_dir, "toplevel");
let output = super::change_licenses(
&test_dir.join("toplevel"),
String::from("TestSourceLicense"),
String::from("TestDocLicense"),
);
assert_eq!(0, output.status);
assert!(output.stderr.is_empty());
assert!(file_contains_content(
&test_dir.join("toplevel").join("package.json"),
9999,
"TestSourceLicense",
));
assert!(file_contains_content(
&test_dir.join("toplevel").join("package.json"),
9999,
"TestDocLicense",
));
assert!(file_contains_content(
&test_dir.join("toplevel").join(".sr"),
9999,
"source_license: TestSourceLicense,"
));
assert!(file_contains_content(
&test_dir.join("toplevel").join(".sr"),
9999,
"documentation_license: TestDocLicense"
));
}
#[test]
fn test_remove() {
let temp_dir = env::temp_dir();
let test_dir = set_up(&temp_dir, "toplevel");
let output = super::remove(&test_dir.join("toplevel"), "level1");
assert_eq!(0, output.status);
assert!(output.stderr.is_empty());
assert!(!&test_dir
.join("toplevel")
.join("components")
.join("level1")
.exists());
let output = super::remove(&test_dir.join("toplevel"), "blink_firmware");
assert_eq!(0, output.status);
assert!(!&test_dir
.join("toplevel")
.join("node_modules")
.join("level1")
.exists());
}
#[test]
fn test_create_component() {
let temp_dir = env::temp_dir();
let test_dir = set_up(&temp_dir, "toplevel");
let output = super::create_component(
&test_dir,
String::from("nextlevel"),
String::from("TestSourceLicense"),
String::from("TestDocLicense"),
);
assert_eq!(0, output.status);
assert_eq!(
"Finished setting up component.",
output.stdout[output.stdout.len() - 1]
);
assert!(is_valid_component(
&test_dir.join("nextlevel"),
"nextlevel",
"TestSourceLicense",
"TestDocLicense"
));
}
#[test]
fn test_refactor() {
let temp_dir = env::temp_dir();
let test_dir = set_up(&temp_dir, "toplevel");
let demo_dir = test_dir.join("demo");
let remote_dir = demo_dir.join("remote");
fs::create_dir(&demo_dir).expect("Failed to create demo directory.");
Command::new("git")
.args(&["init", "--bare"])
.current_dir(&demo_dir)
.output()
.expect("failed to initialize bare git repository in demo directory");
fs::create_dir(&remote_dir).expect("Failed to create top component directory.");
Command::new("git")
.args(&["init", "--bare"])
.current_dir(&remote_dir)
.output()
.expect("failed to initialize bare git repository in demo directory");
Command::new("git")
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.args(&[
"daemon",
"--reuseaddr",
"--export-all",
"--base-path=.",
"--verbose",
"--enable=receive-pack",
".",
])
.current_dir(demo_dir)
.spawn()
.expect("ERROR: Could not launch git daemon.");
let output = super::create_component(
&test_dir.join("toplevel"),
String::from("remote"),
String::from("TestSourceLicense"),
String::from("TestDocLicense"),
);
assert!(is_valid_component(
&test_dir.join("toplevel").join("components").join("remote"),
"remote",
"TestSourceLicense",
"TestDocLicense"
));
assert_eq!(0, output.stderr.len());
let output = super::refactor(
&test_dir.join("toplevel"),
String::from("remote"),
String::from("git://127.0.0.1/remote"),
None,
None,
);
if output.stderr.len() > 0 {
for out in &output.stderr {
println!("{:?}", out);
}
}
assert_eq!(
"Finished refactoring local component to remote repository.",
output.stdout[output.stdout.len() - 1]
);
assert!(is_valid_component(
&test_dir
.join("toplevel")
.join("node_modules")
.join("remote"),
"remote",
"TestSourceLicense",
"TestDocLicense"
));
kill_git();
}
#[test]
fn test_upload_component() {
let temp_dir = env::temp_dir();
let test_dir = set_up(&temp_dir, "toplevel");
let demo_dir = test_dir.join("demo");
let remote_dir = demo_dir.join("nextlevel");
fs::create_dir(&demo_dir).expect("Failed to create demo directory.");
Command::new("git")
.args(&["init", "--bare"])
.current_dir(&demo_dir)
.output()
.expect("failed to initialize bare git repository in demo directory");
fs::create_dir(&remote_dir).expect("Failed to create top component directory.");
Command::new("git")
.args(&["init", "--bare"])
.current_dir(&remote_dir)
.output()
.expect("failed to initialize bare git repository in demo directory");
Command::new("git")
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.args(&[
"daemon",
"--reuseaddr",
"--export-all",
"--base-path=.",
"--verbose",
"--enable=receive-pack",
".",
])
.current_dir(demo_dir)
.spawn()
.expect("ERROR: Could not launch git daemon.");
let output = super::create_component(
&test_dir,
String::from("nextlevel"),
String::from("TestSourceLicense"),
String::from("TestDocLicense"),
);
assert_eq!(0, output.stderr.len());
let output = super::upload_component(
&test_dir.join("nextlevel"),
String::from("Initial commit"),
String::from("git://127.0.0.1/nextlevel"),
None,
None,
);
if output.stderr.len() > 0 {
for out in &output.stderr {
println!("{:?}", out);
}
}
assert_eq!(
"Done uploading component.",
output.stdout[output.stdout.len() - 1]
);
assert_eq!(
"Changes pushed using git.",
output.stdout[output.stdout.len() - 2]
);
let output = super::download_component(
&test_dir.join("toplevel"),
&String::from("git://127.0.0.1/nextlevel"),
);
if output.stderr.len() > 0 {
for out in &output.stderr {
println!("{:?}", out);
}
}
assert!(is_valid_component(
&test_dir.join("toplevel").join("nextlevel"),
"nextlevel",
"TestSourceLicense",
"TestDocLicense"
));
kill_git();
}
#[test]
fn test_get_sr_paths() {
let temp_dir = env::temp_dir();
let test_dir = set_up(&temp_dir, "toplevel");
let sr_paths = super::get_sr_paths(&test_dir.join("toplevel"));
for sr_path in &sr_paths {
println!("{:?}", sr_path);
}
let path_parts = sr_paths[0].components().collect::<Vec<_>>();
assert_eq!(
path_parts[path_parts.len() - 1],
Component::Normal(OsStr::new(".sr"))
);
assert_eq!(
path_parts[path_parts.len() - 2],
Component::Normal(OsStr::new("toplevel"))
);
let path_parts = sr_paths[1].components().collect::<Vec<_>>();
assert_eq!(
path_parts[path_parts.len() - 1],
Component::Normal(OsStr::new(".sr"))
);
assert_eq!(
path_parts[path_parts.len() - 2],
Component::Normal(OsStr::new("level1"))
);
assert_eq!(
path_parts[path_parts.len() - 3],
Component::Normal(OsStr::new("components"))
);
let path_parts = sr_paths[2].components().collect::<Vec<_>>();
assert_eq!(
path_parts[path_parts.len() - 1],
Component::Normal(OsStr::new(".sr"))
);
assert_eq!(
path_parts[path_parts.len() - 2],
Component::Normal(OsStr::new("level2"))
);
assert_eq!(
path_parts[path_parts.len() - 3],
Component::Normal(OsStr::new("components"))
);
assert_eq!(
path_parts[path_parts.len() - 4],
Component::Normal(OsStr::new("level1"))
);
let path_parts = sr_paths[3].components().collect::<Vec<_>>();
assert_eq!(
path_parts[path_parts.len() - 1],
Component::Normal(OsStr::new(".sr"))
);
assert_eq!(
path_parts[path_parts.len() - 2],
Component::Normal(OsStr::new("level3"))
);
assert_eq!(
path_parts[path_parts.len() - 3],
Component::Normal(OsStr::new("components"))
);
assert_eq!(
path_parts[path_parts.len() - 4],
Component::Normal(OsStr::new("level2"))
);
let path_parts = sr_paths[4].components().collect::<Vec<_>>();
assert_eq!(
path_parts[path_parts.len() - 1],
Component::Normal(OsStr::new(".sr"))
);
assert_eq!(
path_parts[path_parts.len() - 2],
Component::Normal(OsStr::new("blink_firmware"))
);
assert_eq!(
path_parts[path_parts.len() - 3],
Component::Normal(OsStr::new("node_modules"))
);
}
fn kill_git() {
let info = os_info::get();
if info.os_type() == os_info::Type::Windows {
Command::new("taskkill")
.args(&["/f", "/t", "/im", "git"])
.output()
.unwrap();
Command::new("taskkill")
.args(&["/f", "/t", "/im", "git-daemon"])
.output()
.unwrap();
Command::new("taskkill")
.args(&["/f", "/t", "/im", "git.exe"])
.output()
.unwrap();
Command::new("taskkill")
.args(&["/f", "/t", "/im", "git-daemon.exe"])
.output()
.unwrap();
} else {
Command::new("killall")
.args(&["git"])
.output()
.expect("failed to kill git process");
}
}
fn set_up(temp_dir: &PathBuf, dir_name: &str) -> PathBuf {
let url = "https://github.com/jmwright/toplevel.git";
let uuid_dir = uuid::Uuid::new_v4();
let test_dir_name = format!("temp_{}", uuid_dir);
fs::create_dir(temp_dir.join(&test_dir_name))
.expect("Unable to create temporary directory.");
match git2::Repository::clone(&url, temp_dir.join(&test_dir_name).join(dir_name)) {
Ok(repo) => repo,
Err(e) => panic!("failed to clone: {}", e),
};
temp_dir.join(test_dir_name)
}
fn is_valid_component(
component_path: &Path,
component_name: &str,
source_license: &str,
doc_license: &str,
) -> bool {
let mut is_valid = true;
if !component_path.exists() {
is_valid = false;
println!(
"The component directory {:?} does not exist",
component_path
);
} else {
let paths = fs::read_dir(component_path).unwrap();
for path in paths {
println!("Component path: {}", path.unwrap().path().display());
}
}
if !component_path.join("bom_data.yaml").exists() {
is_valid = false;
println!(
"The file {:?}/bom_data.yaml does not exist.",
component_path
);
}
if !component_path.join("components").exists() {
is_valid = false;
println!(
"The directory {:?}/components does not exist.",
component_path
);
}
if !component_path.join("docs").exists() {
is_valid = false;
println!("The directory {:?}/docs does not exist.", component_path);
}
if !component_path.join("package.json").exists() {
is_valid = false;
println!("The file {:?}/package.json does not exist.", component_path);
}
if !component_path.join("README.md").exists() {
is_valid = false;
println!("The file {:?}/README.md does not exist.", component_path);
}
if !component_path.join("source").exists() {
is_valid = false;
println!("The directory {:?}/source does not exist.", component_path);
}
let bom_file = component_path.join("bom_data.yaml");
let package_file = component_path.join("package.json");
let readme_file = component_path.join("README.md");
let dot_file = component_path.join(".sr");
if !file_contains_content(
&bom_file,
0,
&format!("# Bill of Materials Data for {}", component_name),
) {
is_valid = false;
println!(
"The bill to materials file in {:?} does not contain the correct header.",
component_path
);
}
if !file_contains_content(&bom_file, 12, "-component_1") {
is_valid = false;
println!("The bill to materials file in {:?} does not contain the '-component_1' entry in the right place.", component_path);
}
if !file_contains_content(
&package_file,
9999,
&format!("\"name\": \"{}\",", component_name),
) {
is_valid = false;
println!("The package.json file in {:?} does not contain the component name entry in the right place.", component_path);
}
if !file_contains_content(
&package_file,
9999,
&format!("\"license\": \"({} AND {})\",", source_license, doc_license),
) {
is_valid = false;
println!("The package.json file in {:?} does not contain the the correct license entry in the right place.", component_path);
}
if !file_contains_content(&readme_file, 0, &format!("# {}", component_name)) {
is_valid = false;
println!("The README.md file in {:?} does not contain the the correct header entry in the right place.", component_path);
}
if !file_contains_content(&readme_file, 1, "New Sliderule component.") {
is_valid = false;
println!("The README.md file in {:?} does not contain the the correct Sliderule mention in the right place.", component_path);
}
if !file_contains_content(
&dot_file,
0,
&format!("source_license: {},", source_license),
) {
is_valid = false;
println!(
"The .sr file in {:?} does not contain the the correct source license in the right place.",
component_path
);
}
if !file_contains_content(
&dot_file,
1,
&format!("documentation_license: {}", doc_license),
) {
is_valid = false;
println!("The .sr file in {:?} does not contain the the correct documentation license in the right place.", component_path);
}
is_valid
}
fn file_contains_content(file_path: &Path, line: usize, text: &str) -> bool {
let contains_content: bool;
let contents =
fs::read_to_string(file_path).expect("ERROR: Cannot read the contents of the file.");
if line == 9999 {
contains_content = contents.contains(text);
} else {
let contents: Vec<&str> = contents.lines().collect();
contains_content = contents[line].trim() == text;
}
contains_content
}
}