rspec-usecases 0.0.12 → 0.0.37
Sign up to get free protection for your applications and to get access to all the features.
- 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
|