yasl 0.2.0 → 0.2.1
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 +4 -4
- data/CHANGELOG.md +9 -0
- data/README.md +6 -4
- data/VERSION +1 -1
- data/lib/yasl/dumper.rb +47 -43
- data/lib/yasl/ext/struct.rb +32 -11
- data/lib/yasl/loader.rb +26 -24
- data/yasl.gemspec +3 -3
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ad1fc5f516869a322107b0bb86c96cf46ea789960e920a14fefbf4173294a52f
|
4
|
+
data.tar.gz: 1bacb014f802c4295ebaa4b4db5ca6a99120f1cd10ac4f469cf77da2457cfcaa
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 97579e85d7bdd8e7d7feec52e0ce6567782c67466dffe103e95261d52628d1c0e3b4a1f40897d39401e5d05d2123ea22b7f4ff98f5134f76a46e32b5103a64a4
|
7
|
+
data.tar.gz: 5f7d04c0a0e821d88bf3cafdba3c497df92f1dd92d4f2e8c8222f5c903cbc866767666d10ee8b09d0fd84d05ad03105ba9fc7f0784e4521060a7aa671005bf77
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,14 @@
|
|
1
1
|
# Change Log
|
2
2
|
|
3
|
+
## 0.2.1
|
4
|
+
|
5
|
+
- Make `YASL::UNSERIALIZABLE_DATA_TYPES` work with regular classes too
|
6
|
+
- Make load `whitelist_classes` work with string class names
|
7
|
+
- Make load `whitelist_classes` work with a single class or class name string
|
8
|
+
- Support deserialization fallback of instance variables for Struct member values
|
9
|
+
- Added missing hash method to optional pure Ruby Struct implementation
|
10
|
+
- Fixed issue with requiring one arg minimum, using `select`, and using undefined `upcase?` in implementation of optional pure Ruby Struct
|
11
|
+
|
3
12
|
## 0.2.0
|
4
13
|
|
5
14
|
- Support Boolean serialization in Opal (instead of TrueClass and FalseClass)
|
data/README.md
CHANGED
@@ -11,7 +11,7 @@ A pure Ruby serialization library that works across different Ruby implementatio
|
|
11
11
|
## Requirements
|
12
12
|
|
13
13
|
- Portablity across different Ruby implementations, especially [Opal](https://opalrb.com/) in the web browser and [JRuby](https://www.jruby.org/).
|
14
|
-
- Zero required configuration. Developers are too busy solving business domain problems to worry about low-level serialization details.
|
14
|
+
- Zero required configuration. Developers are too busy solving business domain problems to worry about low-level serialization details (no mucking around `as_json` methods and serializer classes).
|
15
15
|
- Silently ignore non-serializable objects (unlike Marshal), such as `Proc`, `Binding`, and `IO`.
|
16
16
|
- No special performance requirements. No high throughput usage. Average Internet speeds.
|
17
17
|
- Ensure system safety through secure deserialization.
|
@@ -26,7 +26,7 @@ Run:
|
|
26
26
|
Or add to Gemfile:
|
27
27
|
|
28
28
|
```ruby
|
29
|
-
gem 'yasl', '~> 0.2.
|
29
|
+
gem 'yasl', '~> 0.2.1'
|
30
30
|
```
|
31
31
|
|
32
32
|
And, run:
|
@@ -315,8 +315,6 @@ To avoid some JS and `keyword_init` issues with `Struct` in [Opal](https://opalr
|
|
315
315
|
require 'yasl/ext/struct'
|
316
316
|
```
|
317
317
|
|
318
|
-
This ensures successful serialization in YASL.
|
319
|
-
|
320
318
|
## Contributing
|
321
319
|
|
322
320
|
- Check out the latest master to make sure the feature hasn't been
|
@@ -333,6 +331,10 @@ This ensures successful serialization in YASL.
|
|
333
331
|
is fine, but please isolate to its own commit so I can cherry-pick
|
334
332
|
around it.
|
335
333
|
|
334
|
+
## Software Process
|
335
|
+
|
336
|
+
[Glimmer Process](https://github.com/AndyObtiva/glimmer/blob/master/PROCESS.md)
|
337
|
+
|
336
338
|
## TODO
|
337
339
|
|
338
340
|
[TODO.md](TODO.md)
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.2.
|
1
|
+
0.2.1
|
data/lib/yasl/dumper.rb
CHANGED
@@ -36,14 +36,11 @@ module YASL
|
|
36
36
|
end
|
37
37
|
|
38
38
|
def dump_structure(object, for_classes: false)
|
39
|
+
return if unserializable?(object)
|
39
40
|
structure = {}
|
40
41
|
if top_level_class?(object, for_classes)
|
41
|
-
|
42
|
-
|
43
|
-
else
|
44
|
-
structure[:_class] = object.name
|
45
|
-
add_to_classes(object)
|
46
|
-
end
|
42
|
+
structure[:_class] = object.name
|
43
|
+
add_to_classes(object)
|
47
44
|
elsif YASL.json_basic_data_type?(object)
|
48
45
|
structure = object
|
49
46
|
elsif YASL.ruby_basic_data_type?(object)
|
@@ -73,22 +70,25 @@ module YASL
|
|
73
70
|
dump_structure(klass, for_classes: true) unless klass.class_variables.empty? && klass.instance_variables.empty?
|
74
71
|
end
|
75
72
|
|
76
|
-
def dump_ruby_basic_data_type_data(
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
73
|
+
def dump_ruby_basic_data_type_data(obj)
|
74
|
+
class_ancestors_names_include = lambda do |*class_names|
|
75
|
+
lambda { |obj| class_names.any? { |class_name| obj.class.ancestors.map(&:name).include?(class_name) } }
|
76
|
+
end
|
77
|
+
case obj
|
78
|
+
when class_ancestors_names_include['Time']
|
79
|
+
obj.to_datetime.marshal_dump
|
80
|
+
when class_ancestors_names_include['Date']
|
81
|
+
obj.marshal_dump
|
82
|
+
when class_ancestors_names_include['Complex', 'Rational', 'Regexp', 'Symbol', 'BigDecimal']
|
83
|
+
obj.to_s
|
84
|
+
when class_ancestors_names_include['Set']
|
85
|
+
obj.to_a.uniq.map {|element| dump_structure(element)}
|
86
|
+
when class_ancestors_names_include['Range']
|
87
|
+
[obj.begin, obj.end, obj.exclude_end?]
|
88
|
+
when class_ancestors_names_include['Array']
|
89
|
+
obj.map {|element| dump_structure(element)}
|
90
|
+
when class_ancestors_names_include['Hash']
|
91
|
+
obj.reject do |key, value|
|
92
92
|
[key, value].detect {|element| unserializable?(element)}
|
93
93
|
end.map do |pair|
|
94
94
|
pair.map {|element| dump_structure(element)}
|
@@ -122,10 +122,7 @@ module YASL
|
|
122
122
|
def dump_class_variables(object)
|
123
123
|
structure = {}
|
124
124
|
if object.respond_to?(:class_variables) && !object.class_variables.empty?
|
125
|
-
structure[:_class_variables] = object
|
126
|
-
value = object.class_variable_get(var)
|
127
|
-
unserializable?(value) ? class_vars : class_vars.merge(var.to_s.sub('@@', '') => dump_structure(value))
|
128
|
-
end
|
125
|
+
structure[:_class_variables] = dump_class_variables_hash(object)
|
129
126
|
structure.delete(:_class_variables) if structure[:_class_variables].empty?
|
130
127
|
end
|
131
128
|
structure
|
@@ -134,10 +131,7 @@ module YASL
|
|
134
131
|
def dump_instance_variables(object)
|
135
132
|
structure = {}
|
136
133
|
if !object.instance_variables.empty?
|
137
|
-
structure[:_instance_variables] = object
|
138
|
-
value = object.instance_variable_get(var)
|
139
|
-
unserializable?(value) ? instance_vars : instance_vars.merge(var.to_s.sub('@', '') => dump_structure(value))
|
140
|
-
end
|
134
|
+
structure[:_instance_variables] = dump_instance_variables_hash(object)
|
141
135
|
structure.delete(:_instance_variables) if structure[:_instance_variables].empty?
|
142
136
|
end
|
143
137
|
structure
|
@@ -146,17 +140,14 @@ module YASL
|
|
146
140
|
def dump_struct_member_values(object)
|
147
141
|
structure = {}
|
148
142
|
if object.is_a?(Struct)
|
149
|
-
structure[:_struct_member_values] = object
|
150
|
-
value = object[member]
|
151
|
-
value.nil? || unserializable?(value) ? member_values : member_values.merge(member => dump_structure(value))
|
152
|
-
end
|
143
|
+
structure[:_struct_member_values] = dump_struct_member_values_hash(object)
|
153
144
|
structure.delete(:_struct_member_values) if structure[:_struct_member_values].empty?
|
154
145
|
end
|
155
146
|
structure
|
156
147
|
end
|
157
148
|
|
158
149
|
def unserializable?(value)
|
159
|
-
result = UNSERIALIZABLE_DATA_TYPES.detect {|class_name| value.class.ancestors.map(&:name).include?(class_name)}
|
150
|
+
result = UNSERIALIZABLE_DATA_TYPES.detect {|class_name| value.class.ancestors.map(&:name).include?(class_name.to_s)}
|
160
151
|
result = ((value.is_a?(Class) || value.is_a?(Module)) && value.name.nil?) if result.nil?
|
161
152
|
result
|
162
153
|
end
|
@@ -181,6 +172,27 @@ module YASL
|
|
181
172
|
(object_class_array_index + 1) unless object_class_array_index.nil?
|
182
173
|
end
|
183
174
|
|
175
|
+
def dump_class_variables_hash(object)
|
176
|
+
object.class_variables.reduce({}) do |class_vars, var|
|
177
|
+
value = object.class_variable_get(var)
|
178
|
+
unserializable?(value) ? class_vars : class_vars.merge(var.to_s.sub('@@', '') => dump_structure(value))
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def dump_instance_variables_hash(object)
|
183
|
+
object.instance_variables.sort.reduce({}) do |instance_vars, var|
|
184
|
+
value = object.instance_variable_get(var)
|
185
|
+
unserializable?(value) ? instance_vars : instance_vars.merge(var.to_s.sub('@', '') => dump_structure(value))
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
def dump_struct_member_values_hash(object)
|
190
|
+
object.members.reduce({}) do |member_values, member|
|
191
|
+
value = object[member]
|
192
|
+
value.nil? || unserializable?(value) ? member_values : member_values.merge(member => dump_structure(value))
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
184
196
|
def add_to_class_array(object)
|
185
197
|
object_class = class_for(object)
|
186
198
|
class_objects[object_class] ||= []
|
@@ -188,14 +200,6 @@ module YASL
|
|
188
200
|
class_objects[object_class].index(object) + 1
|
189
201
|
end
|
190
202
|
|
191
|
-
def class_ancestor_names_include?(*class_names)
|
192
|
-
lambda do |object|
|
193
|
-
class_names.reduce(false) do |result, class_name|
|
194
|
-
result || object.class.ancestors.map(&:name).include?(class_name)
|
195
|
-
end
|
196
|
-
end
|
197
|
-
end
|
198
|
-
|
199
203
|
end
|
200
204
|
|
201
205
|
end
|
data/lib/yasl/ext/struct.rb
CHANGED
@@ -24,10 +24,8 @@ Object.send(:remove_const, :Struct) if Object.constants.include?(:Struct)
|
|
24
24
|
# Optional re-implmentation of Struct in Pure Ruby (to get around JS issues in Opal Struct)
|
25
25
|
class Struct
|
26
26
|
class << self
|
27
|
-
|
28
|
-
|
29
|
-
attributes = class_name_and_or_attributes.compact.map(&:to_s).map(&:to_sym)
|
30
|
-
struct_class = Class.new do
|
27
|
+
CLASS_DEFINITION_FOR_ATTRIBUTES = lambda do |attributes, keyword_init|
|
28
|
+
lambda do |defined_class|
|
31
29
|
members_array = attributes
|
32
30
|
|
33
31
|
define_method(:members) do
|
@@ -72,16 +70,23 @@ class Struct
|
|
72
70
|
end
|
73
71
|
|
74
72
|
def select(&block)
|
75
|
-
|
73
|
+
to_a.select(&block)
|
76
74
|
end
|
77
75
|
|
78
76
|
def eql?(other)
|
79
|
-
instance_of?(other.class) &&
|
77
|
+
instance_of?(other.class) &&
|
78
|
+
members.all? { |key| self[key].eql?(other[key]) }
|
80
79
|
end
|
81
80
|
|
82
81
|
def ==(other)
|
83
82
|
other = coerce(other).first if respond_to?(:coerce, true)
|
84
|
-
other.kind_of?(self.class) &&
|
83
|
+
other.kind_of?(self.class) &&
|
84
|
+
members.all? { |key| self[key] == other[key] }
|
85
|
+
end
|
86
|
+
|
87
|
+
def hash
|
88
|
+
self.class.hash +
|
89
|
+
to_a.each_with_index.map {|value, i| (i+1) * value.hash}.sum
|
85
90
|
end
|
86
91
|
|
87
92
|
if keyword_init
|
@@ -117,11 +122,27 @@ class Struct
|
|
117
122
|
end
|
118
123
|
end
|
119
124
|
end
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
125
|
+
end
|
126
|
+
|
127
|
+
ARG_VALIDATION = lambda do |class_name_or_attribute, *attributes|
|
128
|
+
class_name_or_attribute.nil? || attributes.any?(&:nil?)
|
129
|
+
end
|
130
|
+
|
131
|
+
CLASS_NAME_EXTRACTION = lambda do |class_name_or_attribute|
|
132
|
+
if class_name_or_attribute.is_a?(String)
|
133
|
+
raise NameError, "identifier name needs to be constant" unless class_name_or_attribute.match(/^[A-Z]/)
|
134
|
+
class_name_or_attribute
|
124
135
|
end
|
125
136
|
end
|
137
|
+
|
138
|
+
def new(class_name_or_attribute, *attributes, keyword_init: false)
|
139
|
+
raise 'Arguments cannot be nil' if ARG_VALIDATION[class_name_or_attribute, *attributes]
|
140
|
+
class_name = CLASS_NAME_EXTRACTION[class_name_or_attribute]
|
141
|
+
attributes.unshift(class_name_or_attribute) if class_name.nil?
|
142
|
+
attributes = attributes.map(&:to_sym)
|
143
|
+
struct_class = Class.new(&CLASS_DEFINITION_FOR_ATTRIBUTES[attributes, keyword_init])
|
144
|
+
class_name.nil? ? struct_class : const_set(class_name, struct_class)
|
145
|
+
end
|
146
|
+
|
126
147
|
end
|
127
148
|
end
|
data/lib/yasl/loader.rb
CHANGED
@@ -46,15 +46,23 @@ module YASL
|
|
46
46
|
class_for(structure['_class'])
|
47
47
|
elsif YASL.json_basic_data_type?(structure) && !(structure.is_a?(String) && structure.start_with?('_'))
|
48
48
|
structure
|
49
|
-
elsif (structure
|
49
|
+
elsif ruby_basic_data_type_structure?(structure)
|
50
50
|
load_ruby_basic_data_type_object(structure['_class'], structure['_data'])
|
51
|
-
elsif
|
51
|
+
elsif load_non_basic_data_type_structure?(structure)
|
52
52
|
load_non_basic_data_type_object(structure)
|
53
53
|
end
|
54
54
|
end
|
55
55
|
|
56
56
|
private
|
57
57
|
|
58
|
+
def ruby_basic_data_type_structure?(structure)
|
59
|
+
(structure['_class'] && (structure['_data'] || !structure.keys.detect {|key| key.start_with?('_') && key != '_class'} ))
|
60
|
+
end
|
61
|
+
|
62
|
+
def load_non_basic_data_type_structure?(structure)
|
63
|
+
structure['_class'] && (structure['_id'] || structure['_instance_variables'] || structure['_class_variables'] || structure['_struct_member_values'])
|
64
|
+
end
|
65
|
+
|
58
66
|
def load_non_basic_data_type_object(structure, for_classes: false)
|
59
67
|
class_name = structure['_class']
|
60
68
|
object_class = class_for(class_name)
|
@@ -62,20 +70,11 @@ module YASL
|
|
62
70
|
object = object_for_id(object_class, structure['_id'])
|
63
71
|
if object.nil?
|
64
72
|
object = for_classes ? object_class : object_class.new
|
65
|
-
add_to_class_array(object, structure['_id'])
|
66
|
-
end
|
67
|
-
structure['_instance_variables'].to_a.each do |instance_var, value|
|
68
|
-
value = load_structure(value)
|
69
|
-
object.instance_variable_set("@#{instance_var}".to_sym, value)
|
70
|
-
end
|
71
|
-
structure['_struct_member_values'].to_a.each do |member, value|
|
72
|
-
value = load_structure(value)
|
73
|
-
object[member.to_sym] = value
|
74
|
-
end
|
75
|
-
structure['_class_variables'].to_a.each do |class_var, value|
|
76
|
-
value = load_structure(value)
|
77
|
-
object.class_variable_set("@@#{class_var}".to_sym, value)
|
73
|
+
add_to_class_array(object, structure['_id'])
|
78
74
|
end
|
75
|
+
structure['_class_variables'].to_a.each { |class_var, value| object.class_variable_set("@@#{class_var}".to_sym, load_structure(value)) }
|
76
|
+
structure['_instance_variables'].to_a.each { |instance_var, value| object.instance_variable_set("@#{instance_var}".to_sym, load_structure(value)) }
|
77
|
+
structure['_struct_member_values'].to_a.each { |member, value| load_struct_member_value(object, member, value) }
|
79
78
|
object
|
80
79
|
ensure
|
81
80
|
object_class&.define_method(:initialize, object_class.instance_method(:initialize_without_yasl))
|
@@ -106,9 +105,7 @@ module YASL
|
|
106
105
|
when 'Array'
|
107
106
|
data.map {|element| load_structure(element)}
|
108
107
|
when 'Hash'
|
109
|
-
data.reduce({})
|
110
|
-
new_hash.merge(load_structure(pair.first) => load_structure(pair.last))
|
111
|
-
end
|
108
|
+
data.reduce({}) { |new_hash, pair| new_hash.merge(load_structure(pair.first) => load_structure(pair.last)) }
|
112
109
|
end
|
113
110
|
end
|
114
111
|
|
@@ -120,7 +117,7 @@ module YASL
|
|
120
117
|
object_class = class_name_components.reduce(Object) do |result_class, class_name|
|
121
118
|
result_class.const_get(class_name)
|
122
119
|
end
|
123
|
-
if
|
120
|
+
if ![@whitelist_classes].compact.flatten.map(&:to_s).include?(object_class.to_s)
|
124
121
|
raise "Class `#{class_name}` is not mentioned in `whitelist_classes` (e.g. `YASL.load(data, whitelist_classes: [#{class_name}])`)!"
|
125
122
|
end
|
126
123
|
object_class
|
@@ -133,10 +130,6 @@ module YASL
|
|
133
130
|
structure && structure.is_a?(Hash) && structure['_class'] && structure['_id'].nil? && structure['_instance_variables'].nil? && structure['_class_variables'].nil? && structure['_struct_member_values'].nil? && structure['_data'].nil?
|
134
131
|
end
|
135
132
|
|
136
|
-
def add_to_classes(object)
|
137
|
-
classes << object unless classes.include?(object)
|
138
|
-
end
|
139
|
-
|
140
133
|
def class_objects_for(object_class)
|
141
134
|
class_objects[object_class] ||= {}
|
142
135
|
end
|
@@ -148,11 +141,20 @@ module YASL
|
|
148
141
|
end
|
149
142
|
|
150
143
|
def add_to_class_array(object, class_object_id)
|
151
|
-
return if class_object_id.nil?
|
144
|
+
return if object.is_a?(Class) || object.is_a?(Module) || class_object_id.nil?
|
152
145
|
object_class = object.class
|
153
146
|
found_object = object_for_id(object_class, class_object_id)
|
154
147
|
class_objects_for(object_class)[class_object_id.to_i] = object unless found_object
|
155
148
|
end
|
156
149
|
|
150
|
+
def load_struct_member_value(object, member, value)
|
151
|
+
value = load_structure(value)
|
152
|
+
begin
|
153
|
+
object[member.to_sym] = value
|
154
|
+
rescue => e
|
155
|
+
puts "#{e.message}. Setting `@#{member}` instance variable instead."
|
156
|
+
object.instance_variable_set("@#{member}".to_sym, value)
|
157
|
+
end
|
158
|
+
end
|
157
159
|
end
|
158
160
|
end
|
data/yasl.gemspec
CHANGED
@@ -2,16 +2,16 @@
|
|
2
2
|
# DO NOT EDIT THIS FILE DIRECTLY
|
3
3
|
# Instead, edit Juwelier::Tasks in Rakefile, and run 'rake gemspec'
|
4
4
|
# -*- encoding: utf-8 -*-
|
5
|
-
# stub: yasl 0.2.
|
5
|
+
# stub: yasl 0.2.1 ruby lib
|
6
6
|
|
7
7
|
Gem::Specification.new do |s|
|
8
8
|
s.name = "yasl".freeze
|
9
|
-
s.version = "0.2.
|
9
|
+
s.version = "0.2.1"
|
10
10
|
|
11
11
|
s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
|
12
12
|
s.require_paths = ["lib".freeze]
|
13
13
|
s.authors = ["Andy Maleh".freeze]
|
14
|
-
s.date = "
|
14
|
+
s.date = "2021-01-01"
|
15
15
|
s.description = "A pure Ruby serialization library that works across different Ruby implementations like Opal and JRuby as an alternative to YAML/Marshal.".freeze
|
16
16
|
s.email = "andy.am@gmail.com".freeze
|
17
17
|
s.extra_rdoc_files = [
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: yasl
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andy Maleh
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-01-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rspec
|