rspec-documentation 0.0.2 → 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 (102) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -0
  3. data/Gemfile.lock +3 -3
  4. data/Makefile +9 -0
  5. data/README.md +17 -21
  6. data/exe/rspec-documentation +13 -1
  7. data/lib/rspec/documentation/version.rb +1 -1
  8. data/lib/rspec/documentation.rb +1 -51
  9. data/lib/rspec_documentation/configuration.rb +17 -4
  10. data/lib/rspec_documentation/document.rb +18 -4
  11. data/lib/rspec_documentation/documentation.rb +85 -0
  12. data/lib/rspec_documentation/formatters/ansi.rb +44 -0
  13. data/lib/rspec_documentation/formatters/html.rb +31 -0
  14. data/lib/rspec_documentation/formatters/json.rb +32 -0
  15. data/lib/rspec_documentation/formatters/ruby.rb +31 -0
  16. data/lib/rspec_documentation/formatters/yaml.rb +36 -0
  17. data/lib/rspec_documentation/formatters.rb +20 -0
  18. data/lib/rspec_documentation/html_element.rb +21 -18
  19. data/lib/rspec_documentation/javascript_bundle.rb +17 -0
  20. data/lib/rspec_documentation/page_collection.rb +24 -9
  21. data/lib/rspec_documentation/page_tree.rb +16 -8
  22. data/lib/rspec_documentation/page_tree_element.rb +31 -9
  23. data/lib/rspec_documentation/parsed_document.rb +5 -3
  24. data/lib/rspec_documentation/project_initialization.rb +52 -0
  25. data/lib/rspec_documentation/rspec/failure.rb +20 -5
  26. data/lib/rspec_documentation/rspec.rb +0 -2
  27. data/lib/rspec_documentation/spec.rb +58 -12
  28. data/lib/rspec_documentation/stylesheet_bundle.rb +17 -0
  29. data/lib/rspec_documentation/summary.rb +87 -0
  30. data/lib/rspec_documentation/util.rb +12 -0
  31. data/lib/rspec_documentation.rb +16 -3
  32. data/lib/templates/000-Introduction.md.erb +35 -0
  33. data/lib/templates/010-Usage.md.erb +3 -0
  34. data/lib/templates/500-License.md.erb +3 -0
  35. data/lib/templates/bootstrap.js.erb +7 -0
  36. data/lib/templates/footer.html.erb +5 -0
  37. data/lib/templates/header.html.erb +16 -3
  38. data/lib/templates/layout.css.erb +381 -2
  39. data/lib/templates/layout.html.erb +13 -82
  40. data/lib/templates/layout.js.erb +67 -0
  41. data/lib/templates/modal_spec.html.erb +51 -0
  42. data/lib/templates/moon.svg.erb +1 -0
  43. data/lib/templates/script_tags.html.erb +1 -0
  44. data/lib/templates/stylesheet_links.html.erb +1 -0
  45. data/lib/templates/sun.svg.erb +1 -0
  46. data/lib/templates/tabbed_spec.html.erb +12 -49
  47. data/lib/templates/themes/bootstrap.min.css +11 -0
  48. data/lib/templates/themes/cerulean.css +5 -6
  49. data/lib/templates/themes/cosmo.css +5 -6
  50. data/lib/templates/themes/cyborg.css +5 -6
  51. data/lib/templates/themes/darkly.css +5 -6
  52. data/lib/templates/themes/flatly.css +5 -6
  53. data/lib/templates/themes/journal.css +5 -6
  54. data/lib/templates/themes/litera.css +5 -6
  55. data/lib/templates/themes/lumen.css +5 -6
  56. data/lib/templates/themes/lux.css +5 -6
  57. data/lib/templates/themes/materia.css +5 -6
  58. data/lib/templates/themes/minty.css +5 -6
  59. data/lib/templates/themes/morph.css +5 -6
  60. data/lib/templates/themes/pulse.css +5 -6
  61. data/lib/templates/themes/quartz.css +5 -6
  62. data/lib/templates/themes/sandstone.css +5 -6
  63. data/lib/templates/themes/simplex.css +5 -6
  64. data/lib/templates/themes/sketchy.css +5 -6
  65. data/lib/templates/themes/slate.css +5 -6
  66. data/lib/templates/themes/solar.css +5 -6
  67. data/lib/templates/themes/spacelab.css +5 -6
  68. data/lib/templates/themes/superhero.css +5 -6
  69. data/lib/templates/themes/united.css +5 -6
  70. data/lib/templates/themes/vapor.css +5 -6
  71. data/lib/templates/themes/yeti.css +5 -6
  72. data/lib/templates/themes/zephyr.css +5 -6
  73. data/rspec-documentation/pages/000-Introduction/000-Quickstart.md +17 -0
  74. data/rspec-documentation/pages/000-Introduction.md +14 -30
  75. data/rspec-documentation/pages/010-File System/000-Ordering.md +1 -1
  76. data/rspec-documentation/pages/010-File System/010-Documentation Bundle.md +9 -0
  77. data/rspec-documentation/pages/010-File System.md +1 -1
  78. data/rspec-documentation/pages/020-Running Tests.md +41 -0
  79. data/rspec-documentation/pages/030-Examples/010-Basic.md +51 -0
  80. data/rspec-documentation/pages/030-Examples/020-HTML.md +45 -0
  81. data/rspec-documentation/pages/030-Examples/030-ANSI.md +33 -0
  82. data/rspec-documentation/pages/030-Examples/040-JSON.md +39 -0
  83. data/rspec-documentation/pages/030-Examples/050-YAML.md +40 -0
  84. data/rspec-documentation/pages/030-Examples.md +7 -0
  85. data/rspec-documentation/pages/040-Spec Helper.md +11 -0
  86. data/rspec-documentation/pages/050-Linking.md +20 -0
  87. data/rspec-documentation/pages/060-Configuration/010-Context.md +26 -0
  88. data/rspec-documentation/pages/060-Configuration/020-Build Paths.md +33 -0
  89. data/rspec-documentation/pages/060-Configuration/030-Attribution.md +23 -0
  90. data/rspec-documentation/pages/060-Configuration.md +13 -0
  91. data/rspec-documentation/pages/070-Publishing.md +13 -0
  92. data/rspec-documentation/pages/500-License.md +11 -0
  93. data/rspec-documentation/spec_helper.rb +8 -0
  94. data/rspec-documentation.gemspec +2 -1
  95. metadata +46 -12
  96. data/lib/rspec_documentation/ansi_html.rb +0 -28
  97. data/lib/rspec_documentation/context.rb +0 -44
  98. data/lib/rspec_documentation/rspec/example_group.rb +0 -26
  99. data/lib/rspec_documentation/rspec/reporter.rb +0 -45
  100. data/rspec-documentation/pages/010-File System/010-Standalone Directories.md +0 -17
  101. data/rspec-documentation/pages/010-File System/020-Standalone Directory/Example Page.md +0 -3
  102. data/rspec-documentation/pages/020-Running Specs.md +0 -11
