Last active
December 18, 2023 00:40
-
-
Save kujirahand/e39cb7da2285b91e44c3afefd54f7a21 to your computer and use it in GitHub Desktop.
Markdown parser
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// メイン関数 --- (*1) | |
fn main() { | |
// コマンドライン引数が足りない場合は使い方を表示 --- (*2) | |
if std::env::args().len() < 2 { | |
println!("[Usage] markown [input.md]"); | |
return; | |
} | |
// コマンドライン引数からファイル名を取得する --- (*3) | |
let filename = std::env::args().nth(1).unwrap(); | |
// ファイルを読み込む --- (*4) | |
let markdown = std::fs::read_to_string(&filename).unwrap(); | |
// パースしてHTMLに変換する --- (*5) | |
let items = parse_markdown(&markdown); | |
let html = reander_to_html(items); | |
// 完全なHTMLにするため、ヘッダとフッタをくっつける --- (*6) | |
let style = "pre { background-color:#EEF; padding:1em; }"; | |
let html = format!("<html><body><style>{}</style>{}</body></html>", style, html); | |
// ファイルへ保存 --- (*7) | |
let html_filename = filename.replace(".md", "") + ".html"; | |
std::fs::write(&html_filename, html).unwrap(); | |
println!("saved: {}", html_filename); | |
} | |
// マークダウンのアイテム種類を表す列挙型 --- (*8) | |
#[derive(PartialEq, Debug, Clone, Copy)] | |
enum MarkdownKind { | |
None, Title, Text, Code, List | |
} | |
// マークダウンのアイテムを表す | |
#[derive(PartialEq, Debug, Clone)] | |
struct MarkdownItem { | |
kind: MarkdownKind, | |
text: String, | |
level: isize | |
} | |
impl MarkdownItem { // 手軽に構造体を作るための関数 | |
fn new(kind: MarkdownKind, text: String, level: isize) -> Self { | |
Self { kind, text, level } | |
} | |
} | |
// 文字列をマークダウンのアイテムにパースする | |
fn parse_markdown(text: &str) -> Vec<MarkdownItem> { | |
let mut items = Vec::new(); | |
let mut flag_code = false; | |
let mut multiline = String::new(); | |
// テキストを一行ずつ処理する --- (*9) | |
for line in text.lines() { | |
// コードブロックの終端か判断する --- (*10) | |
if flag_code { | |
if line == "```" { | |
items.push(MarkdownItem::new( | |
MarkdownKind::Code, multiline.to_string(), 0)); | |
flag_code = false; | |
} else { | |
multiline.push_str(line); | |
multiline.push_str("\n"); | |
} | |
continue; | |
} | |
// マークダウンのアイテムを判定する --- (*11) | |
if line.starts_with("#") { // タイトル | |
let mut title = String::new(); | |
let mut level = 0; | |
for c in line.chars() { | |
if c == '#' { | |
level += 1; | |
} else { | |
title.push(c); | |
} | |
} | |
items.push(MarkdownItem::new(MarkdownKind::Title, | |
title.trim().to_string(), level)); | |
} else if line.trim().starts_with("-") { // リスト | |
items.push(MarkdownItem::new( | |
MarkdownKind::List, line[1..].to_string(), 0)); | |
} else if line.starts_with("```") { // コードブロック --- (*12) | |
flag_code = true; | |
multiline = String::new(); | |
} else { | |
items.push(MarkdownItem::new(MarkdownKind::Text, line.to_string() ,0)); | |
} | |
} | |
items | |
} | |
// マークダウンのアイテムをHTMLに変換する --- (*13) | |
fn reander_to_html(items: Vec<MarkdownItem>) -> String { | |
let mut result = String::new(); | |
let mut last_kind = MarkdownKind::None; | |
let mut flag_list = false; | |
// 要素を一つずつHTMLに変換していく --- (*14) | |
for item in items.iter() { | |
if flag_list && item.kind != MarkdownKind::List { | |
result.push_str("</ul>\n"); | |
flag_list = false; | |
} | |
// テキストをHTMLに変換する --- (*15) | |
let text = item.text.replace("&", "&") | |
.replace("<", "<").replace(">", ">"); | |
// アイテムの種類によって処理を変える --- (*16) | |
match item.kind { | |
MarkdownKind::Title => { | |
let level = item.level; | |
result.push_str(&format!("<h{}>{}</h{}>\n", level, text, level)); | |
}, | |
MarkdownKind::Text => { | |
result.push_str(&format!("<p>{}</p>\n", text)); | |
}, | |
MarkdownKind::Code => { | |
result.push_str(&format!("<pre>{}</pre>\n", text)); | |
}, | |
MarkdownKind::List => { | |
if last_kind != MarkdownKind::List { | |
result.push_str("<ul>\n"); | |
} | |
result.push_str(&format!("<li>{}</li>\n", text)); | |
flag_list = true; | |
}, | |
_ => {}, | |
} | |
last_kind = item.kind; | |
} | |
result | |
} |
@tresuke ありがとうございます。
確かに、continueを入れる場所がずれてしまっていましたので修正しました。
今後ともどうぞよろしくお願いします。
@kujirahand さん、早速のご対応ありがとうございました!
次回の連載楽しみにしております。こちらこそ今後ともよろしくお願いします。
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
いつも楽しく拝読させていただいております。
このコードだと50行からのコードブロックの終端処理が実行された後、再びその行が76行でコードブロック開始判定されてしまい、コードブロックより下に書かれたマークダウンが変換されないようです。
57行の continue が 58行の下にあればうまくいきそうです。
どこでお伝えすればよいか迷い、こちらにコメントさせていただきましたが、適切でないようでしたら削除いたします。