利用rust編一個(gè)靜態(tài)博客工具
這個(gè)靜態(tài)博客的工具主要是把md文檔轉(zhuǎn)為html靜態(tài)網(wǎng)站/博客。github鏈接:github.com/maochunguang/rust-md-site-tool
首先說(shuō)明md文檔轉(zhuǎn)為靜態(tài)網(wǎng)站/博客的原理,就是先做一個(gè)目錄文檔,叫做summary.md,然后其他文檔都會(huì)鏈接到這個(gè)目錄文檔里。當(dāng)把md文檔轉(zhuǎn)為html時(shí),需要對(duì)鏈接進(jìn)行處理,保證鏈接可以正常跳轉(zhuǎn),這樣就完成了一個(gè)簡(jiǎn)單的md轉(zhuǎn)靜態(tài)博客工具。
第一步,設(shè)計(jì)博客工具
目錄和樣式設(shè)計(jì)
這個(gè)簡(jiǎn)易的博客/站點(diǎn)工具主要是模仿gitbook,可以認(rèn)為是gitbook的簡(jiǎn)易版。頁(yè)面布局也是gitbook一樣,左邊是目錄,右邊是內(nèi)容。

首先需要定義一個(gè)博客的靜態(tài)目錄結(jié)構(gòu),如下圖所示:
- docs目錄是所有md文檔源文件;
- static目錄是所有靜態(tài)文件的存放目錄,比如js,css,image文件;
- md_config.toml是全局配置文件,
- summary.md是全局站點(diǎn)的目錄;
- index.md是全局首頁(yè)內(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>"
功能實(shí)現(xiàn)和設(shè)計(jì)
本質(zhì)上是一個(gè)命令行工具,依賴clap=4.4.0來(lái)創(chuàng)建,第一版支持三個(gè)命令:
init,初始化一些目錄和配置文件build,構(gòu)建所有的文件,轉(zhuǎn)為html到指定的輸出目錄run,本地運(yùn)行,在本地預(yù)覽
由于要實(shí)現(xiàn)md文件轉(zhuǎn)為html,需要借助依賴pulldown-cmark = "0.9"。
第二步,創(chuàng)建三個(gè)命令
創(chuàng)建main.js,把三個(gè)命令寫出來(lái):
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"),
}
}
第三步,實(shí)現(xiàn)init命令
init命令就是創(chuàng)建一個(gè)默認(rèn)的配置文件,以及六個(gè)目錄和一個(gè)css文件。 核心代碼如下:
- config_content是默認(rèn)的配置文件內(nèi)容,可以自定義字符串或者使用模板文件;
- get_style_content是默認(rèn)的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.");
第四步,實(shí)現(xiàn)build命令
構(gòu)建所有的md文件流程也很簡(jiǎn)單:

核心代碼如下:
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());
// 解析每個(gè).md文件 轉(zhuǎn)為HTML
let md_files = read_dir_recursive(md_source_dir).expect("read md dir failed");
// 為每個(gè)Markdown文件生成HTML頁(yè)面
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頁(yè)面邏輯
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]);
// 處理相對(duì)路徑
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)資源目錄和目標(biāo)目錄
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文件這里需要注意幾個(gè)點(diǎn):
- 第一個(gè)是文件路徑,需要把html生成到配置的輸出目錄,需要一個(gè)path轉(zhuǎn)換邏輯;
- summary里面的鏈接需要處理,默認(rèn)是
.md,需要轉(zhuǎn)為.html; - 所有鏈接的相對(duì)路徑需要處理,根據(jù)目錄都是用相對(duì)路徑;
- 復(fù)制靜態(tài)資源時(shí),也需要注意文件路徑,以及文件夾是否存在。
第五步,實(shí)現(xiàn)run命令
這里由于只是本地運(yùn)行查看html,所以選擇tiny_http,啟動(dòng)一個(gè)簡(jiǎn)單的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 作為根路徑的默認(rèn)頁(yè)面
} else {
request.url()
};
let file_path = PathBuf::from(output_dir).join(&url[1..]); // 移除 URL 的首個(gè)斜杠
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會(huì)中文亂碼,所以統(tǒng)一都加了Content-Type", "text/html; charset=utf-8。
第六步,展示成果
新建一個(gè)用于測(cè)試的博客目錄:
- 在博客工具目錄執(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)建好測(cè)試的md文檔和鏈接; rust_md_site_tool build構(gòu)建目錄;- 打開瀏覽器 http://localhost:9900/ ;可以訪問(wèn)index.html;

到此這篇關(guān)于利用rust編一個(gè)靜態(tài)博客工具的文章就介紹到這了,更多相關(guān)rust靜態(tài)博客內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
rust標(biāo)準(zhǔn)庫(kù)std::env環(huán)境相關(guān)的常量
在本章節(jié)中, 我們探討了Rust處理命令行參數(shù)的常見的兩種方式和處理環(huán)境變量的兩種常見方式, 拋開Rust的語(yǔ)法, 實(shí)際上在命令行參數(shù)的處理方式上, 與其它語(yǔ)言大同小異, 可能影響我們習(xí)慣的也就只剩下語(yǔ)法,本文介紹rust標(biāo)準(zhǔn)庫(kù)std::env的相關(guān)知識(shí),感興趣的朋友一起看看吧2024-03-03
Rust使用libloader調(diào)用動(dòng)態(tài)鏈接庫(kù)
這篇文章主要為大家介紹了Rust使用libloader調(diào)用動(dòng)態(tài)鏈接庫(kù)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09

