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.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/main.yml +2 -0
  3. data/.gitignore +5 -0
  4. data/.rubocop.yml +7 -0
  5. data/Gemfile +19 -10
  6. data/Guardfile +1 -0
  7. data/STORIES.md +25 -4
  8. data/bin/console +1 -1
  9. data/docs/regexp-01.md +56 -0
  10. data/docs/samples.md +62 -0
  11. data/docs/test.debug.txt +93 -0
  12. data/docs/test.json +172 -0
  13. data/docs/test.md +39 -0
  14. data/lib/rspec/usecases.rb +20 -4
  15. data/lib/rspec/usecases/configure.rb +40 -0
  16. data/lib/rspec/usecases/contents/base_content.rb +145 -0
  17. data/lib/rspec/usecases/contents/code.rb +33 -0
  18. data/lib/rspec/usecases/contents/outcome.rb +27 -0
  19. data/lib/rspec/usecases/document.rb +173 -0
  20. data/lib/rspec/usecases/documentor.rb +35 -0
  21. data/lib/rspec/usecases/generator/base_generator.rb +58 -0
  22. data/lib/rspec/usecases/generator/debug_generator.rb +106 -0
  23. data/lib/rspec/usecases/generator/json_generator.rb +39 -0
  24. data/lib/rspec/usecases/generator/markdown_generator.rb +136 -0
  25. data/lib/rspec/usecases/groups/base_group.rb +116 -0
  26. data/lib/rspec/usecases/groups/group.rb +14 -0
  27. data/lib/rspec/usecases/groups/usecase.rb +30 -0
  28. data/lib/rspec/usecases/helpers/uc_file_as_markdown_content.rb +26 -0
  29. data/lib/rspec/usecases/helpers/uc_grab_lines.rb +54 -0
  30. data/lib/rspec/usecases/options/debug_options.rb +33 -0
  31. data/lib/rspec/usecases/options/document_options.rb +24 -0
  32. data/lib/rspec/usecases/options/dynamic_options.rb +102 -0
  33. data/lib/rspec/usecases/options/json_options.rb +32 -0
  34. data/lib/rspec/usecases/options/markdown_options.rb +37 -0
  35. data/lib/rspec/usecases/version.rb +1 -1
  36. data/rspec-usecases.gemspec +6 -0
  37. metadata +45 -9
  38. data/lib/rspec/usecases/content.rb +0 -155
  39. data/lib/rspec/usecases/content_code.rb +0 -42
  40. data/lib/rspec/usecases/content_outcome.rb +0 -30
  41. 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
@@ -1,10 +1,26 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'rspec/usecases/version'
4
- require 'rspec/usecases/usecase'
5
- require 'rspec/usecases/content'
6
- require 'rspec/usecases/content_code'
7
- require 'rspec/usecases/content_outcome'
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