rspec-documentation 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rspec +1 -0
- data/.rubocop.yml +2 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +25 -2
- data/README.md +1 -1
- data/Rakefile +1 -0
- data/exe/rspec-documentation +5 -0
- data/lib/rspec/documentation/version.rb +2 -2
- data/lib/rspec/documentation.rb +65 -2
- data/lib/rspec_documentation/ansi_html.rb +28 -0
- data/lib/rspec_documentation/configuration.rb +15 -0
- data/lib/rspec_documentation/context.rb +44 -0
- data/lib/rspec_documentation/document.rb +61 -0
- data/lib/rspec_documentation/html_element.rb +55 -0
- data/lib/rspec_documentation/markdown_renderer.rb +7 -0
- data/lib/rspec_documentation/page_collection.rb +49 -0
- data/lib/rspec_documentation/page_tree.rb +73 -0
- data/lib/rspec_documentation/page_tree_element.rb +56 -0
- data/lib/rspec_documentation/parsed_document.rb +70 -0
- data/lib/rspec_documentation/rspec/example_group.rb +26 -0
- data/lib/rspec_documentation/rspec/failure.rb +37 -0
- data/lib/rspec_documentation/rspec/reporter.rb +45 -0
- data/lib/rspec_documentation/rspec.rb +14 -0
- data/lib/rspec_documentation/spec.rb +62 -0
- data/lib/rspec_documentation/util.rb +46 -0
- data/lib/rspec_documentation.rb +58 -0
- data/lib/tasks/rspec/documentation/generate.rake +10 -0
- data/lib/templates/footer.html.erb +1 -0
- data/lib/templates/header.html.erb +7 -0
- data/lib/templates/layout.css.erb +39 -0
- data/lib/templates/layout.html.erb +100 -0
- data/lib/templates/tabbed_spec.html.erb +119 -0
- data/lib/templates/themes/cerulean.css +12 -0
- data/lib/templates/themes/cosmo.css +12 -0
- data/lib/templates/themes/cyborg.css +12 -0
- data/lib/templates/themes/darkly.css +12 -0
- data/lib/templates/themes/flatly.css +12 -0
- data/lib/templates/themes/journal.css +12 -0
- data/lib/templates/themes/litera.css +12 -0
- data/lib/templates/themes/lumen.css +12 -0
- data/lib/templates/themes/lux.css +12 -0
- data/lib/templates/themes/materia.css +12 -0
- data/lib/templates/themes/minty.css +12 -0
- data/lib/templates/themes/morph.css +12 -0
- data/lib/templates/themes/pulse.css +12 -0
- data/lib/templates/themes/quartz.css +12 -0
- data/lib/templates/themes/sandstone.css +12 -0
- data/lib/templates/themes/simplex.css +12 -0
- data/lib/templates/themes/sketchy.css +12 -0
- data/lib/templates/themes/slate.css +12 -0
- data/lib/templates/themes/solar.css +12 -0
- data/lib/templates/themes/spacelab.css +12 -0
- data/lib/templates/themes/superhero.css +12 -0
- data/lib/templates/themes/united.css +12 -0
- data/lib/templates/themes/vapor.css +12 -0
- data/lib/templates/themes/yeti.css +12 -0
- data/lib/templates/themes/zephyr.css +12 -0
- data/rspec-documentation/pages/000-Introduction.md +39 -0
- data/rspec-documentation/pages/010-File System/000-Ordering.md +14 -0
- data/rspec-documentation/pages/010-File System/010-Standalone Directories.md +17 -0
- data/rspec-documentation/pages/010-File System/020-Standalone Directory/Example Page.md +3 -0
- data/rspec-documentation/pages/010-File System.md +26 -0
- data/rspec-documentation/pages/020-Running Specs.md +11 -0
- data/rspec-documentation.gemspec +9 -1
- data/sig/rspec/documentation.rbs +1 -1
- metadata +158 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1de5016a8e2e1a370ac923d9501c654c56e87e1f236f2f260ea634a8a28167b4
|
4
|
+
data.tar.gz: d75c74d43b80b502daa00778c3d4e8ede22514c2f500d67ee6dc31cb994772e3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1bc358ecfcda3670c648dac2f11698facb57c2dc147826a87a26b24af07a1517fda6cd43431be25facb996280e7de50848630f0f8aab9ead11743ac1ec76e77e
|
7
|
+
data.tar.gz: a622ad2f53e2c743431678ed97e9a36a037c86f5b4b15c67c3eff439158ff0af026878be1f55a1aa5d4ee45b1a672a57db66c28912de012471d5bcd7aeedb6b1
|
data/.rspec
CHANGED
data/.rubocop.yml
CHANGED
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,25 +1,41 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
rspec-documentation (0.0.
|
4
|
+
rspec-documentation (0.0.2)
|
5
|
+
htmlbeautifier (~> 1.4)
|
6
|
+
kramdown (~> 2.4)
|
7
|
+
kramdown-parser-gfm (~> 1.1)
|
8
|
+
paintbrush (~> 0.1.1)
|
9
|
+
redcarpet (~> 3.6)
|
10
|
+
rouge (~> 4.1)
|
11
|
+
rspec (~> 3.12)
|
5
12
|
|
6
13
|
GEM
|
7
14
|
remote: https://rubygems.org/
|
8
15
|
specs:
|
9
16
|
ast (2.4.2)
|
10
17
|
concurrent-ruby (1.2.2)
|
18
|
+
devpack (0.4.1)
|
11
19
|
diff-lcs (1.5.0)
|
20
|
+
htmlbeautifier (1.4.2)
|
12
21
|
i18n (1.13.0)
|
13
22
|
concurrent-ruby (~> 1.0)
|
14
23
|
json (2.6.3)
|
24
|
+
kramdown (2.4.0)
|
25
|
+
rexml
|
26
|
+
kramdown-parser-gfm (1.1.0)
|
27
|
+
kramdown (~> 2.0)
|
15
28
|
paint (2.3.0)
|
29
|
+
paintbrush (0.1.1)
|
16
30
|
parallel (1.23.0)
|
17
31
|
parser (3.2.2.1)
|
18
32
|
ast (~> 2.4.1)
|
19
33
|
rainbow (3.1.1)
|
20
34
|
rake (13.0.6)
|
35
|
+
redcarpet (3.6.0)
|
21
36
|
regexp_parser (2.8.0)
|
22
37
|
rexml (3.2.5)
|
38
|
+
rouge (4.1.1)
|
23
39
|
rspec (3.12.0)
|
24
40
|
rspec-core (~> 3.12.0)
|
25
41
|
rspec-expectations (~> 3.12.0)
|
@@ -29,6 +45,11 @@ GEM
|
|
29
45
|
rspec-expectations (3.12.3)
|
30
46
|
diff-lcs (>= 1.2.0, < 2.0)
|
31
47
|
rspec-support (~> 3.12.0)
|
48
|
+
rspec-file_fixtures (0.1.6)
|
49
|
+
rspec (~> 3.0)
|
50
|
+
rspec-its (1.3.0)
|
51
|
+
rspec-core (>= 3.0.0)
|
52
|
+
rspec-expectations (>= 3.0.0)
|
32
53
|
rspec-mocks (3.12.5)
|
33
54
|
diff-lcs (>= 1.2.0, < 2.0)
|
34
55
|
rspec-support (~> 3.12.0)
|
@@ -62,13 +83,15 @@ GEM
|
|
62
83
|
unicode-display_width (2.4.2)
|
63
84
|
|
64
85
|
PLATFORMS
|
65
|
-
ruby
|
66
86
|
x86_64-linux
|
67
87
|
|
68
88
|
DEPENDENCIES
|
89
|
+
devpack (~> 0.4.1)
|
69
90
|
rake (~> 13.0)
|
70
91
|
rspec (~> 3.0)
|
71
92
|
rspec-documentation!
|
93
|
+
rspec-file_fixtures (~> 0.1.6)
|
94
|
+
rspec-its (~> 1.3)
|
72
95
|
rubocop (~> 1.51)
|
73
96
|
rubocop-rake (~> 0.6.0)
|
74
97
|
rubocop-rspec (~> 2.22)
|
data/README.md
CHANGED
data/Rakefile
CHANGED
data/lib/rspec/documentation.rb
CHANGED
@@ -1,10 +1,73 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative 'documentation/version'
|
4
|
+
require_relative '../rspec_documentation'
|
5
|
+
load File.expand_path(File.join(__dir__, '../tasks/rspec/documentation/generate.rake'))
|
4
6
|
|
5
|
-
module
|
7
|
+
module RSpec
|
8
|
+
# Extension for RSpec, locates RSpec examples found in a Markdown file and executes them,
|
9
|
+
# replacing their output into a tree of Markdown files.
|
6
10
|
module Documentation
|
7
11
|
class Error < StandardError; end
|
8
|
-
|
12
|
+
|
13
|
+
class << self
|
14
|
+
include Paintbrush
|
15
|
+
|
16
|
+
def generate_documentation
|
17
|
+
require_spec_helper
|
18
|
+
page_collection.generate
|
19
|
+
page_collection.flush unless failed?
|
20
|
+
print_summary
|
21
|
+
nil
|
22
|
+
end
|
23
|
+
|
24
|
+
def require_spec_helper
|
25
|
+
path = Pathname.new(Dir.pwd).join('rspec-documentation/spec_helper.rb')
|
26
|
+
require path if path.file?
|
27
|
+
end
|
28
|
+
|
29
|
+
def configure(&block)
|
30
|
+
RSpecDocumentation.configure(&block)
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def print_success_summary
|
36
|
+
warn(paintbrush { green("\n Created #{blue(page_paths.size)} pages.\n") })
|
37
|
+
warn(paintbrush { cyan(" View your documentation here: #{white(bundle_index_path)}\n") })
|
38
|
+
end
|
39
|
+
|
40
|
+
def print_failure_summary
|
41
|
+
page_collection.failures.each do |failure|
|
42
|
+
$stderr.write(failure.message)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def bundle_index_path
|
47
|
+
RSpecDocumentation::Util.bundle_dir.glob('*.html').first.expand_path
|
48
|
+
end
|
49
|
+
|
50
|
+
def page_paths
|
51
|
+
@page_paths ||= Pathname.new(Dir.pwd).join('rspec-documentation/pages').glob('**/*.md')
|
52
|
+
end
|
53
|
+
|
54
|
+
def page_collection
|
55
|
+
@page_collection ||= RSpecDocumentation::PageCollection.new(page_paths: page_paths)
|
56
|
+
end
|
57
|
+
|
58
|
+
def failed?
|
59
|
+
!page_collection.failures.empty?
|
60
|
+
end
|
61
|
+
|
62
|
+
def print_summary
|
63
|
+
if failed?
|
64
|
+
print_failure_summary
|
65
|
+
else
|
66
|
+
print_success_summary
|
67
|
+
end
|
68
|
+
|
69
|
+
nil
|
70
|
+
end
|
71
|
+
end
|
9
72
|
end
|
10
73
|
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RSpecDocumentation
|
4
|
+
# Outputs a string containing ANSI color code escape sequences into HTML with attached classes
|
5
|
+
# for each matched color code. Cleans any remaining escape codes.
|
6
|
+
class AnsiHTML
|
7
|
+
COLOR_CODES = [0, 1, 2, 3, 4, 5, 6, 7, 9].freeze
|
8
|
+
|
9
|
+
def initialize(content)
|
10
|
+
@content = content
|
11
|
+
end
|
12
|
+
|
13
|
+
def render
|
14
|
+
"<div class='ansi-html border m-1 p-4'><span>#{subbed_content}</span></div>"
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
attr_reader :content
|
20
|
+
|
21
|
+
def subbed_content
|
22
|
+
COLOR_CODES.reduce(content) do |string, color_code|
|
23
|
+
string.gsub("\e[3#{color_code}m", "</span><span class='ansi-color-#{color_code}'>")
|
24
|
+
.gsub("\e[0m", "</span><span class='ansi-color-reset'>")
|
25
|
+
end.gsub(/\e\[[0-9]+m/, '')
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RSpecDocumentation
|
4
|
+
# Configures the rspec-documentation gem, allows setting a context that makes values available to each example.
|
5
|
+
class Configuration
|
6
|
+
def initialize
|
7
|
+
@context = Context.new
|
8
|
+
end
|
9
|
+
|
10
|
+
def context
|
11
|
+
yield @context if block_given?
|
12
|
+
@context
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RSpecDocumentation
|
4
|
+
# Used to fetch missing methods called in examples, provides a context available to all
|
5
|
+
# examples to avoid repeated setup in each example. Usage:
|
6
|
+
#
|
7
|
+
# ```ruby
|
8
|
+
# RSpec::Documentation.configure do |config|
|
9
|
+
# config.context do |context|
|
10
|
+
# context.my_value = 'example value'
|
11
|
+
# end
|
12
|
+
# end
|
13
|
+
# ```
|
14
|
+
#
|
15
|
+
# `my_value` will now be available in every example.
|
16
|
+
class Context
|
17
|
+
def initialize
|
18
|
+
@context = {}
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def method_missing(key, *args, &block)
|
24
|
+
return _assign(key, *args, &block) if key.to_s.end_with?('=')
|
25
|
+
return super unless @context.key?(key)
|
26
|
+
|
27
|
+
@context.fetch(key)
|
28
|
+
end
|
29
|
+
|
30
|
+
def respond_to_missing?(_key, *_)
|
31
|
+
true
|
32
|
+
end
|
33
|
+
|
34
|
+
def _assign(key, *args, &block)
|
35
|
+
key = key.to_s.rpartition('=').first.to_sym
|
36
|
+
|
37
|
+
@context[key] = if block_given?
|
38
|
+
block
|
39
|
+
else
|
40
|
+
args.first
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RSpecDocumentation
|
4
|
+
# Translates a Markdown document into a structure of parsed Markdown and embedded RSpec examples.
|
5
|
+
class Document
|
6
|
+
attr_reader :failures, :page_tree
|
7
|
+
|
8
|
+
def initialize(document:, page_tree:)
|
9
|
+
@document = document
|
10
|
+
@page_tree = page_tree
|
11
|
+
@failures = []
|
12
|
+
end
|
13
|
+
|
14
|
+
def specs
|
15
|
+
@specs ||= parsed_document.specs
|
16
|
+
end
|
17
|
+
|
18
|
+
def render
|
19
|
+
parsed_document.execute_and_substitute_examples!
|
20
|
+
if parsed_document.failures.empty?
|
21
|
+
RSpecDocumentation.template('layout').result(binding)
|
22
|
+
else
|
23
|
+
failures.concat(parsed_document.failures)
|
24
|
+
nil
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def html
|
29
|
+
parsed_document.html
|
30
|
+
end
|
31
|
+
|
32
|
+
def title
|
33
|
+
# TODO: Try other methods of inferring documentation title, allow setting by configuration
|
34
|
+
gem_spec&.name
|
35
|
+
end
|
36
|
+
|
37
|
+
def version
|
38
|
+
gem_spec&.version
|
39
|
+
end
|
40
|
+
|
41
|
+
def header
|
42
|
+
RSpecDocumentation.template('header').result(binding)
|
43
|
+
end
|
44
|
+
|
45
|
+
def footer
|
46
|
+
RSpecDocumentation.template('footer').result(binding)
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
attr_reader :document
|
52
|
+
|
53
|
+
def parsed_document
|
54
|
+
@parsed_document ||= ParsedDocument.new(document)
|
55
|
+
end
|
56
|
+
|
57
|
+
def gem_spec
|
58
|
+
@gem_spec ||= Gem::Specification.load(Pathname.new(Dir.pwd).glob('*.gemspec').first.to_s)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RSpecDocumentation
|
4
|
+
# Receives a spec manifest and generates a `Kramdown::Document` contaning an HTML node with a
|
5
|
+
# tabbed view displaying the example's code, its output and, if format is `:html`, the rendered
|
6
|
+
# HTML of the output. Injected into the parsed `Kramdown::Document` for the root Markdown file,
|
7
|
+
# replacing the code block it was produced from.
|
8
|
+
class HtmlElement
|
9
|
+
def initialize(spec:)
|
10
|
+
@spec = spec
|
11
|
+
end
|
12
|
+
|
13
|
+
def element
|
14
|
+
Kramdown::Document.new(tabbed_spec, input: 'html').root
|
15
|
+
end
|
16
|
+
|
17
|
+
def html_source
|
18
|
+
return nil unless spec.format == :html
|
19
|
+
|
20
|
+
formatter = Rouge::Formatters::HTML.new
|
21
|
+
lexer = Rouge::Lexers::HTML.new
|
22
|
+
|
23
|
+
formatter.format(lexer.lex(HtmlBeautifier.beautify(spec.described_object)))
|
24
|
+
end
|
25
|
+
|
26
|
+
def code_source
|
27
|
+
formatted_ruby(spec.source)
|
28
|
+
end
|
29
|
+
|
30
|
+
def rendered_result
|
31
|
+
return spec.described_object if spec.format == :html
|
32
|
+
return AnsiHTML.new(spec.described_object).render if spec.format == :ansi
|
33
|
+
|
34
|
+
formatted_ruby(spec.described_object.inspect)
|
35
|
+
end
|
36
|
+
|
37
|
+
def element_id
|
38
|
+
@element_id ||= SecureRandom.uuid
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
attr_reader :spec
|
44
|
+
|
45
|
+
def tabbed_spec
|
46
|
+
RSpecDocumentation.template('tabbed_spec').result(binding)
|
47
|
+
end
|
48
|
+
|
49
|
+
def formatted_ruby(code)
|
50
|
+
formatter = Rouge::Formatters::HTML.new
|
51
|
+
lexer = Rouge::Lexers::Ruby.new
|
52
|
+
formatter.format(lexer.lex(code))
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RSpecDocumentation
|
4
|
+
# Builds content for a collection of page paths, collates failures from embedded examples.
|
5
|
+
# Writes the final structure to disk.
|
6
|
+
class PageCollection
|
7
|
+
attr_reader :failures
|
8
|
+
|
9
|
+
def initialize(page_paths:)
|
10
|
+
@page_paths = page_paths
|
11
|
+
@buffer = {}
|
12
|
+
@failures = []
|
13
|
+
end
|
14
|
+
|
15
|
+
def generate
|
16
|
+
page_paths.sort.each do |path|
|
17
|
+
document = Document.new(document: path.read, page_tree: page_tree)
|
18
|
+
buffer[bundle_path_for(path)] = document.render
|
19
|
+
@failures.concat(document.failures)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def flush
|
24
|
+
Util.bundle_dir.rmtree if Util.bundle_dir.directory?
|
25
|
+
Util.bundle_dir.mkpath
|
26
|
+
|
27
|
+
@buffer.each do |path, content|
|
28
|
+
path.dirname.mkpath
|
29
|
+
Util.bundle_path(path).write(content)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
attr_reader :page_paths, :buffer
|
36
|
+
|
37
|
+
def page_tree
|
38
|
+
PageTree.new(page_paths: page_paths)
|
39
|
+
end
|
40
|
+
|
41
|
+
def bundle_path_for(path)
|
42
|
+
Util.bundle_path(path)
|
43
|
+
end
|
44
|
+
|
45
|
+
def root_path
|
46
|
+
Pathname.new(Dir.pwd).join('rspec-documentation')
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RSpecDocumentation
|
4
|
+
# A hierarchical structure of all pages in the documentation tree. Used for rendering a navigation section.
|
5
|
+
class PageTree
|
6
|
+
def initialize(page_paths:)
|
7
|
+
@page_paths = page_paths
|
8
|
+
@structure = {}
|
9
|
+
@nodes = []
|
10
|
+
end
|
11
|
+
|
12
|
+
def elements
|
13
|
+
build_nodes(
|
14
|
+
root: tree['rspec-documentation']['pages'],
|
15
|
+
path: root_path.join('rspec-documentation/pages')
|
16
|
+
)
|
17
|
+
nodes.flatten.compact
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
attr_reader :page_paths, :structure, :nodes
|
23
|
+
|
24
|
+
def tree
|
25
|
+
@tree ||= begin
|
26
|
+
build_tree
|
27
|
+
structure
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def build_nodes(root:, path:)
|
32
|
+
root[:children].sort.map do |child|
|
33
|
+
node = page_tree_node(path: path, child: child)
|
34
|
+
next nil if node.nil?
|
35
|
+
|
36
|
+
nodes.concat(node)
|
37
|
+
nodes.push('<ul>')
|
38
|
+
build_nodes(root: root[child], path: path.join(child)) unless root[child].nil?
|
39
|
+
nodes.push('</ul>')
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def build_tree(branch: structure, depth: 0)
|
44
|
+
normalized_paths.each do |path|
|
45
|
+
first, second, *rest = path_segments(path: path, depth: depth)
|
46
|
+
next if second.nil?
|
47
|
+
|
48
|
+
branch[first] ||= {}
|
49
|
+
branch[first][:children] ||= Set.new
|
50
|
+
branch[first][:children].add(second)
|
51
|
+
build_tree(branch: branch[first], depth: depth + 1)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def path_segments(path:, depth:)
|
56
|
+
path.to_s.split('/')[depth..]
|
57
|
+
end
|
58
|
+
|
59
|
+
def root_path
|
60
|
+
@root_path ||= Pathname.new(Dir.pwd)
|
61
|
+
end
|
62
|
+
|
63
|
+
def normalized_paths
|
64
|
+
@normalized_paths ||= page_paths.sort.map do |path|
|
65
|
+
path.relative_path_from(root_path)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def page_tree_node(path:, child:)
|
70
|
+
PageTreeElement.new(path: path, child: child).node
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RSpecDocumentation
|
4
|
+
# An element in a page tree, generates a single entry as a list item, linked to the relevant
|
5
|
+
# document (or just a text entry if no document exists, i.e. if it is a directory without an
|
6
|
+
# index file).
|
7
|
+
class PageTreeElement
|
8
|
+
def initialize(path:, child:)
|
9
|
+
@path = path
|
10
|
+
@child = child
|
11
|
+
@nodes = []
|
12
|
+
end
|
13
|
+
|
14
|
+
def node
|
15
|
+
return nil if entry_and_directory?
|
16
|
+
|
17
|
+
nodes.push('<li>')
|
18
|
+
nodes.push(link) if entry?
|
19
|
+
nodes.push(bullet) unless entry?
|
20
|
+
nodes.push('</li>')
|
21
|
+
nodes
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
attr_reader :path, :child, :nodes
|
27
|
+
|
28
|
+
def entry_and_directory?
|
29
|
+
return false unless path.join(child).sub_ext('.md').file?
|
30
|
+
return false unless path.join(child).sub_ext('').directory?
|
31
|
+
return false unless path.join(child).file?
|
32
|
+
|
33
|
+
true
|
34
|
+
end
|
35
|
+
|
36
|
+
def entry?
|
37
|
+
path.join(child).sub_ext('.md').file?
|
38
|
+
end
|
39
|
+
|
40
|
+
def link
|
41
|
+
"<a href='#{href}'>#{title}</a>"
|
42
|
+
end
|
43
|
+
|
44
|
+
def bullet
|
45
|
+
"<b>#{title}</b>"
|
46
|
+
end
|
47
|
+
|
48
|
+
def href
|
49
|
+
Util.href(path.join(child))
|
50
|
+
end
|
51
|
+
|
52
|
+
def title
|
53
|
+
Util.label(child)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RSpecDocumentation
|
4
|
+
# A parsed Markdown file, provides matched RSpec examples from the document and their location.
|
5
|
+
class ParsedDocument
|
6
|
+
attr_reader :failures
|
7
|
+
|
8
|
+
def initialize(document)
|
9
|
+
@document = Kramdown::Document.new(document, input: 'GFM')
|
10
|
+
@failures = []
|
11
|
+
end
|
12
|
+
|
13
|
+
def html
|
14
|
+
document.to_html
|
15
|
+
end
|
16
|
+
|
17
|
+
def execute_and_substitute_examples!
|
18
|
+
specs.each do |spec|
|
19
|
+
spec.run
|
20
|
+
break failures << spec.failure unless spec.failure.nil?
|
21
|
+
|
22
|
+
spec.parent.children[spec.index] = spec_element(spec)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def specs
|
27
|
+
@specs ||= recursive_specs
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
attr_reader :document
|
33
|
+
|
34
|
+
def recursive_specs(element: document.root)
|
35
|
+
element.children.each.with_index.map do |child, index|
|
36
|
+
next recursive_specs(element: child) unless child.children.empty?
|
37
|
+
next nil unless rspec_codeblock?(child)
|
38
|
+
|
39
|
+
spec_for(element: child, parent: element, index: index)
|
40
|
+
end.flatten.compact
|
41
|
+
end
|
42
|
+
|
43
|
+
def rspec_codeblock?(element)
|
44
|
+
return false unless element.type == :codeblock
|
45
|
+
return false unless element.options.key?(:lang)
|
46
|
+
|
47
|
+
element.options[:lang] == 'rspec' || element.options[:lang].start_with?('rspec:')
|
48
|
+
end
|
49
|
+
|
50
|
+
def spec_for(element:, parent:, index:)
|
51
|
+
Spec.new(
|
52
|
+
spec: element.value,
|
53
|
+
format: element.options[:lang].partition(':').last,
|
54
|
+
parent: parent,
|
55
|
+
index: index,
|
56
|
+
location: element.options[:location]
|
57
|
+
)
|
58
|
+
end
|
59
|
+
|
60
|
+
def spec_element(spec)
|
61
|
+
Kramdown::Element.new(:html_element, 'div', { location: spec.location }).tap do |element|
|
62
|
+
element.children = HtmlElement.new(spec: spec).element.children
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def report_error(spec)
|
67
|
+
$stderr.write(spec.failure.message)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|