@@ -4,19 +4,24 @@ module RSpecDocumentation
4
4
  # Builds content for a collection of page paths, collates failures from embedded examples.
5
5
  # Writes the final structure to disk.
6
6
  class PageCollection
7
- attr_reader :failures
7
+ attr_reader :failures, :page_paths
8
8
 
9
9
  def initialize(page_paths:)
10
- @page_paths = page_paths
10
+ @page_paths = page_paths.sort
11
11
  @buffer = {}
12
12
  @failures = []
13
13
  end
14
14
 
15
15
  def generate
16
- page_paths.sort.each do |path|
17
- document = Document.new(document: path.read, page_tree: page_tree)
16
+ page_paths.zip(documents).each do |path, document|
18
17
  buffer[bundle_path_for(path)] = document.render
19
- @failures.concat(document.failures)
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))
20
25
  end
21
26
  end
22
27
 
@@ -24,18 +29,28 @@ module RSpecDocumentation
24
29
  Util.bundle_dir.rmtree if Util.bundle_dir.directory?
25
30
  Util.bundle_dir.mkpath
26
31
 
27
- @buffer.each do |path, content|
32
+ buffer.each do |path, content|
28
33
  path.dirname.mkpath
29
34
  Util.bundle_path(path).write(content)
30
35
  end
36
+ write_index unless buffer.empty?
37
+ end
38
+
39
+ def examples_count
40
+ documents.map(&:specs).flatten.size
31
41
  end
32
42
 
33
43
  private
34
44
 
35
- attr_reader :page_paths, :buffer
45
+ attr_reader :buffer
46
+
47
+ def write_index
48
+ _path, content = buffer.first
49
+ Util.bundle_index_path.write(content)
50
+ end
36
51
 
