pub mod builder;
pub mod split;
use std::{cmp::max, process};
use crate::args::builder::{print_doc, print_help, CommandLineOption};
#[derive(Debug)]
pub struct Args {
pub input: String,
pub debug: bool,
pub flag_colon: bool,
pub flag_enclose_table: bool,
pub flag_extract_links: bool,
pub flag_extract_links_without_anchor: bool,
pub flag_google_list_style: bool,
pub flag_semicolon: bool,
pub flag_parenthesis: bool,
pub flag_pretty_table: bool,
pub flag_strip_enclosed_url: bool,
pub flag_whitespace_around_bold_font: bool,
pub flag_italic_to_bold_font: bool,
pub print_config: bool,
pub compact_output: bool,
pub debug_markdown_tag_pair: bool,
}
impl Args {
fn new() -> Self {
Self {
input: String::from(""),
debug: false,
flag_colon: false,
flag_enclose_table: false,
flag_semicolon: false,
flag_parenthesis: false,
flag_pretty_table: false,
flag_extract_links: false,
flag_extract_links_without_anchor: false,
flag_whitespace_around_bold_font: false,
flag_italic_to_bold_font: false,
flag_strip_enclosed_url: false,
print_config: false,
compact_output: false,
flag_google_list_style: false,
debug_markdown_tag_pair: false,
}
}
pub fn parse(args: Vec<String>) -> Option<Self> {
if args.len() > 1 {
let mut val = Self::new();
val.input = args.last().unwrap().to_string();
let mut options = vec![];
for arg in args {
let mut should_explain = true;
match arg.as_str() {
"--help" => {
print_help_info(Some(true), Some(options));
process::exit(0)
}
"-h" => {
print_help_info(None, None);
process::exit(0)
}
"--print" | "-p" => val.print_config = true,
"--print-compact" => {
val.compact_output = true;
val.print_config = true
}
"--colon" => val.flag_colon = true,
"--extract-links" => val.flag_extract_links = true,
"--enclose-table" => val.flag_enclose_table = true,
"--extract-no-anchor" => val.flag_extract_links_without_anchor = true,
"--google-list-style" => val.flag_google_list_style = true,
"--italic-to-bold" => val.flag_italic_to_bold_font = true,
"--parenthesis" => val.flag_parenthesis = true,
"--semicolon" => val.flag_semicolon = true,
"--pretty-table" => {
val.flag_pretty_table = true;
panic!("Unimplemented style")
}
"--whitespace-around-bold" => val.flag_whitespace_around_bold_font = true,
"--strip-enclosed-url" => val.flag_strip_enclosed_url = true,
"--flavor-hugging-face-wechat" => val.set_flavor_hugging_face_wechat(),
"--debug-markdown-tag-pair" => val.debug_markdown_tag_pair = true,
_ => {
if arg.starts_with("-") {
panic!("无法识别的选项 `{}`", arg);
} else {
should_explain = false;
}
}
}
if should_explain {
options.push(arg.clone());
}
}
Some(val)
} else {
None
}
}
pub fn set_flavor_hugging_face_wechat(&mut self) {
self.flag_colon = true;
self.flag_enclose_table = true;
self.flag_semicolon = true;
self.flag_parenthesis = true;
self.flag_whitespace_around_bold_font = true;
self.flag_italic_to_bold_font = false; self.flag_extract_links = false; self.flag_extract_links_without_anchor = false; self.flag_strip_enclosed_url = true;
self.flag_google_list_style = false;
}
pub fn print_config(&self) {
println!("渲染选项启用状态");
let mut results: Vec<(&str, bool)> = vec![];
let mut max_width = 0;
results.push((
"Colon conversion",
if self.flag_colon { true } else { false },
));
results.push((
"Extract links",
if self.flag_extract_links { true } else { false },
));
results.push((
"Extract links without anchor",
if self.flag_extract_links_without_anchor {
true
} else {
false
},
));
results.push((
"Semicolon conversion",
if self.flag_semicolon { true } else { false },
));
results.push((
"List using Google style",
if self.flag_google_list_style {
true
} else {
false
},
));
results.push((
"Parenthesis conversion",
if self.flag_parenthesis { true } else { false },
));
results.push((
"Prettify table",
if self.flag_pretty_table { true } else { false },
));
results.push((
"Italic to bold font conversion",
if self.flag_italic_to_bold_font {
true
} else {
false
},
));
results.push((
"Whitespace around bold font",
if self.flag_whitespace_around_bold_font {
true
} else {
false
},
));
results.push((
"Strip whitespace around enclosed URL",
if self.flag_strip_enclosed_url {
true
} else {
false
},
));
results.sort_by(|item_a, item_b| {
max_width = max(max_width, max(item_a.0.len(), item_b.0.len()));
if item_a.1 == item_b.1 {
item_a.0.cmp(&item_b.0)
} else {
if item_a.1 {
std::cmp::Ordering::Less
} else {
std::cmp::Ordering::Greater
}
}
});
let mut counter: usize = 1;
let digits = if results.len() < 10 {
1
} else if results.len() < 100 {
2
} else if results.len() < 1000 {
3
} else {
4
};
println!(
"┌─{}──{}┬───┐",
"─".repeat(digits),
"─".repeat(max_width + 1)
);
results.iter().for_each(|res| {
if !self.compact_output && counter > 1 {
println!(
"├─{}──{}┼───┤",
"─".repeat(digits),
"─".repeat(max_width + 1)
);
}
println!(
"│ {:digits$}. {:max_width$} │ {} │",
counter,
res.0,
if res.1 { "Y" } else { "N" }
);
counter += 1;
});
println!(
"└─{}──{}┴───┘",
"─".repeat(digits),
"─".repeat(max_width + 1)
);
}
}
fn print_help_info(
long_help: Option<bool>,
filtered: Option<Vec<String>>,
) -> Vec<CommandLineOption> {
println!("命令用法: md-fmt [选项] <Markdown 文件名>\n");
let mut commands = vec![];
commands.push(
CommandLineOption::new("--help", "输出当前的帮助信息 (--help 更详细)", "")
.set_condition(builder::CommandLineOptionCondition::Standard)
.set_short("-h"),
);
commands.push(
CommandLineOption::new(
"--colon",
"中文冒号转为英文冒号+空格",
"比如: “:” => “: ”。如果在行末,则会生成“:”,即删除末尾的空格。",
)
.set_condition(builder::CommandLineOptionCondition::Format),
);
commands.push(
CommandLineOption::new(
"--enclose-table",
"表格的每一行两端使用 |",
"比如表格的对齐行: “:-- | :--” => “| :-- | :-- |”。",
)
.set_condition(builder::CommandLineOptionCondition::Format),
);
commands.push(
CommandLineOption::new("--extract-links", "从段落中提取超链接", "比如:\n“这句话包含了一个[超链接](https://dongs.xyz/)” => “这句话包含了一个[超链接](https://dongs.xyz/)\\n\\n超链接:\\n<url>https://dongs.xyz/</url>”")
.set_condition(builder::CommandLineOptionCondition::Format),
);
commands.push(
CommandLineOption::new("--extract-no-anchor", "提取链接时不包括锚点部分", "")
.set_condition(builder::CommandLineOptionCondition::Format),
);
commands.push(
CommandLineOption::new("--google-list-style", "使用 Google 风格的列表样式", "")
.set_condition(builder::CommandLineOptionCondition::Format),
);
commands.push(
CommandLineOption::new(
"--italic-to-bold",
"斜体字更改为加粗文字",
"比如: “a*b*c” => “a**b**c”",
)
.set_condition(builder::CommandLineOptionCondition::Format),
);
commands.push(
CommandLineOption::new(
"--parenthesis",
"中文括号转为英文括号+空格",
"比如:\n“()” => “ () ”\n最终结果将根据上下文去除多余的空格。",
)
.set_condition(builder::CommandLineOptionCondition::Format),
);
commands.push(
CommandLineOption::new("--pretty-table", "以相同列宽展示表格中的每一列", "")
.set_condition(builder::CommandLineOptionCondition::Format),
);
commands.push(
CommandLineOption::new(
"--semicolon",
"中文分号转为英文分号+空格",
"比如: “;” => “; ”\n最终结果将根据上下文去除多余的空格。",
)
.set_condition(builder::CommandLineOptionCondition::Format),
);
commands.push(
CommandLineOption::new(
"--strip-enclosed-url",
"去除 <url> 内部的非必要空格",
"比如: “<url> https://dongs.xyz/ </url>” => “<url>https://dongs.xyz/</url>”",
)
.set_condition(builder::CommandLineOptionCondition::Format),
);
commands.push(
CommandLineOption::new(
"--whitespace-around-bold",
"加粗文字前后使用空格",
"比如:\n“a**b**c” => “a **b** c”;\n“a **b**c” => “a **b** c”。\n最终结果将根据上下文去除多余的空格。",
)
.set_condition(builder::CommandLineOptionCondition::Format),
);
commands.push(
CommandLineOption::new("--print", "在控制台输出当前启用的调整", "")
.set_condition(builder::CommandLineOptionCondition::Additional),
);
commands.push(
CommandLineOption::new("--print-compact", "窄行距输出当前启用的调整", "")
.set_condition(builder::CommandLineOptionCondition::Additional),
);
commands.push(
CommandLineOption::new(
"--debug-markdown-tag-pair",
"执行 Markdown 模块的函数时输出成对的 Tag (仅适用于 `debug` feature)",
"",
)
.set_condition(builder::CommandLineOptionCondition::Debug),
);
commands.push(
CommandLineOption::new(
"--flavor-hugging-face-wechat",
"使用 Hugging Face 微信公众号风格来调整输入内容",
"",
)
.set_condition(builder::CommandLineOptionCondition::Flavor),
);
use termsize_alt::{get as get_termsize, Size};
let Size { rows: _, cols } = get_termsize().unwrap_or(Size {
rows: 40,
cols: 120,
});
let line_width = Some(cols.into());
match long_help {
Some(true) => print_doc(commands.clone(), filtered, line_width),
_ => print_help(commands.clone(), line_width),
}
commands
}
#[cfg(test)]
mod test {
use crate::args::Args;
#[test]
fn test_argument_parsing() {
let mut options: Vec<String> = vec!["--colon", "--parenthesis", "test.md"]
.iter()
.map(|s| s.to_string())
.collect();
if let Some(args) = Args::parse(options) {
assert_eq!(args.flag_colon, true);
assert_eq!(args.flag_parenthesis, true);
};
options = vec!["--flavor-hugging-face-wechat", "test.md"]
.iter()
.map(|s| s.to_string())
.collect();
if let Some(args) = Args::parse(options) {
assert_eq!(args.flag_colon, true);
assert_eq!(args.flag_parenthesis, true);
assert_eq!(args.flag_italic_to_bold_font, true);
assert_eq!(args.flag_whitespace_around_bold_font, true);
};
}
#[test]
fn test_malformed_options() {
let options = vec!["--flavor-hugging-face-wechat"]
.iter()
.map(|s| s.to_string())
.collect();
Args::parse(options);
}
}