From 1aa6af3b1aaf79bf100c475ec6a9aa64dc19567a Mon Sep 17 00:00:00 2001 From: Kerdonov Date: Fri, 21 Nov 2025 01:37:19 +0200 Subject: [PATCH] refactored cli and logging --- .gitignore | 4 +- Cargo.lock | 1 + cracked_md/Cargo.toml | 1 + cracked_md/src/lib.rs | 80 +++++++++++++------ gravel_cli/src/args.rs | 84 -------------------- gravel_cli/src/config.rs | 162 +++++++++++++++++++++++++++++++++++++++ gravel_cli/src/main.rs | 26 +++++-- slogger/src/lib.rs | 11 ++- stdsrv/src/lib.rs | 5 +- stdsrv/src/request.rs | 8 ++ 10 files changed, 259 insertions(+), 123 deletions(-) delete mode 100644 gravel_cli/src/args.rs create mode 100644 gravel_cli/src/config.rs diff --git a/.gitignore b/.gitignore index af95524..3cf8b8f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ -html/ -web/ +pebbles/ +site/ target/ result diff --git a/Cargo.lock b/Cargo.lock index 9ca3c78..2f7c9db 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7,6 +7,7 @@ name = "cracked_md" version = "0.1.0" dependencies = [ "fstools", + "slogger", ] [[package]] diff --git a/cracked_md/Cargo.toml b/cracked_md/Cargo.toml index 3e63602..9880575 100644 --- a/cracked_md/Cargo.toml +++ b/cracked_md/Cargo.toml @@ -5,3 +5,4 @@ edition = "2024" [dependencies] fstools = { path = "../fstools" } +slogger = { path = "../slogger" } diff --git a/cracked_md/src/lib.rs b/cracked_md/src/lib.rs index ae73c61..c652090 100644 --- a/cracked_md/src/lib.rs +++ b/cracked_md/src/lib.rs @@ -6,11 +6,13 @@ use fstools::crawl_fs; use parser::parse; +use slogger::{Level, log}; use std::{ fmt::Display, fs::{self, File}, io::Write, path::PathBuf, + time::Instant, }; use to_html::ToHtml; @@ -104,17 +106,7 @@ impl std::error::Error for MdParseError {} #[derive(Debug)] pub enum Error { - InDirIsNotDir, - OutDirIsNotEmpty, - OutDirIsNotDir, - OutDirFileOverwriteWithoutForce, - OutDirFileDeleteNotAllowed, - OutDirDirectoryInPlaceOfFile, - FileRead, - DirRead, - FileWrite, - FileCreate, - DirCreate, + FSError(String), Parse(MdParseError), } @@ -140,11 +132,14 @@ type Result = std::result::Result; /// # Errors /// Anything wrong with reading files from the directories or parsing the files. pub fn generate(indir: &PathBuf, outdir: &PathBuf, force: bool) -> Result<()> { + let start_time = Instant::now(); + let mut generated_files = 0; + if !indir.is_dir() { - Err(Error::InDirIsNotDir)?; + Err(Error::FSError("In directory not found".to_string()))?; } if !outdir.is_dir() { - Err(Error::OutDirIsNotDir)?; + Err(Error::FSError("Out directory not found".to_string()))?; } let files = crawl_fs(indir); @@ -152,12 +147,13 @@ pub fn generate(indir: &PathBuf, outdir: &PathBuf, force: bool) -> Result<()> { let fullpath = indir.as_path().join(&path); // read and parse md file - let content = fs::read_to_string(&fullpath).map_err(|_e| Error::FileRead)?; + let content = fs::read_to_string(&fullpath) + .map_err(|_e| Error::FSError(format!("File `{}` read error", path.display())))?; let html = parse(&content)?.to_html(); // write html data to file let mut newpath = outdir.to_owned(); - newpath.push(path); + newpath.push(&path); newpath.set_extension("html"); // check if path exists @@ -165,25 +161,61 @@ pub fn generate(indir: &PathBuf, outdir: &PathBuf, force: bool) -> Result<()> { // remove if is file and if force, otherwise error if newpath.is_file() { if force { - fs::remove_file(&newpath).map_err(|_e| Error::OutDirFileDeleteNotAllowed)?; + fs::remove_file(&newpath).map_err(|_e| { + Error::FSError(format!("File `{}` deleting not allowed", newpath.display())) + })?; } else { - Err(Error::OutDirFileOverwriteWithoutForce)?; + Err(Error::FSError( + "File overwrite denied, enable force overwrite".to_string(), + ))?; } } else { - Err(Error::OutDirDirectoryInPlaceOfFile)?; + Err(Error::FSError(format!( + "Directory `{}` in place of file in out directory", + newpath.display() + )))?; } } //println!("About to write file '{}'", newpath.display()); - let parent = newpath.parent().ok_or(Error::DirCreate)?; - fs::create_dir_all(parent).map_err(|_e| Error::DirCreate)?; - let mut newfile = File::create_new(newpath).map_err(|_e| Error::FileCreate)?; + let parent = newpath.parent().ok_or(Error::FSError(format!( + "Access to parent directory of `{}` denied", + newpath.display() + )))?; + fs::create_dir_all(parent) + .map_err(|_e| Error::FSError("Creating directory tree failed".to_string()))?; + let mut newfile = File::create_new(&newpath).map_err(|_e| { + Error::FSError(format!("Creating file `{}` failed", &newpath.display())) + })?; - newfile - .write(html.as_bytes()) - .map_err(|_e| Error::FileWrite)?; + newfile.write(html.as_bytes()).map_err(|_e| { + Error::FSError(format!("Writing to file `{}` failed", newpath.display())) + })?; + + log!( + Level::Debug, + "File `{}` generation to `{}` successful", + path.display(), + newpath.display() + ); + generated_files += 1; } + let time = start_time.elapsed(); + + let time_report = if time.as_micros() < 10000 { + format!("{} μs", time.as_micros()) + } else { + format!("{} ms", time.as_millis()) + }; + + log!( + Level::Info, + "Generated {} files in {} without reported errors", + generated_files, + time_report + ); + Ok(()) } diff --git a/gravel_cli/src/args.rs b/gravel_cli/src/args.rs deleted file mode 100644 index 2e65e45..0000000 --- a/gravel_cli/src/args.rs +++ /dev/null @@ -1,84 +0,0 @@ -//! Simple and program specific command line argument parsing solution. - -// todo: refactor to [] - -use slogger::{LOG_LEVEL, Level}; - -use crate::error::Error; -use std::net::Ipv4Addr; -use std::path::PathBuf; - -pub struct ProgramArgs { - pub outdir: PathBuf, - pub indir: PathBuf, - pub generate: bool, - pub force: bool, - pub addr: Ipv4Addr, - pub port: u16, - pub verbose: bool, -} - -impl Default for ProgramArgs { - fn default() -> Self { - Self { - indir: PathBuf::from("./web"), - outdir: PathBuf::from("./html"), - generate: false, - force: false, - addr: Ipv4Addr::UNSPECIFIED, - port: 8080, - verbose: false, - } - } -} - -impl TryFrom for ProgramArgs { - type Error = crate::error::Error; - fn try_from(mut value: std::env::Args) -> Result { - let mut a = Self::default(); - let _ = value.next(); // ignore executable path - while let Some(v) = value.next() { - match v.as_str() { - "-i" => { - a.indir = value - .next() - .ok_or(Error::CommandLineArgsParse( - "Expected input directory after option `-i`".to_string(), - ))? - .into(); - } - "-a" => { - let addr_string = value.next().ok_or(Error::CommandLineArgsParse( - "Expected listener IPv4 address after option `-a`".to_string(), - ))?; - a.addr = Ipv4Addr::parse_ascii(addr_string.as_bytes()).map_err(|_e| { - Error::CommandLineArgsParse( - "Invalid IPv4 address after option `-a`".to_string(), - ) - })?; - } - "-p" => { - let port_string = value.next().ok_or(Error::CommandLineArgsParse( - "Expected listener port after option `-p`".to_string(), - ))?; - a.port = port_string.parse().map_err(|_e| { - Error::CommandLineArgsParse( - "Invalid 16-bit port number after option `-p`".to_string(), - ) - })?; - } - "-g" => a.generate = true, - "-f" => a.force = true, - "-v" => { - a.verbose = true; - LOG_LEVEL.get_or_init(|| Level::Debug); - } - _ => { - a.outdir = v.into(); - } - } - } - LOG_LEVEL.get_or_init(|| Level::Info); - Ok(a) - } -} diff --git a/gravel_cli/src/config.rs b/gravel_cli/src/config.rs new file mode 100644 index 0000000..4cf0b91 --- /dev/null +++ b/gravel_cli/src/config.rs @@ -0,0 +1,162 @@ +//! Simple and program specific command line argument parsing solution. + +// todo: refactor to [] + +use slogger::{LOG_LEVEL, Level, log}; + +use crate::error::Error; +use std::env::Args; +use std::net::Ipv4Addr; +use std::path::{Path, PathBuf}; + +pub enum Command { + Generate { force: bool }, + Serve { addr: Ipv4Addr, port: u16 }, + Init, +} + +impl Default for Command { + fn default() -> Self { + Self::Generate { force: true } + } +} + +impl TryFrom for Command { + type Error = Error; + fn try_from(mut value: Args) -> Result { + let mut comm = Command::default(); + let _ = value.next(); // ignore executable + let command = value.next(); + + // `gravel serve` command + if let Some("serve") = command.as_deref() { + let mut addr = Ipv4Addr::UNSPECIFIED; + let mut port = 8080; + while let Some(a) = value.next() { + match a.as_str() { + "-a" => { + let address_str = value.next().ok_or(Error::CommandLineArgsParse( + "Missing argument after `-a`. Expected IPv4 address.".to_string(), + ))?; + addr = Ipv4Addr::parse_ascii(address_str.as_bytes()).map_err(|_e| { + Error::CommandLineArgsParse("Parsing IP address failed".to_string()) + })?; + } + "-p" => { + let port_str = value.next().ok_or(Error::CommandLineArgsParse( + "Missing argument after `-p`. Expected TCP port number.".to_string(), + ))?; + port = port_str.parse().map_err(|_e| { + Error::CommandLineArgsParse("Parsing TCP port failed".to_string()) + })?; + } + &_ => Err(Error::CommandLineArgsParse(format!( + "Unknown argument: `{a}`" + )))?, + } + } + comm = Command::Serve { addr, port }; + } + // `gravel init` command + else if let Some("init") = command.as_deref() { + if let Some(a) = value.next() { + Err(Error::CommandLineArgsParse(format!( + "Unexpected argument: `{a}`" + )))?; + } + comm = Command::Init; + } + // `gravel` command + else if let Some(a) = value.next() { + Err(Error::CommandLineArgsParse(format!( + "Unexpected argument: `{a}`" + )))?; + } + + Ok(comm) + } +} + +#[allow(unused)] +pub struct ProgramConfig { + pub outdir: PathBuf, + pub indir: PathBuf, + pub command: Command, + pub verbose: bool, +} + +impl Default for ProgramConfig { + fn default() -> Self { + Self { + indir: PathBuf::from("./pebbles"), + outdir: PathBuf::from("./site"), + command: Command::default(), + verbose: true, + } + } +} + +impl ProgramConfig { + pub fn new>(_toml_file: P, args: Args) -> Result { + let conf = Self { + command: args.try_into()?, + ..Default::default() + }; + LOG_LEVEL.get_or_init(|| Level::Debug); + log!(Level::Warn, "TOML parsing not implemented, skipping"); + Ok(conf) + } +} + +/* +impl TryFrom for ProgramConfig { + type Error = crate::error::Error; + fn try_from(mut value: std::env::Args) -> Result { + let mut a = Self::default(); + let _ = value.next(); // ignore executable path + while let Some(v) = value.next() { + match v.as_str() { + "-i" => { + a.indir = value + .next() + .ok_or(Error::CommandLineArgsParse( + "Expected input directory after option `-i`".to_string(), + ))? + .into(); + } + "-a" => { + let addr_string = value.next().ok_or(Error::CommandLineArgsParse( + "Expected listener IPv4 address after option `-a`".to_string(), + ))?; + a.addr = Ipv4Addr::parse_ascii(addr_string.as_bytes()).map_err(|_e| { + Error::CommandLineArgsParse( + "Invalid IPv4 address after option `-a`".to_string(), + ) + })?; + } + "-p" => { + let port_string = value.next().ok_or(Error::CommandLineArgsParse( + "Expected listener port after option `-p`".to_string(), + ))?; + a.port = port_string.parse().map_err(|_e| { + Error::CommandLineArgsParse( + "Invalid 16-bit port number after option `-p`".to_string(), + ) + })?; + } + "-g" => a.generate = true, + "-f" => a.force = true, + "-v" => { + a.verbose = true; + LOG_LEVEL.get_or_init(|| Level::Debug); + } + _ => { + a.outdir = v.into(); + } + } + } + LOG_LEVEL.get_or_init(|| Level::Info); + Ok(a) + } +} +*/ diff --git a/gravel_cli/src/main.rs b/gravel_cli/src/main.rs index c027db3..6920a81 100644 --- a/gravel_cli/src/main.rs +++ b/gravel_cli/src/main.rs @@ -1,16 +1,30 @@ #![feature(addr_parse_ascii, never_type)] -use args::ProgramArgs; +use std::process; + +use config::{Command, ProgramConfig}; use cracked_md::generate; use error::Error; +use slogger::{Level, log}; use stdsrv::serve; -mod args; +mod config; mod error; -fn main() -> Result { - let args = ProgramArgs::try_from(std::env::args())?; +fn run() -> Result<(), Error> { + let conf = ProgramConfig::new("gravel.toml", std::env::args())?; - generate(&args.indir, &args.outdir, args.force)?; - serve(args.addr, args.port, args.outdir)?; + match conf.command { + Command::Init => todo!("project init not implemented"), + Command::Serve { addr, port } => serve(addr, port, conf.outdir)?, + Command::Generate { force } => generate(&conf.indir, &conf.outdir, force)?, + } + Ok(()) +} + +fn main() { + let _ = run().map_err(|e| { + log!(Level::Error, "{}", e); + process::exit(1); + }); } diff --git a/slogger/src/lib.rs b/slogger/src/lib.rs index 14bf304..b2bcec0 100644 --- a/slogger/src/lib.rs +++ b/slogger/src/lib.rs @@ -30,13 +30,16 @@ impl Display for Level { #[macro_export] macro_rules! log { ($level:expr, $($arg:tt)*) => {{ + let log_from = if &$level == &$crate::Level::Debug { + format!(" {}:{}", std::file!(), std::line!()) + } else { + String::new() + }; if &$level <= $crate::LOG_LEVEL.get().unwrap_or(&$crate::Level::Info) { println!( - "{} {}:{}:{}: {}", + "{}{}: {}", $level, - std::module_path!(), - std::file!(), - std::line!(), + log_from, format!($($arg)*) ); } diff --git a/stdsrv/src/lib.rs b/stdsrv/src/lib.rs index 1c67923..3b71f1a 100644 --- a/stdsrv/src/lib.rs +++ b/stdsrv/src/lib.rs @@ -29,7 +29,7 @@ mod response; /// /// # Panics /// Never. Added to allow compiler to check for ! type. -pub fn serve(addr: Ipv4Addr, port: u16, dir: PathBuf) -> Result { +pub fn serve(addr: Ipv4Addr, port: u16, dir: PathBuf) -> Result<(), Error> { /* let args: ProgramArgs = std::env::args().try_into()?; @@ -88,6 +88,5 @@ pub fn serve(addr: Ipv4Addr, port: u16, dir: PathBuf) -> Result { Err(e) => log!(Level::Warn, "Connection failed: {}", e), } } - - panic!("Code shouldn't get to here"); + Ok(()) } diff --git a/stdsrv/src/request.rs b/stdsrv/src/request.rs index 5dfdacd..69821a8 100644 --- a/stdsrv/src/request.rs +++ b/stdsrv/src/request.rs @@ -110,6 +110,14 @@ impl TryFrom<&mut BufReader<&TcpStream>> for HttpRequest { } } + log!( + Level::Info, + "{} /{} {}", + req.method, + req.path.display(), + req.version + ); + Ok(req) } }