nrser 0.0.18 → 0.0.19

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