Rust: Setup core architecture
This commit is contained in:
parent
bed93fd58c
commit
397ed5a236
6 changed files with 217 additions and 3 deletions
|
|
@ -10,5 +10,3 @@ documentation = "https://tori.jutty.dev/docs/"
|
|||
|
||||
edition = "2024"
|
||||
rust-version = "1.94.0"
|
||||
|
||||
[dependencies]
|
||||
|
|
|
|||
22
rust/src/conf.rs
Normal file
22
rust/src/conf.rs
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
use crate::log;
|
||||
|
||||
pub fn load() -> Configuration {
|
||||
log::elog("Loading configuration");
|
||||
|
||||
// TODO A3.1. Before parsing the user arguments, a configuration file at
|
||||
// $XDG_CONFIG_DIR/tori/tori.conf MUST be read for a line such as:
|
||||
// 'su_command = doas'.
|
||||
// TODO A3.2. If this line is not found, the su_command MUST default to 'su -c'.
|
||||
// TODO A3.3. If it is found, the su_command used MUST be whatever was specified.
|
||||
// TODO A3.4. Whatever su_command MUST be validated once for presence at the path
|
||||
// provided or obtained from $PATH and filesystem permission to execute
|
||||
|
||||
Configuration {
|
||||
su_command: String::default(),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Configuration {
|
||||
su_command: String,
|
||||
}
|
||||
3
rust/src/lib.rs
Normal file
3
rust/src/lib.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
pub mod conf;
|
||||
pub mod log;
|
||||
pub mod run;
|
||||
6
rust/src/log.rs
Normal file
6
rust/src/log.rs
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
pub fn elog(message: &str) {
|
||||
// DONE MUST be printed only if DEBUG is set in the environment
|
||||
if let Ok(debug) = std::env::var("DEBUG") && !debug.is_empty() {
|
||||
eprintln!(" [log] {message}");
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,11 @@
|
|||
use tori::{conf, log, run};
|
||||
|
||||
fn main() {
|
||||
println!("Hello, Rust!");
|
||||
log::elog(&format!("tori {}", env!("CARGO_PKG_VERSION")));
|
||||
let configuration = conf::load();
|
||||
log::elog(&format!("Configuration: {configuration:#?}"));
|
||||
let mut order = run::teller::parse(std::env::args());
|
||||
log::elog(&format!("Order: {order:#?}"));
|
||||
order.fill();
|
||||
log::elog(&format!("Filled Order: {order:#?}"));
|
||||
}
|
||||
|
|
|
|||
177
rust/src/run.rs
Normal file
177
rust/src/run.rs
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
// B2.1. DONE version | -v | --version -> MUST print the version as in v0.8.0
|
||||
// B2.2. TODO help | -h | --help -> MUST print '<long help>'
|
||||
// B2.3. TODO os -> MUST print the OS name and MUST log contents of /etc/os-release
|
||||
// B2.4. TODO user -> MUST print the output of the 'whoami' command
|
||||
// B2.5. TODO pkg p -> MUST call the system package manager using the su_command
|
||||
// to install and then uninstall package p. The user MUST be able to
|
||||
// freely input to these commands' interactive inputs before control
|
||||
// is returned. When done, it MUST log 'Done:', a newline, and the
|
||||
// system commands executed, one per line. If no p is provided, it
|
||||
// MUST NOT run any system commands and print a message
|
||||
// B2.6. TODO echo x y z -> MUST print x y z
|
||||
// B2.7. TODO echo -> MUST NOT print any output and exit with status code 0
|
||||
// B2.8. DONE [no input] -> MUST NOT print any output and exit with status code 0
|
||||
// B2.9. TODO [any other input] -> MUST print 'Unrecognized command: [command]',
|
||||
// a newline, '<short help>' and exit with status code 1
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct Order {
|
||||
tasks: Vec<Task>,
|
||||
}
|
||||
|
||||
impl Order {
|
||||
|
||||
pub fn fill(&mut self) {
|
||||
for task in self.tasks.iter_mut() {
|
||||
if task.done { continue }
|
||||
task.complete();
|
||||
}
|
||||
}
|
||||
|
||||
fn is_complete(&self) -> bool {
|
||||
self.tasks.iter().any(|e| !e.done)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Task {
|
||||
kind: TaskKind,
|
||||
done: bool,
|
||||
parameters: Vec<String>,
|
||||
}
|
||||
|
||||
impl Task {
|
||||
pub fn complete(&mut self) {
|
||||
use crate::{run::exec::{meta, os, shell, pkg}};
|
||||
use TaskKind::*;
|
||||
|
||||
self.done = match self.kind {
|
||||
Version => { meta::print_version() },
|
||||
_ => false, // TODO
|
||||
}
|
||||
}
|
||||
|
||||
fn new(kind: TaskKind, parameters: Vec<String>) -> Task {
|
||||
Task {
|
||||
kind,
|
||||
done: false,
|
||||
parameters,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum TaskKind {
|
||||
Version,
|
||||
OperatingSystem,
|
||||
Package,
|
||||
User,
|
||||
Echo,
|
||||
}
|
||||
|
||||
pub mod teller {
|
||||
use crate::{log::elog, run::{Order, Task, TaskKind}};
|
||||
use std::{env, path::PathBuf};
|
||||
|
||||
pub fn parse(mut raw_args: env::Args) -> Order {
|
||||
let (argument, parameters): (String, Vec<String>) = if let Some(first) = raw_args.next() {
|
||||
if is_executable_path(&first) {
|
||||
elog("First argument is the executable path");
|
||||
if let Some(second) = raw_args.next() {
|
||||
elog(&format!(
|
||||
"Assembled command {second}, arguments {raw_args:?}"
|
||||
));
|
||||
(second, raw_args.collect())
|
||||
} else {
|
||||
elog("No arguments provided");
|
||||
return Order::default();
|
||||
}
|
||||
} else {
|
||||
elog("First argument is not the executable path");
|
||||
elog(&format!(
|
||||
"Assembled command {first}, arguments {raw_args:?}"
|
||||
));
|
||||
(first, raw_args.collect())
|
||||
}
|
||||
} else {
|
||||
elog("No arguments provided");
|
||||
return Order::default();
|
||||
};
|
||||
|
||||
use TaskKind::*;
|
||||
|
||||
if argument == "version" || argument == "-v" || argument == "--version" {
|
||||
elog("Command is 'version'");
|
||||
Order { tasks: vec![Task::new(Version, parameters)] }
|
||||
} else if argument == "os" {
|
||||
elog("Command is 'os'");
|
||||
Order { tasks: vec![Task::new(OperatingSystem, parameters)] }
|
||||
} else if argument == "pkg" {
|
||||
elog("Command is 'pkg'");
|
||||
Order { tasks: vec![Task::new(Package, parameters)] }
|
||||
} else if argument == "user" {
|
||||
elog("Command is 'user'");
|
||||
Order { tasks: vec![Task::new(User, parameters)] }
|
||||
} else if argument == "echo" {
|
||||
elog("Command is 'echo'");
|
||||
Order { tasks: vec![Task::new(Echo, parameters)] }
|
||||
} else {
|
||||
Order::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn is_executable_path(candidate: &str) -> bool {
|
||||
|
||||
fn assume(message: &str) -> bool {
|
||||
elog(&format!("Assuming args[0] is the executable {message}"));
|
||||
true
|
||||
}
|
||||
|
||||
let Ok(executable_path) = env::current_exe() else {
|
||||
return assume("Failed to get executable path")
|
||||
};
|
||||
let Some(executable_file) = executable_path.file_name() else {
|
||||
return assume("Executable path lacks a file component")
|
||||
};
|
||||
|
||||
let argument_path = PathBuf::from(candidate);
|
||||
let Some(argument_file) = argument_path.file_name() else {
|
||||
return assume("Argument path lacks a file component")
|
||||
};
|
||||
|
||||
elog(&format!(
|
||||
"Executable path: {executable_path:?}, file {executable_file:?} \
|
||||
Argument path: {argument_path:?}, file {argument_file:?} "
|
||||
));
|
||||
|
||||
if argument_path.exists() {
|
||||
if let Ok(argument_canonical) = argument_path.canonicalize()
|
||||
&& let Ok(executable_canonical) = executable_path.canonicalize() {
|
||||
let judgment = argument_canonical == executable_canonical;
|
||||
elog(&format!("args[0] canonically is executable path: {judgment}"));
|
||||
judgment
|
||||
} else {
|
||||
assume("Could not canonicalize executable and argument paths")
|
||||
}
|
||||
} else {
|
||||
let judgment = argument_file == executable_file;
|
||||
elog(&format!("args[0] matches executable path by name only: {judgment}"));
|
||||
judgment
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
pub mod expeditor {}
|
||||
|
||||
pub mod exec {
|
||||
pub mod meta {
|
||||
pub fn print_version() -> bool {
|
||||
println!("v{}", env!("CARGO_PKG_VERSION"));
|
||||
true
|
||||
}
|
||||
}
|
||||
pub mod os {}
|
||||
pub mod shell {}
|
||||
pub mod pkg {}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue