dotrain/cli/
rainconfig.rs

1use rain_metadata::Store;
2use serde::{Serialize, Deserialize};
3use std::{
4    path::PathBuf,
5    sync::{Arc, RwLock},
6    fs::{read, read_to_string, read_dir},
7};
8
9pub(crate) const RAINCONFIG_DESCRIPTION: &str = r"
10Description:
11rainconfig.json provides configuration details and information required for .rain composer.
12
13usually it should be placed at the root directory of the working workspace and named as 
14'rainconfig.json', however if this is not desired at times, it is possible to pass any path for 
15rainconfig when using the dotrain command using --config option.
16
17all fields in the rainconfig are optional and are as follows:
18
19  - include: Specifies a list of directories (files/folders) to be included and watched. 
20  folders will be watched recursively for .rain files. These files will be available as 
21  dotrain meta in the cas so if their hash is specified in a composition target they will 
22  get resolved.
23
24  - subgraphs: Additional subgraph endpoint URLs to include when searching for metas of 
25  specified meta hashes in a rainlang document.
26";
27pub(crate) const RAINCONFIG_INCLUDE_DESCRIPTION: &str = r"Specifies a list of directories (files/folders) to be included and watched. folders will be watched recursively for .rain files. These files will be available as dotrain meta in the cas so if their hash is specified in a compilation target they will get resolved.";
28pub(crate) const RAINCONFIG_SUBGRAPHS_DESCRIPTION: &str = r"Additional subgraph endpoint URLs to include when searching for metas of specified meta hashes in a rainlang document.";
29
30/// Data structure of deserialized rainconfig.json
31#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
32pub struct RainConfigStruct {
33    pub include: Option<Vec<PathBuf>>,
34    pub subgraphs: Option<Vec<String>>,
35}
36
37impl RainConfigStruct {
38    /// reads rainconfig from the given path
39    pub fn read(path: &PathBuf) -> anyhow::Result<RainConfigStruct> {
40        let content = read(path)?;
41        let rainconfig: RainConfigStruct = serde_json::from_slice(&content)?;
42        Ok(rainconfig)
43    }
44
45    pub fn read_included_files(&self, force: bool) -> anyhow::Result<Vec<(PathBuf, String)>> {
46        let mut files_contents = vec![];
47        if let Some(included_dirs) = &self.include {
48            for included_dir in included_dirs {
49                match read_dotrain_files(included_dir, force) {
50                    Ok(v) => files_contents.extend(v),
51                    Err(e) => {
52                        if !force {
53                            Err(e)?
54                        }
55                    }
56                }
57            }
58        }
59        Ok(files_contents)
60    }
61
62    fn process(&self, force: bool) -> anyhow::Result<Vec<(PathBuf, String)>> {
63        let mut dotrains = vec![];
64        match self.read_included_files(force) {
65            Ok(v) => dotrains.extend(v),
66            Err(e) => {
67                if !force {
68                    Err(e)?
69                }
70            }
71        }
72        Ok(dotrains)
73    }
74
75    /// Build a Store instance from all specified configuraion in rainconfig
76    pub fn build_store(&self) -> anyhow::Result<Arc<RwLock<Store>>> {
77        let empty = vec![];
78        let subgraphs = self.subgraphs.as_ref().unwrap_or(&empty);
79        let dotrains = self.process(true)?;
80        let mut store = Store::default();
81        store.add_subgraphs(subgraphs);
82        for (path, text) in dotrains {
83            if let Some(uri) = path.to_str() {
84                let uri = uri.to_string();
85                store.set_dotrain(&text, &uri, true)?;
86            } else {
87                return Err(anyhow::anyhow!(format!(
88                    "could not derive a valid utf-8 encoded URI from path: {:?}",
89                    path
90                )));
91            }
92        }
93        Ok(Arc::new(RwLock::new(store)))
94    }
95
96    /// Builds a Store instance from all specified configuraion in rainconfig by ignoring all erroneous path/items
97    pub fn force_build_store(&self) -> anyhow::Result<Arc<RwLock<Store>>> {
98        let empty = vec![];
99        let subgraphs = self.subgraphs.as_ref().unwrap_or(&empty);
100        let dotrains = self.process(false)?;
101        let mut store = Store::default();
102        store.add_subgraphs(subgraphs);
103        for (path, text) in dotrains {
104            if let Some(uri) = path.to_str() {
105                let uri = uri.to_string();
106                store.set_dotrain(&text, &uri, true)?;
107            }
108        }
109        Ok(Arc::new(RwLock::new(store)))
110    }
111}
112
113/// reads rain files recursively from the provided path
114fn read_dotrain_files(path: &PathBuf, force: bool) -> anyhow::Result<Vec<(PathBuf, String)>> {
115    let mut files_contents = vec![];
116    for read_dir_result in read_dir(path)? {
117        let dir = read_dir_result?.path();
118        if dir.is_dir() {
119            match read_dotrain_files(&dir, force) {
120                Ok(v) => files_contents.extend(v),
121                Err(e) => {
122                    if !force {
123                        Err(e)?
124                    }
125                }
126            }
127        } else if dir.is_file() {
128            if let Some(ext) = dir.extension() {
129                if ext == "rain" {
130                    match read_to_string(&dir) {
131                        Ok(v) => files_contents.push((dir.clone(), v)),
132                        Err(e) => {
133                            if !force {
134                                Err(e)?
135                            }
136                        }
137                    }
138                }
139            }
140        }
141    }
142    Ok(files_contents)
143}