37
- def page_tree
38
- PageTree.new(page_paths: page_paths)
52
+ def page_tree(path:)
53
+ PageTree.new(page_paths: page_paths, current_path: path)
39
54
  end
40
55
 
41
56
  def bundle_path_for(path)
@@ -3,8 +3,9 @@
3
3
  module RSpecDocumentation
4
4
  # A hierarchical structure of all pages in the documentation tree. Used for rendering a navigation section.
5
5
  class PageTree
6
- def initialize(page_paths:)
6
+ def initialize(page_paths:, current_path:)
7
7
  @page_paths = page_paths
8
+ @current_path = current_path
8
9
  @structure = {}
9
10
  @nodes = []
10
11
  end
@@ -19,7 +20,7 @@ module RSpecDocumentation
19
20
 
20
21
  private
21
22
 
22
- attr_reader :page_paths, :structure, :nodes
23
+ attr_reader :page_paths, :structure, :nodes, :current_path
23
24
 
24
25
  def tree
25
26
  @tree ||= begin
@@ -29,17 +30,24 @@ module RSpecDocumentation
29
30
  end
30
31
 
31
32
  def build_nodes(root:, path:)
32
- root[:children].sort.map do |child|
33
+ root[:children].sort.each do |child|
33
34
  node = page_tree_node(path: path, child: child)
34
35
  next nil if node.nil?
35
36
 
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>')
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)
40
42
  end
41
43
  end
42
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
+
43
51
  def build_tree(branch: structure, depth: 0)
44
52
  normalized_paths.each do |path|
45
53
  first, second, *rest = path_segments(path: path, depth: depth)
@@ -67,7 +75,7 @@ module RSpecDocumentation
67
75
  end
68
76
 
69
77
  def page_tree_node(path:, child:)
70
- PageTreeElement.new(path: path, child: child).node
78
+ PageTreeElement.new(path: path, child: child, current_path: current_path).node
71
79
  end
72
80
  end
73
81
  end
@@ -5,25 +5,26 @@ module RSpecDocumentation
5
5
  # document (or just a text entry if no document exists, i.e. if it is a directory without an
6
6
  # index file).
7
7
  class PageTreeElement
8
- def initialize(path:, child:)
8
+ def initialize(path:, child:, current_path:)
9
9
  @path = path
10
10
  @child = child
11
+ @current_path = current_path
11
12
  @nodes = []
13
+ raise MissingFileError, "Missing file: #{entry_path.relative_path_from(Util.base_dir)}" unless entry?
12
14
  end
13
15
 
14
16
  def node
15
17
  return nil if entry_and_directory?
16
18
 
17
- nodes.push('<li>')
18
- nodes.push(link) if entry?
19
- nodes.push(bullet) unless entry?
19
+ nodes.push(li_open)
20
+ nodes.push(link)
20
21
  nodes.push('</li>')
21
22
  nodes
22
23
  end
23
24
 
24
25
  private
25
26
 
26
- attr_reader :path, :child, :nodes
27
+ attr_reader :path, :child, :current_path, :nodes
27
28
 
28
29
  def entry_and_directory?
29
30
  return false unless path.join(child).sub_ext('.md').file?
@@ -33,22 +34,43 @@ module RSpecDocumentation
33
34
  true
34
35
  end
35
36
 
37
+ def entry_path
38
+ path.join(child).sub_ext('.md')
39
+ end
40
+
36
41
  def entry?
37
- path.join(child).sub_ext('.md').file?
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}'>"
38
48
  end
39
49
 
40
50
  def link
41
- "<a href='#{href}'>#{title}</a>"
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)
42
60
  end
43
61
 
44
- def bullet
45
- "<b>#{title}</b>"
62
+ def active_class
63
+ active? ? 'class="active"' : nil
46
64
  end
47
65
 
48
66
  def href
49
67
  Util.href(path.join(child))
50
68
  end
51
69
 
70
+ def active?
71
+ Util.href(current_path) == href
72
+ end
73
+
52
74
  def title
53
75
  Util.label(child)
54
76
  end
@@ -5,8 +5,9 @@ module RSpecDocumentation
5
5
  class ParsedDocument
6
6
  attr_reader :failures
7
7
 
8
- def initialize(document)
9
- @document = Kramdown::Document.new(document, input: 'GFM')
8
+ def initialize(document, path:)
9
+ @document = Kramdown::Document.new(document, input: 'GFM', syntax_highlighter: 'rouge')
10
+ @path = path
10
11
  @failures = []
