Compare commits

...

2 Commits

Author SHA1 Message Date
1aa6af3b1a refactored cli and logging
All checks were successful
Test the running changes / Test (push) Successful in 41s
2025-11-21 01:37:19 +02:00
f68b1bb276 modified toolchain, added helix editor 2025-11-20 23:57:53 +02:00
12 changed files with 271 additions and 134 deletions

4
.gitignore vendored
View File

@@ -1,5 +1,5 @@
html/
web/
pebbles/
site/
target/
result

1
Cargo.lock generated
View File

@@ -7,6 +7,7 @@ name = "cracked_md"
version = "0.1.0"
dependencies = [
"fstools",
"slogger",
]
[[package]]

View File

@@ -5,3 +5,4 @@ edition = "2024"
[dependencies]
fstools = { path = "../fstools" }
slogger = { path = "../slogger" }

View File

@@ -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<T> = std::result::Result<T, crate::Error>;
/// # 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(())
}

View File

@@ -1,5 +1,5 @@
{
description = "stdsrv Flake file";
description = "gravel project flake";
inputs = {
nixpkgs.url = "nixpkgs/nixos-unstable";
@@ -14,16 +14,17 @@
flake-utlis.lib.eachDefaultSystem (
system: let
pkgs = import nixpkgs {inherit system;};
stdsrv = import ./package.nix {inherit pkgs;};
gravel = import ./package.nix {inherit pkgs;};
in {
packages.default = stdsrv;
packages.default = gravel;
devShells.default = pkgs.mkShell {
packages = [
pkgs.rustup
pkgs.bacon
pkgs.cargo-nextest
pkgs.cargo-expand
pkgs.cargo-watch
packages = with pkgs; [
rustup
helix
bacon
cargo-nextest
cargo-expand
cargo-watch
];
};
}

View File

@@ -1,84 +0,0 @@
//! Simple and program specific command line argument parsing solution.
// todo: refactor to <command> <subcommand> [<options>]
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<std::env::Args> for ProgramArgs {
type Error = crate::error::Error;
fn try_from(mut value: std::env::Args) -> Result<Self, Self::Error> {
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)
}
}

162
gravel_cli/src/config.rs Normal file
View File

@@ -0,0 +1,162 @@
//! Simple and program specific command line argument parsing solution.
// todo: refactor to <command> <subcommand> [<options>]
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<Args> for Command {
type Error = Error;
fn try_from(mut value: Args) -> Result<Self, Self::Error> {
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<P: AsRef<Path>>(_toml_file: P, args: Args) -> Result<Self, Error> {
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<std::env::Args> for ProgramConfig {
type Error = crate::error::Error;
fn try_from(mut value: std::env::Args) -> Result<Self, Self::Error> {
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)
}
}
*/

View File

@@ -1,17 +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::{LOG_LEVEL, Level};
use slogger::{Level, log};
use stdsrv::serve;
mod args;
mod config;
mod error;
fn main() -> Result<!, Error> {
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);
});
}

View File

@@ -5,5 +5,6 @@ targets = [
]
components = [
"clippy",
"rustfmt"
"rustfmt",
"rust-analyzer"
]

View File

@@ -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)*)
);
}

View File

@@ -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<!, Error> {
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<!, Error> {
Err(e) => log!(Level::Warn, "Connection failed: {}", e),
}
}
panic!("Code shouldn't get to here");
Ok(())
}

View File

@@ -110,6 +110,14 @@ impl TryFrom<&mut BufReader<&TcpStream>> for HttpRequest {
}
}
log!(
Level::Info,
"{} /{} {}",
req.method,
req.path.display(),
req.version
);
Ok(req)
}
}