mirror of
https://github.com/Pagwin-Fedora/ppi.git
synced 2025-07-17 22:15:42 +00:00
207 lines
8.2 KiB
Rust
207 lines
8.2 KiB
Rust
extern crate clap;
|
|
extern crate serde;
|
|
extern crate toml;
|
|
extern crate git2;
|
|
|
|
use std::{path::{PathBuf, Path}, io::{Read, Cursor}, collections::{HashMap, HashSet}, ffi::OsStr};
|
|
|
|
use clap::{Command, Arg};
|
|
use serde::{Serialize, Deserialize};
|
|
|
|
|
|
#[derive(Serialize, Deserialize,Default)]
|
|
struct Subcommands{
|
|
//a hashmap of subcommands which can be created via cloning a skeleton repository
|
|
skeletons:HashMap<String, (String, String)>,
|
|
// a hashmap of subcommands which can be created via running an external executable
|
|
scripts: HashMap<String,PathBuf>
|
|
}
|
|
#[derive(Serialize, Deserialize,Default)]
|
|
struct Config{
|
|
subcommands: Subcommands
|
|
}
|
|
|
|
|
|
#[derive(Debug)]
|
|
enum Errors{IoErr(std::io::Error), GitErr(git2::Error), CliErr(CliError), Unknown}
|
|
impl From<std::io::Error> for Errors {
|
|
fn from(e: std::io::Error) -> Self {
|
|
Self::IoErr(e)
|
|
}
|
|
}
|
|
impl From<git2::Error> for Errors {
|
|
fn from(e: git2::Error) -> Self {
|
|
Self::GitErr(e)
|
|
}
|
|
}
|
|
impl From<CliError> for Errors{
|
|
fn from(e: CliError) -> Self{
|
|
Self::CliErr(e)
|
|
}
|
|
}
|
|
|
|
fn main() -> Result<(),Errors> {
|
|
|
|
let config:Config = toml::from_str({
|
|
let mut buf = String::new();
|
|
let _ = std::fs::File::open(
|
|
{let mut n = dirs::config_dir().expect("no config dir so edit the source code to make it work buddy");
|
|
n.push(clap::crate_name!().to_owned() + "/config.toml");
|
|
n
|
|
}).expect("error opening file").read_to_string(&mut buf);
|
|
buf
|
|
}.as_str()).unwrap_or_default();
|
|
|
|
let Config {subcommands:Subcommands { skeletons, scripts }} = config;
|
|
|
|
// Making sure the skeletons and scripts don't have overlap that will cause issue later
|
|
{
|
|
let scripts = scripts.keys().map(String::clone).collect::<HashSet<String>>();
|
|
let skeletons = skeletons.keys().map(String::clone).collect::<HashSet<String>>();
|
|
if scripts.intersection(&skeletons).count() > 0 {
|
|
panic!("Overlap between skeletons and scripts");
|
|
}
|
|
}
|
|
|
|
let mut program = Command::new(clap::crate_name!())
|
|
.version(clap::crate_version!())
|
|
.author(clap::crate_authors!())
|
|
.subcommands(skeletons.keys().map(|v|Command::new(v).arg(Arg::new("output_dir").takes_value(true).action(clap::ArgAction::Append))))
|
|
.subcommands(scripts.keys().map(|v|Command::new(v).arg(Arg::new("script_args").takes_value(true))));
|
|
let mut short_help = String::new();
|
|
let mut long_help = String::new();
|
|
{
|
|
let mut short_help_buf = Vec::new();
|
|
let mut long_help_buf = Vec::new();
|
|
program.write_help(&mut Cursor::new(&mut short_help_buf))?;
|
|
program.write_help(&mut Cursor::new(&mut long_help_buf))?;
|
|
short_help_buf.as_slice().read_to_string(&mut short_help)?;
|
|
long_help_buf.as_slice().read_to_string(&mut long_help)?;
|
|
}
|
|
program.build();
|
|
let matches = program.get_matches();
|
|
for (skelly_name, (skelly_src, skelly_branch)) in skeletons {
|
|
if let Some(sub) = matches.subcommand_matches(&skelly_name){
|
|
match sub.get_one::<String>("output_dir"){
|
|
Some(loc)=>{
|
|
|
|
//cloning the repo
|
|
let repo = match git2::Repository::clone(skelly_src.as_ref(), loc){
|
|
Ok(repo)=>{
|
|
repo
|
|
}
|
|
Err(_)=>{
|
|
eprintln!("libgit2 failed clone falling back to cli");
|
|
cli_fallback(skelly_src,loc)?;
|
|
git2::Repository::open(loc)?
|
|
}
|
|
};
|
|
#[cfg(debug_assertions)]
|
|
{
|
|
eprintln!("repo cloned");
|
|
std::io::stdin().read_line(&mut String::new())?;
|
|
}
|
|
|
|
eprintln!("checking out appropriate branch");
|
|
handle_process(std::process::Command::new("git")
|
|
.args(["checkout", skelly_branch.as_str()]))?;
|
|
// let branch = repo.branches(Some(git2::BranchType::Remote))?.find(|branch|{
|
|
// if let Ok(branch) = branch{
|
|
// if let Ok(Some(name)) = branch.0.name(){
|
|
// name == format!("origin/{}",skelly_branch)
|
|
// }else{false}
|
|
// }else{false}
|
|
// }).ok_or(git2::Error::from_str(format!("no branch with name {}",skelly_branch).as_str()))??;
|
|
|
|
// match repo.set_head(branch.0.into_reference().name().ok_or(git2::Error::from_str("the branch with a name somehow has a reference without a name"))?){
|
|
// Err(e)=>{
|
|
// panic!("{}",e.message());
|
|
// }
|
|
// _=>{}
|
|
// }
|
|
|
|
//#[cfg(debug_assertions)]
|
|
//{
|
|
// eprintln!("head set");
|
|
// std::io::stdin().read_line(&mut String::new())?;
|
|
//}
|
|
|
|
repo.remote_delete("origin")?;
|
|
|
|
#[cfg(debug_assertions)]
|
|
{
|
|
eprintln!("origin deleted");
|
|
std::io::stdin().read_line(&mut String::new())?;
|
|
}
|
|
|
|
|
|
|
|
let mut walk = repo.revwalk()?;
|
|
walk.set_sorting(git2::Sort::TIME)?;
|
|
walk.push_head()?;
|
|
//let head = walk.next().ok_or(git2::Error::from_str("no head"))?.map(|oid|repo.find_commit(oid))??;
|
|
let oldest_commit = repo.find_commit(walk.last().ok_or(git2::Error::from_str("No Oldest commit"))??)?;
|
|
//
|
|
#[cfg(debug_assertions)]
|
|
{
|
|
eprintln!("oldest commit found");
|
|
std::io::stdin().read_line(&mut String::new())?;
|
|
}
|
|
let pwd = repo.path().parent().expect("very bad cloning into the root dir happening");
|
|
eprintln!("rebasing skeleton's commits down into single commit {:?}", pwd);
|
|
// I give up git2 documentation/api is just too bad for me to do this with it
|
|
|
|
|
|
handle_process(std::process::Command::new("git")
|
|
.current_dir(pwd)
|
|
.args(["reset", "--mixed" , oldest_commit.id().as_bytes().iter().map(|byte|format!("{:x}",byte)).collect::<String>().chars().take(7).collect::<String>().as_str()]))?;
|
|
|
|
handle_process(std::process::Command::new("git")
|
|
.current_dir(pwd)
|
|
.args(["add", "--all"]))?;
|
|
|
|
handle_process(std::process::Command::new("git")
|
|
.current_dir(pwd)
|
|
.args(["commit", "--amend", "-am", format!("initialized from {} skeleton", skelly_name).as_str()]))?;
|
|
std::process::exit(0)
|
|
}
|
|
None=>{
|
|
eprintln!("failed to provide a path to clone the skeleton directory into");
|
|
std::process::exit(1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for script in scripts {
|
|
if let Some(sub) = matches.subcommand_matches(script.0){
|
|
std::process::exit(std::process::Command::new(script.1).args(sub.get_many::<String>("script_args").map(Iterator::collect).unwrap_or(Vec::new())).spawn()?.wait()?.code().ok_or(Errors::Unknown)?);
|
|
}
|
|
}
|
|
println!("{}", short_help);
|
|
Ok(())
|
|
}
|
|
#[derive(Debug)]
|
|
enum CliError{Io(std::io::Error), NonZero}
|
|
impl From<std::io::Error> for CliError {
|
|
fn from(e: std::io::Error) -> Self {
|
|
Self::Io(e)
|
|
}
|
|
}
|
|
fn cli_fallback<S:AsRef<OsStr>, P:AsRef<Path>>(source:S, dest: P)->Result<(),CliError>{
|
|
|
|
let mut child = std::process::Command::new("git");
|
|
child
|
|
.arg("clone")
|
|
.arg(source)
|
|
.arg(dest.as_ref());
|
|
handle_process(&mut child)
|
|
}
|
|
|
|
fn handle_process<'proc,P:Into<&'proc mut std::process::Command>>(cmd:P) -> Result<(),CliError>{
|
|
if !cmd.into().status()?.success() {
|
|
Err(CliError::NonZero)
|
|
}
|
|
else{
|
|
Ok(())
|
|
}
|
|
}
|