From 9209299af6e56bf67c9dfe9d928052adf06a38fd Mon Sep 17 00:00:00 2001 From: aitzol Date: Sat, 16 Aug 2025 17:20:25 +0200 Subject: [PATCH] v0.1.3 --- 7 | 297 ++++++++++++++++++++++++++++++++++++++++++++++ Cargo.lock | 2 +- Cargo.toml | 2 +- locales/en_US.yml | 4 +- locales/eu_ES.yml | 2 + src/main.rs | 26 +++- 6 files changed, 324 insertions(+), 9 deletions(-) create mode 100644 7 diff --git a/7 b/7 new file mode 100644 index 0000000..12d13da --- /dev/null +++ b/7 @@ -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::>().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{ + 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 +} diff --git a/Cargo.lock b/Cargo.lock index cb5638f..cd713a4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -105,7 +105,7 @@ dependencies = [ [[package]] name = "dokugile" -version = "0.1.2" +version = "0.1.3" dependencies = [ "colorize", "dirs", diff --git a/Cargo.toml b/Cargo.toml index 411f934..110644c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dokugile" -version = "0.1.2" +version = "0.1.3" edition = "2021" author = "Aitzol Berasategi" repository = "https://git.lainoa.eus/aitzol/dokugile.git" diff --git a/locales/en_US.yml b/locales/en_US.yml index 1358ea6..43ecfb0 100644 --- a/locales/en_US.yml +++ b/locales/en_US.yml @@ -2,12 +2,14 @@ _version: 1 Project name: 'Project name' 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]' +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' 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' found... %{s}: 'found... %{s}' 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}' 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!' diff --git a/locales/eu_ES.yml b/locales/eu_ES.yml index 3c1e1fe..f75d630 100644 --- a/locales/eu_ES.yml +++ b/locales/eu_ES.yml @@ -2,6 +2,8 @@ _version: 1 Project name: 'Proiektuaren izena' 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]' +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' 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' diff --git a/src/main.rs b/src/main.rs index f9dd54c..9dd4e14 100644 --- a/src/main.rs +++ b/src/main.rs @@ -68,7 +68,7 @@ fn main() { 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); + "y" | "yes" | "Y" | "b" | "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())); }, @@ -81,7 +81,21 @@ fn main() { attempts += 1; } 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(); @@ -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 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)?; Ok(()) } -/* + fn capitalize_first_letter(s: &str) -> String { s[0..1].to_uppercase() + &s[1..] } -*/ + fn processing(project: Project) -> Result{ let mut md_count :i32 = 0; @@ -215,7 +229,7 @@ fn processing(project: Project) -> Result{ 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!")); + println!("{}", t!("Please 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)); }