aboutsummaryrefslogtreecommitdiff
path: root/mling/src
diff options
context:
space:
mode:
Diffstat (limited to 'mling/src')
-rw-r--r--mling/src/cargo_style.rs60
-rw-r--r--mling/src/cli.rs69
-rw-r--r--mling/src/helps/mling_help.txt2
-rw-r--r--mling/src/lib.rs43
-rw-r--r--mling/src/pkg_mgr/mod.rs16
-rw-r--r--mling/src/proj_mgr/metadata.rs142
-rw-r--r--mling/src/proj_mgr/mod.rs22
-rw-r--r--mling/src/res/manifest_path.rs13
-rw-r--r--mling/src/res/mod.rs3
9 files changed, 345 insertions, 25 deletions
diff --git a/mling/src/cargo_style.rs b/mling/src/cargo_style.rs
index f127cc9..c212094 100644
--- a/mling/src/cargo_style.rs
+++ b/mling/src/cargo_style.rs
@@ -54,6 +54,29 @@ macro_rules! eformat_cargo {
};
}
+/// Formats a help message in cargo-style format with a bright white "help" prefix.
+///
+/// # Macros
+///
+/// - `hformat_cargo!("prefix: {}", arg)` — format-style invocation
+/// - `hformat_cargo!(expr)` — direct expression invocation
+///
+/// # Examples
+///
+/// ```ignore
+/// hformat_cargo!("use --verbose for more info");
+/// // Output: "help: use --verbose for more info" (bright white "help")
+/// ```
+#[macro_export]
+macro_rules! hformat_cargo {
+ ($fmt:literal, $($arg:tt)*) => {
+ $crate::get_cargo_help_format(format!($fmt, $($arg)*))
+ };
+ ($cmd:expr) => {
+ $crate::get_cargo_help_format($cmd)
+ };
+}
+
/// Print a message in cargo-style format with a bold green prefix.
///
/// # Macros
@@ -98,6 +121,28 @@ macro_rules! eprintln_cargo {
};
}
+/// Print a help message in cargo-style format with a bright white "help" prefix.
+///
+/// # Macros
+///
+/// - `hprintln_cargo!("prefix: {}", arg)` — format-style invocation
+/// - `hprintln_cargo!(expr)` — direct expression invocation
+///
+/// # Examples
+///
+/// ```ignore
+/// hprintln_cargo!("use --verbose for more info");
+/// ```
+#[macro_export]
+macro_rules! hprintln_cargo {
+ ($fmt:literal, $($arg:tt)*) => {
+ println!("{}", $crate::get_cargo_help_format(format!($fmt, $($arg)*)))
+ };
+ ($cmd:expr) => {
+ println!("{}", $crate::get_cargo_help_format($cmd))
+ };
+}
+
/// Format a message in cargo style format, with bold green prefix.
///
/// The input string is split at the first `:`. The part before the colon becomes
@@ -159,3 +204,18 @@ pub fn get_cargo_info_format(str: impl Into<String>) -> String {
pub fn get_cargo_error_format(str: impl Into<String>) -> String {
format!("{}: {}", "error".bold().bright_red(), str.into())
}
+
+/// Format a help message in cargo style format, with bright white "help" prefix.
+///
+/// The input string is printed as the help content, prefixed by a bright white
+/// `help:` label (not bold).
+///
+/// # Examples
+///
+/// ```ignore
+/// get_cargo_help_format("use --verbose for more info");
+/// // returns "help: use --verbose for more info"
+/// ```
+pub fn get_cargo_help_format(str: impl Into<String>) -> String {
+ format!("{}: {}", "help".bright_white(), str.into())
+}
diff --git a/mling/src/cli.rs b/mling/src/cli.rs
index 57f562f..0bae4a4 100644
--- a/mling/src/cli.rs
+++ b/mling/src/cli.rs
@@ -1,15 +1,16 @@
-use std::{env::current_dir, process::exit};
+use std::{env::current_dir, path::PathBuf, process::exit, str::FromStr};
use crate::{
- CMDCompletion, ThisProgram,
+ CMDCompletion, PackageManagerSetup, ProjectManagerSetup, ThisProgram,
display::markdown,
- proj_mgr::{CMDInstall, CMDListNamespace, CMDRemoveNamespace},
- res::ResCurrentDir,
+ eprintln_cargo,
+ pkg_mgr::{CMDInstall, CMDListNamespace, CMDRemoveNamespace},
+ res::{ResCurrentDir, ResManifestPath},
};
use mingling::{
Program,
hook::ProgramHook,
- macros::program_setup,
+ macros::{help, program_setup},
setup::{ExitCodeSetup, GeneralRendererSetup, HelpFlagSetup, QuietFlagSetup},
};
@@ -48,22 +49,40 @@ pub fn run() {
_ => {}
}));
+ // Commands
+ program.with_dispatcher(CMDCompletion);
+
// Setups
program.with_setup(HelpFlagSetup::new(["-h", "--help"]));
program.with_setup(GeneralRendererSetup);
- program.with_setup(StandardOutputSetup);
program.with_setup(ExitCodeSetup::default());
+ program.with_setup(StandardOutputSetup);
+ program.with_setup(PackageManagerSetup);
+ program.with_setup(ProjectManagerSetup);
+
// Resources
program.with_resource(ResCurrentDir {
path: current_dir().unwrap(),
});
- // Commands
- program.with_dispatcher(CMDCompletion);
- program.with_dispatcher(CMDInstall);
- program.with_dispatcher(CMDListNamespace);
- program.with_dispatcher(CMDRemoveNamespace);
+ let manifest_path = program.pick_global_argument(["-P", "--manifest-path"]);
+ program.with_resource(ResManifestPath {
+ raw: manifest_path,
+ resolved: None,
+ });
+
+ // Manifest Path Check
+ program.with_hook(ProgramHook::empty().on_post_dispatch(|c| match c {
+ // Skip completion (bypass completion)
+ ThisProgram::CompletionContext => {}
+ _ => {
+ let p = ThisProgram::this();
+ p.modify_res(|manifest_path: &mut ResManifestPath| {
+ manifest_path.resolved = Some(resolve_manifest_path(manifest_path.raw.clone()));
+ });
+ }
+ }));
// Execute
let quiet = program.stdout_setting.quiet;
@@ -93,3 +112,31 @@ fn standard_output_setup(program: &mut Program<ThisProgram>) {
program.stdout_setting.quiet = true;
});
}
+
+fn resolve_manifest_path(provided: Option<String>) -> PathBuf {
+ if let Some(path) = provided {
+ let p = PathBuf::from_str(&path).unwrap();
+ if p.is_dir() {
+ let candidate = p.join("Cargo.toml");
+ if candidate.exists() {
+ return candidate;
+ }
+ eprintln_cargo!("`{}` is not a crate root", p.display());
+ exit(1);
+ }
+ return p;
+ }
+ // Walk up from current directory to find nearest Cargo.toml
+ let mut dir = current_dir().unwrap();
+ loop {
+ let candidate = dir.join("Cargo.toml");
+ if candidate.exists() {
+ return candidate;
+ }
+ if !dir.pop() {
+ // Reached filesystem root without finding Cargo.toml
+ eprintln_cargo!("`{}` is not a crate root", current_dir().unwrap().display());
+ exit(1);
+ }
+ }
+}
diff --git a/mling/src/helps/mling_help.txt b/mling/src/helps/mling_help.txt
index 4bf1ca6..73bfd4d 100644
--- a/mling/src/helps/mling_help.txt
+++ b/mling/src/helps/mling_help.txt
@@ -15,6 +15,6 @@ __ [[b_cyan]]**--no-result**[[/]] Suppress result output
[[b_green]]**Commands:**[[/]]
__ [[b_cyan]]**install**[[/]] Install current project into
__ the **mling** package manager
-__ [[b_cyan]]**ls**[[/]], [[b_cyan]]**add**[[/]], [[b_cyan]]**rm**[[/]] List, add, or remove something
+__ [[b_cyan]]**ls**[[/]], [[b_cyan]]**show**[[/]], [[b_cyan]]**add**[[/]], [[b_cyan]]**rm**[[/]] List, show, add, or remove something
Run \`[[b_cyan]]**cargo help mling**[[/]]\` for more detailed information.
diff --git a/mling/src/lib.rs b/mling/src/lib.rs
index d4ce697..add20ac 100644
--- a/mling/src/lib.rs
+++ b/mling/src/lib.rs
@@ -1,32 +1,57 @@
#![allow(unused_imports)]
+use colored::Colorize;
use mingling::{
- macros::{gen_program, r_println, renderer},
+ macros::{chain, gen_program, pack, r_println, renderer},
res::ResExitCode,
};
pub mod cli;
+pub use cli::*;
mod cargo_style;
pub use cargo_style::*;
pub mod display;
pub mod res;
+mod pkg_mgr;
+pub use pkg_mgr::*;
+
mod proj_mgr;
-use proj_mgr::*;
+pub use proj_mgr::*;
use crate::display::markdown;
-#[renderer]
-pub fn render_error_dispatch_not_found(err: ErrorDispatcherNotFound, ec: &mut ResExitCode) {
+pack!(ResultMlingHelp = ());
+pack!(ResultUnknownCommand = String);
+
+#[chain]
+fn handle_error_dispatcher_not_found(err: ErrorDispatcherNotFound) -> Next {
if err.is_empty() {
- r_println!("{}", markdown(include_str!("helps/mling_help.txt")));
- ec.exit_code = 0;
+ ResultMlingHelp::default().to_render()
} else {
- let s = eformat_cargo!("Cannot find subcommand \"{}\"", err.join(" "));
- r_println!("{}", s);
- ec.exit_code = 1;
+ ResultUnknownCommand::new(err.join(" ")).to_render()
}
}
+#[renderer]
+fn render_mling_help(_prev: ResultMlingHelp, ec: &mut ResExitCode) {
+ r_println!("{}", markdown(include_str!("helps/mling_help.txt")));
+ ec.exit_code = 0;
+}
+
+#[renderer]
+fn render_unknown_command(prev: ResultUnknownCommand, ec: &mut ResExitCode) {
+ r_println!(
+ "{}",
+ eformat_cargo!("no such command: `{}`", prev.bright_yellow().bold())
+ );
+ r_println!();
+ r_println!(
+ "{}",
+ hformat_cargo!("view all commands with `cargo help mling`")
+ );
+ ec.exit_code = 101;
+}
+
gen_program!();
diff --git a/mling/src/pkg_mgr/mod.rs b/mling/src/pkg_mgr/mod.rs
new file mode 100644
index 0000000..d03e1d8
--- /dev/null
+++ b/mling/src/pkg_mgr/mod.rs
@@ -0,0 +1,16 @@
+use crate::ThisProgram;
+use mingling::{
+ Program,
+ macros::{dispatcher, program_setup},
+};
+
+dispatcher!("install");
+dispatcher!("ls.namespace", CMDListNamespace => EntryListNamespace);
+dispatcher!("rm.namespace", CMDRemoveNamespace => EntryRemoveNamespace);
+
+#[program_setup]
+pub fn package_manager_setup(p: &mut Program<ThisProgram>) {
+ p.with_dispatcher(CMDInstall);
+ p.with_dispatcher(CMDListNamespace);
+ p.with_dispatcher(CMDRemoveNamespace);
+}
diff --git a/mling/src/proj_mgr/metadata.rs b/mling/src/proj_mgr/metadata.rs
new file mode 100644
index 0000000..c44ed46
--- /dev/null
+++ b/mling/src/proj_mgr/metadata.rs
@@ -0,0 +1,142 @@
+use serde::Deserialize;
+use serde::Serialize;
+
+use std::path::PathBuf;
+use std::process::Command;
+
+/// Read cargo metadata by running `cargo metadata` with the given manifest path.
+pub fn read_metadata(cargo_toml: PathBuf) -> Result<CargoLockFile, std::io::Error> {
+ let output = Command::new("cargo")
+ .arg("metadata")
+ .arg("--format-version")
+ .arg("1")
+ .arg("--manifest-path")
+ .arg(cargo_toml)
+ .output()?;
+
+ if !output.status.success() {
+ let stderr = String::from_utf8_lossy(&output.stderr);
+ return Err(std::io::Error::new(
+ std::io::ErrorKind::Other,
+ format!("cargo metadata failed: {}", stderr),
+ ));
+ }
+
+ let lock_file: CargoLockFile = serde_json::from_slice(&output.stdout)
+ .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
+
+ Ok(lock_file)
+}
+
+/// A cargo metadata lock file that serde can serialize and deserialize.
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct CargoLockFile {
+ pub packages: Vec<Package>,
+ #[serde(rename = "workspace_members")]
+ pub workspace_members: Vec<String>,
+ #[serde(rename = "workspace_default_members")]
+ pub workspace_default_members: Vec<String>,
+ pub resolve: Resolve,
+ #[serde(rename = "target_directory")]
+ pub target_directory: String,
+ #[serde(rename = "build_directory")]
+ pub build_directory: String,
+ pub version: u64,
+ #[serde(rename = "workspace_root")]
+ pub workspace_root: String,
+ pub metadata: Option<serde_json::Value>,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct Package {
+ pub name: String,
+ pub version: String,
+ pub id: String,
+ pub license: Option<String>,
+ #[serde(rename = "license_file")]
+ pub license_file: Option<String>,
+ pub description: Option<String>,
+ pub source: Option<String>,
+ pub dependencies: Vec<Dependency>,
+ pub targets: Vec<Target>,
+ pub features: std::collections::BTreeMap<String, Vec<String>>,
+ #[serde(rename = "manifest_path")]
+ pub manifest_path: String,
+ pub metadata: Option<serde_json::Value>,
+ pub publish: Option<serde_json::Value>,
+ pub authors: Vec<String>,
+ pub categories: Vec<String>,
+ pub keywords: Vec<String>,
+ pub readme: Option<String>,
+ pub repository: Option<String>,
+ pub homepage: Option<String>,
+ pub documentation: Option<String>,
+ pub edition: String,
+ pub links: Option<String>,
+ #[serde(rename = "default_run")]
+ pub default_run: Option<String>,
+ #[serde(rename = "rust_version")]
+ pub rust_version: Option<String>,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct Dependency {
+ pub name: String,
+ pub source: Option<String>,
+ pub req: String,
+ pub kind: Option<String>,
+ pub rename: Option<String>,
+ pub optional: bool,
+ #[serde(rename = "uses_default_features")]
+ pub uses_default_features: bool,
+ pub features: Vec<String>,
+ pub target: Option<String>,
+ pub registry: Option<String>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub path: Option<String>,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct Target {
+ pub kind: Vec<String>,
+ #[serde(rename = "crate_types")]
+ pub crate_types: Vec<String>,
+ pub name: String,
+ #[serde(rename = "src_path")]
+ pub src_path: String,
+ pub edition: String,
+ pub doc: bool,
+ pub doctest: bool,
+ pub test: bool,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ #[serde(rename = "required-features")]
+ pub required_features: Option<Vec<String>>,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct Resolve {
+ pub nodes: Vec<ResolveNode>,
+ pub root: Option<String>,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct ResolveNode {
+ pub id: String,
+ pub dependencies: Vec<String>,
+ pub deps: Vec<DepInfo>,
+ pub features: Vec<String>,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct DepInfo {
+ pub name: String,
+ pub pkg: String,
+ #[serde(rename = "dep_kinds")]
+ pub dep_kinds: Vec<DepKind>,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct DepKind {
+ pub kind: Option<String>,
+ pub target: Option<String>,
+}
diff --git a/mling/src/proj_mgr/mod.rs b/mling/src/proj_mgr/mod.rs
index 381784c..07a9910 100644
--- a/mling/src/proj_mgr/mod.rs
+++ b/mling/src/proj_mgr/mod.rs
@@ -1,6 +1,20 @@
-use mingling::macros::dispatcher;
+use crate::ThisProgram;
+use mingling::{
+ Program,
+ macros::{dispatcher, program_setup},
+};
-dispatcher!("install");
+pub mod metadata;
-dispatcher!("ls.namespace", CMDListNamespace => EntryListNamespace);
-dispatcher!("rm.namespace", CMDRemoveNamespace => EntryRemoveNamespace);
+dispatcher!("show.binaries");
+dispatcher!("show.workspace");
+dispatcher!("show.target-dir",
+ CMDShowTargetDirectories => EntryShowTargetDirectories
+);
+
+#[program_setup]
+pub fn project_manager_setup(p: &mut Program<ThisProgram>) {
+ p.with_dispatcher(CMDShowBinaries);
+ p.with_dispatcher(CMDShowWorkspace);
+ p.with_dispatcher(CMDShowTargetDirectories);
+}
diff --git a/mling/src/res/manifest_path.rs b/mling/src/res/manifest_path.rs
new file mode 100644
index 0000000..7f1686e
--- /dev/null
+++ b/mling/src/res/manifest_path.rs
@@ -0,0 +1,13 @@
+use std::path::PathBuf;
+
+#[derive(Default, Clone)]
+pub struct ResManifestPath {
+ pub raw: Option<String>,
+ pub resolved: Option<PathBuf>,
+}
+
+impl ResManifestPath {
+ pub fn resolved(&self) -> &PathBuf {
+ self.resolved.as_ref().unwrap()
+ }
+}
diff --git a/mling/src/res/mod.rs b/mling/src/res/mod.rs
index b6ea60d..caa843c 100644
--- a/mling/src/res/mod.rs
+++ b/mling/src/res/mod.rs
@@ -1,2 +1,5 @@
mod current_dir;
pub use current_dir::*;
+
+mod manifest_path;
+pub use manifest_path::*;