11
12
  end
12
13
 
@@ -29,7 +30,7 @@ module RSpecDocumentation
29
30
 
30
31
  private
31
32
 
32
- attr_reader :document
33
+ attr_reader :document, :path
33
34
 
34
35
  def recursive_specs(element: document.root)
35
36
  element.children.each.with_index.map do |child, index|
@@ -53,6 +54,7 @@ module RSpecDocumentation
53
54
  format: element.options[:lang].partition(':').last,
54
55
  parent: parent,
55
56
  index: index,
57
+ path: path,
56
58
  location: element.options[:location]
57
59
  )
58
60
  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
@@ -4,34 +4,49 @@ module RSpecDocumentation
4
4
  module RSpec
5
5
  # Stores information about a failed RSpec example. Thin wrapper around `RSpec::Failure`.
6
6
  class Failure
7
+ include Paintbrush
8
+
9
+ attr_reader :spec
10
+
7
11
  def initialize(cause:, spec:)
8
12
  @cause = cause
9
13
  @spec = spec
10
14
  end
11
15
 
12
16
  def message
13
- "\n#{formatted_header}\n\n#{formatted_source}\n#{formatted_cause}\n\n"
17
+ "\n#{formatted_header}\n\n#{formatted_source}\n\n#{formatted_cause}\n\n"
14
18
  end
15
19
 
16
20
  private
17
21
 
18
- attr_reader :cause, :spec
22
+ attr_reader :cause
19
23
 
20
24
  def formatted_header
21
- "\e[1;37m#{indented('Failure in example')}:\e[0m"
25
+ paintbrush { cyan indented("# #{path}:#{spec.location}") }
26
+ end
27
+
28
+ def path
29
+ spec.path.relative_path_from(Pathname.new(Dir.pwd))
22
30
  end
23
31
 
24
32
  def formatted_source
25
- "\e[1;34m#{indented(spec.source)}\e[0m"
33
+ paintbrush { white indented(spec.source) }
26
34
  end
27
35
 
28
36
  def formatted_cause
29
- "\e[31m#{indented(cause.message)}\e[0m"
37
+ paintbrush { red indented(without_anonymous_group_text(cause.message)) }
30
38
  end
31
39
 
32
40
  def indented(text)
33
41
  text.split("\n").map { |line| " #{line}" }.join("\n")
34
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
35
50
  end
36
51
  end
37
52
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'rspec/example_group'
4
- require_relative 'rspec/reporter'
5
3
  require_relative 'rspec/failure'
6
4
 
7
5
  module RSpecDocumentation
@@ -3,20 +3,30 @@
3
3
  module RSpecDocumentation
4
4
  # Executes specs from a Markdown code block and provides the outcome in the appropriate format.
5
5
  class Spec
6
- attr_reader :parent, :location, :index, :format
6
+ attr_reader :parent, :location, :index, :format, :path
7
7
 
8
- def initialize(spec:, format:, parent:, location:, index:)
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
9
15
  @spec = spec
10
16
  @format = format.empty? ? :plaintext : format.to_sym
11
17
  @parent = parent
12
18
  @location = location
19
+ @path = path
13
20
  @index = index
14
21
  @failures = []
15
- @reporter = RSpec::Reporter.new
16
22
  end
17
23
 
18
- def described_object
19
- @described_object ||= reporter.described_object
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
20
30
  end
21
31
 
22
32
  def source
@@ -27,15 +37,33 @@ module RSpecDocumentation
27
37
  failures.first
28
38
  end
29
39
 
40
+ def reporter
41
+ @reporter ||= ::RSpec::Core::Reporter.new(::RSpec::Core::Configuration.new)
42
+ end
43
+
30
44
  def run
45
+ self.class.subjects = []
31
46
  RSpec.with_failure_notifier(failure_notifier) do
32
- example_group.run(reporter)
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
33
53
  end
34
54
  end
35
55
 
36
56
  private
37
57
 
38
- attr_reader :spec, :failures, :reporter
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
39
67
 
40
68
  def examples
41
69
  @examples ||= example_group.examples
@@ -45,18 +73,36 @@ module RSpecDocumentation
45
73
  # rubocop:disable Style/DocumentDynamicEvalDefinition, Security/Eval
