Skip to content

Instantly share code, notes, and snippets.

@kujirahand
Last active December 18, 2023 00:40
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kujirahand/e39cb7da2285b91e44c3afefd54f7a21 to your computer and use it in GitHub Desktop.
Save kujirahand/e39cb7da2285b91e44c3afefd54f7a21 to your computer and use it in GitHub Desktop.
Markdown parser
// メイン関数 --- (*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("&", "&amp;")
.replace("<", "&lt;").replace(">", "&gt;");
// アイテムの種類によって処理を変える --- (*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
Copy link

tresuke commented Dec 15, 2023

いつも楽しく拝読させていただいております。
このコードだと50行からのコードブロックの終端処理が実行された後、再びその行が76行でコードブロック開始判定されてしまい、コードブロックより下に書かれたマークダウンが変換されないようです。
57行の continue が 58行の下にあればうまくいきそうです。
どこでお伝えすればよいか迷い、こちらにコメントさせていただきましたが、適切でないようでしたら削除いたします。

@kujirahand
Copy link
Author

kujirahand commented Dec 15, 2023

@tresuke ありがとうございます。
確かに、continueを入れる場所がずれてしまっていましたので修正しました。
今後ともどうぞよろしくお願いします。

@tresuke
Copy link

tresuke commented Dec 18, 2023

@kujirahand さん、早速のご対応ありがとうございました!
次回の連載楽しみにしております。こちらこそ今後ともよろしくお願いします。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment