1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
use clap::{App, Arg};
use serde::{de::DeserializeOwned, Serialize};
use std::{cmp::Eq, collections::HashMap, error::Error, fmt::Debug, hash::Hash, path::Path};

use crate::{Cpt, StringOrVecString};

const VERSION: Option<&'static str> = option_env!("CARGO_PKG_VERSION");
pub trait FromArgs: Default {
	fn from_args(defaults: Option<&Self>) -> Result<Self, Box<dyn Error>>;
}

impl<K, V, S> FromArgs for Cpt<K, V, S>
where
	K: Hash + Eq + DeserializeOwned + Serialize + Debug,
	V: Hash + Eq + DeserializeOwned + Serialize + Debug + Into<StringOrVecString>,
	S: std::hash::BuildHasher + Default + Debug,
{
	#[cfg_attr(tarpaulin, skip)]
	fn from_args(defaults: Option<&Cpt<K, V, S>>) -> Result<Cpt<K, V, S>, Box<dyn Error>> {
		let plain_def = Self::default();
		let def = defaults.unwrap_or(&plain_def);
		let m = App::new("cpt")
			.version(VERSION.unwrap_or("unknown"))
			.about(
				"Copies one folder structure to another place with files. Also formats templates!",
			)
			.author("AlexAegis")
			.arg(
				Arg::with_name("from")
					.short("f")
					.long("from")
					.required(true)
					.index(1)
					.default_value(&def.from)
					.validator(|s| {
						if Path::new(&s).exists() {
							Ok(())
						} else {
							Err("Source folder not exists".to_string())
						}
					})
					.help("The folder that will be copied"),
			)
			.arg(
				Arg::with_name("to")
					.short("t")
					.long("to")
					.required(true)
					.index(2)
					.default_value(&def.to)
					.help("The folder where the folder will be placed"),
			)
			.arg(
				Arg::with_name("json")
					.short("-j")
					.long("--json")
					.takes_value(true)
					.validator(|s| match serde_json::from_str::<HashMap<K, V>>(&s) {
						Ok(_) => Ok(()),
						Err(e) => Err(e.to_string()),
					})
					.help("JSON formatted templating data"),
			)
			.arg(
				Arg::with_name("dry")
					.short("-d")
					.long("--dry")
					.help("If set, nothing will be written to the disk"),
			)
			.arg(
				Arg::with_name("force")
					.short("-f")
					.long("--force")
					.help("If set, files can be overwritten in the target folder"),
			)
			.arg(
				Arg::with_name("quiet")
					.short("-q")
					.long("--quiet")
					.help("Tarpaulin"),
			)
			.get_matches();

		let from = m.args["from"]
			.vals
			.first()
			.ok_or("No from specified")?
			.to_str()
			.ok_or("Invalid string")?;
		let to = m.args["to"]
			.vals
			.first()
			.ok_or("No to specified")?
			.to_str()
			.ok_or("Invalid string")?;

		let dry = m.args.get("dry").is_some();
		let force = m.args.get("force").is_some();

		let mut data_map = None;
		if m.args.contains_key("json") {
			if let Some(d) = m.args["json"].vals.first() {
				let data_str = d.to_str().ok_or("Invalid string")?;
				data_map.replace(Box::from(serde_json::from_str::<HashMap<K, V, S>>(
					&data_str,
				)?));
			}
		}

		Ok(Cpt::new(from.to_string(), to.to_string())
			.try_data(data_map)
			.set_dry(dry)
			.set_force(force))
	}
}