Add --man subcommand (#2041)

This commit is contained in:
Casey Rodarmor
2024-05-15 00:28:50 -07:00
committed by GitHub
parent 07aaa4f440
commit a9b0912b2b
22 changed files with 220 additions and 159 deletions

View File

@@ -1,9 +0,0 @@
[package]
name = "generate-book"
version = "0.0.0"
edition = "2018"
publish = false
[dependencies]
pulldown-cmark = "0.9.1"
pulldown-cmark-to-cmark = "10.0.1"

View File

@@ -1,190 +0,0 @@
use {
pulldown_cmark::{CowStr, Event, HeadingLevel, Options, Parser, Tag},
pulldown_cmark_to_cmark::cmark,
std::{collections::BTreeMap, error::Error, fmt::Write, fs, ops::Deref},
};
type Result<T = ()> = std::result::Result<T, Box<dyn Error>>;
#[derive(Copy, Clone, Debug)]
enum Language {
English,
Chinese,
}
impl Language {
fn code(&self) -> &'static str {
match self {
Self::English => "en",
Self::Chinese => "zh",
}
}
fn suffix(&self) -> &'static str {
match self {
Self::English => "",
Self::Chinese => ".中文",
}
}
fn introduction(&self) -> &'static str {
match self {
Self::Chinese => "说明",
Self::English => "Introduction",
}
}
}
#[derive(Debug)]
struct Chapter<'a> {
level: HeadingLevel,
events: Vec<Event<'a>>,
index: usize,
language: Language,
}
impl<'a> Chapter<'a> {
fn title(&self) -> String {
if self.index == 0 {
return self.language.introduction().into();
}
self
.events
.iter()
.skip_while(|event| !matches!(event, Event::Start(Tag::Heading(..))))
.skip(1)
.take_while(|event| !matches!(event, Event::End(Tag::Heading(..))))
.filter_map(|event| match event {
Event::Code(content) | Event::Text(content) => Some(content.deref()),
_ => None,
})
.collect()
}
fn number(&self) -> usize {
self.index + 1
}
fn markdown(&self) -> Result<String> {
let mut markdown = String::new();
cmark(self.events.iter(), &mut markdown)?;
if self.index == 0 {
markdown = markdown.split_inclusive('\n').skip(1).collect::<String>();
}
Ok(markdown)
}
}
fn slug(s: &str) -> String {
let mut slug = String::new();
for c in s.chars() {
match c {
'A'..='Z' => slug.extend(c.to_lowercase()),
' ' => slug.push('-'),
'?' | '.' | '' => {}
_ => slug.push(c),
}
}
slug
}
fn main() -> Result {
for language in [Language::English, Language::Chinese] {
let src = format!("book/{}/src", language.code());
fs::remove_dir_all(&src).ok();
fs::create_dir(&src)?;
let txt = fs::read_to_string(format!("README{}.md", language.suffix()))?;
let mut chapters = vec![Chapter {
level: HeadingLevel::H1,
events: Vec::new(),
index: 0,
language,
}];
for event in Parser::new_ext(&txt, Options::all()) {
if let Event::Start(Tag::Heading(level @ (HeadingLevel::H2 | HeadingLevel::H3), ..)) = event {
let index = chapters.last().unwrap().index + 1;
chapters.push(Chapter {
level,
events: Vec::new(),
index,
language,
});
}
chapters.last_mut().unwrap().events.push(event);
}
let mut links = BTreeMap::new();
for chapter in &chapters {
let mut current = None;
for event in &chapter.events {
match event {
Event::Start(Tag::Heading(..)) => current = Some(Vec::new()),
Event::End(Tag::Heading(level, ..)) => {
let events = current.unwrap();
let title = events
.iter()
.filter_map(|event| match event {
Event::Code(content) | Event::Text(content) => Some(content.deref()),
_ => None,
})
.collect::<String>();
let slug = slug(&title);
let link = if let HeadingLevel::H1 | HeadingLevel::H2 | HeadingLevel::H3 = level {
format!("chapter_{}.html", chapter.number())
} else {
format!("chapter_{}.html#{}", chapter.number(), slug)
};
links.insert(slug, link);
current = None;
}
_ => {
if let Some(events) = &mut current {
events.push(event.clone());
}
}
}
}
}
for chapter in &mut chapters {
for event in &mut chapter.events {
if let Event::Start(Tag::Link(_, dest, _)) | Event::End(Tag::Link(_, dest, _)) = event {
if let Some(anchor) = dest.clone().strip_prefix('#') {
*dest = CowStr::Borrowed(&links[anchor]);
}
}
}
}
let mut summary = String::new();
for chapter in chapters {
let path = format!("{}/chapter_{}.md", src, chapter.number());
fs::write(path, chapter.markdown()?)?;
let indent = match chapter.level {
HeadingLevel::H1 => 0,
HeadingLevel::H2 => 1,
HeadingLevel::H3 => 2,
HeadingLevel::H4 => 3,
HeadingLevel::H5 => 4,
HeadingLevel::H6 => 5,
};
writeln!(
summary,
"{}- [{}](chapter_{}.md)",
" ".repeat(indent * 4),
chapter.title(),
chapter.number()
)?;
}
fs::write(format!("{src}/SUMMARY.md"), summary).unwrap();
}
Ok(())
}

