nrser 0.1.1 → 0.1.2

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.
@@ -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
+ # =======================================================================