├── .circleci └── config.yml ├── .dockerignore ├── .gitignore ├── .vscode └── tasks.json ├── Cargo.lock ├── Cargo.toml ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── src ├── lib.rs └── main.rs └── tests ├── output ├── test.md └── test.rs /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | docker_login: &docker_login 4 | run: 5 | name: authentication 6 | command: | 7 | docker --version 8 | docker-compose --version 9 | docker login -u "$DOCKER_USER" -p "$DOCKER_PASS" 10 | cargo_login: &cargo_login 11 | run: 12 | name: authentication 13 | command: | 14 | cargo --version 15 | cargo login "$CARGO_TOKEN" 16 | 17 | workflows: 18 | version: 2 19 | flow: 20 | jobs: 21 | - test 22 | - musl_build 23 | - docker_push: 24 | requires: 25 | - test 26 | - musl_build 27 | filters: 28 | branches: 29 | only: 30 | - master 31 | - cargo_publish: 32 | requires: 33 | - test 34 | - musl_build 35 | filters: 36 | branches: 37 | only: 38 | - master 39 | jobs: 40 | test: 41 | docker: 42 | - image: clux/muslrust 43 | working_directory: /volume 44 | steps: 45 | - checkout 46 | - restore_cache: 47 | keys: 48 | - cargo.registry-{{ checksum "Cargo.lock" }} 49 | - cargo.registry- 50 | - restore_cache: 51 | keys: 52 | - target-{{ checksum "Cargo.lock" }} 53 | - target- 54 | - run: cargo test 55 | musl_build: 56 | docker: 57 | - image: clux/muslrust 58 | working_directory: /volume 59 | steps: 60 | - checkout 61 | - restore_cache: 62 | keys: 63 | - cargo.registry-{{ checksum "Cargo.lock" }} 64 | - cargo.registry- 65 | - restore_cache: 66 | keys: 67 | - target-{{ checksum "Cargo.lock" }} 68 | - target- 69 | - run: cargo build --release 70 | - save_cache: 71 | key: target-{{ checksum "Cargo.lock" }} 72 | paths: 73 | - target 74 | - save_cache: 75 | key: cargo.registry-{{ checksum "Cargo.lock" }} 76 | paths: 77 | - /root/.cargo 78 | - persist_to_workspace: 79 | root: target/x86_64-unknown-linux-musl/release/ 80 | paths: 81 | - md-toc 82 | docker_push: 83 | machine: true 84 | steps: 85 | - checkout 86 | - attach_workspace: 87 | at: . 88 | - <<: *docker_login 89 | - run: make tag-latest 90 | - run: make tag-semver 91 | cargo_publish: 92 | docker: 93 | - image: clux/muslrust 94 | working_directory: /volume 95 | steps: 96 | - checkout 97 | - restore_cache: 98 | keys: 99 | - cargo.registry-{{ checksum "Cargo.lock" }} 100 | - cargo.registry- 101 | - restore_cache: 102 | keys: 103 | - target-{{ checksum "Cargo.lock" }} 104 | - target- 105 | - <<: *cargo_login 106 | - run: curl -sSL https://github.com/stedolan/jq/releases/download/jq-1.5/jq-linux64 --output /usr/bin/jq 107 | - run: chmod +x /usr/bin/jq 108 | - run: make cargo-publish 109 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | /.git 2 | /target 3 | **/*.rs.bk 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | 4 | /md-toc 5 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "shell", 8 | "group": "build", 9 | "label": "cargo check", 10 | "command": "cargo", 11 | "args": [ 12 | "check" 13 | ], 14 | "problemMatcher": [ 15 | "$rustc" 16 | ], 17 | "presentation": { 18 | "echo": true, 19 | "reveal": "always", 20 | "focus": false, 21 | "panel": "shared", 22 | "showReuseMessage": true 23 | } 24 | }, 25 | { 26 | "type": "shell", 27 | "group": "build", 28 | "label": "cargo clippy", 29 | "command": "cargo", 30 | "args": [ 31 | "clippy" 32 | ], 33 | "problemMatcher": [ 34 | "$rustc" 35 | ], 36 | "presentation": { 37 | "echo": true, 38 | "reveal": "always", 39 | "focus": false, 40 | "panel": "shared", 41 | "showReuseMessage": true 42 | } 43 | } 44 | ] 45 | } 46 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "getopts" 7 | version = "0.2.21" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" 10 | dependencies = [ 11 | "unicode-width", 12 | ] 13 | 14 | [[package]] 15 | name = "markdown-toc" 16 | version = "0.2.0" 17 | dependencies = [ 18 | "getopts", 19 | "percent-encoding", 20 | ] 21 | 22 | [[package]] 23 | name = "percent-encoding" 24 | version = "2.1.0" 25 | source = "registry+https://github.com/rust-lang/crates.io-index" 26 | checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" 27 | 28 | [[package]] 29 | name = "unicode-width" 30 | version = "0.1.8" 31 | source = "registry+https://github.com/rust-lang/crates.io-index" 32 | checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" 33 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "markdown-toc" 3 | version = "0.2.0" 4 | authors = ["pbzweihander "] 5 | description = "Markdown Table of Contents generator" 6 | license = "MIT" 7 | repository = "https://github.com/pbzweihander/markdown-toc" 8 | readme = "README.md" 9 | keywords = ["markdown"] 10 | 11 | [[bin]] 12 | name = "md-toc" 13 | path = "src/main.rs" 14 | 15 | [dependencies] 16 | getopts = "0.2" 17 | percent-encoding = "2.1" 18 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM scratch 2 | COPY md-toc /usr/bin/md-toc 3 | 4 | ENTRYPOINT [ "md-toc" ] 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018-2020 pbzweihander 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | NAME=markdown-toc 2 | VERSION=$(shell git rev-parse HEAD) 3 | SEMVER_VERSION=$(shell grep version Cargo.toml | awk -F"\"" '{print $$2}' | head -n 1) 4 | REPO=pbzweihander 5 | SHELL := /bin/bash 6 | 7 | compile: 8 | docker run --rm \ 9 | -v cargo-cache:/root/.cargo \ 10 | -v $$PWD:/volume \ 11 | -w /volume \ 12 | -it clux/muslrust \ 13 | cargo build --release 14 | sudo chown $$USER:$$USER -R target 15 | strip target/x86_64-unknown-linux-musl/release/md-toc 16 | mv target/x86_64-unknown-linux-musl/release/md-toc . 17 | 18 | build: 19 | @echo "Reusing built binary in current directory from make compile" 20 | @ls -lah ./md-toc 21 | docker build -t $(REPO)/$(NAME):$(VERSION) . 22 | 23 | tag-latest: build 24 | docker tag $(REPO)/$(NAME):$(VERSION) $(REPO)/$(NAME):latest 25 | docker push $(REPO)/$(NAME):latest 26 | 27 | tag-semver: build 28 | if curl -sSL https://registry.hub.docker.com/v1/repositories/$(REPO)/$(NAME)/tags | jq -r ".[].name" | grep -q $(SEMVER_VERSION); then \ 29 | echo "Tag $(SEMVER_VERSION) already exists - not publishing" ; \ 30 | else \ 31 | docker tag $(REPO)/$(NAME):$(VERSION) $(REPO)/$(NAME):$(SEMVER_VERSION) ; \ 32 | docker push $(REPO)/$(NAME):$(SEMVER_VERSION) ; \ 33 | fi 34 | 35 | cargo-publish: 36 | if curl -sSL https://crates.io/api/v1/crates/$(NAME)/versions | jq -r ".versions | .[].num" | grep -q $(SEMVER_VERSION); then \ 37 | echo "Tag $(SEMVER_VERSION) already exists - not publishing" ; \ 38 | else \ 39 | cargo publish ; \ 40 | fi 41 | 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # markdown-toc 2 | 3 | [![circleci](https://circleci.com/gh/pbzweihander/markdown-toc.svg?style=shield)](https://circleci.com/gh/pbzweihander/markdown-toc) 4 | [![crate.io](https://img.shields.io/crates/v/markdown-toc.svg)](https://crates.io/crates/markdown-toc) 5 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) 6 | 7 | Table-of-Contents (toc) generator, written in Rust. Inspired by [sebdah/markdown-toc](https://github.com/sebdah/markdown-toc). 8 | 9 | ## Table of Contents 10 | 11 | 1. [Table of Contents](#table-of-contents) 12 | 1. [Installation](#installation) 13 | 1. [Run in Docker](#run-in-docker) 14 | 1. [Install with cargo](#install-with-cargo) 15 | 1. [Build yourself](#build-yourself) 16 | 1. [Usage](#usage) 17 | 1. [Generating basic ToC](#generating-basic-toc) 18 | 1. [Customizing bullets](#customizing-bullets) 19 | 1. [Limiting the depth of headers](#limiting-the-depth-of-headers) 20 | 1. [Excluding links](#excluding-links) 21 | 1. [Customizing header of ToC](#customizing-header-of-toc) 22 | 1. [Excluding header](#excluding-header) 23 | 24 | ## Installation 25 | 26 | ### Run in Docker 27 | 28 | ```bash 29 | docker run -v $PWD:/app -w /app --rm -it pbzweihander/markdown-toc README.md 30 | ``` 31 | 32 | ### Install with cargo 33 | 34 | ```bash 35 | cargo install markdown-toc 36 | md-toc README.md 37 | ``` 38 | 39 | ### Build yourself 40 | 41 | ```bash 42 | git clone https://github.com/pbzweihander/markdown-toc.git 43 | cd markdown-toc 44 | cargo build --release 45 | cargo install --path . 46 | md-toc README.md 47 | ``` 48 | 49 | ## Usage 50 | 51 | ```bash 52 | $ md-toc -h 53 | Usage: md-toc FILE [options] 54 | 55 | FILE The Markdown file to parse for table of contents, 56 | or "-" to read from stdin 57 | 58 | Options: 59 | -h, --help print this help message 60 | --bullet {str} Custom bullet of the ToC list. (default: "1.") 61 | --indent {int} Indentation of the ToC list. (default: 4) 62 | --max-depth {int} 63 | Max depth of headers to include. 64 | --min-depth {int} 65 | Min depth of headers to include. (default: 0) 66 | --header {str} Custom header of the ToC. (default: "## Table of 67 | Contents") 68 | --no-link Exclude links in ToC 69 | --no-header Exclude the header of ToC 70 | ``` 71 | 72 | ### Generating basic ToC 73 | 74 | ```bash 75 | md-toc README.md 76 | ``` 77 | 78 | Output: 79 | 80 | ## Table of Contents 81 | 82 | 1. [markdown-toc](#markdown-toc) 83 | 1. [Table of Contents](#table-of-contents) 84 | 1. [Installation](#installation) 85 | 1. [Run in Docker](#run-in-docker) 86 | 1. [Install with cargo](#install-with-cargo) 87 | 1. [Build yourself](#build-yourself) 88 | 1. [Usage](#usage) 89 | 1. [Generating basic ToC](#generating-basic-toc) 90 | 1. [Customizing bullets](#customizing-bullets) 91 | 1. [Limiting the depth of headers](#limiting-the-depth-of-headers) 92 | 1. [Excluding links](#excluding-links) 93 | 1. [Customizing header of ToC](#customizing-header-of-toc) 94 | 1. [Excluding header](#excluding-header) 95 | 96 | ### Customizing bullets 97 | 98 | ```bash 99 | md-toc README.md --bullet "-" --indent 2 100 | ``` 101 | 102 | Output: 103 | 104 | ## Table of Contents 105 | 106 | - [markdown-toc](#markdown-toc) 107 | - [Table of Contents](#table-of-contents) 108 | - [Installation](#installation) 109 | - [Run in Docker](#run-in-docker) 110 | - [Install with cargo](#install-with-cargo) 111 | - [Build yourself](#build-yourself) 112 | - [Usage](#usage) 113 | - [Generating basic ToC](#generating-basic-toc) 114 | - [Customizing bullets](#customizing-bullets) 115 | - [Limiting the depth of headers](#limiting-the-depth-of-headers) 116 | - [Excluding links](#excluding-links) 117 | - [Customizing header of ToC](#customizing-header-of-toc) 118 | - [Excluding header](#excluding-header) 119 | 120 | ### Limiting the depth of headers 121 | 122 | ```bash 123 | md-toc README.md --min-depth 1 --max-depth 2 124 | ``` 125 | 126 | Output: 127 | 128 | ## Table of Contents 129 | 130 | 1. [Table of Contents](#table-of-contents) 131 | 1. [Installation](#installation) 132 | 1. [Run in Docker](#run-in-docker) 133 | 1. [Install with cargo](#install-with-cargo) 134 | 1. [Build yourself](#build-yourself) 135 | 1. [Usage](#usage) 136 | 1. [Generating basic ToC](#generating-basic-toc) 137 | 1. [Customizing bullets](#customizing-bullets) 138 | 1. [Limiting the depth of headers](#limiting-the-depth-of-headers) 139 | 1. [Excluding links](#excluding-links) 140 | 1. [Customizing header of ToC](#customizing-header-of-toc) 141 | 142 | ### Excluding links 143 | 144 | ```bash 145 | md-toc README.md --no-link 146 | ``` 147 | 148 | Output: 149 | 150 | ## Table of Contents 151 | 152 | 1. markdown-toc 153 | 1. Table of Contents 154 | 1. Installation 155 | 1. Run in Docker 156 | 1. Install with cargo 157 | 1. Build yourself 158 | 1. Usage 159 | 1. Generating basic ToC 160 | 1. Customizing bullets 161 | 1. Limiting the depth of headers 162 | 1. Excluding links 163 | 1. Customizing header of ToC 164 | 1. Excluding header 165 | 166 | ### Customizing header of ToC 167 | 168 | ```bash 169 | md-toc README.md --header "# ToC" 170 | ``` 171 | 172 | Output: 173 | 174 | # ToC 175 | 176 | 1. [markdown-toc](#markdown-toc) 177 | 1. [Table of Contents](#table-of-contents) 178 | 1. [Installation](#installation) 179 | 1. [Run in Docker](#run-in-docker) 180 | 1. [Install with cargo](#install-with-cargo) 181 | 1. [Build yourself](#build-yourself) 182 | 1. [Usage](#usage) 183 | 1. [Generating basic ToC](#generating-basic-toc) 184 | 1. [Customizing bullets](#customizing-bullets) 185 | 1. [Limiting the depth of headers](#limiting-the-depth-of-headers) 186 | 1. [Excluding links](#excluding-links) 187 | 1. [Customizing header of ToC](#customizing-header-of-toc) 188 | 1. [Excluding header](#excluding-header) 189 | 190 | #### Excluding header 191 | 192 | ```bash 193 | md-toc README.md --no-header 194 | ``` 195 | 196 | Output: 197 | 198 | 1. [markdown-toc](#markdown-toc) 199 | 1. [Table of Contents](#table-of-contents) 200 | 1. [Installation](#installation) 201 | 1. [Run in Docker](#run-in-docker) 202 | 1. [Install with cargo](#install-with-cargo) 203 | 1. [Build yourself](#build-yourself) 204 | 1. [Usage](#usage) 205 | 1. [Generating basic ToC](#generating-basic-toc) 206 | 1. [Customizing bullets](#customizing-bullets) 207 | 1. [Limiting the depth of headers](#limiting-the-depth-of-headers) 208 | 1. [Excluding links](#excluding-links) 209 | 1. [Customizing header of ToC](#customizing-header-of-toc) 210 | 1. [Excluding header](#excluding-header) 211 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate percent_encoding; 2 | 3 | use percent_encoding::{percent_encode, CONTROLS}; 4 | use std::path::PathBuf; 5 | use std::str::FromStr; 6 | 7 | fn slugify(text: &str) -> String { 8 | percent_encode( 9 | text 10 | .chars() 11 | .filter(|c| c.is_alphanumeric() || *c == ' ' || *c == '-') 12 | .map(|c| match c { 13 | ' ' => '-', 14 | _ => c.to_ascii_lowercase() 15 | }) 16 | .collect::() 17 | .as_bytes(), 18 | CONTROLS 19 | ).to_string() 20 | } 21 | 22 | pub struct Heading { 23 | pub depth: usize, 24 | pub title: String, 25 | } 26 | 27 | impl FromStr for Heading { 28 | type Err = (); 29 | 30 | fn from_str(s: &str) -> Result { 31 | let trimmed = s.trim_end(); 32 | if trimmed.starts_with("#") { 33 | let mut depth = 0usize; 34 | let title = trimmed 35 | .chars() 36 | .skip_while(|c| { 37 | if *c == '#' { 38 | depth += 1; 39 | true 40 | } else { 41 | false 42 | } 43 | }) 44 | .collect::() 45 | .trim_start() 46 | .to_owned(); 47 | Ok(Heading { 48 | depth: depth - 1, 49 | title, 50 | }) 51 | } else { 52 | Err(()) 53 | } 54 | } 55 | } 56 | 57 | impl Heading { 58 | pub fn format(&self, config: &Config) -> Option { 59 | if self.depth >= config.min_depth 60 | && config.max_depth.map(|d| self.depth <= d).unwrap_or(true) 61 | { 62 | Some(format!( 63 | "{}{} {}", 64 | " ".repeat(config.indent) 65 | .repeat(self.depth - config.min_depth), 66 | &config.bullet, 67 | if config.no_link { 68 | self.title.clone() 69 | } else { 70 | format!("[{}](#{})", &self.title, slugify(&self.title)) 71 | } 72 | )) 73 | } else { 74 | None 75 | } 76 | } 77 | 78 | pub fn reduce_ident(&self, config: &Config) -> Option { 79 | let ident = format!("{}", " ".repeat(config.indent)); 80 | if let Some(heading) = self.format(config) { 81 | return Some(heading.replacen(&ident, "", 1)); 82 | } 83 | 84 | None 85 | } 86 | } 87 | 88 | pub enum InputFile { 89 | Path(PathBuf), 90 | StdIn, 91 | } 92 | 93 | impl InputFile { 94 | pub fn is_file(&self) -> bool { 95 | match self { 96 | InputFile::Path(_) => true, 97 | _ => false, 98 | } 99 | } 100 | } 101 | 102 | #[derive(PartialEq, Eq)] 103 | pub enum Inline { 104 | None, 105 | Inline, 106 | InlineAndReplace, 107 | } 108 | 109 | pub struct Config { 110 | pub input_file: InputFile, 111 | pub bullet: String, 112 | pub indent: usize, 113 | pub max_depth: Option, 114 | pub min_depth: usize, 115 | pub header: Option, 116 | pub no_link: bool, 117 | pub inline: Inline, 118 | } 119 | 120 | impl Default for Config { 121 | fn default() -> Self { 122 | Config { 123 | input_file: InputFile::StdIn, 124 | bullet: String::from("1."), 125 | indent: 4, 126 | max_depth: None, 127 | min_depth: 0, 128 | no_link: false, 129 | header: Some(String::from("## Table of Contents")), 130 | inline: Inline::None, 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate getopts; 2 | extern crate markdown_toc; 3 | 4 | use markdown_toc::*; 5 | use std::fs::{self, File}; 6 | use std::io::Read; 7 | use std::path::PathBuf; 8 | use std::process; 9 | use std::str::FromStr; 10 | 11 | fn parse_command(opts: &mut getopts::Options, args: &[String]) -> Result { 12 | opts.optflag("h", "help", "print this help message") 13 | .optopt( 14 | "", 15 | "bullet", 16 | "Custom bullet of the ToC list. (default: \"1.\")", 17 | "{str}", 18 | ) 19 | .optopt( 20 | "", 21 | "indent", 22 | "Indentation of the ToC list. (default: 4)", 23 | "{int}", 24 | ) 25 | .optopt("", "max-depth", "Max depth of headers to include.", "{int}") 26 | .optopt( 27 | "", 28 | "min-depth", 29 | "Min depth of headers to include. (default: 0)", 30 | "{int}", 31 | ) 32 | .optopt( 33 | "", 34 | "header", 35 | "Custom header of the ToC. (default: \"## Table of Contents\")", 36 | "{str}", 37 | ) 38 | .optflag("", "no-link", "Exclude links in ToC") 39 | .optflag("", "no-header", "Exclude the header of ToC") 40 | .optflag( 41 | "i", 42 | "inline", 43 | "With this flag, the full markdown file will be printed with ToC." 44 | ) 45 | .optflag( 46 | "r", 47 | "replace", "Should be used with --inline option and FILE should not be stdin. The original file will be replace instead of printing to standard output." 48 | ); 49 | 50 | let opt_matches = opts.parse(args).map_err(|_| ())?; 51 | 52 | if opt_matches.opt_present("h") { 53 | return Err(()); 54 | } 55 | 56 | let default = Config::default(); 57 | 58 | macro_rules! get_opt_or_default { 59 | ($o:expr, $p:ident) => { 60 | if let Some(val) = opt_matches.opt_get($o).map_err(|_| ())? { 61 | val 62 | } else { 63 | default.$p 64 | } 65 | }; 66 | } 67 | macro_rules! get_opt_or_default_option { 68 | ($o:expr, $p:ident) => { 69 | opt_matches.opt_get($o).map_err(|_| ())?.or(default.$p) 70 | }; 71 | } 72 | 73 | let (input_file, is_input_stdin) = if !opt_matches.free.is_empty() { 74 | if opt_matches.free[0] == "-" { 75 | (InputFile::StdIn, true) 76 | } else { 77 | (InputFile::Path(PathBuf::from(&opt_matches.free[0])), false) 78 | } 79 | } else { 80 | return Err(()); 81 | }; 82 | 83 | Ok(Config { 84 | input_file, 85 | bullet: get_opt_or_default!("bullet", bullet), 86 | indent: get_opt_or_default!("indent", indent), 87 | max_depth: get_opt_or_default_option!("max-depth", max_depth), 88 | min_depth: get_opt_or_default!("min-depth", min_depth), 89 | no_link: opt_matches.opt_present("no-link"), 90 | header: if opt_matches.opt_present("no-header") { 91 | None 92 | } else { 93 | get_opt_or_default_option!("header", header) 94 | }, 95 | inline: match ( 96 | opt_matches.opt_present("inline"), 97 | opt_matches.opt_present("replace"), 98 | is_input_stdin, 99 | ) { 100 | (true, true, false) => Inline::InlineAndReplace, 101 | (true, false, _) => Inline::Inline, 102 | (false, false, _) => Inline::None, 103 | _ => return Err(()), 104 | }, 105 | }) 106 | } 107 | 108 | fn print_help(program: &str, opts: &getopts::Options) { 109 | let brief = format!("Usage: {} FILE [options]\n\n FILE The Markdown file to parse for table of contents,\n or \"-\" to read from stdin", program); 110 | eprint!("{}", opts.usage(&brief)); 111 | } 112 | 113 | fn main() { 114 | let args = std::env::args().collect::>(); 115 | let program = args[0].clone(); 116 | 117 | let mut opts = getopts::Options::new(); 118 | let config = match parse_command(&mut opts, &args[1..]) { 119 | Ok(c) => c, 120 | Err(_) => { 121 | print_help(&program, &opts); 122 | return; 123 | } 124 | }; 125 | 126 | let mut content = String::new(); 127 | match config.input_file { 128 | InputFile::StdIn => std::io::stdin().read_to_string(&mut content), 129 | InputFile::Path(ref p) => File::open(p).unwrap().read_to_string(&mut content), 130 | } 131 | .unwrap(); 132 | 133 | println!(""); 134 | 135 | if config.header.is_some() && config.inline != Inline::InlineAndReplace { 136 | println!("{}\n", config.header.as_ref().unwrap()); 137 | } 138 | 139 | let mut code_fence = Fence::None; 140 | 141 | let headings = content 142 | .lines() 143 | .filter(|line| match code_fence { 144 | Fence::None => { 145 | if line.starts_with("```") || line.starts_with("~~~") { 146 | code_fence = Fence::Open(&line[..3]); 147 | false 148 | } else { 149 | true 150 | } 151 | } 152 | Fence::Open(tag) => { 153 | if line.starts_with(tag) { 154 | code_fence = Fence::None; 155 | } 156 | false 157 | } 158 | }) 159 | .map(Heading::from_str) 160 | .filter_map(Result::ok) 161 | .collect::>(); 162 | 163 | let print_toc = || { 164 | headings 165 | .iter() 166 | .filter_map(|h| h.format(&config)) 167 | .for_each(|l| { 168 | println!("{}", l); 169 | }); 170 | }; 171 | 172 | match config.inline { 173 | Inline::Inline => { 174 | print_toc(); 175 | println!("{}", content) 176 | } 177 | Inline::InlineAndReplace if config.input_file.is_file() => { 178 | if let InputFile::Path(ref p) = config.input_file { 179 | let mut output = String::new(); 180 | 181 | if let Some(ref header) = config.header { 182 | output.push_str(&header); 183 | output.push_str("\n\n"); 184 | } 185 | 186 | let toc = headings 187 | .iter() 188 | .filter_map(|h| h.reduce_ident(&config)) 189 | .collect::>(); 190 | 191 | output.push_str(&toc.join("\n")); 192 | output.push_str(&content); 193 | 194 | fs::write(p, output).unwrap_or_else(|e| { 195 | eprintln!("Unable to write: {e}"); 196 | process::exit(0); 197 | }); 198 | } 199 | } 200 | _ => print_toc(), 201 | } 202 | } 203 | 204 | enum Fence<'e> { 205 | Open(&'e str), 206 | None, 207 | } 208 | -------------------------------------------------------------------------------- /tests/output: -------------------------------------------------------------------------------- 1 | Test Markdown Document, 0 2 | Overview, 1 3 | Philosophy, 2 4 | Inline HTML, 2 5 | Automatic Escaping for Special Characters, 2 6 | Block Elements, 1 7 | Paragraphs and Line Breaks `
`, 2 8 | Headers, 2 9 | Blockquotes, 2 10 | Lists, unordered and ordered, 2 11 | Code Blocks, 2 12 | Horizontal Rules, 2 13 | Span Elements, 1 14 | Links (Anchors), 2 15 | Emphasis, 2 16 | Code, 2 17 | Images, 2 18 | Miscellaneous, 1 19 | 1. Automatic Links, 2 20 | 2. Backslash Escapes, 2 21 | 한글 테스트, 1 22 | - [Test Markdown Document](#test-markdown-document) 23 | - [Overview](#overview) 24 | - [Philosophy](#philosophy) 25 | - [Inline HTML](#inline-html) 26 | - [Automatic Escaping for Special Characters](#automatic-escaping-for-special-characters) 27 | - [Block Elements](#block-elements) 28 | - [Paragraphs and Line Breaks `
`](#paragraphs-and-line-breaks-br-) 29 | - [Headers](#headers) 30 | - [Blockquotes](#blockquotes) 31 | - [Lists, unordered and ordered](#lists-unordered-and-ordered) 32 | - [Code Blocks](#code-blocks) 33 | - [Horizontal Rules](#horizontal-rules) 34 | - [Span Elements](#span-elements) 35 | - [Links (Anchors)](#links-anchors) 36 | - [Emphasis](#emphasis) 37 | - [Code](#code) 38 | - [Images](#images) 39 | - [Miscellaneous](#miscellaneous) 40 | - [1. Automatic Links](#1-automatic-links) 41 | - [2. Backslash Escapes](#2-backslash-escapes) 42 | - [한글 테스트](#%ED%95%9C%EA%B8%80-%ED%85%8C%EC%8A%A4%ED%8A%B8) 43 | 1. Overview 44 | 1. Block Elements 45 | 1. Span Elements 46 | 1. Miscellaneous 47 | 1. 한글 테스트 48 | -------------------------------------------------------------------------------- /tests/test.md: -------------------------------------------------------------------------------- 1 | # Test Markdown Document 2 | 3 | This document is for testing purpose. [Source](https://daringfireball.net/projects/markdown/syntax.text) 4 | 5 | ## Overview 6 | 7 | ### Philosophy 8 | 9 | Markdown is intended to be as easy-to-read and easy-to-write as is feasible. 10 | 11 | Readability, however, is emphasized above all else. A Markdown-formatted 12 | document should be publishable as-is, as plain text, without looking 13 | like it's been marked up with tags or formatting instructions. While 14 | Markdown's syntax has been influenced by several existing text-to-HTML 15 | filters -- including [Setext] [1], [atx] [2], [Textile] [3], [reStructuredText] [4], 16 | [Grutatext] [5], and [EtText] [6] -- the single biggest source of 17 | inspiration for Markdown's syntax is the format of plain text email. 18 | 19 | [1]: http://docutils.sourceforge.net/mirror/setext.html 20 | [2]: http://www.aaronsw.com/2002/atx/ 21 | [3]: http://textism.com/tools/textile/ 22 | [4]: http://docutils.sourceforge.net/rst.html 23 | [5]: http://www.triptico.com/software/grutatxt.html 24 | [6]: http://ettext.taint.org/doc/ 25 | 26 | To this end, Markdown's syntax is comprised entirely of punctuation 27 | characters, which punctuation characters have been carefully chosen so 28 | as to look like what they mean. E.g., asterisks around a word actually 29 | look like \*emphasis\*. Markdown lists look like, well, lists. Even 30 | blockquotes look like quoted passages of text, assuming you've ever 31 | used email. 32 | 33 | 34 | 35 | ### Inline HTML 36 | 37 | Markdown's syntax is intended for one purpose: to be used as a 38 | format for *writing* for the web. 39 | 40 | Markdown is not a replacement for HTML, or even close to it. Its 41 | syntax is very small, corresponding only to a very small subset of 42 | HTML tags. The idea is *not* to create a syntax that makes it easier 43 | to insert HTML tags. In my opinion, HTML tags are already easy to 44 | insert. The idea for Markdown is to make it easy to read, write, and 45 | edit prose. HTML is a *publishing* format; Markdown is a *writing* 46 | format. Thus, Markdown's formatting syntax only addresses issues that 47 | can be conveyed in plain text. 48 | 49 | For any markup that is not covered by Markdown's syntax, you simply 50 | use HTML itself. There's no need to preface it or delimit it to 51 | indicate that you're switching from Markdown to HTML; you just use 52 | the tags. 53 | 54 | The only restrictions are that block-level HTML elements -- e.g. `
`, 55 | ``, `
`, `

