rspec-usecases 0.0.12 → 0.0.37
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 +4 -4
- data/.github/workflows/main.yml +2 -0
- data/.gitignore +5 -0
- data/.rubocop.yml +7 -0
- data/Gemfile +19 -10
- data/Guardfile +1 -0
- data/STORIES.md +25 -4
- data/bin/console +1 -1
- data/docs/regexp-01.md +56 -0
- data/docs/samples.md +62 -0
- data/docs/test.debug.txt +93 -0
- data/docs/test.json +172 -0
- data/docs/test.md +39 -0
- data/lib/rspec/usecases.rb +20 -4
- data/lib/rspec/usecases/configure.rb +40 -0
- data/lib/rspec/usecases/contents/base_content.rb +145 -0
- data/lib/rspec/usecases/contents/code.rb +33 -0
- data/lib/rspec/usecases/contents/outcome.rb +27 -0
- data/lib/rspec/usecases/document.rb +173 -0
- data/lib/rspec/usecases/documentor.rb +35 -0
- data/lib/rspec/usecases/generator/base_generator.rb +58 -0
- data/lib/rspec/usecases/generator/debug_generator.rb +106 -0
- data/lib/rspec/usecases/generator/json_generator.rb +39 -0
- data/lib/rspec/usecases/generator/markdown_generator.rb +136 -0
- data/lib/rspec/usecases/groups/base_group.rb +116 -0
- data/lib/rspec/usecases/groups/group.rb +14 -0
- data/lib/rspec/usecases/groups/usecase.rb +30 -0
- data/lib/rspec/usecases/helpers/uc_file_as_markdown_content.rb +26 -0
- data/lib/rspec/usecases/helpers/uc_grab_lines.rb +54 -0
- data/lib/rspec/usecases/options/debug_options.rb +33 -0
- data/lib/rspec/usecases/options/document_options.rb +24 -0
- data/lib/rspec/usecases/options/dynamic_options.rb +102 -0
- data/lib/rspec/usecases/options/json_options.rb +32 -0
- data/lib/rspec/usecases/options/markdown_options.rb +37 -0
- data/lib/rspec/usecases/version.rb +1 -1
- data/rspec-usecases.gemspec +6 -0
- metadata +45 -9
- data/lib/rspec/usecases/content.rb +0 -155
- data/lib/rspec/usecases/content_code.rb +0 -42
- data/lib/rspec/usecases/content_outcome.rb +0 -30
- data/lib/rspec/usecases/usecase.rb +0 -103
data/docs/test.md
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
# markdown title
|
2
|
+
|
3
|
+
markdown description
|
4
|
+
|
5
|
+
## valid level 1 usecase #1
|
6
|
+
|
7
|
+
first encountered usecases
|
8
|
+
|
9
|
+
### SomeClass.some_method
|
10
|
+
|
11
|
+
Calls some_method on SomeClass
|
12
|
+
|
13
|
+
- this outcome has a title
|
14
|
+
- this outcome has a note
|
15
|
+
outcome note
|
16
|
+
|
17
|
+
---
|
18
|
+
|
19
|
+
- this outcome has an hr
|
20
|
+
|
21
|
+
#### this is some unknown code
|
22
|
+
|
23
|
+
```
|
24
|
+
# Source code goes here
|
25
|
+
```
|
26
|
+
|
27
|
+
#### this is some ruby code
|
28
|
+
|
29
|
+
```
|
30
|
+
# Source code goes here
|
31
|
+
```
|
32
|
+
|
33
|
+
## valid level 2 usecase #1.1
|
34
|
+
|
35
|
+
override the summary
|
36
|
+
|
37
|
+
## valid level 2 usecase #1.2
|
38
|
+
|
39
|
+
## valid level 1 usecase #2
|
data/lib/rspec/usecases.rb
CHANGED
@@ -1,10 +1,26 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'rspec/usecases/version'
|
4
|
-
require 'rspec/usecases/
|
5
|
-
require 'rspec/usecases/
|
6
|
-
require 'rspec/usecases/
|
7
|
-
require 'rspec/usecases/
|
4
|
+
require 'rspec/usecases/configure'
|
5
|
+
require 'rspec/usecases/contents/base_content'
|
6
|
+
require 'rspec/usecases/contents/code'
|
7
|
+
require 'rspec/usecases/contents/outcome'
|
8
|
+
require 'rspec/usecases/groups/base_group'
|
9
|
+
require 'rspec/usecases/groups/group'
|
10
|
+
require 'rspec/usecases/groups/usecase'
|
11
|
+
require 'rspec/usecases/document'
|
12
|
+
require 'rspec/usecases/documentor'
|
13
|
+
require 'rspec/usecases/generator/base_generator'
|
14
|
+
require 'rspec/usecases/generator/json_generator'
|
15
|
+
require 'rspec/usecases/generator/debug_generator'
|
16
|
+
require 'rspec/usecases/generator/markdown_generator'
|
17
|
+
require 'rspec/usecases/helpers/uc_file_as_markdown_content'
|
18
|
+
require 'rspec/usecases/helpers/uc_grab_lines'
|
19
|
+
require 'rspec/usecases/options/dynamic_options'
|
20
|
+
require 'rspec/usecases/options/document_options'
|
21
|
+
require 'rspec/usecases/options/debug_options'
|
22
|
+
require 'rspec/usecases/options/markdown_options'
|
23
|
+
require 'rspec/usecases/options/json_options'
|
8
24
|
|
9
25
|
module Rspec
|
10
26
|
module Usecases
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rspec
|
4
|
+
# Usecases
|
5
|
+
module Usecases
|
6
|
+
# Configure can be called to setup rspec example and
|
7
|
+
# example_group names that make sense for documentation
|
8
|
+
# rubocop:disable Layout/ExtraSpacing
|
9
|
+
def self.configure(config)
|
10
|
+
# Feels wrong, as this is overriding context which could effect other libraries
|
11
|
+
# it would be nice to get a handle on context and update it, rather then just
|
12
|
+
# overriding it.
|
13
|
+
#
|
14
|
+
# or maybe I have to stop using context in deep hierarchies and use a different
|
15
|
+
# example group name such as group
|
16
|
+
config.alias_example_group_to :context , usecase: false
|
17
|
+
config.alias_example_group_to :describe , usecase: false
|
18
|
+
|
19
|
+
config.alias_example_group_to :usecase , usecase: true , group_type: :usecase
|
20
|
+
config.alias_example_group_to :xusecase , usecase: false , group_type: :usecase
|
21
|
+
|
22
|
+
config.alias_example_group_to :group , usecase: true , group_type: :group
|
23
|
+
config.alias_example_group_to :xgroup , usecase: false , group_type: :group
|
24
|
+
|
25
|
+
config.alias_example_to :code , content_type: :code
|
26
|
+
config.alias_example_to :ruby , content_type: :code, code_type: :ruby
|
27
|
+
config.alias_example_to :fruby , content_type: :code, code_type: :ruby , focus: true
|
28
|
+
config.alias_example_to :css , content_type: :code, code_type: :css
|
29
|
+
config.alias_example_to :js , content_type: :code, code_type: :javascript
|
30
|
+
config.alias_example_to :javascript , content_type: :code, code_type: :javascript
|
31
|
+
|
32
|
+
# This may need to be it's own type
|
33
|
+
config.alias_example_to :content , content_type: :content
|
34
|
+
config.alias_example_to :outcome , content_type: :outcome
|
35
|
+
|
36
|
+
config.extend Rspec::Usecases::Helpers
|
37
|
+
end
|
38
|
+
# rubocop:enable Layout/ExtraSpacing
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# require 'rspec/usecases/content'
|
4
|
+
|
5
|
+
module Rspec
|
6
|
+
module Usecases
|
7
|
+
module Contents
|
8
|
+
# BaseContent
|
9
|
+
class BaseContent
|
10
|
+
METHOD_NAMES = %w[outcome code ruby css js javascript].join('|').freeze
|
11
|
+
EXTRACT_CONTENT_REX = /
|
12
|
+
(?<bos>^) # beginning of string
|
13
|
+
(?<indent>\s*) # find the indent before the method
|
14
|
+
(?<method_type>#{METHOD_NAMES})\s # grab the method name from predefined list
|
15
|
+
(?<method_signature>.*?) # grab the method signature which is every thing up to the first do
|
16
|
+
(?<method_open>do) # code comes after the first do
|
17
|
+
(?<content>.*) # content is what we want
|
18
|
+
(?<method_closure>end)\z # the end keyword at the end of string is where the content finishes
|
19
|
+
/xm.freeze
|
20
|
+
|
21
|
+
# title
|
22
|
+
attr_accessor :title
|
23
|
+
|
24
|
+
# :type
|
25
|
+
attr_accessor :type
|
26
|
+
|
27
|
+
# metadata
|
28
|
+
attr_accessor :metadata
|
29
|
+
|
30
|
+
# source
|
31
|
+
attr_accessor :source
|
32
|
+
|
33
|
+
# is_hr
|
34
|
+
attr_accessor :is_hr
|
35
|
+
|
36
|
+
def self.parse(example)
|
37
|
+
# return nil if example.description.nil?# || example.description.strip.length.zero?
|
38
|
+
return nil if example.metadata[:content_type].nil?
|
39
|
+
|
40
|
+
result = get_instance(example)
|
41
|
+
|
42
|
+
result&.parse_block_source(example)
|
43
|
+
|
44
|
+
result
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.get_instance(example)
|
48
|
+
type = example.metadata[:content_type].to_s
|
49
|
+
|
50
|
+
begin
|
51
|
+
klass = Module.const_get("Rspec::Usecases::Contents::#{type.capitalize}")
|
52
|
+
klass.new(type, example)
|
53
|
+
rescue NameError
|
54
|
+
# TODO: Logging
|
55
|
+
puts "UNKNOWN CONTENT TYPE: #{type}"
|
56
|
+
nil
|
57
|
+
rescue StandardError => e
|
58
|
+
# TODO: Logging
|
59
|
+
puts e
|
60
|
+
nil
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def initialize(type, example)
|
65
|
+
title = example.description.strip
|
66
|
+
@title = title.start_with?('example at .') ? '' : title
|
67
|
+
@type = type
|
68
|
+
|
69
|
+
# May want to delegate this to an OpenStruct called options
|
70
|
+
@is_hr = !!example.metadata[:hr]
|
71
|
+
end
|
72
|
+
|
73
|
+
# Source code for rspec is living on the metadata[:block].source location
|
74
|
+
# Have not written a test for this yet
|
75
|
+
def parse_block_source(example)
|
76
|
+
unless example.metadata[:source_override].nil?
|
77
|
+
@source = example.metadata[:source_override]
|
78
|
+
return
|
79
|
+
end
|
80
|
+
|
81
|
+
source = get_source(example)
|
82
|
+
|
83
|
+
# NOTE: Need to investigate how RSpec deals with code, see:
|
84
|
+
# https://github.com/rspec/rspec-core/blob/fe3084758857f0714f05ada44a18f1dfe9bf7a7e/spec/rspec/core/formatters/snippet_extractor_spec.rb
|
85
|
+
# https://github.com/rspec/rspec-core/blob/fe3084758857f0714f05ada44a18f1dfe9bf7a7e/lib/rspec/core/formatters/html_formatter.rb
|
86
|
+
segments = source.match(EXTRACT_CONTENT_REX)
|
87
|
+
|
88
|
+
unless defined?(segments) && defined?(segments[:content])
|
89
|
+
@source = ''
|
90
|
+
return
|
91
|
+
end
|
92
|
+
@source = remove_wasted_indentation(segments[:content])
|
93
|
+
@source
|
94
|
+
rescue StandardError => e
|
95
|
+
puts 'Could not parse source'
|
96
|
+
puts example.metadata
|
97
|
+
puts e
|
98
|
+
end
|
99
|
+
|
100
|
+
def get_source(example)
|
101
|
+
if defined?(example.metadata) && defined?(example.metadata[:block]) && defined?(example.metadata[:block].source)
|
102
|
+
example.metadata[:block].source.strip
|
103
|
+
else
|
104
|
+
''
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def remove_wasted_indentation(content)
|
109
|
+
lines = content.lines
|
110
|
+
|
111
|
+
whitespace = /^\s*/
|
112
|
+
|
113
|
+
# find the small whitespace sequence
|
114
|
+
# at beginning of line that is not \n or blank
|
115
|
+
# and grab the smallest value
|
116
|
+
indent = lines
|
117
|
+
.map { |l| l.match(whitespace).to_s }
|
118
|
+
.reject { |s| ["\n", ''].include?(s) }
|
119
|
+
.min_by(&:length)
|
120
|
+
|
121
|
+
# remove the smallest indentation from beginning
|
122
|
+
# of all lines, this is the wasted indentation
|
123
|
+
rex_indent = /^#{indent}/
|
124
|
+
|
125
|
+
lines.each { |l| l.gsub!(rex_indent, '') }
|
126
|
+
|
127
|
+
# convert back to a content string
|
128
|
+
lines.join.strip
|
129
|
+
end
|
130
|
+
|
131
|
+
def to_h
|
132
|
+
{
|
133
|
+
title: title,
|
134
|
+
type: type,
|
135
|
+
source: source,
|
136
|
+
is_hr: is_hr
|
137
|
+
# options: [
|
138
|
+
# is_hr: is_hr
|
139
|
+
# ]
|
140
|
+
}
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rspec
|
4
|
+
module Usecases
|
5
|
+
module Contents
|
6
|
+
# Code
|
7
|
+
class Code < Rspec::Usecases::Contents::BaseContent
|
8
|
+
# # Source code
|
9
|
+
# attr_accessor :code
|
10
|
+
|
11
|
+
# Type of code, ruby, javascript, css etc.
|
12
|
+
attr_accessor :code_type
|
13
|
+
|
14
|
+
# Note
|
15
|
+
attr_accessor :note
|
16
|
+
|
17
|
+
def initialize(type, example)
|
18
|
+
super(type, example)
|
19
|
+
|
20
|
+
@code_type = example.metadata[:code_type].to_s
|
21
|
+
@note = example.metadata[:note].to_s
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_h
|
25
|
+
{
|
26
|
+
code_type: code_type,
|
27
|
+
note: note
|
28
|
+
}.merge(super.to_h)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rspec
|
4
|
+
module Usecases
|
5
|
+
module Contents
|
6
|
+
# Content Outcome
|
7
|
+
class Outcome < Rspec::Usecases::Contents::BaseContent
|
8
|
+
# Note, similar to summary on usecase, but due to
|
9
|
+
# metadata inheritance, I needed to use a different
|
10
|
+
# property name
|
11
|
+
attr_accessor :note
|
12
|
+
|
13
|
+
def initialize(type, example)
|
14
|
+
super(type, example)
|
15
|
+
|
16
|
+
@note = example.metadata[:note].to_s
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_h
|
20
|
+
{
|
21
|
+
note: note
|
22
|
+
}.merge(super.to_h)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,173 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rspec
|
4
|
+
module Usecases
|
5
|
+
# A document represents a list of groups, the main group type being usecases
|
6
|
+
#
|
7
|
+
# A document can have a title, description and a list of groups
|
8
|
+
# A group is just an Rspec context or describe block with the
|
9
|
+
# attribute usecase set to true - usecase: true
|
10
|
+
#
|
11
|
+
# The list of groups can have their own child list of
|
12
|
+
# groups that can go down to any practical depth.
|
13
|
+
class Document
|
14
|
+
attr_reader :title
|
15
|
+
attr_reader :description
|
16
|
+
attr_reader :groups
|
17
|
+
attr_reader :options
|
18
|
+
|
19
|
+
def initialize(root_example_group, **options)
|
20
|
+
@root = root_example_group
|
21
|
+
@options = if options.nil? || options.empty?
|
22
|
+
Rspec::Usecases::Options::DocumentOptions.new(@root.metadata)
|
23
|
+
else
|
24
|
+
Rspec::Usecases::Options::DocumentOptions.new(options)
|
25
|
+
end
|
26
|
+
|
27
|
+
parse_title_description
|
28
|
+
build_groups
|
29
|
+
end
|
30
|
+
|
31
|
+
def json?
|
32
|
+
options.json.active?
|
33
|
+
end
|
34
|
+
|
35
|
+
def debug?
|
36
|
+
options.debug.active?
|
37
|
+
end
|
38
|
+
|
39
|
+
def markdown?
|
40
|
+
options.markdown.active?
|
41
|
+
end
|
42
|
+
|
43
|
+
def skip_render?
|
44
|
+
@skip_render
|
45
|
+
end
|
46
|
+
|
47
|
+
def to_h
|
48
|
+
{
|
49
|
+
settings: {
|
50
|
+
json: json?,
|
51
|
+
debug: debug?,
|
52
|
+
markdown: markdown?,
|
53
|
+
markdown_file: markdown_file,
|
54
|
+
markdown_prettier: markdown_prettier?,
|
55
|
+
markdown_open: markdown_open?,
|
56
|
+
skip_render: skip_render?
|
57
|
+
},
|
58
|
+
title: title,
|
59
|
+
description: description,
|
60
|
+
groups: groups.map(&:to_h)
|
61
|
+
}
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
# rubocop:disable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity, Metrics/AbcSize
|
67
|
+
def value_to_type(value, default_value: :detail, fail_value: :skip)
|
68
|
+
if value.nil?
|
69
|
+
[fail_value]
|
70
|
+
elsif !!value == value
|
71
|
+
value ? [default_value] : [fail_value]
|
72
|
+
elsif value.is_a?(String)
|
73
|
+
[value.to_sym]
|
74
|
+
elsif value.is_a?(Symbol)
|
75
|
+
[value]
|
76
|
+
elsif value.is_a?(Array)
|
77
|
+
value.map do |v|
|
78
|
+
case value
|
79
|
+
when Symbol
|
80
|
+
v
|
81
|
+
when String
|
82
|
+
v.to_sym
|
83
|
+
when !!value
|
84
|
+
value ? default_value : fail_value
|
85
|
+
else
|
86
|
+
raise Rspec::Usecases::Error, 'Unknown option paramater'
|
87
|
+
end
|
88
|
+
end
|
89
|
+
else
|
90
|
+
raise Rspec::Usecases::Error, 'Unknown option paramater'
|
91
|
+
end
|
92
|
+
end
|
93
|
+
# rubocop:enable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity, Metrics/AbcSize
|
94
|
+
|
95
|
+
def extract_meta_options
|
96
|
+
{
|
97
|
+
json: @root.metadata[:json],
|
98
|
+
debug: @root.metadata[:debug],
|
99
|
+
markdown: @root.metadata[:markdown],
|
100
|
+
document_title: @root.metadata[:document_title],
|
101
|
+
document_description: @root.metadata[:document_description]
|
102
|
+
|
103
|
+
}
|
104
|
+
end
|
105
|
+
|
106
|
+
def parse_title_description
|
107
|
+
@skip_render = !!@root.metadata[:skip_render] && @root.metadata[:skip_render] == true
|
108
|
+
|
109
|
+
# # Document data
|
110
|
+
@title = @root.metadata[:document_title] || ''
|
111
|
+
@description = @root.metadata[:document_description] || ''
|
112
|
+
end
|
113
|
+
|
114
|
+
def build_groups
|
115
|
+
@groups = []
|
116
|
+
|
117
|
+
# This is a documentor setting
|
118
|
+
return unless @root.metadata[:usecases]
|
119
|
+
|
120
|
+
# Get a list of describe or context blocks with the :usecase
|
121
|
+
# metadata flag, or use `usecase 'xyz' do end` in your code.
|
122
|
+
@groups = flatten_group_hierarchy(@root, 1)
|
123
|
+
|
124
|
+
# debug
|
125
|
+
end
|
126
|
+
|
127
|
+
# rubocop:disable Metrics/AbcSize
|
128
|
+
def flatten_group_hierarchy(example_group, level)
|
129
|
+
# puts "name : #{example_group.name}"
|
130
|
+
# puts "entering level : #{level}"
|
131
|
+
# if example_group.metadata[:usecase] == true
|
132
|
+
# group = Rspec::Usecases::Groups::Usecase.parse(example_group.name, example_group)
|
133
|
+
# return [group]
|
134
|
+
# end
|
135
|
+
|
136
|
+
# { name: example_group.name, is_group: example_group.metadata[:group], child_count: example_group.children.length }
|
137
|
+
# { name: child_example_group.name, is_group: child_example_group.metadata[:group], child_count: child_example_group.children.length }
|
138
|
+
level_groups = []
|
139
|
+
|
140
|
+
example_group.children.each do |child_example_group|
|
141
|
+
if child_example_group.metadata[:usecase] == true
|
142
|
+
raise(Rspec::Usecases::Error, 'Group required') if child_example_group.metadata[:group_type].nil?
|
143
|
+
|
144
|
+
group = Rspec::Usecases::Groups::BaseGroup.parse(child_example_group.name, child_example_group)
|
145
|
+
|
146
|
+
child_groups = flatten_group_hierarchy(child_example_group, level + 1)
|
147
|
+
|
148
|
+
group.groups = child_groups
|
149
|
+
|
150
|
+
groups.push group
|
151
|
+
|
152
|
+
level_groups.push group
|
153
|
+
else
|
154
|
+
# puts 'keep looking'
|
155
|
+
sibling_groups = flatten_group_hierarchy(child_example_group, level)
|
156
|
+
|
157
|
+
# puts "level : #{level}"
|
158
|
+
# puts "level_groups : #{level_groups.length}"
|
159
|
+
# puts "sibling_groups : #{sibling_groups.length}"
|
160
|
+
|
161
|
+
level_groups += sibling_groups
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
# puts "leaving level : #{level}"
|
166
|
+
# puts "count for level : #{level_groups.length}"
|
167
|
+
|
168
|
+
level_groups
|
169
|
+
end
|
170
|
+
# rubocop:enable Metrics/AbcSize
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|