chione 0.8.0 → 0.9.0

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
  SHA256:
3
- metadata.gz: 21a376418607624f09eee0d544f6713a84623e5229e857544fa9029734dd5d77
4
- data.tar.gz: b37e5e8a256eed89ac9b5bef9a00174d4c14db4907cea7bb59deaa7dd980238f
3
+ metadata.gz: 499f4f5ac15a4abdc2e443cc3cb89c0961be6fecb97251e014199cfb0ba82dde
4
+ data.tar.gz: f0cc3da0ebcb1a8aea02cf10dac2eeb7bd26986df587db3a39238bbff69133a1
5
5
  SHA512:
6
- metadata.gz: 45852b79b8669cefd557274d2d2af98b84df92c0ba239302966b3bc2057c4c5f41d171f389ac9cbb40726d56297506459130a35eea14fa7f9f4ab7a13f5271ca
7
- data.tar.gz: 21d21fd326a4a9641fc13dc2c26d1a507b006a776f9234f266dff12dbbe74cf8c3911317a87017fb39242c05b90df1c342047b857ba27b38b5c5c1366b31762c
6
+ metadata.gz: bfeda410c67704987bc1b049f9fe2b2521bfce8411d4360d878db767883590b874e165e6c965b68f1181d82fde2305fe36a93fb3d7d9941a2f8b477c97950132
7
+ data.tar.gz: eeddaa0889e2e6dc59f7c8f9e217a8c6a72902449bab6e7f397b232dfb4da114b07873b1fa6d9a991abecd118ad1403be203983e6c96922d30d9afe7dd666b53
@@ -1 +1 @@
1
- ��?F��Յ� �� k_I��W*���g����=�;cۥk5����rX�Ε���� ��<7�`�t�ۇ��
1
+ ��G&/�9�Cߎ�D�h�צ���`>3�����|SRF�s���,꺅�1���0-� ��D�ϳ�4tu�߲��[6���0?T3����q.��)�}AwQK[��cs��C!�d\�,�A7�����a�X��m��Z'����e�Z���Y��<�O�.���+�@&"Ɏ5d��� ��)��|7�O�}����9���J?��Y��&�W{z�u�y�)e�2X���z��3��2,N?y=N=+5q흵q�s� %��:�8U�z��-�J�= B��uDkiݴ�Ȉo^c>�X(� (��!N�cO�Y�b��Y��_�"-�һ�!H]��?U~��Z�Z��익h�犦��H��(+U�8�{%� �Y,^޼&��
data.tar.gz.sig CHANGED
Binary file
data/History.md CHANGED
@@ -1,3 +1,11 @@
1
+ ## v0.9.0 [2018-07-19] Michael Granger <ged@FaerieMUD.org>
2
+
3
+ Enhancements:
4
+
5
+ - Make component fields more flexible.
6
+ - Add DataUtilities mixin
7
+
8
+
1
9
  ## v0.8.0 [2018-07-18] Michael Granger <ged@FaerieMUD.org>
2
10
 
3
11
  Enhancements:
data/Rakefile CHANGED
@@ -39,10 +39,9 @@ hoespec = Hoe.spec 'chione' do |spec|
39
39
  spec.dependency 'fluent_fixtures', '~> 0.6'
40
40
  spec.dependency 'faker', '~> 1.8'
41
41
 
42
- spec.dependency 'hoe-deveiate', '~> 1.0', :developer
42
+ spec.dependency 'hoe-deveiate', '~> 0.10', :developer
43
43
  spec.dependency 'simplecov', '~> 0.12', :developer
44
- spec.dependency 'rdoc-generator-fivefish', '~> 0.3', :developer
45
- spec.dependency 'rdoc', '~> 5.1', :developer
44
+ spec.dependency 'rdoc-generator-fivefish', '~> 0.4', :developer
46
45
 
47
46
  spec.require_ruby_version( '>=2.5.0' )
48
47
  spec.hg_sign_tags = true if spec.respond_to?( :hg_sign_tags= )
@@ -12,7 +12,7 @@ module Chione
12
12
  extend Loggability
13
13
 
14
14
  # Gem version
15
- VERSION = '0.8.0'
15
+ VERSION = '0.9.0'
16
16
 
17
17
 
18
18
  # Loggability API -- set up a log host
@@ -32,17 +32,46 @@ class Chione::Component
32
32
  ### +default+. If the optional +process_block+ is provided, it will be called
33
33
  ### with the new value being assigned to the field before it is set, and the
34
34
  ### return value of it will be used instead.
35
- def self::field( name, default: nil, &process_block )
35
+ def self::field( name, **options, &process_block )
36
+ options[ :processor ] = process_block
36
37
  self.fields ||= {}
37
- self.fields[ name ] = default
38
+ self.fields[ name ] = options
38
39
 
39
- define_method( "process_#{name}", &process_block ) if process_block
40
- define_method( "#{name}=" ) do |new_val|
41
- new_val = self.send( "process_#{name}", new_val ) if self.respond_to?( "process_#{name}" )
40
+ # Add some class method
41
+ self.define_singleton_method( "processor_for_#{name}" ) do
42
+ return self.fields.dig( name, :processor )
43
+ end
44
+ self.define_singleton_method( "default_for_#{name}" ) do
45
+ default = self.fields.dig( name, :default )
46
+ return default.call( self ) if default.respond_to?( :call )
47
+ return Chione::DataUtilities.deep_copy( default )
48
+ end
49
+ self.define_singleton_method( "options_for_#{name}" ) do
50
+ return self.fields[ name ]
51
+ end
52
+
53
+ # Add instance methods as a mixin so they can be overridden and super()ed to
54
+ mixin = self.make_field_mixin( name )
55
+ self.include( mixin )
56
+
57
+ end
58
+
59
+
60
+ ### Make a mixin module with methods for the field with the specified +name+.
61
+ def self::make_field_mixin( name )
62
+ mixin = Module.new
63
+
64
+ mixin.attr_reader( name )
65
+ mixin.define_method( "process_#{name}" ) do |value|
66
+ processor = self.class.send( "processor_for_#{name}" ) or return value
67
+ return processor.call( value )
68
+ end
69
+ mixin.define_method( "#{name}=" ) do |new_val|
70
+ new_val = self.send( "process_#{name}", new_val )
42
71
  self.instance_variable_set( "@#{name}", new_val )
43
72
  end
44
73
 
45
- attr_reader( name )
74
+ return mixin
46
75
  end
47
76
 
48
77
 
@@ -56,8 +85,9 @@ class Chione::Component
56
85
  @entity_id = entity_id
57
86
 
58
87
  if self.class.fields
59
- self.class.fields.each do |name, default|
60
- self.method( "#{name}=" ).call( values[name] || default_value(default) )
88
+ self.class.fields.each_key do |name|
89
+ val = values[ name ] || self.class.send( "default_for_#{name}" )
90
+ self.public_send( "#{name}=", val )
61
91
  end
62
92
  end
63
93
  end
@@ -102,20 +132,6 @@ class Chione::Component
102
132
  private
103
133
  #######
104
134
 
105
- ### Process the given +default+ value so it's suitable for use as a default
106
- ### attribute value.
107
- def default_value( default )
108
- return default.call( self ) if default.respond_to?( :call )
109
- return deep_copy( default )
110
- end
111
-
112
-
113
- ### Make a deep copy of the specified +value+.
114
- def deep_copy( value )
115
- return Marshal.load( Marshal.dump(value) )
116
- end
117
-
118
-
119
135
  ### Return a slice of the specified +string+ truncated to at most +maxlen+
120
136
  ### characters. Returns the unchanged +string+ if it's not longer than +maxlen+.
121
137
  def truncate_string( string, maxlen )
@@ -1,6 +1,7 @@
1
1
  # -*- ruby -*-
2
2
  #encoding: utf-8
3
3
 
4
+ require 'tempfile'
4
5
  require 'chione' unless defined?( Chione )
5
6
 
6
7
 
@@ -110,4 +111,90 @@ module Chione
110
111
  end # module Inspection
111
112
 
112
113
 
114
+ # A collection of miscellaneous functions that are useful for manipulating
115
+ # complex data structures.
116
+ #
117
+ # include Chione::DataUtilities
118
+ # newhash = deep_copy( oldhash )
119
+ #
120
+ module DataUtilities
121
+
122
+ ###############
123
+ module_function
124
+ ###############
125
+
126
+ ### Recursively copy the specified +obj+ and return the result.
127
+ def deep_copy( obj )
128
+
129
+ # Handle mocks during testing
130
+ return obj if obj.class.name == 'RSpec::Mocks::Mock'
131
+
132
+ return case obj
133
+ when NilClass, Numeric, TrueClass, FalseClass, Symbol,
134
+ Module, Encoding, IO, Tempfile
135
+ obj
136
+
137
+ when Array
138
+ obj.map {|o| deep_copy(o) }
139
+
140
+ when Hash
141
+ newhash = {}
142
+ newhash.default_proc = obj.default_proc if obj.default_proc
143
+ obj.each do |k,v|
144
+ newhash[ deep_copy(k) ] = deep_copy( v )
145
+ end
146
+ newhash
147
+
148
+ else
149
+ obj.clone
150
+ end
151
+ end
152
+
153
+
154
+ ### Create and return a Hash that will auto-vivify any values it is missing with
155
+ ### another auto-vivifying Hash.
156
+ def autovivify( hash, key )
157
+ hash[ key ] = Hash.new( &Chione::DataUtilities.method(:autovivify) )
158
+ end
159
+
160
+
161
+ ### Return a version of the given +hash+ with its keys transformed
162
+ ### into Strings from whatever they were before.
163
+ def stringify_keys( hash )
164
+ newhash = {}
165
+
166
+ hash.each do |key,val|
167
+ if val.is_a?( Hash )
168
+ newhash[ key.to_s ] = stringify_keys( val )
169
+ else
170
+ newhash[ key.to_s ] = val
171
+ end
172
+ end
173
+
174
+ return newhash
175
+ end
176
+
177
+
178
+ ### Return a duplicate of the given +hash+ with its identifier-like keys
179
+ ### transformed into symbols from whatever they were before.
180
+ def symbolify_keys( hash )
181
+ newhash = {}
182
+
183
+ hash.each do |key,val|
184
+ keysym = key.to_s.dup.untaint.to_sym
185
+
186
+ if val.is_a?( Hash )
187
+ newhash[ keysym ] = symbolify_keys( val )
188
+ else
189
+ newhash[ keysym ] = val
190
+ end
191
+ end
192
+
193
+ return newhash
194
+ end
195
+ alias_method :internify_keys, :symbolify_keys
196
+
197
+ end # module DataUtilities
198
+
199
+
113
200
  end # module Chione
@@ -45,6 +45,9 @@ describe Chione::Component do
45
45
  component_subclass.field( :x, default: 0 )
46
46
  component_subclass.field( :y, default: 18 )
47
47
 
48
+ expect( component_subclass.default_for_x ).to eq( 0 )
49
+ expect( component_subclass.default_for_y ).to eq( 18 )
50
+
48
51
  instance = component_subclass.new
49
52
  expect( instance.x ).to eq( 0 )
50
53
  expect( instance.y ).to eq( 18 )
@@ -56,12 +59,22 @@ describe Chione::Component do
56
59
  { x: Integer(vals[0]), y: Integer(vals[1]) }
57
60
  end
58
61
 
62
+ expect( component_subclass.processor_for_coordinates ).to respond_to( :call )
63
+
59
64
  instance = component_subclass.new( coordinates: [88, 19] )
60
65
  expect( instance.coordinates[:x] ).to eq( 88 )
61
66
  expect( instance.coordinates[:y] ).to eq( 19 )
62
67
  end
63
68
 
64
69
 
70
+ it "can declare a field with arbitrary options" do
71
+ component_subclass.field( :x, default: 1, serializable: false )
72
+
73
+ expect( component_subclass.options_for_x ).to include( serializable: false )
74
+ expect( component_subclass.default_for_x ).to eq( 1 )
75
+ end
76
+
77
+
65
78
  it "uses a dup of the default if it's not an immediate object" do
66
79
  component_subclass.field( :things, default: [] )
67
80
 
@@ -77,10 +90,12 @@ describe Chione::Component do
77
90
  it "calls a callable default if it responds to #call" do
78
91
  component_subclass.field( :oid, default: ->(obj) { obj.object_id } )
79
92
 
93
+ expect( component_subclass.default_for_oid ).to eq( component_subclass.object_id )
94
+
80
95
  instance1 = component_subclass.new
81
96
  instance2 = component_subclass.new( oid: 121212 )
82
97
 
83
- expect( instance1.oid ).to eq( instance1.object_id )
98
+ expect( instance1.oid ).to eq( component_subclass.object_id )
84
99
  expect( instance2.oid ).to eq( 121212 )
85
100
  end
86
101
 
@@ -90,5 +90,131 @@ describe Chione, "mixins" do
90
90
  end
91
91
 
92
92
 
93
+ describe Chione::DataUtilities do
94
+
95
+ it "doesn't try to dup immediate objects" do
96
+ expect( Chione::DataUtilities.deep_copy( nil ) ).to be( nil )
97
+ expect( Chione::DataUtilities.deep_copy( 112 ) ).to be( 112 )
98
+ expect( Chione::DataUtilities.deep_copy( true ) ).to be( true )
99
+ expect( Chione::DataUtilities.deep_copy( false ) ).to be( false )
100
+ expect( Chione::DataUtilities.deep_copy( :a_symbol ) ).to be( :a_symbol )
101
+ end
102
+
103
+ it "doesn't try to dup modules/classes" do
104
+ klass = Class.new
105
+ expect( Chione::DataUtilities.deep_copy( klass ) ).to be( klass )
106
+ end
107
+
108
+ it "doesn't try to dup IOs" do
109
+ data = [ $stdin ]
110
+ expect( Chione::DataUtilities.deep_copy( data[0] ) ).to be( $stdin )
111
+ end
112
+
113
+ it "doesn't try to dup Tempfiles" do
114
+ data = Tempfile.new( 'strelka_deepcopy.XXXXX' )
115
+ expect( Chione::DataUtilities.deep_copy( data ) ).to be( data )
116
+ end
117
+
118
+ it "makes distinct copies of arrays and their members" do
119
+ original = [ 'foom', Set.new([ 1,2 ]), :a_symbol ]
120
+
121
+ copy = Chione::DataUtilities.deep_copy( original )
122
+
123
+ expect( copy ).to eq( original )
124
+ expect( copy ).to_not be( original )
125
+ expect( copy[0] ).to eq( original[0] )
126
+ expect( copy[0] ).to_not be( original[0] )
127
+ expect( copy[1] ).to eq( original[1] )
128
+ expect( copy[1] ).to_not be( original[1] )
129
+ expect( copy[2] ).to eq( original[2] )
130
+ expect( copy[2] ).to be( original[2] ) # Immediate
131
+ end
132
+
133
+ it "makes recursive copies of deeply-nested Arrays" do
134
+ original = [ 1, [ 2, 3, [4], 5], 6, [7, [8, 9], 0] ]
135
+
136
+ copy = Chione::DataUtilities.deep_copy( original )
137
+
138
+ expect( copy ).to eq( original )
139
+ expect( copy ).to_not be( original )
140
+ expect( copy[1] ).to_not be( original[1] )
141
+ expect( copy[1][2] ).to_not be( original[1][2] )
142
+ expect( copy[3] ).to_not be( original[3] )
143
+ expect( copy[3][1] ).to_not be( original[3][1] )
144
+ end
145
+
146
+ it "makes distinct copies of Hashes and their members" do
147
+ original = {
148
+ :a => 1,
149
+ 'b' => 2,
150
+ 3 => 'c',
151
+ }
152
+
153
+ copy = Chione::DataUtilities.deep_copy( original )
154
+
155
+ expect( copy ).to eq( original )
156
+ expect( copy ).to_not be( original )
157
+ expect( copy[:a] ).to eq( 1 )
158
+ expect( copy.key( 2 ) ).to eq( 'b' )
159
+ expect( copy.key( 2 ) ).to_not be( original.key(2) )
160
+ expect( copy[3] ).to eq( 'c' )
161
+ expect( copy[3] ).to_not be( original[3] )
162
+ end
163
+
164
+ it "makes distinct copies of deeply-nested Hashes" do
165
+ original = {
166
+ :a => {
167
+ :b => {
168
+ :c => 'd',
169
+ :e => 'f',
170
+ },
171
+ :g => 'h',
172
+ },
173
+ :i => 'j',
174
+ }
175
+
176
+ copy = Chione::DataUtilities.deep_copy( original )
177
+
178
+ expect( copy ).to eq( original )
179
+ expect( copy[:a][:b][:c] ).to eq( 'd' )
180
+ expect( copy[:a][:b][:c] ).to_not be( original[:a][:b][:c] )
181
+ expect( copy[:a][:b][:e] ).to eq( 'f' )
182
+ expect( copy[:a][:b][:e] ).to_not be( original[:a][:b][:e] )
183
+ expect( copy[:a][:g] ).to eq( 'h' )
184
+ expect( copy[:a][:g] ).to_not be( original[:a][:g] )
185
+ expect( copy[:i] ).to eq( 'j' )
186
+ expect( copy[:i] ).to_not be( original[:i] )
187
+ end
188
+
189
+ it "copies the default proc of copied Hashes" do
190
+ original = Hash.new {|h,k| h[ k ] = Set.new }
191
+
192
+ copy = Chione::DataUtilities.deep_copy( original )
193
+
194
+ expect( copy.default_proc ).to eq( original.default_proc )
195
+ end
196
+
197
+ it "preserves taintedness of copied objects" do
198
+ original = Object.new
199
+ original.taint
200
+
201
+ copy = Chione::DataUtilities.deep_copy( original )
202
+
203
+ expect( copy ).to_not be( original )
204
+ expect( copy ).to be_tainted()
205
+ end
206
+
207
+ it "preserves frozen-ness of copied objects" do
208
+ original = Object.new
209
+ original.freeze
210
+
211
+ copy = Chione::DataUtilities.deep_copy( original )
212
+
213
+ expect( copy ).to_not be( original )
214
+ expect( copy ).to be_frozen()
215
+ end
216
+
217
+ end
218
+
93
219
  end
94
220
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: chione
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Granger
@@ -35,7 +35,7 @@ cert_chain:
35
35
  X0qdrKi+2aZZ0NGuFj9AItBsVmAvkBGIpX4TEKQp5haEbPpmaqO5nIIhV26PXmyT
36
36
  OMKv6pWsoS81vw5KAGBmfX8nht/Py90DQrbRvakATGI=
37
37
  -----END CERTIFICATE-----
38
- date: 2018-07-18 00:00:00.000000000 Z
38
+ date: 2018-07-19 00:00:00.000000000 Z
39
39
  dependencies:
40
40
  - !ruby/object:Gem::Dependency
41
41
  name: loggability
@@ -197,28 +197,34 @@ dependencies:
197
197
  requirements:
198
198
  - - "~>"
199
199
  - !ruby/object:Gem::Version
200
- version: '0.3'
200
+ version: '0.4'
201
201
  type: :development
202
202
  prerelease: false
203
203
  version_requirements: !ruby/object:Gem::Requirement
204
204
  requirements:
205
205
  - - "~>"
206
206
  - !ruby/object:Gem::Version
207
- version: '0.3'
207
+ version: '0.4'
208
208
  - !ruby/object:Gem::Dependency
209
209
  name: rdoc
210
210
  requirement: !ruby/object:Gem::Requirement
211
211
  requirements:
212
- - - "~>"
212
+ - - ">="
213
213
  - !ruby/object:Gem::Version
214
- version: '5.1'
214
+ version: '4.0'
215
+ - - "<"
216
+ - !ruby/object:Gem::Version
217
+ version: '6'
215
218
  type: :development
216
219
  prerelease: false
217
220
  version_requirements: !ruby/object:Gem::Requirement
218
221
  requirements:
219
- - - "~>"
222
+ - - ">="
223
+ - !ruby/object:Gem::Version
224
+ version: '4.0'
225
+ - - "<"
220
226
  - !ruby/object:Gem::Version
221
- version: '5.1'
227
+ version: '6'
222
228
  - !ruby/object:Gem::Dependency
223
229
  name: hoe
224
230
  requirement: !ruby/object:Gem::Requirement
metadata.gz.sig CHANGED
Binary file