2 Commits

Author SHA1 Message Date
c33428f3e1 builder rewrite wip 2025-11-12 22:49:59 +01:00
fc98b48601 setting output and input is now global 2025-11-11 13:03:26 +01:00
5 changed files with 162 additions and 151 deletions

View File

@@ -1,49 +1 @@
use std::fs;
use self::asciidoctor::{build_doc, build_slide};
use super::fs_util;
pub mod asciidoctor; pub mod asciidoctor;
pub fn docki_build(in_path: &str, offline_reveal: bool, docs_dir: &str) -> DockiBuildResult {
let out_path = in_path.replace(docs_dir, "./dist");
let convert_out_path = out_path.replace(".adoc", ".html");
if in_path.starts_with(format!("{}/slides/", docs_dir).as_str()) && in_path.ends_with(".adoc") {
if let Err(err) = build_slide(&in_path, &convert_out_path, offline_reveal) {
return DockiBuildResult::Err(err);
}
DockiBuildResult::Slide(convert_out_path)
} else if in_path.ends_with(".adoc") {
if let Err(err) = build_doc(&in_path, &convert_out_path) {
return DockiBuildResult::Err(err);
}
DockiBuildResult::Doc(convert_out_path)
} else {
if let Err(err) = copy(&in_path, &out_path) {
return DockiBuildResult::Err(err);
}
DockiBuildResult::Copy(out_path)
}
}
fn copy(in_path: &str, out_path: &str) -> Result<(), String> {
fs_util::create_parent_dir_recursive(out_path);
if let Err(err) = fs::copy(in_path, out_path) {
return Err(err.to_string());
}
Ok(())
}
pub enum DockiBuildResult {
Slide(String),
Doc(String),
Copy(String),
Err(String),
}

View File

@@ -3,6 +3,8 @@ use crate::app::config::config::Config;
use super::executions::build_execution::BuildExecution; use super::executions::build_execution::BuildExecution;
pub async fn build(config: &Config) -> () { pub async fn build(config: &Config) -> () {
let mut build_execution = BuildExecution::new(&config.input_dir); let mut build_execution = BuildExecution::new(config);
build_execution.execute(&config).await.expect("build failed")
build_execution.prepare().await.expect("could not prepare for build");
build_execution.build_dir().expect("build failed")
} }

View File

