#![warn(clippy::all, clippy::pedantic)] mod cli; use { clap::ArgMatches, cli::cli, hpk::{CreationError, Creator, Dependency, InstallMessage, Installer, Message, Specs, Version}, indicatif::{ProgressBar, ProgressStyle}, ron::ser::{to_writer_pretty, PrettyConfig}, std::{ env, error::Error, ffi::OsStr, fmt, fs::File, io::{self, BufWriter, ErrorKind}, path::PathBuf, sync::mpsc, thread, }, }; static TEMPLATE: &str = "[ {prefix} ] {wide_bar}{pos:>5.cyan}/{len:5.green}{msg:>30}"; fn main() -> Result<(), Box> { let matches = cli().get_matches(); match matches.subcommand() { Some(("create", matches)) => create(matches)?, Some(("init", matches)) => { let specsfile = init(matches)?; println!("Created specsfile {}", specsfile.display()); if matches.get_flag("edit") { cli::edit(specsfile.to_str().unwrap())?; } } Some(("search", matches)) => search(matches)?, Some(("install", matches)) => install(matches)?, Some(("remove", matches)) => remove(matches)?, Some(("upgrade", _)) => upgrade()?, _ => {} } Ok(()) } fn create(matches: &ArgMatches) -> Result<(), Box> { let dir = PathBuf::from(matches.get_one::("directory").unwrap().to_string()); let outdir = if let Some(d) = matches.get_one::("output") { PathBuf::from(&d.to_string()) } else { env::current_dir()? }; let mut specs = if let Some(s) = matches.get_one::("specs") { let path = PathBuf::from(s.to_string()); let fd = File::open(path)?; ron::de::from_reader(fd)? } else { Specs::default() }; if let Some(n) = matches.get_one::("name") { specs.name = n.to_string(); } if let Some(v) = matches.get_one::("package-version") { specs.version = v.to_string().parse::()?; } if let Some(r) = matches.get_one::("release") { specs.release = *r; } if let Some(deps) = matches.get_many::("dependencies") { for d in deps { let d = Dependency { name: d.to_string(), version: (None, None), }; specs.dependencies.push(d); } } let creator = Creator::new(&dir, specs.clone())?; let (sender, receiver) = mpsc::channel(); let len = creator.len(); let pb = ProgressBar::new(len as u64); pb.set_style(ProgressStyle::with_template(TEMPLATE).unwrap()); pb.set_prefix("Adding files"); pb.println(format!( "Creating package {}-{}_{}.tar.zst", &specs.name, &specs.version, &specs.release )); let handle = thread::spawn(move || { for msg in receiver.iter() { match msg { Message::MemberAdded(s) => { pb.set_message(s.split('/').last().unwrap().to_string()); pb.inc(1); } Message::Success(_s) => { pb.inc(1); pb.finish_and_clear(); } } } Ok::<(), CreationError>(()) }); creator.create(&outdir, sender)?; match handle.join() { Ok(_) => { println!("Package created successfully"); Ok(()) } Err(e) => Err(io::Error::new(ErrorKind::Other, format!("{e:?}")).into()), } } fn install_local + fmt::Display>( archive: P, root: P, ) -> Result<(), Box> { let (sender, receiver) = mpsc::channel(); let pb = ProgressBar::new(0); pb.set_style(ProgressStyle::with_template(TEMPLATE).unwrap()); pb.println(format!("Installing package {}", archive,)); let installer = Installer::new_for_file(root, archive)?; let handle = thread::spawn(move || { for msg in receiver.iter() { match msg { InstallMessage::ArchiveLen(len) => pb.set_length(len.try_into().unwrap()), InstallMessage::LinkCreated(link) => { pb.inc(1); } InstallMessage::DirectoryCreated(dir) => { pb.inc(1); } InstallMessage::FileInstalled(file) => { pb.inc(1); } _ => {} } } }); installer.install(sender)?; match handle.join() { Ok(hooks) => { println!("hooks: {hooks:?}"); Ok(()) } Err(_) => Err(io::Error::new(ErrorKind::Other, "Unknown thread error").into()), } } fn init(matches: &ArgMatches) -> Result> { let specsfile = PathBuf::from("package.specs"); let cfg = PrettyConfig::new().struct_names(true); let buf = File::create(&specsfile)?; let writer = BufWriter::new(buf); let specs = create_specs(matches)?; to_writer_pretty(writer, &specs, cfg)?; Ok(specsfile) } fn search(_matches: &ArgMatches) -> Result<(), Box> { unimplemented!(); } fn install(_matches: &ArgMatches) -> Result<(), Box> { unimplemented!(); } fn remove(_matches: &ArgMatches) -> Result<(), Box> { unimplemented!(); } fn upgrade() -> Result<(), Box> { unimplemented!(); } #[allow(clippy::cast_possible_truncation)] pub fn create_specs(matches: &ArgMatches) -> Result> { let mut specs = Specs::default(); let mut name: Option = None; let mut version: Option = None; let cwd = env::current_dir()?; if let Some(dir) = cwd.file_name().and_then(OsStr::to_str) { if let Some((n, v)) = dir.split_once('-') { name = Some(n.to_string()); if let Ok(v) = v.parse() { version = Some(v); } } } if let Some(name) = matches.get_one::("name") { specs.name = name.to_string(); } else if let Some(n) = name { specs.name = n; } if let Some(version) = matches.get_one::("package-version") { specs.version = version.parse()?; } else if let Some(v) = version { specs.version = v; } if let Some(release) = matches.get_one::("release") { specs.release = *release as u8; } if let Some(deps) = matches.get_many::("dependencies") { let deps = deps .map(|d| Dependency { name: d.to_string(), version: (None, None), }) .collect::>(); specs.dependencies = deps; } Ok(specs) }