diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 6838143..a29ae96 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -80,6 +80,16 @@ jobs: with: mdbook-version: latest + - name: Install `mdbook-linkcheck` + if: ${{ matrix.os == 'ubuntu-latest' }} + run: | + mkdir -p mdbook-linkcheck + cd mdbook-linkcheck + wget https://github.com/Michael-F-Bryan/mdbook-linkcheck/releases/latest/download/mdbook-linkcheck.x86_64-unknown-linux-gnu.zip + unzip mdbook-linkcheck.x86_64-unknown-linux-gnu.zip + chmod +x mdbook-linkcheck + pwd >> $GITHUB_PATH + - name: Build book if: ${{ matrix.os == 'ubuntu-latest' }} run: | diff --git a/.gitignore b/.gitignore index 410265d..13702ea 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ /.vagrant /README.html +/book/en/build /book/en/src +/book/zh/build /book/zh/src /fuzz/artifacts /fuzz/corpus @@ -9,4 +11,3 @@ /test-utilities/Cargo.lock /test-utilities/target /tmp -/www/man diff --git a/README.md b/README.md index 42f904b..4f752ce 100644 --- a/README.md +++ b/README.md @@ -313,7 +313,7 @@ Kakoune supports `justfile` syntax highlighting out of the box, thanks to TeddyD ### Sublime Text -A syntax file for Sublime Text written by TonioGela is available in [extras/just.sublime-syntax](extras/just.sublime-syntax). +A syntax file for Sublime Text written by TonioGela is available in [extras/just.sublime-syntax](https://github.com/casey/just/blob/master/extras/just.sublime-syntax). ### Other Editors @@ -940,7 +940,7 @@ $ just system-info This is an x86_64 machine ``` -The `os_family()` function can be used to create cross-platform `justfile`s that work on various operating systems. For an example, see [cross-platform.just](examples/cross-platform.just) file. +The `os_family()` function can be used to create cross-platform `justfile`s that work on various operating systems. For an example, see [cross-platform.just](https://github.com/casey/just/blob/master/examples/cross-platform.just) file. #### Environment Variables @@ -1946,7 +1946,7 @@ complete -F _just -o bashdefault -o default j ### Shell Completion Scripts -Shell completion scripts for Bash, Zsh, Fish, PowerShell, and Elvish are available in the [completions](completions) directory. Please refer to your shell's documentation for how to install them. +Shell completion scripts for Bash, Zsh, Fish, PowerShell, and Elvish are available in the [completions](https://github.com/casey/just/tree/master/completions) directory. Please refer to your shell's documentation for how to install them. The `just` binary can also generate the same completion scripts at runtime, using the `--completions` command: @@ -1956,11 +1956,11 @@ $ just --completions zsh > just.zsh ### Grammar -A non-normative grammar of `justfile`s can be found in [GRAMMAR.md](GRAMMAR.md). +A non-normative grammar of `justfile`s can be found in [GRAMMAR.md](https://github.com/casey/just/blob/master/GRAMMAR.md). ### just.sh -Before `just` was a fancy Rust program it was a tiny shell script that called `make`. You can find the old version in [extras/just.sh](extras/just.sh). +Before `just` was a fancy Rust program it was a tiny shell script that called `make`. You can find the old version in [extras/just.sh](https://github.com/casey/just/blob/master/extras/just.sh). ### User `justfile`s @@ -2099,7 +2099,7 @@ Some ideas for recipes: Even for small, personal projects it's nice to be able to remember commands by name instead of ^Reverse searching your shell history, and it's a huge boon to be able to go into an old project written in a random language with a mysterious build system and know that all the commands you need to do whatever you need to do are in the `justfile`, and that if you type `just` something useful (or at least interesting!) will probably happen. -For ideas for recipes, check out [this project's `justfile`](justfile), or some of the `justfile`s [out in the wild](https://github.com/search?o=desc&q=filename%3Ajustfile&s=indexed&type=Code). +For ideas for recipes, check out [this project's `justfile`](https://github.com/casey/just/blob/master/justfile), or some of the `justfile`s [out in the wild](https://github.com/search?o=desc&q=filename%3Ajustfile&s=indexed&type=Code). Anyways, I think that's about it for this incredibly long-winded README. diff --git a/README.中文.md b/README.中文.md index e6283c5..79d327e 100644 --- a/README.中文.md +++ b/README.中文.md @@ -309,7 +309,7 @@ Kakoune 已经内置支持 `justfile` 语法高亮,这要感谢 TeddyDD。 ### Sublime Text -由 TonioGela 编写的 Sublime Text 的语法高亮文件在 [extras/just.sublim-syntax](extras/just.sublim-syntax) 中提供。 +由 TonioGela 编写的 Sublime Text 的语法高亮文件在 [extras/just.sublim-syntax](https://github.com/casey/just/blob/master/extras/just.sublime-syntax) 中提供。 ### 其它编辑器 @@ -936,7 +936,7 @@ $ just system-info This is an x86_64 machine ``` -`os_family()` 函数可以用来创建跨平台的 `justfile`,使其可以在不同的操作系统上工作。一个例子,见 [cross-platform.just](examples/cross-platform.just) 文件。 +`os_family()` 函数可以用来创建跨平台的 `justfile`,使其可以在不同的操作系统上工作。一个例子,见 [cross-platform.just](https://github.com/casey/just/blob/master/examples/cross-platform.just) 文件。 #### 环境变量 @@ -1941,7 +1941,7 @@ complete -F _just -o bashdefault -o default j ### Shell 自动补全脚本 -Bash、Zsh、Fish、PowerShell 和 Elvish 的 Shell 自动补全脚本可以在 [自动补全](completions) 目录下找到。关于如何安装它们,请参考你的 Shell 文档。 +Bash、Zsh、Fish、PowerShell 和 Elvish 的 Shell 自动补全脚本可以在 [自动补全](https://github.com/casey/just/tree/master/completions) 目录下找到。关于如何安装它们,请参考你的 Shell 文档。 `just` 二进制文件也可以在运行时生成相同的自动补全脚本,使用 `--completions` 命令即可,如下: @@ -1951,11 +1951,11 @@ $ just --completions zsh > just.zsh ### 语法 -在 [GRAMMAR.md](GRAMMAR.md) 中可以找到一个非正式的 `justfile` 语法说明。 +在 [GRAMMAR.md](https://github.com/casey/just/blob/master/GRAMMAR.md) 中可以找到一个非正式的 `justfile` 语法说明。 ### just.sh -在 `just` 成为一个精致的 Rust 程序之前,它是一个很小的 Shell 脚本,叫 `make`。你可以在 [extras/just.sh](extras/just.sh) 中找到旧版本。 +在 `just` 成为一个精致的 Rust 程序之前,它是一个很小的 Shell 脚本,叫 `make`。你可以在 [extras/just.sh](https://github.com/casey/just/blob/master/extras/just.sh) 中找到旧版本。 ### 用户 `justfile` @@ -2094,7 +2094,7 @@ make: `test' is up to date. 即使是小型的个人项目,能够通过名字记住命令,而不是通过 ^Reverse 搜索你的 Shell 历史,这也是一个巨大的福音,能够进入一个用任意语言编写的旧项目,并知道你需要用到的所有命令都在 `justfile` 中,如果你输入 `just`,就可能会输出一些有用的(或至少是有趣的!)信息。 -关于配方的想法,请查看 [这个项目的 `justfile`](justfile),或一些 [在其他项目里](https://github.com/search?o=desc&q=filename%3Ajustfile&s=indexed&type=Code) 的 `justfile`。 +关于配方的想法,请查看 [这个项目的 `justfile`](https://github.com/casey/just/blob/master/justfile),或一些 [在其他项目里](https://github.com/search?o=desc&q=filename%3Ajustfile&s=indexed&type=Code) 的 `justfile`。 总之,我想这个令人难以置信地啰嗦的 README 就到此为止了。 diff --git a/bin/generate-book/src/main.rs b/bin/generate-book/src/main.rs index 54a40d6..4dc9cac 100644 --- a/bin/generate-book/src/main.rs +++ b/bin/generate-book/src/main.rs @@ -1,13 +1,12 @@ use { - pulldown_cmark::{ - Event, - HeadingLevel::{H2, H3}, - Options, Parser, Tag, - }, + pulldown_cmark::{CowStr, Event, HeadingLevel, Options, Parser, Tag}, pulldown_cmark_to_cmark::cmark, - std::{error::Error, fs}, + std::{collections::BTreeMap, error::Error, fs, ops::Deref}, }; +type Result = std::result::Result>; + +#[derive(Copy, Clone, Debug)] enum Language { English, Chinese, @@ -36,7 +35,61 @@ impl Language { } } -fn main() -> Result<(), Box> { +#[derive(Debug)] +struct Chapter<'a> { + level: HeadingLevel, + events: Vec>, + 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 { + let mut markdown = String::new(); + cmark(self.events.iter(), &mut markdown)?; + if self.index == 0 { + markdown = markdown.split_inclusive('\n').skip(1).collect::(); + } + 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(); @@ -44,34 +97,88 @@ fn main() -> Result<(), Box> { let txt = fs::read_to_string(format!("README{}.md", language.suffix()))?; - let mut chapters = vec![(1usize, Vec::new())]; + 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 @ (H2 | H3), ..)) = event { - chapters.push((if level == H2 { 2 } else { 3 }, Vec::new())); + 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::(); + 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]); + } + } } - chapters.last_mut().unwrap().1.push(event); } let mut summary = String::new(); - for (i, (level, chapter)) in chapters.into_iter().enumerate() { - let mut txt = String::new(); - cmark(chapter.iter(), &mut txt)?; - let title = if i == 0 { - txt = txt.split_inclusive('\n').skip(1).collect::(); - language.introduction() - } else { - txt.lines().next().unwrap().split_once(' ').unwrap().1 + 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, }; - - let path = format!("{}/chapter_{}.md", src, i + 1); - fs::write(&path, &txt)?; summary.push_str(&format!( "{}- [{}](chapter_{}.md)\n", - " ".repeat((level.saturating_sub(1)) * 4), - title, - i + 1 + " ".repeat(indent * 4), + chapter.title(), + chapter.number() )); } diff --git a/book/en/book.toml b/book/en/book.toml index 22d0435..dfef328 100644 --- a/book/en/book.toml +++ b/book/en/book.toml @@ -5,4 +5,8 @@ src = "src" title = "Just Programmer's Manual" [build] -build-dir = "../../www/man/en" +build-dir = "build" + +[output.html] + +[output.linkcheck] diff --git a/book/zh/book.toml b/book/zh/book.toml index 9ba38d8..429d9d2 100644 --- a/book/zh/book.toml +++ b/book/zh/book.toml @@ -5,4 +5,8 @@ src = "src" title = "Just 用户指南" [build] -build-dir = "../../www/man/zh" +build-dir = "build" + +[output.html] + +[output.linkcheck] diff --git a/justfile b/justfile index ec454ba..847c96d 100755 --- a/justfile +++ b/justfile @@ -165,6 +165,11 @@ watch-readme: generate-completions: ./bin/generate-completions +build-book: + cargo run --package generate-book + mdbook build book/en + mdbook build book/zh + # run all polyglot recipes polyglot: _python _js _perl _sh _ruby # (recipes that start with `_` are hidden from --list) diff --git a/www/man/en b/www/man/en new file mode 120000 index 0000000..089aa2a --- /dev/null +++ b/www/man/en @@ -0,0 +1 @@ +../../book/en/build/html/ \ No newline at end of file diff --git a/www/man/zh b/www/man/zh new file mode 120000 index 0000000..acf8994 --- /dev/null +++ b/www/man/zh @@ -0,0 +1 @@ +../../book/zh/build/html/ \ No newline at end of file