chione 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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