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.
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