`, etc. -- must be separated from surrounding 56 | content by blank lines, and the start and end tags of the block should 57 | not be indented with tabs or spaces. Markdown is smart enough not 58 | to add extra (unwanted) `

` tags around HTML block-level tags. 59 | 60 | For example, to add an HTML table to a Markdown article: 61 | 62 | This is a regular paragraph. 63 | 64 |

65 | 66 | 67 | 68 |
Foo
69 | 70 | This is another regular paragraph. 71 | 72 | Note that Markdown formatting syntax is not processed within block-level 73 | HTML tags. E.g., you can't use Markdown-style `*emphasis*` inside an 74 | HTML block. 75 | 76 | Span-level HTML tags -- e.g. ``, ``, or `` -- can be 77 | used anywhere in a Markdown paragraph, list item, or header. If you 78 | want, you can even use HTML tags instead of Markdown formatting; e.g. if 79 | you'd prefer to use HTML `` or `` tags instead of Markdown's 80 | link or image syntax, go right ahead. 81 | 82 | Unlike block-level HTML tags, Markdown syntax *is* processed within 83 | span-level tags. 84 | 85 | 86 | ### Automatic Escaping for Special Characters 87 | 88 | In HTML, there are two characters that demand special treatment: `<` 89 | and `&`. Left angle brackets are used to start tags; ampersands are 90 | used to denote HTML entities. If you want to use them as literal 91 | characters, you must escape them as entities, e.g. `<`, and 92 | `&`. 93 | 94 | Ampersands in particular are bedeviling for web writers. If you want to 95 | write about 'AT&T', you need to write '`AT&T`'. You even need to 96 | escape ampersands within URLs. Thus, if you want to link to: 97 | 98 | http://images.google.com/images?num=30&q=larry+bird 99 | 100 | you need to encode the URL as: 101 | 102 | http://images.google.com/images?num=30&q=larry+bird 103 | 104 | in your anchor tag `href` attribute. Needless to say, this is easy to 105 | forget, and is probably the single most common source of HTML validation 106 | errors in otherwise well-marked-up web sites. 107 | 108 | Markdown allows you to use these characters naturally, taking care of 109 | all the necessary escaping for you. If you use an ampersand as part of 110 | an HTML entity, it remains unchanged; otherwise it will be translated 111 | into `&`. 112 | 113 | So, if you want to include a copyright symbol in your article, you can write: 114 | 115 | © 116 | 117 | and Markdown will leave it alone. But if you write: 118 | 119 | AT&T 120 | 121 | Markdown will translate it to: 122 | 123 | AT&T 124 | 125 | Similarly, because Markdown supports [inline HTML](#html), if you use 126 | angle brackets as delimiters for HTML tags, Markdown will treat them as 127 | such. But if you write: 128 | 129 | 4 < 5 130 | 131 | Markdown will translate it to: 132 | 133 | 4 < 5 134 | 135 | However, inside Markdown code spans and blocks, angle brackets and 136 | ampersands are *always* encoded automatically. This makes it easy to use 137 | Markdown to write about HTML code. (As opposed to raw HTML, which is a 138 | terrible format for writing about HTML syntax, because every single `<` 139 | and `&` in your example code needs to be escaped.) 140 | 141 | 142 | * * * 143 | 144 | 145 | ## Block Elements 146 | 147 | 148 | ### Paragraphs and Line Breaks `
` 149 | 150 | A paragraph is simply one or more consecutive lines of text, separated 151 | by one or more blank lines. (A blank line is any line that looks like a 152 | blank line -- a line containing nothing but spaces or tabs is considered 153 | blank.) Normal paragraphs should not be indented with spaces or tabs. 154 | 155 | The implication of the "one or more consecutive lines of text" rule is 156 | that Markdown supports "hard-wrapped" text paragraphs. This differs 157 | significantly from most other text-to-HTML formatters (including Movable 158 | Type's "Convert Line Breaks" option) which translate every line break 159 | character in a paragraph into a `
` tag. 160 | 161 | When you *do* want to insert a `
` break tag using Markdown, you 162 | end a line with two or more spaces, then type return. 163 | 164 | Yes, this takes a tad more effort to create a `
`, but a simplistic 165 | "every line break is a `
`" rule wouldn't work for Markdown. 166 | Markdown's email-style [blockquoting][bq] and multi-paragraph [list items][l] 167 | work best -- and look better -- when you format them with hard breaks. 168 | 169 | [bq]: #blockquote 170 | [l]: #list 171 | 172 | 173 | 174 | ### Headers 175 | 176 | Markdown supports two styles of headers, [Setext] [1] and [atx] [2]. 177 | 178 | Setext-style headers are "underlined" using equal signs (for first-level 179 | headers) and dashes (for second-level headers). For example: 180 | 181 | This is an H1 182 | ============= 183 | 184 | This is an H2 185 | ------------- 186 | 187 | Any number of underlining `=`'s or `-`'s will work. 188 | 189 | Atx-style headers use 1-6 hash characters at the start of the line, 190 | corresponding to header levels 1-6. For example: 191 | 192 | # This is an H1 193 | 194 | ## This is an H2 195 | 196 | ###### This is an H6 197 | 198 | Optionally, you may "close" atx-style headers. This is purely 199 | cosmetic -- you can use this if you think it looks better. The 200 | closing hashes don't even need to match the number of hashes 201 | used to open the header. (The number of opening hashes 202 | determines the header level.) : 203 | 204 | # This is an H1 # 205 | 206 | ## This is an H2 ## 207 | 208 | ### This is an H3 ###### 209 | 210 | 211 | ### Blockquotes 212 | 213 | Markdown uses email-style `>` characters for blockquoting. If you're 214 | familiar with quoting passages of text in an email message, then you 215 | know how to create a blockquote in Markdown. It looks best if you hard 216 | wrap the text and put a `>` before every line: 217 | 218 | > This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet, 219 | > consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus. 220 | > Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus. 221 | > 222 | > Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse 223 | > id sem consectetuer libero luctus adipiscing. 224 | 225 | Markdown allows you to be lazy and only put the `>` before the first 226 | line of a hard-wrapped paragraph: 227 | 228 | > This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet, 229 | consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus. 230 | Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus. 231 | 232 | > Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse 233 | id sem consectetuer libero luctus adipiscing. 234 | 235 | Blockquotes can be nested (i.e. a blockquote-in-a-blockquote) by 236 | adding additional levels of `>`: 237 | 238 | > This is the first level of quoting. 239 | > 240 | > > This is nested blockquote. 241 | > 242 | > Back to the first level. 243 | 244 | Blockquotes can contain other Markdown elements, including headers, lists, 245 | and code blocks: 246 | 247 | > ## This is a header. 248 | > 249 | > 1. This is the first list item. 250 | > 2. This is the second list item. 251 | > 252 | > Here's some example code: 253 | > 254 | > return shell_exec("echo $input | $markdown_script"); 255 | 256 | Any decent text editor should make email-style quoting easy. For 257 | example, with BBEdit, you can make a selection and choose Increase 258 | Quote Level from the Text menu. 259 | 260 | 261 | ### Lists, unordered and ordered 262 | 263 | Markdown supports ordered (numbered) and unordered (bulleted) lists. 264 | 265 | Unordered lists use asterisks, pluses, and hyphens -- interchangably 266 | -- as list markers: 267 | 268 | * Red 269 | * Green 270 | * Blue 271 | 272 | is equivalent to: 273 | 274 | + Red 275 | + Green 276 | + Blue 277 | 278 | and: 279 | 280 | - Red 281 | - Green 282 | - Blue 283 | 284 | Ordered lists use numbers followed by periods: 285 | 286 | 1. Bird 287 | 2. McHale 288 | 3. Parish 289 | 290 | It's important to note that the actual numbers you use to mark the 291 | list have no effect on the HTML output Markdown produces. The HTML 292 | Markdown produces from the above list is: 293 | 294 |
    295 |
  1. Bird
  2. 296 |
  3. McHale
  4. 297 |
  5. Parish
  6. 298 |
299 | 300 | If you instead wrote the list in Markdown like this: 301 | 302 | 1. Bird 303 | 1. McHale 304 | 1. Parish 305 | 306 | or even: 307 | 308 | 3. Bird 309 | 1. McHale 310 | 8. Parish 311 | 312 | you'd get the exact same HTML output. The point is, if you want to, 313 | you can use ordinal numbers in your ordered Markdown lists, so that 314 | the numbers in your source match the numbers in your published HTML. 315 | But if you want to be lazy, you don't have to. 316 | 317 | If you do use lazy list numbering, however, you should still start the 318 | list with the number 1. At some point in the future, Markdown may support 319 | starting ordered lists at an arbitrary number. 320 | 321 | List markers typically start at the left margin, but may be indented by 322 | up to three spaces. List markers must be followed by one or more spaces 323 | or a tab. 324 | 325 | To make lists look nice, you can wrap items with hanging indents: 326 | 327 | * Lorem ipsum dolor sit amet, consectetuer adipiscing elit. 328 | Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi, 329 | viverra nec, fringilla in, laoreet vitae, risus. 330 | * Donec sit amet nisl. Aliquam semper ipsum sit amet velit. 331 | Suspendisse id sem consectetuer libero luctus adipiscing. 332 | 333 | But if you want to be lazy, you don't have to: 334 | 335 | * Lorem ipsum dolor sit amet, consectetuer adipiscing elit. 336 | Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi, 337 | viverra nec, fringilla in, laoreet vitae, risus. 338 | * Donec sit amet nisl. Aliquam semper ipsum sit amet velit. 339 | Suspendisse id sem consectetuer libero luctus adipiscing. 340 | 341 | If list items are separated by blank lines, Markdown will wrap the 342 | items in `

