mdtoc 0.1.4 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/bin/mdtoc +1 -1
- data/lib/mdtoc/cli.rb +9 -9
- data/lib/mdtoc/markdown/header.rb +12 -4
- data/lib/mdtoc/markdown/parser.rb +7 -7
- data/lib/mdtoc/node.rb +28 -28
- data/lib/mdtoc/version.rb +2 -2
- data/lib/mdtoc/writer.rb +4 -4
- data/lib/mdtoc.rb +6 -6
- metadata +58 -43
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 67fab334c40c8a785d1ac0bcca6234a1fc048ad479ed7fb4df093f0eb14c2116
|
4
|
+
data.tar.gz: c80647e458fd2ceaa60189688d0c7a831ad023a20cb471fcc37d512e382db627
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2e6e9abb89b30d7abed179b42b00a8d871ab7b5ea6b96a4607ea5284bed3a1e1b2bb3355e0258d84498b78c8dba1463ab170f46e9c4c7963f4931b2efa910483
|
7
|
+
data.tar.gz: a3c414d2472f1f2ad976bc02eca284c9b4f522ee21624ad30534d33563242cb2ac04457cf33a4bc1998f2af232f4aa803c2051ee066f910444d6f881c7b69396
|
data/bin/mdtoc
CHANGED
data/lib/mdtoc/cli.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
|
-
# typed:
|
1
|
+
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
4
|
+
require "optparse"
|
5
|
+
require "sorbet-runtime"
|
6
|
+
require "tempfile"
|
7
7
|
|
8
8
|
module Mdtoc
|
9
9
|
module CLI
|
@@ -14,19 +14,19 @@ module Mdtoc
|
|
14
14
|
def parse(args)
|
15
15
|
parser = OptionParser.new do |parser_|
|
16
16
|
parser_.banner = "Usage: #{parser_.program_name} [options] files or directories..."
|
17
|
-
parser_.on(
|
17
|
+
parser_.on("-h", "--help", "Show this message") do
|
18
18
|
puts parser_
|
19
19
|
exit
|
20
20
|
end
|
21
|
-
parser_.on(
|
22
|
-
parser_.on(
|
23
|
-
parser_.on(
|
21
|
+
parser_.on("-o", "--output PATH", "Update a table of contents in the file at PATH")
|
22
|
+
parser_.on("-a", "--[no-]append", "Append to the --output file if a <!-- mdtoc --> tag isn't found")
|
23
|
+
parser_.on("-c", "--[no-]create", "Create the --output file if it does not exist")
|
24
24
|
end
|
25
25
|
|
26
26
|
options = Options.new
|
27
27
|
options.paths = parser.parse(args, into: options)
|
28
28
|
if options.paths.empty?
|
29
|
-
warn(
|
29
|
+
warn("Specify at least one file or directory to read")
|
30
30
|
exit(1)
|
31
31
|
end
|
32
32
|
options
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# typed: true
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
require
|
4
|
+
require "sorbet-runtime"
|
5
5
|
|
6
6
|
module Mdtoc
|
7
7
|
module Markdown
|
@@ -13,14 +13,15 @@ module Mdtoc
|
|
13
13
|
if depth < 0
|
14
14
|
raise ArgumentError, "Header depth must be >= 0, but was #{depth}"
|
15
15
|
end
|
16
|
+
|
16
17
|
@depth = depth
|
17
|
-
@label = label
|
18
|
+
@label = normalize_label(label)
|
18
19
|
@url = url
|
19
20
|
end
|
20
21
|
|
21
22
|
sig { returns(String) }
|
22
23
|
def to_s
|
23
|
-
prefix =
|
24
|
+
prefix = " " * 2 * @depth
|
24
25
|
"#{prefix}* [#{@label}](#{@url})"
|
25
26
|
end
|
26
27
|
|
@@ -28,13 +29,20 @@ module Mdtoc
|
|
28
29
|
def top_level?(relative_to_depth)
|
29
30
|
@depth == relative_to_depth
|
30
31
|
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def normalize_label(label)
|
36
|
+
label = label.strip.tr("\t\n\r", "") # Remove whitespace characters other than spaces.
|
37
|
+
label.gsub(/\[(.*)\]\(.*\)/, '\1') # Remove links
|
38
|
+
end
|
31
39
|
end
|
32
40
|
|
33
41
|
class HeaderWithFragment < Header
|
34
42
|
sig { params(depth: Integer, label: String, url: String).void }
|
35
43
|
def initialize(depth, label, url)
|
36
|
-
url = "#{url}##{label.strip.downcase.tr(' ', '-').gsub(/[^\w\-]/, '')}"
|
37
44
|
super
|
45
|
+
@url += "##{@label.downcase.tr(" ", "-").gsub(/[^\w\-]/, "")}"
|
38
46
|
end
|
39
47
|
end
|
40
48
|
end
|
@@ -1,8 +1,8 @@
|
|
1
|
-
# typed:
|
1
|
+
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
require
|
5
|
-
require_relative
|
4
|
+
require "sorbet-runtime"
|
5
|
+
require_relative "header"
|
6
6
|
|
7
7
|
module Mdtoc
|
8
8
|
module Markdown
|
@@ -22,10 +22,10 @@ module Mdtoc
|
|
22
22
|
skip = T.let(false, T::Boolean)
|
23
23
|
lines.filter_map do |line|
|
24
24
|
# Skip code blocks.
|
25
|
-
if line.start_with?(
|
25
|
+
if line.start_with?("```") && !T.must(line[3..]).strip.end_with?("```")
|
26
26
|
skip = !skip
|
27
27
|
end
|
28
|
-
next if skip || !line.start_with?(
|
28
|
+
next if skip || !line.start_with?("#")
|
29
29
|
|
30
30
|
header(line)
|
31
31
|
end
|
@@ -36,9 +36,9 @@ module Mdtoc
|
|
36
36
|
sig { params(line: String).returns(HeaderWithFragment) }
|
37
37
|
def header(line)
|
38
38
|
m = T.must(line.strip.match(/^(#+)\s*(.*)$/))
|
39
|
-
num_hashes = m[1]&.count(
|
39
|
+
num_hashes = m[1]&.count("#") || 1
|
40
40
|
depth = @depth + num_hashes - 1
|
41
|
-
label = m[2] ||
|
41
|
+
label = m[2] || ""
|
42
42
|
HeaderWithFragment.new(depth, label, @url)
|
43
43
|
end
|
44
44
|
end
|
data/lib/mdtoc/node.rb
CHANGED
@@ -1,10 +1,10 @@
|
|
1
|
-
# typed:
|
1
|
+
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require_relative
|
7
|
-
require_relative
|
4
|
+
require "pathname"
|
5
|
+
require "sorbet-runtime"
|
6
|
+
require_relative "markdown/header"
|
7
|
+
require_relative "markdown/parser"
|
8
8
|
|
9
9
|
module Mdtoc
|
10
10
|
class Node
|
@@ -43,35 +43,35 @@ module Mdtoc
|
|
43
43
|
|
44
44
|
sig { returns(String) }
|
45
45
|
def label
|
46
|
-
File.basename(@path, File.extname(@path)).gsub(/_+/,
|
46
|
+
File.basename(@path, File.extname(@path)).gsub(/_+/, " ").gsub(/\s+/, " ").capitalize
|
47
47
|
end
|
48
|
-
end
|
49
48
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
49
|
+
class DirNode < Node
|
50
|
+
sig { override.returns(T::Array[Mdtoc::Markdown::Header]) }
|
51
|
+
def headers
|
52
|
+
readme_path = T.let(nil, T.nilable(String))
|
53
|
+
child_headers = Dir
|
54
|
+
.each_child(@path)
|
55
|
+
.reject { |path| readme_path = File.join(@path, path) if path.casecmp?("readme.md") }
|
56
|
+
.sort!
|
57
|
+
.flat_map { |path| Node.for_path(File.join(@path, path), @depth + 1).headers }
|
58
|
+
return child_headers unless readme_path
|
60
59
|
|
61
|
-
|
62
|
-
|
63
|
-
|
60
|
+
# Include the headers from the README at the beginning.
|
61
|
+
readme_headers = FileNode.new(readme_path, @depth).headers
|
62
|
+
readme_headers + child_headers
|
63
|
+
end
|
64
64
|
end
|
65
|
-
end
|
66
65
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
66
|
+
class FileNode < Node
|
67
|
+
sig { override.returns(T::Array[Mdtoc::Markdown::Header]) }
|
68
|
+
def headers
|
69
|
+
parser = Markdown::Parser.new(@depth, @path)
|
70
|
+
headers = parser.headers(File.foreach(@path))
|
71
|
+
return headers if headers[0]&.top_level?(@depth)
|
73
72
|
|
74
|
-
|
73
|
+
headers.unshift(Mdtoc::Markdown::Header.new(@depth, label, @path))
|
74
|
+
end
|
75
75
|
end
|
76
76
|
end
|
77
77
|
end
|
data/lib/mdtoc/version.rb
CHANGED
data/lib/mdtoc/writer.rb
CHANGED
@@ -1,10 +1,10 @@
|
|
1
|
-
# typed:
|
1
|
+
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
module Mdtoc
|
5
5
|
module Writer
|
6
|
-
COMMENT_BEGIN =
|
7
|
-
COMMENT_END =
|
6
|
+
COMMENT_BEGIN = "<!-- mdtoc -->"
|
7
|
+
COMMENT_END = "<!-- mdtoc-end -->"
|
8
8
|
|
9
9
|
class << self
|
10
10
|
extend T::Sig
|
@@ -13,7 +13,7 @@ module Mdtoc
|
|
13
13
|
def write(toc, path, append, create)
|
14
14
|
validate_path(path, create)
|
15
15
|
new_content = content(toc, path, append)
|
16
|
-
File.open(path,
|
16
|
+
File.open(path, "w") do |f|
|
17
17
|
f.write(new_content)
|
18
18
|
end
|
19
19
|
end
|
data/lib/mdtoc.rb
CHANGED
@@ -1,11 +1,11 @@
|
|
1
|
-
# typed:
|
1
|
+
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require_relative
|
7
|
-
require_relative
|
8
|
-
require_relative
|
4
|
+
require "optparse"
|
5
|
+
require "tempfile"
|
6
|
+
require_relative "mdtoc/cli"
|
7
|
+
require_relative "mdtoc/node"
|
8
|
+
require_relative "mdtoc/writer"
|
9
9
|
|
10
10
|
module Mdtoc
|
11
11
|
class << self
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mdtoc
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- andornaut
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2025-04-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: minitest
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '5'
|
19
|
+
version: '5.25'
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '5'
|
26
|
+
version: '5.25'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: rake
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -58,56 +58,56 @@ dependencies:
|
|
58
58
|
requirements:
|
59
59
|
- - "~>"
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: '
|
61
|
+
version: '1.50'
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version: '
|
68
|
+
version: '1.50'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: rubocop-shopify
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
73
|
- - ">="
|
74
74
|
- !ruby/object:Gem::Version
|
75
|
-
version:
|
75
|
+
version: 2.0.0
|
76
76
|
type: :development
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
80
|
- - ">="
|
81
81
|
- !ruby/object:Gem::Version
|
82
|
-
version:
|
82
|
+
version: 2.0.0
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
84
|
name: rubocop-sorbet
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
86
86
|
requirements:
|
87
87
|
- - "~>"
|
88
88
|
- !ruby/object:Gem::Version
|
89
|
-
version:
|
89
|
+
version: 0.10.0
|
90
90
|
type: :development
|
91
91
|
prerelease: false
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
93
93
|
requirements:
|
94
94
|
- - "~>"
|
95
95
|
- !ruby/object:Gem::Version
|
96
|
-
version:
|
96
|
+
version: 0.10.0
|
97
97
|
- !ruby/object:Gem::Dependency
|
98
98
|
name: unparser
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|
100
100
|
requirements:
|
101
101
|
- - "~>"
|
102
102
|
- !ruby/object:Gem::Version
|
103
|
-
version: 0.
|
103
|
+
version: 0.6.0
|
104
104
|
type: :development
|
105
105
|
prerelease: false
|
106
106
|
version_requirements: !ruby/object:Gem::Requirement
|
107
107
|
requirements:
|
108
108
|
- - "~>"
|
109
109
|
- !ruby/object:Gem::Version
|
110
|
-
version: 0.
|
110
|
+
version: 0.6.0
|
111
111
|
- !ruby/object:Gem::Dependency
|
112
112
|
name: sorbet-runtime
|
113
113
|
requirement: !ruby/object:Gem::Requirement
|
@@ -133,13 +133,13 @@ description: |
|
|
133
133
|
|
134
134
|
* [Ruby](https://www.ruby-lang.org/en/) (see [.ruby-version](./.ruby-version))
|
135
135
|
|
136
|
-
```
|
137
|
-
|
136
|
+
```bash
|
137
|
+
gem install mdtoc
|
138
138
|
```
|
139
139
|
|
140
140
|
## Usage
|
141
141
|
|
142
|
-
```
|
142
|
+
```bash
|
143
143
|
$ mdtoc --help
|
144
144
|
Usage: mdtoc [options] files or directories...
|
145
145
|
-h, --help Show this message
|
@@ -149,67 +149,82 @@ description: |
|
|
149
149
|
```
|
150
150
|
|
151
151
|
1. Add a `<!-- mdtoc -->` tag to a Markdown file.
|
152
|
+
|
153
|
+
```bash
|
154
|
+
echo '<!-- mdtoc -->' >> README.md
|
152
155
|
```
|
153
|
-
|
154
|
-
```
|
156
|
+
|
155
157
|
2. Run `mdtoc` and specify input files or directories (eg. the "test/samples" directory) and an output file (eg. "README.md").
|
156
|
-
|
157
|
-
|
158
|
+
|
159
|
+
```bash
|
160
|
+
mdtoc -aco README.md test/samples
|
158
161
|
```
|
159
162
|
|
160
163
|
## Example Rakefile
|
161
164
|
|
162
|
-
|
163
|
-
|
165
|
+
Create a `Rakefile` with the contents below, then run
|
166
|
+
[`rake`](https://github.com/ruby/rake) to:
|
164
167
|
|
165
|
-
|
168
|
+
* `git pull`
|
169
|
+
* `git add` any *.md files
|
170
|
+
* Run `mdtoc` to update the generated table of contents in the ./README.md file
|
171
|
+
* Git commit and push any changes
|
172
|
+
|
173
|
+
```ruby
|
166
174
|
task default: %w[mdtoc]
|
167
175
|
|
168
176
|
desc 'Update Markdown table of contents and push changes to the git repository'
|
169
|
-
task :mdtoc do
|
170
|
-
command = <<~
|
177
|
+
task :mdtoc do
|
178
|
+
command = <<~CMD
|
171
179
|
set -e
|
172
|
-
git pull
|
173
180
|
if [ -n "$(git diff --name-only --diff-filter=U)" ]; then
|
174
181
|
echo 'Error: conflicts exist' >&2
|
175
182
|
exit 1
|
176
183
|
fi
|
177
184
|
mdtoc --append --create --output README.md docs/
|
178
185
|
git add *.md **/*.md
|
179
|
-
git commit -
|
186
|
+
git commit -qm 'Update TOC' || true
|
187
|
+
git pull
|
180
188
|
git push
|
181
|
-
|
182
|
-
|
189
|
+
CMD
|
190
|
+
sh command, verbose: false do |ok, status|
|
191
|
+
unless ok
|
192
|
+
fail "Failed with status: #{status.exitstatus}"
|
193
|
+
end
|
194
|
+
end
|
183
195
|
end
|
184
196
|
```
|
185
197
|
|
198
|
+
See [andornaut/til](https://github.com/andornaut/til/blob/master/Rakefile) for an example.
|
199
|
+
|
186
200
|
## Development
|
187
201
|
|
188
|
-
###
|
202
|
+
### Setup
|
189
203
|
|
190
204
|
Requirements:
|
191
205
|
|
192
206
|
* [Bundler](https://bundler.io/)
|
193
207
|
|
194
|
-
```
|
208
|
+
```bash
|
195
209
|
# Install dependencies
|
196
|
-
|
210
|
+
bundle
|
197
211
|
```
|
198
212
|
|
199
|
-
###
|
213
|
+
### Tasks
|
200
214
|
|
201
|
-
```
|
215
|
+
```bash
|
202
216
|
# List rake tasks
|
203
217
|
$ rake -T
|
204
|
-
rake build
|
205
|
-
rake default
|
206
|
-
rake install
|
207
|
-
rake install:local
|
208
|
-
rake release[remote]
|
209
|
-
rake rubocop
|
210
|
-
rake rubocop:
|
211
|
-
rake
|
212
|
-
rake
|
218
|
+
rake build # Build mdtoc-0.1.5.gem into the pkg directory
|
219
|
+
rake default # Run the build, rubocop:autocorrect_all, sorbet and test tasks
|
220
|
+
rake install # Build and install mdtoc-0.1.5.gem into system gems
|
221
|
+
rake install:local # Build and install mdtoc-0.1.5.gem into system gems without network access
|
222
|
+
rake release[remote] # Create tag v0.1.5 and build and push mdtoc-0.1.5.gem to rubygems.org
|
223
|
+
rake rubocop # Run RuboCop
|
224
|
+
rake rubocop:autocorrect # Autocorrect RuboCop offenses (only when it's safe)
|
225
|
+
rake rubocop:autocorrect_all # Autocorrect RuboCop offenses (safe and unsafe)
|
226
|
+
rake sorbet # Run the Sorbet type checker
|
227
|
+
rake test # Run tests
|
213
228
|
|
214
229
|
# Run mdtoc with test inputs
|
215
230
|
$ ruby -Ilib bin/mdtoc test/samples
|
@@ -250,7 +265,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
250
265
|
- !ruby/object:Gem::Version
|
251
266
|
version: '0'
|
252
267
|
requirements: []
|
253
|
-
rubygems_version: 3.
|
268
|
+
rubygems_version: 3.2.3
|
254
269
|
signing_key:
|
255
270
|
specification_version: 4
|
256
271
|
summary: Read Markdown files and output a table of contents
|