Skip to main content

onefig/
conff.rs

1use std::{path::{PathBuf, Path}, mem::replace, io::{BufWriter, BufReader, Write}, fs::{File, self}, process::Command};
2use flexar::{prelude::*, compile_error::CompileError};
3use hashbrown::HashMap;
4use serde::{Serialize, Deserialize};
5use crate::{safe_unwrap, lexer::Token, errors::{SyntaxError, RuntimeError}, visitor::{ActionTree, DbgValue, Value}, patt_unwrap, target_lang::{json, toml, nix}, recur};
6
7#[derive(Debug, Serialize, Deserialize)]
8pub struct ConffTree {
9    pub conf_files: Box<[ConfFile]>,
10    pub include: Box<[(Box<[u8]>, PathBuf)]>,
11}
12
13impl ConffTree {
14    /// Gets a boxed slice of conffiles from a `Action Tree` (or att for short)
15    pub fn from_att(mut att: ActionTree) -> Self {
16        let mut out = Vec::new();
17
18        for (conff_type, path, file_path) in att.conff_list.iter_mut() {
19            let file_path = PathBuf::from(file_path.to_string());
20            let conff_type = *conff_type;
21
22            // Collect the table
23            let table = {
24                let mut current = &mut att.uni_table;
25                for (_, key) in path.iter() {
26                    current = patt_unwrap!((current.get_mut(key)) Some((_, DbgValue::Table(x))) => x); // broken and throws error when `conff toml etc.another: "etc"` fix later
27                }
28
29                current.clone()
30            };
31
32            // Collect the shell commands
33            let shell = {
34                let mut shell_cmds = Vec::new();
35                for (s_path, cmd) in att.shell_list.iter_mut() {
36                    if path.iter().map(|(_, x)| x).collect::<Vec<_>>() == s_path.iter().map(|(_, x)| x).collect::<Vec<_>>() {shell_cmds.push(
37                        replace(cmd, Box::new([]))
38                    )}
39                }
40                shell_cmds.into_boxed_slice()
41            };
42
43            // Check validity of configs for each target config language
44            // (Each config langauge might have different features)
45            use ConffType as CFT;
46            match conff_type {
47                CFT::Json => json::check_table(&table),
48                CFT::Toml => toml::check_table(&table),
49                CFT::Nix => (), // everything in the language is supported in nix 
50            }
51            
52            out.push(ConfFile {
53                conff_type,
54                table: table.into_iter().map(|(k, (_, x))| (k, x.into())).collect(),
55                path: file_path,
56                shell,
57            })
58        }
59
60        // Collect Included files recursively
61        let mut include = Vec::new();
62        for (path, target) in att.included.into_iter() {
63            recur! {
64                walk_dir(path: PathBuf, target: PathBuf, include: &mut Vec<(Box<[u8]>, PathBuf)>) <- (path, target, &mut include) {
65                    if path.is_dir() {
66                        let dir = safe_unwrap!(fs::read_dir(&path) => RT014, path.to_string_lossy());
67                        for item in dir {
68                            let item = safe_unwrap!(item => RT015, path.to_string_lossy());
69                            walk_dir(item.path(), target.join(item.file_name()), include);
70                        }
71                    } else if path.is_symlink() {
72                        let sym_path = safe_unwrap!(path.read_link() => RT008, path.to_string_lossy());
73                        if !sym_path.exists() { return }; // skip invalid symbolic paths
74                        walk_dir(sym_path, target, include);
75                    } else {
76                        include.push((lz4_flex::block::compress_prepend_size(&safe_unwrap!(fs::read(&path) => RT008, path.to_string_lossy())).into_boxed_slice(), target));
77                    }
78                }
79            }
80        }
81
82        Self {
83            conf_files: out.into_boxed_slice(),
84            include: include.into_boxed_slice(),
85        }
86    }
87
88    pub fn compile(&self, path: impl AsRef<std::path::Path>) { // todo: implement better error handling
89        let buffer = BufWriter::new(safe_unwrap!(File::create(&path) => RT003, path.as_ref().to_string_lossy()));
90        safe_unwrap!(bincode::serialize_into(buffer, self) => RT003, path.as_ref().to_string_lossy());
91    }
92
93    pub fn load_compiled(path: impl AsRef<std::path::Path>) -> Self { // todo: implement better error handling
94        let buffer = BufReader::new(safe_unwrap!(File::open(&path) => RT005, path.as_ref().to_string_lossy()));
95        safe_unwrap!(bincode::deserialize_from(buffer) => RT002, path.as_ref().to_string_lossy())
96    }
97
98    /// Generates the configuration files
99    pub fn generate(&self) {
100        // Generate config files
101        self.conf_files.iter()
102            .for_each(|x| x.generate());
103
104        // Copy the included files
105        for (contents, path) in self.include.iter() {
106            let _ = fs::create_dir_all(path.parent().unwrap_or(Path::new("")));
107            safe_unwrap!(fs::write(path,
108                safe_unwrap!(lz4_flex::block::decompress_size_prepended(contents)
109                    => RT013, path.to_string_lossy()
110                )) => RT009, path.to_string_lossy()
111            );
112        }
113    }
114}
115
116#[derive(Debug, Serialize, Deserialize)]
117pub struct ConfFile {
118    pub conff_type: ConffType,
119    pub table: HashMap<Box<str>, Value>,
120    pub path: PathBuf,
121    pub shell: Box<[Box<[Box<str>]>]>,
122}
123
124impl ConfFile {
125    pub fn generate(&self) { // todo: proper errors and handling of such
126        use ConffType as C;
127        match self.conff_type {
128            C::Json => safe_unwrap!(json::generate(&self.path, &self.table) => RT006, self.path.to_string_lossy()),
129            C::Toml => safe_unwrap!(toml::generate(&self.path, &self.table) => RT006, self.path.to_string_lossy()),
130            C::Nix => safe_unwrap!(nix::generate(&self.path, &self.table) => RT006, self.path.to_string_lossy()),
131        }
132        self.execute_shell();
133    }
134
135    pub fn execute_shell(&self) { // todo: proper errors and handling of such
136        for cmd in self.shell.iter() {
137            let cmd_display = cmd.join(" ");
138
139            // Ask user if they confirm
140            let mut user_out = String::new();
141            println!("\n{}", flexar::colour_format![
142                cyan("Are you sure you want to run this cmd `"),
143                none(&cmd_display),
144                cyan("`?\n"),
145                blue("("),
146                yellow("rewrite the command below"),
147                blue(")"),
148            ]);
149            print!("{}", flexar::colour_format![blue("-> ")]);
150            std::io::stdout().flush().unwrap();
151            std::io::stdin().read_line(&mut user_out).unwrap();
152
153            // Check if the user wrote correctly
154            if user_out.trim() != cmd_display.trim() {
155                println!("{}", flexar::colour_format![
156                    cyan("Shell commands "),
157                    red("do not"),
158                    cyan(" match, "),
159                    yellow("skipping"),
160                    cyan("...\n")
161                ]);
162                continue;
163            }
164
165            // Execute and use stdout
166            println!("{}", flexar::colour_format![
167                blue("\n==="),
168                cyan(" shell cmd `"),
169                none(&cmd_display),
170                cyan("` stdout start "),
171                blue("==="),
172            ]);
173
174            let status = safe_unwrap!(Command::new(cmd[0].as_ref())
175                .args(cmd[1..].iter().map(|x| x.as_ref()))
176                .status() => RT004, cmd_display);
177            if !status.success() {
178                return flexar::compiler_error!((RT001, Position::new_oneline("<shell>", &cmd_display, None))).throw();
179            }
180
181            println!("{}", flexar::colour_format![
182                blue("==="),
183                cyan(" shell cmd `"),
184                none(&cmd_display),
185                cyan("` stdout end   "),
186                blue("===\n"),
187            ]);
188        }
189    }
190}
191
192#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
193pub enum ConffType {
194    Toml,
195    Json,
196    Nix,
197}
198
199impl ConffType {
200    pub fn parse(parxt: &mut Parxt<'_, Token>) -> Result<Node<Self>, (u8, CompileError)> {
201        if let Some(Token::Ident(ident)) = parxt.current() {
202            let out = Ok(
203                Node { position: parxt.position(), node: match ident.as_ref() {
204                    "toml" => Self::Toml,
205                    "json" => Self::Json,
206                    "nix" => Self::Nix,
207                    _ => return Err((1, compiler_error!((SY017, parxt.position()) parxt.current_token()))),
208                }
209            });
210            parxt.advance();
211            return out;
212        }
213
214        Err((0, compiler_error!((SY018, parxt.position()) parxt.current_token())))
215    }
216}