` tags in the HTML output. For example, this input: 343 | 344 | * Bird 345 | * Magic 346 | 347 | will turn into: 348 | 349 |

    350 |
  • Bird
  • 351 |
  • Magic
  • 352 |
353 | 354 | But this: 355 | 356 | * Bird 357 | 358 | * Magic 359 | 360 | will turn into: 361 | 362 |
    363 |
  • Bird

  • 364 |
  • Magic

  • 365 |
366 | 367 | List items may consist of multiple paragraphs. Each subsequent 368 | paragraph in a list item must be indented by either 4 spaces 369 | or one tab: 370 | 371 | 1. This is a list item with two paragraphs. Lorem ipsum dolor 372 | sit amet, consectetuer adipiscing elit. Aliquam hendrerit 373 | mi posuere lectus. 374 | 375 | Vestibulum enim wisi, viverra nec, fringilla in, laoreet 376 | vitae, risus. Donec sit amet nisl. Aliquam semper ipsum 377 | sit amet velit. 378 | 379 | 2. Suspendisse id sem consectetuer libero luctus adipiscing. 380 | 381 | It looks nice if you indent every line of the subsequent 382 | paragraphs, but here again, Markdown will allow you to be 383 | lazy: 384 | 385 | * This is a list item with two paragraphs. 386 | 387 | This is the second paragraph in the list item. You're 388 | only required to indent the first line. Lorem ipsum dolor 389 | sit amet, consectetuer adipiscing elit. 390 | 391 | * Another item in the same list. 392 | 393 | To put a blockquote within a list item, the blockquote's `>` 394 | delimiters need to be indented: 395 | 396 | * A list item with a blockquote: 397 | 398 | > This is a blockquote 399 | > inside a list item. 400 | 401 | To put a code block within a list item, the code block needs 402 | to be indented *twice* -- 8 spaces or two tabs: 403 | 404 | * A list item with a code block: 405 | 406 | 407 | 408 | 409 | It's worth noting that it's possible to trigger an ordered list by 410 | accident, by writing something like this: 411 | 412 | 1986. What a great season. 413 | 414 | In other words, a *number-period-space* sequence at the beginning of a 415 | line. To avoid this, you can backslash-escape the period: 416 | 417 | 1986\. What a great season. 418 | 419 | 420 | 421 | ### Code Blocks 422 | 423 | Pre-formatted code blocks are used for writing about programming or 424 | markup source code. Rather than forming normal paragraphs, the lines 425 | of a code block are interpreted literally. Markdown wraps a code block 426 | in both `
` and `` tags.
427 | 
428 | To produce a code block in Markdown, simply indent every line of the
429 | block by at least 4 spaces or 1 tab. For example, given this input:
430 | 
431 |     This is a normal paragraph:
432 | 
433 |         This is a code block.
434 | 
435 | Markdown will generate:
436 | 
437 |     

This is a normal paragraph:

438 | 439 |
This is a code block.
440 |     
441 | 442 | One level of indentation -- 4 spaces or 1 tab -- is removed from each 443 | line of the code block. For example, this: 444 | 445 | Here is an example of AppleScript: 446 | 447 | tell application "Foo" 448 | beep 449 | end tell 450 | 451 | will turn into: 452 | 453 |

Here is an example of AppleScript:

454 | 455 |
tell application "Foo"
456 |         beep
457 |     end tell
458 |     
459 | 460 | A code block continues until it reaches a line that is not indented 461 | (or the end of the article). 462 | 463 | Within a code block, ampersands (`&`) and angle brackets (`<` and `>`) 464 | are automatically converted into HTML entities. This makes it very 465 | easy to include example HTML source code using Markdown -- just paste 466 | it and indent it, and Markdown will handle the hassle of encoding the 467 | ampersands and angle brackets. For example, this: 468 | 469 | 472 | 473 | will turn into: 474 | 475 |
<div class="footer">
476 |         &copy; 2004 Foo Corporation
477 |     </div>
478 |     
479 | 480 | Regular Markdown syntax is not processed within code blocks. E.g., 481 | asterisks are just literal asterisks within a code block. This means 482 | it's also easy to use Markdown to write about Markdown's own syntax. 483 | 484 | 485 | 486 | ### Horizontal Rules 487 | 488 | You can produce a horizontal rule tag (`
`) by placing three or 489 | more hyphens, asterisks, or underscores on a line by themselves. If you 490 | wish, you may use spaces between the hyphens or asterisks. Each of the 491 | following lines will produce a horizontal rule: 492 | 493 | * * * 494 | 495 | *** 496 | 497 | ***** 498 | 499 | - - - 500 | 501 | --------------------------------------- 502 | 503 | 504 | * * * 505 | 506 | ## Span Elements 507 | 508 | ### Links (Anchors) 509 | 510 | Markdown supports two style of links: *inline* and *reference*. 511 | 512 | In both styles, the link text is delimited by [square brackets]. 513 | 514 | To create an inline link, use a set of regular parentheses immediately 515 | after the link text's closing square bracket. Inside the parentheses, 516 | put the URL where you want the link to point, along with an *optional* 517 | title for the link, surrounded in quotes. For example: 518 | 519 | This is [an example](http://example.com/ "Title") inline link. 520 | 521 | [This link](http://example.net/) has no title attribute. 522 | 523 | Will produce: 524 | 525 |

This is 526 | an example inline link.

527 | 528 |

This link has no 529 | title attribute.

530 | 531 | If you're referring to a local resource on the same server, you can 532 | use relative paths: 533 | 534 | See my [About](/about/) page for details. 535 | 536 | Reference-style links use a second set of square brackets, inside 537 | which you place a label of your choosing to identify the link: 538 | 539 | This is [an example][id] reference-style link. 540 | 541 | You can optionally use a space to separate the sets of brackets: 542 | 543 | This is [an example] [id] reference-style link. 544 | 545 | Then, anywhere in the document, you define your link label like this, 546 | on a line by itself: 547 | 548 | [id]: http://example.com/ "Optional Title Here" 549 | 550 | That is: 551 | 552 | * Square brackets containing the link identifier (optionally 553 | indented from the left margin using up to three spaces); 554 | * followed by a colon; 555 | * followed by one or more spaces (or tabs); 556 | * followed by the URL for the link; 557 | * optionally followed by a title attribute for the link, enclosed 558 | in double or single quotes, or enclosed in parentheses. 559 | 560 | The following three link definitions are equivalent: 561 | 562 | [foo]: http://example.com/ "Optional Title Here" 563 | [foo]: http://example.com/ 'Optional Title Here' 564 | [foo]: http://example.com/ (Optional Title Here) 565 | 566 | **Note:** There is a known bug in Markdown.pl 1.0.1 which prevents 567 | single quotes from being used to delimit link titles. 568 | 569 | The link URL may, optionally, be surrounded by angle brackets: 570 | 571 | [id]: "Optional Title Here" 572 | 573 | You can put the title attribute on the next line and use extra spaces 574 | or tabs for padding, which tends to look better with longer URLs: 575 | 576 | [id]: http://example.com/longish/path/to/resource/here 577 | "Optional Title Here" 578 | 579 | Link definitions are only used for creating links during Markdown 580 | processing, and are stripped from your document in the HTML output. 581 | 582 | Link definition names may consist of letters, numbers, spaces, and 583 | punctuation -- but they are *not* case sensitive. E.g. these two 584 | links: 585 | 586 | [link text][a] 587 | [link text][A] 588 | 589 | are equivalent. 590 | 591 | The *implicit link name* shortcut allows you to omit the name of the 592 | link, in which case the link text itself is used as the name. 593 | Just use an empty set of square brackets -- e.g., to link the word 594 | "Google" to the google.com web site, you could simply write: 595 | 596 | [Google][] 597 | 598 | And then define the link: 599 | 600 | [Google]: http://google.com/ 601 | 602 | Because link names may contain spaces, this shortcut even works for 603 | multiple words in the link text: 604 | 605 | Visit [Daring Fireball][] for more information. 606 | 607 | And then define the link: 608 | 609 | [Daring Fireball]: http://daringfireball.net/ 610 | 611 | Link definitions can be placed anywhere in your Markdown document. I 612 | tend to put them immediately after each paragraph in which they're 613 | used, but if you want, you can put them all at the end of your 614 | document, sort of like footnotes. 615 | 616 | Here's an example of reference links in action: 617 | 618 | I get 10 times more traffic from [Google] [1] than from 619 | [Yahoo] [2] or [MSN] [3]. 620 | 621 | [1]: http://google.com/ "Google" 622 | [2]: http://search.yahoo.com/ "Yahoo Search" 623 | [3]: http://search.msn.com/ "MSN Search" 624 | 625 | Using the implicit link name shortcut, you could instead write: 626 | 627 | I get 10 times more traffic from [Google][] than from 628 | [Yahoo][] or [MSN][]. 629 | 630 | [google]: http://google.com/ "Google" 631 | [yahoo]: http://search.yahoo.com/ "Yahoo Search" 632 | [msn]: http://search.msn.com/ "MSN Search" 633 | 634 | Both of the above examples will produce the following HTML output: 635 | 636 |

I get 10 times more traffic from Google than from 638 | Yahoo 639 | or MSN.

640 | 641 | For comparison, here is the same paragraph written using 642 | Markdown's inline link style: 643 | 644 | I get 10 times more traffic from [Google](http://google.com/ "Google") 645 | than from [Yahoo](http://search.yahoo.com/ "Yahoo Search") or 646 | [MSN](http://search.msn.com/ "MSN Search"). 647 | 648 | The point of reference-style links is not that they're easier to 649 | write. The point is that with reference-style links, your document 650 | source is vastly more readable. Compare the above examples: using 651 | reference-style links, the paragraph itself is only 81 characters 652 | long; with inline-style links, it's 176 characters; and as raw HTML, 653 | it's 234 characters. In the raw HTML, there's more markup than there 654 | is text. 655 | 656 | With Markdown's reference-style links, a source document much more 657 | closely resembles the final output, as rendered in a browser. By 658 | allowing you to move the markup-related metadata out of the paragraph, 659 | you can add links without interrupting the narrative flow of your 660 | prose. 661 | 662 | 663 | ### Emphasis 664 | 665 | Markdown treats asterisks (`*`) and underscores (`_`) as indicators of 666 | emphasis. Text wrapped with one `*` or `_` will be wrapped with an 667 | HTML `` tag; double `*`'s or `_`'s will be wrapped with an HTML 668 | `` tag. E.g., this input: 669 | 670 | *single asterisks* 671 | 672 | _single underscores_ 673 | 674 | **double asterisks** 675 | 676 | __double underscores__ 677 | 678 | will produce: 679 | 680 | single asterisks 681 | 682 | single underscores 683 | 684 | double asterisks 685 | 686 | double underscores 687 | 688 | You can use whichever style you prefer; the lone restriction is that 689 | the same character must be used to open and close an emphasis span. 690 | 691 | Emphasis can be used in the middle of a word: 692 | 693 | un*frigging*believable 694 | 695 | But if you surround an `*` or `_` with spaces, it'll be treated as a 696 | literal asterisk or underscore. 697 | 698 | To produce a literal asterisk or underscore at a position where it 699 | would otherwise be used as an emphasis delimiter, you can backslash 700 | escape it: 701 | 702 | \*this text is surrounded by literal asterisks\* 703 | 704 | 705 | 706 | ### Code 707 | 708 | To indicate a span of code, wrap it with backtick quotes (`` ` ``). 709 | Unlike a pre-formatted code block, a code span indicates code within a 710 | normal paragraph. For example: 711 | 712 | Use the `printf()` function. 713 | 714 | will produce: 715 | 716 |

