nrser 0.0.18 → 0.0.19

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a82b7acc13e484b54ab2d87d8d5062c0ce9d2df6
4
- data.tar.gz: a552517767686380e2b992bfa05c1464dfdc742f
3
+ metadata.gz: f34385ea491972db4f43263773d9e9655ddabf85
4
+ data.tar.gz: af6e560b27729d1d16e8aa007515eb2d4eee354f
5
5
  SHA512:
6
- metadata.gz: 962bd3242210ac27e0adaffefaf8f147eb933aef2c03d9c302dc48cb3a03e80a7bf19b2f0a466b244e303d120ce219c9f050a4fb5feadcdcca3abd72a948a81d
7
- data.tar.gz: 4c2ef1b895ac39ad7882952ecc8761595a37860688ef456c2174f0fff48b9670a9f09f88a852c7a6a6d7351e3aa39c70177d41e1ee448647c9b372744128d798
6
+ metadata.gz: 8b9e68cefda2494539cd0f44efdee84ce70ddba6bbaccbe989e1cae0de30356cfbac0c5ede7df901489317a970cc3bec8dd38a720654344497e7dc3b47e66d3f
7
+ data.tar.gz: c400d4be2fed9d8120453f049dd86904c9bd8a2283525dee076318054d690f66b36f838453ef5a0c650f37323c57df86ee0acf33bf303893959f02d27c8342cc
@@ -0,0 +1,14 @@
1
+ require_relative './string'
2
+
3
+ module NRSER
4
+ class << self
5
+
6
+ def erb bnd, str
7
+ require 'erb'
8
+ filter_repeated_blank_lines ERB.new(dedent(str)).result(bnd)
9
+ end # erb
10
+
11
+ alias_method :template, :erb
12
+
13
+ end # class << self
14
+ end # module NRSER
@@ -2,7 +2,7 @@ module NRSER
2
2
  # Maps an enumerable object to a *new* hash with the same keys and values
3
3
  # obtained by calling `block` with the current key and value.
4
4
  #
5
- # If `enumerable` *does not* sucessfully respond to `#to_h` then it's
5
+ # If `enumerable` *does not* successfully respond to `#to_h` then it's
6
6
  # treated as a hash where it's elements are the keys and all the values
7
7
  # are `nil`.
8
8
  #
@@ -13,14 +13,14 @@ module NRSER
13
13
  # next step, it's going to probably be *the* most common argument type,
14
14
  # and there's no reason to do the tests and set up the exception
15
15
  # handler if we already know what's up with it.
16
- return NRSER.map_hash_values(enumerable) if enumerable.is_a? Hash
16
+ return NRSER.map_hash_values(enumerable, &block) if enumerable.is_a? Hash
17
17
 
18
18
  if enumerable.respond_to? :to_h
19
19
  begin
20
20
  hash = enumerable.to_h
21
21
  rescue TypeError => e
22
22
  else
23
- return NRSER.map_hash_values hash
23
+ return NRSER.map_hash_values hash, &block
24
24
  end
25
25
  end
26
26
 
