rspec-rfc-helper 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: []