rspec-rfc-helper 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 77bf0b42fa2165a6521a2929936cb1a1b58ab359cb19bbe19681ff7e2767d75f
4
+ data.tar.gz: 42e537f5b31324440b1cf98ac49a23ae1aea884cfe734e660943441ee88acafa
5
+ SHA512:
6
+ metadata.gz: ca6a4cd4d10c244b4d2ab310d17a4e51f831c3c2e3bb97abd984996913f19d76621de9e93bde197dfc005616a73be1684351d4c1867f6cf958502d70f4317400
7
+ data.tar.gz: de7b0bc373efa3875b961354733c8ce5a9fc2405447d7d98d61c336fc7380bbdf8215eb8c7228c1b7a4076b2d77f80caa7c2811b6c4486695767deb4fdf48ca2
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,32 @@
1
+ ---
2
+ require:
3
+ - rubocop-performance
4
+ - rubocop-rake
5
+ - rubocop-rspec
6
+
7
+ AllCops:
8
+ # Do not lint generated files
9
+ Exclude:
10
+ - coverage/**/*
11
+ - vendor/**/*
12
+ TargetRubyVersion: 3.1.2
13
+ NewCops: enable
14
+
15
+ Layout/HashAlignment:
16
+ EnforcedColonStyle: table
17
+ EnforcedHashRocketStyle: table
18
+
19
+ Layout/LineLength:
20
+ Max: 130
21
+
22
+ Style/HashSyntax:
23
+ EnforcedShorthandSyntax: never
24
+
25
+ Style/SymbolArray:
26
+ EnforcedStyle: brackets
27
+
28
+ Style/TrailingCommaInArrayLiteral:
29
+ EnforcedStyleForMultiline: comma
30
+
31
+ Style/TrailingCommaInHashLiteral:
32
+ EnforcedStyleForMultiline: comma
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.1.2
data/CHANGELOG.md ADDED
@@ -0,0 +1,34 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ <!--
9
+ Quick remainder of the possible sections:
10
+ -----------------------------------------
11
+ ### Added
12
+ for new features.
13
+ ### Changed
14
+ for changes in existing functionality.
15
+ ### Deprecated
16
+ for soon-to-be removed features.
17
+ ### Removed
18
+ for now removed features.
19
+ ### Fixed
20
+ for any bug fixes.
21
+ ### Security
22
+ in case of vulnerabilities.
23
+ ### Maintenance
24
+ in case of rework, dependencies change
25
+
26
+ Please, keep them in this order when updating.
27
+ -->
28
+
29
+ ## [Unreleased]
30
+
31
+ ## [0.1.0] - 2023-11-07 - Initial release
32
+
33
+ Initial release
34
+
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2020 Experiments Labs / Experimentations
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,175 @@
1
+ # RSpec::RfcHelper
2
+
3
+ RSpec RFC Helper is a RSpec module to help tracking implementation of big specifications, when having only comments in
4
+ code or tests becomes too tedious to maintain and follow.
5
+
6
+ ## Installation
7
+
8
+ Add the gem to your Gemfile:
9
+
10
+ ```rb
11
+ gem 'rspec-rfc-helper'
12
+ ```
13
+
14
+ ## Usage
15
+
16
+ 1. Write the specs: read the RFC/specifications that needs to be implemented and extract everything that has to be implemented. This is tedious.
17
+ 2. Use the gem in Rspec
18
+ 3. Sprinkle your tests to reference the specs
19
+ 4. Run the tests
20
+ 5. Open the report
21
+
22
+ ### Writing specs
23
+
24
+ #### ...in a YAML file
25
+ Specs in a YAML file can be loaded easily when integrated in RSpec; the format is simple:
26
+
27
+ ```yaml
28
+ # Optional, the specification/RFC name
29
+ name:
30
+ # Optional, the URL to the RFC/specification
31
+ url:
32
+ # Here we are
33
+ specs:
34
+ # You should at least have one section in the file
35
+ #
36
+ # Required. Section/chapter... It's converted to a string when imported
37
+ - section: 1
38
+ # Required. Unique section identifier. IDs of the specs in this section will be prefixed by it. Converted to symbol
39
+ # so only the character class used for symbols should be used.
40
+ id:
41
+ # Optional: URL to the section
42
+ url:
43
+ specs:
44
+ # Optional. A spec without an ID will appear with an "unknown" status in the report. It allows you to fill the
45
+ # specs list without thinking right now how you will identify it.
46
+ # Spec IDS must be unique across all the specs, but are automatically prefixed with the section's ID, which allows
47
+ # definition of two specs with the same ID over different sections.
48
+ - id:
49
+ # Required. Text must be unique across specs. Imperative verbs have to be bracketed, so you can use the same paragraph
50
+ # in multiple specs, targeting something different without losing context.
51
+ text: It [[MUST]] work
52
+ ```
53
+
54
+ For more example, there is a fixture in `spec/fixtures/rfc.yaml`, and the example spec file in `spec/rfc.yaml`.
55
+
56
+ #### ...programmatically
57
+
58
+ Declaring specs programmatically can be interesting if you manage somehow to process the original specification
59
+ automatically.
60
+
61
+ ```rb
62
+ # Create an instance of the Specs class: it holds the specs. "name" and "url" are optional
63
+ specs = RSpec::RfcHelper::Specs.new name: 'The easy thing RFC', url: 'https://somewhere'
64
+
65
+ # Create a section
66
+ spec.add_section number: '1.1', title: 'Implementation', id: :implementation
67
+
68
+ # Add a spec:
69
+ # ID is optional. A spec without an ID will appear with an "unknown" status in the report. It allows you to fill the
70
+ # specs list without thinking right now how you will identify it.
71
+ # Spec IDS must be unique across all the specs, but are automatically prefixed with the section's ID, which allows
72
+ # definition of two specs with the same ID over different sections.
73
+ #
74
+ # Here, spec ID will be :implementation__do_something
75
+ spec.add section: '1.1', text: 'It [[MUST]] do something', id: :do_something
76
+
77
+ # For long texts, use heredocs:
78
+ spec.add section: '1.1', text: <<~TXT, id: :do_something
79
+ Some long text, but the context is important to understand
80
+ what you [[MUST]] implement, how you SHOULD do it
81
+ and what you MAY do if you feel like it
82
+ TXT
83
+ ```
84
+
85
+ ### Usage in RSpec
86
+
87
+ #### ...with the spec file: use the module
88
+
89
+ ```rb
90
+ # spec_helper.rb
91
+
92
+ require 'rspec-rfc-helper'
93
+
94
+ RSpec.configure do |config|
95
+ config.before(:suite) do
96
+ RSpec::RfcHelper.start_from_file 'path/to/your/spec.yaml'
97
+ end
98
+
99
+ config.after do |example|
100
+ RSpec::RfcHelper.add_example example
101
+ end
102
+
103
+ config.after( :suite) do
104
+ RSpec::RfcHelper.save_markdown_report 'path/to/report.md'
105
+ end
106
+ end
107
+ ```
108
+
109
+ #### ...programmatically: use the classes
110
+
111
+ ```rb
112
+ # spec_helper.rb
113
+
114
+ require 'rspec-rfc-helper'
115
+
116
+ RSpec.configure do |config|
117
+ # Putting all the specs in the spec_helper.rb is not a good idea, but as you go programmatically, i'll let you find
118
+ # a way to organize yourself better.
119
+ # The main point here is to use the same instance for the definitions and the usages in the RSpec hooks
120
+ rfc_helper = RSpec::RfcHelper::Spec.new name: 'Plumbus management', url: 'https://somewhere'
121
+ rfc_helper.add_section #...
122
+ rfc_helper.add #...
123
+ # Alternatively, you can still load a file
124
+ rfc_helper = RSpec::RfcHelper::Spec.new_from_file 'path_to_file'
125
+
126
+ config.after do |example|
127
+ rfc_helper.add_example example
128
+ end
129
+
130
+ config.after( :suite) do
131
+ rfc_helper.save_markdown_report 'path/to/report.md'
132
+ end
133
+ end
134
+ ```
135
+
136
+ ### Reference the specs in your suite
137
+
138
+ RFC Helper uses the RSpec tagging system to track and assign examples to _specs_.
139
+
140
+ ```rb
141
+ RSpec.describe 'Something' do
142
+ # references spec "spec_id" in section "section1"
143
+ it 'works', rfc: :section1__spec_id do
144
+ # ...
145
+ end
146
+
147
+ # references spec "spec_id" in section "section1", and "other_spec_id" in "other_section"
148
+ it 'works', rfc: [:section1__spec_id, :other_section__other_spec_id] do
149
+ # ...
150
+ end
151
+ end
152
+ ```
153
+
154
+ ## Development
155
+
156
+ After checking out the repo, run `bundle install` to install dependencies. Then, run `bundle exec rspec` to run the tests.
157
+ You can also run `bin/console` for an interactive prompt that will allow you to experiment.
158
+
159
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the
160
+ version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version,
161
+ push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
162
+
163
+ ### Tools
164
+
165
+ Well, we use RSpec for testing.
166
+
167
+ Also, we use Rubocop for code style.
168
+
169
+ ## Contributing
170
+
171
+ All contributions, ideas and discussions are welcome. Feel free to open issues and feature requests on the
172
+ [bug tracker](https://gitlab.com/experimentslabs/rspec-rfc-helper/-/issues).
173
+
174
+ You also can join the ExperimentsLabs [Matrix chatroom](https://matrix.to/#/!qpmxpfSIevwoRUgrxm:matrix.org?via=matrix.org)
175
+ to discuss of the project.
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: [:spec, :standard]
@@ -0,0 +1,178 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RSpec
4
+ module RfcHelper
5
+ ##
6
+ # Markdown renderer for specs
7
+ class MarkdownRenderer
8
+ STATUS_COLORS = {
9
+ # Status from Spec class
10
+ failing: 'red',
11
+ pass: 'green',
12
+ has_pending: 'orange',
13
+ unknown: 'gray',
14
+
15
+ # Statuses from RSpec::Core::Example
16
+ 'failed' => 'red',
17
+ 'pending' => 'orange',
18
+ 'passed' => 'green',
19
+ }.freeze
20
+
21
+ ##
22
+ # @param specs [RSpec::RfcHelper::Specs]
23
+ def initialize(specs)
24
+ @specs = specs
25
+ end
26
+
27
+ ##
28
+ # Generates a report in Markdown format.
29
+ #
30
+ # @return [String]
31
+ def render
32
+ main_table_lines = []
33
+ detailed_spec_report_blocks = []
34
+
35
+ @specs.each do |spec|
36
+ main_table_lines << main_table_row(spec)
37
+ detailed_spec_report_blocks << detailed_spec_report_block(spec)
38
+ end
39
+
40
+ <<~MD.chomp
41
+ #{header}
42
+
43
+ #{main_table(main_table_lines)}
44
+
45
+ #{detailed_spec_report_blocks.join("\n")}
46
+ MD
47
+ end
48
+
49
+ ##
50
+ # Generates the report header
51
+ #
52
+ # @return [String]
53
+ def header
54
+ title = 'Compliance status'
55
+ title = "#{title}: #{@specs.name}" if @specs.name
56
+ url = @specs.specs_url ? "[#{@specs.specs_url}](#{@specs.specs_url})" : '~'
57
+
58
+ <<~MD.chomp
59
+ # #{title}
60
+
61
+ Report generated on #{Time.now}
62
+
63
+ Source specification: #{url}
64
+ MD
65
+ end
66
+
67
+ ##
68
+ # Generates a colored HTML string to represent the spec status
69
+ #
70
+ # @param value [String, Symbol] Status
71
+ # @param string [String?] Custom text replacement
72
+ #
73
+ # @return [String]
74
+ def status(value, string = nil)
75
+ "<span style='color: #{STATUS_COLORS[value]}'>#{string || value}</span>"
76
+ end
77
+
78
+ ##
79
+ # Makes some replacement in a text
80
+ #
81
+ # @param string [String]
82
+ #
83
+ # @return [String]
84
+ def paragraph(string)
85
+ string.gsub("\n", ' <br> ')
86
+ .gsub(/\[\[([^\]]*)\]\]/, '<mark>\1</mark>')
87
+ end
88
+
89
+ ##
90
+ # Generates a spec report block (header and examples table)
91
+ #
92
+ # @param spec [RSpec::RfcHelper::Spec]
93
+ #
94
+ # @return [String]
95
+ def detailed_spec_report_block(spec)
96
+ <<~MD.chomp
97
+ #{detailed_spec_report_header(spec)}
98
+
99
+ #{detailed_spec_report_examples(spec)}
100
+ MD
101
+ end
102
+
103
+ ##
104
+ # Generates header for a spec report
105
+ #
106
+ # @param spec [RSpec::RfcHelper::Spec]
107
+ #
108
+ # @return [String]
109
+ def detailed_spec_report_header(spec) # rubocop:disable Metrics/AbcSize
110
+ section = "#{spec.section} - #{@specs.sections[spec.section][:title]}"
111
+ spec_link = if @specs.sections[spec.section][:url]
112
+ "[#{section}](#{@specs.sections[spec.section][:url]})"
113
+ else
114
+ section
115
+ end
116
+ <<~MD.chomp
117
+ ## #{status(spec.status, '○')} `#{spec.verb.upcase}` #{spec.id}
118
+
119
+ **Specification:** #{spec_link}
120
+
121
+ > #{paragraph(spec.text)}
122
+ MD
123
+ end
124
+
125
+ ##
126
+ # Generates the example table of a spec report
127
+ #
128
+ # @param spec [RSpec::RfcHelper::Spec]
129
+ #
130
+ # @return [String]
131
+ def detailed_spec_report_examples(spec) # rubocop:disable Metrics/MethodLength
132
+ lines = []
133
+ spec.examples.each do |example|
134
+ meta = example.metadata
135
+ lines << "| #{status(meta[:last_run_status])} | `#{meta[:type]}`: `#{meta[:location]}` |"
136
+ end
137
+
138
+ return "**No related examples**\n" if lines.empty?
139
+
140
+ <<~MD.chomp
141
+ **Examples:**
142
+
143
+ | Status | Reference |
144
+ |:------:|-----------|
145
+ #{lines.join("\n")}
146
+
147
+ MD
148
+ end
149
+
150
+ ##
151
+ # Generates a row for the main table
152
+ #
153
+ # @param spec [RSpec::RfcHelper::Spec]
154
+ #
155
+ # @return [String]
156
+ def main_table_row(spec)
157
+ id = spec.id ? "`#{spec.id}`" : ''
158
+ section = @specs.sections[spec.section]
159
+ section_link = section[:url] ? "[#{spec.section}](#{section[:url]})" : spec.section
160
+ "| #{status(spec.status)} | #{id} | #{section_link} | #{spec.verb} | #{paragraph(spec.text)} |"
161
+ end
162
+
163
+ ##
164
+ # Generates the main table
165
+ #
166
+ # @param lines [Array<String>] Table rows
167
+ #
168
+ # @return [String]
169
+ def main_table(lines)
170
+ <<~MD.chomp
171
+ | status | id | section | verb | text |
172
+ |-------:|---:|---------|------|------|
173
+ #{lines.join("\n")}
174
+ MD
175
+ end
176
+ end
177
+ end
178
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RSpec
4
+ module RfcHelper
5
+ ##
6
+ # Represents one spec
7
+ class Spec
8
+ attr_reader :id, :section, :verb, :text, :examples
9
+
10
+ ##
11
+ # Initializes a new Spec
12
+ #
13
+ # @param section [String] Section number
14
+ # @param text [String] Paragraph from the RFC.
15
+ # @param id [Symbol, NilClass] Unique identifier
16
+ #
17
+ # +text+ should have one _verb_ surrounded by brackets, representing what this Spec expects:
18
+ #
19
+ # bad: It [[MUST]] work with a missing user but [[MAY]] log a warning
20
+ #
21
+ # Use two Specs instead:
22
+ # It [[MUST]] work with a missing user but MAY log a warning
23
+ # It MUST work with a missing user but [[MAY]] log a warning
24
+ #
25
+ # When nothing is highlighted, spec will appear as a "note".
26
+ def initialize(section:, text:, id: nil)
27
+ raise "Missing text ({section: #{section}, text: #{text}, id: #{id})" unless text.is_a?(String) && !text.empty?
28
+ raise "Missing section ({section: #{section}, text: #{text}, id: #{id})" unless section.is_a?(String) && !section.empty?
29
+
30
+ @id = id
31
+ @section = section.to_s
32
+ @text = text
33
+ @examples = []
34
+ set_verb
35
+ end
36
+
37
+ ##
38
+ # Determines status from RSpec examples
39
+ #
40
+ # @return [:unknown|:pass|:has_pending|:failing] Spec state from all examples
41
+ #
42
+ # Statuses:
43
+ # unknown: No test currently covers this spec
44
+ # failing: At least one example failed
45
+ # has_pending: Some examples are pending, other passed
46
+ # pass: All examples passed
47
+ def status
48
+ value = :unknown
49
+ @examples.each do |example|
50
+ return :failing if example.metadata[:last_run_status] == 'failed'
51
+ return :has_pending if example.metadata[:last_run_status] == 'pending'
52
+
53
+ value = :pass
54
+ end
55
+
56
+ value
57
+ end
58
+
59
+ ##
60
+ # Adds an RSpec example
61
+ #
62
+ # @param example [RSpec::Core::Example] RSpec example
63
+ def add_example(example)
64
+ @examples << example
65
+ end
66
+
67
+ private
68
+
69
+ ##
70
+ # Determines the verb of interest in the given text; falls back to +:note+
71
+ #
72
+ # The key words MAY, MUST, MUST NOT, SHOULD, and SHOULD NOT are to be interpreted as described in RFC2119:
73
+ # @see https://www.w3.org/TR/activitypub/#bib-RFC2119
74
+ def set_verb
75
+ occurrences = @text.scan(/\[\[((?:MUST|SHOULD)(?: NOT)?|MAY)\]\]/)
76
+ case occurrences.size
77
+ when 0
78
+ @verb = :note
79
+ when 1
80
+ @verb = occurrences.first.first.downcase.tr(' ', '_').to_sym
81
+ else
82
+ raise 'Too much verbs in paragraph' if occurrences.size > 1
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,138 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+
5
+ require_relative 'spec'
6
+
7
+ module RSpec
8
+ module RfcHelper
9
+ ##
10
+ # Spec collection
11
+ class Specs
12
+ attr_reader :name, :specs_url, :sections
13
+
14
+ ##
15
+ # Instantiates a Spec collection from a YAML file.
16
+ #
17
+ # Note: String keys are expected.
18
+ #
19
+ # @param file [String] Path to the specs file
20
+ #
21
+ # @return [RSpec::RfcHelper::Specs]
22
+ def self.new_from_file(file) # rubocop:disable Metrics/AbcSize
23
+ specs = YAML.load_file(file)
24
+
25
+ instance = new name: specs['name'], specs_url: specs['url']
26
+
27
+ specs['specs'].each do |section|
28
+ section_number = section['section'].to_s
29
+
30
+ instance.add_section id: section['id'].to_sym, number: section_number, title: section['title'], url: section['url']
31
+
32
+ section['specs'].each do |spec|
33
+ instance.add section: section_number, text: spec['text'], id: spec['id']&.to_sym
34
+ end
35
+ end
36
+
37
+ instance
38
+ end
39
+
40
+ ##
41
+ # Instantiate a new list of specs
42
+ #
43
+ # @param name [String?] Specification name
44
+ # @param specs_url [String?] URL to the full specification
45
+ def initialize(name: nil, specs_url: nil)
46
+ @name = name
47
+ @specs_url = specs_url
48
+ @sections = {}
49
+ @specs = []
50
+ end
51
+
52
+ ##
53
+ # Adds a spec section
54
+ #
55
+ # @param number [String] Section number
56
+ # @param title [String] Section title
57
+ # @param id [Symbol] Unique identifier
58
+ # @param url [String?] URL to this section
59
+ def add_section(number:, title:, id:, url: nil)
60
+ if @sections.key?(number) || @sections.find { |_number, section| section[:id] == id }
61
+ raise "Section number #{number} already exists"
62
+ end
63
+
64
+ @sections[number] = { title: title, id: id, url: url }
65
+ end
66
+
67
+ ##
68
+ # Finds an example by id
69
+ #
70
+ # @param id [Symbol] Spec identifier
71
+ #
72
+ # @return [RSpec::RfcHelper::Spec]
73
+ def find(id)
74
+ @specs.find { |spec| spec.id == id } || raise("Spec not found with id #{id}")
75
+ end
76
+
77
+ ##
78
+ # Adds a spec to the list
79
+ #
80
+ # It will prefix the spec ID with relevant section ID
81
+ #
82
+ # @param section [String] Section number
83
+ # @param text [String] Content
84
+ # @param id [Symbol?] Unique section identifier
85
+ def add(section:, text:, id: nil)
86
+ if id.is_a? Symbol
87
+ id = "#{section_id(section)}__#{id}".to_sym
88
+ raise "Duplicated id #{id}" if ids.include? id
89
+ end
90
+
91
+ @specs << Spec.new(section: section, text: text, id: id)
92
+ end
93
+
94
+ ##
95
+ # Adds an RSpec example to all the relevant specs
96
+ #
97
+ # @param example [RSpec::Core::Example]
98
+ def add_example(example)
99
+ ids = example.metadata[:rfc]
100
+ return if ids.nil? || ids.empty?
101
+
102
+ ids = [ids] if ids.is_a? Symbol
103
+ ids.each do |id|
104
+ find(id).add_example example
105
+ end
106
+ end
107
+
108
+ def ids
109
+ @specs.map(&:id)
110
+ end
111
+
112
+ ##
113
+ # Delegation
114
+ def each(&)
115
+ @specs.each(&)
116
+ end
117
+
118
+ ##
119
+ # Saves the report in Markdown
120
+ #
121
+ # @param file [String] Output file path
122
+ def save_markdown_report(file)
123
+ File.write file, RSpec::RfcHelper::MarkdownRenderer.new(self).render
124
+ end
125
+
126
+ private
127
+
128
+ ##
129
+ # @param number [String] Section number
130
+ #
131
+ # @return [Symbol] Section ID from its number
132
+ def section_id(number)
133
+ section = @sections[number] || raise("Section not found with number #{number}")
134
+ section[:id]
135
+ end
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rspec
4
+ module RfcHelper
5
+ VERSION = '0.1.0'
6
+ end
7
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'rfc_helper/version'
4
+ require_relative 'rfc_helper/specs'
5
+
6
+ module RSpec
7
+ ##
8
+ # RSpec module to track specifications with more context
9
+ module RfcHelper
10
+ class Error < StandardError; end
11
+
12
+ ##
13
+ # Delegation when using the module as a starting point
14
+ def self.add_example(example)
15
+ raise 'Did you start?' unless @specs
16
+
17
+ @specs.add_example(example)
18
+ end
19
+
20
+ ##
21
+ # Delegation when using the module as a starting point
22
+ def self.start_from_file(file)
23
+ raise 'Already started' if @specs
24
+
25
+ @specs = Specs.new_from_file file
26
+ end
27
+
28
+ def self.save_markdown_report(file)
29
+ raise 'Did you start?' unless @specs
30
+
31
+ @specs.save_markdown_report file
32
+ end
33
+ end
34
+ end
data/report.md ADDED
@@ -0,0 +1,204 @@
1
+ # Compliance status: RSpec RFC Helper
2
+
3
+ Report generated on 2023-11-07 22:11:05 +0100
4
+
5
+ Source specification: [https://gitlab.com/experimentslabs/rspec-rfc-helper/-/blob/main/specification_example.md](https://gitlab.com/experimentslabs/rspec-rfc-helper/-/blob/main/specification_example.md)
6
+
7
+ | status | id | section | verb | text |
8
+ |-------:|---:|---------|------|------|
9
+ | <span style='color: green'>pass</span> | `features__have_version` | 2.1 | must | The RFC Helper <mark>MUST</mark> have a version number |
10
+ | <span style='color: green'>pass</span> | `features__can_define_spec` | 2.1 | must | The RFC Helper <mark>MUST</mark> have methods to define specs that needs to be enforced and tracked. |
11
+ | <span style='color: gray'>unknown</span> | `features__wrap_verbs` | 2.1 | must | To tackle this issue, the text used in a spec defined for the RFC Helper <mark>MUST</mark> wrap the right verb in the relevant <br> paragraph into double square brackets, like, for example `<mark>VERB</mark>`. The same paragraph MAY be used in multiple specs, <br> with everytime a different verb wrapper. <br> |
12
+ | <span style='color: gray'>unknown</span> | `features__use_same_paragraph_twice` | 2.1 | may | To tackle this issue, the text used in a spec defined for the RFC Helper MUST wrap the right verb in the relevant <br> paragraph into double square brackets, like, for example `<mark>VERB</mark>`. The same paragraph <mark>MAY</mark> be used in multiple specs, <br> with everytime a different verb wrapper. <br> |
13
+ | <span style='color: green'>pass</span> | `features__identify_specs_with_id` | 2.1 | must | Defined specs also need to be identifiable, so a unique ID <mark>MUST</mark> be assigned to them. |
14
+ | <span style='color: gray'>unknown</span> | `features__no_verb_means_note` | 2.1 | note | When a portion of text is interesting but has no imperative verb in it, it still can be added, and the RFC Helper will <br> give it a status of "note". <br> |
15
+ | <span style='color: green'>pass</span> | `reporting__rfc_tag` | 2.2 | must | Once the RFC helper is configured in a RSpec suite, it <mark>MUST</mark> handle examples tagged with the `rfc` tag. |
16
+ | <span style='color: green'>pass</span> | `reporting__rfc_with_single_id_or_list` | 2.2 | must | The RFC helper <mark>MUST</mark> handle a single ID, as well as an array of IDs. |
17
+ | <span style='color: green'>pass</span> | `reporting__export_method` | 2.2 | must | The RFC Helper <mark>MUST</mark> have at least one method to export the generated report in a file. |
18
+ | <span style='color: gray'>unknown</span> | `reporting__export_date` | 2.2 | must | The report <mark>MUST</mark> contain the generation date. |
19
+ | <span style='color: gray'>unknown</span> | `usage__two_ways` | 3 | should | To have some flexibility, the helper <mark>SHOULD</mark> be useable in two ways: using the module `RSpec::RfcHelper`, or the classes. <br> |
20
+ | <span style='color: gray'>unknown</span> | `usage_module__start_method` | 3.1 | must | The `RSpec::RfcHelper` <mark>MUST</mark> have a method to "start" the helper |
21
+ | <span style='color: gray'>unknown</span> | `usage_module__handle_examples` | 3.1 | must | It <mark>MUST</mark> have a method to add examples to specs once they are evaluated |
22
+ | <span style='color: gray'>unknown</span> | `usage_module__export_method` | 3.1 | must | it <mark>MUST</mark> have a method to save the report |
23
+ | <span style='color: gray'>unknown</span> | | 3.2 | note | The RFC Helper SHOULD be used directly by instantiating a class. |
24
+ | <span style='color: gray'>unknown</span> | | 3.2 | note | The instance MUST provide methods to add sections |
25
+ | <span style='color: gray'>unknown</span> | | 3.2 | note | The instance MUST provide methods to define the specs |
26
+ | <span style='color: gray'>unknown</span> | | 3.2 | note | The instance MUST provide methods to generate the report |
27
+ | <span style='color: gray'>unknown</span> | | 3.2 | note | The instance MAY provide methods to load sections and specs from a file |
28
+
29
+ ## <span style='color: green'>○</span> `MUST` features__have_version
30
+
31
+ **Specification:** 2.1 - Features
32
+
33
+ > The RFC Helper <mark>MUST</mark> have a version number
34
+
35
+ **Examples:**
36
+
37
+ | Status | Reference |
38
+ |:------:|-----------|
39
+ | <span style='color: green'>passed</span> | ``: `./spec/rspec/rfc_helper_spec.rb:4` |
40
+
41
+ ## <span style='color: green'>○</span> `MUST` features__can_define_spec
42
+
43
+ **Specification:** 2.1 - Features
44
+
45
+ > The RFC Helper <mark>MUST</mark> have methods to define specs that needs to be enforced and tracked.
46
+
47
+ **Examples:**
48
+
49
+ | Status | Reference |
50
+ |:------:|-----------|
51
+ | <span style='color: green'>passed</span> | ``: `./spec/rspec/rfc_helper/spec_spec.rb:10` |
52
+ | <span style='color: green'>passed</span> | ``: `./spec/rspec/rfc_helper/specs_spec.rb:64` |
53
+
54
+ ## <span style='color: gray'>○</span> `MUST` features__wrap_verbs
55
+
56
+ **Specification:** 2.1 - Features
57
+
58
+ > To tackle this issue, the text used in a spec defined for the RFC Helper <mark>MUST</mark> wrap the right verb in the relevant <br> paragraph into double square brackets, like, for example `<mark>VERB</mark>`. The same paragraph MAY be used in multiple specs, <br> with everytime a different verb wrapper. <br>
59
+
60
+ **No related examples**
61
+
62
+ ## <span style='color: gray'>○</span> `MAY` features__use_same_paragraph_twice
63
+
64
+ **Specification:** 2.1 - Features
65
+
66
+ > To tackle this issue, the text used in a spec defined for the RFC Helper MUST wrap the right verb in the relevant <br> paragraph into double square brackets, like, for example `<mark>VERB</mark>`. The same paragraph <mark>MAY</mark> be used in multiple specs, <br> with everytime a different verb wrapper. <br>
67
+
68
+ **No related examples**
69
+
70
+ ## <span style='color: green'>○</span> `MUST` features__identify_specs_with_id
71
+
72
+ **Specification:** 2.1 - Features
73
+
74
+ > Defined specs also need to be identifiable, so a unique ID <mark>MUST</mark> be assigned to them.
75
+
76
+ **Examples:**
77
+
78
+ | Status | Reference |
79
+ |:------:|-----------|
80
+ | <span style='color: green'>passed</span> | ``: `./spec/rspec/rfc_helper/specs_spec.rb:75` |
81
+
82
+ ## <span style='color: gray'>○</span> `NOTE` features__no_verb_means_note
83
+
84
+ **Specification:** 2.1 - Features
85
+
86
+ > When a portion of text is interesting but has no imperative verb in it, it still can be added, and the RFC Helper will <br> give it a status of "note". <br>
87
+
88
+ **No related examples**
89
+
90
+ ## <span style='color: green'>○</span> `MUST` reporting__rfc_tag
91
+
92
+ **Specification:** 2.2 - Reporting
93
+
94
+ > Once the RFC helper is configured in a RSpec suite, it <mark>MUST</mark> handle examples tagged with the `rfc` tag.
95
+
96
+ **Examples:**
97
+
98
+ | Status | Reference |
99
+ |:------:|-----------|
100
+ | <span style='color: green'>passed</span> | ``: `./spec/rspec/rfc_helper/specs_spec.rb:91` |
101
+
102
+ ## <span style='color: green'>○</span> `MUST` reporting__rfc_with_single_id_or_list
103
+
104
+ **Specification:** 2.2 - Reporting
105
+
106
+ > The RFC helper <mark>MUST</mark> handle a single ID, as well as an array of IDs.
107
+
108
+ **Examples:**
109
+
110
+ | Status | Reference |
111
+ |:------:|-----------|
112
+ | <span style='color: green'>passed</span> | ``: `./spec/rspec/rfc_helper/specs_spec.rb:91` |
113
+
114
+ ## <span style='color: green'>○</span> `MUST` reporting__export_method
115
+
116
+ **Specification:** 2.2 - Reporting
117
+
118
+ > The RFC Helper <mark>MUST</mark> have at least one method to export the generated report in a file.
119
+
120
+ **Examples:**
121
+
122
+ | Status | Reference |
123
+ |:------:|-----------|
124
+ | <span style='color: green'>passed</span> | ``: `./spec/rspec/rfc_helper/specs_spec.rb:103` |
125
+
126
+ ## <span style='color: gray'>○</span> `MUST` reporting__export_date
127
+
128
+ **Specification:** 2.2 - Reporting
129
+
130
+ > The report <mark>MUST</mark> contain the generation date.
131
+
132
+ **No related examples**
133
+
134
+ ## <span style='color: gray'>○</span> `SHOULD` usage__two_ways
135
+
136
+ **Specification:** 3 - Usage
137
+
138
+ > To have some flexibility, the helper <mark>SHOULD</mark> be useable in two ways: using the module `RSpec::RfcHelper`, or the classes. <br>
139
+
140
+ **No related examples**
141
+
142
+ ## <span style='color: gray'>○</span> `MUST` usage_module__start_method
143
+
144
+ **Specification:** 3.1 - Using the module
145
+
146
+ > The `RSpec::RfcHelper` <mark>MUST</mark> have a method to "start" the helper
147
+
148
+ **No related examples**
149
+
150
+ ## <span style='color: gray'>○</span> `MUST` usage_module__handle_examples
151
+
152
+ **Specification:** 3.1 - Using the module
153
+
154
+ > It <mark>MUST</mark> have a method to add examples to specs once they are evaluated
155
+
156
+ **No related examples**
157
+
158
+ ## <span style='color: gray'>○</span> `MUST` usage_module__export_method
159
+
160
+ **Specification:** 3.1 - Using the module
161
+
162
+ > it <mark>MUST</mark> have a method to save the report
163
+
164
+ **No related examples**
165
+
166
+ ## <span style='color: gray'>○</span> `NOTE`
167
+
168
+ **Specification:** 3.2 - Using the classes
169
+
170
+ > The RFC Helper SHOULD be used directly by instantiating a class.
171
+
172
+ **No related examples**
173
+
174
+ ## <span style='color: gray'>○</span> `NOTE`
175
+
176
+ **Specification:** 3.2 - Using the classes
177
+
178
+ > The instance MUST provide methods to add sections
179
+
180
+ **No related examples**
181
+
182
+ ## <span style='color: gray'>○</span> `NOTE`
183
+
184
+ **Specification:** 3.2 - Using the classes
185
+
186
+ > The instance MUST provide methods to define the specs
187
+
188
+ **No related examples**
189
+
190
+ ## <span style='color: gray'>○</span> `NOTE`
191
+
192
+ **Specification:** 3.2 - Using the classes
193
+
194
+ > The instance MUST provide methods to generate the report
195
+
196
+ **No related examples**
197
+
198
+ ## <span style='color: gray'>○</span> `NOTE`
199
+
200
+ **Specification:** 3.2 - Using the classes
201
+
202
+ > The instance MAY provide methods to load sections and specs from a file
203
+
204
+ **No related examples**
@@ -0,0 +1,27 @@
1
+ module RSpec
2
+ module RfcHelper
3
+ class MarkdownRenderer
4
+ STATUS_COLORS: Hash[String|Symbol, String]
5
+
6
+ @specs: Specs
7
+
8
+ def detailed_spec_report_block: -> String
9
+
10
+ def detailed_spec_report_examples: -> String
11
+
12
+ def detailed_spec_report_header: -> String
13
+
14
+ def header: -> String
15
+
16
+ def main_table: -> String
17
+
18
+ def main_table_row: -> String
19
+
20
+ def paragraph: -> String
21
+
22
+ def render: -> String
23
+
24
+ def status: -> String
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,19 @@
1
+ module RSpec
2
+ module RfcHelper
3
+ class Spec
4
+ attr_reader id: Symbol?
5
+ attr_reader section: String
6
+ attr_reader text: String
7
+ attr_reader verb: Symbol
8
+ attr_reader examples: Array[RSpec::Core::Example]
9
+
10
+ def add_example: -> void
11
+
12
+ def status: -> Symbol
13
+
14
+ private
15
+
16
+ def set_verb: -> void
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,25 @@
1
+ module RSpec
2
+ module RfcHelper
3
+ class Specs
4
+ @name: String?
5
+ @sections: Hash[String, {id: Symbol, title: String, url: String? }]
6
+ @specs: Array[Spec]
7
+ @specs_url: String?
8
+
9
+ def self.new_from_file: -> Specs
10
+
11
+ attr_reader name: String?
12
+ attr_reader sections: Hash[String, {id: Symbol, title: String, url: String? }]
13
+ attr_reader specs_url: String?
14
+
15
+ def add: -> void
16
+ def add_example: -> void
17
+ def add_section: -> void
18
+ def each: -> void
19
+ def find: -> Spec
20
+ def ids: -> Array[Symbol]
21
+ def save_markdown_report: -> void
22
+ def section_id: -> Symbol
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,12 @@
1
+ module RSpec
2
+ module RfcHelper
3
+ VERSION: String
4
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
5
+
6
+ @specs: ::RSpec::RfcHelper::Specs
7
+
8
+ def self.add_example: -> void
9
+ def self.load_specs: -> void
10
+ def self.start_from_file: -> void
11
+ end
12
+ end
@@ -0,0 +1,74 @@
1
+ # RSpec RFC Helper Specification
2
+
3
+ Well... kind of, it's for the example.
4
+
5
+ This is the specification on how `rspec-rfc-helper` must behave and should be used in a test suite.
6
+
7
+ They somehow lack of substance, as I'm not a spec writer...
8
+
9
+ ### 1 - Overview
10
+
11
+ RSpec RFC Helper (_RFC Helper_) is a RSpec module to help implement complicated specs: it is often hard to keep track of
12
+ the external specifications and their implementation; leaving comments in the code is not ideal to understand the state
13
+ of the overall implementation.
14
+
15
+ ### 2 - Features
16
+
17
+ #### 2.1 Spec definition
18
+
19
+ The RFC Helper MUST have a version number
20
+
21
+ The RFC Helper MUST have methods to define specs that needs to be enforced and tracked.
22
+
23
+ Sometimes, in RFC and other formal documents, the same paragraph can have multiple imperative verbs as `MUST`, `SHOULD`,
24
+ ..., making it complicated to break down and summarize.
25
+ To tackle this issue, the text used in a spec defined for the RFC Helper MUST wrap the right verb in the relevant
26
+ paragraph into double square brackets, like, for example `[[VERB]]`. The same paragraph MAY be used in multiple specs,
27
+ with everytime a different verb wrapper.
28
+
29
+ Defined specs also need to be identifiable, so a unique ID MUST be assigned to them.
30
+
31
+ When a portion of text is interesting but has no imperative verb in it, it still can be added, and the RFC Helper will
32
+ give it a status of "note".
33
+
34
+ Specifications are often separated in multiple sections. These sections SHOULD be used to group the specs together.
35
+
36
+ #### 2.2 Reporting
37
+
38
+ Once the RFC helper is configured in a RSpec suite, it MUST handle examples tagged with the `rfc` tag.
39
+
40
+ As a value, developers needs to specify one or many spec IDs targeted by the example. The RFC helper MUST handle a
41
+ single ID, as well as an array of IDs.
42
+
43
+ The RFC Helper MUST have at least one method to export the generated report in a file.
44
+
45
+ The report MUST contain the generation date.
46
+
47
+ ### 3 - Usage
48
+
49
+ To have some flexibility, the helper SHOULD be useable in two ways : using the module `RSpec::RfcHelper`, or the classes.
50
+
51
+ #### 3.1 Using the module
52
+
53
+ The `RSpec::RfcHelper` MUST have a method to "start" the helper, reading a file with sections and specs. It MUST have a
54
+ method to add examples to specs once they are evaluated, and it MUST have a method to save the report.
55
+
56
+ Using the module is similar to using a singleton. It has its goods and bad sides.
57
+
58
+ #### 3.2 Using the classes
59
+
60
+ The RFC Helper SHOULD be used directly by instantiating a class.
61
+
62
+ The instance MUST provide methods to add sections
63
+
64
+ The instance MUST provide methods to define the specs
65
+
66
+ The instance MUST provide methods to generate the report
67
+
68
+ The instance MAY provide methods to load sections and specs from a file
69
+
70
+ Using the classes is a bit more complex but allows more flexibility.
71
+
72
+ ### 3 - Final words
73
+
74
+ Yeah! that's some specification!
metadata ADDED
@@ -0,0 +1,79 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rspec-rfc-helper
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Manuel Tancoigne
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2023-11-07 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rspec
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '3.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '3.0'
27
+ description: Helps keeping track of specifications with more context than just RSpec
28
+ examples
29
+ email:
30
+ - manu@experimentslabs.com
31
+ executables: []
32
+ extensions: []
33
+ extra_rdoc_files: []
34
+ files:
35
+ - ".rspec"
36
+ - ".rubocop.yml"
37
+ - ".ruby-version"
38
+ - CHANGELOG.md
39
+ - LICENSE
40
+ - README.md
41
+ - Rakefile
42
+ - lib/rspec/rfc_helper.rb
43
+ - lib/rspec/rfc_helper/markdown_renderer.rb
44
+ - lib/rspec/rfc_helper/spec.rb
45
+ - lib/rspec/rfc_helper/specs.rb
46
+ - lib/rspec/rfc_helper/version.rb
47
+ - report.md
48
+ - sig/rspec/rfc_helper.rbs
49
+ - sig/rspec/rfc_helper/markdown_renderer.rbs
50
+ - sig/rspec/rfc_helper/spec.rbs
51
+ - sig/rspec/rfc_helper/specs.rbs
52
+ - specification_example.md
53
+ homepage: https://gitlab.com/experimentslabs/rspec-rfc-helper
54
+ licenses: []
55
+ metadata:
56
+ rubygems_mfa_required: 'true'
57
+ homepage_uri: https://gitlab.com/experimentslabs/rspec-rfc-helper
58
+ source_code_uri: https://gitlab.com/experimentslabs/rspec-rfc-helper
59
+ changelog_uri: https://gitlab.com/experimentslabs/rspec-rfc-helper/-/blob/main/CHANGELOG.md
60
+ post_install_message:
61
+ rdoc_options: []
62
+ require_paths:
63
+ - lib
64
+ required_ruby_version: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: 3.1.2
69
+ required_rubygems_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: '0'
74
+ requirements: []
75
+ rubygems_version: 3.4.15
76
+ signing_key:
77
+ specification_version: 4
78
+ summary: RSpec plugin to track RFC compliance
79
+ test_files: []