Compare commits
29 Commits
4-make-ser
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 1bb50c09bf | |||
| 1995e5310d | |||
| 379a337611 | |||
| bc3283eb29 | |||
| 89c4d48c1c | |||
| bf5782fc5e | |||
| b0617efe93 | |||
| f4d91aabe6 | |||
| 904d9ccb28 | |||
| 1f2d9e4363 | |||
| 3ae1b2cc6e | |||
| f6940f1db8 | |||
| c33428f3e1 | |||
| fc98b48601 | |||
| bf30e8eeb6 | |||
| fa9f282194 | |||
| 64400c07c7 | |||
| b3d6d21ea5 | |||
| a0bdc34792 | |||
| 659bb5da63 | |||
| 24e1ade129 | |||
| 88dc0e47b1 | |||
| 0ae02c8950 | |||
| e053f51bf3 | |||
| f3bfd8c114 | |||
| 2ed789aa46 | |||
| 131bf15240 | |||
| fd1fe0d896 | |||
| 5996db8d80 |
BIN
.github/assets/directory_structure.png
vendored
Normal file
BIN
.github/assets/directory_structure.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 181 KiB |
4
.github/workflows/rust_deploy.yml
vendored
4
.github/workflows/rust_deploy.yml
vendored
@@ -4,7 +4,7 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches: ["main"]
|
branches: ["main"]
|
||||||
paths:
|
paths:
|
||||||
- "Cargo.toml"
|
- "./Cargo.toml"
|
||||||
|
|
||||||
env:
|
env:
|
||||||
CARGO_TERM_COLOR: always
|
CARGO_TERM_COLOR: always
|
||||||
@@ -16,7 +16,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Build
|
- name: Build
|
||||||
run: cargo build --verbose
|
run: cargo build --release --bin docki
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: cargo test --verbose
|
run: cargo test --verbose
|
||||||
- name: Publish to crates.io
|
- name: Publish to crates.io
|
||||||
|
|||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,6 +1,6 @@
|
|||||||
/target
|
/target
|
||||||
dist
|
dist
|
||||||
/docs
|
|
||||||
res/test/docs/slides/revealjs
|
res/test/docs/slides/revealjs
|
||||||
|
res/test/asciidocs/slides/revealjs
|
||||||
res/test/result
|
res/test/result
|
||||||
result
|
result
|
||||||
|
|||||||
2954
Cargo.lock
generated
2954
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
14
Cargo.toml
14
Cargo.toml
@@ -1,11 +1,20 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "docki"
|
name = "docki"
|
||||||
version = "1.2.3"
|
version = "1.3.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "cli for building and publishing documentation using asciidoctor"
|
description = "cli for building and publishing documentation using asciidoctor"
|
||||||
license-file = "LICENSE.txt"
|
license-file = "LICENSE.txt"
|
||||||
authors = ["Quirin Ecker"]
|
authors = ["Quirin Ecker"]
|
||||||
exclude = [".gitlab", ".github"]
|
exclude = [".gitlab", ".github"]
|
||||||
|
default-run = "docki"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "docki"
|
||||||
|
path = "src/main.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "util"
|
||||||
|
path = "src/util.rs"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
@@ -23,3 +32,6 @@ tokio = { version = "1.26.0", features = ["full"] }
|
|||||||
zip-extract = "0.1.1"
|
zip-extract = "0.1.1"
|
||||||
clap_complete = "4.1.4"
|
clap_complete = "4.1.4"
|
||||||
nu-ansi-term = "0.50.3"
|
nu-ansi-term = "0.50.3"
|
||||||
|
config = { version = "0.15.18", features = ["toml"] }
|
||||||
|
serde = "1.0.228"
|
||||||
|
toml = "0.9.6"
|
||||||
|
|||||||
19
README.md
19
README.md
@@ -1,9 +1,5 @@
|
|||||||
# Docki
|
# Docki
|
||||||
|
|
||||||
> [!NOTE]
|
|
||||||
> This project is under renovations. It can be used, but it might not be completely stable yet.
|
|
||||||
|
|
||||||
|
|
||||||
## Preview
|
## Preview
|
||||||
|
|
||||||

