nrser 0.2.0.pre.3 → 0.2.0

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 (48) hide show
  1. checksums.yaml +4 -4
  2. data/lib/nrser/ext/enumerable.rb +12 -3
  3. data/lib/nrser/ext/module.rb +62 -0
  4. data/lib/nrser/ext.rb +1 -0
  5. data/lib/nrser/functions/binding.rb +33 -0
  6. data/lib/nrser/functions/enumerable/associate.rb +103 -0
  7. data/lib/nrser/functions/enumerable/map_keys.rb +0 -0
  8. data/lib/nrser/functions/enumerable/map_values.rb +94 -0
  9. data/lib/nrser/functions/enumerable.rb +2 -87
  10. data/lib/nrser/functions/module/methods.rb +206 -0
  11. data/lib/nrser/functions/module/source_locations.rb +213 -0
  12. data/lib/nrser/functions/module.rb +2 -0
  13. data/lib/nrser/functions.rb +1 -0
  14. data/lib/nrser/logging/appender/sync.rb +148 -0
  15. data/lib/nrser/logging/appender.rb +3 -0
  16. data/lib/nrser/logging/formatters/color.rb +165 -0
  17. data/lib/nrser/logging/formatters.rb +1 -0
  18. data/lib/nrser/logging.rb +353 -0
  19. data/lib/nrser/refinements/module.rb +5 -0
  20. data/lib/nrser/refinements.rb +1 -0
  21. data/lib/nrser/rspex/described.rb +99 -0
  22. data/lib/nrser/rspex/example_group/describe_called_with.rb +2 -2
  23. data/lib/nrser/rspex/example_group/describe_class.rb +31 -0
  24. data/lib/nrser/rspex/example_group/describe_instance.rb +1 -1
  25. data/lib/nrser/rspex/example_group/describe_method.rb +40 -0
  26. data/lib/nrser/rspex/example_group.rb +2 -34
  27. data/lib/nrser/rspex/format.rb +19 -6
  28. data/lib/nrser/rspex.rb +1 -1
  29. data/lib/nrser/types/numbers.rb +16 -16
  30. data/lib/nrser/version.rb +1 -1
  31. data/lib/nrser.rb +2 -5
  32. data/spec/design/mapping_spec.rb +42 -0
  33. data/spec/lib/nrser/mean_streak/identity_instance_spec.rb +7 -5
  34. data/spec/spec_helper.rb +23 -105
  35. data/spec/support/shared/types.rb +92 -0
  36. data/spec/support/shared.rb +1 -0
  37. metadata +27 -24
  38. data/lib/nrser/labs/unicode_math.rb +0 -48
  39. data/lib/nrser/labs/where.rb +0 -50
  40. data/lib/nrser/logger.rb +0 -457
  41. data/spec/lib/nrser/logger/dest_spec.rb +0 -15
  42. data/spec/lib/nrser/logger/die_spec.rb +0 -41
  43. data/spec/lib/nrser/logger/install_spec.rb +0 -98
  44. data/spec/lib/nrser/logger/level_int_spec.rb +0 -22
  45. data/spec/lib/nrser/logger/level_name_spec.rb +0 -23
  46. data/spec/lib/nrser/logger/level_sym_spec.rb +0 -22
  47. data/spec/lib/nrser/logger/send_log_spec.rb +0 -63
  48. data/spec/lib/nrser/logger/use_spec.rb +0 -16