@@ -0,0 +1,7 @@
1
+ module NRSER
2
+ class << self
3
+ def format_exception e
4
+ "#{ e.message } (#{ e.class }):\n #{ e.backtrace.join("\n ") }"
5
+ end
6
+ end # class << self
7
+ end # module NRSER
@@ -0,0 +1,76 @@
1
+ module NRSER
2
+ module Meta
3
+
4
+ # Mixin to provide methods to define and access class attributes - variables
5
+ # that act like instance variables with regards to inheritance but for the
6
+ # class itself.
7
+ #
8
+ # The motivation is to create a easy-to-use class instance variables that
9
+ # resolve like regular instance variables by looking up the inheritance
10
+ # hierarchy - meaning that:
11
+ #
12
+ # 1. When the value is set, it is set on the class in which the operation
13
+ # happens.
14
+ #
15
+ # 2. That value is read for that class and any subclasses.
16
+ # - Class 'self' attr_accessor values are not visible to subclasses.
17
+ #
18
+ # 3. But that value is not visible to any classes further up the inheritance
19
+ # chain.
20
+ # - Class variables (`@@` variables) are global to the *entire class
21
+ # hierarchy* rooted at the definition point.
22
+ #
23
+ # The tests in `spec/nrser/class_attrs_spec.rb` provide detailed walk-through
24
+ # of usage and differences from other approaches.
25
+ #
26
+ module ClassAttrs
27
+
28
+ # Class methods to extend the receiver with when {NRSER::Meta::ClassAttrs}
29
+ # is included.
30
+ module ClassMethods
31
+ def instance_variable_lookup name
32
+ instance_variable_get(name) || if (
33
+ superclass.respond_to? :instance_variable_lookup
34
+ )
35
+ superclass.instance_variable_lookup name
36
+ else
37
+ raise NoMethodError.new NRSER.squish <<-END
38
+ #{ name.inspect } is not defined anywhere in the class hierarchy
39
+ END
40
+ end
41
+ end
42
+
43
+ def class_attr_accessor attr
44
+ var_name = "@#{ attr }".to_sym
45
+
46
+ singleton_class.class_eval do
47
+ define_method(attr) do |*args|
48
+ case args.length
49
+ when 0
50
+ instance_variable_lookup var_name
51
+ when 1
52
+ instance_variable_set var_name, args[0]
53
+ else
54
+ raise ArgumentError.new NRSER.squish <<-END
55
+ wrong number of arguments
56
+ (given #{ args.length }, expected 0 or 1)
57
+ END
58
+ end
59
+ end
60
+
61
+ define_method("#{ attr }=") do |value|
62
+ instance_variable_set var_name, value
63
+ end
64
+ end
65
+ end
66
+ end # module ClassMethods
67
+
68
+ # Extend the including class with {NRSER::Meta::ClassAttrs::ClassMethods}
69
+ def self.included base
70
+ base.extend ClassMethods
71
+ end
72
+
73
+ end # module ClassAttrs
74
+
75
+ end # module Meta
76
+ end # module NRSER
@@ -0,0 +1,15 @@
1
+ module NRSER
2
+ module Meta
3
+ module Props
4
+
5
+ class Base
6
+ include NRSER::Meta::Props
7
+
8
+ def initialize **values
9
+ initialize_props values
10
+ end
11
+ end # class Base
12
+
13
+ end # module Props
14
+ end # module Meta
15
+ end # module NRSER
@@ -0,0 +1,363 @@
1
+ module NRSER
2
+ module Meta
3
+
4
+ T = NRSER::Types
5
+
6
+ #
7
+ module Props
8
+ PROPS_VARIABLE_NAME = :@__NRSER_props
9
+ PROP_VALUES_VARIABLE_NAME = :@__NRSER_prop_values
10
+
11
+ class Prop
12
+ attr_accessor :defined_in,
13
+ :name,
14
+ :type,
15
+ :source
16
+
17
+
18
+ def initialize defined_in,
19
+ name,
20
+ type: T.any,
21
+ default: NRSER::NO_ARG,
22
+ source: nil
23
+
24
+ @defined_in = defined_in
25
+ @name = name
26
+ @type = NRSER::Types.make type
27
+ @source = source
28
+ @default = default
29
+
30
+ if @source.nil?
31
+ @instance_variable_source = false
32
+ else
33
+ source_str = source.to_s
34
+ @instance_variable_source = source_str[0] == '@'
35
+ end
36
+ end
37
+
38
+
39
+ # @todo Document default? method.
40
+ #
41
+ # @param [type] arg_name
42
+ # @todo Add name param description.
43
+ #
44
+ # @return [return_type]
45
+ # @todo Document return value.
46
+ #
47
+ def default?
48
+ @default != NRSER::NO_ARG
49
+ end # #default?
50
+
51
+
52
+ def default
53
+ if default?
54
+ @default
55
+ else
56
+ raise NameError.new NRSER.squish <<-END
57
+ Prop #{ self } has no default value.
58
+ END
59
+ end
60
+ end
61
+
62
+
63
+ # @todo Document source? method.
64
+ #
65
+ # @param [type] arg_name
66
+ # @todo Add name param description.
67
+ #
68
+ # @return [return_type]
69
+ # @todo Document return value.
70
+ #
71
+ def source?
72
+ !@source.nil?
73
+ end # #source?
74
+
75
+
76
+ # @todo Document instance_variable_source? method.
77
+ #
78
+ # @param [type] arg_name
79
+ # @todo Add name param description.
80
+ #
81
+ # @return [return_type]
82
+ # @todo Document return value.
83
+ #
84
+ def instance_variable_source?
85
+ @instance_variable_source
86
+ end # #instance_variable_source?
87
+
88
+
89
+ # @todo Document primary? method.
90
+ #
91
+ # @param [type] arg_name
92
+ # @todo Add name param description.
93
+ #
94
+ # @return [return_type]
95
+ # @todo Document return value.
96
+ #
97
+ def primary?
98
+ !source?
99
+ end # #primary?
100
+
101
+
102
+ # @todo Document get method.
103
+ #
104
+ # @param [type] arg_name
105
+ # @todo Add name param description.
106
+ #
107
+ # @return [return_type]
108
+ # @todo Document return value.
109
+ #
110
+ def get instance
111
+ if source?
112
+ if instance_variable_source?
113
+ instance.instance_variable_get source
114
+ else
115
+ instance.send source
116
+ end
117
+ else
118
+ values(instance)[name]
119
+ end
120
+ end # #get
121
+
122
+
123
+ # @todo Document set method.
124
+ #
125
+ # @param [type] arg_name
126
+ # @todo Add name param description.
127
+ #
128
+ # @return [return_type]
129
+ # @todo Document return value.
130
+ #
131
+ def set instance, value
132
+ unless type.test value
133
+ raise TypeError.new NRSER.squish <<-END
134
+ #{ defined_in }##{ name } must be of type #{ type };
135
+ found #{ value.inspect }
136
+ END
137
+ end
138
+
139
+ values(instance)[name] = value
140
+ end # #set
141
+
142
+
143
+
144
+ # @todo Document set_from_hash method.
145
+ #
146
+ # @param [type] arg_name
147
+ # @todo Add name param description.
148
+ #
149
+ # @return [return_type]
150
+ # @todo Document return value.
151
+ #
152
+ def set_from_values_hash instance, **values
153
+ if values.key? name
154
+ set instance, values[name]
155
+ else
156
+ if default?
157
+ set instance, default.dup
158
+ else
159
+ raise TypeError.new NRSER.squish <<-END
160
+ Prop #{ name } has no default value and no value was provided in
161
+ values #{ values.inspect }.
162
+ END
163
+ end
164
+ end
165
+ end # #set_from_hash
166
+
167
+
168
+ private
169
+
170
+ # @todo Document values method.
171
+ #
172
+ # @param [type] arg_name
173
+ # @todo Add name param description.
174
+ #
175
+ # @return [return_type]
176
+ # @todo Document return value.
177
+ #
178
+ def values instance
179
+ unless instance.instance_variable_defined? PROP_VALUES_VARIABLE_NAME
180
+ instance.instance_variable_set PROP_VALUES_VARIABLE_NAME, {}
181
+ end
182
+
183
+ instance.instance_variable_get PROP_VALUES_VARIABLE_NAME
184
+ end # #value
185
+
186
+ end # class Prop
187
+
188
+
189
+ # @todo Document get_props_ref method.
190
+ #
191
+ # @param [type] arg_name
192
+ # @todo Add name param description.
193
+ #
194
+ # @return [return_type]
195
+ # @todo Document return value.
196
+ #
197
+ def self.get_props_ref klass
198
+ unless klass.instance_variable_defined? PROPS_VARIABLE_NAME
199
+ klass.instance_variable_set PROPS_VARIABLE_NAME, {}
200
+ end
201
+
202
+ klass.instance_variable_get PROPS_VARIABLE_NAME
203
+ end # .get_props_ref
204
+
205
+
206
+ module ClassMethods
207
+
208
+
209
+ # @todo Document props method.
210
+ #
211
+ # @param [type] arg_name
212
+ # @todo Add name param description.
213
+ #
214
+ # @return [return_type]
215
+ # @todo Document return value.
216
+ #
217
+ def props own: false, primary: false
218
+ result = if !own && superclass.respond_to?(:props)
219
+ superclass.props own: own, primary: primary
220
+ else
221
+ {}
222
+ end
223
+
224
+ own_props = NRSER::Meta::Props.get_props_ref self
225
+
226
+ if primary
227
+ own_props.each {|name, prop|
228
+ if prop.primary?
229
+ result[name] = prop
230
+ end
231
+ }
232
+ else
233
+ result.merge! own_props
234
+ end
235
+
236
+ result
237
+ end # #own_props
238
+
239
+
240
+ # @todo Document prop method.
241
+ #
242
+ # @param [type] arg_name
243
+ # @todo Add name param description.
244
+ #
245
+ # @return [return_type]
246
+ # @todo Document return value.
247
+ #
248
+ def prop name, **opts
249
+ ref = NRSER::Meta::Props.get_props_ref self
250
+
251
+ T.sym.check name
252
+
253
+ if ref.key? name
254
+ raise ArgumentError.new NRSER.squish <<-END
255
+ Prop #{ name.inspect } already set for #{ self }:
256
+ #{ ref[name].inspect }
257
+ END
258
+ end
259
+
260
+ prop = Prop.new self, name, **opts
261
+ ref[name] = prop
262
+
263
+ unless prop.source?
264
+ class_eval do
265
+ define_method(name) do
266
+ prop.get self
267
+ end
268
+
269
+ # protected
270
+ # define_method("#{ name }=") do |value|
271
+ # prop.set self, value
272
+ # end
273
+ end
274
+ end
275
+ end # #prop
276
+
277
+
278
+
279
+ # @todo Document from_h method.
280
+ #
281
+ # @param [type] arg_name
282
+ # @todo Add name param description.
283
+ #
284
+ # @return [return_type]
285
+ # @todo Document return value.
286
+ #
287
+ def from_h hash
288
+ self.new(
289
+ NRSER.slice_keys(
290
+ NRSER.symbolize_keys(hash),
291
+ *self.props(primary: true).keys
292
+ )
293
+ )
294
+ end # #from_h
295
+
296
+
297
+ end # module ClassMethods
298
+
299
+
300
+ # Extend the including class with {NRSER::Meta::Props:ClassMethods}
301
+ def self.included base
302
+ base.extend ClassMethods
303
+ end
304
+
305
+
306
+ # Instance Methods
307
+ # =====================================================================
308
+
309
+
310
+ # @todo Document initialize_props method.
311
+ #
312
+ # @param [type] arg_name
313
+ # @todo Add name param description.
314
+ #
315
+ # @return [return_type]
316
+ # @todo Document return value.
317
+ #
318
+ def initialize_props values
319
+ self.class.props(primary: true).each { |name, prop|
320
+ prop.set_from_values_hash self, values
321
+ }
322
+ end # #initialize_props
323
+
324
+
325
+ # @todo Document to_h method.
326
+ #
327
+ # @param [type] arg_name
328
+ # @todo Add name param description.
329
+ #
330
+ # @return [return_type]
331
+ # @todo Document return value.
332
+ #
333
+ def to_h primary: false, own: false
334
+ NRSER.map_values(
335
+ self.class.props own: own, primary: primary
336
+ ) { |name, prop| prop.get self }
337
+ end # #to_h
338
+
339
+
340
+ # @todo Document to_json method.
341
+ #
342
+ # @param [type] arg_name
343
+ # @todo Add name param description.
344
+ #
345
+ # @return [return_type]
346
+ # @todo Document return value.
347
+ #
348
+ def to_json *args
349
+ to_h.to_json *args
350
+ end # #to_json
351
+
352
+
353
+ def to_yaml *args
354
+ to_h.to_yaml *args
355
+ end
356
+
357
+
358
+ end # module Props
359
+
360
+ end # module Meta
361
+ end # module NRSER
362
+
363
+ require_relative './props/base'
data/lib/nrser/meta.rb ADDED
@@ -0,0 +1,2 @@
1
+ require_relative './meta/class_attrs'
2
+ require_relative './meta/props'
@@ -0,0 +1,15 @@
1
+ require 'singleton'
2
+
3
+ module NRSER
4
+ # A singleton class who's instance is used to denote the lack of value
5
+ # for an argument when used as the default.
6
+ #
7
+ # For situations where an argument is optional and `nil` is a legitimate
8
+ # value.
9
+ #
10
+ class NoArg
11
+ include Singleton
12
+ end
13
+
14
+ NO_ARG = NoArg.instance
15
+ end # module NRSER
@@ -0,0 +1,7 @@
1
+ module NRSER
2
+ refine Binding do
3
+ def erb str
4
+ NRSER.template self, str
5
+ end
6
+ end
7
+ end # NRSER
@@ -0,0 +1,7 @@
1
+ module NRSER
2
+ refine Exception do
3
+ def format
4
+ NRSER.format_exception self
5
+ end
6
+ end
7
+ end # NRSER
@@ -0,0 +1,9 @@
1
+ module NRSER
2
+ module Types
3
+ refine Object do
4
+ def t
5
+ NRSER::Types
6
+ end
7
+ end
8
+ end # module Types
9
+ end # module NRSER
@@ -3,17 +3,5 @@ require_relative './refinements/string'
3
3
  require_relative './refinements/array'
4
4
  require_relative './refinements/hash'
5
5
  require_relative './refinements/pathname'
6
-
7
- module NRSER
8
- refine Exception do
9
- def format
10
- NRSER.format_exception self
11
- end
12
- end
13
-
14
- refine Binding do
15
- def erb str
16
- NRSER.template self, str
17
- end
18
- end
19
- end # NRSER
6
+ require_relative './refinements/exception'
7
+ require_relative './refinements/binding'
@@ -0,0 +1,109 @@
1
+ module NRSER
2
+ class << self
3
+ # Functions the operate on strings.
4
+
5
+ # turn a multi-line string into a single line, collapsing whitespace
6
+ # to a single space.
7
+ #
8
+ # same as ActiveSupport's String.squish, adapted from there.
9
+ def squish str
10
+ str.gsub(/[[:space:]]+/, ' ').strip
11
+ end # squish
12
+
13
+ alias_method :unblock, :squish
14
+
15
+
16
+ def common_prefix strings
17
+ raise ArgumentError.new("argument can't be empty") if strings.empty?
18
+ sorted = strings.sort.reject {|line| line == "\n"}
19
+ i = 0
20
+ while sorted.first[i] == sorted.last[i] &&
21
+ i < [sorted.first.length, sorted.last.length].min
22
+ i = i + 1
23
+ end
24
+ strings.first[0...i]
25
+ end # common_prefix
26
+
27
+
28
+ def dedent str
29
+ return str if str.empty?
30
+ lines = str.lines
31
+ indent = common_prefix(lines).match(/^\s*/)[0]
32
+ return str if indent.empty?
33
+ lines.map {|line|
34
+ line = line[indent.length..line.length] if line.start_with? indent
35
+ }.join
36
+ end # dedent
37
+
38
+ # I like dedent better, but other libs seems to call it deindent
39
+ alias_method :deindent, :dedent
40
+
41
+
42
+ def filter_repeated_blank_lines str
43
+ out = []
44
+ lines = str.lines
45
+ skipping = false
46
+ str.lines.each do |line|
47
+ if line =~ /^\s*$/
48
+ unless skipping
49
+ out << line
50
+ end
51
+ skipping = true
52
+ else
53
+ skipping = false
54
+ out << line
55
+ end
56
+ end
57
+ out.join
58
+ end # filter_repeated_blank_lines
59
+
60
+
61
+ # adapted from acrive_support 4.2.0
62
+ #
63
+ # <https://github.com/rails/rails/blob/7847a19f476fb9bee287681586d872ea43785e53/activesupport/lib/active_support/core_ext/string/indent.rb>
64
+ #
65
+ def indent str, amount = 2, indent_string=nil, indent_empty_lines=false
66
+ indent_string = indent_string || str[/^[ \t]/] || ' '
67
+ re = indent_empty_lines ? /^/ : /^(?!$)/
68
+ str.gsub(re, indent_string * amount)
69
+ end
70
+
71
+ # Truncates a given +text+ after a given <tt>length</tt> if +text+ is longer than <tt>length</tt>:
72
+ #
73
+ # 'Once upon a time in a world far far away'.truncate(27)
74
+ # # => "Once upon a time in a wo..."
75
+ #
76
+ # Pass a string or regexp <tt>:separator</tt> to truncate +text+ at a natural break:
77
+ #
78
+ # 'Once upon a time in a world far far away'.truncate(27, separator: ' ')
79
+ # # => "Once upon a time in a..."
80
+ #
81
+ # 'Once upon a time in a world far far away'.truncate(27, separator: /\s/)
82
+ # # => "Once upon a time in a..."
83
+ #
84
+ # The last characters will be replaced with the <tt>:omission</tt> string (defaults to "...")
85
+ # for a total length not exceeding <tt>length</tt>:
86
+ #
87
+ # 'And they found that many people were sleeping better.'.truncate(25, omission: '... (continued)')
88
+ # # => "And they f... (continued)"
89
+ #
90
+ # adapted from
91
+ #
92
+ # <https://github.com/rails/rails/blob/7847a19f476fb9bee287681586d872ea43785e53/activesupport/lib/active_support/core_ext/string/filters.rb#L46>
93
+ #
94
+ def truncate(str, truncate_at, options = {})
95
+ return str.dup unless str.length > truncate_at
96
+
97
+ omission = options[:omission] || '...'
98
+ length_with_room_for_omission = truncate_at - omission.length
99
+ stop = \
100
+ if options[:separator]
101
+ str.rindex(options[:separator], length_with_room_for_omission) || length_with_room_for_omission
102
+ else
103
+ length_with_room_for_omission
104
+ end
105
+
106
+ "#{str[0, stop]}#{omission}"
107
+ end
108
+ end # class << self
109
+ end # module NRSER
@@ -4,7 +4,7 @@ require 'nrser/types/where'
4
4
  using NRSER
5
5
 
6
6
  module NRSER::Types
7
- ANY = where(name: 'Any', from_s: ->(s) { s }) { true }
7
+ ANY = where(name: 'Any', from_s: ->(s) { s }) { true }.freeze
8
8
 
9
9
  # anything
10
10
  def self.any
@@ -10,13 +10,13 @@ module NRSER::Types
10
10
 
11
11
  attr_reader :item_type
12
12
 
13
- def initialize item_type = NRSER::Types.any, **options
13
+ def initialize item_type = NRSER::Types::ANY, **options
14
14
  super ::Array, **options
15
15
  @item_type = NRSER::Types.make item_type
16
16
  end
17
17
 
18
18
  def test value
19
- super(value) && if @item_type == NRSER::Types.any
19
+ super(value) && if @item_type == NRSER::Types::ANY
20
20
  true
21
21
  else
22
22
  value.all? {|v| @item_type.test v}
@@ -55,7 +55,6 @@ module NRSER::Types
55
55
  Array.new *args
56
56
  end
57
57
 
58
- def self.list
59
- array
60
- end
58
+ singleton_class.send :alias_method, :list, :array
59
+
61
60
  end # NRSER::Types
@@ -63,10 +63,7 @@ module NRSER::Types
63
63
  NRSER::Types::Union.new *types, **options
64
64
  end
65
65
 
66
- # match any of the types
67
- def self.one_of *types, **options
68
- union *types, **options
69
- end
66
+ singleton_class.send :alias_method, :one_of, :union
70
67
 
71
68
  class Intersection < Combinator
72
69
  def test value
@@ -79,8 +76,6 @@ module NRSER::Types
79
76
  Intersection.new *types, **options
80
77
  end
81
78
 
82
- # match all of the types
83
- def self.all_of *types, **options
84
- intersection *types, **options
85
- end
79
+ singleton_class.send :alias_method, :all_of, :intersection
80
+
86
81
  end # NRSER::Types
@@ -5,11 +5,41 @@ using NRSER
5
5
 
6
6
  module NRSER::Types
7
7
 
8
- class Hash < NRSER::Types::Type
9
- attr_reader :keys, :values, :including, :exactly, :min, :max
8
+ class HashType < IsA
9
+ attr_reader :keys, :values #, :including, :exactly, :min, :max
10
10
 
11
- def initialize options = {}
11
+ def initialize keys: NRSER::Types::ANY,
12
+ values: NRSER::Types::ANY,
13
+ **options
14
+ super ::Hash, **options
12
15
 
16
+ @keys = NRSER::Types.make keys
17
+ @values = NRSER::Types.make keys
18
+
19
+ end
20
+
21
+ def test value
22
+ return false unless super(value)
23
+
24
+ if keys == NRSER::Types::ALL && values == NRSER::Types::ALL
25
+ return true
26
+ end
27
+
28
+ value.all? { |k, v|
29
+ keys.test(k) && values.test(v)
30
+ }
13
31
  end
14
- end # Hash
32
+ end # HashType
33
+
34
+ HASH = HashType.new.freeze
35
+
36
+ def self.hash_ *args
37
+ if args.empty?
38
+ HASH
39
+ else
40
+ HashType.new *args
41
+ end
42
+ end
43
+
44
+ singleton_class.send :alias_method, :dict, :hash_
15
45
  end
@@ -0,0 +1,23 @@
1
+ require 'nrser/refinements'
2
+ require 'nrser/types/is'
3
+ require 'nrser/types/is_a'
4
+
5
+ using NRSER
6
+
7
+ module NRSER::Types
8
+ SYM = IsA.new Symbol, name: 'Sym', from_s: ->(s) { s.to_sym }
9
+
10
+ def self.sym **options
11
+ if options.empty?
12
+ # if there are no options can point to the constant for efficiency
13
+ SYM
14
+ else
15
+ raise "Not Implemented"
16
+ end
17
+ end # string
18
+
19
+ def self.symbol *args
20
+ sym *args
21
+ end
22
+
23
+ end # NRSER::Types
data/lib/nrser/types.rb CHANGED
@@ -72,4 +72,6 @@ require 'nrser/types/any'
72
72
  require 'nrser/types/booleans'
73
73
  require 'nrser/types/numbers'
74
74
  require 'nrser/types/strings'
75
- require 'nrser/types/array'
75
+ require 'nrser/types/symbol'
76
+ require 'nrser/types/array'
77
+ require 'nrser/types/hash'
data/lib/nrser/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module NRSER
2
- VERSION = "0.0.18"
2
+ VERSION = "0.0.19"
3
3
  end
data/lib/nrser.rb CHANGED
@@ -1,122 +1,14 @@
1
+ module NRSER; end
2
+
1
3
  require_relative './nrser/version'
4
+ require_relative './nrser/no_arg'
2
5
  require_relative './nrser/collection'
3
6
  require_relative './nrser/truthy'
4
-
5
- module NRSER
6
- class << self
7
-
8
- # turn a multi-line string into a single line, collapsing whitespace
9
- # to a single space.
10
- #
11
- # same as ActiveSupport's String.squish, adapted from there.
12
- def squish str
13
- str.gsub(/[[:space:]]+/, ' ').strip
14
- end # squish
15
-
16
- alias_method :unblock, :squish
17
-
18
- def common_prefix strings
19
- raise ArgumentError.new("argument can't be empty") if strings.empty?
20
- sorted = strings.sort.reject {|line| line == "\n"}
21
- i = 0
22
- while sorted.first[i] == sorted.last[i] &&
23
- i < [sorted.first.length, sorted.last.length].min
24
- i = i + 1
25
- end
26
- strings.first[0...i]
27
- end # common_prefix
28
-
29
- def dedent str
30
- return str if str.empty?
31
- lines = str.lines
32
- indent = common_prefix(lines).match(/^\s*/)[0]
33
- return str if indent.empty?
34
- lines.map {|line|
35
- line = line[indent.length..line.length] if line.start_with? indent
36
- }.join
37
- end # dedent
38
-
39
- def filter_repeated_blank_lines str
40
- out = []
41
- lines = str.lines
42
- skipping = false
43
- str.lines.each do |line|
44
- if line =~ /^\s*$/
45
- unless skipping
46
- out << line
47
- end
48
- skipping = true
49
- else
50
- skipping = false
51
- out << line
52
- end
53
- end
54
- out.join
55
- end # filter_repeated_blank_lines
56
-
57
- def erb bnd, str
58
- require 'erb'
59
- filter_repeated_blank_lines ERB.new(dedent(str)).result(bnd)
60
- end # erb
61
-
62
- alias_method :template, :erb
63
-
64
- def format_exception e
65
- "#{ e.message } (#{ e.class }):\n #{ e.backtrace.join("\n ") }"
66
- end
67
-
68
- # adapted from acrive_support 4.2.0
69
- #
70
- # <https://github.com/rails/rails/blob/7847a19f476fb9bee287681586d872ea43785e53/activesupport/lib/active_support/core_ext/string/indent.rb>
71
- #
72
- def indent str, amount = 2, indent_string=nil, indent_empty_lines=false
73
- indent_string = indent_string || str[/^[ \t]/] || ' '
74
- re = indent_empty_lines ? /^/ : /^(?!$)/
75
- str.gsub(re, indent_string * amount)
76
- end
77
-
78
- # Truncates a given +text+ after a given <tt>length</tt> if +text+ is longer than <tt>length</tt>:
79
- #
80
- # 'Once upon a time in a world far far away'.truncate(27)
81
- # # => "Once upon a time in a wo..."
82
- #
83
- # Pass a string or regexp <tt>:separator</tt> to truncate +text+ at a natural break:
84
- #
85
- # 'Once upon a time in a world far far away'.truncate(27, separator: ' ')
86
- # # => "Once upon a time in a..."
87
- #
88
- # 'Once upon a time in a world far far away'.truncate(27, separator: /\s/)
89
- # # => "Once upon a time in a..."
90
- #
91
- # The last characters will be replaced with the <tt>:omission</tt> string (defaults to "...")
92
- # for a total length not exceeding <tt>length</tt>:
93
- #
94
- # 'And they found that many people were sleeping better.'.truncate(25, omission: '... (continued)')
95
- # # => "And they f... (continued)"
96
- #
97
- # adapted from
98
- #
99
- # <https://github.com/rails/rails/blob/7847a19f476fb9bee287681586d872ea43785e53/activesupport/lib/active_support/core_ext/string/filters.rb#L46>
100
- #
101
- def truncate(str, truncate_at, options = {})
102
- return str.dup unless str.length > truncate_at
103
-
104
- omission = options[:omission] || '...'
105
- length_with_room_for_omission = truncate_at - omission.length
106
- stop = \
107
- if options[:separator]
108
- str.rindex(options[:separator], length_with_room_for_omission) || length_with_room_for_omission
109
- else
110
- length_with_room_for_omission
111
- end
112
-
113
- "#{str[0, stop]}#{omission}"
114
- end
115
-
116
- end # class << self
117
- end
118
-
7
+ require_relative './nrser/string'
8
+ require_relative './nrser/binding'
9
+ require_relative './nrser/exception'
119
10
  require_relative './nrser/enumerable'
120
11
  require_relative './nrser/hash'
121
12
  require_relative './nrser/array'
122
- require_relative "./nrser/types"
13
+ require_relative './nrser/types'
14
+ require_relative './nrser/meta'
@@ -0,0 +1,241 @@
1
+ require 'spec_helper'
2
+
3
+ describe NRSER::Meta::ClassAttrs do
4
+ # I'm writing this as I'm developing the module, so I'm going to walk through
5
+ # some of the motivation, features and differences from other approaches
6
+ # as notes to self/others...
7
+ #
8
+
9
+
10
+ # Setup
11
+ # =====================================================================
12
+ #
13
+ # Because any changes would global and RSpec seems to do things some-what
14
+ # asynchronously we need to use dynamic class creation, which is sub-optimal
15
+ # because it has subtle difference with normal definitions (@@ variable
16
+ # declaration being the major one I've hit do far), but it seems like the
17
+ # most reasonable way to go about it at the moment.
18
+ #
19
+
20
+ let (:base) {
21
+ Class.new do
22
+ include NRSER::Meta::ClassAttrs
23
+
24
+ # To compare, I'll also work with standard '@@' class variables and
25
+ # an standard attr_accessors added to the classes themselves.
26
+
27
+ # Normally this would be
28
+ #
29
+ # @@base_class_var = :base_class_var_value
30
+ #
31
+ # but that is different for dynamic classes, to we use
32
+ class_variable_set :@@base_class_var, :base_class_var_value
33
+
34
+ class << self
35
+ attr_accessor :base_self_attr_accessor
36
+ end
37
+
38
+ # Set the self attr_accessor value
39
+ self.base_self_attr_accessor = :base_self_attr_accessor_value
40
+
41
+ # And now some variables using the mixin.
42
+
43
+ # I won't ever set this
44
+ class_attr_accessor :never_set
45
+
46
+ # I'll define this here, but won't set it from base
47
+ class_attr_accessor :def_in_base
48
+
49
+ # These will be set from here
50
+ class_attr_accessor :set_in_base_1
51
+ class_attr_accessor :set_in_base_2
52
+
53
+ # There are two ways of setting values:
54
+ #
55
+ # 1. self assignment:
56
+
57
+ self.set_in_base_1 = :set_in_base_1_value
58
+
59
+ # Not that `x = value` will not work - the `self` is required.
60
+ # I haven't found any way to get `x = value` to trigger the setter
61
+ # methods.
62
+ #
63
+ # 2. "DSL-style":
64
+
65
+ set_in_base_2 :set_in_base_2_value
66
+
67
+ # This is the style i started with, because it made it really easy
68
+ # to have nice-feeling DSL-like functionality with minimal work.
69
+ #
70
+ # I'm unsure at the moment if it's something I want to keep (probably),
71
+ # and if so if it should be optional via a flag on
72
+ # `class_attr_accessor` (seems like a good idea).
73
+ #
74
+ # It's "nice", but it's perhaps kind-of counter-intuitive in the Ruby
75
+ # world?
76
+ #
77
+
78
+ end
79
+ }
80
+
81
+ let(:child_1) {
82
+ Class.new(base) do
83
+ end
84
+ }
85
+
86
+ let(:child_1_1) {
87
+ Class.new(child_1) do
88
+ end
89
+ }
90
+
91
+ let(:child_2) {
92
+ Class.new(base) do
93
+ end
94
+ }
95
+
96
+ let(:child_2_1) {
97
+ Class.new(child_2) do
98
+ end
99
+ }
100
+
101
+
102
+ # Tests
103
+ # =====================================================================
104
+
105
+ describe "Class (@@) Variables and Their Problems" do
106
+ # ---------------------------------------------------------------------
107
+ #
108
+ # Examples of the desired features that work (OK examples) and those that
109
+ # don't (PROBLEM! examples) for class (@@) variables.
110
+ #
111
+
112
+ it "can access @@ variables from base and any subclass (OK)" do
113
+ [base, child_1, child_1_1, child_2, child_2_1].each do |klass|
114
+ expect(
115
+ klass.class_variable_get :@@base_class_var
116
+ ).to be :base_class_var_value
117
+ end
118
+ end
119
+
120
+
121
+ context "@@ variable changed in base" do
122
+ let(:new_value) { :new_base_class_var_value }
123
+
124
+ before {
125
+ base.class_variable_set :@@base_class_var, new_value
126
+ }
127
+
128
+ it "can access new value from base and any subclass (OK)" do
129
+ [base, child_1, child_1_1, child_2, child_2_1].each do |klass|
130
+ expect(
131
+ klass.class_variable_get :@@base_class_var
132
+ ).to be new_value
133
+ end
134
+ end
135
+ end # @@ variable changed in base
136
+
137
+
138
+ context "@@ variable changed in subclass" do
139
+ let(:new_value) { :new_child_1_class_var_value }
140
+
141
+ before {
142
+ child_1.class_variable_set :@@base_class_var, new_value
143
+ }
144
+
145
+ # This is the problem: changes to the value anywhere in the hierarchy
146
+ # are global to the hierarchy
147
+ it "reads that value from all classes (PROBLEM!)" do
148
+ [base, child_1, child_1_1, child_2, child_2_1].each do |klass|
149
+ expect(
150
+ klass.class_variable_get :@@base_class_var
151
+ ).to be new_value
152
+ end
153
+ end
154
+ end # @@ variable changed in base
155
+
156
+ end # Class (@@) Variables and Their Problems
157
+
158
+
159
+ describe "Class 'Self' attr_accessor and Their Problems" do
160
+ # ---------------------------------------------------------------------
161
+
162
+ it "can access from base (OK)" do
163
+ expect(base.base_self_attr_accessor).to be :base_self_attr_accessor_value
164
+ end
165
+
166
+ it "can't access from any subclasses (PROBLEM!)" do
167
+ [child_1, child_1_1, child_2, child_2_1].each do |klass|
168
+ expect(klass.base_self_attr_accessor).to be nil
169
+ end
170
+ end
171
+
172
+ end # Class 'Self' Attribute Accessors and Their Problems
173
+
174
+
175
+
176
+ describe "NRSER::ClassAttrs Solution" do
177
+ # ---------------------------------------------------------------------
178
+ #
179
+ # Examples of the desired features that work (OK examples) and those that
180
+ # don't (PROBLEM! examples) for class 'self' attr_accessor.
181
+ #
182
+
183
+ it "raises NoMethodError when accessing unset class attrs" do
184
+ expect { base.never_set }.to raise_error NoMethodError
185
+ expect { base.def_in_base }.to raise_error NoMethodError
186
+ end
187
+
188
+
189
+ it "reads values set in base from base and any subclass" do
190
+ [base, child_1, child_1_1, child_2, child_2_1].each do |klass|
191
+ expect(klass.set_in_base_1).to be :set_in_base_1_value
192
+ expect(klass.set_in_base_2).to be :set_in_base_2_value
193
+ end
194
+ end
195
+
196
+
197
+ context "class_attr_accessor value changed in base" do
198
+ # With class_attr_accessor, the new value will be visible to base and
199
+ # all subclasses.
200
+
201
+ let(:new_value) { :new_set_in_base_1_value }
202
+
203
+ before {
204
+ base.set_in_base_1 = new_value
205
+ }
206
+
207
+ it "can access new value from base and any subclass" do
208
+ [base, child_1, child_1_1, child_2, child_2_1].each do |klass|
209
+ expect(klass.set_in_base_1).to be new_value
210
+ end
211
+ end
212
+ end # class_attr_accessor value changed in base
213
+
214
+
215
+ context "class_attr_accessor value changed in subclass" do
216
+ # With class_attr_accessor, the new value will be visible to the class
217
+ # it is set in and any subclasses without affecting any others.
218
+
219
+ let(:new_value) { :set_in_child_1 }
220
+
221
+ before {
222
+ child_1.set_in_base_1 = new_value
223
+ }
224
+
225
+ it "reads the new value from that class and any subclasses" do
226
+ [child_1, child_1_1].each do |klass|
227
+ expect(klass.set_in_base_1).to be new_value
228
+ end
229
+ end
230
+
231
+ it "reads the old value from any other classes" do
232
+ [base, child_2, child_2_1].each do |klass|
233
+ expect(klass.set_in_base_1).to be :set_in_base_1_value
234
+ end
235
+ end
236
+
237
+ end # class_attr_accessor value changed in subclass
238
+
239
+ end # NRSER::ClassAttrs Solution
240
+
241
+ end # NRSER::ClassAttrs
@@ -0,0 +1,67 @@
1
+ require 'spec_helper'
2
+
3
+ T = NRSER::Types
4
+
5
+ describe NRSER::Meta::Props do
6
+
7
+ # Setup
8
+ # =====================================================================
9
+
10
+ let(:point) {
11
+ Class.new(NRSER::Meta::Props::Base) do
12
+ # include NRSER::Meta::Props
13
+
14
+ prop :x, type: T.int
15
+ prop :y, type: T.int
16
+ prop :blah, type: T.str, source: :blah
17
+
18
+ def blah
19
+ "blah!"
20
+ end
21
+ end
22
+ }
23
+
24
+ it "has the props" do
25
+ props = point.props
26
+
27
+ expect(props).to be_a Hash
28
+
29
+ [:x, :y, :blah].each do |name|
30
+ expect(props[name]).to be_a NRSER::Meta::Props::Prop
31
+ end
32
+
33
+ [:x, :y].each do |name|
34
+ expect(props[name].source?).to be false
35
+ expect(props[name].primary?).to be true
36
+ end
37
+
38
+ expect(props[:blah].source?).to be true
39
+ expect(props[:blah].primary?).to be false
40
+
41
+ primary_props = point.props primary: true
42
+
43
+ expect(primary_props.key? :blah).to be false
44
+
45
+ p = point.new x: 1, y: 2
46
+
47
+ expect(p.x).to be 1
48
+ expect(p.y).to be 2
49
+
50
+ expect(p.to_h).to eq({x: 1, y: 2, blah: "blah!"})
51
+ expect(p.to_h(primary: true)).to eq({x: 1, y: 2})
52
+
53
+ expect { point.new x: 1, y: 'why?' }.to raise_error TypeError
54
+ expect { p.x = 3 }.to raise_error NoMethodError
55
+
56
+ p_hash = p.to_h
57
+
58
+ p2 = point.from_h p_hash
59
+
60
+ expect(p2.x).to be 1
61
+ expect(p2.y).to be 2
62
+
63
+ expect(p2.to_h).to eq({x: 1, y: 2, blah: "blah!"})
64
+ end
65
+
66
+ end # NRSER::Meta::Props
67
+
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nrser
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.18
4
+ version: 0.0.19
5
5
  platform: ruby
6
6
  authors:
7
7
  - nrser
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-08-16 00:00:00.000000000 Z
11
+ date: 2017-08-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -91,16 +91,27 @@ files:
91
91
  - README.md
92
92
  - lib/nrser.rb
93
93
  - lib/nrser/array.rb
94
+ - lib/nrser/binding.rb
94
95
  - lib/nrser/collection.rb
95
96
  - lib/nrser/enumerable.rb
97
+ - lib/nrser/exception.rb
96
98
  - lib/nrser/hash.rb
97
99
  - lib/nrser/logger.rb
100
+ - lib/nrser/meta.rb
101
+ - lib/nrser/meta/class_attrs.rb
102
+ - lib/nrser/meta/props.rb
103
+ - lib/nrser/meta/props/base.rb
104
+ - lib/nrser/no_arg.rb
98
105
  - lib/nrser/refinements.rb
99
106
  - lib/nrser/refinements/array.rb
107
+ - lib/nrser/refinements/binding.rb
108
+ - lib/nrser/refinements/exception.rb
100
109
  - lib/nrser/refinements/hash.rb
101
110
  - lib/nrser/refinements/object.rb
102
111
  - lib/nrser/refinements/pathname.rb
103
112
  - lib/nrser/refinements/string.rb
113
+ - lib/nrser/refinements/types.rb
114
+ - lib/nrser/string.rb
104
115
  - lib/nrser/truthy.rb
105
116
  - lib/nrser/types.rb
106
117
  - lib/nrser/types/any.rb
@@ -115,6 +126,7 @@ files:
115
126
  - lib/nrser/types/maybe.rb
116
127
  - lib/nrser/types/numbers.rb
117
128
  - lib/nrser/types/strings.rb
129
+ - lib/nrser/types/symbol.rb
118
130
  - lib/nrser/types/type.rb
119
131
  - lib/nrser/types/where.rb
120
132
  - lib/nrser/version.rb
@@ -133,6 +145,8 @@ files:
133
145
  - spec/nrser/logger/level_sym_spec.rb
134
146
  - spec/nrser/logger/send_log_spec.rb
135
147
  - spec/nrser/logger/use_spec.rb
148
+ - spec/nrser/meta/class_attrs_spec.rb
149
+ - spec/nrser/meta/props_spec.rb
136
150
  - spec/nrser/refinements/array_spec.rb
137
151
  - spec/nrser/refinements/erb_spec.rb
138
152
  - spec/nrser/refinements/format_exception_spec.rb
@@ -189,6 +203,8 @@ test_files:
189
203
  - spec/nrser/logger/level_sym_spec.rb
190
204
  - spec/nrser/logger/send_log_spec.rb
191
205
  - spec/nrser/logger/use_spec.rb
206
+ - spec/nrser/meta/class_attrs_spec.rb
207
+ - spec/nrser/meta/props_spec.rb
192
208
  - spec/nrser/refinements/array_spec.rb
193
209
  - spec/nrser/refinements/erb_spec.rb
194
210
  - spec/nrser/refinements/format_exception_spec.rb