利用rust編一個靜態(tài)博客工具
這個靜態(tài)博客的工具主要是把md文檔轉(zhuǎn)為html靜態(tài)網(wǎng)站/博客。github鏈接:github.com/maochunguang/rust-md-site-tool
首先說明md文檔轉(zhuǎn)為靜態(tài)網(wǎng)站/博客的原理,就是先做一個目錄文檔,叫做summary.md,然后其他文檔都會鏈接到這個目錄文檔里。當把md文檔轉(zhuǎn)為html時,需要對鏈接進行處理,保證鏈接可以正常跳轉(zhuǎn),這樣就完成了一個簡單的md轉(zhuǎn)靜態(tài)博客工具。
第一步,設(shè)計博客工具
目錄和樣式設(shè)計
這個簡易的博客/站點工具主要是模仿gitbook,可以認為是gitbook的簡易版。頁面布局也是gitbook一樣,左邊是目錄,右邊是內(nèi)容。
首先需要定義一個博客的靜態(tài)目錄結(jié)構(gòu),如下圖所示:
- docs目錄是所有md文檔源文件;
- static目錄是所有靜態(tài)文件的存放目錄,比如js,css,image文件;
- md_config.toml是全局配置文件,
- summary.md是全局站點的目錄;
- index.md是全局首頁內(nèi)容
├── docs
│ ├── chapter1
│ │ ├── chapter1.html
│ │ ├── chapter1.md
│ ├── chapter2
│ │ ├── chapter2.html
│ │ ├── chapter2.md
│ ├── index.md
│ └── summary.md
├── md_config.toml
└── static
├── css
│ └── style.css
├── images
└── js
summary.md格式如下:
* [章節(jié)1](chapter1/chapter1.md) * [小節(jié)1.1](chapter1/section1.1.md) * [小節(jié)1.2](chapter1/section1.2.md) * [章節(jié)2](chapter2/chapter2.md) * [小節(jié)2.1](chapter2/section2.1.md) * [小節(jié)2.2](chapter2/section2.2.md)
配置文件結(jié)構(gòu)如下:
title = "My Blog" author = "Your Name" description = "This is my blog." port = 9900 static_dir = "static" md_source_dir = "docs" output_dir = ".site" default_css_header = "<link rel=\"stylesheet\" href=\"./css/style.css\">" default_code_header = "<link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.3.1/styles/default.min.css\"><script src=\"https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.3.1/highlight.min.js\"></script>" default_code_plugin = "<script>hljs.highlightAll();</script>"
功能實現(xiàn)和設(shè)計
本質(zhì)上是一個命令行工具,依賴clap=4.4.0
來創(chuàng)建,第一版支持三個命令:
init
,初始化一些目錄和配置文件build
,構(gòu)建所有的文件,轉(zhuǎn)為html到指定的輸出目錄run
,本地運行,在本地預(yù)覽
由于要實現(xiàn)md文件轉(zhuǎn)為html,需要借助依賴pulldown-cmark = "0.9"
。
第二步,創(chuàng)建三個命令
創(chuàng)建main.js,把三個命令寫出來:
fn main() { let matches = Command::new("rust-md-blog-tool") .version("1.0") .author("Your Name") .about("A simple static site generator") .subcommand(Command::new("init") .about("Initializes the blog with default configuration")) .subcommand(Command::new("build") .about("Builds the static site from markdown files")) .subcommand(Command::new("run") .about("Runs a local server to view the blog")) .get_matches(); match matches.subcommand() { Some(("init", _)) => init_lib::init_command(), Some(("build", _)) => build_lib::build_command(), Some(("run", _)) => server_lib::run_command(), _ => println!("Invalid command or no command provided"), } }
第三步,實現(xiàn)init命令
init命令就是創(chuàng)建一個默認的配置文件,以及六個目錄和一個css文件。 核心代碼如下:
- config_content是默認的配置文件內(nèi)容,可以自定義字符串或者使用模板文件;
- get_style_content是默認的css文件,可以自定義字符串或者使用模板文件;
let _ = File::create("md_config.toml").and_then(|mut file| file.write_all(config_content.as_bytes())); // 創(chuàng)建所需的目錄 let _ = fs::create_dir_all("docs").map(|_| println!("created 'docs' Success")); let _ = fs::create_dir_all(".site").map(|_| println!("created '.site' Success")); let _ = fs::create_dir_all("static").map(|_| println!("created 'static' Success")); let _ = fs::create_dir_all("static/js").map(|_| println!("created 'static/js' Success")); let _ = fs::create_dir_all("static/css").map(|_| println!("created 'static/css' Success")); let _ = fs::create_dir_all("static/images").map(|_| println!("created 'static/images' Success")); let _ = File::create("static/css/style.css").and_then(|mut file| file.write_all(get_style_content().as_bytes())); println!("Blog initialized with default configuration.");
第四步,實現(xiàn)build命令
構(gòu)建所有的md文件流程也很簡單:
核心代碼如下:
pub fn build_command() { // ... 讀取配置文件和設(shè)置目錄 ... let config = fs::read_to_string("md_config.toml").expect("Unable to read config file"); let parsed_config = config.parse::<Value>().expect("Unable to parse config"); let source_dir = parsed_config.get("md_source_dir").and_then(Value::as_str).unwrap_or("docs"); // ..... println!("load config source_dir :{}, output_dir:{}", source_dir, output_dir); let md_source_dir = Path::new(source_dir); let summary_path =format!("{}{}", source_dir, "/summary.md"); // 解析 summary.md 并構(gòu)建目錄HTML let toc_html = build_toc_content(summary_path.clone()); // 解析每個.md文件 轉(zhuǎn)為HTML let md_files = read_dir_recursive(md_source_dir).expect("read md dir failed"); // 為每個Markdown文件生成HTML頁面 for entry in md_files { let path = entry; if path.ends_with("summary.md"){ continue; } let output_file_path = transform_path(&path, source_dir, output_dir); if path.extension().and_then(|s| s.to_str()) == Some("md") { let mut md_content = String::new(); File::open(&path).and_then(|mut file| file.read_to_string(&mut md_content)).expect("Failed to read Markdown file"); let parser = Parser::new(&md_content); let mut html_content = String::new(); html::push_html(&mut html_content, parser); let _ = html_content.replace(".md", ".html"); let output_file = output_file_path.with_extension("html"); println!("output file:{}", output_file.as_path().display()); let mut full_html; // 處理index頁面邏輯 full_html = format!("<html><head></head><body><div class=\"toc\">{}</div><div class=\"content\">{}</div></body></html>", toc_html, html_content); full_html = append_html(&full_html, "</head>", &[default_css_header, default_code_header]); // 增加highlight.js處理代碼塊 full_html = append_html(&full_html, "</body>", &[default_code_plugin]); // 處理相對路徑 if contains_sub_dir(&output_file, output_dir){ full_html = full_html.replace("./", "../").replace("<a href=\"", "<a href=\"../") } if let Some(parent) = output_file.parent() { // 創(chuàng)建所有必要的父文件夾 fs::create_dir_all(parent).expect("create html parent dir failed"); } fs::write(output_file, full_html).expect("Failed to write HTML file"); } } // ... 復(fù)制靜態(tài)資源 ... // 定義靜態(tài)資源目錄和目標目錄 let static_dir = Path::new(static_dir); let output_dir = Path::new(output_dir); // 復(fù)制靜態(tài)資源 if static_dir.exists() { copy_dir_all(static_dir, output_dir).expect("Failed to copy static files"); } println!("Site built successfully."); }
生成html文件這里需要注意幾個點:
- 第一個是文件路徑,需要把html生成到配置的輸出目錄,需要一個path轉(zhuǎn)換邏輯;
- summary里面的鏈接需要處理,默認是
.md
,需要轉(zhuǎn)為.html
; - 所有鏈接的相對路徑需要處理,根據(jù)目錄都是用相對路徑;
- 復(fù)制靜態(tài)資源時,也需要注意文件路徑,以及文件夾是否存在。
第五步,實現(xiàn)run命令
這里由于只是本地運行查看html,所以選擇tiny_http,啟動一個簡單的http服務(wù)。核心代碼如下:
pub fn run_command() { // 讀取配置文件 let config = fs::read_to_string("md_config.toml").expect("Unable to read config file"); let parsed_config = config.parse::<Value>().expect("Unable to parse config"); let port = parsed_config.get("port").and_then(Value::as_str).unwrap_or("9900"); let output_dir = parsed_config.get("output_dir").and_then(Value::as_str).unwrap_or(".site"); let address = format!("0.0.0.0:{}", port); let server = Server::http(address).unwrap(); println!("Running local server on port {}", port); for request in server.incoming_requests() { let url = if request.url() == "/" { "/index.html" // 使用 index.html 作為根路徑的默認頁面 } else { request.url() }; let file_path = PathBuf::from(output_dir).join(&url[1..]); // 移除 URL 的首個斜杠 match fs::read(&file_path) { Ok(contents) => { let response = Response::from_data(contents).with_header( Header::from_bytes("Content-Type", "text/html; charset=utf-8").unwrap(), ); request.respond(response).unwrap(); } Err(_) => { let response = Response::from_string("Not Found").with_status_code(404); request.respond(response).unwrap(); } } } }
這里需要注意的是,直接返回html會中文亂碼,所以統(tǒng)一都加了Content-Type", "text/html; charset=utf-8
。
第六步,展示成果
新建一個用于測試的博客目錄:
- 在博客工具目錄執(zhí)行
cargo run
生成執(zhí)行的命令文件; - 復(fù)制博客工具命令,
cp target/debug/rust_md_site_tool ~/.cargo/bin
; - 執(zhí)行
rust_md_site_tool init
初始化博客; - 在docs目錄新建
summary.md
和index.md
,并在summary里創(chuàng)建好測試的md文檔和鏈接; rust_md_site_tool build
構(gòu)建目錄;- 打開瀏覽器 http://localhost:9900/ ;可以訪問index.html;
到此這篇關(guān)于利用rust編一個靜態(tài)博客工具的文章就介紹到這了,更多相關(guān)rust靜態(tài)博客內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
rust標準庫std::env環(huán)境相關(guān)的常量
在本章節(jié)中, 我們探討了Rust處理命令行參數(shù)的常見的兩種方式和處理環(huán)境變量的兩種常見方式, 拋開Rust的語法, 實際上在命令行參數(shù)的處理方式上, 與其它語言大同小異, 可能影響我們習(xí)慣的也就只剩下語法,本文介紹rust標準庫std::env的相關(guān)知識,感興趣的朋友一起看看吧2024-03-03Rust使用libloader調(diào)用動態(tài)鏈接庫
這篇文章主要為大家介紹了Rust使用libloader調(diào)用動態(tài)鏈接庫示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-09-09