@@ -0,0 +1,353 @@
1
+ # encoding: UTF-8
2
+ # frozen_string_literal: true
3
+
4
+ # Requirements
5
+ # =======================================================================
6
+
7
+ # Stdlib
8
+ # -----------------------------------------------------------------------
9
+
10
+ # Deps
11
+ # -----------------------------------------------------------------------
12
+ require 'semantic_logger'
13
+
14
+ # Project / Package
15
+ # -----------------------------------------------------------------------
16
+ require_relative './logging/formatters'
17
+ require_relative './logging/appender'
18
+
19
+
20
+ # Definitions
21
+ # =======================================================================
22
+
23
+ module NRSER
24
+
25
+ # Mix in {.logger} and {#logger} to NRSER for functions to use
26
+ include SemanticLogger::Loggable
27
+
28
+ # Unified logging support via {SemanticLogger}.
29
+ #
30
+ # @see https://rocketjob.github.io/semantic_logger/index.html
31
+ #
32
+ module Logging
33
+
34
+ # Constants
35
+ # ============================================================================
36
+
37
+ # Include this guy in modules and classes to add `.logger` and `#logger` methods
38
+ # that point to their own named logger.
39
+ #
40
+ # Right now, just points to {SemanticLogger::Loggable}, but may expand on that
41
+ # some time in the future, such as to add `.on`/`#on` methods like the old
42
+ # `NRSER::Logger` had, etc.
43
+ #
44
+ # @see http://www.rubydoc.info/gems/semantic_logger/SemanticLogger/Loggable
45
+ #
46
+ # @return [Module]
47
+ #
48
+ Mixin = SemanticLogger::Loggable
49
+
50
+
51
+ # Mixins
52
+ # ============================================================================
53
+
54
+ # Mix in {.logger} and {#logger}
55
+ include Mixin
56
+
57
+ extend SingleForwardable
58
+
59
+
60
+ # Delegation
61
+ # ============================================================================
62
+
63
+ def_single_delegators(
64
+ SemanticLogger,
65
+ :application,
66
+ :application=,
67
+ :[],
68
+ # NOTE These are funky due to different in SemLog's int level and Ruby
69
+ # stdlib / Rails logger int levels, so omit for now.
70
+ #
71
+ # :index_to_level,
72
+ # :level_to_index
73
+ )
74
+
75
+
76
+ # Module Attributes
77
+ # ============================================================================
78
+
79
+ @__mutex = Mutex.new
80
+
81
+
82
+ # Module (Class) Methods
83
+ # =====================================================================
84
+
85
+ # Normalize a level name or number to a symbol, raising if it's not valid.
86
+ #
87
+ # Relies on Semantic Logger's "internal" {SemanticLogger.level_to_index}
88
+ # method.
89
+ #
90
+ # @see https://github.com/rocketjob/semantic_logger/blob/97247126de32e6ecbf74cbccaa3b3732768d52c5/lib/semantic_logger/semantic_logger.rb#L454
91
+ #
92
+ # @param [Symbol | String | Integer]
93
+ # Representation of a level in one of the following formats:
94
+ #
95
+ # 1. {Symbol} - verified as member of {SemanticLogger::LEVELS} and
96
+ # returned.
97
+ #
98
+ # 2. {String} - accepts string representations of the level symbols,
99
+ # case insensitive.
100
+ #
101
+ # 3. {Integer} - interpreted as a Ruby StdLib Logger / Rails Logger
102
+ # level, which are **different** than Semantic Logger's!
103
+ #
104
+ # @return [:trace | :debug | :info | :warn | :error | :fatal]
105
+ # Log level symbol.
106
+ #
107
+ # @raise
108
+ # When `level` is invalid.
109
+ #
110
+ def self.level_sym_for level
111
+ if SemanticLogger::LEVELS.include? level
112
+ level
113
+ else
114
+ SemanticLogger.index_to_level SemanticLogger.level_to_index( level )
115
+ end
116
+ end
117
+
118
+
119
+ # Global / default log level, which we always normalize to a symbol.
120
+ #
121
+ # @return [:trace | :debug | :info | :warn | :error | :fatal]
122
+ #
123
+ def self.level
124
+ level_sym_for SemanticLogger.default_level
125
+ end
126
+
127
+
128
+ def self.level_index
129
+ SemanticLogger.default_level_index
130
+ end
131
+
132
+
133
+ # Set the global default log level.
134
+ #
135
+ # @param level (see .level_sym_for)
136
+ # @return (see .level_sym_for)
137
+ # @raise (see .level_sym_for)
138
+ #
139
+ def self.level= level
140
+ SemanticLogger.default_level = level_sym_for level
141
+ end
142
+
143
+
144
+ # Try to set the level, logging a warning and returning `nil` if it fails.
145
+ #
146
+ # @param level (see .level=)
147
+ #
148
+ # @return [Symbol]
149
+ # The level symbol if it was set successfully.
150
+ #
151
+ # @return [nil]
152
+ # If the set failed (also logs a warning).
153
+ #
154
+ def self.try_set_level level
155
+ begin
156
+ self.level = level
157
+ rescue Exception => error
158
+ logger.warn "Unable to set level, probably bad value",
159
+ level: level,
160
+ error: error
161
+ nil
162
+ end
163
+ end # .try_set_level
164
+
165
+
166
+ def self.level_from_ENV prefix:
167
+ if NRSER.truthy? ENV["#{ prefix }_TRACE"]
168
+ return :trace
169
+ elsif NRSER.truthy? ENV["#{ prefix }_DEBUG"]
170
+ return :debug
171
+ end
172
+
173
+ level = ENV["#{ prefix }_LOG_LEVEL"]
174
+
175
+ unless level.nil? || level == ''
176
+ return level
177
+ end
178
+
179
+ nil
180
+ end
181
+
182
+
183
+ # Setup logging.
184
+ #
185
+ # @param [type] arg_name
186
+ # @todo Add name param description.
187
+ #
188
+ # @return [nil]
189
+ #
190
+ def self.setup! level: nil,
191
+ dest: nil,
192
+ sync: false,
193
+ say_hi: :debug,
194
+ application: 'NRSER',
195
+ env_var_prefix: nil
196
+
197
+ unless @__mutex.try_lock
198
+ raise ThreadError, <<~END
199
+ Mutex is already held.
200
+
201
+ You should pretty generally NOT have multiple threads trying to
202
+ setup logging at once or re-enter {NRSER::Logging.setup}!
203
+ END
204
+ end
205
+
206
+ # Wrap around everything to make sure we release the mutex
207
+ begin
208
+ self.appender = dest unless dest.nil?
209
+
210
+ # Force synchronous logging
211
+ sync! if sync
212
+
213
+ # If we didn't receive a level, check the ENV
214
+ if level.nil?
215
+ if env_var_prefix.nil?
216
+ env_var_prefix = application.gsub( /[^a-zA-Z0-0_]+/, '_' ).upcase
217
+ end
218
+
219
+ level = level_from_ENV prefix: env_var_prefix
220
+ end
221
+
222
+ # If we ended up with a level, try to set it (will only log a warning
223
+ # if it fails, not raise, which could crash things on boot)
224
+ try_set_level level unless level.nil?
225
+
226
+ self.application = application unless application.nil?
227
+
228
+ ensure
229
+ # Make sure we release the mutex; don't need to hold it for the rest
230
+ @__mutex.unlock
231
+ end
232
+
233
+ will_say_hi = case say_hi
234
+ when true, false
235
+ say_hi
236
+ when Symbol, String, Fixnum
237
+ begin
238
+ level_index < SemanticLogger.level_to_index( say_hi )
239
+ rescue Exception => error
240
+ logger.warn "Bad `say_hi` kwd in {NRSER::Logging.setup}",
241
+ say_hi: say_hi,
242
+ expected: "Symbol, String, or Fixnum representing log level",
243
+ error: error
244
+
245
+ false
246
+ end
247
+ else
248
+ logger.warn "Bad `say_hi` kwd in {NRSER::Logging.setup}",
249
+ say_hi: say_hi,
250
+ expected: [true, false, Symbol, String, Fixnum]
251
+
252
+ false
253
+ end
254
+
255
+ if will_say_hi
256
+ logger.info "Hi! Logging is setup",
257
+ level: self.level,
258
+ dest: dest,
259
+ sync: sync
260
+ end
261
+
262
+ nil
263
+ rescue Exception => error
264
+ # Suppress errors in favor of a warning
265
+
266
+ logger.warn \
267
+ message: "Error setting up logging",
268
+ payload: {
269
+ args: {
270
+ level: level,
271
+ dest: dest,
272
+ env_var_prefix: env_var_prefix,
273
+ say_hi: say_hi,
274
+ },
275
+ },
276
+ exception: error
277
+
278
+ nil
279
+ end # .setup!
280
+
281
+
282
+ # Call {.setup!} with some default keywords that are nice for CLI apps.
283
+ #
284
+ # @param (see .setup!)
285
+ # @return (see .setup!)
286
+ #
287
+ def self.setup_for_cli! dest: $stderr,
288
+ sync: true,
289
+ **kwds
290
+ setup! dest: dest, sync: sync, **kwds
291
+ end # .setup_for_cli!
292
+
293
+
294
+ # Hack up SemanticLogger to do sync logging in the main thread
295
+ #
296
+ # @return [nil]
297
+ #
298
+ def self.sync!
299
+ # Create a {Locd::Logging::Appender::Sync}, which implements the
300
+ # {SemanticLogger::Appender::Async} interface but just forwards directly
301
+ # to it's appender in the same thread, and point it where
302
+ # {SemanticLogger::Processor.instance} (which is an Async) points.
303
+ #
304
+ sync_appender = NRSER::Logging::Appender::Sync.new \
305
+ appender: SemanticLogger::Processor.instance.appender
306
+
307
+ # Swap our sync in for the async
308
+ SemanticLogger::Processor.instance_variable_set \
309
+ :@processor,
310
+ sync_appender
311
+
312
+ nil
313
+ end
314
+
315
+
316
+ # The current "main" appender (destination), if any.
317
+ #
318
+ # This is just to simplify things in simple cases, you can always still
319
+ # add multiple appenders.
320
+ #
321
+ # @return [SemanticLogger::Subscriber | nil]
322
+ #
323
+ def self.appender
324
+ @appender
325
+ end
326
+
327
+
328
+ def self.appender= value
329
+ # Save ref to current appender (if any) so we can remove it after adding
330
+ # the new one.
331
+ old_appender = @appender
332
+
333
+ @appender = case value
334
+ when Hash
335
+ SemanticLogger.add_appender value
336
+ when String, Pathname
337
+ SemanticLogger.add_appender file_name: value.to_s
338
+ else
339
+ SemanticLogger.add_appender \
340
+ io: value,
341
+ formatter: NRSER::Logging::Formatters::Color.new
342
+ end
343
+
344
+ # Remove the old appender (if there was one). This is done after adding
345
+ # the new one so that failing won't result with no appenders.
346
+ SemanticLogger.remove_appender( old_appender ) if old_appender
347
+
348
+ @appender
349
+ end
350
+
351
+
352
+ end # module Logging
353
+ end # module NRSER
@@ -0,0 +1,5 @@
1
+ module NRSER
2
+ refine Module do
3
+ include NRSER::Ext::Module
4
+ end
5
+ end
@@ -14,3 +14,4 @@ require_relative './refinements/open_struct'
14
14
  require_relative './refinements/enumerator'
