nrser 0.2.0.pre.3 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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