@@ -1,52 +1,41 @@
use std::{ use std::{
io::Cursor, path::PathBuf fs, io::Cursor, path::PathBuf
}; };
use crate::app::{ use crate::app::{
build::{DockiBuildResult, docki_build}, config::config::Config, fs_util::{self, create_dir_recursive}, log::display_status build::{asciidoctor::build_slide, asciidoctor::build_doc}, config::config::Config, fs_util::{self, create_dir_recursive}, log::display_status
}; };
pub struct BuildExecution { pub struct BuildExecution<'a> {
progress: usize, progress: usize,
goal: usize, goal: usize,
docs_dir: String config: &'a Config
} }
impl BuildExecution { impl <'a> BuildExecution <'a> {
pub fn new(docs_dir: &str) -> Self { pub fn new(config: &'a Config) -> Self {
return BuildExecution { return Self {
progress: 0, progress: 0,
goal: 0, goal: 0,
docs_dir: docs_dir.to_string() config: config
}; };
} }
pub async fn execute(&mut self, config: &Config) -> Result<(), String> { pub async fn prepare(&self) -> Result<(), String> {
let path = self.docs_dir.to_string(); if !fs_util::directory_exists(&self.config.input_dir) {
if !fs_util::directory_exists(&path) {
return Err( return Err(
"docs directory does not exist it. Create it or use the template".to_string(), "docs directory does not exist it. Create it or use the template".to_string(),
); );
} }
if let Err(error) = self.prepare(config.offline_reveal).await { if !self.config.offline_reveal {
return Err(error);
}
return self.build_dir(&path, config.offline_reveal);
}
async fn prepare(&self, offline_reveal: bool) -> Result<(), String> {
if !offline_reveal {
return Ok(()) return Ok(())
} }
let reveal_version = "5.2.1"; let reveal_version = "5.2.1";
let target = format!("https://github.com/hakimel/reveal.js/archive/{reveal_version}.zip"); let target = format!("https://github.com/hakimel/reveal.js/archive/{reveal_version}.zip");
create_dir_recursive(format!("{}/slides", self.docs_dir).as_str()); create_dir_recursive(format!("{}/slides", self.config.input_dir).as_str());
reqwest::get(target.clone()).await.unwrap(); reqwest::get(target.clone()).await.unwrap();
let Ok(response) = reqwest::get(target).await else { let Ok(response) = reqwest::get(target).await else {
@@ -57,7 +46,7 @@ impl BuildExecution {
return Err("could not extract bytes".to_string()) return Err("could not extract bytes".to_string())
}; };
let out = PathBuf::from(format!("{}/slides/revealjs", self.docs_dir)); let out = PathBuf::from(format!("{}/slides/revealjs", self.config.input_dir));
if zip_extract::extract(Cursor::new(bytes), &out, true).is_err() { if zip_extract::extract(Cursor::new(bytes), &out, true).is_err() {
return Err("could not write extracted archive to disk".to_string()); return Err("could not write extracted archive to disk".to_string());
@@ -66,23 +55,22 @@ impl BuildExecution {
return Ok(()); return Ok(());
} }
fn build_dir(&mut self, path: &str, offline_reveal: bool) -> Result<(), String> { pub fn build_dir(&mut self) -> Result<(), String> {
let result = fs_util::fetch_paths_recursive(&path); let result = fs_util::fetch_paths_recursive(&self.config.input_dir);
let Ok(paths) = result else { let Ok(paths) = result else {
return Err(result.unwrap_err()) return Err(result.unwrap_err())
}; };
let reveal_dir = format!("{}/slides/revealjs", path); let reveal_dir = format!("{}/slides/revealjs", self.config.input_dir);
let paths = paths.into_iter() let paths = paths.into_iter()
.filter(|path| offline_reveal || !path.starts_with(reveal_dir.as_str())) .filter(|path| self.config.offline_reveal || !path.starts_with(reveal_dir.as_str()))
.collect::<Vec<String>>(); .collect::<Vec<String>>();
self.goal = paths.len(); self.goal = paths.len();
for (index, in_path) in paths.iter().enumerate() { for (index, in_path) in paths.iter().enumerate() {
self.progress = index + 1; self.progress = index + 1;
let result = docki_build(&in_path, offline_reveal, &self.docs_dir); let result = self.build_file(&in_path);
match result { match result {
DockiBuildResult::Err(err) => { DockiBuildResult::Err(err) => {
@@ -98,9 +86,52 @@ impl BuildExecution {
return Ok(()); return Ok(());
} }
pub fn build_file(&mut self, path: &str) -> DockiBuildResult {
let out_path = path.replace(&self.config.input_dir, "./dist");
let convert_out_path = out_path.replace(".adoc", ".html");
if path.starts_with(format!("{}/slides/", &self.config.input_dir).as_str()) && path.ends_with(".adoc") {
if let Err(err) = build_slide(&path, &convert_out_path, self.config.offline_reveal) {
return DockiBuildResult::Err(err);
}
DockiBuildResult::Slide(convert_out_path)
} else if path.ends_with(".adoc") {
if let Err(err) = build_doc(&path, &convert_out_path) {
return DockiBuildResult::Err(err);
}
DockiBuildResult::Doc(convert_out_path)
} else {
if let Err(err) = Self::copy(&path, &out_path) {
return DockiBuildResult::Err(err);
}
DockiBuildResult::Copy(out_path)
}
}
fn copy(in_path: &str, out_path: &str) -> Result<(), String> {
fs_util::create_parent_dir_recursive(out_path);
if let Err(err) = fs::copy(in_path, out_path) {
return Err(err.to_string());
}
Ok(())
}
fn display_building_status(&self, status_type: &str, in_path: &str, out_path: &str) -> () { fn display_building_status(&self, status_type: &str, in_path: &str, out_path: &str) -> () {
let progress_str = format!("{} / {}", self.progress, self.goal); let progress_str = format!("{} / {}", self.progress, self.goal);
display_status(&progress_str, status_type, in_path, out_path); display_status(&progress_str, status_type, in_path, out_path);
} }
} }
pub enum DockiBuildResult {
Slide(String),
Doc(String),
Copy(String),
Err(String),
}

View File

@@ -7,23 +7,35 @@ use notify::{
}; };
use std::{env, path::Path}; use std::{env, path::Path};
use crate::app::{ build::{DockiBuildResult, docki_build}, commands::build::build, config::config::Config, log::display_status, watcher::watcher}; use crate::app::{ commands::executions::build_execution::{self, BuildExecution, DockiBuildResult}, config::config::Config, log::display_status, watcher::watcher};
pub async fn serve(config: &Config) { pub async fn serve(config: &Config) {
build(config).await; let build_execution = BuildExecution::new(config);
tokio::join!(watch_and_build(&config.input_dir), start_server(config.port)); let mut server = Server::new(build_execution, config);
server.serve().await;
} }
async fn watch_and_build(docs_dir: &str) { struct Server<'a> {
watch(Path::new(docs_dir), docs_dir) build_execution: BuildExecution<'a>,
.await config: &'a Config,
.expect("something went wrong")
} }
impl <'a> Server <'a> {
pub fn new(build_execution: BuildExecution<'a>, config: &'a Config) -> Self {
return Self {
build_execution: build_execution,
config: config
}
}
async fn start_server(port: u16) { async fn serve(&mut self) {
let link = &format!("http://localhost:{}", port); self.build_execution.prepare().await.expect("could not prepare for build");
self.build_execution.build_dir().expect("build failed");
tokio::join!(self.start_server(), self.watch_and_build());
}
async fn start_server(&self) {
let link = &format!("http://localhost:{}", self.config.port);
let hyperlink = Green.paint(link).hyperlink(link); let hyperlink = Green.paint(link).hyperlink(link);
println!( println!(
@@ -31,32 +43,40 @@ async fn start_server(port: u16) {
hyperlink hyperlink
); );
let Ok(()) = listen("localhost", port, "./dist").await else { let Ok(()) = listen("localhost", self.config.port, "./dist").await else {
panic!("could not start server") panic!("could not start server")
}; };
} }
async fn watch(path: &Path, docs_dir: &str) -> notify::Result<()> { async fn watch_and_build(&mut self) {
self.watch()
.await
.expect("something went wrong")
}
async fn watch(&mut self) -> notify::Result<()> {
let path = Path::new(&self.config.input_dir);
let (mut watcher, mut rx) = watcher()?; let (mut watcher, mut rx) = watcher()?;
watcher.watch(path.as_ref(), RecursiveMode::Recursive)?; watcher.watch(path.as_ref(), RecursiveMode::Recursive)?;
while let Some(res) = rx.next().await { while let Some(res) = rx.next().await {
let event = res.expect("watching failed"); let event = res.expect("watching failed");
file_change(event, docs_dir) self.file_change(event)
} }
Ok(()) Ok(())
}
fn file_change(event: Event, docs_dir: &str) { }
fn file_change(&mut self, event: Event) {
match event.kind { match event.kind {
EventKind::Modify(ModifyKind::Data(_)) => build_file(event.paths, docs_dir), EventKind::Modify(ModifyKind::Data(_)) => self.build_file(event.paths),
_ => (), _ => (),
} }
} }
fn build_file(paths: Vec<std::path::PathBuf>, docs_dir: &str) { fn build_file(&mut self, paths: Vec<std::path::PathBuf>) {
let invalid_path_message = "changed path is invalid"; let invalid_path_message = "changed path is invalid";
let in_path = paths let in_path = paths
.first() .first()
@@ -67,7 +87,7 @@ fn build_file(paths: Vec<std::path::PathBuf>, docs_dir: &str) {
.expect(invalid_path_message); .expect(invalid_path_message);
let in_path = format!("./{}", in_path); let in_path = format!("./{}", in_path);
let result = docki_build(&in_path, false, docs_dir); let result = self.build_execution.build_file(&in_path);
match result { match result {
DockiBuildResult::Slide(out_path) => display_rebuilding_status("Slide", &in_path, &out_path), DockiBuildResult::Slide(out_path) => display_rebuilding_status("Slide", &in_path, &out_path),
@@ -78,8 +98,14 @@ fn build_file(paths: Vec<std::path::PathBuf>, docs_dir: &str) {
println!("{}", err); println!("{}", err);
}, },
} }
}
} }
fn display_rebuilding_status(context: &str, in_path: &str, out_path: &str) { fn display_rebuilding_status(context: &str, in_path: &str, out_path: &str) {
display_status("Rebuildng", context, in_path, out_path) display_status("Rebuildng", context, in_path, out_path)
} }

View File

@@ -6,7 +6,7 @@ fn github_hyperlink() -> AnsiGenericString<'static, str> {
.bold() .bold()
.underline() .underline()
.paint("https://github.com/quirinecker/docki") .paint("https://github.com/quirinecker/docki")
.hyperlink("https://github.com/quirinecker/docki") .hyperlink("https://github.com/quirinecker/docki");
} }
#[derive(Parser)] #[derive(Parser)]
@@ -16,11 +16,11 @@ pub struct Args {
pub command: CommandArg, pub command: CommandArg,
/// The directory where the documentation is located /// The directory where the documentation is located
#[arg(short, long)] #[arg(short, long, global = true)]
pub input_dir: Option<String>, pub input_dir: Option<String>,
/// The directory where the documentation will be built /// The directory where the documentation will be built
#[arg(short, long)] #[arg(short, long, global = true)]
pub output_dir: Option<String>, pub output_dir: Option<String>,
} }