nrser 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NRSER::RSpex::ExampleGroup
4
+
5
+ # The core, mostly internal method that all RSpex's description methods lead
6
+ # back too (or should / will when refactoring is done).
7
+ #
8
+ # Keyword options are explicitly broken out in this method, versus the sugary
9
+ # ones that call it, so `metadata` can be set without restriction (save the
10
+ # `type` key, which is also it's own keyword here). You can use this method
11
+ # if you want the RSpex functionality but absolutely have to set some
12
+ # metadata key that we use for something else.
13
+ #
14
+ # @param [Array] *description
15
+ # Optional list of elements that compose the custom description.
16
+ #
17
+ # Will be passed to {NRSER::RSpex::Format.description} to produce the
18
+ # string value that is in turn passed to {RSpec.describe}.
19
+ #
20
+ # @param [Symbol] type:
21
+ # The RSpex "type" of the example group, which is used to determine the
22
+ # prefix of the final description and is assigned to the `:type` metadata
23
+ # key.
24
+ #
25
+ # @param [Hash<Symbol, Object>] metadata:
26
+ # Metadata to add to the new example group.
27
+ #
28
+ # In addition to the keys RSpec will reject, we prohibit `:type` *unless*
29
+ # it is the same as the `type` keyword argument or `nil`.
30
+ #
31
+ # In either of these cases, the `type` keyword arg will be used for the new
32
+ # example group's `:type` metadata value.
33
+ #
34
+ # @param [Hash<Symbol, Object>] bindings:
35
+ #
36
+ #
37
+ # @return [return_type]
38
+ # @todo Document return value.
39
+ #
40
+ def describe_x *description,
41
+ type:,
42
+ metadata: {},
43
+ bindings: {},
44
+ add_binding_desc: true,
45
+ subject_block: nil,
46
+ &body
47
+
48
+ # Check that `metadata` doesn't have a `:type` value too... although we
49
+ # allow it if's equal to `type` or `nil` 'cause why not I guess?
50
+ #
51
+ if metadata.key?( :type ) &&
52
+ metadata[:type] != nil &&
53
+ metadata[:type] != type
54
+ raise ArgumentError.new binding.erb <<-END
55
+ `metadata:` keyword argument may not have a `:type` key that conflicts
56
+ with the `type:` keyword argument.
57
+
58
+ Received:
59
+ `type`:
60
+
61
+ <%= type.inspect %>
62
+
63
+ `metadata[:type]`:
64
+
65
+ <%= metadata[:type].pretty_inspect %>
66
+
67
+ END
68
+ end
69
+
70
+ unless bindings.empty? || add_binding_desc == false
71
+ # bindings_desc = NRSER::RSpex::Opts[bindings].to_desc
72
+ bindings_desc = ["(", bindings.ai( multiline: false ), ")"]
73
+
74
+ if description.empty?
75
+ description = bindings.ai( multiline: false )
76
+ else
77
+ description += ["(", bindings.ai( multiline: false ), ")"]
78
+ end
79
+ end
80
+
81
+ formatted = NRSER::RSpex::Format.description *description, type: type
82
+
83
+ describe formatted, **metadata, type: type do
84
+ subject( &subject_block ) if subject_block
85
+
86
+ unless bindings.empty?
87
+ bindings.each { |name, value|
88
+ let( name ) { unwrap value, context: self }
89
+ }
90
+ end
91
+
92
+ module_exec &body
93
+ end # description,
94
+
95
+ end # #describe_x
96
+
97
+ alias_method :describe_x_type, :describe_x
98
+
99
+
100
+ end # module NRSER::RSpex::ExampleGroup
@@ -0,0 +1,270 @@
1
+ # frozen_string_literal: true
2
+ # encoding: utf-8
3
+
4
+ # Instance methods to extend examples groups with. Also included globally so
5
+ # they're available at the top-level in files.
6
+ #
7
+ module NRSER::RSpex::ExampleGroup
8
+
9
+ # Create a new {RSpec.describe} section where the subject is set by
10
+ # calling the parent subject with `args` and evaluate `block` in it.
11
+ #
12
+ # @example
13
+ # describe "hi sayer" do
14
+ # subject{ ->( name ) { "Hi #{ name }!" } }
15
+ #
16
+ # describe_called_with 'Mom' do
17
+ # it { is_expected.to eq 'Hi Mom!' }
18
+ # end
19
+ # end
20
+ #
21
+ # @param [Array] *args
22
+ # Arguments to call `subject` with to produce the new subject.
23
+ #
24
+ # @param [#call] &block
25
+ # Block to execute in the context of the example group after refining
26
+ # the subject.
27
+ #
28
+ def describe_called_with *args, &body
29
+ describe_x_type "called with", List(*args),
30
+ type: :invocation,
31
+ subject_block: -> { super().call *args },
32
+ &body
33
+ end # #describe_called_with
34
+
35
+ # Aliases to other names I was using at first... not preferring their use
36
+ # at the moment.
37
+ #
38
+ # The `when_` one sucks because Atom de-dents the line, and `describe_`
39
+ # is just clearer what the block is doing for people reading it.
40
+ alias_method :called_with, :describe_called_with
41
+ alias_method :when_called_with, :describe_called_with
42
+
43
+
44
+ def describe_message symbol, *args, &body
45
+ description = \
46
+ "message #{ [symbol, *args].map( &NRSER::RSpex.method( :short_s ) ).join( ', ' ) }"
47
+
48
+ describe description, type: :message do
49
+ subject { NRSER::Message.new symbol, *args }
50
+ module_exec &body
51
+ end
52
+ end
53
+
54
+
55
+ # For use when `subject` is a {NRSER::Message}. Create a new context for
56
+ # the `receiver` where the subject is the result of sending that message
57
+ # to the receiver.
58
+ #
59
+ # @param [Object] receiver
60
+ # Object that will receive the message to create the new subject.
61
+ #
62
+ # @param [Boolean] publicly:
63
+ # Send message publicly via {Object#public_send} (default) or privately
64
+ # via {Object.send}.
65
+ #
66
+ # @return
67
+ # Whatever the `context` call returns.
68
+ #
69
+ def describe_sent_to receiver, publicly: true, &block
70
+ mode = if publicly
71
+ "publicly"
72
+ else
73
+ "privately"
74
+ end
75
+
76
+ describe "sent to #{ receiver } (#{ mode })" do
77
+ subject { super().send_to unwrap( receiver, context: self ) }
78
+ module_exec &block
79
+ end
80
+ end # #describe_sent_to
81
+
82
+ # Aliases to other names I was using at first... not preferring their use
83
+ # at the moment.
84
+ #
85
+ # The `when_` one sucks because Atom de-dents the line, and `describe_`
86
+ # is just clearer what the block is doing for people reading it.
87
+ alias_method :sent_to, :describe_sent_to
88
+ alias_method :when_sent_to, :describe_sent_to
89
+
90
+
91
+ def describe_return_value *args, &body
92
+ msg = NRSER::Message.from *args
93
+
94
+ describe "return value from #{ msg }" do
95
+ subject { msg.send_to super() }
96
+ module_exec &body
97
+ end # "return value from #{ msg }"
98
+ end
99
+
100
+
101
+ # Describe a "section". Just like {RSpec.describe} except it:
102
+ #
103
+ # 1. Expects a string title.
104
+ #
105
+ # 2. Prepends a little section squiggle `§` to the title so sections are
106
+ # easier to pick out visually.
107
+ #
108
+ # 3. Adds `type: :section` metadata.
109
+ #
110
+ # @param [String] title
111
+ # String title for the section.
112
+ #
113
+ # @param [Hash<Symbol, Object>] **metadata
114
+ # Additional [RSpec metadata][] for the example group.
115
+ #
116
+ # [RSpec metadata]: https://relishapp.com/rspec/rspec-core/docs/metadata/user-defined-metadata
117
+ #
118
+ # @return
119
+ # Whatever {RSpec.describe} returns.
120
+ #
121
+ def describe_section title, **metadata, &block
122
+ describe(
123
+ "#{ NRSER::RSpex::PREFIXES[:section] } #{ title }",
124
+ type: :section,
125
+ **metadata
126
+ ) do
127
+ module_exec &block
128
+ end
129
+ end # #describe_section
130
+
131
+ # Old name
132
+ alias_method :describe_topic, :describe_section
133
+
134
+
135
+ def describe_file path, **metadata, &body
136
+ title = path
137
+
138
+ describe(
139
+ "#{ NRSER::RSpex::PREFIXES[:file] } #{ title }",
140
+ type: :file,
141
+ file: path,
142
+ **metadata
143
+ ) do
144
+ module_exec &body
145
+ end
146
+ end
147
+
148
+
149
+ def describe_module mod, bind_subject: true, **metadata, &block
150
+ describe(
151
+ "#{ NRSER::RSpex::PREFIXES[:module] } #{ mod.name }",
152
+ type: :module,
153
+ module: mod,
154
+ **metadata
155
+ ) do
156
+ if bind_subject
157
+ subject { mod }
158
+ end
159
+
160
+ module_exec &block
161
+ end
162
+ end # #describe_module
163
+
164
+
165
+ def describe_class klass, bind_subject: true, **metadata, &block
166
+ description = "#{ NRSER::RSpex::PREFIXES[:class] } #{ klass.name }"
167
+
168
+ describe(
169
+ description,
170
+ type: :class,
171
+ class: klass,
172
+ **metadata
173
+ ) do
174
+ if bind_subject
175
+ subject { klass }
176
+ end
177
+
178
+ module_exec &block
179
+ end
180
+ end # #describe_class
181
+
182
+
183
+ def described_class
184
+ metadata[:class] || super()
185
+ end
186
+
187
+
188
+ def describe_group title, **metadata, &block
189
+ describe(
190
+ "#{ NRSER::RSpex::PREFIXES[:group] } #{ title }",
191
+ type: :group,
192
+ **metadata
193
+ ) do
194
+ module_exec &block
195
+ end
196
+ end # #describe_class
197
+
198
+
199
+ def describe_method name, **metadata, &block
200
+ describe(
201
+ "#{ NRSER::RSpex::PREFIXES[:method] } #{ name }",
202
+ type: :method,
203
+ method_name: name,
204
+ **metadata
205
+ ) do
206
+ if name.is_a? Symbol
207
+ subject { super().method name }
208
+ end
209
+
210
+ module_exec &block
211
+ end
212
+ end # #describe_method
213
+
214
+
215
+ def describe_attribute symbol, **metadata, &block
216
+ describe(
217
+ "#{ NRSER::RSpex::PREFIXES[:attribute] } ##{ symbol }",
218
+ type: :attribute,
219
+ **metadata
220
+ ) do
221
+ subject { super().public_send symbol }
222
+ module_exec &block
223
+ end
224
+ end # #describe_attribute
225
+
226
+ # Shorter name
227
+ alias_method :describe_attr, :describe_attribute
228
+
229
+
230
+ # Define a `context` block with `let` bindings and evaluate the `body`
231
+ # block in it.
232
+ #
233
+ # @param [Hash<Symbol, Object>] **bindings
234
+ # Map of symbol names to value to bind using `let`.
235
+ #
236
+ # @param [#call] &body
237
+ # Body block to evaluate in the context.
238
+ #
239
+ # @return
240
+ # Whatever `context` returns.
241
+ #
242
+ def context_where description = nil, **bindings, &body
243
+
244
+ if description.nil?
245
+ description = bindings.map { |name, value|
246
+ "#{ name }: #{ NRSER::RSpex.short_s value }"
247
+ }.join( ", " )
248
+ end
249
+
250
+ context "△ #{ description }", type: :where do
251
+ bindings.each { |name, value|
252
+ let( name ) { unwrap value, context: self }
253
+ }
254
+
255
+ module_exec &body
256
+ end
257
+ end
258
+
259
+ end # module NRSER:RSpex::ExampleGroup
260
+
261
+
262
+ # Post-Processing
263
+ # =======================================================================
264
+
265
+ require_relative './example_group/describe_x'
266
+ require_relative './example_group/describe_spec_file'
267
+ require_relative './example_group/describe_when'
268
+ require_relative './example_group/describe_setup'
269
+ require_relative './example_group/describe_use_case'
270
+ require_relative './example_group/describe_instance'
@@ -0,0 +1,174 @@
1
+ require 'pastel'
2
+
3
+ using NRSER
4
+
5
+ # Definitions
6
+ # =======================================================================
7
+
8
+ # String formatting utilities.
9
+ #
10
+ module NRSER::RSpex::Format
11
+
12
+
13
+ PASTEL = Pastel.new
14
+
15
+ def self.transpose_A_z string, lower_a:, upper_a:
16
+ string
17
+ .gsub( /[A-Z]/ ) { |char|
18
+ [upper_a.ord + (char.ord - 'A'.ord)].pack 'U*'
19
+ }
20
+ .gsub( /[a-z]/ ) { |char|
21
+ [lower_a.ord + (char.ord - 'a'.ord)].pack 'U*'
22
+ }
23
+ end
24
+
25
+
26
+ # Italicize a string
27
+ #
28
+ # @param [type] arg_name
29
+ # @todo Add name param description.
30
+ #
31
+ # @return [return_type]
32
+ # @todo Document return value.
33
+ #
34
+ def self.unicode_italic string
35
+ transpose_A_z string, lower_a: '𝑎', upper_a: '𝐴'
36
+ end # .italic
37
+
38
+
39
+ def self.esc_seq_italic string
40
+ PASTEL.italic string
41
+ end
42
+
43
+
44
+ def self.italic string
45
+ public_send "#{ RSpec.configuration.x_style }_#{ __method__ }", string
46
+ end
47
+
48
+ singleton_class.send :alias_method, :i, :italic
49
+
50
+
51
+ def self.fix_esc_seq commonmark
52
+ commonmark.gsub( "\e\\[", "\e[" )
53
+ end
54
+
55
+
56
+ # @todo Document render_commonmark method.
57
+ #
58
+ # @param [type] arg_name
59
+ # @todo Add name param description.
60
+ #
61
+ # @return [return_type]
62
+ # @todo Document return value.
63
+ #
64
+ def self.render_shelldown *render_doc_args
65
+ doc = CommonMarker.render_doc *render_doc_args
66
+
67
+ transformed = transform_node( doc ).only!
68
+ commonmark = transformed.to_commonmark
69
+ ansi = fix_esc_seq commonmark
70
+ ansi
71
+ end # .render_commonmark
72
+
73
+
74
+ def self.text_node string_content
75
+ CommonMarker::Node.new( :text ).tap { |node|
76
+ node.string_content = string_content
77
+ }
78
+ end
79
+
80
+
81
+ def self.pastel_node name
82
+ text_node PASTEL.lookup( name )
83
+ end
84
+
85
+
86
+ def self.transform_node node
87
+ case node.type
88
+ when :emph
89
+ [
90
+ pastel_node( :italic ),
91
+ node.map { |child| transform_node child },
92
+ pastel_node( :clear ),
93
+ ].flatten
94
+ when :strong
95
+ [
96
+ pastel_node( :bold ),
97
+ node.map { |child| transform_node child},
98
+ pastel_node( :clear ),
99
+ ].flatten
100
+ when :text
101
+ [node]
102
+ when :code
103
+ [
104
+ pastel_node( :magenta ),
105
+ text_node( node.string_content ),
106
+ pastel_node( :clear ),
107
+ ]
108
+ else
109
+ new_node = CommonMarker::Node.new node.type
110
+
111
+ # new_node.string_content = node.string_content
112
+
113
+ node.
114
+ each { |child|
115
+ transform_node( child ).each { |new_child|
116
+ new_node.append_child new_child
117
+ }
118
+ }
119
+
120
+ [new_node]
121
+ end
122
+ end
123
+
124
+
125
+ # @todo Document format_type method.
126
+ #
127
+ # @param [type] arg_name
128
+ # @todo Add name param description.
129
+ #
130
+ # @return [return_type]
131
+ # @todo Document return value.
132
+ #
133
+ def self.prepend_type type, description
134
+ return description if type.nil?
135
+
136
+ prefixes = RSpec.configuration.x_type_prefixes
137
+
138
+ prefix = prefixes[type] ||
139
+ PASTEL.magenta( i( type.to_s.upcase.gsub('_', ' ') ) )
140
+
141
+ "#{ prefix } #{ description }"
142
+ end # .format_type
143
+
144
+
145
+ # @todo Document format method.
146
+ #
147
+ # @param [type] arg_name
148
+ # @todo Add name param description.
149
+ #
150
+ # @return [String]
151
+ #
152
+ def self.description *parts, type: nil
153
+ parts.
154
+ map { |part|
155
+ if part.respond_to? :to_desc
156
+ part.to_desc
157
+ elsif part.is_a? String
158
+ part
159
+ else
160
+ short_s part
161
+ end
162
+ }.
163
+ join( ' ' ).
164
+ squish.
165
+ thru { |description|
166
+ render_shelldown prepend_type( type, description )
167
+ }
168
+ end # .description
169
+
170
+ end # module NRSER::RSpex::Format
171
+
172
+
173
+ # Post-Processing
174
+ # =======================================================================