Use the printf() function.

717 | 718 | To include a literal backtick character within a code span, you can use 719 | multiple backticks as the opening and closing delimiters: 720 | 721 | ``There is a literal backtick (`) here.`` 722 | 723 | which will produce this: 724 | 725 |

There is a literal backtick (`) here.

726 | 727 | The backtick delimiters surrounding a code span may include spaces -- 728 | one after the opening, one before the closing. This allows you to place 729 | literal backtick characters at the beginning or end of a code span: 730 | 731 | A single backtick in a code span: `` ` `` 732 | 733 | A backtick-delimited string in a code span: `` `foo` `` 734 | 735 | will produce: 736 | 737 |

A single backtick in a code span: `

738 | 739 |

A backtick-delimited string in a code span: `foo`

740 | 741 | With a code span, ampersands and angle brackets are encoded as HTML 742 | entities automatically, which makes it easy to include example HTML 743 | tags. Markdown will turn this: 744 | 745 | Please don't use any `` tags. 746 | 747 | into: 748 | 749 |

Please don't use any <blink> tags.

750 | 751 | You can write this: 752 | 753 | `—` is the decimal-encoded equivalent of `—`. 754 | 755 | to produce: 756 | 757 |

&#8212; is the decimal-encoded 758 | equivalent of &mdash;.

759 | 760 | 761 | 762 | ### Images 763 | 764 | Admittedly, it's fairly difficult to devise a "natural" syntax for 765 | placing images into a plain text document format. 766 | 767 | Markdown uses an image syntax that is intended to resemble the syntax 768 | for links, allowing for two styles: *inline* and *reference*. 769 | 770 | Inline image syntax looks like this: 771 | 772 | ![Alt text](/path/to/img.jpg) 773 | 774 | ![Alt text](/path/to/img.jpg "Optional title") 775 | 776 | That is: 777 | 778 | * An exclamation mark: `!`; 779 | * followed by a set of square brackets, containing the `alt` 780 | attribute text for the image; 781 | * followed by a set of parentheses, containing the URL or path to 782 | the image, and an optional `title` attribute enclosed in double 783 | or single quotes. 784 | 785 | Reference-style image syntax looks like this: 786 | 787 | ![Alt text][id] 788 | 789 | Where "id" is the name of a defined image reference. Image references 790 | are defined using syntax identical to link references: 791 | 792 | [id]: url/to/image "Optional title attribute" 793 | 794 | As of this writing, Markdown has no syntax for specifying the 795 | dimensions of an image; if this is important to you, you can simply 796 | use regular HTML `` tags. 797 | 798 | 799 | * * * 800 | 801 | 802 | ## Miscellaneous 803 | 804 | ### 1. Automatic Links 805 | 806 | Markdown supports a shortcut style for creating "automatic" links for URLs and email addresses: simply surround the URL or email address with angle brackets. What this means is that if you want to show the actual text of a URL or email address, and also have it be a clickable link, you can do this: 807 | 808 | 809 | 810 | Markdown will turn this into: 811 | 812 | http://example.com/ 813 | 814 | Automatic links for email addresses work similarly, except that 815 | Markdown will also perform a bit of randomized decimal and hex 816 | entity-encoding to help obscure your address from address-harvesting 817 | spambots. For example, Markdown will turn this: 818 | 819 | 820 | 821 | into something like this: 822 | 823 | address@exa 826 | mple.com 827 | 828 | which will render in a browser as a clickable link to "address@example.com". 829 | 830 | (This sort of entity-encoding trick will indeed fool many, if not 831 | most, address-harvesting bots, but it definitely won't fool all of 832 | them. It's better than nothing, but an address published in this way 833 | will probably eventually start receiving spam.) 834 | 835 | 836 | 837 | ### 2. Backslash Escapes 838 | 839 | Markdown allows you to use backslash escapes to generate literal 840 | characters which would otherwise have special meaning in Markdown's 841 | formatting syntax. For example, if you wanted to surround a word 842 | with literal asterisks (instead of an HTML `` tag), you can use 843 | backslashes before the asterisks, like this: 844 | 845 | \*literal asterisks\* 846 | 847 | Markdown provides backslash escapes for the following characters: 848 | 849 | \ backslash 850 | ` backtick 851 | * asterisk 852 | _ underscore 853 | {} curly braces 854 | [] square brackets 855 | () parentheses 856 | # hash mark 857 | + plus sign 858 | - minus sign (hyphen) 859 | . dot 860 | ! exclamation mark 861 | 862 | ## 한글 테스트 863 | 864 | 유니코드 테스트 865 | -------------------------------------------------------------------------------- /tests/test.rs: -------------------------------------------------------------------------------- 1 | extern crate markdown_toc; 2 | 3 | use markdown_toc::*; 4 | use std::str::FromStr; 5 | 6 | static TEST_DOC: &'static str = include_str!("test.md"); 7 | static OUTPUT: &'static str = include_str!("output"); 8 | 9 | #[test] 10 | fn test() { 11 | let content = String::from(TEST_DOC); 12 | 13 | let heads: Vec<_> = content 14 | .lines() 15 | .map(Heading::from_str) 16 | .filter_map(Result::ok) 17 | .collect(); 18 | 19 | let expected_vec: Vec<_> = OUTPUT.lines().collect(); 20 | 21 | let first_config = Config { 22 | bullet: "-".to_string(), 23 | indent: 2, 24 | ..Default::default() 25 | }; 26 | let second_config = Config { 27 | max_depth: Some(1), 28 | min_depth: 1, 29 | no_link: true, 30 | ..Default::default() 31 | }; 32 | 33 | let actual_vec: Vec<_> = heads 34 | .iter() 35 | .map(|h| format!("{}, {}", &h.title, &h.depth)) 36 | .chain(heads.iter().filter_map(|h| h.format(&first_config))) 37 | .chain(heads.iter().filter_map(|h| h.format(&second_config))) 38 | .collect(); 39 | 40 | assert_eq!(actual_vec.len(), expected_vec.len()); 41 | for (actual, expected) in actual_vec.into_iter().zip(expected_vec) { 42 | assert_eq!(actual, expected); 43 | } 44 | } 45 | --------------------------------------------------------------------------------