tawasuyu

tawasuyu/brahman

Rust 521TOML 202Markdown 57JSON 197505 Definiciones

feat(shuma): macros del shell — barra [RUN]

shuma-intent: + módulo macros.

- Macro — secuencia de intenciones nombrada, con tecla física opcional
  (F1-F3...). Builder bind()/step(). Serializable: compartible entre
  sesiones y usuarios (requisito de la spec).
- MacroBook — colección con lookup por tecla y por nombre; insert
  reemplaza por nombre.

Completa el núcleo agnóstico del shell shuma: prompt de intenciones +
grafo de contexto + macros. 11 tests verdes. cargo check --workspace
verde.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

b9a6cd33fd60c1866e6f7b3fdffc6460f4681ca9 · por sergio · (hace 5 meses) · padre 1da4ee11d · ver el árbol en este commit

Delta semántico · 17

crates/modules/shuma/shuma-intent/src/lib.rs

crates/modules/shuma/shuma-intent/src/macros.rs

2 archivos · +116 −0

diff --git a/crates/modules/shuma/shuma-intent/src/lib.rs b/crates/modules/shuma/shuma-intent/src/lib.rs
index 8b0ad23..d520c89 100644
--- a/crates/modules/shuma/shuma-intent/src/lib.rs
+++ b/crates/modules/shuma/shuma-intent/src/lib.rs
@@ -14,6 +14,8 @@
 
 pub mod parse;
 pub mod graph;
+pub mod macros;
 
 pub use graph::{CommandNode, NodeStatus, SessionGraph};
+pub use macros::{Macro, MacroBook};
 pub use parse::{Intention, Ref, Stage};
diff --git a/crates/modules/shuma/shuma-intent/src/macros.rs b/crates/modules/shuma/shuma-intent/src/macros.rs
new file mode 100644
index 0000000..a1f0eca
--- /dev/null
+++ b/crates/modules/shuma/shuma-intent/src/macros.rs
@@ -0,0 +1,114 @@
+//! Macros del shell — la barra de ejecución [RUN].
+//!
+//! Una macro es una secuencia de intenciones nombrada y opcionalmente
+//! mapeada a una tecla física (F1-F3...). Son serializables: la spec
+//! pide que sean compartibles entre sesiones y entre usuarios.
+
+use serde::{Deserialize, Serialize};
+
+/// Una macro: un nombre, una tecla opcional y las intenciones que dispara.
+#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
+pub struct Macro {
+    pub name: String,
+    /// Tecla física que la dispara (`"F1"`, `"F2"`, ...). `None` = sin atajo.
+    pub key: Option<String>,
+    /// Líneas de prompt que ejecuta, en orden.
+    pub intentions: Vec<String>,
+}
+
+impl Macro {
+    pub fn new(name: impl Into<String>) -> Self {
+        Self { name: name.into(), key: None, intentions: Vec::new() }
+    }
+
+    /// Builder: asigna una tecla.
+    pub fn bind(mut self, key: impl Into<String>) -> Self {
+        self.key = Some(key.into());
+        self
+    }
+
+    /// Builder: agrega una intención.
+    pub fn step(mut self, intention: impl Into<String>) -> Self {
+        self.intentions.push(intention.into());
+        self
+    }
+}
+
+/// Colección de macros de la barra [RUN]. Serializable para compartir.
+#[derive(Debug, Clone, Default, Serialize, Deserialize)]
+pub struct MacroBook {
+    macros: Vec<Macro>,
+}
+
+impl MacroBook {
+    pub fn new() -> Self {
+        Self::default()
+    }
+
+    /// Agrega (o reemplaza por nombre) una macro.
+    pub fn insert(&mut self, m: Macro) {
+        if let Some(slot) = self.macros.iter_mut().find(|x| x.name == m.name) {
+            *slot = m;
+        } else {
+            self.macros.push(m);
+        }
+    }
+
+    pub fn len(&self) -> usize {
+        self.macros.len()
+    }
+
+    pub fn is_empty(&self) -> bool {
+        self.macros.is_empty()
+    }
+
+    pub fn all(&self) -> &[Macro] {
+        &self.macros
+    }
+
+    /// Macro mapeada a una tecla física dada.
+    pub fn by_key(&self, key: &str) -> Option<&Macro> {
+        self.macros.iter().find(|m| m.key.as_deref() == Some(key))
+    }
+
+    /// Macro por nombre exacto.
+    pub fn by_name(&self, name: &str) -> Option<&Macro> {
+        self.macros.iter().find(|m| m.name == name)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn macro_builder_composes() {
+        let m = Macro::new("deploy")
+            .bind("F2")
+            .step("cargo build --release")
+            .step("scp target/release/app host:/srv");
+        assert_eq!(m.key.as_deref(), Some("F2"));
+        assert_eq!(m.intentions.len(), 2);
+    }
+
+    #[test]
+    fn book_lookup_by_key_and_name() {
+        let mut book = MacroBook::new();
+        book.insert(Macro::new("build").bind("F1").step("cargo build"));
+        book.insert(Macro::new("clean").bind("F3").step("cargo clean"));
+        assert_eq!(book.len(), 2);
+        assert_eq!(book.by_key("F1").unwrap().name, "build");
+        assert_eq!(book.by_key("F3").unwrap().name, "clean");
+        assert!(book.by_key("F9").is_none());
+        assert!(book.by_name("clean").is_some());
+    }
+
+    #[test]
+    fn insert_replaces_by_name() {
+        let mut book = MacroBook::new();
+        book.insert(Macro::new("x").step("v1"));
+        book.insert(Macro::new("x").step("v2"));
+        assert_eq!(book.len(), 1);
+        assert_eq!(book.by_name("x").unwrap().intentions, vec!["v2"]);
+    }
+}