46
74
  @example_group ||= binding.eval(
47
75
  <<-SPEC, __FILE__, __LINE__.to_i
48
- RSpec::ExampleGroup.describe do
49
- #{spec}
76
+ ::RSpec::Core::ExampleGroup.describe do
77
+ after { RSpecDocumentation::Spec.subjects << subject }
78
+ include_context '__rspec_documentation' do
79
+ #{spec}
80
+ end
50
81
  end
51
82
  SPEC
52
83
  )
53
84
  # rubocop:enable Style/DocumentDynamicEvalDefinition, Security/Eval
54
85
  end
55
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
+
56
104
  def failure_notifier
57
- proc do |failure|
58
- failures << RSpec::Failure.new(cause: failure, spec: self)
59
- end
105
+ proc { |failure| notify_failure(failure) }
60
106
  end
61
107
  end
62
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
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RSpecDocumentation
4
+ # Provides a summary of the test run including details of any failures.
5
+ class Summary
6
+ include Paintbrush
7
+
8
+ def initialize(page_collection:, pwd:, started_at:)
9
+ @page_collection = page_collection
10
+ @pwd = pwd
11
+ @duration = Time.now.utc - started_at
12
+ end
13
+
14
+ def flush
15
+ if page_collection.failures.empty?
16
+ print_success_summary
17
+ else
18
+ print_failure_summary
19
+ end
20
+
21
+ print_duration_summary
22
+
23
+ nil
24
+ end
25
+
26
+ private
27
+
28
+ attr_reader :page_collection, :pwd, :duration
29
+
30
+ def print_success_summary
31
+ warn(successful_examples_summary)
32
+ warn(paintbrush { blue("\n Created #{green(page_collection.page_paths.size)} pages.\n") })
33
+ warn(paintbrush { white(" View your documentation here: #{blue(Util.bundle_index_path)}\n") })
34
+ end
35
+
36
+ def print_failure_summary
37
+ page_collection.failures.each do |failure|
38
+ warn(failure.message)
39
+ end
40
+
41
+ warn(failure_summary)
42
+ warn("\nFailed examples:\n\n")
43
+ warn(failed_examples_summary)
44
+ $stderr.puts
45
+ end
46
+
47
+ def print_duration_summary
48
+ warn(duration_summary)
49
+ end
50
+
51
+ def failure_summary
52
+ examples = pluralize(page_collection.examples_count, 'example')
53
+ failures = pluralize(page_collection.failures.size, 'failure')
54
+ paintbrush do
55
+ red "#{page_collection.examples_count} #{examples}, #{page_collection.failures.size} #{failures}."
56
+ end
57
+ end
58
+
59
+ def successful_examples_summary
60
+ examples = pluralize(page_collection.examples_count, 'example')
61
+ failures = pluralize(page_collection.failures.size, 'failure')
62
+ paintbrush do
63
+ green "\n #{cyan page_collection.examples_count} #{examples}, " \
64
+ "#{cyan page_collection.failures.size} #{failures}."
65
+ end
66
+ end
67
+
68
+ def pluralize(count, noun)
69
+ count.zero? || count > 1 ? "#{noun}s" : noun
70
+ end
71
+
72
+ def failed_examples_summary
73
+ page_collection.failures.map(&:spec).map do |spec|
74
+ paintbrush { red " #{spec.path.relative_path_from(pwd)}:#{spec.location}" }
75
+ end.join("\n")
76
+ end
77
+
78
+ def duration_summary
79
+ formatted_spec_duration = format('%.2g', RSpecDocumentation::Spec.durations.sum)
80
+ formatted_total_duration = format('%.2g', duration)
81
+ paintbrush do
82
+ cyan " Total build time: #{white formatted_total_duration} seconds, " \
83
+ "examples executed in #{white formatted_spec_duration} seconds.\n"
84
+ end
85
+ end
86
+ end
87
+ end
@@ -10,6 +10,10 @@ module RSpecDocumentation
10
10
  bundle_dir.join(*relative_path.split.map { |segment| normalized_filename(segment) }).sub_ext('.html')
11
11
  end
12
12
 
13
+ def self.bundle_index_path
14
+ bundle_dir.join('index.html')
15
+ end
16
+
13
17
  def self.base_dir
14
18
  root_dir.join('rspec-documentation', 'pages')
15
19
  end
@@ -28,6 +32,10 @@ module RSpecDocumentation
28
32
  Pathname.new(path).basename.sub_ext('').sub(ORDERING_PREFIX_REGEXP, '')
29
33
  end
30
34
 
