mdtoc 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/bin/mdtoc +6 -0
- data/lib/mdtoc/cli.rb +48 -0
- data/lib/mdtoc/markdown.rb +78 -0
- data/lib/mdtoc/node.rb +67 -0
- data/lib/mdtoc/version.rb +6 -0
- data/lib/mdtoc/writer.rb +68 -0
- data/lib/mdtoc.rb +40 -0
- data/test/samples/README.md +18 -0
- data/test/samples/a/c.md +2 -0
- data/test/samples/a/d/f.md +1 -0
- data/test/samples/a/e.md +1 -0
- data/test/samples/a/g/README.md +1 -0
- data/test/samples/a/g/h.md +1 -0
- data/test/samples/a/readme.md +4 -0
- data/test/test_markdown.rb +127 -0
- data/test/test_node.rb +50 -0
- metadata +171 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: ae0f7d7fa8f96a2d68073f0fbc5a0b92f8ac8182b82fbd2a7bcabb074449d525
|
4
|
+
data.tar.gz: a0af9901b92c93dc3eea21ddae16fa349f7be653945f51e9867d2f69efc1073a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 350566fff5655c9ab17502f02992c23d04e23d844cd94a6c25788d9c745142135f3cc2a1d41e7b7cc6a691caf209edfb9f689816d2b3a1379a75f061a756fddc
|
7
|
+
data.tar.gz: 33af4aeb021e9b61bb111ab2c20904f1af90afb4bf5c62361fea8e08d3bb21fcde68a9dc45b1863035f321db4a23a367898b9648f270931394c76e3437562140
|
data/bin/mdtoc
ADDED
data/lib/mdtoc/cli.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
# typed: true
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'optparse'
|
5
|
+
require 'sorbet-runtime'
|
6
|
+
require 'tempfile'
|
7
|
+
require_relative 'markdown'
|
8
|
+
require_relative 'node'
|
9
|
+
require_relative 'writer'
|
10
|
+
|
11
|
+
module Mdtoc
|
12
|
+
module CLI
|
13
|
+
class Options < T::Struct
|
14
|
+
extend T::Sig
|
15
|
+
|
16
|
+
prop :append, T::Boolean, default: false
|
17
|
+
prop :create, T::Boolean, default: false
|
18
|
+
prop :output, T.nilable(String)
|
19
|
+
prop :paths, T::Array[String], default: []
|
20
|
+
|
21
|
+
def []=(key, val)
|
22
|
+
send("#{key}=", val)
|
23
|
+
end
|
24
|
+
|
25
|
+
sig { params(args: T::Array[String]).returns(Options) }
|
26
|
+
def self.for_args(args)
|
27
|
+
parser = OptionParser.new do |parser_|
|
28
|
+
parser_.banner = "Usage: #{parser_.program_name} [options] files or directories..."
|
29
|
+
parser_.on('-h', '--help', 'Show this message') do
|
30
|
+
puts parser_
|
31
|
+
exit
|
32
|
+
end
|
33
|
+
parser_.on('-o', '--output PATH', 'Update a table of contents in the file at PATH')
|
34
|
+
parser_.on('-a', '--[no-]append', 'Append to the --output file if a <!-- mdtoc --> tag isn\'t found')
|
35
|
+
parser_.on('-c', '--[no-]create', 'Create the --output file if it does not exist')
|
36
|
+
end
|
37
|
+
|
38
|
+
options = Options.new
|
39
|
+
options.paths = parser.parse(args, into: options)
|
40
|
+
if options.paths.empty?
|
41
|
+
warn('Specify at least one file or directory to read')
|
42
|
+
exit(1)
|
43
|
+
end
|
44
|
+
options
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# typed: true
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'sorbet-runtime'
|
5
|
+
|
6
|
+
module Mdtoc
|
7
|
+
module Markdown
|
8
|
+
class Header
|
9
|
+
extend T::Sig
|
10
|
+
|
11
|
+
sig { params(depth: Integer, label: String, url: String).void }
|
12
|
+
def initialize(depth, label, url)
|
13
|
+
if depth < 0
|
14
|
+
raise ArgumentError, "Header depth must be >= 0, but was #{depth}"
|
15
|
+
end
|
16
|
+
@depth = depth
|
17
|
+
@label = label.strip.gsub(/\s+/, ' ')
|
18
|
+
@url = url
|
19
|
+
end
|
20
|
+
|
21
|
+
sig { params(relative_to_depth: Integer).returns(T::Boolean) }
|
22
|
+
def top_level?(relative_to_depth)
|
23
|
+
@depth == relative_to_depth
|
24
|
+
end
|
25
|
+
|
26
|
+
sig { returns(String) }
|
27
|
+
def to_s
|
28
|
+
prefix = ' ' * 2 * @depth
|
29
|
+
"#{prefix}* [#{@label}](#{@url})"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class HeaderWithFragment < Header
|
34
|
+
sig { params(depth: Integer, label: String, url: String).void }
|
35
|
+
def initialize(depth, label, url)
|
36
|
+
url = "#{url}##{label.downcase.strip.gsub(/ /, '-').gsub(/[^\w\-_ ]/, '')}"
|
37
|
+
super
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class Parser
|
42
|
+
extend T::Sig
|
43
|
+
|
44
|
+
sig { params(depth: Integer, url: String).void }
|
45
|
+
def initialize(depth, url)
|
46
|
+
@depth = depth
|
47
|
+
@url = url
|
48
|
+
end
|
49
|
+
|
50
|
+
sig { params(lines: T::Enumerable[String]).returns(T::Array[Header]) }
|
51
|
+
def headers(lines)
|
52
|
+
# TODO: Skip headers within multi-line comments.
|
53
|
+
# TODO: Handle --- and === style headers.
|
54
|
+
skip = T.let(false, T::Boolean)
|
55
|
+
lines.filter_map do |line|
|
56
|
+
# Skip code blocks.
|
57
|
+
if line.start_with?('```') && !T.must(line[3..]).strip.end_with?('```')
|
58
|
+
skip = !skip
|
59
|
+
end
|
60
|
+
next if skip || !line.start_with?('#')
|
61
|
+
|
62
|
+
header(line)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
sig { params(line: String).returns(Header) }
|
69
|
+
def header(line)
|
70
|
+
m = T.must(line.strip.match(/^(#+)\s*(.*)$/))
|
71
|
+
num_hashes = m[1]&.count('#') || 1
|
72
|
+
depth = @depth + num_hashes - 1
|
73
|
+
label = m[2] || ''
|
74
|
+
HeaderWithFragment.new(depth, label, @url)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
data/lib/mdtoc/node.rb
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
# typed: true
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'pathname'
|
5
|
+
require 'sorbet-runtime'
|
6
|
+
require_relative 'markdown'
|
7
|
+
|
8
|
+
module Mdtoc
|
9
|
+
class Node
|
10
|
+
extend T::Helpers
|
11
|
+
extend T::Sig
|
12
|
+
abstract!
|
13
|
+
|
14
|
+
sig { params(path: String, depth: Integer).returns(Node) }
|
15
|
+
def self.for_path(path, depth = 0)
|
16
|
+
# Ensure that `path` is a relative path, so that all links are relative and therefore portable.
|
17
|
+
path = Pathname.new(path)
|
18
|
+
path = path.relative_path_from(Dir.pwd) if path.absolute?
|
19
|
+
path = path.to_s
|
20
|
+
File.directory?(path) ? DirNode.new(path, depth) : FileNode.new(path, depth)
|
21
|
+
end
|
22
|
+
|
23
|
+
sig { params(path: String, depth: Integer).void }
|
24
|
+
def initialize(path, depth)
|
25
|
+
@path = path
|
26
|
+
@depth = depth
|
27
|
+
end
|
28
|
+
|
29
|
+
sig { returns(String) }
|
30
|
+
def label
|
31
|
+
File.basename(@path, File.extname(@path)).gsub(/_+/, ' ').gsub(/\s+/, ' ').capitalize
|
32
|
+
end
|
33
|
+
|
34
|
+
sig { abstract.returns(T::Array[Mdtoc::Markdown::Header]) }
|
35
|
+
def headers; end
|
36
|
+
end
|
37
|
+
|
38
|
+
class DirNode < Node
|
39
|
+
sig { override.returns(T::Array[Mdtoc::Markdown::Header]) }
|
40
|
+
def headers
|
41
|
+
readme_path = T.let(nil, T.nilable(String))
|
42
|
+
child_headers = Dir
|
43
|
+
.each_child(@path)
|
44
|
+
.map { |path| File.join(@path, path) }
|
45
|
+
.reject { |path| readme_path = path if File.basename(path).downcase == 'readme.md' }
|
46
|
+
.sort!
|
47
|
+
.map { |path| Node.for_path(path, @depth + 1).headers }
|
48
|
+
.flatten(1)
|
49
|
+
return child_headers unless readme_path
|
50
|
+
|
51
|
+
# Include the headers from the README at the beginning.
|
52
|
+
readme_headers = FileNode.new(readme_path, @depth).headers
|
53
|
+
readme_headers.push(*child_headers)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
class FileNode < Node
|
58
|
+
sig { override.returns(T::Array[Mdtoc::Markdown::Header]) }
|
59
|
+
def headers
|
60
|
+
parser = Markdown::Parser.new(@depth, @path)
|
61
|
+
headers = parser.headers(File.foreach(@path))
|
62
|
+
return headers if headers[0]&.top_level?(@depth)
|
63
|
+
|
64
|
+
headers.unshift(Mdtoc::Markdown::Header.new(@depth, label, @path))
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
data/lib/mdtoc/writer.rb
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
# typed: true
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Mdtoc
|
5
|
+
module Writer
|
6
|
+
COMMENT_BEGIN = '<!-- mdtoc -->'
|
7
|
+
COMMENT_END = '<!-- mdtoc-end -->'
|
8
|
+
|
9
|
+
class << self
|
10
|
+
extend T::Sig
|
11
|
+
|
12
|
+
sig { params(toc: String, path: String, append: T::Boolean, create: T::Boolean).void }
|
13
|
+
def write(toc, path, append, create)
|
14
|
+
validate_path(path, create)
|
15
|
+
new_content = content(toc, path, append)
|
16
|
+
File.open(path, 'w') do |f|
|
17
|
+
f.write(new_content)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
sig { params(toc: String, path: String, append: T::Boolean).returns(String) }
|
24
|
+
def content(toc, path, append)
|
25
|
+
toc = "#{COMMENT_BEGIN}\n#{toc}\n#{COMMENT_END}"
|
26
|
+
|
27
|
+
begin
|
28
|
+
f = File.open(path)
|
29
|
+
rescue
|
30
|
+
# If File.open failed because the file didn't exist, then we know that --create
|
31
|
+
# was specified due to the validation in self.validate_path.
|
32
|
+
return "#{toc}\n"
|
33
|
+
end
|
34
|
+
begin
|
35
|
+
old_content = T.must(f.read)
|
36
|
+
ensure
|
37
|
+
f.close
|
38
|
+
end
|
39
|
+
|
40
|
+
if Regexp.new(Regexp.escape(COMMENT_BEGIN), Regexp::IGNORECASE).match?(old_content)
|
41
|
+
return old_content.gsub(
|
42
|
+
/#{Regexp.escape(COMMENT_BEGIN)}(.*#{Regexp.escape(COMMENT_END)})?/im, toc
|
43
|
+
)
|
44
|
+
elsif append
|
45
|
+
return "#{old_content}\n#{toc}\n"
|
46
|
+
end
|
47
|
+
|
48
|
+
warn("Could not update #{path}, because the target HTML tag \"#{COMMENT_BEGIN}\" was not found")
|
49
|
+
exit(1)
|
50
|
+
end
|
51
|
+
|
52
|
+
sig { params(path: String, create: T::Boolean).void }
|
53
|
+
def validate_path(path, create)
|
54
|
+
if path
|
55
|
+
if File.exist?(path)
|
56
|
+
unless File.file?(path)
|
57
|
+
warn("--output PATH \"#{path}\" is not a regular file")
|
58
|
+
exit
|
59
|
+
end
|
60
|
+
elsif !create
|
61
|
+
warn("--output PATH \"#{path}\" does not exist. Specify --create to create it.")
|
62
|
+
exit
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
data/lib/mdtoc.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
# typed: true
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'optparse'
|
5
|
+
require 'tempfile'
|
6
|
+
require_relative 'mdtoc/cli'
|
7
|
+
require_relative 'mdtoc/markdown'
|
8
|
+
require_relative 'mdtoc/node'
|
9
|
+
require_relative 'mdtoc/writer'
|
10
|
+
|
11
|
+
module Mdtoc
|
12
|
+
class << self
|
13
|
+
extend T::Sig
|
14
|
+
|
15
|
+
sig { params(args: T::Array[String]).void }
|
16
|
+
def main(args)
|
17
|
+
options = Mdtoc::CLI::Options.for_args(args)
|
18
|
+
toc = render_toc(options.paths)
|
19
|
+
unless options.output
|
20
|
+
puts toc
|
21
|
+
return
|
22
|
+
end
|
23
|
+
|
24
|
+
Mdtoc::Writer.write(toc, T.must(options.output), options.append, options.create)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
sig { params(paths: T::Array[String]).returns(String) }
|
30
|
+
def render_toc(paths)
|
31
|
+
paths
|
32
|
+
.map { |path| Mdtoc::Node.for_path(path).headers }
|
33
|
+
.flatten(1)
|
34
|
+
.map(&:to_s)
|
35
|
+
.join("\n")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
Mdtoc.main(ARGV) if __FILE__ == $PROGRAM_NAME
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# Title
|
2
|
+
intro text
|
3
|
+
# Ignore this non-title
|
4
|
+
# ignore this non title
|
5
|
+
```
|
6
|
+
ignore this multi-line code block
|
7
|
+
```
|
8
|
+
```ignore this inline block```
|
9
|
+
<!-- ignore this comment -->
|
10
|
+
## 2
|
11
|
+
### 3
|
12
|
+
|
13
|
+
## 2
|
14
|
+
text
|
15
|
+
|
16
|
+
#### 4
|
17
|
+
text
|
18
|
+
## 2
|
data/test/samples/a/c.md
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
## f 2
|
data/test/samples/a/e.md
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
# e 1
|
@@ -0,0 +1 @@
|
|
1
|
+
# README 1 for g
|
@@ -0,0 +1 @@
|
|
1
|
+
# h 1
|
@@ -0,0 +1,127 @@
|
|
1
|
+
# typed: false
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "minitest/autorun"
|
5
|
+
require "mdtoc/markdown"
|
6
|
+
|
7
|
+
class TestHeader < Minitest::Test
|
8
|
+
def test_fragment_normalization
|
9
|
+
sample = [
|
10
|
+
'Spaces 1 space',
|
11
|
+
'Spaces 2 spaces',
|
12
|
+
"Spaces\t1 tab",
|
13
|
+
"Spaces\t\t2 tabs",
|
14
|
+
' Spaces leading and trailing ',
|
15
|
+
'Numbers 1234567890',
|
16
|
+
'Symbols -=~!@#$%^&',
|
17
|
+
'Symbols *()_+',
|
18
|
+
'Symbols <>?:"{}|[]\;\',./',
|
19
|
+
]
|
20
|
+
expecteds = [
|
21
|
+
'/a#spaces-1-space',
|
22
|
+
'/a#spaces--2-spaces',
|
23
|
+
'/a#spaces1-tab',
|
24
|
+
'/a#spaces2-tabs',
|
25
|
+
'/a#spaces-leading-and-trailing',
|
26
|
+
'/a#numbers-1234567890',
|
27
|
+
'/a#symbols--',
|
28
|
+
'/a#symbols-_',
|
29
|
+
'/a#symbols-',
|
30
|
+
]
|
31
|
+
actuals = sample.map do |label|
|
32
|
+
Mdtoc::Markdown::HeaderWithFragment.new(1, label, '/a').instance_variable_get(:@url)
|
33
|
+
end
|
34
|
+
|
35
|
+
expecteds.zip(actuals).each { |expected, actual| assert_equal(expected, actual) }
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_invalid_depth
|
39
|
+
assert_raises(ArgumentError) do
|
40
|
+
Mdtoc::Markdown::Header.new(-1, 'a', '/a')
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_label_normalization
|
45
|
+
sample = [
|
46
|
+
' strip ',
|
47
|
+
"squeeze internal \t spaces",
|
48
|
+
'Don\'t change "#1?|!@#$%^&*()+ 2--',
|
49
|
+
]
|
50
|
+
expecteds = [
|
51
|
+
'strip',
|
52
|
+
"squeeze internal spaces",
|
53
|
+
'Don\'t change "#1?|!@#$%^&*()+ 2--',
|
54
|
+
]
|
55
|
+
actuals = sample.map { |label| Mdtoc::Markdown::Header.new(0, label, '/a').instance_variable_get(:@label) }
|
56
|
+
|
57
|
+
expecteds.zip(actuals).each { |expected, actual| assert_equal(expected, actual) }
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_to_s_prefix
|
61
|
+
str = Mdtoc::Markdown::Header.new(3, 'a', '/a').to_s
|
62
|
+
|
63
|
+
assert_equal(' * [a](/a)', str)
|
64
|
+
end
|
65
|
+
|
66
|
+
def test_to_s_with_fragment
|
67
|
+
str = Mdtoc::Markdown::HeaderWithFragment.new(0, 'a', '/a').to_s
|
68
|
+
|
69
|
+
assert_equal('* [a](/a#a)', str)
|
70
|
+
end
|
71
|
+
|
72
|
+
def test_to_s_without_fragment
|
73
|
+
str = Mdtoc::Markdown::Header.new(0, 'a', '/a').to_s
|
74
|
+
|
75
|
+
assert_equal('* [a](/a)', str)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
class TestParser < Minitest::Test
|
80
|
+
def test_skips_multiline_code_blocks
|
81
|
+
parser = Mdtoc::Markdown::Parser.new(0, '/')
|
82
|
+
sample = <<~END
|
83
|
+
# title
|
84
|
+
```
|
85
|
+
code
|
86
|
+
# code
|
87
|
+
```
|
88
|
+
END
|
89
|
+
|
90
|
+
headers = parser.headers(sample.each_line)
|
91
|
+
|
92
|
+
assert_equal(1, headers.size)
|
93
|
+
assert_equal('title', headers[0].instance_variable_get(:@label))
|
94
|
+
end
|
95
|
+
|
96
|
+
def test_skips_inline_code_blocks
|
97
|
+
parser = Mdtoc::Markdown::Parser.new(0, '/')
|
98
|
+
sample = <<~END
|
99
|
+
```code #```
|
100
|
+
# Title
|
101
|
+
```# code```
|
102
|
+
```code```#
|
103
|
+
END
|
104
|
+
|
105
|
+
headers = parser.headers(sample.each_line)
|
106
|
+
|
107
|
+
assert_equal(1, headers.size)
|
108
|
+
assert_equal(headers[0].instance_variable_get(:@label), 'Title')
|
109
|
+
end
|
110
|
+
|
111
|
+
def test_depth
|
112
|
+
parser = Mdtoc::Markdown::Parser.new(10, '/')
|
113
|
+
sample = <<~END
|
114
|
+
# 1
|
115
|
+
## 2
|
116
|
+
### 3
|
117
|
+
#### 4
|
118
|
+
END
|
119
|
+
|
120
|
+
headers = parser.headers(sample.each_line)
|
121
|
+
|
122
|
+
assert_equal(4, headers.size)
|
123
|
+
(0..3).each do |i|
|
124
|
+
assert_equal(10 + i, headers[i].instance_variable_get(:@depth))
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
data/test/test_node.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
# typed: false
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "minitest/autorun"
|
5
|
+
require "mdtoc/node"
|
6
|
+
|
7
|
+
class TestNode < Minitest::Test
|
8
|
+
SAMPLE_DIR = File.join(File.dirname(__FILE__), 'samples')
|
9
|
+
|
10
|
+
def test_dir
|
11
|
+
expected = <<~END
|
12
|
+
* [readme 1](test/samples/a/readme.md#readme-1)
|
13
|
+
* [readme 2](test/samples/a/readme.md#readme-2)
|
14
|
+
* [readme 3](test/samples/a/readme.md#readme-3)
|
15
|
+
* [readme 4](test/samples/a/readme.md#readme-4)
|
16
|
+
* [c 1](test/samples/a/c.md#c-1)
|
17
|
+
* [c 2](test/samples/a/c.md#c-2)
|
18
|
+
* [F](test/samples/a/d/f.md)
|
19
|
+
* [f 2](test/samples/a/d/f.md#f-2)
|
20
|
+
* [e 1](test/samples/a/e.md#e-1)
|
21
|
+
* [README 1 for g](test/samples/a/g/README.md#readme-1-for-g)
|
22
|
+
* [h 1](test/samples/a/g/h.md#h-1)
|
23
|
+
END
|
24
|
+
node = Mdtoc::Node.for_path(sample_path('a'))
|
25
|
+
actual = node.headers.join("\n") + "\n"
|
26
|
+
|
27
|
+
assert_equal(expected, actual)
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_file
|
31
|
+
expected = <<~END
|
32
|
+
* [Title](test/samples/README.md#title)
|
33
|
+
* [2](test/samples/README.md#2)
|
34
|
+
* [3](test/samples/README.md#3)
|
35
|
+
* [2](test/samples/README.md#2)
|
36
|
+
* [4](test/samples/README.md#4)
|
37
|
+
* [2](test/samples/README.md#2)
|
38
|
+
END
|
39
|
+
node = Mdtoc::Node.for_path(sample_path('README.md'))
|
40
|
+
actual = node.headers.join("\n") + "\n"
|
41
|
+
|
42
|
+
assert_equal(expected, actual)
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def sample_path(path)
|
48
|
+
File.join('test/samples', path)
|
49
|
+
end
|
50
|
+
end
|
metadata
ADDED
@@ -0,0 +1,171 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mdtoc
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.3
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- andornaut
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-11-02 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: minitest
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '5'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '5'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: sorbet
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rubocop
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0.86'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0.86'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rubocop-shopify
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 1.0.4
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 1.0.4
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rubocop-sorbet
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0.5'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0.5'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: unparser
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 0.4.9
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 0.4.9
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: sorbet-runtime
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :runtime
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
description:
|
126
|
+
email:
|
127
|
+
executables:
|
128
|
+
- mdtoc
|
129
|
+
extensions: []
|
130
|
+
extra_rdoc_files: []
|
131
|
+
files:
|
132
|
+
- bin/mdtoc
|
133
|
+
- lib/mdtoc.rb
|
134
|
+
- lib/mdtoc/cli.rb
|
135
|
+
- lib/mdtoc/markdown.rb
|
136
|
+
- lib/mdtoc/node.rb
|
137
|
+
- lib/mdtoc/version.rb
|
138
|
+
- lib/mdtoc/writer.rb
|
139
|
+
- test/samples/README.md
|
140
|
+
- test/samples/a/c.md
|
141
|
+
- test/samples/a/d/f.md
|
142
|
+
- test/samples/a/e.md
|
143
|
+
- test/samples/a/g/README.md
|
144
|
+
- test/samples/a/g/h.md
|
145
|
+
- test/samples/a/readme.md
|
146
|
+
- test/test_markdown.rb
|
147
|
+
- test/test_node.rb
|
148
|
+
homepage: https://github.com/andornaut/mdtoc
|
149
|
+
licenses:
|
150
|
+
- MIT
|
151
|
+
metadata: {}
|
152
|
+
post_install_message:
|
153
|
+
rdoc_options: []
|
154
|
+
require_paths:
|
155
|
+
- lib
|
156
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
157
|
+
requirements:
|
158
|
+
- - ">="
|
159
|
+
- !ruby/object:Gem::Version
|
160
|
+
version: 2.7.2
|
161
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
162
|
+
requirements:
|
163
|
+
- - ">="
|
164
|
+
- !ruby/object:Gem::Version
|
165
|
+
version: '0'
|
166
|
+
requirements: []
|
167
|
+
rubygems_version: 3.1.4
|
168
|
+
signing_key:
|
169
|
+
specification_version: 4
|
170
|
+
summary: Read Markdown files and output a table of contents
|
171
|
+
test_files: []
|