valis_core/modules/script/
engine.rs

1use std::env;
2use std::path::PathBuf;
3
4use rlua::{Context, Lua, Result, ToLua, UserData, Value};
5use rlua::Error as LuaError;
6use rlua::FromLua;
7use rlua::Table;
8use termion::color;
9use tokio::runtime::Runtime;
10use uuid::Uuid;
11
12use crate::modules::{core, db};
13use crate::modules::db::DatabaseOperations;
14use crate::modules::db::serializers::SerializableDateTime;
15use crate::modules::formats::text;
16use crate::modules::formats::yaml::{get_yaml_value, update_yaml_value};
17use crate::modules::log::ack;
18use crate::modules::notes::markdown;
19use crate::modules::notes::markdown::Page;
20use crate::modules::projects::{agile, git};
21use crate::modules::projects::git::core::{GitOperations, SimpleRepo};
22use crate::modules::tasks::todoist;
23
24/// Remove she-bang comment lines from a script
25fn remove_comment_lines(s: &str) -> String {
26    s.lines()
27        .filter(|line| !line.trim().starts_with("#"))
28        .collect::<Vec<&str>>()
29        .join("\n")
30}
31
32fn pretty_print_table(table: &Table, indent: usize) -> Result<()> {
33    let pairs = table.clone().pairs::<Value, Value>();
34    for pair in pairs {
35        let (key, value) = pair?;
36        print!("{}{}", " ".repeat(indent), color::Fg(color::Cyan)); // keys in cyan
37        match key {
38            Value::String(s) => print!("{}", s.to_str()?),
39            Value::Integer(i) => print!("{}", i),
40            Value::Number(n) => print!("{}", n),
41            _ => print!("(non-string/number key)"),
42        }
43        print!(": {}", color::Fg(color::Reset));
44        match value {
45            Value::Table(t) => {
46                println!();
47                pretty_print_table(&t, indent + 2)?; // recursive call for nested tables
48            }
49            Value::String(s) => println!("{}{}", color::Fg(color::White), s.to_str()?), // strings in white
50            Value::Integer(i) => println!("{}{}", color::Fg(color::Magenta), i), // integers in magenta
51            Value::Number(n) => println!("{}{}", color::Fg(color::Magenta), n), // numbers in magenta
52            _ => println!("(non-table/string/number value)"),
53        }
54        print!("{}", color::Fg(color::Reset));
55    }
56    Ok(())
57}
58
59/// Add built-in functions to the Lua `context`.
60/// All functions are available in the global scope.
61/// # Arguments
62/// * `ctx` - The Lua context
63pub fn prepare_context(ctx: &Context) {
64    let globals = ctx.globals();
65    let git_clone = ctx
66        .create_function(|_, (url, destination): (String, String)| {
67            let repo = SimpleRepo {
68                url: url.to_owned(),
69                branch: None,
70                destination: destination.to_owned(),
71            };
72            ack(&format!(
73                "cloning {} into {}",
74                &url.to_owned(),
75                &destination.to_owned()
76            ));
77            repo.clone();
78            return Ok(());
79        })
80        .unwrap();
81    globals.set("git_clone", git_clone).unwrap();
82    let run = ctx
83        .create_function(|_, command: String| {
84            return Ok(core::run_buffered(&command));
85        })
86        .unwrap();
87    globals.set("run", run).unwrap();
88    let get_yaml_value = ctx
89        .create_function(|_, (file_path, yaml_key_path): (String, String)| {
90            return Ok(get_yaml_value(&file_path, &yaml_key_path).ok().unwrap());
91        })
92        .unwrap();
93    globals.set("yaml_get_value", get_yaml_value).unwrap();
94    let update_yaml_value = ctx
95        .create_function(
96            |_, (file_path, yaml_key_path, new_value): (String, String, String)| {
97                // TODO: Return the error properly
98                let v = update_yaml_value(&file_path, &yaml_key_path, &new_value)
99                    .ok()
100                    .unwrap();
101                return Ok(());
102            },
103        )
104        .unwrap();
105    globals.set("yaml_set_value", update_yaml_value).unwrap();
106    let replace_matching_line = ctx
107        .create_function(
108            |_, (file_path, regex, new_value): (String, String, String)| {
109                match text::replace_matching_line(&file_path, &regex, &new_value) {
110                    Ok(()) => Ok(()),
111                    Err(e) => Err(LuaError::RuntimeError(
112                        "Could not replace matching line".to_string(),
113                    )),
114                }
115            },
116        )
117        .unwrap();
118    globals.set("replace_line", replace_matching_line).unwrap();
119    let set_dir = ctx
120        .create_function(|_, dir: String| match core::set_dir(&dir) {
121            Ok(()) => Ok(()),
122            Err(e) => Err(LuaError::RuntimeError(
123                "Could not set current directory".to_string(),
124            )),
125        })
126        .unwrap();
127    globals.set("set_dir", set_dir).unwrap();
128    let get_dir = ctx
129        .create_function(|_, ()| match core::get_dir() {
130            Ok(dir) => Ok(dir),
131            Err(e) => Err(LuaError::RuntimeError(
132                "Could not get current directory".to_string(),
133            )),
134        })
135        .unwrap();
136    globals.set("get_dir", get_dir).unwrap();
137    let from_home = ctx
138        .create_function(|_, path: String| {
139            return core::from_home(&path)
140                .ok_or_else(|| LuaError::RuntimeError("Could not get path from home".to_string()));
141        })
142        .unwrap();
143    globals.set("from_home", from_home).unwrap();
144    let git_from_root = ctx
145        .create_function(|_, path: String| {
146            return git::core::from_root(&path).ok_or_else(|| {
147                LuaError::RuntimeError("Could not get path from git root".to_string())
148            });
149        })
150        .unwrap();
151    globals.set("git_from_root", git_from_root).unwrap();
152    let md_load = ctx
153        .create_function(|ctx, path: String| {
154            let mut pb = PathBuf::new();
155            pb.push(path);
156            let page: Page = markdown::PageLoader::from_path(&pb);
157            let table = ctx.create_table()?;
158            table.set("title", page.title.to_string())?;
159            table.set("path", page.path.to_str().unwrap_or(""))?;
160            table.set("contents", page.contents.to_string())?;
161            let wikilinks = page
162                .wikilinks
163                .into_iter()
164                .map(|wikilink| {
165                    let table = ctx.create_table().ok().unwrap();
166                    table.set("name", wikilink.name).ok().unwrap();
167                    table.set("link", wikilink.link).ok().unwrap();
168                    table.set("anchor", wikilink.anchor).ok().unwrap();
169                    table
170                        .set("link_type", wikilink.link_type as u8)
171                        .ok()
172                        .unwrap();
173                    table
174                })
175                .collect::<Vec<Table>>();
176            table.set("wikilinks", wikilinks).ok().unwrap();
177
178            Ok(table)
179        })
180        .unwrap();
181    globals.set("md_load", md_load).unwrap();
182    let pprint = ctx
183        .create_function(|_, table: Table| {
184            pretty_print_table(&table, 2)
185        })
186        .unwrap();
187    globals.set("pprint", pprint).unwrap();
188    todoist::lua::todoist_sync(ctx);
189    todoist::lua::todoist_add_task_to_sprint(ctx);
190    agile::lua::agile_create_project(ctx);
191    agile::lua::agile_create_sprint(ctx);
192    agile::lua::agile_show_sprint(ctx);
193    git::lua::_get_git_project_root_path(ctx);
194    git::lua::_get_git_project_branches(ctx);
195}
196
197/// Execute a script.
198/// # Arguments
199/// * `script` - The script to execute, as a `str`.
200pub fn execute(script: &str) -> Result<()> {
201    let lua = Lua::new();
202
203    lua.context(|lua_ctx| {
204        prepare_context(&lua_ctx);
205        let prelude = include_str!("prelude.lua");
206        lua_ctx.load(prelude).exec().unwrap();
207        lua_ctx.load(&remove_comment_lines(script)).exec().unwrap();
208        Ok(())
209    })
210}