rspec-documentation 0.0.1 → 0.0.3

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.
Files changed (101) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +1 -0
  3. data/.rubocop.yml +3 -0
  4. data/Gemfile +3 -0
  5. data/Gemfile.lock +25 -2
  6. data/Makefile +9 -0
  7. data/README.md +18 -22
  8. data/Rakefile +1 -0
  9. data/exe/rspec-documentation +17 -0
  10. data/lib/rspec/documentation/version.rb +2 -2
  11. data/lib/rspec/documentation.rb +15 -2
  12. data/lib/rspec_documentation/configuration.rb +28 -0
  13. data/lib/rspec_documentation/document.rb +75 -0
  14. data/lib/rspec_documentation/documentation.rb +85 -0
  15. data/lib/rspec_documentation/formatters/ansi.rb +44 -0
  16. data/lib/rspec_documentation/formatters/html.rb +31 -0
  17. data/lib/rspec_documentation/formatters/json.rb +32 -0
  18. data/lib/rspec_documentation/formatters/ruby.rb +31 -0
  19. data/lib/rspec_documentation/formatters/yaml.rb +36 -0
  20. data/lib/rspec_documentation/formatters.rb +20 -0
  21. data/lib/rspec_documentation/html_element.rb +58 -0
  22. data/lib/rspec_documentation/javascript_bundle.rb +17 -0
  23. data/lib/rspec_documentation/markdown_renderer.rb +7 -0
  24. data/lib/rspec_documentation/page_collection.rb +64 -0
  25. data/lib/rspec_documentation/page_tree.rb +81 -0
  26. data/lib/rspec_documentation/page_tree_element.rb +78 -0
  27. data/lib/rspec_documentation/parsed_document.rb +72 -0
  28. data/lib/rspec_documentation/project_initialization.rb +52 -0
  29. data/lib/rspec_documentation/rspec/failure.rb +52 -0
  30. data/lib/rspec_documentation/rspec.rb +12 -0
  31. data/lib/rspec_documentation/spec.rb +108 -0
  32. data/lib/rspec_documentation/stylesheet_bundle.rb +17 -0
  33. data/lib/rspec_documentation/summary.rb +87 -0
  34. data/lib/rspec_documentation/util.rb +58 -0
  35. data/lib/rspec_documentation.rb +71 -0
  36. data/lib/tasks/rspec/documentation/generate.rake +10 -0
  37. data/lib/templates/000-Introduction.md.erb +35 -0
  38. data/lib/templates/010-Usage.md.erb +3 -0
  39. data/lib/templates/500-License.md.erb +3 -0
  40. data/lib/templates/bootstrap.js.erb +7 -0
  41. data/lib/templates/footer.html.erb +6 -0
  42. data/lib/templates/header.html.erb +20 -0
  43. data/lib/templates/layout.css.erb +418 -0
  44. data/lib/templates/layout.html.erb +31 -0
  45. data/lib/templates/layout.js.erb +67 -0
  46. data/lib/templates/modal_spec.html.erb +51 -0
  47. data/lib/templates/moon.svg.erb +1 -0
  48. data/lib/templates/script_tags.html.erb +1 -0
  49. data/lib/templates/stylesheet_links.html.erb +1 -0
  50. data/lib/templates/sun.svg.erb +1 -0
  51. data/lib/templates/tabbed_spec.html.erb +82 -0
  52. data/lib/templates/themes/bootstrap.min.css +11 -0
  53. data/lib/templates/themes/cerulean.css +11 -0
  54. data/lib/templates/themes/cosmo.css +11 -0
  55. data/lib/templates/themes/cyborg.css +11 -0
  56. data/lib/templates/themes/darkly.css +11 -0
  57. data/lib/templates/themes/flatly.css +11 -0
  58. data/lib/templates/themes/journal.css +11 -0
  59. data/lib/templates/themes/litera.css +11 -0
  60. data/lib/templates/themes/lumen.css +11 -0
  61. data/lib/templates/themes/lux.css +11 -0
  62. data/lib/templates/themes/materia.css +11 -0
  63. data/lib/templates/themes/minty.css +11 -0
  64. data/lib/templates/themes/morph.css +11 -0
  65. data/lib/templates/themes/pulse.css +11 -0
  66. data/lib/templates/themes/quartz.css +11 -0
  67. data/lib/templates/themes/sandstone.css +11 -0
  68. data/lib/templates/themes/simplex.css +11 -0
  69. data/lib/templates/themes/sketchy.css +11 -0
  70. data/lib/templates/themes/slate.css +11 -0
  71. data/lib/templates/themes/solar.css +11 -0
  72. data/lib/templates/themes/spacelab.css +11 -0
  73. data/lib/templates/themes/superhero.css +11 -0
  74. data/lib/templates/themes/united.css +11 -0
  75. data/lib/templates/themes/vapor.css +11 -0
  76. data/lib/templates/themes/yeti.css +11 -0
  77. data/lib/templates/themes/zephyr.css +11 -0
  78. data/rspec-documentation/pages/000-Introduction/000-Quickstart.md +17 -0
  79. data/rspec-documentation/pages/000-Introduction.md +23 -0
  80. data/rspec-documentation/pages/010-File System/000-Ordering.md +14 -0
  81. data/rspec-documentation/pages/010-File System/010-Documentation Bundle.md +9 -0
  82. data/rspec-documentation/pages/010-File System.md +26 -0
  83. data/rspec-documentation/pages/020-Running Tests.md +41 -0
  84. data/rspec-documentation/pages/030-Examples/010-Basic.md +51 -0
  85. data/rspec-documentation/pages/030-Examples/020-HTML.md +45 -0
  86. data/rspec-documentation/pages/030-Examples/030-ANSI.md +33 -0
  87. data/rspec-documentation/pages/030-Examples/040-JSON.md +39 -0
  88. data/rspec-documentation/pages/030-Examples/050-YAML.md +40 -0
  89. data/rspec-documentation/pages/030-Examples.md +7 -0
  90. data/rspec-documentation/pages/040-Spec Helper.md +11 -0
  91. data/rspec-documentation/pages/050-Linking.md +20 -0
  92. data/rspec-documentation/pages/060-Configuration/010-Context.md +26 -0
  93. data/rspec-documentation/pages/060-Configuration/020-Build Paths.md +33 -0
  94. data/rspec-documentation/pages/060-Configuration/030-Attribution.md +23 -0
  95. data/rspec-documentation/pages/060-Configuration.md +13 -0
  96. data/rspec-documentation/pages/070-Publishing.md +13 -0
  97. data/rspec-documentation/pages/500-License.md +11 -0
  98. data/rspec-documentation/spec_helper.rb +8 -0
  99. data/rspec-documentation.gemspec +10 -1
  100. data/sig/rspec/documentation.rbs +1 -1
  101. metadata +193 -5
