aboutsummaryrefslogtreecommitdiff
path: root/mling/src/proj_mgr
diff options
context:
space:
mode:
Diffstat (limited to 'mling/src/proj_mgr')
-rw-r--r--mling/src/proj_mgr/checklist_reader.rs294
-rw-r--r--mling/src/proj_mgr/generator.rs57
-rw-r--r--mling/src/proj_mgr/metadata.rs142
-rw-r--r--mling/src/proj_mgr/mod.rs30
-rw-r--r--mling/src/proj_mgr/show_binaries.rs78
-rw-r--r--mling/src/proj_mgr/show_directories.rs61
6 files changed, 0 insertions, 662 deletions
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
-}