diff options
Diffstat (limited to 'mling')
33 files changed, 0 insertions, 1997 deletions
diff --git a/mling/.gitignore b/mling/.gitignore deleted file mode 100644 index b124b7c..0000000 --- a/mling/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -debug.rs -version.txt diff --git a/mling/Cargo.toml b/mling/Cargo.toml deleted file mode 100644 index 8008463..0000000 --- a/mling/Cargo.toml +++ /dev/null @@ -1,41 +0,0 @@ -[package] -name = "mingling-cli" -version.workspace = true -edition.workspace = true -authors = ["Weicao-CatilGrass"] -license.workspace = true -readme = "README.md" -repository.workspace = true -description = "Mingling's scaffolding tool" -keywords = ["cli", "scaffolding", "command-line", "framework"] -categories = ["command-line-interface"] - -[[bin]] -name = "cargo-mling" -path = "src/bin/cargo-mling.rs" - -[[bin]] -name = "mling" -path = "src/bin/mling.rs" - -[dependencies] -mingling = { path = "../mingling", features = [ - "parser", - "comp", - "structural_renderer", - "extra_macros", - "yaml_serde_fmt", - "pathf" -] } - -serde = { version = "1", features = ["derive"] } -serde_json = "1" - -colored = "3.1.1" -dirs = "6.0.0" -just_fmt = "0.1.2" -toml.workspace = true - -[build-dependencies] -chrono = "0.4" -mingling = { path = "../mingling", features = ["comp", "pathf", "builds"] } diff --git a/mling/LICENSE-APACHE b/mling/LICENSE-APACHE deleted file mode 120000 index 965b606..0000000 --- a/mling/LICENSE-APACHE +++ /dev/null @@ -1 +0,0 @@ -../LICENSE-APACHE
\ No newline at end of file diff --git a/mling/LICENSE-MIT b/mling/LICENSE-MIT deleted file mode 120000 index 76219eb..0000000 --- a/mling/LICENSE-MIT +++ /dev/null @@ -1 +0,0 @@ -../LICENSE-MIT
\ No newline at end of file diff --git a/mling/README.md b/mling/README.md deleted file mode 100644 index a4d0b1d..0000000 --- a/mling/README.md +++ /dev/null @@ -1,3 +0,0 @@ -This directory is the companion CLI tool for Mingling. After installation, it can be invoked via `cargo mling` or `mling`. - -It is currently under refactoring and **completely unusable**. diff --git a/mling/build.rs b/mling/build.rs deleted file mode 100644 index 9a7b503..0000000 --- a/mling/build.rs +++ /dev/null @@ -1,63 +0,0 @@ -use std::path::Path; -use std::process::Command; - -use mingling::build::analyze_and_build_type_mapping; -use mingling::build::build_comp_scripts; - -fn main() { - build_version_info(); - build_completion(); - analyze_and_build_type_mapping().unwrap(); -} - -fn build_version_info() { - // Read version from CARGO_PKG_VERSION (inherited from workspace Cargo.toml) - let version = env!("CARGO_PKG_VERSION"); - - // Get git commit hash (first 9 characters) - let commit_hash = Command::new("git") - .args(["rev-parse", "--short=9", "HEAD"]) - .output() - .ok() - .and_then(|output| { - if output.status.success() { - String::from_utf8(output.stdout).ok() - } else { - None - } - }) - .map(|s| s.trim().to_string()) - .unwrap_or_else(|| "unknown".to_string()); - - // Get date from git commit, fallback to current date - let date = Command::new("git") - .args(["log", "-1", "--format=%ad", "--date=format:%Y-%m-%d"]) - .output() - .ok() - .and_then(|output| { - if output.status.success() { - String::from_utf8(output.stdout).ok() - } else { - None - } - }) - .map(|s| s.trim().to_string()) - .unwrap_or_else(|| chrono::Local::now().format("%Y-%m-%d").to_string()); - - let version_string = format!("mling {version} ({commit_hash} {date})"); - - let out_dir = Path::new(&std::env::var("CARGO_MANIFEST_DIR").unwrap()) - .join("src") - .join("helps") - .join("version.txt"); - - std::fs::write(&out_dir, version_string).expect("failed to write version.txt"); - - println!("cargo::rerun-if-changed=../Cargo.toml"); - println!("cargo::rerun-if-changed=../Cargo.lock"); - println!("cargo::rerun-if-changed=.git/HEAD"); -} - -fn build_completion() { - build_comp_scripts("mling").unwrap(); -} diff --git a/mling/res/CHECKLIST.md b/mling/res/CHECKLIST.md deleted file mode 100644 index bc7b8bc..0000000 --- a/mling/res/CHECKLIST.md +++ /dev/null @@ -1,110 +0,0 @@ -> [!NOTE] -> -> You are using `mling` to create your `mingling` project. Before proceeding, please fill in your project information. - -## Question 1: Fill in your project information - -Use `snake-case` style to fill in the project name. It will serve as your `crate` name: - -```name -my-cli -``` - -Describe your `program` in one sentence: - -```description -This is a command-line program -``` - -## Question 2: Which Mingling version do you need? - -> [!TIP] -> -> You must select **EXACTLY ONE** option - -- [x] `ver:0.2` Based on the `Mingling@0.2.x` template - -## Question 3: What is your project layout style? - -> [!TIP] -> -> You must select **EXACTLY ONE** option - -- [x] `base:flat` Flat mode (Simple) - -``` -# The most basic mode -./main.rs -./foo.rs -./bar.rs -./globals.rs -``` - -- [ ] `base:modularity` Modular mode - -``` -├─ module1/ # Stores private concepts internally -│ ├── mod.rs # Command registration -│ ├── chains/ # Core logic -│ └── renderers/ # Result rendering -├─ module2/ -│ ├── mod.rs # Command registration -│ ├── chains/ # Core logic -│ └── renderers/ # Result rendering -├─ global/ # Stores public concepts in the global directory -│ ├── errors/ # Global Errors -│ ├── setups/ # Setups -│ └── resources/ # Global Resources -└─ main.rs -``` - -- [ ] `base:command` Command mode - -``` -# Stores all commands -├─ commands/ # Organizes subcommands by hierarchy -│ ├── remote/add.rs # "remote add" -│ ├── remote/rm.rs # "remote rm" -│ ├── remote.rs # Not a command, -│ │ but a local implementation for -│ │ the remote series of commands -│ ├── foo.rs # "foo" -│ └── bar.rs # "bar" -├─ errors/ # Global Errors -├─ resources/ # Global Resources -├─ setups/ # Setups -└─ main.rs -``` - -## Question 4: What additional features do you need to enable? - -> [!TIP] -> -> All of the following options are **OPTIONAL** - -**Pre:** - -- [x] `exec:git_init` Initialize this project as a git repository (requires `git` to be installed) - -**Features:** - -- [x] `feat:extra_macros` Extra macro support -- [x] `feat:dispatch_tree` Compile-time dispatch tree (solidify all commands) -- [ ] `feat:structural_renderer` Structural renderer (JSON/YAML) for feature output -- [ ] `feat:comp` Dynamic completion system -- [ ] `feat:clap` Clap support -- [ ] `feat:parser` Mingling Picker parser support -- [ ] `feat:async` Async support - -**Implementations:** - -- [ ] `impl:exit_code` Introduce exit code control -- [ ] `impl:fallback` Fallback functionality when no command is given - ---- - -After filling in the above, execute the following command to create the project: - -``` -~# mling gen -``` diff --git a/mling/res/template_0.2/command/Cargo.toml.tmpl b/mling/res/template_0.2/command/Cargo.toml.tmpl deleted file mode 100644 index e69de29..0000000 --- a/mling/res/template_0.2/command/Cargo.toml.tmpl +++ /dev/null diff --git a/mling/res/template_0.2/command/src/main.rs b/mling/res/template_0.2/command/src/main.rs deleted file mode 100644 index 8b13789..0000000 --- a/mling/res/template_0.2/command/src/main.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/mling/res/template_0.2/flat/Cargo.toml.tmpl b/mling/res/template_0.2/flat/Cargo.toml.tmpl deleted file mode 100644 index e69de29..0000000 --- a/mling/res/template_0.2/flat/Cargo.toml.tmpl +++ /dev/null diff --git a/mling/res/template_0.2/flat/src/main.rs b/mling/res/template_0.2/flat/src/main.rs deleted file mode 100644 index e69de29..0000000 --- a/mling/res/template_0.2/flat/src/main.rs +++ /dev/null diff --git a/mling/res/template_0.2/modularity/Cargo.toml.tmpl b/mling/res/template_0.2/modularity/Cargo.toml.tmpl deleted file mode 100644 index e69de29..0000000 --- a/mling/res/template_0.2/modularity/Cargo.toml.tmpl +++ /dev/null diff --git a/mling/res/template_0.2/modularity/src/main.rs b/mling/res/template_0.2/modularity/src/main.rs deleted file mode 100644 index e69de29..0000000 --- a/mling/res/template_0.2/modularity/src/main.rs +++ /dev/null diff --git a/mling/src/bin/cargo-mling.rs b/mling/src/bin/cargo-mling.rs deleted file mode 100644 index 4c285c6..0000000 --- a/mling/src/bin/cargo-mling.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - mingling_cli::cli::run(); -} diff --git a/mling/src/bin/mling.rs b/mling/src/bin/mling.rs deleted file mode 100644 index 4c285c6..0000000 --- a/mling/src/bin/mling.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - mingling_cli::cli::run(); -} diff --git a/mling/src/cargo_style.rs b/mling/src/cargo_style.rs deleted file mode 100644 index c212094..0000000 --- a/mling/src/cargo_style.rs +++ /dev/null @@ -1,221 +0,0 @@ -use colored::Colorize; - -/// Formats a message in cargo-style format with a bold green prefix. -/// -/// The message should be in the format `prefix: content`. The prefix will be -/// bold green and right-padded to 12 characters. If there is no colon in the -/// string, the entire string is printed as content with an empty prefix. -/// -/// # Macros -/// -/// - `format_cargo!("prefix: {}", arg)` — format-style invocation -/// - `format_cargo!(expr)` — direct expression invocation -/// -/// # Panics -/// -/// Panics if the prefix (text before the first `:`) exceeds 12 characters. -/// -/// # Examples -/// -/// ```ignore -/// format_cargo!("Compiling: hello.rs"); -/// // Output: " Compiling hello.rs" (green bold "Compiling" padded to 12) -/// ``` -#[macro_export] -macro_rules! format_cargo { - ($fmt:literal, $($arg:tt)*) => { - $crate::get_cargo_info_format(format!($fmt, $($arg)*)) - }; - ($cmd:expr) => { - $crate::get_cargo_info_format($cmd) - }; -} - -/// Formats an error message in cargo-style format with a bold red "error" prefix. -/// -/// # Macros -/// -/// - `eformat_cargo!("prefix: {}", arg)` — format-style invocation -/// - `eformat_cargo!(expr)` — direct expression invocation -/// -/// # Examples -/// -/// ```ignore -/// eformat_cargo!("failed to parse input"); -/// // Output: "error: failed to parse input" (red bold "error") -/// ``` -#[macro_export] -macro_rules! eformat_cargo { - ($fmt:literal, $($arg:tt)*) => { - $crate::get_cargo_error_format(format!($fmt, $($arg)*)) - }; - ($cmd:expr) => { - $crate::get_cargo_error_format($cmd) - }; -} - -/// 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 -/// -/// - `println_cargo!("prefix: {}", arg)` — format-style invocation -/// - `println_cargo!(expr)` — direct expression invocation -/// -/// # Examples -/// -/// ```ignore -/// println_cargo!("Compiling: hello.rs"); -/// ``` -#[macro_export] -macro_rules! println_cargo { - ($fmt:literal, $($arg:tt)*) => { - println!("{}", $crate::get_cargo_info_format(format!($fmt, $($arg)*))) - }; - ($cmd:expr) => { - println!("{}", $crate::get_cargo_info_format($cmd)) - }; -} - -/// Print an error message in cargo-style format with a bold red "error" prefix. -/// -/// # Macros -/// -/// - `eprintln_cargo!("prefix: {}", arg)` — format-style invocation -/// - `eprintln_cargo!(expr)` — direct expression invocation -/// -/// # Examples -/// -/// ```ignore -/// eprintln_cargo!("failed to parse input"); -/// ``` -#[macro_export] -macro_rules! eprintln_cargo { - ($fmt:literal, $($arg:tt)*) => { - eprintln!("{}", $crate::get_cargo_error_format(format!($fmt, $($arg)*))) - }; - ($cmd:expr) => { - eprintln!("{}", $crate::get_cargo_error_format($cmd)) - }; -} - -/// 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 -/// the prefix (bold green, right-padded to 12 characters), and the part after -/// becomes the content. -/// -/// If no colon is found, the entire string is treated as content and no prefix -/// is shown. -/// -/// # Panics -/// -/// Panics if the prefix (text before the first `:`) exceeds 12 characters. -/// -/// # Examples -/// -/// ```ignore -/// get_cargo_info_format("Compiling: my_program.rs"); -/// // returns " Compiling my_program.rs" -/// ``` -pub fn get_cargo_info_format(str: impl Into<String>) -> String { - let s = str.into(); - let (prefix, content) = if let Some(pos) = s.find(':') { - ( - s[..pos].trim().to_string(), - s[pos + 1..].trim_start().to_string(), - ) - } else { - (String::new(), s.trim().to_string()) - }; - - assert!( - prefix.len() <= 12, - "prefix length exceeds 12: '{}' has length {}", - prefix, - prefix.len() - ); - - let padding = " ".repeat(12 - prefix.len()); - - format!( - "{}{} {}", - padding, - prefix.bold().bright_green(), - content.trim() - ) -} - -/// Format an error message in cargo style format, with bold red "error" prefix. -/// -/// The input string is printed as the error content, prefixed by a bold red -/// `error:` label. -/// -/// # Examples -/// -/// ```ignore -/// get_cargo_error_format("something went wrong"); -/// // returns "error: something went wrong" -/// ``` -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 deleted file mode 100644 index 67d2ef0..0000000 --- a/mling/src/cli.rs +++ /dev/null @@ -1,195 +0,0 @@ -use crate::{ - CMDCompletion, ErrorDispatcherNotFound, Next, ThisProgram, - display::markdown, - eformat_cargo, eprintln_cargo, hformat_cargo, - pkg_mgr::{CMDInstall, CMDListNamespace, CMDRemoveNamespace, PackageManagerSetup}, - proj_mgr::ProjectManagerSetup, - res::{ResCurrentDir, ResManifestPath}, -}; -use colored::Colorize; -use mingling::{ - Groupped, Program, RenderResult, - hook::ProgramHook, - macros::{chain, help, pack, program_setup, renderer}, - res::ResExitCode, - setup::{ExitCodeSetup, HelpFlagSetup, QuietFlagSetup, StructuralRendererSetup}, -}; -use std::{env::current_dir, io::Write, path::PathBuf, process::exit, str::FromStr}; - -pub fn run() { - #[cfg(windows)] - colored::control::set_virtual_terminal(true).unwrap(); - - // Preprocess args to handle cargo-mling invocations - let mut args: Vec<String> = std::env::args().collect(); - if args.first().is_some_and(|a| a.contains("cargo-mling")) { - args[0] = "cargo-mling".to_string(); - } - if args.get(1).is_some_and(|a| a == "mling") { - args.remove(1); - } - - // Build program with preprocessed args - let mut program = Program::<ThisProgram>::new_with_args(args); - - // Intercept Version - program.global_flag(["-V", "--version"], |_| { - eprintln!(include_str!("helps/version.txt")); - exit(0) - }); - - // Intercept Help - program.with_hook( - ProgramHook::empty().on_post_dispatch(|info| match info.entry { - // When dispatcher is not found - ThisProgram::ErrorDispatcherNotFound - // And user requests Help - if ThisProgram::this().user_context.help => { - // Print help - eprintln!("{}", markdown(include_str!("helps/mling_help.txt"))); - exit(0) - } - _ => {} - }), - ); - - // Commands - program.with_dispatcher(CMDCompletion); - - // Setups - program.with_setup(HelpFlagSetup::new(["-h", "--help"])); - program.with_setup(StructuralRendererSetup); - 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(), - }); - - let manifest_path = program.pick_global_argument(["-P", "--manifest-path"]); - program.with_resource(ResManifestPath { - raw: manifest_path, - resolved: None, - }); - - // Manifest Path Check - if !program.is_completing() { - program.with_hook(ProgramHook::empty().on_post_dispatch(|_| { - 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; - let error_output = program.stdout_setting.error_output && !quiet; - let render_output = program.stdout_setting.render_output && !quiet; - let result = program.exec_without_render().unwrap(); - if !result.is_empty() { - if result.exit_code == 0 && render_output { - println!("{}", result.trim()); - } else if error_output { - eprintln!("{}", result.trim()); - } - } - exit(result.exit_code); -} - -#[program_setup] -fn standard_output_setup(program: &mut Program<ThisProgram>) { - program.with_setup(QuietFlagSetup::new("--silence")); - program.global_flag(["--no-error"], |program| { - program.stdout_setting.error_output = false; - }); - program.global_flag(["--no-result"], |program| { - program.stdout_setting.render_output = false; - }); - program.global_flag(["--silence", "--quiet"], |program| { - program.stdout_setting.quiet = true; - }); -} - -fn resolve_manifest_path(provided: Option<String>) -> PathBuf { - let p = ThisProgram::this(); - let disable_error_output = - // --no-error - !p.stdout_setting.error_output || - // or --quiet/--silence - p.stdout_setting.quiet; - - 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; - } - if !disable_error_output { - 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 - if !disable_error_output { - eprintln_cargo!("`{}` is not a crate root", current_dir().unwrap().display()); - } - exit(1); - } - } -} - -pack!(ResultMlingHelp = ()); -pack!(ResultUnknownCommand = String); - -#[chain] -pub fn handle_error_dispatcher_not_found(err: ErrorDispatcherNotFound) -> Next { - if err.is_empty() { - ResultMlingHelp::default().to_render() - } else { - ResultUnknownCommand::new(err.join(" ")).to_render() - } -} - -#[renderer] -pub fn render_mling_help(_prev: ResultMlingHelp, ec: &mut ResExitCode) -> RenderResult { - let mut result = RenderResult::default(); - writeln!(result, "{}", markdown(include_str!("helps/mling_help.txt"))).ok(); - ec.exit_code = 0; - result -} - -#[renderer] -pub fn render_unknown_command(prev: ResultUnknownCommand, ec: &mut ResExitCode) -> RenderResult { - let mut result = RenderResult::default(); - writeln!( - result, - "{}", - eformat_cargo!("no such command: `{}`", prev.bright_yellow().bold()) - ) - .ok(); - writeln!(result).ok(); - writeln!( - result, - "{}", - hformat_cargo!("view all commands with `cargo help mling`") - ) - .ok(); - ec.exit_code = 101; - result -} diff --git a/mling/src/display.rs b/mling/src/display.rs deleted file mode 100644 index 313a054..0000000 --- a/mling/src/display.rs +++ /dev/null @@ -1,384 +0,0 @@ -use colored::Colorize; -use std::collections::VecDeque; - -/// Trait for adding markdown formatting to strings -pub trait Markdown { - fn markdown(&self) -> String; -} - -impl Markdown for &str { - fn markdown(&self) -> String { - markdown(self) - } -} - -impl Markdown for String { - fn markdown(&self) -> String { - markdown(self) - } -} - -/// Converts a string to colored/formatted text with ANSI escape codes. -/// -/// Supported syntax: -/// - Bold: `**text**` -/// - Italic: `*text*` -/// - Underline: `_text_` -/// - Angle-bracketed content: `<text>` (displayed as cyan) -/// - Inline code: `` `text` `` (displayed as green) -/// - Color tags: `[[color_name]]` and `[[/]]` to close color -/// - Escape characters: `\*`, `\<`, `\>`, `` \` ``, `\_` for literal characters -/// - Headings: `# Heading 1`, `## Heading 2`, up to `###### Heading 6` -/// - Blockquote: `> text` (displays a gray background marker at the beginning of the line) -/// -/// Color tags support the following color names: -/// Color tags support the following color names: -/// -/// | Type | Color Names | -/// |-------------------------|-----------------------------------------------------------------------------| -/// | Standard colors | `black`, `red`, `green`, `yellow`, `blue`, `magenta`, `cyan`, `white` | -/// | Bright colors | `bright_black` | -/// | | `bright_red` | -/// | | `bright_green` | -/// | | `bright_yellow` | -/// | | `bright_blue` | -/// | | `bright_magenta` | -/// | | `bright_cyan` | -/// | | `bright_white` | -/// | Bright color shorthands | `b_black` | -/// | | `b_red` | -/// | | `b_green` | -/// | | `b_yellow` | -/// | | `b_blue` | -/// | | `b_magenta` | -/// | | `b_cyan` | -/// | | `b_white` | -/// | Gray colors | `gray`/`grey` | -/// | | `bright_gray`/`bright_grey` | -/// | | `b_gray`/`b_grey` | -/// -/// Color tags can be nested, `[[/]]` will close the most recently opened color tag. -/// -/// # Arguments -/// * `text` - The text to format, can be any type that implements `AsRef<str>` -/// -/// # Returns -/// Returns a `String` containing ANSI escape codes that can display colored/formatted text in ANSI-supported terminals. -/// -/// # Examples -/// ``` -/// # use mingling_cli::display::markdown; -/// let formatted = markdown("Hello **world**!"); -/// println!("{}", formatted); -/// -/// let colored = markdown("[[red]]Red text[[/]] and normal text"); -/// println!("{}", colored); -/// -/// let nested = markdown("[[blue]]Blue [[green]]Green[[/]] Blue[[/]] normal"); -/// println!("{}", nested); -/// ``` -pub fn markdown(text: impl AsRef<str>) -> String { - let text = text.as_ref(); - let lines: Vec<&str> = text.lines().collect(); - let mut result = String::new(); - let mut content_indent = 0; - - for line in lines { - // Don't trim the line initially, we need to check if it starts with # - let trimmed_line = line.trim(); - let mut line_result = String::new(); - - // Check if line starts with # for heading - // Check if the original line (not trimmed) starts with # - if line.trim_start().starts_with('#') { - let chars: Vec<char> = line.trim_start().chars().collect(); - let mut level = 0; - - // Count # characters at the beginning - while level < chars.len() && level < 7 && chars[level] == '#' { - level += 1; - } - - // Cap level at 6 - let effective_level = if level > 6 { 6 } else { level }; - - // Skip # characters and any whitespace after them - let mut content_start = level; - while content_start < chars.len() && chars[content_start].is_whitespace() { - content_start += 1; - } - - // Extract heading content - let heading_content: String = if content_start < chars.len() { - chars[content_start..].iter().collect() - } else { - String::new() - }; - - // Process the heading content with formatting - let processed_content = process_line(&heading_content); - - // Format heading as white background, black text, bold - // ANSI codes: \x1b[1m for bold, \x1b[47m for white background, \x1b[30m for black text - let formatted_heading = format!("\x1b[1m\x1b[47m\x1b[30m {processed_content} \x1b[0m"); - - // Add indentation to the heading line itself - // Heading indentation = level - 1 - let heading_indent = if effective_level > 0 { - effective_level - 1 - } else { - 0 - }; - let indent = " ".repeat(heading_indent); - line_result.push_str(&indent); - line_result.push_str(&formatted_heading); - - // Update content indent level for subsequent content - // Content after heading should be indented by effective_level - content_indent = effective_level; - } else if !trimmed_line.is_empty() { - // Process regular line with existing formatting - let processed_line = process_line_with_quote(trimmed_line); - - // Add indentation based on content_indent - let indent = " ".repeat(content_indent); - line_result.push_str(&indent); - line_result.push_str(&processed_line); - } else { - line_result.push(' '); - } - - if !line_result.is_empty() { - result.push_str(&line_result); - result.push('\n'); - } - } - - // Remove trailing newline - if result.ends_with('\n') { - result.pop(); - } - - result -} - -// Helper function to process a single line with existing formatting and handle > quotes -fn process_line_with_quote(line: &str) -> String { - let chars: Vec<char> = line.chars().collect(); - - // Check if line starts with '>' and not escaped - if !chars.is_empty() && chars[0] == '>' { - // Check if it's escaped - if chars.len() > 1 && chars[1] == '\\' { - // It's \>, so treat as normal text starting from position 0 - return process_line(line); - } - - // It's a regular > at the beginning, replace with gray background gray text space - let gray_bg_space = "\x1b[48;5;242m\x1b[38;5;242m \x1b[0m"; - let rest_of_line = if chars.len() > 1 { - chars[1..].iter().collect::<String>() - } else { - String::new() - }; - - // Process the rest of the line normally - let processed_rest = process_line(&rest_of_line); - - // Combine the gray background space with the processed rest - format!("{gray_bg_space}{processed_rest}") - } else { - // No > at the beginning, process normally - process_line(line) - } -} - -// Helper function to process a single line with existing formatting -fn process_line(line: &str) -> String { - let mut result = String::new(); - let mut color_stack: VecDeque<String> = VecDeque::new(); - - let chars: Vec<char> = line.chars().collect(); - let mut i = 0; - - while i < chars.len() { - // Check for escape character \ - if chars[i] == '\\' && i + 1 < chars.len() { - let escaped_char = chars[i + 1]; - // Only escape specific characters - if matches!(escaped_char, '*' | '<' | '>' | '`' | '_') { - let mut escaped_text = escaped_char.to_string(); - apply_color_stack(&mut escaped_text, &color_stack); - result.push_str(&escaped_text); - i += 2; - continue; - } - } - - // Check for color tag start [[color]] - if i + 1 < chars.len() - && chars[i] == '[' - && chars[i + 1] == '[' - && let Some(end) = find_tag_end(&chars, i) - { - let tag_content: String = chars[i + 2..end].iter().collect(); - - // Check if it's a closing tag [[/]] - if tag_content == "/" { - color_stack.pop_back(); - } else { - // It's a color tag - color_stack.push_back(tag_content.clone()); - } - i = end + 2; - continue; - } - - // Check for bold **text** - if i + 1 < chars.len() - && chars[i] == '*' - && chars[i + 1] == '*' - && let Some(end) = find_matching(&chars, i + 2, "**") - { - let bold_text: String = chars[i + 2..end].iter().collect(); - let mut formatted_text = bold_text.bold().to_string(); - apply_color_stack(&mut formatted_text, &color_stack); - result.push_str(&formatted_text); - i = end + 2; - continue; - } - - // Check for italic *text* - if chars[i] == '*' - && let Some(end) = find_matching(&chars, i + 1, "*") - { - let italic_text: String = chars[i + 1..end].iter().collect(); - let mut formatted_text = italic_text.italic().to_string(); - apply_color_stack(&mut formatted_text, &color_stack); - result.push_str(&formatted_text); - i = end + 1; - continue; - } - - // Check for underline _text_ - if chars[i] == '_' - && let Some(end) = find_matching(&chars, i + 1, "_") - { - let underline_text: String = chars[i + 1..end].iter().collect(); - let mut formatted_text = format!("\x1b[4m{underline_text}\x1b[0m"); - apply_color_stack(&mut formatted_text, &color_stack); - result.push_str(&formatted_text); - i = end + 1; - continue; - } - - // Check for angle-bracketed content <text> - if chars[i] == '<' - && let Some(end) = find_matching(&chars, i + 1, ">") - { - // Include the angle brackets in the output - let angle_text: String = chars[i..=end].iter().collect(); - let mut formatted_text = angle_text.cyan().to_string(); - apply_color_stack(&mut formatted_text, &color_stack); - result.push_str(&formatted_text); - i = end + 1; - continue; - } - - // Check for inline code `text` - if chars[i] == '`' - && let Some(end) = find_matching(&chars, i + 1, "`") - { - // Include the backticks in the output - let code_text: String = chars[i..=end].iter().collect(); - let mut formatted_text = code_text.green().to_string(); - apply_color_stack(&mut formatted_text, &color_stack); - result.push_str(&formatted_text); - i = end + 1; - continue; - } - - // Regular character - let mut current_char = chars[i].to_string(); - apply_color_stack(&mut current_char, &color_stack); - result.push_str(¤t_char); - i += 1; - } - - result -} - -// Helper function to find matching delimiter -fn find_matching(chars: &[char], start: usize, delimiter: &str) -> Option<usize> { - let delim_chars: Vec<char> = delimiter.chars().collect(); - let delim_len = delim_chars.len(); - - let mut j = start; - while j < chars.len() { - if delim_len == 1 { - if chars[j] == delim_chars[0] { - return Some(j); - } - } else if j + 1 < chars.len() - && chars[j] == delim_chars[0] - && chars[j + 1] == delim_chars[1] - { - return Some(j); - } - j += 1; - } - None -} - -// Helper function to find color tag end -fn find_tag_end(chars: &[char], start: usize) -> Option<usize> { - let mut j = start + 2; - while j + 1 < chars.len() { - if chars[j] == ']' && chars[j + 1] == ']' { - return Some(j); - } - j += 1; - } - None -} - -// Helper function to apply color stack to text -fn apply_color_stack(text: &mut String, color_stack: &VecDeque<String>) { - let mut result = text.clone(); - for color in color_stack.iter().rev() { - result = apply_color(&result, color); - } - *text = result; -} - -// Helper function to apply color to text -fn apply_color(text: impl AsRef<str>, color_name: impl AsRef<str>) -> String { - let text = text.as_ref(); - let color_name = color_name.as_ref(); - match color_name { - // Normal colors - "black" => text.black().to_string(), - "red" => text.red().to_string(), - "green" => text.green().to_string(), - "yellow" => text.yellow().to_string(), - "blue" => text.blue().to_string(), - "magenta" => text.magenta().to_string(), - "cyan" => text.cyan().to_string(), - "white" | "b_white" | "bright_gray" | "bright_grey" | "b_gray" | "b_grey" => { - text.white().to_string() - } - - // Bright colors and their b_ short aliases - "bright_black" | "b_black" | "gray" | "grey" => text.bright_black().to_string(), - "bright_red" | "b_red" => text.bright_red().to_string(), - "bright_green" | "b_green" => text.bright_green().to_string(), - "bright_yellow" | "b_yellow" => text.bright_yellow().to_string(), - "bright_blue" | "b_blue" => text.bright_blue().to_string(), - "bright_magenta" | "b_magenta" => text.bright_magenta().to_string(), - "bright_cyan" | "b_cyan" => text.bright_cyan().to_string(), - "bright_white" => text.bright_white().to_string(), - - // Default to white if color not recognized - _ => text.to_string(), - } -} diff --git a/mling/src/errors.rs b/mling/src/errors.rs deleted file mode 100644 index ece80ce..0000000 --- a/mling/src/errors.rs +++ /dev/null @@ -1,2 +0,0 @@ -mod io_error; -pub use io_error::*; diff --git a/mling/src/errors/io_error.rs b/mling/src/errors/io_error.rs deleted file mode 100644 index ac503cc..0000000 --- a/mling/src/errors/io_error.rs +++ /dev/null @@ -1,224 +0,0 @@ -use mingling::{ - RenderResult, - macros::{group, renderer}, - res::ResExitCode, -}; -use std::io::Write as _; - -use crate::eformat_cargo; - -group!(ErrorIo = std::io::Error); - -// Error code constants for each std::io::ErrorKind variant -pub const EC_IO_ERR_NOT_FOUND: i32 = 1000; -pub const EC_IO_ERR_PERMISSION_DENIED: i32 = 1001; -pub const EC_IO_ERR_CONNECTION_REFUSED: i32 = 1002; -pub const EC_IO_ERR_CONNECTION_RESET: i32 = 1003; -pub const EC_IO_ERR_HOST_UNREACHABLE: i32 = 1004; -pub const EC_IO_ERR_NETWORK_UNREACHABLE: i32 = 1005; -pub const EC_IO_ERR_CONNECTION_ABORTED: i32 = 1006; -pub const EC_IO_ERR_NOT_CONNECTED: i32 = 1007; -pub const EC_IO_ERR_ADDR_IN_USE: i32 = 1008; -pub const EC_IO_ERR_ADDR_NOT_AVAILABLE: i32 = 1009; -pub const EC_IO_ERR_NETWORK_DOWN: i32 = 1010; -pub const EC_IO_ERR_BROKEN_PIPE: i32 = 1011; -pub const EC_IO_ERR_ALREADY_EXISTS: i32 = 1012; -pub const EC_IO_ERR_WOULD_BLOCK: i32 = 1013; -pub const EC_IO_ERR_NOT_A_DIRECTORY: i32 = 1014; -pub const EC_IO_ERR_IS_A_DIRECTORY: i32 = 1015; -pub const EC_IO_ERR_DIRECTORY_NOT_EMPTY: i32 = 1016; -pub const EC_IO_ERR_READ_ONLY_FILESYSTEM: i32 = 1017; -pub const EC_IO_ERR_STALE_NETWORK_FILE_HANDLE: i32 = 1018; -pub const EC_IO_ERR_INVALID_INPUT: i32 = 1019; -pub const EC_IO_ERR_INVALID_DATA: i32 = 1020; -pub const EC_IO_ERR_TIMED_OUT: i32 = 1021; -pub const EC_IO_ERR_WRITE_ZERO: i32 = 1022; -pub const EC_IO_ERR_STORAGE_FULL: i32 = 1023; -pub const EC_IO_ERR_NOT_SEEKABLE: i32 = 1024; -pub const EC_IO_ERR_QUOTA_EXCEEDED: i32 = 1025; -pub const EC_IO_ERR_FILE_TOO_LARGE: i32 = 1026; -pub const EC_IO_ERR_RESOURCE_BUSY: i32 = 1027; -pub const EC_IO_ERR_EXECUTABLE_FILE_BUSY: i32 = 1028; -pub const EC_IO_ERR_DEADLOCK: i32 = 1029; -pub const EC_IO_ERR_CROSSES_DEVICES: i32 = 1030; -pub const EC_IO_ERR_TOO_MANY_LINKS: i32 = 1031; -pub const EC_IO_ERR_INVALID_FILENAME: i32 = 1032; -pub const EC_IO_ERR_ARGUMENT_LIST_TOO_LONG: i32 = 1033; -pub const EC_IO_ERR_INTERRUPTED: i32 = 1034; -pub const EC_IO_ERR_UNSUPPORTED: i32 = 1035; -pub const EC_IO_ERR_UNEXPECTED_EOF: i32 = 1036; -pub const EC_IO_ERR_OUT_OF_MEMORY: i32 = 1037; -pub const EC_IO_ERR_OTHER: i32 = 1038; - -#[renderer] -pub fn render_error_io(err: ErrorIo, ec: &mut ResExitCode) -> RenderResult { - let mut result = RenderResult::default(); - match err.kind() { - std::io::ErrorKind::NotFound => { - writeln!(result, "{}", eformat_cargo!("file or directory not found")).ok(); - ec.exit_code = EC_IO_ERR_NOT_FOUND; - } - std::io::ErrorKind::PermissionDenied => { - writeln!(result, "{}", eformat_cargo!("permission denied")).ok(); - ec.exit_code = EC_IO_ERR_PERMISSION_DENIED; - } - std::io::ErrorKind::ConnectionRefused => { - writeln!(result, "{}", eformat_cargo!("connection refused")).ok(); - ec.exit_code = EC_IO_ERR_CONNECTION_REFUSED; - } - std::io::ErrorKind::ConnectionReset => { - writeln!(result, "{}", eformat_cargo!("connection reset")).ok(); - ec.exit_code = EC_IO_ERR_CONNECTION_RESET; - } - std::io::ErrorKind::HostUnreachable => { - writeln!(result, "{}", eformat_cargo!("host unreachable")).ok(); - ec.exit_code = EC_IO_ERR_HOST_UNREACHABLE; - } - std::io::ErrorKind::NetworkUnreachable => { - writeln!(result, "{}", eformat_cargo!("network unreachable")).ok(); - ec.exit_code = EC_IO_ERR_NETWORK_UNREACHABLE; - } - std::io::ErrorKind::ConnectionAborted => { - writeln!(result, "{}", eformat_cargo!("connection aborted")).ok(); - ec.exit_code = EC_IO_ERR_CONNECTION_ABORTED; - } - std::io::ErrorKind::NotConnected => { - writeln!(result, "{}", eformat_cargo!("not connected")).ok(); - ec.exit_code = EC_IO_ERR_NOT_CONNECTED; - } - std::io::ErrorKind::AddrInUse => { - writeln!(result, "{}", eformat_cargo!("address in use")).ok(); - ec.exit_code = EC_IO_ERR_ADDR_IN_USE; - } - std::io::ErrorKind::AddrNotAvailable => { - writeln!(result, "{}", eformat_cargo!("address not available")).ok(); - ec.exit_code = EC_IO_ERR_ADDR_NOT_AVAILABLE; - } - std::io::ErrorKind::NetworkDown => { - writeln!(result, "{}", eformat_cargo!("network down")).ok(); - ec.exit_code = EC_IO_ERR_NETWORK_DOWN; - } - std::io::ErrorKind::BrokenPipe => { - writeln!(result, "{}", eformat_cargo!("broken pipe")).ok(); - ec.exit_code = EC_IO_ERR_BROKEN_PIPE; - } - std::io::ErrorKind::AlreadyExists => { - writeln!( - result, - "{}", - eformat_cargo!("file or directory already exists") - ) - .ok(); - ec.exit_code = EC_IO_ERR_ALREADY_EXISTS; - } - std::io::ErrorKind::WouldBlock => { - writeln!(result, "{}", eformat_cargo!("operation would block")).ok(); - ec.exit_code = EC_IO_ERR_WOULD_BLOCK; - } - std::io::ErrorKind::NotADirectory => { - writeln!(result, "{}", eformat_cargo!("not a directory")).ok(); - ec.exit_code = EC_IO_ERR_NOT_A_DIRECTORY; - } - std::io::ErrorKind::IsADirectory => { - writeln!(result, "{}", eformat_cargo!("is a directory")).ok(); - ec.exit_code = EC_IO_ERR_IS_A_DIRECTORY; - } - std::io::ErrorKind::DirectoryNotEmpty => { - writeln!(result, "{}", eformat_cargo!("directory not empty")).ok(); - ec.exit_code = EC_IO_ERR_DIRECTORY_NOT_EMPTY; - } - std::io::ErrorKind::ReadOnlyFilesystem => { - writeln!(result, "{}", eformat_cargo!("read-only filesystem")).ok(); - ec.exit_code = EC_IO_ERR_READ_ONLY_FILESYSTEM; - } - std::io::ErrorKind::StaleNetworkFileHandle => { - writeln!(result, "{}", eformat_cargo!("stale network file handle")).ok(); - ec.exit_code = EC_IO_ERR_STALE_NETWORK_FILE_HANDLE; - } - std::io::ErrorKind::InvalidInput => { - writeln!(result, "{}", eformat_cargo!("invalid input")).ok(); - ec.exit_code = EC_IO_ERR_INVALID_INPUT; - } - std::io::ErrorKind::InvalidData => { - writeln!(result, "{}", eformat_cargo!("invalid data")).ok(); - ec.exit_code = EC_IO_ERR_INVALID_DATA; - } - std::io::ErrorKind::TimedOut => { - writeln!(result, "{}", eformat_cargo!("timed out")).ok(); - ec.exit_code = EC_IO_ERR_TIMED_OUT; - } - std::io::ErrorKind::WriteZero => { - writeln!(result, "{}", eformat_cargo!("write zero")).ok(); - ec.exit_code = EC_IO_ERR_WRITE_ZERO; - } - std::io::ErrorKind::StorageFull => { - writeln!(result, "{}", eformat_cargo!("storage full")).ok(); - ec.exit_code = EC_IO_ERR_STORAGE_FULL; - } - std::io::ErrorKind::NotSeekable => { - writeln!(result, "{}", eformat_cargo!("not seekable")).ok(); - ec.exit_code = EC_IO_ERR_NOT_SEEKABLE; - } - std::io::ErrorKind::QuotaExceeded => { - writeln!(result, "{}", eformat_cargo!("quota exceeded")).ok(); - ec.exit_code = EC_IO_ERR_QUOTA_EXCEEDED; - } - std::io::ErrorKind::FileTooLarge => { - writeln!(result, "{}", eformat_cargo!("file too large")).ok(); - ec.exit_code = EC_IO_ERR_FILE_TOO_LARGE; - } - std::io::ErrorKind::ResourceBusy => { - writeln!(result, "{}", eformat_cargo!("resource busy")).ok(); - ec.exit_code = EC_IO_ERR_RESOURCE_BUSY; - } - std::io::ErrorKind::ExecutableFileBusy => { - writeln!(result, "{}", eformat_cargo!("executable file busy")).ok(); - ec.exit_code = EC_IO_ERR_EXECUTABLE_FILE_BUSY; - } - std::io::ErrorKind::Deadlock => { - writeln!(result, "{}", eformat_cargo!("deadlock")).ok(); - ec.exit_code = EC_IO_ERR_DEADLOCK; - } - std::io::ErrorKind::CrossesDevices => { - writeln!(result, "{}", eformat_cargo!("crosses devices")).ok(); - ec.exit_code = EC_IO_ERR_CROSSES_DEVICES; - } - std::io::ErrorKind::TooManyLinks => { - writeln!(result, "{}", eformat_cargo!("too many links")).ok(); - ec.exit_code = EC_IO_ERR_TOO_MANY_LINKS; - } - std::io::ErrorKind::InvalidFilename => { - writeln!(result, "{}", eformat_cargo!("invalid filename")).ok(); - ec.exit_code = EC_IO_ERR_INVALID_FILENAME; - } - std::io::ErrorKind::ArgumentListTooLong => { - writeln!(result, "{}", eformat_cargo!("argument list too long")).ok(); - ec.exit_code = EC_IO_ERR_ARGUMENT_LIST_TOO_LONG; - } - std::io::ErrorKind::Interrupted => { - writeln!(result, "{}", eformat_cargo!("interrupted")).ok(); - ec.exit_code = EC_IO_ERR_INTERRUPTED; - } - std::io::ErrorKind::Unsupported => { - writeln!(result, "{}", eformat_cargo!("unsupported")).ok(); - ec.exit_code = EC_IO_ERR_UNSUPPORTED; - } - std::io::ErrorKind::UnexpectedEof => { - writeln!(result, "{}", eformat_cargo!("unexpected end of file")).ok(); - ec.exit_code = EC_IO_ERR_UNEXPECTED_EOF; - } - std::io::ErrorKind::OutOfMemory => { - writeln!(result, "{}", eformat_cargo!("out of memory")).ok(); - ec.exit_code = EC_IO_ERR_OUT_OF_MEMORY; - } - std::io::ErrorKind::Other => { - writeln!(result, "{}", eformat_cargo!(err.to_string())).ok(); - ec.exit_code = EC_IO_ERR_OTHER; - } - _ => { - writeln!(result, "{}", eformat_cargo!(err.to_string())).ok(); - ec.exit_code = EC_IO_ERR_OTHER; - } - } - result -} diff --git a/mling/src/helps/mling_help.txt b/mling/src/helps/mling_help.txt deleted file mode 100644 index 73bfd4d..0000000 --- a/mling/src/helps/mling_help.txt +++ /dev/null @@ -1,20 +0,0 @@ -Mingling's scaffolding tool - -[[b_green]]**Usage:**[[/]] [[b_cyan]]**cargo mling**[[/]] [[cyan]][COMMAND] [OPTIONS]...[[/]] - -[[b_green]]**Options:**[[/]] -__ [[b_cyan]]**-V**[[/]], [[b_cyan]]**--version**[[/]] Print version info and exit -__ [[b_cyan]]**-h**[[/]], [[b_cyan]]**--help**[[/]] Print this help message - -__ [[b_cyan]]**-P**[[/]], [[b_cyan]]**--manifest-path**[[/]] [[cyan]]<PATH>[[/]] Path to _Cargo.toml_ - -__ [[b_cyan]]**--silence**[[/]], [[b_cyan]]**--quiet**[[/]] Suppress all output -__ [[b_cyan]]**--no-error**[[/]] Suppress error output -__ [[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]]**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 deleted file mode 100644 index 1de1228..0000000 --- a/mling/src/lib.rs +++ /dev/null @@ -1,18 +0,0 @@ -#![allow(unused_imports)] - -use mingling::{ - macros::{chain, gen_program, pack, renderer}, - res::ResExitCode, -}; - -mod cargo_style; -pub use cargo_style::*; - -pub mod cli; -pub mod display; -pub mod errors; -pub mod pkg_mgr; -pub mod proj_mgr; -pub mod res; - -gen_program!(); diff --git a/mling/src/pkg_mgr/installer.rs b/mling/src/pkg_mgr/installer.rs deleted file mode 100644 index 8b13789..0000000 --- a/mling/src/pkg_mgr/installer.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/mling/src/pkg_mgr/mod.rs b/mling/src/pkg_mgr/mod.rs deleted file mode 100644 index 682b433..0000000 --- a/mling/src/pkg_mgr/mod.rs +++ /dev/null @@ -1,18 +0,0 @@ -use crate::ThisProgram; -use mingling::{ - Program, - macros::{dispatcher, program_setup}, -}; - -pub mod installer; - -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/checklist_reader.rs b/mling/src/proj_mgr/checklist_reader.rs deleted file mode 100644 index 558d303..0000000 --- a/mling/src/proj_mgr/checklist_reader.rs +++ /dev/null @@ -1,294 +0,0 @@ -use std::{collections::HashMap, io, path::Path}; - -/// Reads and parses a `CHECKLIST.md` file, extracting both *values* (from -/// fenced code blocks) and *toggles* (from checkbox lines). -/// -/// # Format -/// -/// - **Values**: fenced code blocks where the info string is the key and -/// the first non-empty line is the value. Example: -/// <code>\`\`\`name<br>my-cli<br>\`\`\`</code> -/// - **Toggles**: checkbox lines like `- [x] \`ns:key\`` (checked) or -/// `- [ ] \`ns:key\`` (unchecked). -/// -/// Checked items are stored as `"namespace:key"`. -/// -/// # Usage -/// -/// ```rust,ignore -/// let reader = CheckListReader::from(&Path::new("CHECKLIST.md")).unwrap(); -/// assert_eq!(reader.read_value("name"), Some("my-cli".into())); -/// assert!(reader.read_toggle("ver:0.2")); -/// assert!(!reader.read_toggle("feat:comp")); -/// assert_eq!(reader.read_toggles("feat"), vec!["feat:async"]); -/// ``` -pub struct CheckListReader { - /// Values extracted from fenced code blocks: `key → value`. - values: HashMap<String, String>, - - /// Checked toggles: `"namespace:key" → true` (only checked items are stored). - toggles: HashMap<String, bool>, - - /// All toggles (checked or not): `"namespace:key" → checked`. - all_toggles: HashMap<String, bool>, -} - -impl CheckListReader { - /// Parse a CHECKLIST.md from a file path in a single left-to-right pass - pub fn from(path: &Path) -> Result<Self, io::Error> { - let content = std::fs::read_to_string(path)?; - - let mut reader = Self { - values: HashMap::new(), - toggles: HashMap::new(), - all_toggles: HashMap::new(), - }; - reader.parse(&content); - Ok(reader) - } - - /// Parse CHECKLIST.md content in a single pass. - fn parse(&mut self, content: &str) { - let lines: Vec<&str> = content.lines().collect(); - let mut i = 0; - - while i < lines.len() { - let line = lines[i]; - - if let Some(key) = line.strip_prefix("```").map(|s| s.trim()) - && !key.is_empty() - && !key.starts_with(' ') - { - let mut block_lines = Vec::new(); - i += 1; - while i < lines.len() && !lines[i].trim_start().starts_with("```") { - block_lines.push(lines[i]); - i += 1; - } - let value = block_lines - .into_iter() - .find(|l| !l.trim().is_empty()) - .map(|l| l.trim().to_string()); - if let Some(val) = value { - self.values.insert(key.to_string(), val); - } - continue; - } - - if let Some(toggle_key) = Self::parse_toggle_line(line) { - let is_checked = line.contains("[x]") || line.contains("[X]"); - self.all_toggles.insert(toggle_key.clone(), is_checked); - if is_checked { - self.toggles.insert(toggle_key, true); - } - } - - i += 1; - } - } - - /// Extract the `namespace:key` from a toggle line. - /// - /// Matches: `- [x] `namespace:key`` or `- [ ] `namespace:key`` - fn parse_toggle_line(line: &str) -> Option<String> { - let line = line.trim(); - if !line.starts_with("- [") { - return None; - } - // Find the backtick-enclosed key - let start = line.find('`')?; - let rest = &line[start + 1..]; - let end = rest.find('`')?; - let key = rest[..end].trim(); - if key.is_empty() { - return None; - } - Some(key.to_string()) - } - - /// Read a value by its key. - /// - /// Returns `Some(value)` if a fenced code block with that key was found, - /// or `None` if the key does not exist. - #[must_use] - pub fn read_value(&self, key: &str) -> Option<String> { - self.values.get(key).cloned() - } - - /// Check if a toggle (`namespace:key`) is checked. - /// - /// Returns `true` if the checkbox line was `- [x]`, `false` if it was - /// `- [ ]` or the key does not exist in the file. - #[must_use] - pub fn read_toggle(&self, key: &str) -> bool { - self.toggles.contains_key(key) - } - - /// Get all toggles under a given `namespace` that are checked. - /// - /// For example, `read_toggles("feat")` returns all checked keys starting - /// with `"feat:"`, such as `["feat:comp", "feat:parser"]`. - #[must_use] - pub fn read_toggles(&self, namespace: &str) -> Vec<String> { - let prefix = format!("{namespace}:"); - let mut keys: Vec<String> = self - .toggles - .keys() - .filter(|k| k.starts_with(&prefix)) - .cloned() - .collect(); - keys.sort(); - keys - } - - /// Get ALL toggles (checked or not) under a namespace. - /// - /// Useful for iterating all available options. - #[must_use] - pub fn all_toggles(&self, namespace: &str) -> Vec<String> { - let prefix = format!("{namespace}:"); - let mut keys: Vec<String> = self - .all_toggles - .keys() - .filter(|k| k.starts_with(&prefix)) - .cloned() - .collect(); - keys.sort(); - keys - } -} - -#[cfg(test)] -mod tests { - use super::*; - - fn sample_md() -> &'static str { - r#"> Some intro - -## Question 1: What is your project name? - -```name -my-cli -``` - -## Question 2: Which version? - -- [x] `ver:0.2` - -## Question 3: Features? - -- [ ] `feat:structural_renderer` -- [x] `feat:comp` -- [x] `feat:parser` -- [ ] `feat:async` - -```other -some value -``` -"# - } - - #[test] - fn test_read_value() { - let mut reader = CheckListReader { - values: HashMap::new(), - toggles: HashMap::new(), - all_toggles: HashMap::new(), - }; - reader.parse(sample_md()); - assert_eq!(reader.read_value("name"), Some("my-cli".into())); - assert_eq!(reader.read_value("other"), Some("some value".into())); - assert_eq!(reader.read_value("nonexistent"), None); - } - - #[test] - fn test_read_toggle() { - let mut reader = CheckListReader { - values: HashMap::new(), - toggles: HashMap::new(), - all_toggles: HashMap::new(), - }; - reader.parse(sample_md()); - assert!(reader.read_toggle("ver:0.2")); - assert!(reader.read_toggle("feat:comp")); - assert!(reader.read_toggle("feat:parser")); - assert!(!reader.read_toggle("feat:structural_renderer")); - assert!(!reader.read_toggle("feat:async")); - assert!(!reader.read_toggle("nonexistent:key")); - } - - #[test] - fn test_read_toggles() { - let mut reader = CheckListReader { - values: HashMap::new(), - toggles: HashMap::new(), - all_toggles: HashMap::new(), - }; - reader.parse(sample_md()); - let feat_toggles = reader.read_toggles("feat"); - assert_eq!(feat_toggles, vec!["feat:comp", "feat:parser"]); - } - - #[test] - fn test_all_toggles() { - let mut reader = CheckListReader { - values: HashMap::new(), - toggles: HashMap::new(), - all_toggles: HashMap::new(), - }; - reader.parse(sample_md()); - let all = reader.all_toggles("feat"); - assert_eq!( - all, - vec![ - "feat:async", - "feat:comp", - "feat:parser", - "feat:structural_renderer", - ] - ); - } - - #[test] - fn test_from_path() { - // Write a temp CHECKLIST.md, read it, then clean up - let dir = std::env::temp_dir().join("mling_checklist_test"); - std::fs::create_dir_all(&dir).unwrap(); - let path = dir.join("CHECKLIST.md"); - std::fs::write(&path, sample_md()).unwrap(); - - let reader = CheckListReader::from(&path).unwrap(); - assert_eq!(reader.read_value("name"), Some("my-cli".into())); - assert!(reader.read_toggle("ver:0.2")); - - let _ = std::fs::remove_dir_all(&dir); - } - - #[test] - fn test_empty_file() { - let mut reader = CheckListReader { - values: HashMap::new(), - toggles: HashMap::new(), - all_toggles: HashMap::new(), - }; - reader.parse(""); - assert_eq!(reader.read_value("anything"), None); - assert!(!reader.read_toggle("ns:key")); - assert!(reader.read_toggles("ns").is_empty()); - } - - #[test] - fn test_no_toggle_or_value_lines() { - let md = "# Just a heading\n\nSome text\n\n```code\nstill not a value\n```\n"; - let mut reader = CheckListReader { - values: HashMap::new(), - toggles: HashMap::new(), - all_toggles: HashMap::new(), - }; - reader.parse(md); - // "code" isn't a valid value key (it's used as a language tag, not a name/value key) - // but our parser treats ANY ```key as a value block. This is correct behavior — - // malformed CHECKLIST.md may produce unexpected values. - assert_eq!(reader.read_value("code"), Some("still not a value".into())); - } -} diff --git a/mling/src/proj_mgr/generator.rs b/mling/src/proj_mgr/generator.rs deleted file mode 100644 index 4f965d9..0000000 --- a/mling/src/proj_mgr/generator.rs +++ /dev/null @@ -1,57 +0,0 @@ -use std::path::{self, PathBuf}; - -use mingling::{ - Groupped, RenderResult, - macros::{chain, pack, renderer, route}, -}; -use std::io::Write as _; - -use crate::{Next, proj_mgr::EntryGenerateProject, res::ResCurrentDir}; - -pack!(StateGenerateProjectReady = PathBuf); -pack!(ResultGenerateProjectChecklistCreated = PathBuf); - -pack!(StateGenerateProjectExecBegin = PathBuf); -pack!(StateGenerateProjectExecuting = ()); - -const CHECK_LIST_NAME: &str = "CHECKLIST.md"; -const CHECK_LIST_CONTENT: &str = include_str!("../../res/CHECKLIST.md"); - -#[chain] -pub fn handle_generate(_args: EntryGenerateProject, cwd: &ResCurrentDir) -> Next { - let checklist_path = cwd.path.join(CHECK_LIST_NAME); - - if !checklist_path.exists() { - StateGenerateProjectReady::new(checklist_path).to_chain() - } else { - StateGenerateProjectExecBegin::new(checklist_path).to_chain() - } -} - -#[chain] -pub fn handle_state_gen_proj_ready(prev: StateGenerateProjectReady) -> Next { - let path = prev.inner; - route!(std::fs::write(&path, CHECK_LIST_CONTENT)); - ResultGenerateProjectChecklistCreated::new(path).to_render() -} - -#[renderer] -pub fn render_gen_proj_checklist_created( - result: ResultGenerateProjectChecklistCreated, -) -> RenderResult { - let mut res = RenderResult::default(); - writeln!( - res, - "Successfully create {} at \"{}\"", - CHECK_LIST_NAME, - result.to_string_lossy() - ) - .ok(); - writeln!(res).ok(); - writeln!( - res, - "Please fill in {CHECK_LIST_NAME} and run `mling gen` again to continue generating" - ) - .ok(); - res -} diff --git a/mling/src/proj_mgr/metadata.rs b/mling/src/proj_mgr/metadata.rs deleted file mode 100644 index 1ba24e1..0000000 --- a/mling/src/proj_mgr/metadata.rs +++ /dev/null @@ -1,142 +0,0 @@ -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::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 deleted file mode 100644 index e0a4216..0000000 --- a/mling/src/proj_mgr/mod.rs +++ /dev/null @@ -1,30 +0,0 @@ -use crate::ThisProgram; -use mingling::{ - Program, - macros::{dispatcher, program_setup}, -}; - -pub mod checklist_reader; -pub mod generator; -pub mod metadata; -pub mod show_binaries; -pub mod show_directories; - -dispatcher!("gen", CMDGenerateProject => EntryGenerateProject); - -dispatcher!("show.binaries"); -dispatcher!("show.workspace-dir", - CMDShowWorkspaceDirectory => EntryShowWorkspaceDirectory -); -dispatcher!("show.target-dir", - CMDShowTargetDirectories => EntryShowTargetDirectories -); - -#[program_setup] -pub fn project_manager_setup(p: &mut Program<ThisProgram>) { - p.with_dispatcher(CMDGenerateProject); - - p.with_dispatcher(CMDShowBinaries); - p.with_dispatcher(CMDShowWorkspaceDirectory); - p.with_dispatcher(CMDShowTargetDirectories); -} diff --git a/mling/src/proj_mgr/show_binaries.rs b/mling/src/proj_mgr/show_binaries.rs deleted file mode 100644 index 9d5caf0..0000000 --- a/mling/src/proj_mgr/show_binaries.rs +++ /dev/null @@ -1,78 +0,0 @@ -use std::path::PathBuf; - -use colored::Colorize; -use mingling::{ - Groupped, RenderResult, - macros::{chain, pack, renderer}, -}; -use serde::Serialize; -use std::io::Write as _; - -use crate::{ - Next, - proj_mgr::{ - EntryShowBinaries, - metadata::{CargoLockFile, read_metadata}, - }, - res::ResManifestPath, -}; - -#[derive(Serialize, Groupped)] -pub struct ResultBinaries { - pub binaries: Vec<DataBinary>, -} - -#[derive(Serialize)] -pub struct DataBinary { - pub name: String, - pub path: PathBuf, -} - -#[chain] -pub fn handle_show_binaries(_args: EntryShowBinaries, manifest_path: &ResManifestPath) -> Next { - let metadata = read_metadata(manifest_path.resolved()).unwrap(); - let CargoLockFile { - packages, - workspace_members, - .. - } = metadata; - - let binaries: Vec<DataBinary> = packages - .into_iter() - .filter(|pkg| workspace_members.contains(&pkg.id)) - .flat_map(|pkg| { - pkg.targets - .into_iter() - .filter(|target| target.kind.iter().any(|k| k == "bin")) - .map(move |target| DataBinary { - name: target.name, - path: PathBuf::from(pkg.manifest_path.clone()) - .parent() - .unwrap_or(&PathBuf::from(".")) - .join("src") - .join(&target.src_path), - }) - }) - .collect(); - - ResultBinaries { binaries }.to_render() -} - -#[renderer] -pub fn render_binaries(binaries: ResultBinaries) -> RenderResult { - let mut result = RenderResult::default(); - writeln!(result, "{}", "Binaries:".bright_cyan().bold()).ok(); - if let Some(max_name_len) = binaries.binaries.iter().map(|b| b.name.len()).max() { - for binary in &binaries.binaries { - writeln!( - result, - " {:width$} `{}`", - binary.name.bright_yellow().bold(), - binary.path.display().to_string().italic(), - width = max_name_len - ) - .ok(); - } - } - result -} diff --git a/mling/src/proj_mgr/show_directories.rs b/mling/src/proj_mgr/show_directories.rs deleted file mode 100644 index 7d7c074..0000000 --- a/mling/src/proj_mgr/show_directories.rs +++ /dev/null @@ -1,61 +0,0 @@ -use colored::Colorize; -use mingling::{ - Groupped, RenderResult, - macros::{chain, pack, renderer}, -}; -use serde::Serialize; -use std::io::Write as _; - -use crate::{ - Next, - proj_mgr::{EntryShowTargetDirectories, EntryShowWorkspaceDirectory, metadata::read_metadata}, - res::ResManifestPath, -}; - -#[derive(Serialize, Groupped)] -pub struct ResultWorkspaceDirectory { - pub path: String, -} - -#[derive(Serialize, Groupped)] -pub struct ResultTargetDirectory { - pub path: String, -} - -#[chain] -pub fn handle_show_workspace_directory( - _args: EntryShowWorkspaceDirectory, - manifest_path: &ResManifestPath, -) -> Next { - let metadata = read_metadata(manifest_path.resolved()).unwrap(); - ResultWorkspaceDirectory { - path: metadata.workspace_root, - } - .to_render() -} - -#[chain] -pub fn handle_show_target_directory( - _args: EntryShowTargetDirectories, - manifest_path: &ResManifestPath, -) -> Next { - let metadata = read_metadata(manifest_path.resolved()).unwrap(); - ResultTargetDirectory { - path: metadata.target_directory, - } - .to_render() -} - -#[renderer] -pub fn render_workspace_directory(prev: ResultWorkspaceDirectory) -> RenderResult { - let mut result = RenderResult::default(); - writeln!(result, "{}", prev.path.bright_cyan().bold()).ok(); - result -} - -#[renderer] -pub fn render_target_directory(prev: ResultTargetDirectory) -> RenderResult { - let mut result = RenderResult::default(); - writeln!(result, "{}", prev.path.bright_cyan().bold()).ok(); - result -} diff --git a/mling/src/res/current_dir.rs b/mling/src/res/current_dir.rs deleted file mode 100644 index b928596..0000000 --- a/mling/src/res/current_dir.rs +++ /dev/null @@ -1,6 +0,0 @@ -use std::path::PathBuf; - -#[derive(Default, Clone)] -pub struct ResCurrentDir { - pub path: PathBuf, -} diff --git a/mling/src/res/manifest_path.rs b/mling/src/res/manifest_path.rs deleted file mode 100644 index 7f1686e..0000000 --- a/mling/src/res/manifest_path.rs +++ /dev/null @@ -1,13 +0,0 @@ -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 deleted file mode 100644 index caa843c..0000000 --- a/mling/src/res/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod current_dir; -pub use current_dir::*; - -mod manifest_path; -pub use manifest_path::*; |
