aimx/aim/
workspace.rs

1//! Internal workspace spine backed by [`Node`].
2//! Provides a process-global workspace root used by path and rule resolution.
3//! External users should use higher-level APIs.
4
5use crate::{
6    Sheet, aim::{ Rule, Workflow, WorkflowLike}, expressions::Reference, reference::parse_identifier, values::{Instance, Node}
7};
8use anyhow::{Result, anyhow};
9use once_cell::sync::{
10    Lazy,
11    OnceCell,
12};
13use std::{
14    fs,
15    path::{Path, PathBuf},
16    sync::{Arc, RwLock},
17};
18
19static WORKSPACE_PATH: OnceCell<PathBuf> = OnceCell::new();
20pub struct BasePath {}
21impl BasePath {
22    pub fn init(path: &Path) -> Result<()> {
23        let basepath = path.parent().unwrap_or(Path::new("")).to_path_buf();
24        fs::create_dir_all(&basepath)?;
25        WORKSPACE_PATH.set(basepath)
26            .map_err(|_| anyhow!("Workspace path already initialized"))
27    }
28    
29    pub fn convert(locator: &Reference) -> Result<Arc<PathBuf>> {
30        let base_path = WORKSPACE_PATH.get()
31            .ok_or_else(|| anyhow!("Workspace path not initialized"))?;
32
33        if let Reference::One(one) = locator {
34            if one.as_ref() == "_" {
35                return Ok(Arc::new(base_path.join("workspace.aim")));
36            }
37        }
38        let root_path = base_path.join("workspace");
39        std::fs::create_dir_all(&root_path)?;
40
41        Ok(Arc::new(match locator {
42            Reference::One(one) => {
43                if !root_path.exists() {
44                    fs::create_dir_all(&root_path)?;
45                }
46                root_path.join(format!("{}.aim", one))
47            }
48            Reference::Two(one, two) => {
49                let branch_path = root_path.join(one.as_ref());
50                if !branch_path.exists() {
51                    fs::create_dir_all(&branch_path)?;
52                }
53                branch_path.join(format!("{}.aim", two))
54            }
55            Reference::Three(one, two, three) => {
56                let branch_path = root_path.join(one.as_ref()).join(two.as_ref());
57                if !branch_path.exists() {
58                    fs::create_dir_all(&branch_path)?;
59                }
60                branch_path.join(format!("{}.aim", three))
61            }
62            Reference::Four(one, two, three, four) => {
63                let branch_path = root_path.join(one.as_ref()).join(two.as_ref()).join(three.as_ref());
64                if !branch_path.exists() {
65                    fs::create_dir_all(&branch_path)?;
66                }
67                branch_path.join(format!("{}.aim", four))
68            }
69        }))
70    }
71}
72
73/// Process-global workspace root.
74///
75/// Holds the root [`Node`] and base filesystem path.
76pub struct Workspace {
77    node: Node,
78}
79
80/// Global workspace singleton used by free functions in this module.
81static GLOBAL_WORKSPACE: Lazy<RwLock<Workspace>> =
82    Lazy::new(|| RwLock::new(Workspace::new()));
83
84/// Access the global workspace.
85fn get_workspace_lock() -> &'static RwLock<Workspace> {
86    &GLOBAL_WORKSPACE
87}
88
89impl Workspace {
90    /// New empty workspace with default paths.
91    fn new() -> Self {
92        Workspace {
93            // This shouldn't fail because no other threads have access yet.
94            node: Node::init_default(Reference::workspace()),
95        }
96    }
97
98    pub fn workflow() -> Result<Arc<Workflow>> {
99        let read_guard = get_workspace_lock().read()
100            .expect("Lock poisoned in workflow() workspace read.");
101        read_guard.node.get_workflow()      
102    }
103
104    // -----------------------------------------------------------------------------
105    // Workspace Functions
106    // -----------------------------------------------------------------------------
107
108    pub fn create(path: Arc<PathBuf>) -> Result<Sheet> {
109        match BasePath::init(&path) {
110            Ok(_) => {
111                // Initialize default workflow structure
112                let mut workspace = Workflow::new_from(Reference::workspace(), path);
113                workspace.add_node(Arc::from("context"), Node::default())?;
114                workspace.add_node(Arc::from("inference"), Node::default())?;
115                workspace.add_node(Arc::from("status"), Node::default())?;
116                workspace.add_node(Arc::from("tool"), Node::default())?;
117                workspace.add_node(Arc::from("workflow"), Node::default())?;
118                workspace.save()?;
119                // Bind MVCC snapshot
120                let write_guard = get_workspace_lock().write()
121                    .expect("Lock poisoned in create() workspace write.");
122                write_guard.node.set_workflow(workspace);
123                Sheet::convert(&write_guard.node)
124            }
125            _ => {
126                let write_guard = get_workspace_lock().write()
127                    .expect("Lock poisoned in create() workspace write.");
128                Sheet::convert(&write_guard.node)
129            }
130        }
131    }
132
133    /// Initialize the workspace path
134    pub fn open(path: &Path) -> Result<Sheet> {
135        let _ = BasePath::init(&path);
136        let write_guard = get_workspace_lock().write()
137            .expect("Lock poisoned in open() workspace write.");
138        Sheet::convert(&write_guard.node)
139    }
140
141    pub fn save() -> Result<()> {
142        // Write workspace and save
143        let write_guard = get_workspace_lock().write()
144            .expect("Lock poisoned in save() workspace write.");
145        let workflow = write_guard.node.get_workflow()?;
146        if workflow.is_touched() {
147            let mut workspace = write_guard.node.get_workflow_mut()?;
148            workspace.save()?;
149            write_guard.node.set_workflow(workspace); 
150        }
151        Ok(())
152    }
153
154    pub fn save_all() -> Result<()> {
155        {
156            let write_guard = get_workspace_lock().write()
157                .expect("Lock poisoned in save_all() workspace write.");
158            let workspace = write_guard.node.get_workflow()?;
159            if workspace.is_touched() {
160                let mut workspace = write_guard.node.get_workflow_mut()?;
161                workspace.save_all()?;
162                write_guard.node.set_workflow(workspace);
163                return Ok(());
164            }
165        }
166        {
167            let read_guard = get_workspace_lock().read()
168                .expect("Lock poisoned in save_all() workspace read.");
169            let workspace = read_guard.node.get_workflow()?;
170            for rule in workspace.iter_rules() {
171                if let Some(node) = rule.get_node() {
172                    node.save_all()?;
173                } else if let Some(instance) = rule.get_instance() {
174                    instance.save_all()?;
175                }
176            }
177            Ok(())
178        }
179    }
180
181    // Prune open read-only nodes from memory
182    pub fn compact() -> Result<()> {
183        let read_guard = get_workspace_lock().read()
184            .expect("Lock poisoned in compact() workspace read.");
185        let workspace = read_guard.node.get_workflow()?;
186        for rule in workspace.iter_rules() {
187            if let Some(node) = rule.get_node() {
188                node.compact();
189            } else if let Some(instance) = rule.get_instance() {
190                instance.compact();
191            }
192        }
193        Ok(())
194    }
195
196    pub fn sheet() -> Result<Sheet> {
197        let read_guard = get_workspace_lock().read()
198            .expect("Lock poisoned in sheet() workspace read.");
199        Sheet::convert(&read_guard.node)
200    }
201
202    // -----------------------------------------------------------------------------
203    // Workspace Node Functions
204    // -----------------------------------------------------------------------------
205
206    pub fn catalog() -> Result<Vec<Arc<Sheet>>> {
207        let read_guard = get_workspace_lock().read()
208            .expect("Lock poisoned in catalog() workspace read.");
209        let workspace = read_guard.node.get_workflow_like()?;
210        let mut list = Vec::new();
211        for rule in workspace.iter_rules() {
212            if let Some(node) = rule.get_node() {
213                let sheet = Sheet::convert(&node)?;
214                list.push(Arc::new(sheet));
215            }
216        }
217        Ok(list)
218    }
219
220    pub fn contains_node(identifier: &str) -> Result<bool> {
221        let read_guard = get_workspace_lock().read()
222            .expect("Lock poisoned in contains_node() workspace read.");
223        let workspace = read_guard.node.get_workflow_like()?;
224        Ok(workspace.contains(&identifier))
225    }
226
227    fn arc_identifier(input: &str) -> Result<Arc<str>> {
228        if input == "" {
229            return Err(anyhow!("Empty identifier"));
230        }
231        match parse_identifier(input) {
232            Ok((remain, identifier)) => {
233                if remain == "" {
234                    if identifier == "_" {
235                        Err(anyhow!("Invalid identifier {}", input))
236                    } else {
237                        Ok(Arc::from(identifier))
238                    }
239                } else {
240                    Err(anyhow!("Invalid identifier {}~{}", input, remain))
241                }
242            }
243            Err(e) => Err(anyhow!("Syntax error ({})", e)),
244        }
245    }
246
247    pub fn add_node(identifier: &str, parameters: &str) -> Result<Arc<Reference>> {
248        let arc_identifier = Self::arc_identifier(identifier)?;
249        let node = Node::parse(parameters)?;
250        // Read workspace
251        {
252            let read_guard = get_workspace_lock().read()
253                .expect("Lock poisoned in add_node() workspace read.");
254            let workspace = read_guard.node.get_workflow()?;
255            if workspace.contains(&arc_identifier) {
256                return Err(anyhow!("Workspace node {} already exists", identifier));
257            }
258        }
259        // Write workspace and save
260        {
261            let write_guard = get_workspace_lock().write()
262                .expect("Lock poisoned in add_node() workspace write.");
263            let mut workspace = write_guard.node.get_workflow_mut()?;
264            let reference = workspace.add_node(arc_identifier, node)?;
265            let _ = workspace.save();
266            write_guard.node.set_workflow(workspace);
267            Ok(reference)
268        }
269    }
270
271    pub fn copy_node(from: Arc<str>, to: Arc<str>) -> Result<()> {
272        // First check if the target node already exists
273        {
274            let read_guard = get_workspace_lock().read()
275                .expect("Lock poisoned in copy_node() workspace read.");
276            let workspace = read_guard.node.get_workflow()?;
277            if false == workspace.contains(&from) {
278                return Err(anyhow!("Workspace node {} not found", from));
279            }
280            if workspace.contains(&to) {
281                return Err(anyhow!("Workspace node {} already exists", to));
282            }
283        }
284
285        // Rename the workspace node
286        {
287            let write_guard = get_workspace_lock().write()
288                .expect("Lock poisoned in copy_node() workspace write.");
289            let mut workspace = write_guard.node.get_workflow_mut()?;
290            let from_node = workspace.get_node(&from)?;
291            workspace.copy_node(&from_node, to)?;
292            let _ = workspace.save();
293            write_guard.node.set_workflow(workspace);
294            Ok(())
295        }
296    }
297
298    pub fn get_node(identifier: &str) -> Result<Node> {
299        let read_guard = get_workspace_lock().read()
300            .expect("Lock poisoned in get_node() workspace read.");
301        let workspace = read_guard.node.get_workflow()?;
302        if let Some(rule) = workspace.get_rule(identifier) {
303            if let Some(node) = rule.get_node() {
304                return Ok(node);
305            }
306        }
307        return Err(anyhow!("Workspace node {} not found", identifier))
308    }
309
310    pub fn rename_node(from: &str, to: Arc<str>) -> Result<()> {
311        if from == "context" {
312            return Err(anyhow!("Cannot rename context node"))
313        }
314        // First check if the target node already exists
315        {
316            let read_guard = get_workspace_lock().read()
317                .expect("Lock poisoned in rename_node() workspace read.");
318            let workspace = read_guard.node.get_workflow()?;
319            if false == workspace.contains(from) {
320                return Err(anyhow!("Workspace node {} not found", from));
321            }
322            if workspace.contains(&to) {
323                return Err(anyhow!("Workspace node {} already exists", to));
324            }
325        }
326
327        // Rename the workspace node
328        {
329            let write_guard = get_workspace_lock().write()
330                .expect("Lock poisoned in rename_node() workspace write.");
331            let mut workspace = write_guard.node.get_workflow_mut()?;
332            workspace.rename_node(from, to)?;
333            let _ = workspace.save();
334            write_guard.node.set_workflow(workspace);
335            Ok(())
336        }
337    }
338
339    pub fn remove_node(identifier: &str) -> Result<()> {
340        if identifier == "context" {
341            return Err(anyhow!("Cannot remove context node"))
342        }
343        // First check if the node exists
344        {
345            let read_guard = get_workspace_lock().read()
346                .expect("Lock poisoned in remove_node() workspace read.");
347            let workspace = read_guard.node.get_workflow()?;
348            if false == workspace.contains(identifier) {
349                return Err(anyhow!("Workspace node {} not found", identifier));
350            }
351        }
352        
353        // Remove the node from the workspace
354        {
355            let write_guard = get_workspace_lock().write()
356                .expect("Lock poisoned in remove_node() workspace write.");
357            let mut workspace = write_guard.node.get_workflow_mut()?;
358            workspace.delete_node(identifier)?;
359            let _ = workspace.save();
360            write_guard.node.set_workflow(workspace);
361            Ok(())
362        }
363    }
364
365    // -----------------------------------------------------------------------------
366    // Workspace Tree Functions
367    // -----------------------------------------------------------------------------
368
369    /// Resolve a [`Node`] from a hierarchical [`Reference`].
370    pub fn locate_node(locator: &Reference) -> Result<Node> {
371        match locator {
372            Reference::One(one) => {
373                // Try workspace
374                if let Ok(node) = Self::get_node(one) {
375                    return Ok(node);
376                }
377            }
378            Reference::Two(one, two) => {
379                if let Ok(node) = Self::get_node(one) {
380                    if let Some(rule) = &node.get_workflow()?.get_rule(two) {
381                        if let Some(node) = rule.get_node() {
382                            return Ok(node);
383                        }
384                    }
385                }
386            }
387            Reference::Three(one, two, three) => {
388                if let Ok(node) = Self::get_node(one) {
389                    if let Some(rule) = &node.get_workflow()?.get_rule(two) {
390                        if let Some(node) = rule.get_node() {
391                            if let Some(rule) = &node.get_workflow()?.get_rule(three) {
392                                if let Some(node) = rule.get_node() {
393                                    return Ok(node);
394                                }
395                            }
396                        }
397                    }
398                }
399            }
400            _ => {}
401        }
402        Err(anyhow!("Node {} not found", locator))
403    }
404
405    /// Resolve a [`Node`] from a hierarchical [`Reference`].
406    pub fn locate_instance(locator: &Reference, identifier: &str) -> Result<Instance> {
407        match locator {
408            Reference::One(one) => {
409                // Try workspace
410                if let Ok(node) = Self::get_node(one) {
411                    if let Some(rule) = &node.get_workflow()?.get_rule(identifier) {
412                        if let Some(instance) = rule.get_instance() {
413                            return Ok(instance);
414                        }
415                    }
416                }
417            }
418            Reference::Two(one, two) => {
419                if let Ok(node) = Self::get_node(one) {
420                    if let Some(rule) = &node.get_workflow()?.get_rule(two) {
421                        if let Some(node) = rule.get_node() {
422                            if let Some(rule) = &node.get_workflow()?.get_rule(identifier) {
423                                if let Some(instance) = rule.get_instance() {
424                                    return Ok(instance);
425                                }
426                            }
427                        }
428                    }
429                }
430            }
431            Reference::Three(one, two, three) => {
432                if let Ok(node) = Self::get_node(one) {
433                    if let Some(rule) = &node.get_workflow()?.get_rule(two) {
434                        if let Some(node) = rule.get_node() {
435                            if let Some(rule) = &node.get_workflow()?.get_rule(three) {
436                                if let Some(node) = rule.get_node() {
437                                    if let Some(rule) = &node.get_workflow()?.get_rule(identifier) {
438                                        if let Some(instance) = rule.get_instance() {
439                                            return Ok(instance);
440                                        }
441                                    }
442                                }
443                            }
444                        }
445                    }
446                }
447            }
448            _ => {}
449        }
450        Err(anyhow!("Instance {} not found", locator))
451    }
452
453    /// Resolve the [`Rule`] addressed by a hierarchical [`Reference`].
454    pub fn locate_rule(reference: &Reference) -> Result<Option<Arc<Rule>>> {
455        match reference {
456            Reference::Two(one, two) => {
457                // Try workspace
458                if let Ok(node) = Self::get_node(one) {
459                    return Ok(node.get_workflow_like()?.get_rule(two));
460                }
461            }
462            Reference::Three(one, two, three) => {
463            // Try workspace
464                if let Ok(node) = Self::get_node(one) {
465                    if let Some(rule) = &node.get_workflow_like()?.get_rule(two) {
466                        if let Some(node) = rule.get_node() {
467                            return Ok(node.get_workflow_like()?.get_rule(three));
468                        }
469                    }
470                }
471            }
472            Reference::Four(one, two, three, four) => {
473                if let Ok(node) = Self::get_node(one) {
474                    if let Some(rule) = &node.get_workflow_like()?.get_rule(two) {
475                        if let Some(node) = rule.get_node() {
476                            if let Some(rule) = &node.get_workflow_like()?.get_rule(three) {
477                                if let Some(node) = rule.get_node() {
478                                    return Ok(node.get_workflow_like()?.get_rule(four));
479                                }
480                            }
481                        }
482                    }
483                }
484            }
485            _ => {}
486        }
487        Ok(None)
488    }
489}