danger-toc 0.1.3 → 0.3.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/CHANGELOG.md +14 -0
- data/LICENSE.txt +1 -1
- data/README.md +103 -24
- data/lib/toc/config.rb +59 -24
- data/lib/toc/constructors/kramdown_constructor.rb +5 -6
- data/lib/toc/constructors.rb +0 -4
- data/lib/toc/extractor.rb +2 -1
- data/lib/toc/gem_version.rb +1 -1
- data/lib/toc/markdown_file.rb +62 -22
- data/lib/toc/plugin.rb +59 -37
- metadata +18 -163
- data/.gitignore +0 -11
- data/.rspec +0 -2
- data/.rubocop.yml +0 -14
- data/.rubocop_todo.yml +0 -86
- data/.travis.yml +0 -14
- data/CONTRIBUTING.md +0 -125
- data/Dangerfile +0 -4
- data/Gemfile +0 -5
- data/Guardfile +0 -19
- data/RELEASING.md +0 -67
- data/Rakefile +0 -23
- data/danger-toc.gemspec +0 -33
- data/images/toc-missing.png +0 -0
- data/spec/config_spec.rb +0 -66
- data/spec/constructors_spec.rb +0 -23
- data/spec/fixtures/markdown_file/one_section.md +0 -5
- data/spec/fixtures/markdown_file/one_section_with_custom_toc.md +0 -11
- data/spec/fixtures/markdown_file/one_section_with_invalid_toc.md +0 -11
- data/spec/fixtures/markdown_file/one_section_with_toc.md +0 -11
- data/spec/fixtures/markdown_file/one_section_with_toc_level.md +0 -11
- data/spec/fixtures/markdown_file/one_section_with_underscore_in_name.md +0 -11
- data/spec/fixtures/markdown_file/with_quoted_example.md +0 -26
- data/spec/markdown_file/danger_toc_readme_spec.rb +0 -19
- data/spec/markdown_file/one_section_spec.rb +0 -28
- data/spec/markdown_file/one_section_with_toc_level_spec.rb +0 -28
- data/spec/markdown_file/one_section_with_toc_spec.rb +0 -28
- data/spec/markdown_file/with_quoted_example_spec.rb +0 -33
- data/spec/markdown_file/with_underscore_in_section_name_spec.rb +0 -32
- data/spec/spec_helper.rb +0 -38
- data/spec/toc_spec.rb +0 -136
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 873eadcebb5c7ad1e01a9789251aaaf974f3852e6259d85cb95c4799c6d151b5
|
|
4
|
+
data.tar.gz: 4a852a1f1e5b173d136c47e022bd8037d7bdb40f0d09158460f8c53a0b39c1f1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1f163608c603f0e539a9e26ed82580b0ecfd22b120fc055e9376811645527147d2d0f243535898d123737307b0cdc5e2423c09c9fc533cb07d392c1f7fb12595
|
|
7
|
+
data.tar.gz: 1b5b1e067a7491ba736f62b4cada1226cb814bcbbe14c43774f6733e2a3c8c4d199b88125fdbfda4de1ee07aa6e4460439677411b53e5ce7e159774681534d51
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
## Changelog
|
|
2
2
|
|
|
3
|
+
### 0.3.0 (2026-10-20)
|
|
4
|
+
|
|
5
|
+
* [#21](https://github.com/dblock/danger-toc/pull/21): Support every global TOC setting as a per-check keyword override without changing defaults; snapshot configuration and report every failing file - [@dblock](https://github.com/dblock).
|
|
6
|
+
* [#20](https://github.com/dblock/danger-toc/pull/20): Configure bullets, indentation, maximum heading depth, and input parsers; exclude document titles from missing TOCs and support opt-in diff reports - [@dblock](https://github.com/dblock).
|
|
7
|
+
* [#19](https://github.com/dblock/danger-toc/pull/19): Require Ruby 3.1, test through Ruby 4.0, declare the GFM parser dependency, and update pinned Danger workflows and contributor and release instructions - [@dblock](https://github.com/dblock).
|
|
8
|
+
* [#18](https://github.com/dblock/danger-toc/pull/18): Migrate to danger-pr-comment workflow - [@dblock](https://github.com/dblock).
|
|
9
|
+
* [#18](https://github.com/dblock/danger-toc/pull/18): Migrate from Travis CI to GitHub Actions - [@dblock](https://github.com/dblock).
|
|
10
|
+
* [#18](https://github.com/dblock/danger-toc/pull/18): Upgrade rubocop to 1.82.0 - [@dblock](https://github.com/dblock).
|
|
11
|
+
|
|
12
|
+
### 0.2.0 (2019/07/28)
|
|
13
|
+
|
|
14
|
+
* [#16](https://github.com/dblock/danger-toc/pull/16): Deprecate `check` in favor of `check!` - [@dblock](https://github.com/dblock).
|
|
15
|
+
* [#16](https://github.com/dblock/danger-toc/pull/16): Deprecate `configure`, configure directly on `toc` - [@dblock](https://github.com/dblock).
|
|
16
|
+
|
|
3
17
|
### 0.1.3 (2018/08/15)
|
|
4
18
|
|
|
5
19
|
* [#7](https://github.com/dblock/danger-toc/issues/7): Parse documents as Github Flavored Markdown by default - [@dblock](https://github.com/dblock).
|
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
|
@@ -1,19 +1,25 @@
|
|
|
1
1
|
# danger-toc
|
|
2
2
|
|
|
3
|
-
A plugin for [danger.systems](
|
|
3
|
+
A plugin for [danger.systems](https://danger.systems) that makes sure your project's `README.md` has a TOC. Requires Ruby 3.1 or newer.
|
|
4
4
|
|
|
5
5
|
[](https://badge.fury.io/rb/danger-toc)
|
|
6
|
-
[](https://github.com/dblock/danger-toc/actions/workflows/ci.yml)
|
|
7
7
|
|
|
8
8
|
## Table of Contents
|
|
9
9
|
|
|
10
10
|
- [Installation](#installation)
|
|
11
11
|
- [Usage](#usage)
|
|
12
|
-
- [
|
|
13
|
-
- [
|
|
14
|
-
- [
|
|
15
|
-
- [
|
|
16
|
-
- [
|
|
12
|
+
- [toc.check!](#toccheck)
|
|
13
|
+
- [Options](#options)
|
|
14
|
+
- [files](#files)
|
|
15
|
+
- [header](#header)
|
|
16
|
+
- [parser](#parser)
|
|
17
|
+
- [format](#format)
|
|
18
|
+
- [bullet](#bullet)
|
|
19
|
+
- [indent](#indent)
|
|
20
|
+
- [max_depth](#max_depth)
|
|
21
|
+
- [report](#report)
|
|
22
|
+
- [Global Defaults](#global-defaults)
|
|
17
23
|
- [Contributing](#contributing)
|
|
18
24
|
- [Copyright](#copyright)
|
|
19
25
|
|
|
@@ -22,41 +28,114 @@ A plugin for [danger.systems](http://danger.systems) that makes sure your projec
|
|
|
22
28
|
Add `danger-toc` to your Gemfile.
|
|
23
29
|
|
|
24
30
|
```
|
|
25
|
-
gem 'danger-toc'
|
|
31
|
+
gem 'danger-toc'
|
|
26
32
|
```
|
|
27
33
|
|
|
28
|
-
Add `toc.check
|
|
34
|
+
Add `toc.check!` to your Dangerfile. Make a pull request and see this plugin in action.
|
|
29
35
|
|
|
30
36
|
<img src='images/toc-missing.png'>
|
|
31
37
|
|
|
32
38
|
## Usage
|
|
33
39
|
|
|
34
|
-
|
|
40
|
+
Methods from this plugin are available in your `Dangerfile` under the `toc` namespace. Every option can be set globally or passed to a check. Per-check options override global defaults without changing them.
|
|
35
41
|
|
|
36
|
-
|
|
42
|
+
### toc.check!
|
|
43
|
+
|
|
44
|
+
Check README.md with default options, or pass options for a particular set of files:
|
|
45
|
+
|
|
46
|
+
```ruby
|
|
47
|
+
toc.check!
|
|
48
|
+
toc.check!(files: ['README.md', 'DEV.md'], header: 'Contents')
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
The method returns whether all checked TOCs are correct and reports every failing file. Use `toc.toc_changes?(files: ['README.md'])` to detect modified or added files before running a check.
|
|
52
|
+
|
|
53
|
+
### Options
|
|
37
54
|
|
|
38
55
|
```ruby
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
config.header = 'Table of Contents'
|
|
42
|
-
end
|
|
56
|
+
toc.check!(files: ['README.md'], max_depth: 3)
|
|
57
|
+
toc.check!(files: ['docs/guide.md'], parser: 'Kramdown', format: :kramdown, bullet: '*', indent: 4)
|
|
43
58
|
```
|
|
44
59
|
|
|
45
|
-
|
|
60
|
+
#### files
|
|
46
61
|
|
|
47
|
-
|
|
62
|
+
File paths to check. Default: `['README.md']`.
|
|
48
63
|
|
|
49
|
-
|
|
64
|
+
```ruby
|
|
65
|
+
toc.check!(files: ['README.md', 'DEV.md'])
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
#### header
|
|
50
69
|
|
|
51
|
-
|
|
70
|
+
TOC heading text, without `#`. Default: `'Table of Contents'`.
|
|
71
|
+
|
|
72
|
+
```ruby
|
|
73
|
+
toc.check!(header: 'Contents')
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
When a TOC is missing, a leading `# Title` is excluded if every subsequent heading is nested below it. A lone heading or multiple top-level sections are retained. When a TOC exists, headings before it are excluded.
|
|
77
|
+
|
|
78
|
+
#### parser
|
|
79
|
+
|
|
80
|
+
Kramdown input parser name. Default: `'GFM'`. GFM and Kramdown input are available out of the box; install and require additional Kramdown parsers before selecting them.
|
|
81
|
+
|
|
82
|
+
```ruby
|
|
83
|
+
toc.check!(parser: 'Kramdown')
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
#### format
|
|
87
|
+
|
|
88
|
+
Anchor ID format: `:github` (default) or `:kramdown`. This is independent of the input parser.
|
|
89
|
+
|
|
90
|
+
```ruby
|
|
91
|
+
toc.check!(parser: 'Kramdown', format: :kramdown)
|
|
92
|
+
```
|
|
52
93
|
|
|
53
|
-
####
|
|
94
|
+
#### bullet
|
|
54
95
|
|
|
55
|
-
|
|
96
|
+
List marker: `'-'` (default), `'*'`, or `'+'`.
|
|
56
97
|
|
|
57
|
-
|
|
98
|
+
```ruby
|
|
99
|
+
toc.check!(bullet: '*')
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
#### indent
|
|
103
|
+
|
|
104
|
+
Non-negative number of spaces per nesting level. Default: `2`.
|
|
105
|
+
|
|
106
|
+
```ruby
|
|
107
|
+
toc.check!(indent: 4)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
#### max_depth
|
|
111
|
+
|
|
112
|
+
Maximum absolute Markdown heading level (1–6). Default: `nil`, including all levels. For example, `2` includes `#` and `##` headings but not `###`.
|
|
113
|
+
|
|
114
|
+
```ruby
|
|
115
|
+
toc.check!(max_depth: 2)
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
#### report
|
|
119
|
+
|
|
120
|
+
Report style: `:full` (default) shows the complete expected TOC. Use `:diff` to show mismatched entries with two surrounding entries of context. Removed lines are prefixed with `-`, additions with `+`. Missing TOCs always show the full suggestion.
|
|
121
|
+
|
|
122
|
+
```ruby
|
|
123
|
+
toc.check!(report: :diff)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Global Defaults
|
|
127
|
+
|
|
128
|
+
Set shared defaults on `toc`, then override individual settings for a particular check:
|
|
129
|
+
|
|
130
|
+
```ruby
|
|
131
|
+
toc.files = ['README.md', 'DEV.md']
|
|
132
|
+
toc.header = 'Contents'
|
|
133
|
+
toc.check!
|
|
134
|
+
toc.check!(files: ['docs/guide.md'], header: 'Guide Contents', parser: 'Kramdown')
|
|
135
|
+
toc.check! # still checks README.md and DEV.md using the Contents header
|
|
136
|
+
```
|
|
58
137
|
|
|
59
|
-
|
|
138
|
+
The same global/per-check choice applies to every option in the table, including `report`, `format`, and `max_depth`. Global settings are shared across plugin instances; each check captures its own immutable configuration snapshot.
|
|
60
139
|
|
|
61
140
|
## Contributing
|
|
62
141
|
|
|
@@ -64,6 +143,6 @@ See [CONTRIBUTING](CONTRIBUTING.md).
|
|
|
64
143
|
|
|
65
144
|
## Copyright
|
|
66
145
|
|
|
67
|
-
Copyright (c) Daniel Doubrovkine, 2017
|
|
146
|
+
Copyright (c) Daniel Doubrovkine, 2017-2019
|
|
68
147
|
|
|
69
148
|
MIT License, see [LICENSE](LICENSE.txt) for details.
|
data/lib/toc/config.rb
CHANGED
|
@@ -1,46 +1,81 @@
|
|
|
1
1
|
module Danger
|
|
2
2
|
module Toc
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
class Config
|
|
4
|
+
DEFAULTS = {
|
|
5
|
+
files: ['README.md'].freeze,
|
|
6
|
+
header: 'Table of Contents',
|
|
7
|
+
format: :github,
|
|
8
|
+
parser: 'GFM',
|
|
9
|
+
bullet: '-',
|
|
10
|
+
indent: 2,
|
|
11
|
+
max_depth: nil,
|
|
12
|
+
report: :full
|
|
13
|
+
}.freeze
|
|
5
14
|
|
|
6
|
-
|
|
7
|
-
|
|
15
|
+
ATTRIBUTES = DEFAULTS.keys.freeze
|
|
16
|
+
DELEGATORS = (ATTRIBUTES + ATTRIBUTES.map { |name| :"#{name}=" }).freeze
|
|
8
17
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
18
|
+
VALIDATORS = {
|
|
19
|
+
files: ->(value) { value.is_a?(Array) && value.all? { |file| file.is_a?(String) && !file.strip.empty? } },
|
|
20
|
+
header: ->(value) { value.is_a?(String) && !value.strip.empty? },
|
|
21
|
+
parser: ->(value) { value.is_a?(String) && !value.strip.empty? },
|
|
22
|
+
format: ->(value) { %i[github kramdown].include?(value) },
|
|
23
|
+
bullet: ->(value) { %w[- * +].include?(value) },
|
|
24
|
+
indent: ->(value) { value.is_a?(Integer) && value >= 0 },
|
|
25
|
+
max_depth: ->(value) { value.nil? || (value.is_a?(Integer) && (1..6).cover?(value)) },
|
|
26
|
+
report: ->(value) { %i[full diff].include?(value) }
|
|
27
|
+
}.freeze
|
|
28
|
+
|
|
29
|
+
attr_reader(*ATTRIBUTES)
|
|
30
|
+
|
|
31
|
+
ATTRIBUTES.each do |name|
|
|
32
|
+
define_method("#{name}=") do |value|
|
|
33
|
+
validate_option!(name, value)
|
|
34
|
+
instance_variable_set("@#{name}", snapshot(value))
|
|
35
|
+
end
|
|
12
36
|
end
|
|
13
37
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
38
|
+
def initialize(**options)
|
|
39
|
+
unknown = options.keys - ATTRIBUTES
|
|
40
|
+
raise ArgumentError, "Unknown TOC options: #{unknown.join(', ')}" unless unknown.empty?
|
|
41
|
+
|
|
42
|
+
DEFAULTS.merge(options).each { |name, value| public_send("#{name}=", value) }
|
|
17
43
|
end
|
|
18
44
|
|
|
19
|
-
def
|
|
20
|
-
|
|
45
|
+
def with(**options)
|
|
46
|
+
self.class.new(**to_h, **options).freeze
|
|
21
47
|
end
|
|
22
48
|
|
|
23
|
-
def
|
|
24
|
-
|
|
49
|
+
def to_h
|
|
50
|
+
ATTRIBUTES.to_h { |name| [name, public_send(name)] }
|
|
25
51
|
end
|
|
26
52
|
|
|
27
53
|
def reset
|
|
28
|
-
|
|
29
|
-
self.header = 'Table of Contents'
|
|
30
|
-
self.format = default_format
|
|
54
|
+
DEFAULTS.each { |name, value| public_send("#{name}=", value) }
|
|
31
55
|
end
|
|
32
56
|
|
|
33
|
-
|
|
34
|
-
end
|
|
57
|
+
private
|
|
35
58
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
block_given? ? yield(Config) : Config
|
|
59
|
+
def validate_option!(name, value)
|
|
60
|
+
raise ArgumentError, "Invalid TOC #{name}: #{value.inspect}" unless VALIDATORS.fetch(name).call(value)
|
|
39
61
|
end
|
|
40
62
|
|
|
41
|
-
def
|
|
42
|
-
|
|
63
|
+
def snapshot(value)
|
|
64
|
+
case value
|
|
65
|
+
when Array then value.map { |item| snapshot(item) }.freeze
|
|
66
|
+
when String then value.dup.freeze
|
|
67
|
+
else value
|
|
68
|
+
end
|
|
43
69
|
end
|
|
44
70
|
end
|
|
71
|
+
|
|
72
|
+
def self.config
|
|
73
|
+
@config ||= Config.new
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def self.configure
|
|
77
|
+
warn '[DEPRECATION] `configure` is deprecated. Please directly configure the Danger plugin via `toc.xyz=` instead.'
|
|
78
|
+
block_given? ? yield(config) : config
|
|
79
|
+
end
|
|
45
80
|
end
|
|
46
81
|
end
|
|
@@ -6,6 +6,7 @@ module Danger
|
|
|
6
6
|
class KramdownConstructor < Kramdown::Converter::Toc
|
|
7
7
|
def flatten(el)
|
|
8
8
|
return [] unless el.type == :toc
|
|
9
|
+
|
|
9
10
|
result = []
|
|
10
11
|
if el.value
|
|
11
12
|
result << {
|
|
@@ -14,20 +15,18 @@ module Danger
|
|
|
14
15
|
depth: el.value.options[:level]
|
|
15
16
|
}
|
|
16
17
|
end
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
result.concat(flatten(child))
|
|
20
|
-
end
|
|
18
|
+
el.children&.each do |child|
|
|
19
|
+
result.concat(flatten(child))
|
|
21
20
|
end
|
|
22
21
|
result
|
|
23
22
|
end
|
|
24
23
|
|
|
25
24
|
def convert(el)
|
|
26
|
-
toc = flatten(super
|
|
25
|
+
toc = flatten(super)
|
|
27
26
|
has_toc = false
|
|
28
27
|
headers = []
|
|
29
28
|
toc.each do |line|
|
|
30
|
-
if !has_toc && line[:text] ==
|
|
29
|
+
if !has_toc && line[:text] == (@options[:toc_header] || 'Table of Contents')
|
|
31
30
|
headers = [] # drop any headers prior to TOC
|
|
32
31
|
has_toc = true
|
|
33
32
|
else
|
data/lib/toc/constructors.rb
CHANGED
data/lib/toc/extractor.rb
CHANGED
|
@@ -8,10 +8,11 @@ module Danger
|
|
|
8
8
|
@toc_start = nil
|
|
9
9
|
@toc_end = nil
|
|
10
10
|
@in_toc = false
|
|
11
|
+
@toc_header = options.fetch(:toc_header, 'Table of Contents')
|
|
11
12
|
end
|
|
12
13
|
|
|
13
14
|
def convert(el)
|
|
14
|
-
if el.type == :header && el.options[:raw_text] ==
|
|
15
|
+
if el.type == :header && el.options[:raw_text] == @toc_header
|
|
15
16
|
@in_toc = true
|
|
16
17
|
@toc_start = el.options[:location]
|
|
17
18
|
elsif el.type == :header
|
data/lib/toc/gem_version.rb
CHANGED
data/lib/toc/markdown_file.rb
CHANGED
|
@@ -1,24 +1,25 @@
|
|
|
1
1
|
require 'active_support/core_ext/string/inflections'
|
|
2
|
+
require 'kramdown-parser-gfm'
|
|
3
|
+
require 'diff/lcs'
|
|
2
4
|
|
|
3
5
|
require_relative 'extractor'
|
|
4
6
|
require_relative 'constructors'
|
|
7
|
+
require_relative 'config'
|
|
5
8
|
|
|
6
9
|
module Danger
|
|
7
10
|
module Toc
|
|
8
11
|
class MarkdownFile
|
|
9
|
-
attr_reader :filename
|
|
10
|
-
attr_reader :exists
|
|
11
|
-
attr_reader :toc
|
|
12
|
-
attr_reader :headers
|
|
12
|
+
attr_reader :filename, :exists, :toc, :headers, :config
|
|
13
13
|
|
|
14
|
-
def initialize(filename = 'README.md')
|
|
14
|
+
def initialize(filename = 'README.md', config: Danger::Toc.config.with)
|
|
15
|
+
@config = config
|
|
15
16
|
@filename = filename
|
|
16
17
|
@exists = File.exist?(filename)
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
return unless @exists
|
|
19
|
+
|
|
20
|
+
parse!
|
|
21
|
+
reduce!
|
|
22
|
+
validate!
|
|
22
23
|
end
|
|
23
24
|
|
|
24
25
|
def exists?
|
|
@@ -40,27 +41,66 @@ module Danger
|
|
|
40
41
|
def toc_from_headers
|
|
41
42
|
headers.map do |header|
|
|
42
43
|
[
|
|
43
|
-
' ' * header[:depth] *
|
|
44
|
-
"
|
|
44
|
+
' ' * header[:depth] * config.indent,
|
|
45
|
+
"#{config.bullet} [#{header[:text]}]",
|
|
45
46
|
"(##{header[:id]})"
|
|
46
47
|
].compact.join
|
|
47
48
|
end
|
|
48
49
|
end
|
|
49
50
|
|
|
51
|
+
def toc_diff
|
|
52
|
+
changes = Diff::LCS.sdiff(toc || [], toc_from_headers)
|
|
53
|
+
lines = []
|
|
54
|
+
previous = nil
|
|
55
|
+
diff_context(changes).each do |index|
|
|
56
|
+
lines << '...' if previous && index > previous + 1
|
|
57
|
+
lines.concat(diff_lines(changes[index]))
|
|
58
|
+
previous = index
|
|
59
|
+
end
|
|
60
|
+
lines.join("\n")
|
|
61
|
+
end
|
|
62
|
+
|
|
50
63
|
private
|
|
51
64
|
|
|
65
|
+
def diff_context(changes)
|
|
66
|
+
changes.each_index.reject { |index| changes[index].action == '=' }.flat_map do |index|
|
|
67
|
+
(([index - 2, 0].max)..([index + 2, changes.length - 1].min)).to_a
|
|
68
|
+
end.uniq.sort
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def diff_lines(change)
|
|
72
|
+
case change.action
|
|
73
|
+
when '!' then ["-#{change.old_element}", "+#{change.new_element}"]
|
|
74
|
+
when '-' then ["-#{change.old_element}"]
|
|
75
|
+
when '+' then ["+#{change.new_element}"]
|
|
76
|
+
else [" #{change.old_element}"]
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
52
80
|
# Parse markdown file for TOC.
|
|
53
81
|
def parse!
|
|
54
82
|
md = File.read(filename)
|
|
55
|
-
doc = Kramdown::Document.new(md, input:
|
|
83
|
+
doc = Kramdown::Document.new(md, input: config.parser)
|
|
84
|
+
|
|
85
|
+
extract_toc!(md, doc)
|
|
86
|
+
construct_headers!(doc)
|
|
87
|
+
end
|
|
56
88
|
|
|
57
|
-
|
|
58
|
-
toc_start, toc_end = Danger::Toc::Extractor.convert(doc.root).first
|
|
89
|
+
def extract_toc!(markdown, doc)
|
|
90
|
+
toc_start, toc_end = Danger::Toc::Extractor.convert(doc.root, toc_header: config.header).first
|
|
91
|
+
toc_end ||= markdown.lines.length + 1 if toc_start
|
|
59
92
|
@has_toc = toc_start && toc_end
|
|
60
|
-
@toc =
|
|
93
|
+
@toc = markdown.split("\n")[toc_start, toc_end - toc_start - 1].reject(&:empty?) if @has_toc
|
|
94
|
+
end
|
|
61
95
|
|
|
62
|
-
|
|
63
|
-
@headers = Danger::Toc::Constructors.
|
|
96
|
+
def construct_headers!(doc)
|
|
97
|
+
@headers = Danger::Toc::Constructors.get(config.format).convert(doc.root, toc_header: config.header).first
|
|
98
|
+
headers.shift if !@has_toc && document_title?
|
|
99
|
+
headers.select! { |header| header[:depth] <= config.max_depth } if config.max_depth
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def document_title?
|
|
103
|
+
headers.length > 1 && headers.first[:depth] == 1 && headers.drop(1).all? { |header| header[:depth] > 1 }
|
|
64
104
|
end
|
|
65
105
|
|
|
66
106
|
def reduce!
|
|
@@ -68,10 +108,10 @@ module Danger
|
|
|
68
108
|
headers.each do |header|
|
|
69
109
|
min_depth = header[:depth] unless min_depth && min_depth < header[:depth]
|
|
70
110
|
end
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
111
|
+
return unless min_depth
|
|
112
|
+
|
|
113
|
+
headers.each do |header|
|
|
114
|
+
header[:depth] -= min_depth
|
|
75
115
|
end
|
|
76
116
|
end
|
|
77
117
|
|
data/lib/toc/plugin.rb
CHANGED
|
@@ -3,65 +3,87 @@ module Danger
|
|
|
3
3
|
#
|
|
4
4
|
# @example Run all checks on the default README.md.
|
|
5
5
|
#
|
|
6
|
-
# toc.check
|
|
6
|
+
# toc.check!
|
|
7
7
|
#
|
|
8
|
-
# @example Customize
|
|
8
|
+
# @example Customize files and remind the requester to update TOCs when necessary.
|
|
9
9
|
#
|
|
10
|
-
# toc.
|
|
11
|
-
# toc.is_toc_correct?
|
|
10
|
+
# toc.check!(files: ['README.md'], max_depth: 3)
|
|
12
11
|
#
|
|
13
12
|
# @see dblock/danger-toc
|
|
14
13
|
# @tags toc
|
|
15
14
|
|
|
16
15
|
class DangerToc < Plugin
|
|
17
|
-
|
|
18
|
-
# @return [Array]
|
|
19
|
-
attr_accessor :filenames
|
|
16
|
+
extend Forwardable
|
|
20
17
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
18
|
+
def_delegators 'Danger::Toc.config', *Danger::Toc::Config::DELEGATORS
|
|
19
|
+
|
|
20
|
+
# Run all checks.
|
|
21
|
+
# @param options [Hash] Per-check files, header, parser, format, bullet, indent, max_depth, and report settings.
|
|
22
|
+
# @return [boolean] Whether all files have correct TOCs.
|
|
23
|
+
def check!(**options)
|
|
24
|
+
config = Danger::Toc.config.with(**options)
|
|
25
|
+
config.files.map { |filename| check_file(filename, config) }.all?
|
|
24
26
|
end
|
|
25
27
|
|
|
26
28
|
# Run all checks.
|
|
27
|
-
# @return [
|
|
28
|
-
def check
|
|
29
|
-
|
|
29
|
+
# @return [boolean]
|
|
30
|
+
def check(**options)
|
|
31
|
+
warn '[DEPRECATION] `check` is deprecated. Please use `check!` instead.'
|
|
32
|
+
check!(**options)
|
|
30
33
|
end
|
|
31
34
|
|
|
32
35
|
# Has the README file been modified?
|
|
33
36
|
# @return [boolean]
|
|
34
|
-
def toc_changes?
|
|
35
|
-
|
|
37
|
+
def toc_changes?(**options)
|
|
38
|
+
files = Danger::Toc.config.with(**options).files
|
|
39
|
+
git.modified_files.intersect?(files) || git.added_files.intersect?(files)
|
|
36
40
|
end
|
|
37
41
|
|
|
38
42
|
# Is the TOC format correct?
|
|
39
43
|
# @return [boolean]
|
|
40
|
-
def is_toc_correct?
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
if !toc_file.exists?
|
|
44
|
-
messaging.fail("The #{filename} file does not exist.", sticky: false)
|
|
45
|
-
false
|
|
46
|
-
elsif toc_file.good?
|
|
47
|
-
true
|
|
48
|
-
else
|
|
49
|
-
markdown <<-MARKDOWN
|
|
50
|
-
Here's the expected TOC for #{filename}:
|
|
44
|
+
def is_toc_correct?(**options)
|
|
45
|
+
check!(**options)
|
|
46
|
+
end
|
|
51
47
|
|
|
52
|
-
|
|
53
|
-
# #{Danger::Toc.config.header}
|
|
48
|
+
private
|
|
54
49
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
50
|
+
def check_file(filename, config)
|
|
51
|
+
toc_file = Danger::Toc::MarkdownFile.new(filename, config: config)
|
|
52
|
+
if !toc_file.exists?
|
|
53
|
+
messaging.fail("The #{filename} file does not exist.", sticky: false)
|
|
54
|
+
false
|
|
55
|
+
elsif toc_file.good?
|
|
56
|
+
true
|
|
57
|
+
else
|
|
58
|
+
report_toc(toc_file)
|
|
59
|
+
if toc_file.has_toc?
|
|
60
|
+
messaging.fail("The TOC found in #{filename} doesn't match the sections of the file.", sticky: false)
|
|
61
|
+
else
|
|
62
|
+
messaging.fail("The #{filename} file is missing a TOC.", sticky: false)
|
|
64
63
|
end
|
|
64
|
+
false
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def report_toc(toc_file)
|
|
69
|
+
if toc_file.config.report == :diff && toc_file.has_toc?
|
|
70
|
+
markdown <<~MARKDOWN
|
|
71
|
+
Here's the TOC diff for #{toc_file.filename} (- current, + expected):
|
|
72
|
+
|
|
73
|
+
```diff
|
|
74
|
+
#{toc_file.toc_diff}
|
|
75
|
+
```
|
|
76
|
+
MARKDOWN
|
|
77
|
+
else
|
|
78
|
+
markdown <<~MARKDOWN
|
|
79
|
+
Here's the expected TOC for #{toc_file.filename}:
|
|
80
|
+
|
|
81
|
+
```markdown
|
|
82
|
+
# #{toc_file.config.header}
|
|
83
|
+
|
|
84
|
+
#{toc_file.toc_from_headers.join("\n")}
|
|
85
|
+
```
|
|
86
|
+
MARKDOWN
|
|
65
87
|
end
|
|
66
88
|
end
|
|
67
89
|
end
|