15
15
  require_relative './refinements/symbol'
16
16
  require_relative './refinements/types'
17
+ require_relative './refinements/module'
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Requirements
4
+ # =======================================================================
5
+
6
+ # Stdlib
7
+ # -----------------------------------------------------------------------
8
+
9
+ # Deps
10
+ # -----------------------------------------------------------------------
11
+
12
+ # Project / Package
13
+ # -----------------------------------------------------------------------
14
+
15
+
16
+ # Refinements
17
+ # =======================================================================
18
+
19
+
20
+ # Declarations
21
+ # =======================================================================
22
+
23
+
24
+ # Definitions
25
+ # =======================================================================
26
+
27
+ #
28
+ class NRSER::RSpex::Described
29
+
30
+ # Constants
31
+ # ======================================================================
32
+
33
+
34
+ # Class Methods
35
+ # ======================================================================
36
+
37
+
38
+ # Attributes
39
+ # ======================================================================
40
+
41
+
42
+ # Constructor
43
+ # ======================================================================
44
+
45
+ # Instantiate a new `NRSER::RSpex::Described`.
46
+ def initialize **metadata
47
+
48
+ end # #initialize
49
+
50
+
51
+ # Instance Methods
52
+ # ======================================================================
53
+
54
+
55
+ end # class NRSER::RSpex::Described
56
+
57
+
58
+
59
+
60
+ # @todo document NRSER::RSpex::Described::Class class.
61
+ class NRSER::RSpex::Described::Class < NRSER::RSpex::Described
62
+ def initialize class:
63
+ end
64
+
65
+
66
+ def location
67
+ # Get a reasonable file and line for the class
68
+ file, line = klass.
69
+ # Get an array of all instance methods, excluding inherited ones
70
+ # (the `false` arg)
71
+ instance_methods( false ).
72
+ # Add `#initialize` since it isn't in `#instance_methods` for some
73
+ # reason
74
+ <<( :initialize ).
75
+ # Map those to their {UnboundMethod} objects
76
+ map { |sym| klass.instance_method sym }.
77
+ # Toss any `nil` values
78
+ compact.
79
+ # Get the source locations
80
+ map( &:source_location ).
81
+ # Get the first line in the shortest path
82
+ min_by { |(path, line)| [path.length, line] }
83
+
84
+ # Another approach I thought of... (untested)
85
+ #
86
+ # Get the path
87
+ # # Get frequency of the paths
88
+ # count_by { |(path, line)| path }.
89
+ # # Get the one with the most occurrences
90
+ # max_by { |path, count| count }.
91
+ # # Get just the path (not the count)
92
+ # first
93
+ end
94
+
95
+
96
+ def to_desc
97
+
98
+ end
99
+ end # class NRSER::RSpex::Described::Class
@@ -25,8 +25,8 @@ module NRSER::RSpex::ExampleGroup
25
25
  # @return [void]
