#![deny(missing_docs)]
#![warn(clippy::all)]
pub mod abi;
mod export;
use std::ffi::OsString;
use std::fmt;
use std::path::PathBuf;
use std::str::FromStr;
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Family(std::borrow::Cow<'static, str>);
impl Family {
pub const NODE: Family = Family(std::borrow::Cow::Borrowed("node"));
pub const RUST: Family = Family(std::borrow::Cow::Borrowed("rust"));
pub const PYTHON: Family = Family(std::borrow::Cow::Borrowed("python"));
pub const GO: Family = Family(std::borrow::Cow::Borrowed("go"));
pub const JVM: Family = Family(std::borrow::Cow::Borrowed("jvm"));
pub const DOTNET: Family = Family(std::borrow::Cow::Borrowed("dotnet"));
pub const PHP: Family = Family(std::borrow::Cow::Borrowed("php"));
pub const RUBY: Family = Family(std::borrow::Cow::Borrowed("ruby"));
pub fn new(name: impl Into<std::borrow::Cow<'static, str>>) -> Self {
Family(name.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
pub fn display(&self) -> &str {
match self.as_str() {
"node" => "Node / 前端",
"rust" => "Rust",
"python" => "Python",
"go" => "Go",
"jvm" => "JVM",
"dotnet" => ".NET",
"php" => "PHP",
"ruby" => "Ruby",
other => other,
}
}
}
impl fmt::Display for Family {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl From<&'static str> for Family {
fn from(s: &'static str) -> Self {
Family::new(s)
}
}
impl AsRef<str> for Family {
fn as_ref(&self) -> &str {
self.as_str()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[repr(u32)]
pub enum Verb {
Install = abi::VERB_INSTALL,
Remove = abi::VERB_REMOVE,
Run = abi::VERB_RUN,
Build = abi::VERB_BUILD,
Test = abi::VERB_TEST,
Update = abi::VERB_UPDATE,
Exec = abi::VERB_EXEC,
}
impl Verb {
pub const ALL: &'static [Verb] = &[
Verb::Install,
Verb::Remove,
Verb::Run,
Verb::Build,
Verb::Test,
Verb::Update,
Verb::Exec,
];
pub const fn to_abi(self) -> u32 {
self as u32
}
pub const fn from_abi(n: u32) -> Option<Verb> {
match n {
abi::VERB_INSTALL => Some(Verb::Install),
abi::VERB_REMOVE => Some(Verb::Remove),
abi::VERB_RUN => Some(Verb::Run),
abi::VERB_BUILD => Some(Verb::Build),
abi::VERB_TEST => Some(Verb::Test),
abi::VERB_UPDATE => Some(Verb::Update),
abi::VERB_EXEC => Some(Verb::Exec),
_ => None,
}
}
pub const fn as_str(self) -> &'static str {
match self {
Verb::Install => "install",
Verb::Remove => "remove",
Verb::Run => "run",
Verb::Build => "build",
Verb::Test => "test",
Verb::Update => "update",
Verb::Exec => "exec",
}
}
}
impl fmt::Display for Verb {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl FromStr for Verb {
type Err = PluginError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Verb::ALL
.iter()
.copied()
.find(|v| v.as_str() == s)
.ok_or_else(|| PluginError::other(format!("不认识的动词:{s}")))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CommandSpec {
pub program: OsString,
pub args: Vec<OsString>,
pub cwd: Option<PathBuf>,
}
impl CommandSpec {
pub fn new(program: impl Into<OsString>) -> Self {
Self {
program: program.into(),
args: Vec::new(),
cwd: None,
}
}
pub fn arg(mut self, arg: impl Into<OsString>) -> Self {
self.args.push(arg.into());
self
}
pub fn args<I, S>(mut self, args: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<OsString>,
{
self.args.extend(args.into_iter().map(Into::into));
self
}
pub fn cwd(mut self, dir: impl Into<PathBuf>) -> Self {
self.cwd = Some(dir.into());
self
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Context {
pub project_root: PathBuf,
pub matched: Vec<String>,
}
impl Context {
pub fn has_matched(&self, file: &str) -> bool {
self.matched.iter().any(|m| m == file)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PluginError {
UnsupportedVerb(Verb),
InvalidArgs(String),
Other(String),
}
impl PluginError {
pub fn unsupported_verb(verb: Verb) -> Self {
PluginError::UnsupportedVerb(verb)
}
pub fn invalid_args(message: impl Into<String>) -> Self {
PluginError::InvalidArgs(message.into())
}
pub fn other(message: impl Into<String>) -> Self {
PluginError::Other(message.into())
}
pub fn code(&self) -> u32 {
match self {
PluginError::UnsupportedVerb(_) => abi::PMPX_ERR_UNSUPPORTED_VERB,
PluginError::InvalidArgs(_) => abi::PMPX_ERR_INVALID_ARGS,
PluginError::Other(_) => abi::PMPX_ERR_INTERNAL,
}
}
pub fn message(&self) -> String {
match self {
PluginError::UnsupportedVerb(v) => format!("不支持动词 {v}"),
PluginError::InvalidArgs(m) => m.clone(),
PluginError::Other(m) => m.clone(),
}
}
}
impl fmt::Display for PluginError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.message())
}
}
impl std::error::Error for PluginError {}
impl From<String> for PluginError {
fn from(message: String) -> Self {
PluginError::Other(message)
}
}
impl From<&str> for PluginError {
fn from(message: &str) -> Self {
PluginError::Other(message.to_string())
}
}
impl From<std::io::Error> for PluginError {
fn from(e: std::io::Error) -> Self {
PluginError::Other(e.to_string())
}
}
pub trait PackageManager: Send + Sync {
fn name(&self) -> &str;
fn family(&self) -> Family;
fn command(
&self,
ctx: &Context,
verb: Verb,
args: &[OsString],
) -> Result<CommandSpec, PluginError>;
}