35
+ def self.path_id(path)
36
+ "path-id-#{Digest::SHA256.hexdigest(bundle_path(path).relative_path_from(bundle_dir).to_s)}"
37
+ end
38
+
31
39
  def self.href(path)
32
40
  relative_path = Pathname.new(path).relative_path_from(base_dir)
33
41
  url_root.join(*relative_path.split.map { |segment| normalized_filename(segment) }).sub_ext('.html')
@@ -39,6 +47,10 @@ module RSpecDocumentation
39
47
  Pathname.new(ENV.fetch('RSPEC_DOCUMENTATION_URL_ROOT'))
40
48
  end
41
49
 
50
+ def self.assets_root
51
+ url_root.join('assets')
52
+ end
53
+
42
54
  def self.normalized_filename(path)
43
55
  path.to_s.gsub(' ', '-').downcase.sub(ORDERING_PREFIX_REGEXP, '')
44
56
  end
@@ -12,17 +12,26 @@ require 'rake'
12
12
 
13
13
  require 'pathname'
14
14
  require 'securerandom'
15
+ require 'digest'
16
+ require 'json'
17
+ require 'yaml'
18
+ require 'date'
19
+ require 'time'
15
20
 
16
21
  require_relative 'rspec_documentation/rspec'
17
22
  require_relative 'rspec_documentation/util'
18
- require_relative 'rspec_documentation/context'
23
+ require_relative 'rspec_documentation/summary'
24
+ require_relative 'rspec_documentation/project_initialization'
19
25
  require_relative 'rspec_documentation/configuration'
26
+ require_relative 'rspec_documentation/stylesheet_bundle'
27
+ require_relative 'rspec_documentation/javascript_bundle'
20
28
  require_relative 'rspec_documentation/document'
21
29
  require_relative 'rspec_documentation/parsed_document'
22
30
  require_relative 'rspec_documentation/html_element'
23
- require_relative 'rspec_documentation/ansi_html'
31
+ require_relative 'rspec_documentation/formatters'
24
32
  require_relative 'rspec_documentation/spec'
25
33
  require_relative 'rspec_documentation/markdown_renderer'
34
+ require_relative 'rspec_documentation/documentation'
26
35
  require_relative 'rspec_documentation/page_collection'
27
36
  require_relative 'rspec_documentation/page_tree'
28
37
  require_relative 'rspec_documentation/page_tree_element'
@@ -30,13 +39,17 @@ require_relative 'rspec_documentation/page_tree_element'
30
39
  # Internal module used by RSpec::Documentation to run examples and write output into an HTML document.
31
40
  module RSpecDocumentation
32
41
  class Error < StandardError; end
42
+ class MissingFileError < Error; end
33
43
 
34
44
  def self.root
35
45
  Pathname.new(File.dirname(__dir__))
36
46
  end
37
47
 
38
48
  def self.template(name, format = :html)
39
- ERB.new(root.join('lib/templates', "#{name}.#{format}.erb").read)
49
+ ERB.new(
50
+ root.join('lib/templates', "#{name}.#{format}.erb").read,
51
+ eoutvar: "@eout#{SecureRandom.hex(16)}"
52
+ )
40
53
  end
41
54
 
42
55
  def self.theme(name)
@@ -0,0 +1,35 @@
1
+ # Introduction
2
+
3
+ Welcome to _RSpec Documentation_! This is your first documentation page.
4
+
5
+ Here's an example spec:
6
+
7
+ ```rspec
8
+ subject { true }
9
+
10
+ it { is_expected.to be true }
11
+ ```
12
+
13
+ And here's another:
14
+
15
+ ```rspec:html
16
+ subject { '<h1>Title</h1><button class="btn btn-primary">Button</button>' }
17
+
18
+ it { is_expected.to include 'Button' }
19
+ ```
20
+
21
+ And here's one more:
22
+
23
+ ```rspec:ansi
24
+ subject { "\e[35mSome purple text\e[0m" }
25
+
26
+ it { is_expected.to include 'purple text' }
27
+ ```
28
+
29
+ See the [documentation](https://docs.bob.frl/rspec-documentation) for more information on adding more content to your documentation.
30
+
31
+ Run the `rspec-documentation` command any time you want to generate your documentation bundle:
32
+
33
+ ```console
34
+ $ rspec-documentation
35
+ ```
@@ -0,0 +1,3 @@
1
+ # Usage
2
+
3
+ Describe usage of your software here. Add _Markdown_ and _RSpec_ examples as needed.