26
26
  #
27
27
  def describe_called_with *args, &body
28
- describe_x_type "called with", List(*args),
29
- type: :invocation,
28
+ describe_x_type List(*args),
29
+ type: :called_with,
30
30
  subject_block: -> { super().call *args },
31
31
  &body
32
32
  end # #describe_called_with
@@ -0,0 +1,31 @@
1
+ # encoding: UTF-8
2
+ # frozen_string_literal: true
3
+
4
+ module NRSER::RSpex::ExampleGroup
5
+
6
+ # @todo Document describe_class method.
7
+ #
8
+ # @return [void]
9
+ #
10
+ def describe_class klass,
11
+ *description,
12
+ bind_subject: true,
13
+ **metadata,
14
+ &body
15
+ subject_block = if bind_subject
16
+ -> { klass }
17
+ end
18
+
19
+ describe_x \
20
+ NRSER::RSpex::Format.md_code_quote( klass.name ),
21
+ *description,
22
+ type: :class,
23
+ metadata: {
24
+ **metadata,
25
+ class: klass,
26
+ },
27
+ subject_block: subject_block,
28
+ &body
29
+ end # #describe_class
30
+
31
+ end # module NRSER::RSpex::ExampleGroup
@@ -11,7 +11,7 @@ module NRSER::RSpex::ExampleGroup
11
11
  # @todo Document return value.