@@ -0,0 +1,58 @@
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 code_source
18
+ formatter = Rouge::Formatters::HTML.new
19
+ lexer = Rouge::Lexers::Ruby.new
20
+ Formatters.with_translated_html_entities(formatter.format(lexer.lex(spec.source)))
21
+ end
22
+
23
+ def prettified_output
24
+ Formatters.with_translated_html_entities(formatter.prettified_output)
25
+ end
26
+
27
+ def rendered_output
28
+ return formatter.rendered_output if render_raw?
29
+
30
+ Formatters.with_translated_html_entities(formatter.rendered_output)
31
+ end
32
+
33
+ def render_raw?
34
+ formatter.render_raw?
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 formatter
46
+ @formatter ||= {
47
+ html: Formatters::Html,
48
+ ansi: Formatters::Ansi,
49
+ json: Formatters::Json,
50
+ yaml: Formatters::Yaml
51
+ }.fetch(spec.format, Formatters::Ruby).new(subject: spec.subject)
52
+ end
53
+
54
+ def tabbed_spec
55
+ RSpecDocumentation.template('tabbed_spec').result(binding)
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RSpecDocumentation
4
+ # Compiles Javascript assets into a single file.
5
+ class JavascriptBundle
6
+ def flush
7
+ Util.bundle_dir.join('assets').mkpath
8
+ javascript_bundle_path.write(RSpecDocumentation.template(:layout, :js).result(binding))
9
+ end
10
+
11
+ private
12
+
13
+ def javascript_bundle_path
14
+ Util.bundle_dir.join('assets/bundle.js')
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RSpecDocumentation
4
+ class MarkdownRenderer < Redcarpet::Render::HTML
5
+ include Rouge::Plugins::Redcarpet
6
+ end
7
+ end
@@ -0,0 +1,64 @@
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, :page_paths
8
+
9
+ def initialize(page_paths:)
10
+ @page_paths = page_paths.sort
11
+ @buffer = {}
12
+ @failures = []
13
+ end
14
+
15
+ def generate
16
+ page_paths.zip(documents).each do |path, document|
17
+ buffer[bundle_path_for(path)] = document.render
18
+ failures.concat(document.failures)
19
+ end
20
+ end
21
+
22
+ def documents
23
+ @documents ||= page_paths.map do |path|
24
+ Document.new(document: path.read, path: path, page_tree: page_tree(path: path))
25
+ end
26
+ end
27
+
28
+ def flush
29
+ Util.bundle_dir.rmtree if Util.bundle_dir.directory?
30
+ Util.bundle_dir.mkpath
31
+
32
+ buffer.each do |path, content|
33
+ path.dirname.mkpath
34
+ Util.bundle_path(path).write(content)
35
+ end
36
+ write_index unless buffer.empty?
37
+ end
38
+
39
+ def examples_count
40
+ documents.map(&:specs).flatten.size
41
+ end
42
+
43
+ private
44
+
45
+ attr_reader :buffer
46
+
47
+ def write_index
48
+ _path, content = buffer.first
49
+ Util.bundle_index_path.write(content)
50
+ end
51
+
52
+ def page_tree(path:)
53
+ PageTree.new(page_paths: page_paths, current_path: path)
54
+ end
55
+
56
+ def bundle_path_for(path)
57
+ Util.bundle_path(path)
58
+ end
59
+
60
+ def root_path
61
+ Pathname.new(Dir.pwd).join('rspec-documentation')
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,81 @@
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:, current_path:)
7
+ @page_paths = page_paths
8
+ @current_path = current_path
9
+ @structure = {}
10
+ @nodes = []
11
+ end
12
+
13
+ def elements
14
+ build_nodes(
15
+ root: tree['rspec-documentation']['pages'],
16
+ path: root_path.join('rspec-documentation/pages')
17
+ )
18
+ nodes.flatten.compact
19
+ end
20
+
21
+ private
22
+
23
+ attr_reader :page_paths, :structure, :nodes, :current_path
24
+
25
+ def tree
26
+ @tree ||= begin
27
+ build_tree
28
+ structure
29
+ end
30
+ end
31
+
32
+ def build_nodes(root:, path:)
33
+ root[:children].sort.each do |child|
34
+ node = page_tree_node(path: path, child: child)
35
+ next nil if node.nil?
36
+
37
+ li_open, *li_body, li_close = node
38
+ nodes.push(li_open)
39
+ nodes.concat(li_body)
40
+ push_children(root: root, path: path, child: child)
41
+ nodes.push(li_close)
42
+ end
43
+ end
44
+
45
+ def push_children(root:, path:, child:)
46
+ nodes.push('<ol>')
47
+ build_nodes(root: root[child], path: path.join(child)) unless root[child].nil?
48
+ nodes.last == '<ol>' ? nodes.pop : nodes.push('</ol>')
49
+ end
50
+
51
+ def build_tree(branch: structure, depth: 0)
52
+ normalized_paths.each do |path|
53
+ first, second, *rest = path_segments(path: path, depth: depth)
54
+ next if second.nil?
55
+
56
+ branch[first] ||= {}
57
+ branch[first][:children] ||= Set.new
58
+ branch[first][:children].add(second)
59
+ build_tree(branch: branch[first], depth: depth + 1)
60
+ end
61
+ end
62
+
63
+ def path_segments(path:, depth:)
64
+ path.to_s.split('/')[depth..]
65
+ end
66
+
67
+ def root_path
68
+ @root_path ||= Pathname.new(Dir.pwd)
69
+ end
70
+
71
+ def normalized_paths
72
+ @normalized_paths ||= page_paths.sort.map do |path|
73
+ path.relative_path_from(root_path)
74
+ end
75
+ end
76
+
77
+ def page_tree_node(path:, child:)
78
+ PageTreeElement.new(path: path, child: child, current_path: current_path).node
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,78 @@
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:, current_path:)
9
+ @path = path
10
+ @child = child
11
+ @current_path = current_path
12
+ @nodes = []
13
+ raise MissingFileError, "Missing file: #{entry_path.relative_path_from(Util.base_dir)}" unless entry?
14
+ end
15
+
16
+ def node
17
+ return nil if entry_and_directory?
18
+
19
+ nodes.push(li_open)
20
+ nodes.push(link)
21
+ nodes.push('</li>')
22
+ nodes
23
+ end
24
+
25
+ private
26
+
27
+ attr_reader :path, :child, :current_path, :nodes
28
+
29
+ def entry_and_directory?
30
+ return false unless path.join(child).sub_ext('.md').file?
31
+ return false unless path.join(child).sub_ext('').directory?
32
+ return false unless path.join(child).file?
33
+
34
+ true
35
+ end
36
+
37
+ def entry_path
38
+ path.join(child).sub_ext('.md')
39
+ end
40
+
41
+ def entry?
42
+ entry_path.file?
43
+ end
44
+
45
+ def li_open
46
+ "<li id='#{Util.path_id(path.join(child))}' #{active_class} data-list-item-id='##{path_id}' " \
47
+ "data-parent-id='##{parent_path_id}'>"
48
+ end
49
+
50
+ def link
51
+ "<a #{active_class} href='#{href}'>#{title}</a>"
52
+ end
53
+
54
+ def path_id
55
+ @path_id ||= Util.path_id(path.join(child))
56
+ end
57
+
58
+ def parent_path_id
59
+ @parent_path_id ||= Util.path_id(path)
60
+ end
61
+
62
+ def active_class
63
+ active? ? 'class="active"' : nil
64
+ end
65
+
66
+ def href
67
+ Util.href(path.join(child))
68
+ end
69
+
70
+ def active?
71
+ Util.href(current_path) == href
72
+ end
73
+
74
+ def title
75
+ Util.label(child)
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,72 @@
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, path:)
9
+ @document = Kramdown::Document.new(document, input: 'GFM', syntax_highlighter: 'rouge')
10
+ @path = path
11
+ @failures = []
12
+ end
13
+
14
+ def html
15
+ document.to_html
16
+ end
17
+
18
+ def execute_and_substitute_examples!
19
+ specs.each do |spec|
20
+ spec.run
21
+ break failures << spec.failure unless spec.failure.nil?
22
+
23
+ spec.parent.children[spec.index] = spec_element(spec)
24
+ end
25
+ end
26
+
27
+ def specs
28
+ @specs ||= recursive_specs
29
+ end
30
+
31
+ private
32
+
33
+ attr_reader :document, :path
34
+
35
+ def recursive_specs(element: document.root)
36
+ element.children.each.with_index.map do |child, index|
37
+ next recursive_specs(element: child) unless child.children.empty?
38
+ next nil unless rspec_codeblock?(child)
39
+
40
+ spec_for(element: child, parent: element, index: index)
41
+ end.flatten.compact
42
+ end
43
+
44
+ def rspec_codeblock?(element)
45
+ return false unless element.type == :codeblock
46
+ return false unless element.options.key?(:lang)
47
+
48
+ element.options[:lang] == 'rspec' || element.options[:lang].start_with?('rspec:')
49
+ end
50
+
51
+ def spec_for(element:, parent:, index:)
52
+ Spec.new(
53
+ spec: element.value,
54
+ format: element.options[:lang].partition(':').last,
55
+ parent: parent,
56
+ index: index,
57
+ path: path,
58
+ location: element.options[:location]
59
+ )
60
+ end
61
+
62
+ def spec_element(spec)
63
+ Kramdown::Element.new(:html_element, 'div', { location: spec.location }).tap do |element|
64
+ element.children = HtmlElement.new(spec: spec).element.children
65
+ end
66
+ end
67
+
68
+ def report_error(spec)
69
+ $stderr.write(spec.failure.message)
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RSpecDocumentation
4
+ # Initializes a new project if `rspec-documentation/` directory is empty.
5
+ class ProjectInitialization
6
+ include Paintbrush
7
+
8
+ def flush
9
+ print_welcome
10
+ create_base_dir
11
+ create_sample_files
12
+ print_initialization_complete
13
+ end
14
+
15
+ private
16
+
17
+ def print_welcome
18
+ warn(paintbrush { blue "\nWelcome to #{cyan 'RSpec Documentation'}. A new project is being initialized.\n" })
19
+ warn(paintbrush { blue "If you want undo at any point, simply delete #{cyan 'rspec-documentation/'}.\n" })
20
+ end
21
+
22
+ def create_base_dir
23
+ Util.base_dir.mkpath
24
+ print_created(Util.base_dir)
25
+ end
26
+
27
+ def create_sample_files
28
+ sample_files.each do |sample_file|
29
+ Util.base_dir.join(sample_file).sub_ext('.md').write(RSpecDocumentation.template(sample_file, :md).result)
30
+ print_created(Util.base_dir.join(sample_file).sub_ext('.md'))
31
+ end
32
+ end
33
+
34
+ def print_created(path)
35
+ warn(paintbrush { " #{green_b 'create'} #{cyan path.relative_path_from(pwd)}" })
36
+ end
37
+
38
+ def print_initialization_complete
39
+ warn(paintbrush do
40
+ blue "\nYour documentation project has been #{green 'initialized'} wlth smoe example pages."
41
+ end)
42
+ end
43
+
44
+ def pwd
45
+ Pathname.new(Dir.pwd)
46
+ end
47
+
48
+ def sample_files
49
+ %w[000-Introduction 010-Usage 500-License]
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RSpecDocumentation
4
+ module RSpec
5
+ # Stores information about a failed RSpec example. Thin wrapper around `RSpec::Failure`.
6
+ class Failure
7
+ include Paintbrush
8
+
9
+ attr_reader :spec
10
+
11
+ def initialize(cause:, spec:)
12
+ @cause = cause
13
+ @spec = spec
14
+ end
15
+
16
+ def message
17
+ "\n#{formatted_header}\n\n#{formatted_source}\n\n#{formatted_cause}\n\n"
18
+ end
19
+
20
+ private
21
+
22
+ attr_reader :cause
23
+
24
+ def formatted_header
25
+ paintbrush { cyan indented("# #{path}:#{spec.location}") }
26
+ end
27
+
28
+ def path
29
+ spec.path.relative_path_from(Pathname.new(Dir.pwd))
30
+ end
31
+
32
+ def formatted_source
33
+ paintbrush { white indented(spec.source) }
34
+ end
35
+
36
+ def formatted_cause
37
+ paintbrush { red indented(without_anonymous_group_text(cause.message)) }
38
+ end
39
+
40
+ def indented(text)
41
+ text.split("\n").map { |line| " #{line}" }.join("\n")
42
+ end
43
+
44
+ # If an error occurs outside of a test, RSpec will provide an error to the Reporter
45
+ # referring to an anonymous group due to the way specs are evaluated. This does not help
46
+ # with debugging so we remove it. TODO: Find a better way ?
47
+ def without_anonymous_group_text(text)
48
+ text&.gsub(/ for #<RSpec::ExampleGroups::Anonymous.*$/, '')
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'rspec/failure'
4
+
5
+ module RSpecDocumentation
6
+ # Provides various classes that wrap or interface RSpec internals.
7
+ module RSpec
8
+ def self.with_failure_notifier(callable, &block)
9
+ ::RSpec::Support.with_failure_notifier(callable, &block)
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RSpecDocumentation
4
+ # Executes specs from a Markdown code block and provides the outcome in the appropriate format.
5
+ class Spec
6
+ attr_reader :parent, :location, :index, :format, :path
7
+
8
+ @durations = []
9
+
10
+ class << self
11
+ attr_accessor :subjects, :durations
12
+ end
13
+
14
+ def initialize(spec:, format:, parent:, location:, path:, index:) # rubocop:disable Metrics/ParameterLists
15
+ @spec = spec
16
+ @format = format.empty? ? :plaintext : format.to_sym
17
+ @parent = parent
18
+ @location = location
19
+ @path = path
20
+ @index = index
21
+ @failures = []
22
+ end
23
+
24
+ def subject
25
+ raise Error, "Code block did not define an example (e.g. with `it`).\n#{spec}" if examples.empty?
26
+ raise Error, "Code block did not define a subject:\n#{spec}" if subjects.empty?
27
+ raise Error, "Cannot define more than one example per code block:\n#{spec}" if subjects.size > 1
28
+
29
+ subjects.last
30
+ end
31
+
32
+ def source
33
+ spec
34
+ end
35
+
36
+ def failure
37
+ failures.first
38
+ end
39
+
40
+ def reporter
41
+ @reporter ||= ::RSpec::Core::Reporter.new(::RSpec::Core::Configuration.new)
42
+ end
43
+
44
+ def run
45
+ self.class.subjects = []
46
+ RSpec.with_failure_notifier(failure_notifier) do
47
+ succeeded = example_group.run(reporter)
48
+ durations << run_time if run_time
49
+ next succeeded if succeeded
50
+
51
+ notify_failure(reported_failure)
52
+ succeeded
53
+ end
54
+ end
55
+
56
+ private
57
+
58
+ attr_reader :spec, :failures
59
+
60
+ def subjects
61
+ self.class.subjects
62
+ end
63
+
64
+ def durations
65
+ self.class.durations
66
+ end
67
+
68
+ def examples
69
+ @examples ||= example_group.examples
70
+ end
71
+
72
+ def example_group
73
+ # rubocop:disable Style/DocumentDynamicEvalDefinition, Security/Eval
74
+ @example_group ||= binding.eval(
75
+ <<-SPEC, __FILE__, __LINE__.to_i
76
+ ::RSpec::Core::ExampleGroup.describe do
77
+ after { RSpecDocumentation::Spec.subjects << subject }
78
+ include_context '__rspec_documentation' do
79
+ #{spec}
80
+ end
81
+ end
82
+ SPEC
83
+ )
84
+ # rubocop:enable Style/DocumentDynamicEvalDefinition, Security/Eval
85
+ end
86
+
87
+ def run_time
88
+ return nil unless reporter&.examples&.first
89
+
90
+ @run_time ||= reporter.examples.first.execution_result.run_time
91
+ end
92
+
93
+ def notify_failure(failure)
94
+ failures << RSpec::Failure.new(cause: failure, spec: self)
95
+ end
96
+
97
+ def reported_failure
98
+ exception = reporter.failed_examples.first.exception
99
+ return exception unless exception.is_a?(::RSpec::Core::MultipleExceptionError)
100
+
101
+ exception.all_exceptions.first
102
+ end
103
+
104
+ def failure_notifier
105
+ proc { |failure| notify_failure(failure) }
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RSpecDocumentation
4
+ # Compiles CSS assets into a single file.
5
+ class StylesheetBundle
6
+ def flush
7
+ Util.bundle_dir.join('assets').mkpath
8
+ stylesheet_bundle_path.write(RSpecDocumentation.template(:layout, :css).result(binding))
9
+ end
10
+
11
+ private
12
+
13
+ def stylesheet_bundle_path
14
+ Util.bundle_dir.join('assets/bundle.css')
15
+ end
16
+ end
17
+ end