rspec-documentation 0.0.2 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
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 +5 -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 +17 -5
  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.