12
12
  #
13
13
  def describe_instance *constructor_args, &body
14
- describe_x_type ".new(", Args(*constructor_args), ")",
14
+ describe_x ".new", Args(*constructor_args),
15
15
  type: :instance,
16
16
  metadata: {
17
17
  constructor_args: constructor_args,
@@ -0,0 +1,40 @@
1
+ # encoding: UTF-8
2
+ # frozen_string_literal: true
3
+
4
+ module NRSER::RSpex::ExampleGroup
5
+
6
+ # @todo Document describe_method method.
7
+ #
8
+ # @return [void]
9
+ #
10
+ def describe_method method_name, *description, **metadata, &body
11
+ # Due to legacy, we only auto-bind if the name is a symbol
12
+ #
13
+ # TODO Get rid of this
14
+ #
15
+ subject_block = if method_name.is_a? Symbol
16
+ -> { super().method method_name }
17
+ end
18
+
19
+ name_prefix = if self.respond_to?( :metadata ) &&
20
+ self.metadata.key?( :constructor_args )
21
+ '#'
22
+ else
23
+ '.'
24
+ end
25
+
26
+ name_string = NRSER::RSpex::Format.md_code_quote \
27
+ "#{ name_prefix }#{ method_name }"
28
+
29
+ # Create the RSpec example group context
30
+ describe_x name_string, *description,
31
+ type: :method,
32
+ metadata: {
33
+ **metadata,
34
+ method_name: method_name,
35
+ },
36
+ subject_block: subject_block,
37
+ &body
38
+ end # #describe_method
39
+
40
+ end # module NRSER::RSpex::ExampleGroup
@@ -194,24 +194,6 @@ module NRSER::RSpex::ExampleGroup
194
194
  end # #describe_module
195
195
 
196
196
 
197
- def describe_class klass, bind_subject: true, **metadata, &block
198
- description = "#{ NRSER::RSpex::PREFIXES[:class] } #{ klass.name }"
199
-
200
- describe(
201
- description,
202
- type: :class,
203
- class: klass,
204
- **metadata
205
- ) do
206
- if bind_subject
207
- subject { klass }
208
- end
209
-
210
- module_exec &block
211
- end
212
- end # #describe_class
213
-
214
-
215
197
  def describe_group title, **metadata, &block
216
198
  describe(
217
199
  "#{ NRSER::RSpex::PREFIXES[:group] } #{ title }",
@@ -223,22 +205,6 @@ module NRSER::RSpex::ExampleGroup
223
205
  end # #describe_class
224
206
 
225
207
 
226
- def describe_method name, **metadata, &block
227
- describe(
228
- "#{ NRSER::RSpex::PREFIXES[:method] } #{ name }",
229
- type: :method,
230
- method_name: name,
231
- **metadata
232
- ) do
233
- if name.is_a? Symbol
234
- subject { super().method name }
235
- end
236
-
237
- module_exec &block
238
- end
239
- end # #describe_method
240
-
241
-
242
208
  def describe_attribute symbol, **metadata, &block
243
209
  describe(
244
210
  "#{ NRSER::RSpex::PREFIXES[:attribute] } ##{ symbol }",
@@ -296,3 +262,5 @@ require_relative './example_group/describe_setup'
296
262
  require_relative './example_group/describe_use_case'
297
263
  require_relative './example_group/describe_instance'
298
264
  require_relative './example_group/describe_called_with'
265
+ require_relative './example_group/describe_method'
266
+ require_relative './example_group/describe_class'
@@ -76,8 +76,18 @@ module NRSER::RSpex::Format
76
76
  singleton_class.send :alias_method, :b, :bold
77
77
 
78
78
 
79
+ def self.rspec_syntax_highlighter
80
+ @rspec_syntax_highlighter ||= \
81
+ RSpec::Core::Formatters::SyntaxHighlighter.new RSpec.configuration
82
+ end
83
+
84
+
79
85
  def self.code string
80
- pastel.yellow string
86
+ if string =~ /\A\#[a-zA-Z][a-zA-Z0-9_]*(?:\?|\!)?/
87
+ pastel.bold.blue string
88
+ else
89
+ rspec_syntax_highlighter.highlight string.lines
90
+ end
81
91
  end
82
92
 
83
93
 
@@ -98,10 +108,13 @@ module NRSER::RSpex::Format
98
108
  def self.prepend_type type, description
99
109
  return description if type.nil?
100
110
 
101
- prefixes = RSpec.configuration.x_type_prefixes
111
+ # prefixes = RSpec.configuration.x_type_prefixes
112
+ #
113
+ # prefix = prefixes[type] ||
114
+ # pastel.magenta( i( type.to_s.upcase.gsub('_', ' ') ) )
102
115
 
103
- prefix = prefixes[type] ||
104
- pastel.magenta( i( type.to_s.upcase.gsub('_', ' ') ) )
116
+ # prefix = "*" + type.to_s.upcase.gsub('_', ' ') + "*"
117
+ prefix = pastel.magenta( i( type.to_s.upcase.gsub('_', ' ') ) )
105
118
 
106
119
  "#{ prefix } #{ description }"
107
120
  end # .format_type
@@ -127,13 +140,13 @@ module NRSER::RSpex::Format
127
140
  elsif part.is_a? String
128
141
  part
129
142
  else
130
- short_s part
143
+ NRSER::RSpex.short_s part
131
144
  end
132
145
  }.
133
146
  join( ' ' ).
134
147
  squish.
135
148
  thru { |description|
136
- mean_streak.render prepend_type( type, description )
149
+ prepend_type type, mean_streak.render( description )
137
150
  }
138
151
  end # .description
139
152
 
data/lib/nrser/rspex.rb CHANGED
@@ -270,7 +270,7 @@ RSpec.configure do |config|
270
270
  config.x_type_prefixes = \
271
271
  NRSER::RSpex::PREFIXES
272
272
 
273
- config.add_setting :x_style, default: :unicode
273
+ config.add_setting :x_style, default: :esc_seq
274
274
  end
275
275
 
276
276
  # Make available at the top-level