This commit is contained in:
parent
193036fab7
commit
d6d2909de0
6 changed files with 371 additions and 29 deletions
265
src/bootstrap.rs
Normal file
265
src/bootstrap.rs
Normal file
|
@ -0,0 +1,265 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use either::Either;
|
||||
use serde::Deserialize;
|
||||
use subprocess::Exec;
|
||||
|
||||
use crate::git::switch_branch;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct BootstrapConfig {
|
||||
pub ask: HashMap<String, AskConfig>,
|
||||
pub actions: ActionsConfig,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub enum AskKind {
|
||||
#[serde(rename = "text")]
|
||||
Text,
|
||||
#[serde(rename = "number")]
|
||||
Number,
|
||||
#[serde(rename = "selection")]
|
||||
Selection,
|
||||
#[serde(rename = "bool")]
|
||||
Bool,
|
||||
}
|
||||
|
||||
pub enum AskValue {
|
||||
Text(String, String),
|
||||
Number(String, i64),
|
||||
Selection(String, String),
|
||||
Bool(String, bool),
|
||||
}
|
||||
|
||||
impl AskValue {
|
||||
pub fn has_name(&self, name: &str) -> bool {
|
||||
match self {
|
||||
AskValue::Text(n, _) => n.as_str() == name,
|
||||
AskValue::Number(n, _) => n.as_str() == name,
|
||||
AskValue::Selection(n, _) => n.as_str() == name,
|
||||
AskValue::Bool(n, _) => n.as_str() == name,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn text(&self) -> String {
|
||||
match self {
|
||||
AskValue::Text(_, t) => t.clone(),
|
||||
AskValue::Number(_, n) => n.to_string(),
|
||||
AskValue::Selection(_, s) => s.clone(),
|
||||
AskValue::Bool(_, b) => b.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct AskConfig {
|
||||
pub kind: AskKind,
|
||||
pub prompt: String,
|
||||
pub options: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct ActionsConfig {
|
||||
pub replace: Vec<ReplaceAction>,
|
||||
pub branch: Vec<BranchAction>,
|
||||
pub script: Vec<ScriptAction>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct VarCompare {
|
||||
pub var: String,
|
||||
pub eq: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct ReplaceAction {
|
||||
pub when: Option<Either<String, VarCompare>>,
|
||||
pub from: String,
|
||||
pub to: String,
|
||||
pub to_var: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct BranchAction {
|
||||
pub when: Option<Either<String, VarCompare>>,
|
||||
pub branch: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct ScriptAction {
|
||||
pub when: Option<Either<String, VarCompare>>,
|
||||
pub script: String,
|
||||
pub expose: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
pub fn eval(when: &Either<String, VarCompare>, vars: &[AskValue]) -> bool {
|
||||
if when.is_left() {
|
||||
let when = when.as_ref().left().unwrap();
|
||||
let var = vars.into_iter().find(|x| x.has_name(&when)).unwrap();
|
||||
if let AskValue::Bool(_, ret) = var {
|
||||
return *ret;
|
||||
}
|
||||
} else {
|
||||
let when = when.as_ref().right().unwrap();
|
||||
let var = vars.into_iter().find(|x| x.has_name(&when.var)).unwrap();
|
||||
match var {
|
||||
AskValue::Text(_, t) => {
|
||||
return *t == when.eq;
|
||||
}
|
||||
AskValue::Number(_, n) => {
|
||||
return n.to_string() == when.eq;
|
||||
}
|
||||
AskValue::Selection(_, s) => {
|
||||
return *s == when.eq;
|
||||
}
|
||||
AskValue::Bool(_, b) => {
|
||||
return b.to_string() == when.eq;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
panic!("Could not evaluate condition");
|
||||
}
|
||||
|
||||
pub fn build_script_vars(expose: Option<Vec<String>>, vars: &[AskValue]) -> String {
|
||||
if let Some(expose) = expose {
|
||||
let mut exp = Vec::new();
|
||||
|
||||
for ex in expose {
|
||||
let v = vars.iter().find(|x| x.has_name(&ex)).unwrap();
|
||||
exp.push(v);
|
||||
}
|
||||
|
||||
return exp
|
||||
.iter()
|
||||
.map(|x| match x {
|
||||
AskValue::Text(name, text) => format!("{name}='{text}'"),
|
||||
AskValue::Number(name, num) => format!("{name}={num}"),
|
||||
AskValue::Selection(name, select) => format!("{name}='{select}'"),
|
||||
AskValue::Bool(name, b) => format!("{name}='{}'", b.to_string()),
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
}
|
||||
|
||||
String::new()
|
||||
}
|
||||
|
||||
pub fn do_script(action: &ScriptAction, vars: &[AskValue], name: &str) {
|
||||
let pre = build_script_vars(action.expose.clone(), &vars);
|
||||
println!("Running script '{}'", action.script);
|
||||
Exec::cmd("sh")
|
||||
.arg("-c")
|
||||
.arg(format!("{pre}\n./{}", action.script))
|
||||
.cwd(std::path::Path::new(name))
|
||||
.popen()
|
||||
.unwrap()
|
||||
.wait()
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub fn do_replace(action: &ReplaceAction, name: &str, vars: &[AskValue]) {
|
||||
let to = if let Some(var) = &action.to_var {
|
||||
vars.iter().find(|x| x.has_name(var)).unwrap().text()
|
||||
} else {
|
||||
action.to.clone()
|
||||
};
|
||||
println!("Replacing '{}' -> '{}'", action.from, to);
|
||||
Exec::cmd("fd")
|
||||
.arg(".")
|
||||
.arg("-tf")
|
||||
.arg("-x")
|
||||
.arg("sd")
|
||||
.arg(&action.from)
|
||||
.arg(&to)
|
||||
.cwd(std::path::Path::new(name))
|
||||
.popen()
|
||||
.unwrap()
|
||||
.wait()
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
/// Bootstrap a git repository using a base template
|
||||
pub fn bootstrap(base: &str, name: &str) {
|
||||
Exec::cmd("git")
|
||||
.arg("clone")
|
||||
.arg(base)
|
||||
.arg(name)
|
||||
.popen()
|
||||
.unwrap()
|
||||
.wait()
|
||||
.unwrap();
|
||||
|
||||
if std::fs::exists(format!("{name}/bootstrap.toml")).unwrap_or(false) {
|
||||
let config: BootstrapConfig =
|
||||
toml::from_str(&std::fs::read_to_string(&format!("{name}/bootstrap.toml")).unwrap())
|
||||
.unwrap();
|
||||
let mut vars = Vec::new();
|
||||
|
||||
for (var_name, def) in &config.ask {
|
||||
match def.kind {
|
||||
AskKind::Text => {
|
||||
let a = inquire::prompt_text(&def.prompt).unwrap();
|
||||
vars.push(AskValue::Text(var_name.clone(), a));
|
||||
}
|
||||
AskKind::Number => {
|
||||
let a = inquire::prompt_text(&def.prompt).unwrap();
|
||||
let a: i64 = a.parse().unwrap();
|
||||
vars.push(AskValue::Number(var_name.clone(), a));
|
||||
}
|
||||
AskKind::Selection => {
|
||||
let a = inquire::Select::new(&def.prompt, def.options.clone().unwrap())
|
||||
.prompt()
|
||||
.unwrap();
|
||||
vars.push(AskValue::Selection(var_name.clone(), a));
|
||||
}
|
||||
AskKind::Bool => {
|
||||
let a = inquire::prompt_confirmation(&def.prompt).unwrap();
|
||||
vars.push(AskValue::Bool(var_name.clone(), a));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for action in config.actions.branch {
|
||||
if let Some(when) = action.when {
|
||||
if eval(&when, &vars) {
|
||||
println!("Switching to '{}' branch", action.branch);
|
||||
switch_branch(&action.branch);
|
||||
}
|
||||
} else {
|
||||
println!("Switching to '{}' branch", action.branch);
|
||||
switch_branch(&action.branch);
|
||||
}
|
||||
}
|
||||
|
||||
for action in config.actions.replace {
|
||||
if let Some(when) = &action.when {
|
||||
if eval(when, &vars) {
|
||||
do_replace(&action, name, &vars);
|
||||
}
|
||||
} else {
|
||||
do_replace(&action, name, &vars);
|
||||
}
|
||||
}
|
||||
|
||||
for action in config.actions.script {
|
||||
if let Some(when) = &action.when {
|
||||
if eval(when, &vars) {
|
||||
do_script(&action, &vars, name);
|
||||
}
|
||||
} else {
|
||||
do_script(&action, &vars, name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::fs::remove_dir_all(std::path::Path::new(name).join(".git")).unwrap();
|
||||
Exec::cmd("git")
|
||||
.arg("init")
|
||||
.arg("--quiet")
|
||||
.cwd(std::path::Path::new(name))
|
||||
.popen()
|
||||
.unwrap()
|
||||
.wait()
|
||||
.unwrap();
|
||||
}
|
30
src/git.rs
30
src/git.rs
|
@ -13,6 +13,15 @@ pub fn has_remote() -> bool {
|
|||
!str.is_empty()
|
||||
}
|
||||
|
||||
pub fn switch_branch(branch: &str) {
|
||||
let mut git = Exec::cmd("git")
|
||||
.arg("checkout")
|
||||
.arg(branch)
|
||||
.popen()
|
||||
.unwrap();
|
||||
git.wait().unwrap();
|
||||
}
|
||||
|
||||
/// Create a new branch and switch to it in the current repository
|
||||
pub fn create_new_branch(branch: &str) {
|
||||
let mut git = Exec::cmd("git")
|
||||
|
@ -269,24 +278,3 @@ pub fn pull() {
|
|||
.wait()
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
/// Bootstrap a git repository using a base template
|
||||
pub fn bootstrap(base: &str, name: &str) {
|
||||
Exec::cmd("git")
|
||||
.arg("clone")
|
||||
.arg(base)
|
||||
.arg(name)
|
||||
.popen()
|
||||
.unwrap()
|
||||
.wait()
|
||||
.unwrap();
|
||||
std::fs::remove_dir_all(std::path::Path::new(name).join(".git")).unwrap();
|
||||
Exec::cmd("git")
|
||||
.arg("init")
|
||||
.arg("--quiet")
|
||||
.cwd(std::path::Path::new(name))
|
||||
.popen()
|
||||
.unwrap()
|
||||
.wait()
|
||||
.unwrap();
|
||||
}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
use git::{
|
||||
bootstrap, commit, create_new_branch, delete_branch, fetch, git_add, pull, push, revert_commits,
|
||||
};
|
||||
use bootstrap::bootstrap;
|
||||
use git::{commit, create_new_branch, delete_branch, fetch, git_add, pull, push, revert_commits};
|
||||
use gitmoji::{CommitMessage, select_gitmoji};
|
||||
use std::io::Read;
|
||||
use subprocess::{Exec, Redirection};
|
||||
use yansi::{Color, Paint};
|
||||
|
||||
mod args;
|
||||
mod bootstrap;
|
||||
mod git;
|
||||
mod gitmoji;
|
||||
mod precommit;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue