├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ ├── feature_request.md
│ └── question.md
└── workflows
│ ├── ci.yml
│ └── dockerimage.yml
├── .gitignore
├── Dockerfile
├── LICENSE
├── Makefile
├── README.md
├── gh-md-toc
└── tests
├── test directory
├── test_backquote.md
├── test_filepathwithspace.md
├── test_nonenglishchars.md
├── test_plussign.md
└── test_setextwithformatting.md
├── test_helper.bash
└── tests.bats
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: "[ BUG ] ..."
5 | labels: bug
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior
15 |
16 | **Expected behavior**
17 | A clear and concise description of what you expected to happen.
18 |
19 | **Environment (please complete the following information):**
20 | - OS: [e.g. iOS]
21 | - Version [e.g. 0.7]
22 |
23 | **Additional context**
24 | Add any other context about the problem here.
25 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: "[Feature]"
5 | labels: enhancement
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/question.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Question
3 | about: Got a question? ask it here
4 | title: "[Question]"
5 | labels: question
6 | assignees: ''
7 |
8 | ---
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | schedule:
5 | # twice a week, on sundays, fridays at 8:00am UTC
6 | - cron: "0 8 * * 0,5"
7 | push:
8 | branches: [ master ]
9 | pull_request:
10 | branches: [ master ]
11 |
12 | # Allows you to run this workflow manually from the Actions tab
13 | workflow_dispatch:
14 |
15 | jobs:
16 | build:
17 | runs-on: ubuntu-latest
18 | steps:
19 | - name: Setup bats
20 | uses: mig4/setup-bats@v1
21 | with:
22 | bats-version: 1.6.0
23 |
24 | - name: Check out code
25 | uses: actions/checkout@v2
26 |
27 | - name: Run tests
28 | run: make test
29 | env:
30 | GH_TOC_TOKEN: ${{ secrets.GITHUB_TOKEN }}
31 |
--------------------------------------------------------------------------------
/.github/workflows/dockerimage.yml:
--------------------------------------------------------------------------------
1 | name: Publish Docker image
2 | on:
3 | release:
4 | types: [published]
5 |
6 | jobs:
7 | push_to_registry:
8 | name: Push Docker image to Docker Hub
9 | runs-on: ubuntu-latest
10 | steps:
11 | - name: Check out the repo
12 | uses: actions/checkout@v2
13 | - name: Push to Docker Hub
14 | uses: docker/build-push-action@v1
15 | with:
16 | username: ${{ secrets.DOCKER_USERNAME }}
17 | password: ${{ secrets.DOCKER_PASSWORD }}
18 | repository: evkalinin/gh-md-toc
19 | tag_with_ref: true
20 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.swp
2 | token.txt
3 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM debian
2 |
3 | RUN apt update -y && \
4 | apt upgrade -y && \
5 | apt install curl -y
6 |
7 | WORKDIR app
8 |
9 | COPY gh-md-toc .
10 |
11 | RUN chmod +x gh-md-toc
12 |
13 | ENTRYPOINT ["./gh-md-toc"]
14 | CMD []
15 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Eugene Kalinin
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | 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, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | release: test
2 | @git tag `grep -o -E '[0-9]\.[0-9]{2}\.[0-9]{1,2}' gh-md-toc`
3 | @git push --tags origin master
4 |
5 | test:
6 | @bats tests
7 |
8 | lint:
9 | @shellcheck -e SC2008 gh-md-toc
10 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | gh-md-toc
2 | =========
3 |
4 | [](https://github.com/ekalinin/github-markdown-toc/actions/workflows/ci.yml)
5 | 
6 |
7 | gh-md-toc — is for you if you **want to generate TOC** (Table Of Content) for a README.md or
8 | a GitHub wiki page **without installing additional software**.
9 |
10 | It's my try to fix a problem:
11 |
12 | * [github/issues/215](https://github.com/isaacs/github/issues/215)
13 |
14 | gh-md-toc is able to process:
15 |
16 | * stdin
17 | * local files (markdown files in local file system)
18 | * remote files (html files on github.com)
19 |
20 | gh-md-toc tested on Ubuntu, and macOS High Sierra (gh-md-toc release 0.4.9). If you want it on Windows, you
21 | better to use a golang based implementation:
22 |
23 | * [github-markdown-toc.go](https://github.com/ekalinin/github-markdown-toc.go)
24 |
25 | It's more solid, reliable and with ability of a parallel processing. And
26 | absolutely without dependencies.
27 |
28 | Table of contents
29 | =================
30 |
31 |
32 | * [Installation](#installation)
33 | * [Usage](#usage)
34 | * [STDIN](#stdin)
35 | * [Local files](#local-files)
36 | * [Remote files](#remote-files)
37 | * [Multiple files](#multiple-files)
38 | * [Combo](#combo)
39 | * [Auto insert and update TOC](#auto-insert-and-update-toc)
40 | * [GitHub token](#github-token)
41 | * [TOC generation with Github Actions](#toc-generation-with-github-actions)
42 | * [Tests](#tests)
43 | * [Dependency](#dependency)
44 | * [Docker](#docker)
45 | * [Local](#local)
46 | * [Public](#public)
47 |
48 |
49 |
50 | Installation
51 | ============
52 |
53 | Linux (manual installation)
54 | ```bash
55 | $ wget https://raw.githubusercontent.com/ekalinin/github-markdown-toc/master/gh-md-toc
56 | $ chmod a+x gh-md-toc
57 | ```
58 |
59 | MacOS (manual installation)
60 | ```bash
61 | $ curl https://raw.githubusercontent.com/ekalinin/github-markdown-toc/master/gh-md-toc -o gh-md-toc
62 | $ chmod a+x gh-md-toc
63 | ```
64 |
65 | Linux or MacOS (using [Basher](https://github.com/basherpm/basher))
66 | ```bash
67 | $ basher install ekalinin/github-markdown-toc
68 | # `gh-md-toc` will automatically be available in the PATH
69 | ```
70 |
71 | Usage
72 | =====
73 |
74 |
75 | STDIN
76 | -----
77 |
78 | Here's an example of TOC creating for markdown from STDIN:
79 |
80 | ```bash
81 | ➥ cat ~/projects/Dockerfile.vim/README.md | ./gh-md-toc -
82 | * [Dockerfile.vim](#dockerfilevim)
83 | * [Screenshot](#screenshot)
84 | * [Installation](#installation)
85 | * [OR using Pathogen:](#or-using-pathogen)
86 | * [OR using Vundle:](#or-using-vundle)
87 | * [License](#license)
88 | ```
89 |
90 | Local files
91 | -----------
92 |
93 | Here's an example of TOC creating for a local README.md:
94 |
95 | ```bash
96 | ➥ ./gh-md-toc ~/projects/Dockerfile.vim/README.md
97 |
98 |
99 | Table of Contents
100 | =================
101 |
102 | * [Dockerfile.vim](#dockerfilevim)
103 | * [Screenshot](#screenshot)
104 | * [Installation](#installation)
105 | * [OR using Pathogen:](#or-using-pathogen)
106 | * [OR using Vundle:](#or-using-vundle)
107 | * [License](#license)
108 | ```
109 |
110 | Remote files
111 | ------------
112 |
113 | And here's an example, when you have a README.md like this:
114 |
115 | * [README.md without TOC](https://github.com/ekalinin/envirius/blob/f939d3b6882bfb6ecb28ef7b6e62862f934ba945/README.md)
116 |
117 | And you want to generate TOC for it.
118 |
119 | There is nothing easier:
120 |
121 | ```bash
122 | ➥ ./gh-md-toc https://github.com/ekalinin/envirius/blob/master/README.md
123 |
124 | Table of Contents
125 | =================
126 |
127 | * [envirius](#envirius)
128 | * [Idea](#idea)
129 | * [Features](#features)
130 | * [Installation](#installation)
131 | * [Uninstallation](#uninstallation)
132 | * [Available plugins](#available-plugins)
133 | * [Usage](#usage)
134 | * [Check available plugins](#check-available-plugins)
135 | * [Check available versions for each plugin](#check-available-versions-for-each-plugin)
136 | * [Create an environment](#create-an-environment)
137 | * [Activate/deactivate environment](#activatedeactivate-environment)
138 | * [Activating in a new shell](#activating-in-a-new-shell)
139 | * [Activating in the same shell](#activating-in-the-same-shell)
140 | * [Get list of environments](#get-list-of-environments)
141 | * [Get current activated environment](#get-current-activated-environment)
142 | * [Do something in environment without enabling it](#do-something-in-environment-without-enabling-it)
143 | * [Get help](#get-help)
144 | * [Get help for a command](#get-help-for-a-command)
145 | * [How to add a plugin?](#how-to-add-a-plugin)
146 | * [Mandatory elements](#mandatory-elements)
147 | * [plug_list_versions](#plug_list_versions)
148 | * [plug_url_for_download](#plug_url_for_download)
149 | * [plug_build](#plug_build)
150 | * [Optional elements](#optional-elements)
151 | * [Variables](#variables)
152 | * [Functions](#functions)
153 | * [Examples](#examples)
154 | * [Example of the usage](#example-of-the-usage)
155 | * [Dependencies](#dependencies)
156 | * [Supported OS](#supported-os)
157 | * [Tests](#tests)
158 | * [Version History](#version-history)
159 | * [License](#license)
160 | * [README in another language](#readme-in-another-language)
161 | ```
162 |
163 | That's all! Now all you need — is copy/paste result from console into original
164 | README.md.
165 |
166 | If you do not want to copy from console you can add `> YOURFILENAME.md` at the end of the command like `./gh-md-toc https://github.com/ekalinin/envirius/blob/master/README.md > table-of-contents.md` and this will store the table of contents to a file named table-of-contents.md in your current folder.
167 |
168 | And here is a result:
169 |
170 | * [README.md with TOC](https://github.com/ekalinin/envirius/blob/24ea3be0d3cc03f4235fa4879bb33dc122d0ae29/README.md)
171 |
172 | Moreover, it's able to work with GitHub's wiki pages:
173 |
174 | ```bash
175 | ➥ ./gh-md-toc https://github.com/ekalinin/nodeenv/wiki/Who-Uses-Nodeenv
176 |
177 | Table of Contents
178 | =================
179 |
180 | * [Who Uses Nodeenv?](#who-uses-nodeenv)
181 | * [OpenStack](#openstack)
182 | * [pre-commit.com](#pre-commitcom)
183 | ```
184 |
185 | Multiple files
186 | --------------
187 |
188 | It supports multiple files as well:
189 |
190 | ```bash
191 | ➥ ./gh-md-toc \
192 | https://github.com/aminb/rust-for-c/blob/master/hello_world/README.md \
193 | https://github.com/aminb/rust-for-c/blob/master/control_flow/README.md \
194 | https://github.com/aminb/rust-for-c/blob/master/primitive_types_and_operators/README.md \
195 | https://github.com/aminb/rust-for-c/blob/master/unique_pointers/README.md
196 |
197 | * [Hello world](https://github.com/aminb/rust-for-c/blob/master/hello_world/README.md#hello-world)
198 |
199 | * [Control Flow](https://github.com/aminb/rust-for-c/blob/master/control_flow/README.md#control-flow)
200 | * [If](https://github.com/aminb/rust-for-c/blob/master/control_flow/README.md#if)
201 | * [Loops](https://github.com/aminb/rust-for-c/blob/master/control_flow/README.md#loops)
202 | * [For loops](https://github.com/aminb/rust-for-c/blob/master/control_flow/README.md#for-loops)
203 | * [Switch/Match](https://github.com/aminb/rust-for-c/blob/master/control_flow/README.md#switchmatch)
204 | * [Method call](https://github.com/aminb/rust-for-c/blob/master/control_flow/README.md#method-call)
205 |
206 | * [Primitive Types and Operators](https://github.com/aminb/rust-for-c/blob/master/primitive_types_and_operators/README.md#primitive-types-and-operators)
207 |
208 | * [Unique Pointers](https://github.com/aminb/rust-for-c/blob/master/unique_pointers/README.md#unique-pointers)
209 | ```
210 |
211 | Combo
212 | -----
213 |
214 | You can easily combine both ways:
215 |
216 | ```bash
217 | ➥ ./gh-md-toc \
218 | ~/projects/Dockerfile.vim/README.md \
219 | https://github.com/ekalinin/sitemap.s/blob/master/README.md
220 |
221 | * [Dockerfile.vim](~/projects/Dockerfile.vim/README.md#dockerfilevim)
222 | * [Screenshot](~/projects/Dockerfile.vim/README.md#screenshot)
223 | * [Installation](~/projects/Dockerfile.vim/README.md#installation)
224 | * [OR using Pathogen:](~/projects/Dockerfile.vim/README.md#or-using-pathogen)
225 | * [OR using Vundle:](~/projects/Dockerfile.vim/README.md#or-using-vundle)
226 | * [License](~/projects/Dockerfile.vim/README.md#license)
227 |
228 | * [sitemap.js](https://github.com/ekalinin/sitemap.js/blob/master/README.md#sitemapjs)
229 | * [Installation](https://github.com/ekalinin/sitemap.js/blob/master/README.md#installation)
230 | * [Usage](https://github.com/ekalinin/sitemap.js/blob/master/README.md#usage)
231 | * [License](https://github.com/ekalinin/sitemap.js/blob/master/README.md#license)
232 |
233 |
234 | ```
235 |
236 | Auto insert and update TOC
237 | --------------------------
238 |
239 | Just put into a file these two lines:
240 |
241 | ```
242 |
243 |
244 | ```
245 |
246 | And run:
247 |
248 | ```bash
249 | $ ./gh-md-toc --insert README.test.md
250 |
251 | Table of Contents
252 | =================
253 |
254 | * [gh-md-toc](#gh-md-toc)
255 | * [Installation](#installation)
256 | * [Usage](#usage)
257 | * [STDIN](#stdin)
258 | * [Local files](#local-files)
259 | * [Remote files](#remote-files)
260 | * [Multiple files](#multiple-files)
261 | * [Combo](#combo)
262 | * [Tests](#tests)
263 | * [Dependency](#dependency)
264 |
265 | !! TOC was added into: 'README.test.md'
266 | !! Origin version of the file: 'README.test.md.orig.2018-02-04_192655'
267 | !! TOC added into a separate file: 'README.test.md.toc.2018-02-04_192655'
268 |
269 |
270 |
271 | ```
272 |
273 | Now check the same file:
274 |
275 | ```bash
276 | ➜ grep -A15 "<\!\-\-ts" README.test.md
277 |
278 | * [gh-md-toc](#gh-md-toc)
279 | * [Table of contents](#table-of-contents)
280 | * [Installation](#installation)
281 | * [Usage](#usage)
282 | * [STDIN](#stdin)
283 | * [Local files](#local-files)
284 | * [Remote files](#remote-files)
285 | * [Multiple files](#multiple-files)
286 | * [Combo](#combo)
287 | * [Auto insert and update TOC](#auto-insert-and-update-toc)
288 | * [Tests](#tests)
289 | * [Dependency](#dependency)
290 |
291 |
292 |
293 |
294 | ```
295 |
296 | Next time when your file will be changed just repeat the command (`./gh-md-toc
297 | --insert ...`) and TOC will be refreshed again.
298 |
299 | GitHub token
300 | ------------
301 |
302 | All your tokens are [here](https://github.com/settings/tokens).
303 |
304 | You will need them if you get an error like this:
305 |
306 | ```
307 | Parsing local markdown file requires access to github API
308 | Error: You exceeded the hourly limit. See: https://developer.github.com/v3/#rate-limiting
309 | or place github auth token here: ./token.txt
310 | ```
311 |
312 | A token can be used as an env variable:
313 |
314 | ```bash
315 | ➥ GH_TOC_TOKEN=2a2dab...563 ./gh-md-toc README.md
316 |
317 | Table of Contents
318 | =================
319 |
320 | * [github\-markdown\-toc](#github-markdown-toc)
321 | * [Table of Contents](#table-of-contents)
322 | * [Installation](#installation)
323 | * [Tests](#tests)
324 | * [Usage](#usage)
325 | * [LICENSE](#license)
326 | ```
327 |
328 | Or from a file:
329 |
330 | ```bash
331 | ➥ echo "2a2dab...563" > ./token.txt
332 | ➥ ./gh-md-toc README.md
333 |
334 | Table of Contents
335 | =================
336 |
337 | * [github\-markdown\-toc](#github-markdown-toc)
338 | * [Table of Contents](#table-of-contents)
339 | * [Installation](#installation)
340 | * [Tests](#tests)
341 | * [Usage](#usage)
342 | * [LICENSE](#license)
343 | ```
344 |
345 | TOC generation with Github Actions
346 | ----------------------------------
347 |
348 | Config:
349 |
350 | ```yaml
351 | on:
352 | push:
353 | branches: [main]
354 | paths: ['foo.md']
355 |
356 | jobs:
357 | build:
358 | runs-on: ubuntu-latest
359 | timeout-minutes: 5
360 | steps:
361 | - uses: actions/checkout@v2
362 | - run: |
363 | curl https://raw.githubusercontent.com/ekalinin/github-markdown-toc/master/gh-md-toc -o gh-md-toc
364 | chmod a+x gh-md-toc
365 | ./gh-md-toc --insert --no-backup --hide-footer foo.md
366 | rm gh-md-toc
367 | - uses: stefanzweifel/git-auto-commit-action@v4
368 | with:
369 | commit_message: Auto update markdown TOC
370 | ```
371 |
372 | Tests
373 | =====
374 |
375 | Done with [bats](https://github.com/bats-core/bats-core).
376 | Useful articles:
377 |
378 | * https://www.engineyard.com/blog/how-to-use-bats-to-test-your-command-line-tools/
379 | * http://blog.spike.cx/post/60548255435/testing-bash-scripts-with-bats
380 |
381 |
382 | How to run tests:
383 |
384 | ```bash
385 | ➥ make test
386 |
387 | ✓ TOC for local README.md
388 | ✓ TOC for remote README.md
389 | ✓ TOC for mixed README.md (remote/local)
390 | ✓ TOC for markdown from stdin
391 | ✓ --help
392 | ✓ --version
393 |
394 | 6 tests, 0 failures
395 | ```
396 |
397 | Dependency
398 | ==========
399 |
400 | * curl or wget
401 | * awk (mawk is not tested)
402 | * grep
403 | * sed
404 | * bats (for unit tests)
405 |
406 | Tested on Ubuntu 14.04/14.10 in bash/zsh.
407 |
408 | Docker
409 | ======
410 |
411 | Local
412 | -----
413 |
414 | * Build
415 |
416 | ```shell
417 | $ docker build -t markdown-toc-generator .
418 | ```
419 |
420 | * Run on an URL
421 |
422 | ```shell
423 | $ docker run -it markdown-toc-generator https://github.com/ekalinin/envirius/blob/master/README.md
424 | ```
425 |
426 | * Run on a local file (need to share volume with docker)
427 |
428 | ```shell
429 | $ docker run -it -v /data/ekalinin/envirius:/data markdown-toc-generator /data/README.md
430 | ```
431 |
432 | Public
433 | -------
434 |
435 | ```shell
436 | $ docker pull evkalinin/gh-md-toc:0.7.0
437 |
438 | $ docker images | grep toc
439 | evkalinin/gh-md-toc 0.7.0 0b8db6aed298 11 minutes ago 147MB
440 |
441 | $ docker run -it evkalinin/gh-md-toc:0.7.0 \
442 | https://github.com/ekalinin/envirius/blob/master/README.md
443 | ```
444 |
--------------------------------------------------------------------------------
/gh-md-toc:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | #
4 | # Steps:
5 | #
6 | # 1. Download corresponding html file for some README.md:
7 | # curl -s $1
8 | #
9 | # 2. Discard rows where no substring 'user-content-' (github's markup):
10 | # awk '/user-content-/ { ...
11 | #
12 | # 3.1 Get last number in each row like ' ... sitemap.js.*<\/h/)+2, RLENGTH-5)
21 | #
22 | # 5. Find anchor and insert it inside "(...)":
23 | # substr($0, match($0, "href=\"[^\"]+?\" ")+6, RLENGTH-8)
24 | #
25 |
26 | gh_toc_version="0.10.0"
27 |
28 | gh_user_agent="gh-md-toc v$gh_toc_version"
29 |
30 | #
31 | # Download rendered into html README.md by its url.
32 | #
33 | #
34 | gh_toc_load() {
35 | local gh_url=$1
36 |
37 | if type curl &>/dev/null; then
38 | curl --user-agent "$gh_user_agent" -s "$gh_url"
39 | elif type wget &>/dev/null; then
40 | wget --user-agent="$gh_user_agent" -qO- "$gh_url"
41 | else
42 | echo "Please, install 'curl' or 'wget' and try again."
43 | exit 1
44 | fi
45 | }
46 |
47 | #
48 | # Converts local md file into html by GitHub
49 | #
50 | # -> curl -X POST --data '{"text": "Hello world github/linguist#1 **cool**, and #1!"}' https://api.github.com/markdown
51 | #
Hello world github/linguist#1 cool, and #1!
'"
52 | gh_toc_md2html() {
53 | local gh_file_md=$1
54 | local skip_header=$2
55 |
56 | URL=https://api.github.com/markdown/raw
57 |
58 | if [ -n "$GH_TOC_TOKEN" ]; then
59 | TOKEN=$GH_TOC_TOKEN
60 | else
61 | TOKEN_FILE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/token.txt"
62 | if [ -f "$TOKEN_FILE" ]; then
63 | TOKEN="$(cat "$TOKEN_FILE")"
64 | fi
65 | fi
66 | if [ -n "${TOKEN}" ]; then
67 | AUTHORIZATION="Authorization: token ${TOKEN}"
68 | fi
69 |
70 | local gh_tmp_file_md=$gh_file_md
71 | if [ "$skip_header" = "yes" ]; then
72 | if grep -Fxq "" "$gh_src"; then
73 | # cut everything before the toc
74 | gh_tmp_file_md=$gh_file_md~~
75 | sed '1,//d' "$gh_file_md" > "$gh_tmp_file_md"
76 | fi
77 | fi
78 |
79 | # echo $URL 1>&2
80 | OUTPUT=$(curl -s \
81 | --user-agent "$gh_user_agent" \
82 | --data-binary @"$gh_tmp_file_md" \
83 | -H "Content-Type:text/plain" \
84 | -H "$AUTHORIZATION" \
85 | "$URL")
86 |
87 | rm -f "${gh_file_md}~~"
88 |
89 | if [ "$?" != "0" ]; then
90 | echo "XXNetworkErrorXX"
91 | fi
92 | if [ "$(echo "${OUTPUT}" | awk '/API rate limit exceeded/')" != "" ]; then
93 | echo "XXRateLimitXX"
94 | else
95 | echo "${OUTPUT}"
96 | fi
97 | }
98 |
99 |
100 | #
101 | # Is passed string url
102 | #
103 | gh_is_url() {
104 | case $1 in
105 | https* | http*)
106 | echo "yes";;
107 | *)
108 | echo "no";;
109 | esac
110 | }
111 |
112 | #
113 | # TOC generator
114 | #
115 | gh_toc(){
116 | local gh_src=$1
117 | local gh_src_copy=$1
118 | local gh_ttl_docs=$2
119 | local need_replace=$3
120 | local no_backup=$4
121 | local no_footer=$5
122 | local indent=$6
123 | local skip_header=$7
124 |
125 | if [ "$gh_src" = "" ]; then
126 | echo "Please, enter URL or local path for a README.md"
127 | exit 1
128 | fi
129 |
130 |
131 | # Show "TOC" string only if working with one document
132 | if [ "$gh_ttl_docs" = "1" ]; then
133 |
134 | echo "Table of Contents"
135 | echo "================="
136 | echo ""
137 | gh_src_copy=""
138 |
139 | fi
140 |
141 | if [ "$(gh_is_url "$gh_src")" == "yes" ]; then
142 | gh_toc_load "$gh_src" | gh_toc_grab "$gh_src_copy" "$indent"
143 | if [ "${PIPESTATUS[0]}" != "0" ]; then
144 | echo "Could not load remote document."
145 | echo "Please check your url or network connectivity"
146 | exit 1
147 | fi
148 | if [ "$need_replace" = "yes" ]; then
149 | echo
150 | echo "!! '$gh_src' is not a local file"
151 | echo "!! Can't insert the TOC into it."
152 | echo
153 | fi
154 | else
155 | local rawhtml
156 | rawhtml=$(gh_toc_md2html "$gh_src" "$skip_header")
157 | if [ "$rawhtml" == "XXNetworkErrorXX" ]; then
158 | echo "Parsing local markdown file requires access to github API"
159 | echo "Please make sure curl is installed and check your network connectivity"
160 | exit 1
161 | fi
162 | if [ "$rawhtml" == "XXRateLimitXX" ]; then
163 | echo "Parsing local markdown file requires access to github API"
164 | echo "Error: You exceeded the hourly limit. See: https://developer.github.com/v3/#rate-limiting"
165 | TOKEN_FILE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/token.txt"
166 | echo "or place GitHub auth token here: ${TOKEN_FILE}"
167 | exit 1
168 | fi
169 | local toc
170 | toc=`echo "$rawhtml" | gh_toc_grab "$gh_src_copy" "$indent"`
171 | echo "$toc"
172 | if [ "$need_replace" = "yes" ]; then
173 | if grep -Fxq "" "$gh_src" && grep -Fxq "" "$gh_src"; then
174 | echo "Found markers"
175 | else
176 | echo "You don't have or in your file...exiting"
177 | exit 1
178 | fi
179 | local ts="<\!--ts-->"
180 | local te="<\!--te-->"
181 | local dt
182 | dt=$(date +'%F_%H%M%S')
183 | local ext=".orig.${dt}"
184 | local toc_path="${gh_src}.toc.${dt}"
185 | local toc_createdby=""
186 | local toc_footer
187 | toc_footer=""
188 | # http://fahdshariff.blogspot.ru/2012/12/sed-mutli-line-replacement-between-two.html
189 | # clear old TOC
190 | sed -i"${ext}" "/${ts}/,/${te}/{//!d;}" "$gh_src"
191 | # create toc file
192 | echo "${toc}" > "${toc_path}"
193 | if [ "${no_footer}" != "yes" ]; then
194 | echo -e "\n${toc_createdby}\n${toc_footer}\n" >> "$toc_path"
195 | fi
196 |
197 | # insert toc file
198 | if ! sed --version > /dev/null 2>&1; then
199 | sed -i "" "/${ts}/r ${toc_path}" "$gh_src"
200 | else
201 | sed -i "/${ts}/r ${toc_path}" "$gh_src"
202 | fi
203 | echo
204 | if [ "${no_backup}" = "yes" ]; then
205 | rm "$toc_path" "$gh_src$ext"
206 | fi
207 | echo "!! TOC was added into: '$gh_src'"
208 | if [ -z "${no_backup}" ]; then
209 | echo "!! Origin version of the file: '${gh_src}${ext}'"
210 | echo "!! TOC added into a separate file: '${toc_path}'"
211 | fi
212 | echo
213 | fi
214 | fi
215 | }
216 |
217 | #
218 | # Grabber of the TOC from rendered html
219 | #
220 | # $1 - a source url of document.
221 | # It's need if TOC is generated for multiple documents.
222 | # $2 - number of spaces used to indent.
223 | #
224 | gh_toc_grab() {
225 |
226 | href_regex="/href=\"[^\"]+?\"/"
227 | common_awk_script='
228 | modified_href = ""
229 | split(href, chars, "")
230 | for (i=1;i <= length(href); i++) {
231 | c = chars[i]
232 | res = ""
233 | if (c == "+") {
234 | res = " "
235 | } else {
236 | if (c == "%") {
237 | res = "\\x"
238 | } else {
239 | res = c ""
240 | }
241 | }
242 | modified_href = modified_href res
243 | }
244 | print sprintf("%*s", (level-1)*'"$2"', "") "* [" text "](" gh_url modified_href ")"
245 | '
246 | if [ "`uname -s`" == "OS/390" ]; then
247 | grepcmd="pcregrep -o"
248 | echoargs=""
249 | awkscript='{
250 | level = substr($0, 3, 1)
251 | text = substr($0, match($0, /<\/span><\/a>[^<]*<\/h/)+11, RLENGTH-14)
252 | href = substr($0, match($0, '$href_regex')+6, RLENGTH-7)
253 | '"$common_awk_script"'
254 | }'
255 | else
256 | grepcmd="grep -Eo"
257 | echoargs="-e"
258 | awkscript='{
259 | level = substr($0, 3, 1)
260 | text = substr($0, match($0, /">.*<\/h/)+2, RLENGTH-5)
261 | href = substr($0, match($0, '$href_regex')+6, RLENGTH-7)
262 | '"$common_awk_script"'
263 | }'
264 | fi
265 |
266 | # if closed is on the new line, then move it on the prev line
267 | # for example:
268 | # was: The command foo1
269 | #
270 | # became: The command foo1
271 | sed -e ':a' -e 'N' -e '$!ba' -e 's/\n<\/h/<\/h/g' |
272 |
273 | # Sometimes a line can start with . Fix that.
274 | sed -e ':a' -e 'N' -e '$!ba' -e 's/\n//g' | sed 's/<\/code>//g' |
281 |
282 | # remove g-emoji
283 | sed 's/]*[^<]*<\/g-emoji> //g' |
284 |
285 | # now all rows are like:
286 | # title
..
287 | # format result line
288 | # * $0 - whole string
289 | # * last element of each row: "/dev/null; then
313 | $tool --version | head -n 1
314 | else
315 | echo "not installed"
316 | fi
317 | done
318 | }
319 |
320 | show_help() {
321 | local app_name
322 | app_name=$(basename "$0")
323 | echo "GitHub TOC generator ($app_name): $gh_toc_version"
324 | echo ""
325 | echo "Usage:"
326 | echo " $app_name [options] src [src] Create TOC for a README file (url or local path)"
327 | echo " $app_name - Create TOC for markdown from STDIN"
328 | echo " $app_name --help Show help"
329 | echo " $app_name --version Show version"
330 | echo ""
331 | echo "Options:"
332 | echo " --indent Set indent size. Default: 3."
333 | echo " --insert Insert new TOC into original file. For local files only. Default: false."
334 | echo " See https://github.com/ekalinin/github-markdown-toc/issues/41 for details."
335 | echo " --no-backup Remove backup file. Set --insert as well. Default: false."
336 | echo " --hide-footer Do not write date & author of the last TOC update. Set --insert as well. Default: false."
337 | echo " --skip-header Hide entry of the topmost headlines. Default: false."
338 | echo " See https://github.com/ekalinin/github-markdown-toc/issues/125 for details."
339 | echo ""
340 | }
341 |
342 | #
343 | # Options handlers
344 | #
345 | gh_toc_app() {
346 | local need_replace="no"
347 | local indent=3
348 |
349 | if [ "$1" = '--help' ] || [ $# -eq 0 ] ; then
350 | show_help
351 | return
352 | fi
353 |
354 | if [ "$1" = '--version' ]; then
355 | show_version
356 | return
357 | fi
358 |
359 | if [ "$1" = '--indent' ]; then
360 | indent="$2"
361 | shift 2
362 | fi
363 |
364 | if [ "$1" = "-" ]; then
365 | if [ -z "$TMPDIR" ]; then
366 | TMPDIR="/tmp"
367 | elif [ -n "$TMPDIR" ] && [ ! -d "$TMPDIR" ]; then
368 | mkdir -p "$TMPDIR"
369 | fi
370 | local gh_tmp_md
371 | if [ "`uname -s`" == "OS/390" ]; then
372 | local timestamp
373 | timestamp=$(date +%m%d%Y%H%M%S)
374 | gh_tmp_md="$TMPDIR/tmp.$timestamp"
375 | else
376 | gh_tmp_md=$(mktemp "$TMPDIR/tmp.XXXXXX")
377 | fi
378 | while read -r input; do
379 | echo "$input" >> "$gh_tmp_md"
380 | done
381 | gh_toc_md2html "$gh_tmp_md" | gh_toc_grab "" "$indent"
382 | return
383 | fi
384 |
385 | if [ "$1" = '--insert' ]; then
386 | need_replace="yes"
387 | shift
388 | fi
389 |
390 | if [ "$1" = '--no-backup' ]; then
391 | need_replace="yes"
392 | no_backup="yes"
393 | shift
394 | fi
395 |
396 | if [ "$1" = '--hide-footer' ]; then
397 | need_replace="yes"
398 | no_footer="yes"
399 | shift
400 | fi
401 |
402 | if [ "$1" = '--skip-header' ]; then
403 | skip_header="yes"
404 | shift
405 | fi
406 |
407 |
408 | for md in "$@"
409 | do
410 | echo ""
411 | gh_toc "$md" "$#" "$need_replace" "$no_backup" "$no_footer" "$indent" "$skip_header"
412 | done
413 |
414 | echo ""
415 | echo ""
416 | }
417 |
418 | #
419 | # Entry point
420 | #
421 | gh_toc_app "$@"
422 |
--------------------------------------------------------------------------------
/tests/test directory/test_backquote.md:
--------------------------------------------------------------------------------
1 | # The command `foo1`
2 |
3 | Blabla...
4 |
5 | ## The command `foo2` is better
6 |
7 | Blabla...
8 |
9 | # The command `bar1`
10 |
11 | Blabla...
12 |
13 | ## The command `bar2` is better
14 |
15 | Blabla...
16 |
17 | ### The command `bar3` is the best
18 |
19 | Blabla...
20 |
--------------------------------------------------------------------------------
/tests/test directory/test_filepathwithspace.md:
--------------------------------------------------------------------------------
1 | # Title
2 |
3 |
4 | * [Title](#title)
5 | * [This is test for file path with space](#this-is-test-for-file-path-with-space)
6 |
7 |
8 | Blabla...
9 |
10 | ## This is test for file path with space
11 |
12 | Blabla...
13 |
--------------------------------------------------------------------------------
/tests/test directory/test_nonenglishchars.md:
--------------------------------------------------------------------------------
1 | [ Languages: [English](README.md), [Español](README-es.md), [Português](README-pt.md), [中文](README-zh.md) ]
2 |
3 |
4 | # 命令行的艺术
5 |
6 | [](https://gitter.im/jlevy/the-art-of-command-line?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
7 | - [必读](#必读)
8 | - [基础](#基础)
9 | - [日常使用](#日常使用)
10 | - [文件及数据处理](#文件及数据处理)
11 | - [系统调试](#系统调试)
12 | - [一行代码](#一行代码)
13 | - [冷门但有用](#冷门但有用)
14 | - [更多资源](#更多资源)
15 | - [免责声明](#免责声明)
16 | - [授权条款](#授权条款)
17 |
18 |
19 | 
20 |
21 | 熟练使用命令行是一种常常被忽视或被认为难以掌握的技能,但实际上,它可以提高你作为工程师的灵活性以及生产力。本文是一份我在 Linux 上工作时发现的一些关于命令行的使用的小技巧的摘要。这些小技巧有基础的、相当复杂的甚至晦涩难懂的。这篇文章并不长,但当你能够熟练掌握这里列出的所有技巧时,你就学会了很多关于命令行的东西了。
22 |
23 | 这里的大部分内容
24 | [首次](http://www.quora.com/What-are-some-lesser-known-but-useful-Unix-commands)
25 | [出现](http://www.quora.com/What-are-the-most-useful-Swiss-army-knife-one-liners-on-Unix)
26 | 于 [Quora](http://www.quora.com/What-are-some-time-saving-tips-that-every-Linux-user-should-know),但考虑到这里的人们都具有学习的天赋且乐于接受别人的建议,使用 Github 来做这件事是更佳的选择。如果你在本文中发现了错误或者存在可以改善的地方,请果断提交 Issue 或 Pull Request!(当然在提交前请看一下必读节和已有的 PR/issue)。
27 |
28 |
29 | ## 必读
30 |
31 | 涵盖范围:
32 |
33 | - 这篇文章对刚接触命令行的新手以及具有命令行使用经验的人都有用处。本文致力于做到覆盖面广(尽量包括一切重要的内容),具体(给出最常见的具体的例子)以及简洁(避免一些不必要的东西以及一些偏题的可以在其他地方翻阅到文献的东西)。 每个小技巧在某个特定情境下都是基本的或能够显著地节约时间。
34 | - 本文为 Linux 所写,但很多内容(并非所有的)同样适用于 MacOS 甚至 Cygwin。
35 | - 本文关注于交互式 Bash,尽管很多技巧适用于其他 shell 或 Bash 脚本。
36 | - 本文包括了“标准的”Unix 命令和需要安装特定包的命令,只要它们足够重要。
37 |
38 | 注意事项:
39 |
40 | - 为了能在一页内展示尽量多的东西,一些具体的信息会被间接的包含在引用页里。聪明机智的你如果掌握了使用 Google 搜索引擎的基本思路与命令,那么你将可以查阅到更多的详细信息。使用 `apt-get`/`yum`/`dnf`/`pacman`/`pip`/`brew`(以及其它合适的包管理器)来安装新程序。
41 | - 使用 [Explainshell](http://explainshell.com/) 去获取相关命令、参数、管道等内容的解释。
42 |
43 |
44 | ## 基础
45 |
46 | - 学习 Bash 的基础知识。具体来说,输入 `man bash` 并至少全文浏览一遍; 它很简单并且不长。其他的 shell 可能很好用,但 Bash 功能强大且几乎所有情况下都是可用的 ( *只*学习 zsh,fish 或其他的 shell 的话,在你自己的电脑上会显得很方便,但在很多情况下会限制你,比如当你需要在服务器上工作时)。
47 |
48 | - 学习并掌握至少一个基于文本的编辑器。通常 Vim (`vi`) 会是你最好的选择。
49 |
50 | - 学会如何使用 `man` 命令去阅读文档。学会使用 `apropos` 去查找文档。了解有些命令并不对应可执行文件,而是Bash内置的,可以使用 `help` 和 `help -d` 命令获取帮助信息。
51 |
52 | - 学会使用 `>` 和 `<` 来重定向输出和输入,学会使用 `|` 来重定向管道。了解标准输出 stdout 和标准错误 stderr。
53 |
54 | - 学会使用通配符 `*` (或许再算上 `?` 和 `{`...`}`) 和引用以及引用中 `'` 和 `"` 的区别。
55 |
56 | - 熟悉 Bash 任务管理工具:`&`,**ctrl-z**,**ctrl-c**,`jobs`,`fg`,`bg`,`kill` 等。
57 |
58 | - 了解 `ssh`,以及基本的无密码认证,`ssh-agent`,`ssh-add` 等。
59 |
60 | - 学会基本的文件管理:`ls` 和 `ls -l` (了解 `ls -l` 中每一列代表的意义),`less`,`head`,`tail` 和 `tail -f` (甚至 `less +F`),`ln` 和 `ln -s` (了解硬链接与软链接的区别),`chown`,`chmod`,`du` (硬盘使用情况概述:`du -hk *`)。 关于文件系统的管理,学习 `df`,`mount`,`fdisk`,`mkfs`,`lsblk`。
61 |
62 | - 学习基本的网络管理:`ip` 或 `ifconfig`,`dig`。
63 |
64 | - 熟悉正则表达式,以及 `grep`/`egrep` 里不同参数的作用,例如 `-i`,`-o`,`-A`,和 `-B`。
65 |
66 | - 学会使用 `apt-get`,`yum`,`dnf` 或 `pacman` (取决于你使用的 Linux 发行版)来查找或安装包。确保你的环境中有 `pip` 来安装基于 Python 的命令行工具 (部分程序使用 `pip` 来安装会很简单)。
67 |
68 |
69 | ## 日常使用
70 |
71 | - 在 Bash 中,可以使用 **Tab** 自动补全参数,使用 **ctrl-r** 搜索命令行历史。
72 |
73 | - 在 Bash 中,使用 **ctrl-w** 删除你键入的最后一个单词,使用 **ctrl-u** 删除整行,使用 **alt-b** 和 **alt-f** 按单词移动,使用 **ctrl-k** 从光标处删除到行尾,使用 **ctrl-l** 清屏。键入 `man readline` 查看 Bash 中的默认快捷键,内容很多。例如 **alt-.** 循环地移向前一个参数,以及 **alt-*** 展开通配符。
74 |
75 | - 你喜欢的话,可以键入 `set -o vi` 来使用 vi 风格的快捷键。
76 |
77 | - 键入 `history` 查看命令行历史记录。其中有许多缩写,例如 `!$`(最后键入的参数)和 `!!`(最后键入的命令),尽管通常被 **ctrl-r** 和 **alt-.** 取代。
78 |
79 | - 回到上一个工作路径:`cd -`
80 |
81 | - 如果你输入命令的时候改变了主意,按下 **alt-#** 在行首添加 `#`(将你输入的命令视为注释),并回车。这样做的话,之后你可以很方便的利用命令行历史回到你刚才输入到一半的命令。
82 |
83 | - 使用 `xargs` ( 或 `parallel`)。他们非常给力。注意到你可以控制每行参数个数(`-L`)和最大并行数(`-P`)。如果你不确定它们是否会按你想的那样工作,先使用 `xargs echo` 查看一下。此外,使用 `-I{}` 会很方便。例如:
84 | ```bash
85 | find . -name '*.py' | xargs grep some_function
86 | cat hosts | xargs -I{} ssh root@{} hostname
87 | ```
88 |
89 | - `pstree -p` 有助于展示进程树。
90 |
91 | - 使用 `pgrep` 和 `pkill` 根据名字查找进程或发送信号。
92 |
93 | - 了解你可以发往进程的信号的种类。比如,使用 `kill -STOP [pid]` 停止一个进程。使用 `man 7 signal` 查看详细列表。
94 |
95 | - 使用 `nohup` 或 `disown` 使一个后台进程持续运行。
96 |
97 | - 使用 `netstat -lntp` 或 `ss -plat` 检查哪些进程在监听端口(默认是检查 TCP 端口; 使用参数 `-u` 检查 UDP 端口)。
98 |
99 | - 有关打开套接字和文件,请参阅 `lsof`。
100 |
101 | - 在 Bash 脚本中,使用 `set -x` 去调试输出,尽可能的使用严格模式,使用 `set -e` 令脚本在发生错误时退出而不是继续运行,使用 `set -o pipefail` 严谨地对待错误(尽管问题可能很微妙)。当牵扯到很多脚本时,使用 `trap`。
102 |
103 | - 在 Bash 脚本中,子 shell(使用括号`(...)`)是一种便捷的方式去组织参数。一个常见的例子是临时地移动工作路径,代码如下:
104 | ```bash
105 | # do something in current dir
106 | (cd /some/other/dir && other-command)
107 | # continue in original dir
108 | ```
109 |
110 | - 在 Bash 中,注意到其中有许多形式的扩展。检查变量是否存在:`${name:?error message}`。例如,当 Bash 脚本需要一个参数时,可以使用这样的代码 `input_file=${1:?usage: $0 input_file}`。数学表达式:`i=$(( (i + 1) % 5 ))`。序列:`{1..10}`。 截断字符串:`${var%suffix}` 和 `${var#prefix}`。例如,假设 `var=foo.pdf`,那么 `echo ${var%.pdf}.txt` 将输出 `foo.txt`。
111 |
112 | - 通过使用 `<(some command)` 可以将输出视为文件。例如,对比本地文件 `/etc/hosts` 和一个远程文件:
113 | ```sh
114 | diff /etc/hosts <(ssh somehost cat /etc/hosts)
115 | ```
116 |
117 | - 了解 Bash 中的“here documents”,例如 `cat <logfile 2>&1`。通常,为了保证命令不会在标准输入里残留一个打开了的文件句柄导致你当前所在的终端无法操作,添加 ` foo:
191 | rename 's/\.bak$//' *.bak
192 | # Full rename of filenames,directories,and contents foo -> bar:
193 | repren --full --preserve-case --from foo --to bar .
194 | ```
195 |
196 | - 使用 `shuf` 从一个文件中随机选取行。
197 |
198 | - 了解 `sort` 的参数。明白键的工作原理(`-t` 和 `-k`)。例如,注意到你需要 `-k1,1` 来仅按第一个域来排序,而 `-k1` 意味着按整行排序。稳定排序(`sort -s`)在某些情况下很有用。例如,以第二个域为主关键字,第一个域为次关键字进行排序,你可以使用 `sort -k1,1 | sort -s -k2,2`。处理可读性数字(例如 `du -h` 的输出)的时候使用 `sort -h` 。
199 |
200 | - 如果你想在 Bash 命令行中写 tab 制表符,按下 **ctrl-v** **[Tab]** 或键入 `$'\t'` (后者可能更好,因为你可以复制粘贴它)。
201 |
202 | - 标准的源代码对比及合并工具是 `diff` 和 `patch`。使用 `diffstat` 查看变更总览数据。注意到 `diff -r` 对整个文件夹有效。使用 `diff -r tree1 tree2 | diffstat` 查看变更总览数据。
203 |
204 | - 对于二进制文件,使用 `hd` 使其以十六进制显示以及使用 `bvi` 来编辑二进制。
205 |
206 | - 同样对于二进制文件,使用 `strings`(包括 `grep` 等等)允许你查找一些文本。
207 |
208 | - 二进制文件对比(Delta 压缩),使用 `xdelta3`。
209 |
210 | - 使用 `iconv` 更改文本编码。而更高级的用法,可以使用 `uconv`,它支持一些高级的 Unicode 功能。例如,这条命令将所有元音字母转为小写并移除了:
211 | ```sh
212 | uconv -f utf-8 -t utf-8 -x '::Any-Lower; ::Any-NFD; [:Nonspacing Mark:] >; ::Any-NFC; ' < input.txt > output.txt
213 | ```
214 |
215 | - 拆分文件,查看 `split`(按大小拆分)和 `csplit`(按模式拆分)。
216 |
217 | - 使用 `zless`,`zmore`,`zcat` 和 `zgrep`对压缩过的文件进行操作。
218 |
219 |
220 | ## 系统调试
221 |
222 | - `curl` 和 `curl -I` 可以便捷地被应用于 web 调试中,它们的好兄弟 `wget` 也可以,或者是更潮的 [`httpie`](https://github.com/jakubroztocil/httpie)。
223 |
224 | - 使用 `iostat`、`netstat`、`top` (`htop` 更佳)和 `dstat` 去获取硬盘、cpu 和网络的状态。熟练掌握这些工具可以使你快速的对系统的当前状态有一个大概的认识。
225 |
226 | - 若要对系统有一个深度的总体认识,使用 [`glances`](https://github.com/nicolargo/glances)。它在一个终端窗口中向你提供一些系统级的数据。这对于快速的检查各个子系统非常有帮助。
227 |
228 | - 若要了解内存状态,运行并理解 `free` 和 `vmstat` 的输出。尤其注意“cached”的值,它指的是 Linux 内核用来作为文件缓存的内存大小,因此它与空闲内存无关。
229 |
230 | - Java 系统调试则是一件截然不同的事,一个可以用于 Oracle 的 JVM 或其他 JVM 上的调试的小技巧是你可以运行 `kill -3 ` 同时一个完整的栈轨迹和堆概述(包括 GC 的细节)会被保存到标准输出/日志文件。
231 |
232 | - 使用 `mtr` 去跟踪路由,用于确定网络问题。
233 |
234 | - 用 `ncdu` 来查看磁盘使用情况,它比常用的命令,如 `du -sh *`,更节省时间。
235 |
236 | - 查找正在使用带宽的套接字连接或进程,使用 `iftop` 或 `nethogs`。
237 |
238 | - `ab` 工具(捆绑于 Apache)可以简单粗暴地检查 web 服务器的性能。对于更复杂的负载测试,使用 `siege`。
239 |
240 | - `wireshark`,`tshark` 和 `ngrep` 可用于复杂的网络调试。
241 |
242 | - 了解 `strace` 和 `ltrace`。这俩工具在你的程序运行失败、挂起甚至崩溃,而你却不知道为什么或你想对性能有个总体的认识的时候是非常有用的。注意 profile 参数(`-c`)和附加到一个运行的进程参数 (`-p`)。
243 |
244 | - 了解使用 `ldd` 来检查共享库。
245 |
246 | - 了解如何运用 `gdb` 连接到一个运行着的进程并获取它的堆栈轨迹。
247 |
248 | - 学会使用 `/proc`。它在调试正在出现的问题的时候有时会效果惊人。比如:`/proc/cpuinfo`,`/proc/xxx/cwd`,`/proc/xxx/exe`,`/proc/xxx/fd/`,`/proc/xxx/smaps`。
249 |
250 | - 当调试一些之前出现的问题的时候,`sar` 非常有用。它展示了 cpu、内存以及网络等的历史数据。
251 |
252 | - 关于更深层次的系统分析以及性能分析,看看 `stap`([SystemTap](https://sourceware.org/systemtap/wiki)),[`perf`](http://en.wikipedia.org/wiki/Perf_(Linux)),以及[`sysdig`](https://github.com/draios/sysdig)。
253 |
254 | - 查看你当前使用的 Linux 发行版(大部分发行版有效):`lsb_release -a`
255 |
256 | - 无论什么东西工作得很欢乐时试试 `dmesg` (可能是硬件或驱动问题)。
257 |
258 |
259 | ## 一行代码
260 |
261 | 一些命令组合的例子:
262 |
263 | - 当你需要对文本文件做集合交、并、差运算时,结合使用 `sort`/`uniq` 很有帮助。假设 `a` 与 `b` 是两内容不同的文件。这种方式效率很高,并且在小文件和上G的文件上都能运用 (`sort` 不被内存大小约束,尽管在 `/tmp` 在一个小的根分区上时你可能需要 `-T` 参数),参阅前文中关于 `LC_ALL` 和 `sort` 的 `-u` 参数的部分。
264 | ```sh
265 | cat a b | sort | uniq > c # c is a union b
266 | cat a b | sort | uniq -d > c # c is a intersect b
267 | cat a b b | sort | uniq -u > c # c is set difference a - b
268 | ```
269 |
270 | - 使用 `grep . *` 来阅读检查目录下所有文件的内容,例如检查一个充满配置文件的目录比如 `/sys`、`/proc`、`/etc`。
271 |
272 | - 计算文本文件第三列中所有数的和(可能比同等作用的 Python 代码快三倍且代码量少三倍):
273 | ```sh
274 | awk '{ x += $3 } END { print x }' myfile
275 | ```
276 |
277 | - 如果你想在文件树上查看大小\日期,这可能看起来像递归版的 `ls -l` 但比 `ls -lR` 更易于理解:
278 | ```sh
279 | find . -type f -ls
280 | ```
281 |
282 | - 尽可能的使用 `xargs` 或 `parallel`。注意到你可以控制每行参数个数(`-L`)和最大并行数(`-P`)。如果你不确定它们是否会按你想的那样工作,先使用 `xargs echo` 查看一下。此外,使用 `-I{}` 会很方便。例如:
283 | ```sh
284 | find . -name '*.py' | xargs grep some_function
285 | cat hosts | xargs -I{} ssh root@{} hostname
286 | ```
287 |
288 | - 假设你有一个类似于 web 服务器日志文件的文本文件,并且一个确定的值只会出现在某些行上,假设一个 `acct_id` 参数在URI中。如果你想计算出每个 `acct_id` 值有多少次请求,使用如下代码:
289 | ```sh
290 | cat access.log | egrep -o 'acct_id=[0-9]+' | cut -d= -f2 | sort | uniq -c | sort -rn
291 | ```
292 |
293 | - 运行这个函数从这篇文档中随机获取一条小技巧(解析 Markdown 文件并抽取项目):
294 | ```sh
295 | function taocl() {
296 | curl -s https://raw.githubusercontent.com/jlevy/the-art-of-command-line/master/README.md |
297 | pandoc -f markdown -t html |
298 | xmlstarlet fo --html --dropdtd |
299 | xmlstarlet sel -t -v "(html/body/ul/li[count(p)>0])[$RANDOM mod last()+1]" |
300 | xmlstarlet unesc | fmt -80
301 | }
302 | ```
303 |
304 |
305 | ## 冷门但有用
306 |
307 | - `expr`:计算表达式或正则匹配
308 |
309 | - `m4`:简单地宏处理器
310 |
311 | - `yes`:多次打印字符串
312 |
313 | - `cal`:漂亮的日历
314 |
315 | - `env`:执行一个命令(脚本文件中很有用)
316 |
317 | - `printenv`:打印环境变量(调试时或在使用脚本文件时很有用)
318 |
319 | - `look`:查找以特定字符串开头的单词
320 |
321 | - `cut`、`paste` 和 `join`:数据修改
322 |
323 | - `fmt`:格式化文本段落
324 |
325 | - `pr`:将文本格式化成页/列形式
326 |
327 | - `fold`:包裹文本中的几行
328 |
329 | - `column`:将文本格式化成多列或表格
330 |
331 | - `expand` 和 `unexpand`:制表符与空格之间转换
332 |
333 | - `nl`:添加行号
334 |
335 | - `seq`:打印数字
336 |
337 | - `bc`:计算器
338 |
339 | - `factor`:分解因数
340 |
341 | - `gpg`:加密并签名文件
342 |
343 | - `toe`:terminfo entries 列表
344 |
345 | - `nc`:网络调试及数据传输
346 |
347 | - `socat`:套接字代理,与 `netcat` 类似
348 |
349 | - `slurm`:网络可视化
350 |
351 | - `dd`:文件或设备间传输数据
352 |
353 | - `file`:确定文件类型
354 |
355 | - `tree`:以树的形式显示路径和文件,类似于递归的 `ls`
356 |
357 | - `stat`:文件信息
358 |
359 | - `tac`:反向输出文件
360 |
361 | - `shuf`:文件中随机选取几行
362 |
363 | - `comm`:一行一行的比较排序过的文件
364 |
365 | - `pv`:监视通过管道的数据
366 |
367 | - `hd` 和 `bvi`:保存或编辑二进制文件
368 |
369 | - `strings`:从二进制文件中抽取文本
370 |
371 | - `tr`:转换字母
372 |
373 | - `iconv` 或 `uconv`:简易的文件编码
374 |
375 | - `split` 和 `csplit`:分割文件
376 |
377 | - `units`:将一种计量单位转换为另一种等效的计量单位(参阅 `/usr/share/units/definitions.units`)
378 |
379 | - `7z`:高比例的文件压缩
380 |
381 | - `ldd`:动态库信息
382 |
383 | - `nm`:提取 obj 文件中的符号
384 |
385 | - `ab`:性能分析 web 服务器
386 |
387 | - `strace`:系统调用调试
388 |
389 | - `mtr`:更好的网络调试跟踪工具
390 |
391 | - `cssh`:可视化的并发 shell
392 |
393 | - `rsync`:通过 ssh 同步文件和文件夹
394 |
395 | - `wireshark` 和 `tshark`:抓包和网络调试工具
396 |
397 | - `ngrep`:网络层的 grep
398 |
399 | - `host` 和 `dig`:DNS 查找
400 |
401 | - `lsof`:列出当前系统打开文件的工具以及查看端口信息
402 |
403 | - `dstat`:系统状态查看
404 |
405 | - [`glances`](https://github.com/nicolargo/glances):高层次的多子系统总览
406 |
407 | - `iostat`:CPU 和硬盘状态
408 |
409 | - `htop`:top 的加强版
410 |
411 | - `last`:登入记录
412 |
413 | - `w`:查看处于登录状态的用户
414 |
415 | - `id`:用户/组 ID 信息
416 |
417 | - `sar`:系统历史数据
418 |
419 | - `iftop` 或 `nethogs`:套接字及进程的网络利用
420 |
421 | - `ss`:套接字数据
422 |
423 | - `dmesg`:引导及系统错误信息
424 |
425 | - `hdparm`:SATA/ATA 磁盘更改及性能分析
426 |
427 | - `lsb_release`:Linux 发行版信息
428 |
429 | - `lsblk`:列出块设备信息:以树形展示你的磁盘以及磁盘分区信息
430 |
431 | - `lshw`,`lscpu`,`lspci`,`lsusb` 和 `dmidecode`:查看硬件信息,包括 CPU、BIOS、RAID、显卡、USB设备等
432 |
433 | - `fortune`,`ddate` 和 `sl`: 额,这主要取决于你是否认为蒸汽火车和莫名其妙的名人名言是否“有用”
434 |
435 |
436 | ## 更多资源
437 |
438 | - [awesome-shell](https://github.com/alebcay/awesome-shell): 一份精心组织的命令行工具及资源的列表。
439 | - [Strict mode](http://redsymbol.net/articles/unofficial-bash-strict-mode/): 为了编写更好的脚本文件。
440 |
441 |
442 | ## 免责声明
443 |
444 | 除去特别微小的任务,记录下这些代码以便他人查看。责任往往伴随着能力,*可以*做并不意味着应该做。
445 |
446 |
447 | ## 授权条款
448 |
449 | [](http://creativecommons.org/licenses/by-sa/4.0/)
450 |
451 | 本文使用授权协议 [Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/)。
--------------------------------------------------------------------------------
/tests/test directory/test_plussign.md:
--------------------------------------------------------------------------------
1 | # C vs C++
2 |
3 | Blabla...
4 |
5 |
--------------------------------------------------------------------------------
/tests/test directory/test_setextwithformatting.md:
--------------------------------------------------------------------------------
1 | Title one
2 | =========
3 |
4 |
5 | * [Title one](#title-one)
6 | * [This is test for setext-style without formatting](#this-is-test-for-setext-style-without-formatting)
7 | * [Title two](#title-two)
8 | * [This is test for setext-style with formatting](#this-is-test-for-setext-style-with-formatting)
9 | * [Title three](#title-three)
10 | * [This is a regression test for atx-style](#this-is-a-regression-test-for-atx-style)
11 | * [Title four is a particularly long title because of wrapping](#title-four-is-a-particularly-long-title-because-of-wrapping)
12 | * [This is a test for long titles](#this-is-a-test-for-long-titles)
13 |
14 |
15 | Blabla...
16 |
17 | ## This is test for setext-style without formatting
18 |
19 | Blabla...
20 |
21 | *Title `two`*
22 | =============
23 |
24 | Blabla...
25 |
26 | ## This is test for setext-style with formatting
27 |
28 | Blabla...
29 |
30 | # Title `three`
31 |
32 | Blabla...
33 |
34 | ## This is a regression test for atx-style
35 |
36 | Blabla...
37 |
38 | # Title four is a particularly long title because of wrapping
39 |
40 | Blabla...
41 |
42 | ## This is a test for long titles
43 |
44 | Blabla...
--------------------------------------------------------------------------------
/tests/test_helper.bash:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | assert_equal() {
4 | if [ "$1" != "$2" ]; then
5 | echo "actual: $1"
6 | echo "expected: $2"
7 | return 1
8 | fi
9 | }
10 |
11 | assert_range() {
12 | if [ $1 -lt $2 ]; then
13 | echo "expected: $1"
14 | echo "greater than: $2"
15 | return 1
16 | fi
17 | if [ $1 -gt $3 ]; then
18 | echo "expected: $1"
19 | echo "less than: $3"
20 | return 1
21 | fi
22 | }
23 |
24 | assert_output() {
25 | assert_equal "$1" "$output"
26 | }
27 |
28 | assert_success() {
29 | if [ "$status" -ne 0 ]; then
30 | echo "command failed with exit status $status"
31 | return 1
32 | elif [ "$#" -gt 0 ]; then
33 | assert_output "$1"
34 | fi
35 | }
36 |
37 | assert_fail() {
38 | if [ "$status" -eq 0 ]; then
39 | echo "command successed, but should fail"
40 | return 1
41 | elif [ "$#" -gt 0 ]; then
42 | assert_output "$1"
43 | fi
44 | }
45 |
--------------------------------------------------------------------------------
/tests/tests.bats:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bats
2 |
3 | load test_helper
4 |
5 |
6 | @test "TOC for local README.md" {
7 | run $BATS_TEST_DIRNAME/../gh-md-toc README.md
8 | assert_success
9 |
10 | assert_equal "${lines[0]}" "Table of Contents"
11 | assert_equal "${lines[1]}" "================="
12 | assert_equal "${lines[2]}" "* [gh-md-toc](#gh-md-toc)"
13 | assert_equal "${lines[3]}" "* [Table of contents](#table-of-contents)"
14 | assert_equal "${lines[4]}" "* [Installation](#installation)"
15 | assert_equal "${lines[5]}" "* [Usage](#usage)"
16 | assert_equal "${lines[6]}" " * [STDIN](#stdin)"
17 | assert_equal "${lines[7]}" " * [Local files](#local-files)"
18 | assert_equal "${lines[8]}" " * [Remote files](#remote-files)"
19 | assert_equal "${lines[9]}" " * [Multiple files](#multiple-files)"
20 | assert_equal "${lines[10]}" " * [Combo](#combo)"
21 | assert_equal "${lines[11]}" " * [Auto insert and update TOC](#auto-insert-and-update-toc)"
22 | assert_equal "${lines[12]}" " * [GitHub token](#github-token)"
23 | assert_equal "${lines[13]}" " * [TOC generation with Github Actions](#toc-generation-with-github-actions)"
24 | assert_equal "${lines[14]}" "* [Tests](#tests)"
25 | assert_equal "${lines[15]}" "* [Dependency](#dependency)"
26 | assert_equal "${lines[16]}" "* [Docker](#docker)"
27 | assert_equal "${lines[17]}" " * [Local](#local)"
28 | assert_equal "${lines[18]}" " * [Public](#public)"
29 | assert_equal "${lines[19]}" ""
30 |
31 | }
32 |
33 | @test "TOC for local README.md with skip headers" {
34 | run $BATS_TEST_DIRNAME/../gh-md-toc --skip-header README.md
35 | assert_success
36 |
37 | assert_equal "${lines[0]}" "Table of Contents"
38 | assert_equal "${lines[1]}" "================="
39 | assert_equal "${lines[2]}" "* [Installation](#installation)"
40 | assert_equal "${lines[3]}" "* [Usage](#usage)"
41 | assert_equal "${lines[4]}" " * [STDIN](#stdin)"
42 | assert_equal "${lines[5]}" " * [Local files](#local-files)"
43 | assert_equal "${lines[6]}" " * [Remote files](#remote-files)"
44 | assert_equal "${lines[7]}" " * [Multiple files](#multiple-files)"
45 | assert_equal "${lines[8]}" " * [Combo](#combo)"
46 | assert_equal "${lines[9]}" " * [Auto insert and update TOC](#auto-insert-and-update-toc)"
47 | assert_equal "${lines[10]}" " * [GitHub token](#github-token)"
48 | assert_equal "${lines[11]}" " * [TOC generation with Github Actions](#toc-generation-with-github-actions)"
49 | assert_equal "${lines[12]}" "* [Tests](#tests)"
50 | assert_equal "${lines[13]}" "* [Dependency](#dependency)"
51 | assert_equal "${lines[14]}" "* [Docker](#docker)"
52 | assert_equal "${lines[15]}" " * [Local](#local)"
53 | assert_equal "${lines[16]}" " * [Public](#public)"
54 | assert_equal "${lines[17]}" ""
55 |
56 | }
57 | # @test "TOC for remote README.md" {
58 | # run $BATS_TEST_DIRNAME/../gh-md-toc https://github.com/ekalinin/sitemap.js/blob/6bc3eb12c898c1037a35a11b2eb24ababdeb3580/README.md
59 | # assert_success
60 | #
61 | # assert_equal "${lines[0]}" "Table of Contents"
62 | # assert_equal "${lines[1]}" "================="
63 | # assert_equal "${lines[2]}" "* [sitemap.js](#sitemapjs)"
64 | # assert_equal "${lines[3]}" " * [Installation](#installation)"
65 | # assert_equal "${lines[4]}" " * [Usage](#usage)"
66 | # assert_equal "${lines[5]}" " * [License](#license)"
67 | # assert_equal "${lines[6]}" ""
68 | # }
69 |
70 | # @test "TOC for mixed README.md (remote/local)" {
71 | # run $BATS_TEST_DIRNAME/../gh-md-toc \
72 | # README.md \
73 | # https://github.com/ekalinin/sitemap.js/blob/6bc3eb12c898c1037a35a11b2eb24ababdeb3580/README.md
74 | # assert_success
75 | #
76 | # assert_equal "${lines[0]}" "* [gh-md-toc](README.md#gh-md-toc)"
77 | # assert_equal "${lines[1]}" "* [Table of contents](README.md#table-of-contents)"
78 | # assert_equal "${lines[2]}" "* [Installation](README.md#installation)"
79 | # assert_equal "${lines[3]}" "* [Usage](README.md#usage)"
80 | # assert_equal "${lines[4]}" " * [STDIN](README.md#stdin)"
81 | # assert_equal "${lines[5]}" " * [Local files](README.md#local-files)"
82 | # assert_equal "${lines[6]}" " * [Remote files](README.md#remote-files)"
83 | # assert_equal "${lines[7]}" " * [Multiple files](README.md#multiple-files)"
84 | # assert_equal "${lines[8]}" " * [Combo](README.md#combo)"
85 | # assert_equal "${lines[9]}" " * [Auto insert and update TOC](README.md#auto-insert-and-update-toc)"
86 | # assert_equal "${lines[10]}" " * [GitHub token](README.md#github-token)"
87 | # assert_equal "${lines[11]}" " * [TOC generation with Github Actions](README.md#toc-generation-with-github-actions)"
88 | # assert_equal "${lines[12]}" "* [Tests](README.md#tests)"
89 | # assert_equal "${lines[13]}" "* [Dependency](README.md#dependency)"
90 | # assert_equal "${lines[14]}" "* [Docker](README.md#docker)"
91 | # assert_equal "${lines[15]}" " * [Local](README.md#local)"
92 | # assert_equal "${lines[16]}" " * [Public](README.md#public)"
93 | # assert_equal "${lines[17]}" "* [sitemap.js](https://github.com/ekalinin/sitemap.js/blob/6bc3eb12c898c1037a35a11b2eb24ababdeb3580/README.md#sitemapjs)"
94 | # assert_equal "${lines[18]}" " * [Installation](https://github.com/ekalinin/sitemap.js/blob/6bc3eb12c898c1037a35a11b2eb24ababdeb3580/README.md#installation)"
95 | # assert_equal "${lines[19]}" " * [Usage](https://github.com/ekalinin/sitemap.js/blob/6bc3eb12c898c1037a35a11b2eb24ababdeb3580/README.md#usage)"
96 | # assert_equal "${lines[20]}" " * [License](https://github.com/ekalinin/sitemap.js/blob/6bc3eb12c898c1037a35a11b2eb24ababdeb3580/README.md#license)"
97 | # assert_equal "${lines[21]}" ""
98 | # }
99 |
100 | @test "TOC for markdown from stdin" {
101 | cat README.md | {
102 | run $BATS_TEST_DIRNAME/../gh-md-toc -
103 | assert_success
104 | assert_equal "${lines[0]}" "* [gh-md-toc](#gh-md-toc)"
105 | assert_equal "${lines[1]}" "* [Table of contents](#table-of-contents)"
106 | assert_equal "${lines[2]}" "* [Installation](#installation)"
107 | assert_equal "${lines[3]}" "* [Usage](#usage)"
108 | assert_equal "${lines[4]}" " * [STDIN](#stdin)"
109 | assert_equal "${lines[5]}" " * [Local files](#local-files)"
110 | assert_equal "${lines[6]}" " * [Remote files](#remote-files)"
111 | assert_equal "${lines[7]}" " * [Multiple files](#multiple-files)"
112 | assert_equal "${lines[8]}" " * [Combo](#combo)"
113 | assert_equal "${lines[9]}" " * [Auto insert and update TOC](#auto-insert-and-update-toc)"
114 | assert_equal "${lines[10]}" " * [GitHub token](#github-token)"
115 | assert_equal "${lines[11]}" " * [TOC generation with Github Actions](#toc-generation-with-github-actions)"
116 | assert_equal "${lines[12]}" "* [Tests](#tests)"
117 | assert_equal "${lines[13]}" "* [Dependency](#dependency)"
118 | }
119 | }
120 |
121 | test_help() {
122 | assert_equal "${lines[1]}" "Usage:"
123 | assert_equal "${lines[2]}" " gh-md-toc [options] src [src] Create TOC for a README file (url or local path)"
124 | assert_equal "${lines[3]}" " gh-md-toc - Create TOC for markdown from STDIN"
125 | assert_equal "${lines[4]}" " gh-md-toc --help Show help"
126 | assert_equal "${lines[5]}" " gh-md-toc --version Show version"
127 | assert_equal "${lines[6]}" "Options:"
128 | assert_equal "${lines[7]}" " --indent Set indent size. Default: 3."
129 | assert_equal "${lines[8]}" " --insert Insert new TOC into original file. For local files only. Default: false."
130 | assert_equal "${lines[10]}" " --no-backup Remove backup file. Set --insert as well. Default: false."
131 | assert_equal "${lines[11]}" " --hide-footer Do not write date & author of the last TOC update. Set --insert as well. Default: false."
132 | assert_equal "${lines[12]}" " --skip-header Hide entry of the topmost headlines. Default: false."
133 | assert_equal "${#lines[@]}" "14"
134 | }
135 |
136 | @test "--help" {
137 | run $BATS_TEST_DIRNAME/../gh-md-toc --help
138 | assert_success
139 | test_help
140 | }
141 |
142 | @test "no arguments" {
143 | run $BATS_TEST_DIRNAME/../gh-md-toc
144 | assert_success
145 | test_help
146 | }
147 |
148 | @test "--version" {
149 | run $BATS_TEST_DIRNAME/../gh-md-toc --version
150 | assert_success
151 | assert_equal "${lines[0]}" "0.10.0"
152 | assert_equal "${lines[1]}" "os: `uname -s`"
153 | assert_equal "${lines[2]}" "arch: `uname -m`"
154 | }
155 |
156 | @test "TOC for non-english chars, #6, #10" {
157 | run $BATS_TEST_DIRNAME/../gh-md-toc tests/test\ directory/test_nonenglishchars.md
158 | assert_success
159 |
160 | assert_equal "${lines[2]}" "* [命令行的艺术](#命令行的艺术)"
161 | assert_equal "${lines[3]}" " * [必读](#必读)"
162 | assert_equal "${lines[4]}" " * [基础](#基础)"
163 | assert_equal "${lines[5]}" " * [日常使用](#日常使用)"
164 | }
165 |
166 | # @test "TOC for remote non-english chars (remote load), #6, #10" {
167 | # run $BATS_TEST_DIRNAME/../gh-md-toc \
168 | # https://github.com/ekalinin/envirius/blob/f939d3b6882bfb6ecb28ef7b6e62862f934ba945/README.ru.md
169 | # assert_success
170 | #
171 | # assert_equal "${lines[2]}" "* [envirius](#envirius)"
172 | # assert_equal "${lines[3]}" " * [Идея](#идея)"
173 | # assert_equal "${lines[4]}" " * [Особенности](#особенности)"
174 | # assert_equal "${lines[5]}" "* [Установка](#установка)"
175 | #
176 | #
177 | # run $BATS_TEST_DIRNAME/../gh-md-toc \
178 | # https://github.com/jlevy/the-art-of-command-line/blob/217da3b4fa751014ecc122fd9fede2328a7eeb3e/README-zh.md
179 | # assert_success
180 | #
181 | # assert_equal "${lines[2]}" "* [命令行的艺术](#命令行的艺术)"
182 | # assert_equal "${lines[3]}" " * [必读](#必读)"
183 | # assert_equal "${lines[4]}" " * [基础](#基础)"
184 | # assert_equal "${lines[5]}" " * [日常使用](#日常使用)"
185 | #
186 | #
187 | # run $BATS_TEST_DIRNAME/../gh-md-toc \
188 | # https://github.com/jlevy/the-art-of-command-line/blob/217da3b4fa751014ecc122fd9fede2328a7eeb3e/README-pt.md
189 | # assert_success
190 | #
191 | # assert_equal "${lines[2]}" "* [A arte da linha de comando](#a-arte-da-linha-de-comando)"
192 | # assert_equal "${lines[3]}" " * [Meta](#meta)"
193 | # assert_equal "${lines[4]}" " * [Básico](#básico)"
194 | # assert_equal "${lines[5]}" " * [Uso diário](#uso-diário)"
195 | # }
196 |
197 | @test "TOC for text with backquote, #13" {
198 | run $BATS_TEST_DIRNAME/../gh-md-toc tests/test\ directory/test_backquote.md
199 | assert_success
200 |
201 | assert_equal "${lines[2]}" "* [The command foo1](#the-command-foo1)"
202 | assert_equal "${lines[3]}" " * [The command foo2 is better](#the-command-foo2-is-better)"
203 | assert_equal "${lines[4]}" "* [The command bar1](#the-command-bar1)"
204 | assert_equal "${lines[5]}" " * [The command bar2 is better](#the-command-bar2-is-better)"
205 | assert_equal "${lines[6]}" " * [The command bar3 is the best](#the-command-bar3-is-the-best)"
206 | }
207 |
208 | @test "TOC for text with plus signs, #100" {
209 | run $BATS_TEST_DIRNAME/../gh-md-toc tests/test\ directory/test_plussign.md
210 | assert_success
211 |
212 | assert_equal "${lines[2]}" "* [C vs C++](#c-vs-c)"
213 | }
214 |
215 | @test "Toc for file path with space, #136" {
216 | run $BATS_TEST_DIRNAME/../gh-md-toc --no-backup --hide-footer tests/test\ directory/test_filepathwithspace.md
217 | assert_success
218 |
219 | assert_equal "${lines[2]}" "* [Title](#title)"
220 | assert_equal "${lines[3]}" " * [This is test for file path with space](#this-is-test-for-file-path-with-space)"
221 | }
222 |
223 | @test "Toc for setext heading with formatting, #145" {
224 | run $BATS_TEST_DIRNAME/../gh-md-toc --no-backup --hide-footer tests/test\ directory/test_setextwithformatting.md
225 | assert_success
226 |
227 | assert_equal "${lines[2]}" "* [Title one](#title-one)"
228 | assert_equal "${lines[3]}" " * [This is test for setext-style without formatting](#this-is-test-for-setext-style-without-formatting)"
229 | assert_equal "${lines[4]}" "* [Title two](#title-two)"
230 | assert_equal "${lines[5]}" " * [This is test for setext-style with formatting](#this-is-test-for-setext-style-with-formatting)"
231 | assert_equal "${lines[6]}" "* [Title three](#title-three)"
232 | assert_equal "${lines[7]}" " * [This is a regression test for atx-style](#this-is-a-regression-test-for-atx-style)"
233 | assert_equal "${lines[8]}" "* [Title four is a particularly long title because of wrapping](#title-four-is-a-particularly-long-title-because-of-wrapping)"
234 | assert_equal "${lines[9]}" " * [This is a test for long titles](#this-is-a-test-for-long-titles)"
235 | }
236 |
--------------------------------------------------------------------------------