This commit is contained in:
aitzol 2025-08-16 17:20:25 +02:00
parent 1a7b0352eb
commit 9209299af6
6 changed files with 324 additions and 9 deletions

297
7 Normal file
View File

@ -0,0 +1,297 @@
use std::io;
use std::fs;
use std::path::Path;
use std::env;
use walkdir::WalkDir;
use std::process::Command;
use std::ffi::OsStr;
use colorize::AnsiColor;
use dirs;
use pandoc::PandocError;
use dokugile::template;
use rust_i18n::t;
use include_dir::{include_dir, Dir};
rust_i18n::i18n!("locales");
struct Project {
wiki_path: String,
doc_path: String,
doc_title: String,
output_dir: String,
}
impl Project {
fn new(wiki_path: String, doc_path: String, doc_title:String, output_dir: String) -> Project {
Project {
wiki_path,
doc_path,
doc_title,
output_dir,
}
}
}
fn main() {
const NAME: &str = env!("CARGO_PKG_NAME");
const VERSION: &str = env!("CARGO_PKG_VERSION");
const REPO: &str = env!("CARGO_PKG_REPOSITORY");
println!("{} {}\n{}\n",NAME.b_magenta(), VERSION.b_magenta(), REPO.b_blue());
static ASSETS_DIR: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/assets");
let _ = set_lang();
let mut wiki_path = String::new();
let mut doc_path = String::new();
let mut doc_title = String::new();
let mut output_dir = String::new();
let mut attempts: i8 = 0;
while !Path::new(&doc_path).exists() || doc_title.trim().is_empty(){
doc_path = String::new();
let mut input = String::new();
println!("{}:", t!("Project name"));
io::stdin().read_line(&mut input).expect(&t!("Failed to read line"));
let root = env::var("HOME").unwrap().to_string()+"/";
let user_documents_path = dirs::document_dir().unwrap().display().to_string();
let user_doc_dir = user_documents_path.split("/").collect::<Vec<_>>().last().unwrap().to_string();
while !Path::new(&wiki_path).exists() {
let mut project_dir = String::new();
println!("{}:[{}/Wiki] ", t!("Project location"), &user_doc_dir);
io::stdin().read_line(&mut project_dir).expect(&t!("Failed to read line"));
if project_dir.trim().is_empty() { project_dir = String::from(user_doc_dir.clone()+"/Wiki") };
wiki_path = root.clone()+project_dir.trim();
if !Path::new(&wiki_path).exists(){
let mut ans = String::new();
println!("{}", t!("Do you want to create a new location called %{s} for the documents?(y/n)[Y]", s = &project_dir.trim().to_owned().green().bold()));
io::stdin().read_line(&mut ans).expect("Failed to read line");
if ans.trim().is_empty() { ans = String::from("Y") };
match ans.trim() {
"y" | "yes" | "Y" | "B" | "Bai" => { //let _ = fs::create_dir(&wiki_path);
let _ = mkdir(&wiki_path);
println!("{}.", t!("Location %{s} has been created", s = &project_dir.trim().to_owned().green().bold()));
},
_ => continue,
};
}
}
if input.trim().is_empty(){
attempts += 1;
}
if attempts == 3 {
//break
let cwd = env::current_dir().unwrap().display().to_string();
let dir_name: Vec<&str> = cwd.split('/').collect();
if input.trim().is_empty() { input = String::from(dir_name.clone()) };
};
doc_title = input.to_string();
output_dir = input.trim_end().to_owned()+"_html";
doc_path.push_str(&wiki_path);
doc_path.push_str("/");
doc_path.push_str(&doc_title.trim_end());
//doc_path.push_str(&capitalize_first_letter(&doc_title.trim_end()));
let res_path = wiki_path.clone()+"/res";
if !Path::new(&doc_path).exists() || !Path::new(&res_path).exists(){
// extract assets to temporary directory
let _ = ASSETS_DIR.extract("/tmp/assets");
let mut ans = String::new();
if !Path::new(&doc_path).exists() {
println!("{}", t!("The project %{s} has not been found. Do you want to create it?(y/n)[Y]", s = &input.trim().to_owned().green().bold()));
io::stdin().read_line(&mut ans).expect(&t!("Failed to read line"));
}
if ans.trim().is_empty() { ans = String::from("Y") };
match ans.trim() {
"y" | "yes" | "Y" | "B" | "Bai" => {
let _ = mkdir(&doc_path);
// create resources
for entry in WalkDir::new("/tmp/assets").into_iter().filter_map(|e| e.ok()) {
let input_entry = String::from(entry.path().strip_prefix("/tmp/assets").expect(&t!("Path not found")).display().to_string());
let subdirs = wiki_path.to_string()+"/"+&input_entry;
let images = wiki_path.to_string()+"/"+&doc_title.trim()+"/"+&input_entry;
if entry.path().is_dir(){
match Path::new(&input_entry).to_str() {
Some("images") => { //create images directory
let _ = mkdir(&images);
},
Some(..) => { //create resources and its subdirectories
let _ = mkdir(&subdirs);
},
_ => continue
};
}else if entry.path().is_file(){
let source = String::from(entry.path().display().to_string());
match Path::new(&input_entry).to_str() {
Some(d) if d.contains("images/")=>{ //copy sample-image to images folder
let _ = copy(&source, &images);
},
Some(..) => { //copy files from all other directories
let _ = copy(&source, &subdirs);
},
_ => continue
};
}
}
println!("{}.", t!("Project %{s} has been created. Now you can edit index.md file and create content", s = &input.trim().to_owned().green().bold()));
//remove temporary created assets directory
let _ = fs::remove_dir_all(Path::new("/tmp/assets"));
},
_ => continue,
};
}
let index = doc_path.to_string()+"/index.md";
if !Path::new(&index).exists(){
let _ = touch(&index, &template());
};
}
let res = processing(Project::new(wiki_path.clone(), doc_path.clone(), doc_title, output_dir));
match res {
Err(e) => println!("{}", t!("The operation failed with error %{s}", s = e)),
Ok(e) => println!("{}", e)
};
}
fn mkdir(dir: &String) -> std::io::Result<()>{
let _ = fs::create_dir(dir)?;
Ok(())
}
fn touch(file: &String, content: &String) -> std::io::Result<()>{
fs::write(file, content)?;
Ok(())
}
fn copy(source: &String, dest: &String) -> std::io::Result<()> {
fs::copy(source, dest)?;
Ok(())
}
/*
fn capitalize_first_letter(s: &str) -> String {
s[0..1].to_uppercase() + &s[1..]
}
*/
fn processing(project: Project) -> Result<String, String>{
let mut md_count :i32 = 0;
let mut error = String::new();
for entry in WalkDir::new(&project.doc_path).into_iter().filter_map(|e| e.ok()) {
if entry.path().is_dir(){
let input_entry = String::from(entry.path().display().to_string());
let doc_title: String = "/".to_string()+&project.doc_title.trim_end();
let output_dir: String = "/".to_string()+ &project.output_dir.trim_end();
//replace project-directory to project-directory_html(e.g. "Test" to "Test_html")
let new_entry = input_entry.replace(&doc_title, &output_dir);
//create output _html directory and subdirectories except images dir
match &entry.path().file_name() {
Some(p) if *p != "images"=> {
let _ = mkdir(&new_entry);
},
_ => continue,
};
}else if entry.path().is_file(){
if let Some(extension) = entry.path().extension().and_then(OsStr::to_str) {
//filter md files
match extension {
"md" => {
//md_count += 1;
println!("{}", t!("found... %{s}", s = entry.path().display()));
let conv = md_to_html(entry.path(), &project.doc_title, &project.output_dir, &project.wiki_path);
match conv {
Ok(()) => md_count += 1,
Err(e) => { //println!("{}", e);
error = e.to_string();
break;
},
};
},
_ => (),
};
};
};
};
if error.is_empty() {
if md_count == 0 {
println!("{}", t!("No markdown files found!"));
println!("{}", t!("Pleade edit first your documents in markdown format and come back to convert them to html. Bye!"));
}else{
println!("{}", t!("Processed markdown files.. %{s}", s = md_count));
}
let mut index = project.wiki_path.to_owned()+"/"+&project.output_dir.trim_end();
index.push_str("/index.html");
sanitize(Path::new(&index));
//Ok("Success")
let link = String::from("file://".to_owned()+&index).b_blue();
let message:String = String::from(t!("Congrats! Check your documentation at.. %{s}", s = &link));
Ok(message)
}else{
//Err(std::stringify!(error))
Err(error)
}
}
fn md_to_html(input_file: &Path, doc_title: &String, output_dir: &String, wiki_path: &String) -> Result<(), PandocError>{
let page_title: String = "pagetitle=".to_string()+*&doc_title.trim_end();
let doc_title: String = "/".to_string()+*&doc_title.trim_end();
let output_dir: String = "/".to_string()+ *&output_dir.trim_end();
let output_file = input_file.display().to_string().replace(&doc_title, &output_dir).replace(".md", ".html");
let base_path = wiki_path.to_owned()+&doc_title;
let res_path = wiki_path.to_owned()+&"/res:".to_string();
let mut pandoc = pandoc::new();
pandoc.add_input(&input_file);
pandoc.arg("resource-path", &res_path); //for css and javascript resources
pandoc.arg("resource-path", &base_path); //for image search
pandoc.arg("include-after-body", "buttons.html");
pandoc.arg("css", "css/theme.css");
pandoc.arg("css", "css/sidebar.css");
pandoc.arg("css", "css/buttons.css");
pandoc.arg("embed-resources", "true");
pandoc.arg("standalone", "true");
pandoc.arg("shift-heading-level-by", "-1");
pandoc.set_number_sections();
pandoc.set_toc();
pandoc.arg("toc-depth", "4");
pandoc.arg("metadata", &page_title);
pandoc.set_output(pandoc::OutputKind::File(output_file.into()));
//pandoc.execute().unwrap();
match pandoc.execute() {
Ok(..) => Ok(()),
Err(PandocError::PandocNotFound) => Err(PandocError::PandocNotFound),
Err(PandocError::IoErr(e)) => Err(PandocError::IoErr(e)),
Err(PandocError::Err(e)) => Err(PandocError::Err(e)),
Err(PandocError::NoInputSpecified) => Err(PandocError::NoInputSpecified),
Err(PandocError::BadUtf8Conversion(e)) => Err(PandocError::BadUtf8Conversion(e)),
Err(PandocError::NoOutputSpecified) => Err(PandocError::NoOutputSpecified)
}
}
fn sanitize(index: &Path){
if index.exists(){
//convert page links from ".md" to ".html" in the index.html file
Command::new("sed")
.args(["-i", r"-e s/\.md/\.html/g", &index.display().to_string()])
.status()
.expect(&t!("sed command failed start"));
}else{
println!("{}", t!("Please consider renaming your main page to index.md and restart the document conversion again. See you later!"));
};
}
fn set_lang(){
let default = String::from("en_US"); // default locale
let locales = rust_i18n::available_locales!(); // available locales
let lang:String = match env::var("LANG") { // find out user's lang
Ok(val) => match val {
v if !v.is_empty() && locales.iter().any(|e| v.contains(e)) => v[..5].to_string(),
_ => default
} ,
Err(..) => default,
};
rust_i18n::set_locale(&lang); // set user lang
}