View File

@@ -1,13 +0,0 @@
[package]
name = "ref-type"
version = "0.0.0"
authors = ["Casey Rodarmor <casey@rodarmor.com>"]
edition = "2018"
publish = false
[dependencies]
regex = "1.5.4"
structopt = "0.3.21"
[dev-dependencies]
executable-path = "1.0.0"

View File

@@ -1,25 +0,0 @@
use {regex::Regex, structopt::StructOpt};
#[derive(StructOpt)]
struct Arguments {
#[structopt(long)]
reference: String,
}
fn main() {
let arguments = Arguments::from_args();
let regex = Regex::new("^refs/tags/[[:digit:]]+[.][[:digit:]]+[.][[:digit:]]+$")
.expect("Failed to compile release regex");
let value = if regex.is_match(&arguments.reference) {
"release"
} else {
"other"
};
eprintln!("ref: {}", arguments.reference);
eprintln!("value: {value}");
println!("value={value}");
}

View File

@@ -1,38 +0,0 @@
use {
executable_path::executable_path,
std::{process::Command, str},
};
fn stdout(reference: &str) -> String {
let output = Command::new(executable_path("ref-type"))
.args(["--reference", reference])
.output()
.unwrap();
assert!(output.status.success());
String::from_utf8(output.stdout).unwrap()
}
#[test]
fn junk_is_other() {
assert_eq!(stdout("refs/tags/asdf"), "value=other\n");
}
#[test]
fn valid_version_is_release() {
assert_eq!(stdout("refs/tags/0.0.0"), "value=release\n");
}
#[test]
fn valid_version_with_trailing_characters_is_other() {
assert_eq!(stdout("refs/tags/0.0.0-rc1"), "value=other\n");
}
#[test]
fn valid_version_with_lots_of_digits_is_release() {
assert_eq!(
stdout("refs/tags/01232132.098327498374.43268473849734"),
"value=release\n"
);
}

View File

@@ -1,8 +0,0 @@
[package]
name = "update-contributors"
version = "0.0.0"
edition = "2021"
publish = false
[dependencies]
regex = "1.5.4"

View File

@@ -1,44 +0,0 @@
use {
regex::{Captures, Regex},
std::{fs, process::Command, str},
};
fn author(pr: u64) -> String {
eprintln!("#{pr}");
let output = Command::new("sh")
.args([
"-c",
&format!("gh pr view {pr} --json author | jq -r .author.login"),
])
.output()
.unwrap();
assert!(
output.status.success(),
"{}",
String::from_utf8_lossy(&output.stderr)
);
str::from_utf8(&output.stdout).unwrap().trim().to_owned()
}
fn main() {
fs::write(
"CHANGELOG.md",
&*Regex::new(r"\(#(\d+)( by @[a-z]+)?\)")
.unwrap()
.replace_all(
&fs::read_to_string("CHANGELOG.md").unwrap(),
|captures: &Captures| {
let pr = captures[1].parse().unwrap();
match author(pr).as_str() {
"casey" => format!("([#{pr}](https://github.com/casey/just/pull/{pr}))"),
contributor => {
format!("([#{pr}](https://github.com/casey/just/pull/{pr}) by [{contributor}](https://github.com/{contributor}))")
}
}
},
),
)
.unwrap();
}