Moved cli binary and bootstrapper into separate crate

This commit is contained in:
Nathan Fisher 2023-03-28 16:57:13 -04:00
parent 026e8599a5
commit 1160730e1f
8 changed files with 2 additions and 438 deletions

89
Cargo.lock generated
View File

@ -99,57 +99,6 @@ dependencies = [
"winapi",
]
[[package]]
name = "clap"
version = "4.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c911b090850d79fc64fe9ea01e28e465f65e821e08813ced95bced72f7a8a9b"
dependencies = [
"bitflags",
"clap_lex",
"is-terminal",
"strsim",
"termcolor",
]
[[package]]
name = "clap_complete"
version = "4.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37686beaba5ac9f3ab01ee3172f792fc6ffdd685bfb9e63cfef02c0571a4e8e1"
dependencies = [
"clap",
]
[[package]]
name = "clap_complete_nushell"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7fa41f5e6aa83bd151b70fd0ceaee703d68cd669522795dc812df9edad1252c"
dependencies = [
"clap",
"clap_complete",
]
[[package]]
name = "clap_lex"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "033f6b7a4acb1f358c742aaca805c939ee73b4c6209ae4318ec7aca81c42e646"
dependencies = [
"os_str_bytes",
]
[[package]]
name = "clap_mangen"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4237e29de9c6949982ba87d51709204504fb8ed2fd38232fcb1e5bf7d4ba48c8"
dependencies = [
"clap",
"roff",
]
[[package]]
name = "codespan-reporting"
version = "0.11.1"
@ -534,9 +483,7 @@ name = "hpk"
version = "0.1.0"
dependencies = [
"chrono",
"clap",
"deku",
"package-bootstrap",
"rayon",
"reqwest",
"ron",
@ -695,18 +642,6 @@ version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30e22bd8629359895450b59ea7a776c850561b96a3b1d31321c1949d9e6c9146"
[[package]]
name = "is-terminal"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8687c819457e979cc940d09cb16e42a1bf70aa6b60a549de6d3a62a0ee90c69e"
dependencies = [
"hermit-abi 0.3.1",
"io-lifetimes",
"rustix",
"windows-sys 0.45.0",
]
[[package]]
name = "itoa"
version = "1.0.6"
@ -898,24 +833,6 @@ dependencies = [
"vcpkg",
]
[[package]]
name = "os_str_bytes"
version = "6.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ceedf44fb00f2d1984b0bc98102627ce622e083e49a5bacdb3e514fa4238e267"
[[package]]
name = "package-bootstrap"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e99c0257afd97e84d5b56d8cfd9f3c551047119db7664652b544a1061622b2e9"
dependencies = [
"clap",
"clap_complete",
"clap_complete_nushell",
"clap_mangen",
]
[[package]]
name = "percent-encoding"
version = "2.2.0"
@ -1042,12 +959,6 @@ dependencies = [
"winreg",
]
[[package]]
name = "roff"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b833d8d034ea094b1ea68aa6d5c740e0d04bad9d16568d08ba6f76823a114316"
[[package]]
name = "ron"
version = "0.8.0"

View File

@ -9,20 +9,10 @@ license = "GPL-3.0-only"
[workspace]
members = [ "tar" ]
[[bin]]
name = "hpk"
path = "src/hpk.rs"
[[bin]]
name = "bootstrap"
path = "src/bootstrap.rs"
[workspace.dependencies]
clap = "4.1"
deku = "0.16"
[dependencies]
clap.workspace = true
deku.workspace = true
rayon = "1.7"
ron = "0.8"
@ -35,10 +25,6 @@ zstd = "0.12"
version = "0.4"
features = ["serde"]
[dependencies.package-bootstrap]
version = "0.1.0"
features = ["mangen"]
[dependencies.reqwest]
version = "0.11"
features = ["blocking"]

View File

@ -65,9 +65,8 @@ allowing for creation of alternate frontends to be written in the future. The va
majority of the data is exported rather than having secret, private API's that
are not exposed in the library.
While the hpk source distribution will only include a command line client, provision
is made for some nice modern features for a future graphical client. As an example,
the *package.ron* file has an optional field to link to an
Provision is made for some nice modern features for a future graphical client. As
an example, the *package.ron* file has an optional field to link to an
[AppStream](https://www.freedesktop.org/wiki/Distributions/AppStream/) metadata
file, allowing for nice things such as screenshots in a mode *App Store* like
presentation.

View File

@ -1,30 +0,0 @@
use {
clap::{Arg, Command},
hpk::cli,
package_bootstrap::Bootstrap,
std::{error::Error, path::PathBuf},
};
fn main() -> Result<(), Box<dyn Error>> {
let matches = Command::new("bootstrap")
.about("install the software")
.author("Nathan Fisher")
.version(env!("CARGO_PKG_VERSION"))
.args([
Arg::new("arch")
.help("the architecture of the binary to be installed")
.short('a')
.long("arch")
.num_args(1),
Arg::new("output")
.help("the output directory for the installation")
.required(true)
.num_args(1),
])
.get_matches();
let outdir = matches.get_one::<String>("output").unwrap().to_string();
let outdir = PathBuf::from(&outdir);
let arch = matches.get_one::<String>("arch").map(|x| x.to_string());
Bootstrap::new("hpk", cli::cli()).install(&outdir, arch, 1)?;
Ok(())
}

View File

@ -1,159 +0,0 @@
use clap::{value_parser, Arg, ArgAction, Command, ValueHint};
pub fn cli() -> Command {
Command::new("hpk")
.about("A package manager for HitchHiker Linux")
.author("Nathan Fisher")
.version(env!("CARGO_PKG_VERSION"))
.propagate_version(true)
.arg_required_else_help(true)
.subcommands([
create(),
init(),
search(),
info(),
install(),
remove(),
upgrade(),
])
}
fn create() -> Command {
Command::new("create")
.about("Create a new package from a directory of files")
.args([
Arg::new("directory")
.help("the directory where the files are located")
.required(true)
.num_args(1),
Arg::new("output")
.help("the location to save the package archive and package.ron [default: current working directory]")
.short('o')
.long("output")
.value_name("directory")
.value_hint(ValueHint::DirPath)
.num_args(1),
Arg::new("specs")
.help("path to the package specs file in `ron` format")
.short('s')
.long("specs")
.conflicts_with_all(["name", "package-version", "release", "dependencies"])
.num_args(1),
Arg::new("name")
.help("package name")
.short('n')
.long("name")
.num_args(1)
.required_unless_present("specs"),
Arg::new("package-version")
.help("package version")
.short('v')
.long("package-version")
.num_args(1)
.required_unless_present("specs"),
Arg::new("release")
.help("release number")
.short('r')
.long("release")
.default_value("1")
.value_parser(value_parser!(u8)),
Arg::new("dependencies")
.help("a comma separated list of dependencies")
.short('d')
.long("dependencies")
.value_delimiter(',')
.num_args(1..),
])
}
fn init() -> Command {
Command::new("init")
.about("Initialize a package `specs.ron` file in the current directory")
.args([
Arg::new("name")
.help("package name")
.short('n')
.long("name")
.num_args(1),
Arg::new("package-version")
.help("package version")
.short('v')
.long("package-version")
.num_args(1),
Arg::new("release")
.help("release number")
.short('r')
.long("release")
.default_value("1")
.value_parser(value_parser!(usize)),
Arg::new("dependencies")
.help("a comma separated list of dependencies")
.short('d')
.long("dependencies")
.value_delimiter(',')
.num_args(1..),
])
}
fn search() -> Command {
Command::new("search")
.about("Search for packages")
.visible_alias("se")
.args([
Arg::new("installed")
.help("only list installed packages")
.short('i')
.long("installed")
.action(ArgAction::SetTrue),
Arg::new("remote")
.help("do not list installed packages")
.short('r')
.long("remote")
.conflicts_with("installed")
.action(ArgAction::SetTrue),
Arg::new("names")
.help("only search in package names, not descriptions")
.short('n')
.long("names")
.action(ArgAction::SetTrue),
Arg::new("query").use_value_delimiter(false).required(true),
])
}
fn info() -> Command {
Command::new("info")
.about("Display information about a specific package")
.arg(Arg::new("name").required(true))
}
fn install() -> Command {
Command::new("install")
.about("Install packages")
.visible_aliases(["in", "add"])
.args([
Arg::new("package")
.required_unless_present("local")
.conflicts_with("local")
.num_args(1..),
Arg::new("local")
.help("install a package from the filesystem")
.short('l')
.long("local")
.value_name("package")
.value_hint(ValueHint::FilePath)
.num_args(1..),
])
}
fn remove() -> Command {
Command::new("remove")
.about("Remove packages")
.visible_aliases(["rm", "del"])
.arg(Arg::new("package").required(true).num_args(1..))
}
fn upgrade() -> Command {
Command::new("upgrade")
.about("Upgrade packages")
.visible_aliases(["update", "up", "sync"])
}

View File

@ -1,97 +0,0 @@
use {
clap::ArgMatches,
hpk::{cli::cli, Creator, Dependency, Message, Specs, Version},
ron::ser::{to_writer_pretty, PrettyConfig},
std::{env, error::Error, fs::File, io::{BufWriter, self}, path::PathBuf, sync::mpsc},
};
fn main() -> Result<(), Box<dyn Error>> {
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());
}
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<dyn Error>> {
let dir = PathBuf::from(matches.get_one::<String>("directory").unwrap().to_string());
let outdir = if let Some(d) = matches.get_one::<String>("output") {
PathBuf::from(&d.to_string())
} else {
env::current_dir()?
};
let mut specs = if let Some(s) = matches.get_one::<String>("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::<String>("name") {
specs.name = n.to_string();
}
if let Some(v) = matches.get_one::<String>("package-version") {
specs.version = v.to_string().parse::<Version>()?;
}
if let Some(r) = matches.get_one::<u8>("release") {
specs.release = *r;
}
if let Some(deps) = matches.get_many::<String>("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)?;
let (sender, receiver) = mpsc::channel();
creator.create(&outdir, sender)?;
for msg in receiver.iter() {
match msg {
Message::MemberAdded(_s) => {}
Message::Success(s) => println!("{s}"),
Message::Failure(s) => {
eprint!("{s}");
return Err(io::Error::new(io::ErrorKind::Other, s).into());
}
}
}
Ok(())
}
fn init(matches: &ArgMatches) -> Result<PathBuf, Box<dyn Error>> {
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 = Specs::try_from(matches)?;
to_writer_pretty(writer, &specs, cfg)?;
Ok(specsfile)
}
fn search(_matches: &ArgMatches) -> Result<(), Box<dyn Error>> {
unimplemented!();
}
fn install(_matches: &ArgMatches) -> Result<(), Box<dyn Error>> {
unimplemented!();
}
fn remove(_matches: &ArgMatches) -> Result<(), Box<dyn Error>> {
unimplemented!();
}
fn upgrade() -> Result<(), Box<dyn Error>> {
unimplemented!();
}

View File

@ -1,5 +1,4 @@
#![warn(clippy::all, clippy::pedantic)]
pub mod cli;
mod creator;
mod db;
mod hooks;

View File

@ -1,9 +1,7 @@
use {
super::{Group, User},
crate::{Dependency, Version},
clap::ArgMatches,
serde::{Deserialize, Serialize},
std::{env, error::Error, ffi::OsStr},
};
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
@ -31,46 +29,3 @@ pub struct Specs {
/// an optional post installation shell script to be run
pub post_install: Option<String>,
}
impl TryFrom<&ArgMatches> for Specs {
type Error = Box<dyn Error>;
#[allow(clippy::cast_possible_truncation)]
fn try_from(matches: &ArgMatches) -> Result<Self, Self::Error> {
let mut specs = Specs::default();
let mut name: Option<String> = None;
let mut version: Option<Version> = 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::<String>("name") {
specs.name = name.to_string();
} else if let Some(n) = name {
specs.name = n;
}
if let Some(version) = matches.get_one::<String>("package-version") {
specs.version = version.parse()?;
} else if let Some(v) = version {
specs.version = v;
}
if let Some(release) = matches.get_one::<usize>("release") {
specs.release = *release as u8;
}
if let Some(deps) = matches.get_many::<String>("dependencies") {
let deps = deps
.map(|d| Dependency {
name: d.to_string(),
version: (None, None),
})
.collect::<Vec<Dependency>>();
specs.dependencies = deps;
}
Ok(specs)
}
}