2024-08-11 03:52:49 +01:00
|
|
|
use crate::running_command::Command;
|
2024-08-11 04:05:19 +01:00
|
|
|
use ego_tree::{NodeId, Tree};
|
2024-08-11 03:52:49 +01:00
|
|
|
use serde::Deserialize;
|
|
|
|
use std::{
|
2024-08-11 04:05:19 +01:00
|
|
|
collections::HashMap,
|
2024-08-11 03:52:49 +01:00
|
|
|
path::{Path, PathBuf},
|
|
|
|
};
|
2024-08-09 10:22:19 +01:00
|
|
|
|
2024-08-11 03:52:49 +01:00
|
|
|
#[derive(Deserialize)]
|
|
|
|
struct ScriptInfo {
|
2024-08-11 04:05:19 +01:00
|
|
|
// Path to the script file in the UI, formatted as an array of directory names (first of which being the tab)
|
2024-08-11 03:52:49 +01:00
|
|
|
ui_path: Vec<String>,
|
2024-08-11 04:05:19 +01:00
|
|
|
#[allow(dead_code)]
|
2024-08-11 03:52:49 +01:00
|
|
|
#[serde(default)]
|
2024-08-11 04:05:19 +01:00
|
|
|
// Description: Currently unused field, should be added in the future
|
2024-08-11 03:52:49 +01:00
|
|
|
description: String,
|
|
|
|
#[serde(default)]
|
2024-08-11 04:05:19 +01:00
|
|
|
// Requirements that must be met for the script to be displayed
|
2024-08-11 03:52:49 +01:00
|
|
|
preconditions: Option<Vec<Precondition>>,
|
|
|
|
#[serde(default)]
|
2024-08-11 04:05:19 +01:00
|
|
|
// Optional command. This is used for adding "raw" commands to the UI.
|
2024-08-11 03:52:49 +01:00
|
|
|
command: Option<String>,
|
|
|
|
}
|
2024-08-09 10:22:19 +01:00
|
|
|
|
2024-08-11 03:52:49 +01:00
|
|
|
impl ScriptInfo {
|
|
|
|
fn is_supported(&self) -> bool {
|
|
|
|
self.preconditions.as_deref().map_or(true, |preconditions| {
|
|
|
|
preconditions.iter().all(
|
|
|
|
|Precondition {
|
|
|
|
matches,
|
|
|
|
data,
|
|
|
|
values,
|
|
|
|
}| {
|
|
|
|
match data {
|
|
|
|
SystemDataType::Environment(var_name) => std::env::var(var_name)
|
|
|
|
.map_or(false, |var| values.contains(&var) == *matches),
|
|
|
|
SystemDataType::File(path) => {
|
|
|
|
std::fs::read_to_string(path).map_or(false, |data| {
|
|
|
|
values
|
|
|
|
.iter()
|
|
|
|
.any(|matching_value| data.contains(matching_value))
|
|
|
|
== *matches
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Deserialize)]
|
|
|
|
struct Precondition {
|
|
|
|
// If true, the data must be contained within the list of values.
|
|
|
|
// Otherwise, the data must not be contained within the list of values
|
|
|
|
matches: bool,
|
|
|
|
data: SystemDataType,
|
|
|
|
values: Vec<String>,
|
|
|
|
}
|
2024-08-09 10:22:19 +01:00
|
|
|
|
2024-08-11 03:52:49 +01:00
|
|
|
#[derive(Deserialize)]
|
|
|
|
enum SystemDataType {
|
|
|
|
#[serde(rename = "environment")]
|
|
|
|
Environment(String),
|
|
|
|
#[serde(rename = "file")]
|
|
|
|
File(PathBuf),
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Hash, Eq, PartialEq)]
|
2024-08-09 10:22:19 +01:00
|
|
|
pub struct Tab {
|
2024-08-11 03:52:49 +01:00
|
|
|
pub name: String,
|
2024-08-09 10:22:19 +01:00
|
|
|
pub tree: Tree<ListNode>,
|
|
|
|
}
|
|
|
|
|
2024-08-11 03:52:49 +01:00
|
|
|
#[derive(Clone, Hash, Eq, PartialEq)]
|
2024-08-09 10:22:19 +01:00
|
|
|
pub struct ListNode {
|
2024-08-11 03:52:49 +01:00
|
|
|
pub name: String,
|
2024-08-09 10:22:19 +01:00
|
|
|
pub command: Command,
|
|
|
|
}
|
|
|
|
|
2024-08-11 03:52:49 +01:00
|
|
|
pub fn get_tabs(command_dir: &Path, validate: bool) -> Vec<Tab> {
|
|
|
|
let scripts = get_script_list(command_dir);
|
|
|
|
|
|
|
|
let mut paths: HashMap<Vec<String>, (String, NodeId)> = HashMap::new();
|
|
|
|
let mut tabs: Vec<Tab> = Vec::new();
|
|
|
|
|
|
|
|
for (json_file, script) in scripts {
|
|
|
|
let json_text = std::fs::read_to_string(&json_file).unwrap();
|
|
|
|
let script_info: ScriptInfo =
|
|
|
|
serde_json::from_str(&json_text).expect("Unexpected JSON input");
|
|
|
|
if validate && !script_info.is_supported() {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if script_info.ui_path.len() < 2 {
|
|
|
|
panic!(
|
|
|
|
"UI path must contain a tab. Ensure that {} has correct data",
|
|
|
|
json_file.display()
|
|
|
|
);
|
|
|
|
}
|
|
|
|
let command = match script_info.command {
|
|
|
|
Some(command) => Command::Raw(command),
|
|
|
|
None if script.exists() => Command::LocalFile(script),
|
|
|
|
_ => panic!(
|
|
|
|
"Command not specified & matching script does not exist for JSON {}",
|
|
|
|
json_file.display()
|
|
|
|
),
|
|
|
|
};
|
|
|
|
for path_index in 1..script_info.ui_path.len() {
|
2024-08-11 04:48:04 +01:00
|
|
|
let path = &script_info.ui_path[..path_index];
|
|
|
|
// Create tabs and directories which don't yet exist
|
|
|
|
if !paths.contains_key(path) {
|
|
|
|
let path = path.to_vec();
|
2024-08-11 03:52:49 +01:00
|
|
|
let tab_name = script_info.ui_path[0].clone();
|
|
|
|
if path_index == 1 {
|
|
|
|
let tab = Tab {
|
|
|
|
name: tab_name.clone(),
|
|
|
|
tree: Tree::new(ListNode {
|
|
|
|
name: "root".to_string(),
|
|
|
|
command: Command::None,
|
|
|
|
}),
|
|
|
|
};
|
|
|
|
let root_id = tab.tree.root().id();
|
|
|
|
tabs.push(tab);
|
|
|
|
paths.insert(path, (tab_name, root_id));
|
|
|
|
} else {
|
|
|
|
let parent_path = &script_info.ui_path[..path_index - 1];
|
|
|
|
let (tab, parent_id) = paths.get(parent_path).unwrap();
|
|
|
|
let tab = tabs
|
|
|
|
.iter_mut()
|
|
|
|
.find(|Tab { name, .. }| name == tab)
|
|
|
|
.unwrap();
|
|
|
|
let mut parent = tab.tree.get_mut(*parent_id).unwrap();
|
|
|
|
let new_node = ListNode {
|
|
|
|
name: script_info.ui_path[path_index - 1].clone(),
|
|
|
|
command: Command::None,
|
|
|
|
};
|
|
|
|
let new_id = parent.append(new_node).id();
|
|
|
|
paths.insert(path, (tab_name, new_id));
|
2024-08-09 10:22:19 +01:00
|
|
|
}
|
2024-08-11 03:52:49 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
let (tab, parent_id) = paths
|
|
|
|
.get(&script_info.ui_path[..script_info.ui_path.len() - 1])
|
|
|
|
.unwrap();
|
|
|
|
let tab = tabs
|
|
|
|
.iter_mut()
|
|
|
|
.find(|Tab { name, .. }| name == tab)
|
|
|
|
.unwrap();
|
|
|
|
let mut parent = tab.tree.get_mut(*parent_id).unwrap();
|
|
|
|
|
|
|
|
let command = ListNode {
|
|
|
|
name: script_info.ui_path.last().unwrap().clone(),
|
|
|
|
command,
|
|
|
|
};
|
|
|
|
parent.append(command);
|
|
|
|
}
|
|
|
|
if tabs.is_empty() {
|
|
|
|
panic!("No tabs found.");
|
|
|
|
}
|
|
|
|
tabs
|
|
|
|
}
|
|
|
|
|
|
|
|
fn get_script_list(directory: &Path) -> Vec<(PathBuf, PathBuf)> {
|
|
|
|
let mut entries = std::fs::read_dir(directory)
|
|
|
|
.expect("Command directory does not exist.")
|
|
|
|
.flatten()
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
entries.sort_by_key(|d| d.path());
|
|
|
|
|
|
|
|
entries
|
|
|
|
.into_iter()
|
|
|
|
.filter_map(|entry| {
|
|
|
|
let path = entry.path();
|
|
|
|
// Recursively iterate through directories
|
|
|
|
if entry.file_type().map_or(false, |f| f.is_dir()) {
|
|
|
|
Some(get_script_list(&path))
|
|
|
|
} else {
|
|
|
|
let is_json = path.extension().map_or(false, |ext| ext == "json");
|
|
|
|
let script = path.with_extension("sh");
|
2024-08-11 04:48:04 +01:00
|
|
|
is_json.then_some(vec![(path, script)])
|
2024-08-11 03:52:49 +01:00
|
|
|
}
|
|
|
|
})
|
|
|
|
.flatten()
|
|
|
|
.collect()
|
|
|
|
}
|