|

|
||||||
@@ -12,8 +8,15 @@ Docki is cli for converting asciidoctor files into html files.
|
|||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
|
Docki must be run in a directory with the following structure
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
1. Will be converted into asciidoc format
|
||||||
|
2. Will be converted into asciidocs slides
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
> This works in a directory with a directory called `docs`, which contains the asciidoctor documentation.
|
> The input directory can be defined either with a cli argument or the config file. Default is `./docs`
|
||||||
|
|
||||||
### Building the documentation
|
### Building the documentation
|
||||||
|
|
||||||
@@ -21,7 +24,7 @@ Docki is cli for converting asciidoctor files into html files.
|
|||||||
docki build
|
docki build
|
||||||
```
|
```
|
||||||
|
|
||||||
### Serving the documentation on a live server
|
### Serving the documentation with a live server
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
docki serve
|
docki serve
|
||||||
@@ -112,6 +115,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 [docki.config.toml](config/docki.config.toml) file. CLI arguments with the same names as in the config usually have also the same default values. As a fallback you can also use the [config.rs](src/app/config/config.rs) file for reference.
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
### Running it
|
### Running it
|
||||||
|
|||||||
4
config/docki.config.toml
Normal file
4
config/docki.config.toml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
port = 8080
|
||||||
|
input_dir = "./docs"
|
||||||
|
offline_reveal = false
|
||||||
|
output_dir = "./dist"
|
||||||
@@ -64,6 +64,11 @@
|
|||||||
libiconv
|
libiconv
|
||||||
makeWrapper
|
makeWrapper
|
||||||
];
|
];
|
||||||
|
binaries = [ "docki" ];
|
||||||
|
# only including the main binary in build
|
||||||
|
# There is no better option at the time of writing this
|
||||||
|
# https://github.com/nix-community/naersk/issues/127
|
||||||
|
copyBinsFilter = ''select(.reason == "compiler-artifact" and .executable != null and .profile.test == false and .target.name == "docki")'';
|
||||||
postInstall = ''
|
postInstall = ''
|
||||||
mkdir -p $out/share/bash-completion/completions
|
mkdir -p $out/share/bash-completion/completions
|
||||||
mkdir -p $out/share/zsh/site-functions
|
mkdir -p $out/share/zsh/site-functions
|
||||||
|
|||||||
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
|
||||||
3
res/test/docki.config.toml
Normal file
3
res/test/docki.config.toml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
docs_dir = "./asciidocs"
|
||||||
|
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
|
# Slides
|
||||||
:author: John Doe
|
|
||||||
:email: john@doe.com
|
|
||||||
|
|
||||||
== First slide
|
## Slide 1
|
||||||
|
|
||||||
Content
|
* First
|
||||||
|
* Second
|
||||||
|
* Third
|
||||||
|
|
||||||
== Second slide
|
## Slide 2
|
||||||
|
|
||||||
Content
|
* First
|
||||||
|
* Second
|
||||||
|
* Third
|
||||||
|
|
||||||
|
### Slide 3
|
||||||
|
|
||||||
|
* First
|
||||||
|
* Second
|
||||||
|
* Third
|
||||||
|
|||||||
@@ -1,40 +0,0 @@
|
|||||||
use clap::{Parser, Subcommand};
|
|
||||||
|
|
||||||
#[derive(Parser)]
|
|
||||||
pub struct Args {
|
|
||||||
#[command(subcommand)]
|
|
||||||
pub command: CommandArg,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Subcommand)]
|
|
||||||
pub enum ShellArg {
|
|
||||||
Bash,
|
|
||||||
Fish,
|
|
||||||
Zsh,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Subcommand)]
|
|
||||||
pub enum CommandArg {
|
|
||||||
/// Builds the documentation into a dist folder
|
|
||||||
Build {
|
|
||||||
/// When set to true, docki will download revealjs before building the documentation.
|
|
||||||
/// Otherwise it will use the cdn for revealjs
|
|
||||||
#[arg(short, long)]
|
|
||||||
offline_reveal: bool,
|
|
||||||
},
|
|
||||||
/// Checks if everything required for docki is installed
|
|
||||||
Health,
|
|
||||||
/// Deprecated: Helper command for installing asciidoctor-reveal-js
|
|
||||||
InstallReveal,
|
|
||||||
/// Starts a Webserver with the live preview of the Documentation
|
|
||||||
Serve {
|
|
||||||
/// Port for the Live Server
|
|
||||||
#[arg(short, long)]
|
|
||||||
port: Option<u16>,
|
|
||||||
},
|
|
||||||
/// Generates completions for the desired shell
|
|
||||||
Completions {
|
|
||||||
#[command(subcommand)]
|
|
||||||
shell: ShellArg,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use std::process;
|
use std::process;
|
||||||
|
|
||||||
|
use crate::app::config::config::Config;
|
||||||
|
|
||||||
fn exec_command(command: &mut process::Command) -> Result<(), String> {
|
fn exec_command(command: &mut process::Command) -> Result<(), String> {
|
||||||
let result = command.output();
|
let result = command.output();
|
||||||
|
|
||||||
@@ -12,9 +14,13 @@ fn exec_command(command: &mut process::Command) -> Result<(), String> {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
println!("{}", result.unwrap_err());
|
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
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,14 +34,17 @@ fn asciidoctor_docs(in_path: &str, out_path: &str) -> process::Command {
|
|||||||
return command;
|
return command;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn asciidoctor_slides(in_path: &str, out_path: &str, offline_reveal: bool) -> process::Command {
|
fn asciidoctor_slides(in_path: &str, out_path: &str, config: &Config) -> process::Command {
|
||||||
let mut command = process::Command::new(format!("asciidoctor-revealjs"));
|
let mut command = process::Command::new(format!("asciidoctor-revealjs"));
|
||||||
let out_dir = parent_path(out_path);
|
let out_dir = parent_path(out_path);
|
||||||
let revealjs_path = if offline_reveal {
|
let revealjs_path = if config.offline_reveal {
|
||||||
path_between(out_dir.to_string(), "./dist/slides/revealjs".to_string())
|
path_between(
|
||||||
} else {
|
out_dir.to_string(),
|
||||||
"https://cdn.jsdelivr.net/npm/reveal.js@5.2.1".to_string()
|
format!("{}/slides/revealjs", config.output_dir),
|
||||||
};
|
)
|
||||||
|
} else {
|
||||||
|
"https://cdn.jsdelivr.net/npm/reveal.js@5.2.1".to_string()
|
||||||
|
};
|
||||||
|
|
||||||
command
|
command
|
||||||
.arg(format!("{in_path}"))
|
.arg(format!("{in_path}"))
|
||||||
@@ -100,13 +109,13 @@ pub fn matching_from_start(from_segments: &Vec<String>, to_segments: &Vec<String
|
|||||||
return from_segments.len();
|
return from_segments.len();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build_doc(in_path: &str, out_path: &str) -> Result<(), String> {
|
pub fn build_doc(in_path: &str, out_path: &str, _: &Config) -> Result<(), String> {
|
||||||
let mut command = asciidoctor_docs(in_path, out_path);
|
let mut command = asciidoctor_docs(in_path, out_path);
|
||||||
return exec_command(&mut command);
|
return exec_command(&mut command);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build_slide(in_path: &str, out_path: &str, offline_reveal: bool) -> Result<(), String> {
|
pub fn build_slide(in_path: &str, out_path: &str, config: &Config) -> Result<(), String> {
|
||||||
let mut command = asciidoctor_slides(in_path, out_path, offline_reveal);
|
let mut command = asciidoctor_slides(in_path, out_path, config);
|
||||||
return exec_command(&mut command);
|
return exec_command(&mut command);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,54 +1,150 @@
|
|||||||
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) -> DockiBuildResult {
|
use std::{
|
||||||
let out_path = in_path.replace("/docs/", "/dist/");
|
fs, io::Cursor, path::PathBuf
|
||||||
let convert_out_path = out_path.replace(".adoc", ".html");
|
};
|
||||||
|
|
||||||
if in_path.starts_with("./docs/slides/") && in_path.ends_with(".adoc") {
|
use crate::app::{
|
||||||
if let Err(err) = build_slide(&in_path, &convert_out_path, offline_reveal) {
|
build::{asciidoctor::build_slide, asciidoctor::build_doc}, config::config::Config, fs_util::{self, create_dir_recursive}, log::display_status
|
||||||
return DockiBuildResult::Err(err);
|
};
|
||||||
|
|
||||||
|
pub struct DockiBuilder<'a> {
|
||||||
|
progress: usize,
|
||||||
|
goal: usize,
|
||||||
|
config: &'a Config
|
||||||
|
}
|
||||||
|
|
||||||
|
impl <'a> DockiBuilder <'a> {
|
||||||
|
pub fn new(config: &'a Config) -> Self {
|
||||||
|
return Self {
|
||||||
|
progress: 0,
|
||||||
|
goal: 0,
|
||||||
|
config: config
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Prepares everything for building the documentation
|
||||||
|
///
|
||||||
|
/// 1. Checks if the input directory exists and if not, returns an error
|
||||||
|
/// 2. When offline_reveal is set to true, it downloads revealjs. When it fails, it returns an error
|
||||||
|
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(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
DockiBuildResult::Slide(convert_out_path)
|
if !self.config.offline_reveal {
|
||||||
} else if in_path.ends_with(".adoc") {
|
return Ok(())
|
||||||
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) {
|
let reveal_version = "5.2.1";
|
||||||
return DockiBuildResult::Err(err);
|
let target = format!("https://github.com/hakimel/reveal.js/archive/{reveal_version}.zip");
|
||||||
|
|
||||||
|
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 {
|
||||||
|
return Err("could not downlaod revealjs".to_string())
|
||||||
|
};
|
||||||
|
|
||||||
|
let Ok(bytes) = response.bytes().await else {
|
||||||
|
return Err("could not extract bytes".to_string())
|
||||||
|
};
|
||||||
|
|
||||||
|
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());
|
||||||
}
|
}
|
||||||
|
|
||||||
DockiBuildResult::Copy(out_path)
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fn copy(in_path: &str, out_path: &str) -> Result<(), String> {
|
/// Builds all files in the input directory with a pretty output
|
||||||
fs_util::create_parent_dir_recursive(out_path);
|
pub fn build_docs(&mut self) -> Result<(), String> {
|
||||||
|
let result = fs_util::fetch_paths_recursive(&self.config.input_dir);
|
||||||
if let Err(err) = fs::copy(in_path, out_path) {
|
let Ok(paths) = result else {
|
||||||
return Err(err.to_string());
|
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;
|
||||||
|
let result = self.build_file(&in_path);
|
||||||
|
|
||||||
|
match result {
|
||||||
|
DockiBuildResult::Err(err) => {
|
||||||
|
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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Builds a single file without a pretty output
|
||||||
|
pub fn build_file(&self, path: &str) -> DockiBuildResult {
|
||||||
|
let out_path = path.replace(&self.config.input_dir, &self.config.output_dir);
|
||||||
|
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) {
|
||||||
|
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, self.config) {
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Used for the Result of build_file. This way it is known what the builder did
|
||||||
pub enum DockiBuildResult {
|
pub enum DockiBuildResult {
|
||||||
Slide(String),
|
Slide(String),
|
||||||
Doc(String),
|
Doc(String),
|
||||||
Copy(String),
|
Copy(String),
|
||||||
Err(String),
|
Err(String),
|
||||||
Silent,
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
use super::executions::build_execution::BuildExecution;
|
use crate::app::config::config::Config;
|
||||||
|
|
||||||
pub async fn build(offline_reveal: bool) -> () {
|
use crate::app::build::DockiBuilder;
|
||||||
let mut build_execution = BuildExecution::new();
|
|
||||||
build_execution.execute(offline_reveal).await.expect("build failed")
|
pub async fn build(config: &Config) -> () {
|
||||||
|
let mut builder = DockiBuilder::new(config);
|
||||||
|
|
||||||
|
builder.prepare().await.expect("could not prepare for build");
|
||||||
|
builder.build_docs().expect("build failed")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use std::io;
|
|||||||
use clap::CommandFactory;
|
use clap::CommandFactory;
|
||||||
use clap_complete::{generate, shells::{Bash, Fish, Zsh}};
|
use clap_complete::{generate, shells::{Bash, Fish, Zsh}};
|
||||||
|
|
||||||
use crate::app::args::structure::{Args, ShellArg};
|
use crate::app::config::arguments::{Args, ShellArg};
|
||||||
|
|
||||||
pub fn completions(shell: ShellArg) {
|
pub fn completions(shell: ShellArg) {
|
||||||
let mut command = Args::command();
|
let mut command = Args::command();
|
||||||
|
|||||||
@@ -1,101 +0,0 @@
|
|||||||
use std::{
|
|
||||||
io::Cursor,
|
|
||||||
path::PathBuf
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::app::{
|
|
||||||
build::{docki_build, DockiBuildResult},
|
|
||||||
fs_util::{self, create_dir_recursive}, log::display_status,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct BuildExecution {
|
|
||||||
progress: usize,
|
|
||||||
goal: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BuildExecution {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
return BuildExecution {
|
|
||||||
progress: 0,
|
|
||||||
goal: 0,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn execute(&mut self, offline_reveal: bool) -> Result<(), String> {
|
|
||||||
let path = "./docs/".to_string();
|
|
||||||
|
|
||||||
if !fs_util::directory_exists(&path) {
|
|
||||||
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 {
|
|
||||||
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");
|
|
||||||
|
|
||||||
reqwest::get(target.clone()).await.unwrap();
|
|
||||||
let Ok(response) = reqwest::get(target).await else {
|
|
||||||
return Err("could not downlaod revealjs".to_string())
|
|
||||||
};
|
|
||||||
|
|
||||||
let Ok(bytes) = response.bytes().await else {
|
|
||||||
return Err("could not extract bytes".to_string())
|
|
||||||
};
|
|
||||||
|
|
||||||
let out = PathBuf::from("./docs/slides/revealjs");
|
|
||||||
|
|
||||||
if zip_extract::extract(Cursor::new(bytes), &out, true).is_err() {
|
|
||||||
return Err("could not write extracted archive to disk".to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build_dir(&mut self, path: &str, offline_reveal: bool) -> Result<(), String> {
|
|
||||||
let result = fs_util::fetch_paths_recursive(&path);
|
|
||||||
|
|
||||||
let Ok(paths) = result else {
|
|
||||||
return Err(result.unwrap_err())
|
|
||||||
};
|
|
||||||
|
|
||||||
for (index, in_path) in paths.iter().enumerate() {
|
|
||||||
self.progress = index + 1;
|
|
||||||
self.goal = paths.len();
|
|
||||||
let result = docki_build(&in_path, offline_reveal);
|
|
||||||
|
|
||||||
match result {
|
|
||||||
DockiBuildResult::Err(err) => {
|
|
||||||
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(());
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
pub mod build_execution;
|
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
pub mod build;
|
pub mod build;
|
||||||
pub mod completions;
|
pub mod completions;
|
||||||
pub mod executions;
|
|
||||||
pub mod health;
|
pub mod health;
|
||||||
pub mod install_reveal;
|
pub mod install_reveal;
|
||||||
pub mod serve;
|
pub mod serve;
|
||||||
|
|||||||
@@ -7,92 +7,115 @@ use notify::{
|
|||||||
};
|
};
|
||||||
use std::{env, path::Path};
|
use std::{env, path::Path};
|
||||||
|
|
||||||
use crate::app::{ watcher::watcher, build::{docki_build, DockiBuildResult}, commands::build::build, log::display_status};
|
use crate::app::watcher::watcher;
|
||||||
|
use crate::app::log::display_status;
|
||||||
|
use crate::app::config::config::Config;
|
||||||
|
use crate::app::build::{DockiBuildResult, DockiBuilder};
|
||||||
|
|
||||||
|
pub async fn serve(config: &Config) {
|
||||||
pub async fn serve(port: Option<u16>) {
|
let builder = DockiBuilder::new(config);
|
||||||
build(false).await;
|
let mut server = Server::new(builder, config);
|
||||||
tokio::join!(watch_and_build(), start_server(port));
|
server.serve().await;
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn watch_and_build() {
|
struct Server<'a> {
|
||||||
watch(Path::new("./docs"))
|
builder: DockiBuilder<'a>,
|
||||||
.await
|
config: &'a Config,
|
||||||
.expect("something went wrong")
|
}
|
||||||
|
|
||||||
|
impl <'a> Server <'a> {
|
||||||
|
fn new(builder: DockiBuilder<'a>, config: &'a Config) -> Self {
|
||||||
|
return Self {
|
||||||
|
builder,
|
||||||
|
config: config
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn serve(&mut self) {
|
||||||
|
self.builder.prepare().await.expect("could not prepare for build");
|
||||||
|
self.builder.build_docs().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!(
|
||||||
|
"\nServing at {}",
|
||||||
|
hyperlink
|
||||||
|
);
|
||||||
|
|
||||||
|
let Ok(()) = listen("localhost", self.config.port, self.config.output_dir.clone()).await else {
|
||||||
|
panic!("could not start server")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn watch_and_build(&self) {
|
||||||
|
self.watch()
|
||||||
|
.await
|
||||||
|
.expect("something went wrong")
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn watch(&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");
|
||||||
|
self.file_change(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fn file_change(&self, event: Event) {
|
||||||
|
match event.kind {
|
||||||
|
EventKind::Modify(ModifyKind::Data(_)) => self.build_valid_files(event.paths),
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_valid_files(&self, paths: Vec<std::path::PathBuf>) {
|
||||||
|
let invalid_path_message = "changed path is invalid";
|
||||||
|
let in_path = paths
|
||||||
|
.first()
|
||||||
|
.expect(invalid_path_message)
|
||||||
|
.strip_prefix(&Self::current_dir())
|
||||||
|
.expect(invalid_path_message)
|
||||||
|
.to_str()
|
||||||
|
.expect(invalid_path_message);
|
||||||
|
|
||||||
|
let in_path = format!("./{}", in_path);
|
||||||
|
let result = self.builder.build_file(&in_path);
|
||||||
|
|
||||||
|
match result {
|
||||||
|
DockiBuildResult::Slide(out_path) => Self::display_rebuilding_status("Slide", &in_path, &out_path),
|
||||||
|
DockiBuildResult::Doc(out_path) => Self::display_rebuilding_status("Doc", &in_path, &out_path),
|
||||||
|
DockiBuildResult::Copy(out_path) => Self::display_rebuilding_status("Copy", &in_path, &out_path),
|
||||||
|
DockiBuildResult::Err(err) => {
|
||||||
|
Self::display_rebuilding_status("Error", &in_path, "");
|
||||||
|
println!("{}", err);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display_rebuilding_status(context: &str, in_path: &str, out_path: &str) {
|
||||||
|
display_status("Rebuildng", context, in_path, out_path)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn current_dir() -> String {
|
||||||
|
let err_message = "something went wrong";
|
||||||
|
return String::from(
|
||||||
|
env::current_dir()
|
||||||
|
.expect(err_message)
|
||||||
|
.to_str()
|
||||||
|
.expect(err_message),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async fn start_server(port: Option<u16>) {
|
|
||||||
let port = port.unwrap_or(8080);
|
|
||||||
let link = &format!("http://localhost:{}", port);
|
|
||||||
let hyperlink = Green.paint(link).hyperlink(link);
|
|
||||||
|
|
||||||
println!(
|
|
||||||
"\nServing at {}",
|
|
||||||
hyperlink
|
|
||||||
);
|
|
||||||
|
|
||||||
let Ok(()) = listen("localhost", port, "./dist").await else {
|
|
||||||
panic!("could not start server")
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn watch(path: &Path) -> notify::Result<()> {
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn file_change(event: Event) {
|
|
||||||
match event.kind {
|
|
||||||
EventKind::Modify(ModifyKind::Data(_)) => build_file(event.paths),
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build_file(paths: Vec<std::path::PathBuf>) {
|
|
||||||
let invalid_path_message = "changed path is invalid";
|
|
||||||
let in_path = paths
|
|
||||||
.first()
|
|
||||||
.expect(invalid_path_message)
|
|
||||||
.strip_prefix(¤t_dir())
|
|
||||||
.expect(invalid_path_message)
|
|
||||||
.to_str()
|
|
||||||
.expect(invalid_path_message);
|
|
||||||
|
|
||||||
let in_path = format!("./{}", in_path);
|
|
||||||
let result = docki_build(&in_path, false);
|
|
||||||
|
|
||||||
match result {
|
|
||||||
DockiBuildResult::Slide(out_path) => display_rebuilding_status("Slide", &in_path, &out_path),
|
|
||||||
DockiBuildResult::Doc(out_path) => display_rebuilding_status("Doc", &in_path, &out_path),
|
|
||||||
DockiBuildResult::Copy(out_path) => display_rebuilding_status("Copy", &in_path, &out_path),
|
|
||||||
DockiBuildResult::Err(err) => {
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn current_dir() -> String {
|
|
||||||
let err_message = "something went wrong";
|
|
||||||
return String::from(
|
|
||||||
env::current_dir()
|
|
||||||
.expect(err_message)
|
|
||||||
.to_str()
|
|
||||||
.expect(err_message),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
60
src/app/config/arguments.rs
Normal file
60
src/app/config/arguments.rs
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
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,
|
||||||
|
|
||||||
|
/// 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>,
|
||||||
|
|
||||||
|
/// When set to true, docki will download revealjs before building the documentation.
|
||||||
|
/// Otherwise it will use the cdn for revealjs
|
||||||
|
#[arg(long, global = true)]
|
||||||
|
pub offline_reveal: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Subcommand)]
|
||||||
|
pub enum ShellArg {
|
||||||
|
Bash,
|
||||||
|
Fish,
|
||||||
|
Zsh,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Subcommand)]
|
||||||
|
pub enum CommandArg {
|
||||||
|
/// Builds the documentation into the specified output_dir
|
||||||
|
Build,
|
||||||
|
/// Checks if everything required for docki is installed
|
||||||
|
Health,
|
||||||
|
/// Deprecated: Helper command for installing asciidoctor-reveal-js
|
||||||
|
InstallReveal,
|
||||||
|
/// Starts a Webserver with the live preview of the Documentation
|
||||||
|
Serve {
|
||||||
|
/// Port for the Live Server
|
||||||
|
#[arg(short, long)]
|
||||||
|
port: Option<u16>,
|
||||||
|
},
|
||||||
|
/// Generates completions for the desired shell
|
||||||
|
Completions {
|
||||||
|
#[command(subcommand)]
|
||||||
|
shell: ShellArg,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Args {}
|
||||||
44
src/app/config/config.rs
Normal file
44
src/app/config/config.rs
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::app::config::arguments::CommandArg;
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug, Serialize)]
|
||||||
|
#[serde(default)]
|
||||||
|
pub struct Config {
|
||||||
|
pub port: u16,
|
||||||
|
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()?;
|
||||||
|
s.try_deserialize()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn merge_with_args(self, args: &super::arguments::Args) -> Self {
|
||||||
|
Self {
|
||||||
|
port: match args.command {
|
||||||
|
CommandArg::Serve { port } => port.unwrap_or(self.port),
|
||||||
|
_ => self.port,
|
||||||
|
},
|
||||||
|
input_dir: args.input_dir.clone().unwrap_or(self.input_dir),
|
||||||
|
output_dir: args.output_dir.clone().unwrap_or(self.output_dir),
|
||||||
|
offline_reveal: args.offline_reveal || self.offline_reveal,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Config {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
port: 8080,
|
||||||
|
input_dir: "./docs".to_string(),
|
||||||
|
output_dir: "./dist".to_string(),
|
||||||
|
offline_reveal: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
|
||||||
use self::structure::Args;
|
use self::arguments::Args;
|
||||||
|
|
||||||
pub mod structure;
|
pub mod arguments;
|
||||||
|
pub mod config;
|
||||||
|
|
||||||
pub fn args() -> Args {
|
pub fn args() -> Args {
|
||||||
return Args::parse();
|
return Args::parse();
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -3,11 +3,13 @@ pub mod build;
|
|||||||
pub mod fs_util;
|
pub mod fs_util;
|
||||||
pub mod watcher;
|
pub mod watcher;
|
||||||
pub mod log;
|
pub mod log;
|
||||||
mod args;
|
pub mod config;
|
||||||
|
|
||||||
use std::env;
|
use std::env;
|
||||||
|
|
||||||
use self::args::{args, structure::CommandArg};
|
use crate::app::config::config::Config;
|
||||||
|
|
||||||
|
use self::config::{args, arguments::CommandArg};
|
||||||
use self::commands::build::build;
|
use self::commands::build::build;
|
||||||
use self::commands::completions::completions;
|
use self::commands::completions::completions;
|
||||||
use self::commands::health::health;
|
use self::commands::health::health;
|
||||||
@@ -20,13 +22,14 @@ impl App {
|
|||||||
|
|
||||||
pub async fn start(&self) {
|
pub async fn start(&self) {
|
||||||
let args = args();
|
let args = args();
|
||||||
|
let config = Config::load().unwrap_or(Config::default()).merge_with_args(&args);
|
||||||
Self::setup_environment_variables();
|
Self::setup_environment_variables();
|
||||||
|
|
||||||
match args.command {
|
match args.command {
|
||||||
CommandArg::Build { offline_reveal } => build(offline_reveal).await,
|
CommandArg::Build { .. } => build(&config).await,
|
||||||
CommandArg::Health => health(),
|
CommandArg::Health => health(),
|
||||||
CommandArg::InstallReveal => install_reveal().await,
|
CommandArg::InstallReveal => install_reveal().await,
|
||||||
CommandArg::Serve { port } => serve(port).await,
|
CommandArg::Serve { .. } => serve(&config).await,
|
||||||
CommandArg::Completions { shell } => completions(shell)
|
CommandArg::Completions { shell } => completions(shell)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,5 +5,5 @@ fn test_fetch_asciidoctor_paths_recursive() {
|
|||||||
let paths = fs_util::fetch_paths_recursive("res/test/docs").unwrap();
|
let paths = fs_util::fetch_paths_recursive("res/test/docs").unwrap();
|
||||||
let len = paths.len();
|
let len = paths.len();
|
||||||
dbg!(paths);
|
dbg!(paths);
|
||||||
assert_eq!(len, 6);
|
assert_eq!(len, 2);
|
||||||
}
|
}
|
||||||
|
|||||||
34
src/util.rs
Normal file
34
src/util.rs
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
mod app;
|
||||||
|
|
||||||
|
use std::{fs::File, io::Write};
|
||||||
|
|
||||||
|
use app::config::config::Config;
|
||||||
|
use clap::{Parser, Subcommand};
|
||||||
|
|
||||||
|
#[derive(Parser)]
|
||||||
|
pub struct Args {
|
||||||
|
#[command(subcommand)]
|
||||||
|
pub command: CommandArg,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Subcommand)]
|
||||||
|
pub enum CommandArg {
|
||||||
|
/// Generates a default docki.config.toml
|
||||||
|
GenerateDefaultConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let args = Args::parse();
|
||||||
|
match args.command {
|
||||||
|
CommandArg::GenerateDefaultConfig => generate_default_config(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_default_config() {
|
||||||
|
let default_config = Config::default();
|
||||||
|
let target_file = "config/docki.config.toml";
|
||||||
|
let mut file = File::create(target_file).unwrap();
|
||||||
|
let output = toml::to_string_pretty(&default_config).unwrap();
|
||||||
|
|
||||||
|
file.write_all(output.as_bytes()).unwrap();
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user