Compare commits
16 Commits
cbb0dee97a
...
feature/1-
| Author | SHA1 | Date | |
|---|---|---|---|
| c33428f3e1 | |||
| fc98b48601 | |||
| bf30e8eeb6 | |||
| fa9f282194 | |||
| 64400c07c7 | |||
| b3d6d21ea5 | |||
| a0bdc34792 | |||
| 659bb5da63 | |||
| 24e1ade129 | |||
| 88dc0e47b1 | |||
| 0ae02c8950 | |||
| e053f51bf3 | |||
| f3bfd8c114 | |||
| 2ed789aa46 | |||
| 131bf15240 | |||
| fd1fe0d896 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,6 +1,6 @@
|
||||
/target
|
||||
dist
|
||||
/docs
|
||||
res/test/docs/slides/revealjs
|
||||
res/test/asciidocs/slides/revealjs
|
||||
res/test/result
|
||||
result
|
||||
|
||||
@@ -112,6 +112,10 @@ home.packages = with pkgs; [
|
||||
]
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
You can configure some of the cli arguments with a config file. It is always located relatively to the current directory under `./docki.config.toml`. Available options and their defaults can be found in the [config.rs](src/app/config/config.rs) file. CLI arguments with the same names as in the config usually have also the same default values.
|
||||
|
||||
## Development
|
||||
|
||||
### Running it
|
||||
|
||||
16
res/test/asciidocs/index.adoc
Normal file
16
res/test/asciidocs/index.adoc
Normal file
@@ -0,0 +1,16 @@
|
||||
= My Project
|
||||
:toc:
|
||||
:toc-title:
|
||||
:toclevels: 2
|
||||
|
||||
== Introduction
|
||||
|
||||
This is my project.
|
||||
|
||||
== Slides
|
||||
|
||||
* link:slides/index.html[Presentation]
|
||||
|
||||
== License
|
||||
|
||||
This project is licensed under the MIT License - see the link:LICENSE[LICENSE] file for details.
|
||||
19
res/test/asciidocs/slides/index.adoc
Normal file
19
res/test/asciidocs/slides/index.adoc
Normal file
@@ -0,0 +1,19 @@
|
||||
# Slides
|
||||
|
||||
## Slide 1
|
||||
|
||||
* First
|
||||
* Second
|
||||
* Third
|
||||
|
||||
## Slide 2
|
||||
|
||||
* First
|
||||
* Second
|
||||
* Third
|
||||
|
||||
### Slide 3
|
||||
|
||||
* First
|
||||
* Second
|
||||
* Third
|
||||
@@ -1 +1,3 @@
|
||||
docs_dir = "./docs"
|
||||
port = 6969
|
||||
offline_reveal = false
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
= My Project
|
||||
:toc:
|
||||
:toc-title:
|
||||
:toclevels: 2
|
||||
|
||||
== Introduction
|
||||
|
||||
This is my project.
|
||||
|
||||
== Slides
|
||||
|
||||
* link:slides/index.html[Presentation]
|
||||
|
||||
== License
|
||||
|
||||
This project is licensed under the MIT License - see the link:LICENSE[LICENSE] file for details.
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
= My cool presentation
|
||||
:author: John Doe
|
||||
:email: john@doe.com
|
||||
# Slides
|
||||
|
||||
== First slide
|
||||
## Slide 1
|
||||
|
||||
Content
|
||||
* First
|
||||
* Second
|
||||
* Third
|
||||
|
||||
== Second slide
|
||||
## Slide 2
|
||||
|
||||
Content
|
||||
* First
|
||||
* Second
|
||||
* Third
|
||||
|
||||
### Slide 3
|
||||
|
||||
* First
|
||||
* Second
|
||||
* Third
|
||||
|
||||
@@ -12,9 +12,13 @@ fn exec_command(command: &mut process::Command) -> Result<(), String> {
|
||||
}
|
||||
} else {
|
||||
println!("{}", result.unwrap_err());
|
||||
return Err(
|
||||
"asciidoctor not installed. For more information run docki health!".to_string(),
|
||||
);
|
||||
|
||||
let binary_name = command.get_program().to_str().unwrap_or("Something is");
|
||||
|
||||
return Err(format!(
|
||||
"{} not installed. For more information run docki health!",
|
||||
binary_name
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,54 +1 @@
|
||||
use std::fs;
|
||||
|
||||
use self::asciidoctor::{build_doc, build_slide};
|
||||
|
||||
use super::fs_util;
|
||||
|
||||
pub mod asciidoctor;
|
||||
|
||||
pub fn docki_build(in_path: &str, offline_reveal: bool) -> DockiBuildResult {
|
||||
let out_path = in_path.replace("/docs/", "/dist/");
|
||||
let convert_out_path = out_path.replace(".adoc", ".html");
|
||||
|
||||
if in_path.starts_with("./docs/slides/") && 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 in_path.starts_with("./docs/slides/revealjs") && !offline_reveal {
|
||||
return DockiBuildResult::Silent;
|
||||
}
|
||||
|
||||
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),
|
||||
Silent,
|
||||
}
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
use crate::app::config::config::Config;
|
||||
|
||||
use super::executions::build_execution::BuildExecution;
|
||||
|
||||
pub async fn build(offline_reveal: bool) -> () {
|
||||
let mut build_execution = BuildExecution::new();
|
||||
build_execution.execute(offline_reveal).await.expect("build failed")
|
||||
pub async fn build(config: &Config) -> () {
|
||||
let mut build_execution = BuildExecution::new(config);
|
||||
|
||||
build_execution.prepare().await.expect("could not prepare for build");
|
||||
build_execution.build_dir().expect("build failed")
|
||||
}
|
||||
|
||||
@@ -1,52 +1,41 @@
|
||||
use std::{
|
||||
io::Cursor,
|
||||
path::PathBuf
|
||||
fs, io::Cursor, path::PathBuf
|
||||
};
|
||||
|
||||
use crate::app::{
|
||||
build::{docki_build, DockiBuildResult},
|
||||
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,
|
||||
goal: usize,
|
||||
config: &'a Config
|
||||
}
|
||||
|
||||
impl BuildExecution {
|
||||
pub fn new() -> Self {
|
||||
return BuildExecution {
|
||||
impl <'a> BuildExecution <'a> {
|
||||
pub fn new(config: &'a Config) -> Self {
|
||||
return Self {
|
||||
progress: 0,
|
||||
goal: 0,
|
||||
config: config
|
||||
};
|
||||
}
|
||||
|
||||
pub async fn execute(&mut self, offline_reveal: bool) -> Result<(), String> {
|
||||
let path = "./docs/".to_string();
|
||||
|
||||
if !fs_util::directory_exists(&path) {
|
||||
pub async fn prepare(&self) -> Result<(), String> {
|
||||
if !fs_util::directory_exists(&self.config.input_dir) {
|
||||
return Err(
|
||||
"docs directory does not exist it. Create it or use the template".to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
if let Err(error) = Self::prepare(offline_reveal).await {
|
||||
return Err(error);
|
||||
}
|
||||
|
||||
return self.build_dir(&path, offline_reveal);
|
||||
}
|
||||
|
||||
|
||||
async fn prepare(offline_reveal: bool) -> Result<(), String> {
|
||||
if !offline_reveal {
|
||||
if !self.config.offline_reveal {
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
let reveal_version = "5.2.1";
|
||||
let target = format!("https://github.com/hakimel/reveal.js/archive/{reveal_version}.zip");
|
||||
|
||||
create_dir_recursive("./docs/slides");
|
||||
create_dir_recursive(format!("{}/slides", self.config.input_dir).as_str());
|
||||
|
||||
reqwest::get(target.clone()).await.unwrap();
|
||||
let Ok(response) = reqwest::get(target).await else {
|
||||
@@ -57,7 +46,7 @@ impl BuildExecution {
|
||||
return Err("could not extract bytes".to_string())
|
||||
};
|
||||
|
||||
let out = PathBuf::from("./docs/slides/revealjs");
|
||||
let out = PathBuf::from(format!("{}/slides/revealjs", self.config.input_dir));
|
||||
|
||||
if zip_extract::extract(Cursor::new(bytes), &out, true).is_err() {
|
||||
return Err("could not write extracted archive to disk".to_string());
|
||||
@@ -66,36 +55,83 @@ impl BuildExecution {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
fn build_dir(&mut self, path: &str, offline_reveal: bool) -> Result<(), String> {
|
||||
let result = fs_util::fetch_paths_recursive(&path);
|
||||
|
||||
pub fn build_dir(&mut self) -> Result<(), String> {
|
||||
let result = fs_util::fetch_paths_recursive(&self.config.input_dir);
|
||||
let Ok(paths) = result else {
|
||||
return Err(result.unwrap_err())
|
||||
};
|
||||
|
||||
let reveal_dir = format!("{}/slides/revealjs", self.config.input_dir);
|
||||
let paths = paths.into_iter()
|
||||
.filter(|path| self.config.offline_reveal || !path.starts_with(reveal_dir.as_str()))
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
self.goal = paths.len();
|
||||
|
||||
for (index, in_path) in paths.iter().enumerate() {
|
||||
self.progress = index + 1;
|
||||
self.goal = paths.len();
|
||||
let result = docki_build(&in_path, offline_reveal);
|
||||
let result = self.build_file(&in_path);
|
||||
|
||||
match result {
|
||||
DockiBuildResult::Err(err) => {
|
||||
self.display_building_status("Error", in_path, "");
|
||||
self.display_building_status("Error", &in_path, "");
|
||||
println!("{}", err)
|
||||
},
|
||||
DockiBuildResult::Copy(out_path) => self.display_building_status("Copy", &in_path, &out_path),
|
||||
DockiBuildResult::Slide(out_path) => self.display_building_status("Slide", &in_path, &out_path),
|
||||
DockiBuildResult::Doc(out_path) => self.display_building_status("Doc", &in_path, &out_path),
|
||||
DockiBuildResult::Silent => ()
|
||||
}
|
||||
}
|
||||
|
||||
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) -> () {
|
||||
let progress_str = format!("{} / {}", self.progress, self.goal);
|
||||
display_status(&progress_str, status_type, in_path, out_path);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
pub enum DockiBuildResult {
|
||||
Slide(String),
|
||||
Doc(String),
|
||||
Copy(String),
|
||||
Err(String),
|
||||
}
|
||||
|
||||
@@ -7,23 +7,35 @@ use notify::{
|
||||
};
|
||||
use std::{env, path::Path};
|
||||
|
||||
use crate::app::{ watcher::watcher, build::{docki_build, DockiBuildResult}, commands::build::build, log::display_status};
|
||||
use crate::app::{ commands::executions::build_execution::{self, BuildExecution, DockiBuildResult}, config::config::Config, log::display_status, watcher::watcher};
|
||||
|
||||
|
||||
pub async fn serve(port: u16) {
|
||||
build(false).await;
|
||||
tokio::join!(watch_and_build(), start_server(port));
|
||||
pub async fn serve(config: &Config) {
|
||||
let build_execution = BuildExecution::new(config);
|
||||
let mut server = Server::new(build_execution, config);
|
||||
server.serve().await;
|
||||
}
|
||||
|
||||
async fn watch_and_build() {
|
||||
watch(Path::new("./docs"))
|
||||
.await
|
||||
.expect("something went wrong")
|
||||
struct Server<'a> {
|
||||
build_execution: BuildExecution<'a>,
|
||||
config: &'a Config,
|
||||
}
|
||||
|
||||
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) {
|
||||
let link = &format!("http://localhost:{}", port);
|
||||
async fn serve(&mut self) {
|
||||
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);
|
||||
|
||||
println!(
|
||||
@@ -31,32 +43,40 @@ async fn start_server(port: u16) {
|
||||
hyperlink
|
||||
);
|
||||
|
||||
let Ok(()) = listen("localhost", port, "./dist").await else {
|
||||
let Ok(()) = listen("localhost", self.config.port, "./dist").await else {
|
||||
panic!("could not start server")
|
||||
};
|
||||
}
|
||||
|
||||
async fn watch(path: &Path) -> 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()?;
|
||||
|
||||
watcher.watch(path.as_ref(), RecursiveMode::Recursive)?;
|
||||
|
||||
while let Some(res) = rx.next().await {
|
||||
let event = res.expect("watching failed");
|
||||
file_change(event)
|
||||
self.file_change(event)
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
}
|
||||
|
||||
fn file_change(event: Event) {
|
||||
fn file_change(&mut self, event: Event) {
|
||||
match event.kind {
|
||||
EventKind::Modify(ModifyKind::Data(_)) => build_file(event.paths),
|
||||
EventKind::Modify(ModifyKind::Data(_)) => self.build_file(event.paths),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
fn build_file(paths: Vec<std::path::PathBuf>) {
|
||||
fn build_file(&mut self, paths: Vec<std::path::PathBuf>) {
|
||||
let invalid_path_message = "changed path is invalid";
|
||||
let in_path = paths
|
||||
.first()
|
||||
@@ -67,7 +87,7 @@ fn build_file(paths: Vec<std::path::PathBuf>) {
|
||||
.expect(invalid_path_message);
|
||||
|
||||
let in_path = format!("./{}", in_path);
|
||||
let result = docki_build(&in_path, false);
|
||||
let result = self.build_execution.build_file(&in_path);
|
||||
|
||||
match result {
|
||||
DockiBuildResult::Slide(out_path) => display_rebuilding_status("Slide", &in_path, &out_path),
|
||||
@@ -77,9 +97,14 @@ fn build_file(paths: Vec<std::path::PathBuf>) {
|
||||
display_rebuilding_status("Error", &in_path, "");
|
||||
println!("{}", err);
|
||||
},
|
||||
DockiBuildResult::Silent => ()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
fn display_rebuilding_status(context: &str, in_path: &str, out_path: &str) {
|
||||
display_status("Rebuildng", context, in_path, out_path)
|
||||
|
||||
@@ -1,13 +1,27 @@
|
||||
use super::config::Config;
|
||||
use clap::{Parser, Subcommand};
|
||||
use nu_ansi_term::{AnsiGenericString, Style};
|
||||
|
||||
fn github_hyperlink() -> AnsiGenericString<'static, str> {
|
||||
return Style::new()
|
||||
.bold()
|
||||
.underline()
|
||||
.paint("https://github.com/quirinecker/docki")
|
||||
.hyperlink("https://github.com/quirinecker/docki");
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(after_help = format!("More information like defaults can be found at {}", github_hyperlink()))]
|
||||
pub struct Args {
|
||||
#[command(subcommand)]
|
||||
pub command: CommandArg,
|
||||
|
||||
#[arg(short, long)]
|
||||
pub docs_dir: Option<String>,
|
||||
/// The directory where the documentation is located
|
||||
#[arg(short, long, global = true)]
|
||||
pub input_dir: Option<String>,
|
||||
|
||||
/// The directory where the documentation will be built
|
||||
#[arg(short, long, global = true)]
|
||||
pub output_dir: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
|
||||
@@ -6,13 +6,13 @@ use crate::app::config::arguments::CommandArg;
|
||||
#[serde(default)]
|
||||
pub struct Config {
|
||||
pub port: u16,
|
||||
pub docs_dir: String,
|
||||
pub input_dir: String,
|
||||
pub offline_reveal: bool,
|
||||
pub output_dir: String
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn load() -> Result<Self, config::ConfigError> {
|
||||
|
||||
let s = config::Config::builder()
|
||||
.add_source(config::File::with_name("./docki.config.toml"))
|
||||
.build()?;
|
||||
@@ -25,14 +25,15 @@ impl Config {
|
||||
CommandArg::Serve { port } => port.unwrap_or(self.port),
|
||||
_ => self.port,
|
||||
},
|
||||
docs_dir: args.docs_dir.clone().unwrap_or(self.docs_dir),
|
||||
input_dir: args.input_dir.clone().unwrap_or(self.input_dir),
|
||||
output_dir: args.output_dir.clone().unwrap_or(self.output_dir),
|
||||
offline_reveal: {
|
||||
if let CommandArg::Build { offline_reveal } = args.command {
|
||||
offline_reveal
|
||||
} else {
|
||||
self.offline_reveal
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -41,7 +42,8 @@ impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
port: 8080,
|
||||
docs_dir: "./docs".to_string(),
|
||||
input_dir: "./docs".to_string(),
|
||||
output_dir: "./dist".to_string(),
|
||||
offline_reveal: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,10 +26,10 @@ impl App {
|
||||
Self::setup_environment_variables();
|
||||
|
||||
match args.command {
|
||||
CommandArg::Build { .. } => build(config.offline_reveal).await,
|
||||
CommandArg::Build { .. } => build(&config).await,
|
||||
CommandArg::Health => health(),
|
||||
CommandArg::InstallReveal => install_reveal().await,
|
||||
CommandArg::Serve { .. } => serve(config.port).await,
|
||||
CommandArg::Serve { .. } => serve(&config).await,
|
||||
CommandArg::Completions { shell } => completions(shell)
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user