2
Cargo.lock generated
View File

@ -105,7 +105,7 @@ dependencies = [
[[package]] [[package]]
name = "dokugile" name = "dokugile"
version = "0.1.2" version = "0.1.3"
dependencies = [ dependencies = [
"colorize", "colorize",
"dirs", "dirs",

View File

@ -1,6 +1,6 @@
[package] [package]
name = "dokugile" name = "dokugile"
version = "0.1.2" version = "0.1.3"
edition = "2021" edition = "2021"
author = "Aitzol Berasategi" author = "Aitzol Berasategi"
repository = "https://git.lainoa.eus/aitzol/dokugile.git" repository = "https://git.lainoa.eus/aitzol/dokugile.git"

View File

@ -2,12 +2,14 @@ _version: 1
Project name: 'Project name' Project name: 'Project name'
Project location: 'Project location' Project location: 'Project location'
Do you want to create a new location called %{s} for the documents?(y/n)[Y]: 'Do you want to create a new location called %{s} for the documents?(y/n)[Y]' Do you want to create a new location called %{s} for the documents?(y/n)[Y]: 'Do you want to create a new location called %{s} for the documents?(y/n)[Y]'
You must assign a name to your project. Would you like to call it %{s}?(y/n)[Y]: 'You must assign a name to your project. Would you like to call it %{s}?(y/n)[Y]'
Okay, so your project is called %{s}: 'Okay, so your project is called %{s}'
Location %{s} has been created: 'Location %{s} has been created' Location %{s} has been created: 'Location %{s} has been created'
The project %{s} has not been found. Do you want to create it?(y/n)[Y]: 'The project %{s} has not been found. Do you want to create it?(y/n)[Y]' The project %{s} has not been found. Do you want to create it?(y/n)[Y]: 'The project %{s} has not been found. Do you want to create it?(y/n)[Y]'
Project %{s} has been created. Now you can edit index.md file and create content: 'Project %{s} has been created. Now you can edit index.md file and create content' Project %{s} has been created. Now you can edit index.md file and create content: 'Project %{s} has been created. Now you can edit index.md file and create content'
found... %{s}: 'found... %{s}' found... %{s}: 'found... %{s}'
No markdown files found!: 'No markdown files found!' No markdown files found!: 'No markdown files found!'
Pleade edit first your documents in markdown format and come back to convert them to html. Bye!: 'Please edit first your documents in markdown format and come back to convert them to html. Bye!' Please edit first your documents in markdown format and come back to convert them to html. Bye!: 'Please edit first your documents in markdown format and come back to convert them to html. Bye!'
Processed markdown files.. %{s}: 'Processed markdown files.. %{s}' Processed markdown files.. %{s}: 'Processed markdown files.. %{s}'
Congrats! Check your documentation at.. %{s}: 'Congrats! Check your documentation at.. %{s}' Congrats! Check your documentation at.. %{s}: 'Congrats! Check your documentation at.. %{s}'
Please consider renaming your main page to index.md and restart the document conversion again. See you later!: 'Please consider renaming your main page to index.md and restart the document conversion again. See you later!' Please consider renaming your main page to index.md and restart the document conversion again. See you later!: 'Please consider renaming your main page to index.md and restart the document conversion again. See you later!'

View File

@ -2,6 +2,8 @@ _version: 1
Project name: 'Proiektuaren izena' Project name: 'Proiektuaren izena'
Project location: 'Proiektuaren kokapena' Project location: 'Proiektuaren kokapena'
Do you want to create a new location called %{s} for the documents?(y/n)[Y]: 'Dokumentuentzako %{s} izeneko kokapen berria sortu nahi duzu?(b/e)[B]' Do you want to create a new location called %{s} for the documents?(y/n)[Y]: 'Dokumentuentzako %{s} izeneko kokapen berria sortu nahi duzu?(b/e)[B]'
You must assign a name to your project. Would you like to call it %{s}?(y/n)[Y]: 'Izena jarri behar diozu zure proiektuari. %{s} deitzea nahiko zenuke?(b/e)[B]'
Okay, so your project is called %{s}: 'Ederki, zure proiektua %{s} deitzen da'
Location %{s} has been created: '%{s} kokapena sortu da' Location %{s} has been created: '%{s} kokapena sortu da'
The project %{s} has not been found. Do you want to create it?(y/n)[Y]: '%{s} proiektua ez da aurkitu. Sortu egin nahi duzu?(b/e)[B]' The project %{s} has not been found. Do you want to create it?(y/n)[Y]: '%{s} proiektua ez da aurkitu. Sortu egin nahi duzu?(b/e)[B]'
Project %{s} has been created. Now you can edit index.md file and create content: '%{s} proiektua sortu da. Orain index.md fitxategia editatu eta edukiak sor ditzakezu' Project %{s} has been created. Now you can edit index.md file and create content: '%{s} proiektua sortu da. Orain index.md fitxategia editatu eta edukiak sor ditzakezu'

View File

@ -68,7 +68,7 @@ fn main() {
io::stdin().read_line(&mut ans).expect("Failed to read line"); io::stdin().read_line(&mut ans).expect("Failed to read line");
if ans.trim().is_empty() { ans = String::from("Y") }; if ans.trim().is_empty() { ans = String::from("Y") };
match ans.trim() { match ans.trim() {
"y" | "yes" | "Y" | "B" | "Bai" => { //let _ = fs::create_dir(&wiki_path); "y" | "yes" | "Y" | "b" | "B" | "Bai" => { //let _ = fs::create_dir(&wiki_path);
let _ = mkdir(&wiki_path); let _ = mkdir(&wiki_path);
println!("{}.", t!("Location %{s} has been created", s = &project_dir.trim().to_owned().green().bold())); println!("{}.", t!("Location %{s} has been created", s = &project_dir.trim().to_owned().green().bold()));
}, },
@ -81,7 +81,21 @@ fn main() {
attempts += 1; attempts += 1;
} }
if attempts == 3 { if attempts == 3 {
break //break
let cwd = env::current_dir().unwrap().display().to_string();
let dirs: Vec<&str> = cwd.split('/').collect();
if input.trim().is_empty() { input = capitalize_first_letter( dirs.last().unwrap() ) };
let mut ans = String::new();
println!("{}", t!("You must assign a name to your project. Would you like to call it %{s}?(y/n)[Y]", s = input.clone().green()));
io::stdin().read_line(&mut ans).expect("Failed to read line");
if ans.trim().is_empty() { ans = String::from("Y") };
match ans.trim() {
"y" | "yes" | "Y" | "b" | "B" | "Bai" => {
println!("{}.\n", t!("Okay, so your project is called %{s}", s = &input.trim().to_owned().green().bold()));
},
_ => std::process::abort(),
};
}; };
doc_title = input.to_string(); doc_title = input.to_string();
@ -132,7 +146,7 @@ fn main() {
}; };
} }
} }
println!("{}.", t!("Project %{s} has been created. Now you can edit index.md file and create content", s = &input.trim().to_owned().green().bold())); println!("{}.\n", t!("Project %{s} has been created. Now you can edit index.md file and create content", s = &input.trim().to_owned().green().bold()));
//remove temporary created assets directory //remove temporary created assets directory
let _ = fs::remove_dir_all(Path::new("/tmp/assets")); let _ = fs::remove_dir_all(Path::new("/tmp/assets"));
}, },
@ -167,11 +181,11 @@ fn copy(source: &String, dest: &String) -> std::io::Result<()> {
fs::copy(source, dest)?; fs::copy(source, dest)?;
Ok(()) Ok(())
} }
/*
fn capitalize_first_letter(s: &str) -> String { fn capitalize_first_letter(s: &str) -> String {
s[0..1].to_uppercase() + &s[1..] s[0..1].to_uppercase() + &s[1..]
} }
*/
fn processing(project: Project) -> Result<String, String>{ fn processing(project: Project) -> Result<String, String>{
let mut md_count :i32 = 0; let mut md_count :i32 = 0;
@ -215,7 +229,7 @@ fn processing(project: Project) -> Result<String, String>{
if error.is_empty() { if error.is_empty() {
if md_count == 0 { if md_count == 0 {
println!("{}", t!("No markdown files found!")); println!("{}", t!("No markdown files found!"));
println!("{}", t!("Pleade edit first your documents in markdown format and come back to convert them to html. Bye!")); println!("{}", t!("Please edit first your documents in markdown format and come back to convert them to html. Bye!"));
}else{ }else{
println!("{}", t!("Processed markdown files.. %{s}", s = md_count)); println!("{}", t!("Processed markdown files.. %{s}", s = md_count));
} }