icss 0.1.3 → 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (98) hide show
  1. data/.watchr +35 -3
  2. data/CHANGELOG.md +38 -0
  3. data/Gemfile +19 -14
  4. data/README.md +296 -0
  5. data/Rakefile +2 -6
  6. data/TODO.md +13 -0
  7. data/VERSION +1 -1
  8. data/examples/avro_examples/complicated.icss.yaml +14 -13
  9. data/examples/bnc.icss.yaml +70 -0
  10. data/examples/chronic.icss.yaml +3 -3
  11. data/examples/license.icss.yaml +7 -0
  12. data/examples/source1.icss.yaml +4 -0
  13. data/examples/source2.icss.yaml +4 -0
  14. data/examples/test_icss.yaml +67 -0
  15. data/icss.gemspec +103 -43
  16. data/lib/icss.rb +37 -15
  17. data/lib/icss/core_types.rb +19 -0
  18. data/lib/icss/error.rb +4 -0
  19. data/{init.rb → lib/icss/init.rb} +0 -0
  20. data/lib/icss/message.rb +124 -66
  21. data/lib/icss/message/message_sample.rb +144 -0
  22. data/lib/icss/protocol.rb +184 -131
  23. data/lib/icss/protocol/code_asset.rb +18 -0
  24. data/lib/icss/protocol/data_asset.rb +23 -0
  25. data/lib/icss/protocol/license.rb +41 -0
  26. data/lib/icss/protocol/source.rb +37 -0
  27. data/lib/icss/protocol/target.rb +68 -0
  28. data/lib/icss/receiver_model.rb +24 -0
  29. data/lib/icss/receiver_model/active_model_shim.rb +36 -0
  30. data/lib/icss/receiver_model/acts_as_catalog.rb +170 -0
  31. data/lib/icss/receiver_model/acts_as_hash.rb +177 -0
  32. data/lib/icss/receiver_model/acts_as_loadable.rb +47 -0
  33. data/lib/icss/receiver_model/acts_as_tuple.rb +100 -0
  34. data/lib/icss/receiver_model/locale/en.yml +27 -0
  35. data/lib/icss/receiver_model/to_geo_json.rb +19 -0
  36. data/lib/icss/receiver_model/tree_merge.rb +34 -0
  37. data/lib/icss/receiver_model/validations.rb +31 -0
  38. data/lib/icss/serialization.rb +51 -0
  39. data/lib/icss/serialization/zaml.rb +443 -0
  40. data/lib/icss/type.rb +148 -501
  41. data/lib/icss/type/base_type.rb +0 -0
  42. data/lib/icss/type/named_type.rb +184 -0
  43. data/lib/icss/type/record_field.rb +77 -0
  44. data/lib/icss/type/record_model.rb +49 -0
  45. data/lib/icss/type/record_schema.rb +54 -0
  46. data/lib/icss/type/record_type.rb +325 -0
  47. data/lib/icss/type/simple_types.rb +72 -0
  48. data/lib/icss/type/structured_schema.rb +288 -0
  49. data/lib/icss/type/type_factory.rb +144 -0
  50. data/lib/icss/type/union_schema.rb +41 -0
  51. data/lib/icss/view_helper.rb +56 -19
  52. data/notes/named_array.md +32 -0
  53. data/notes/on_include_vs_extend_etc.rb +176 -0
  54. data/notes/technical_details.md +278 -0
  55. data/spec/core_types_spec.rb +119 -0
  56. data/spec/fixtures/zaml_complex_hash.yaml +35 -0
  57. data/spec/icss_spec.rb +86 -23
  58. data/spec/message/message_sample_spec.rb +4 -0
  59. data/spec/message_spec.rb +139 -0
  60. data/spec/protocol/license_spec.rb +67 -0
  61. data/spec/protocol/protocol_catalog_spec.rb +48 -0
  62. data/spec/protocol/protocol_validations_spec.rb +176 -0
  63. data/spec/protocol/source_spec.rb +65 -0
  64. data/spec/protocol_spec.rb +91 -37
  65. data/spec/receiver_model_spec.rb +111 -0
  66. data/spec/serialization/zaml_spec.rb +81 -0
  67. data/spec/serialization/zaml_test.rb +473 -0
  68. data/spec/serialization_spec.rb +63 -0
  69. data/spec/spec_helper.rb +24 -7
  70. data/spec/support/icss_test_helper.rb +67 -0
  71. data/spec/support/load_example_protocols.rb +17 -0
  72. data/spec/type/base_type_spec.rb +0 -0
  73. data/spec/type/named_type_spec.rb +75 -0
  74. data/spec/type/record_field_spec.rb +44 -0
  75. data/spec/type/record_model_spec.rb +206 -0
  76. data/spec/type/record_schema_spec.rb +161 -0
  77. data/spec/type/record_type_spec.rb +155 -0
  78. data/spec/type/simple_types_spec.rb +121 -0
  79. data/spec/type/structured_schema_spec.rb +300 -0
  80. data/spec/type/type_catalog_spec.rb +44 -0
  81. data/spec/type/type_factory_spec.rb +93 -0
  82. data/spec/type/union_schema_spec.rb +0 -0
  83. data/spec/type_spec.rb +63 -0
  84. metadata +205 -144
  85. data/CHANGELOG.textile +0 -9
  86. data/Gemfile.lock +0 -40
  87. data/README.textile +0 -29
  88. data/lib/icss/brevity.rb +0 -136
  89. data/lib/icss/code_asset.rb +0 -16
  90. data/lib/icss/core_ext.rb +0 -9
  91. data/lib/icss/data_asset.rb +0 -22
  92. data/lib/icss/old.rb +0 -96
  93. data/lib/icss/protocol_set.rb +0 -48
  94. data/lib/icss/sample_message_call.rb +0 -142
  95. data/lib/icss/target.rb +0 -72
  96. data/lib/icss/type/factory.rb +0 -196
  97. data/lib/icss/validations.rb +0 -16
  98. data/spec/validations_spec.rb +0 -171
@@ -0,0 +1,47 @@
1
+ require 'yaml'
2
+ require 'json' unless defined?(JSON)
3
+
4
+ module Icss
5
+ module ReceiverModel
6
+
7
+ #
8
+ # adds methods to load and store from json, yaml or magic
9
+ #
10
+ # This will require 'json' UNLESS you have already included something (so if
11
+ # you want to say require 'yajl/json_gem' then do that first).
12
+ #
13
+ module ActsAsLoadable
14
+ module ClassMethods
15
+
16
+ # module ::Icss::ReceiverModel::ClassMethods
17
+ # include Icss::ReceiverModel::ActsAsLoadable::ClassMethods
18
+ # end
19
+
20
+ def receive_json stream
21
+ receive(JSON.load(stream))
22
+ end
23
+
24
+ def receive_yaml stream
25
+ receive(YAML.load(stream))
26
+ end
27
+
28
+ #
29
+ # The file is loaded with
30
+ # * YAML if the filename ends in .yaml or .yml
31
+ # * JSON otherwise
32
+ #
33
+ def receive_from_file filename
34
+ stream = File.open(filename)
35
+ (filename =~ /.ya?ml$/) ? receive_yaml(stream) : receive_json(stream)
36
+ end
37
+ end
38
+
39
+ def merge_from_file! filename
40
+ other_obj = self.class.receive_from_file(filename)
41
+ tree_merge! other_obj
42
+ end
43
+ end
44
+
45
+ end
46
+
47
+ end
@@ -0,0 +1,100 @@
1
+ module Icss
2
+ module ReceiverModel
3
+ module ActsAsTuple
4
+
5
+ def to_tuple
6
+ tuple = []
7
+ self.each_value do |val|
8
+ if val.respond_to?(:to_tuple)
9
+ tuple += val.to_tuple
10
+ else
11
+ tuple << val
12
+ end
13
+ end
14
+ tuple
15
+ end
16
+
17
+ module ClassMethods
18
+
19
+ # returns a depth-first traversal of the object's fields' keys, as Strings:
20
+ #
21
+ # class Address < Icss::Thing
22
+ # field(:housenum, Integer)
23
+ # field(:street, String)
24
+ # end
25
+ # class Person < Icss::Thing
26
+ # field(:full_name, String)
27
+ # field(:street_address, Address)
28
+ # end
29
+ # Person.tuple_keys
30
+ # # => ['fullname', 'street_address.housenum', 'street_address.street']
31
+ #
32
+ # @param [Integer] max_key_segments the maximum length of key (depth to
33
+ # recurse); a stark 3 by default.
34
+ def tuple_keys(max_key_segments=3)
35
+ tuple_fields(max_key_segments).map{|field_set| field_set.map(&:name).join('.') }
36
+ end
37
+
38
+ # returns a depth-first traversal of the object's fields, as RecordFields:
39
+ #
40
+ # class Address < Icss::Thing
41
+ # field(:housenum, Integer)
42
+ # field(:street, String)
43
+ # end
44
+ # class Person < Icss::Thing
45
+ # field(:full_name, String)
46
+ # field(:street_address, Address)
47
+ # end
48
+ # Person.tuple_keys
49
+ # # => [ [<RecordField name='fullname' ...>],
50
+ # # [<RecordField name='street_address' ...>, <RecordField name='housenum' ...>],
51
+ # # [<RecordField name='street_address' ...>, <RecordField name='street' ...>],
52
+ #
53
+ # Note that RecordField helpfully supplies a 'parent' attribute pointing to it parent record.
54
+ #
55
+ # @param [Integer] max_key_segments the maximum length of key (depth to
56
+ # recurse); a stark 3 by default.
57
+ #
58
+ def tuple_fields(max_key_segments=3)
59
+ # return @tuple_fields if @tuple_fields
60
+ @tuple_fields = field_schemas.flat_map do |fn, fld|
61
+ if (max_key_segments > 1) && fld[:type].respond_to?(:tuple_fields)
62
+ fld[:type].tuple_fields(max_key_segments-1).map{|subfield| [fld, subfield].flatten }
63
+ else
64
+ [[fld]]
65
+ end
66
+ end
67
+ end
68
+
69
+ # walks through the tuple, destructively consuming each value in a
70
+ # depth-first walk of the field tree:
71
+ #
72
+ # class Address < Icss::Thing
73
+ # field(:housenum, Integer)
74
+ # field(:street, String)
75
+ # end
76
+ # class Person < Icss::Thing
77
+ # field(:full_name, String)
78
+ # field(:street_address, Address)
79
+ # end
80
+ # Person.consume_tuple(1214, 'W 6th St', 'Joe the Chimp')
81
+ # # => #<Person street_address=#<Address housenum=1214, street='W 6th St'>, fullname='Joe the Chimp'>
82
+ #
83
+ def consume_tuple(tuple)
84
+ obj = self.new
85
+ fields.each do |field|
86
+ if field[:type].respond_to?(:consume_tuple)
87
+ val = field[:type].consume_tuple(tuple)
88
+ else
89
+ val = tuple.shift
90
+ end
91
+ obj.send("receive_#{field[:name]}", val) if val
92
+ end
93
+ obj.send(:run_after_receivers, {})
94
+ obj
95
+ end
96
+ end
97
+ def self.included(base) base.extend(Icss::ReceiverModel::ActsAsTuple::ClassMethods) ; end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,27 @@
1
+ en:
2
+ errors:
3
+ # The default format to use in full error messages.
4
+ format: "%{attribute} %{message}"
5
+
6
+ # The values :model, :attribute and :value are always available for interpolation
7
+ # The value :count is available when applicable. Can be used for pluralization.
8
+ messages:
9
+ inclusion: "is not included in the list"
10
+ exclusion: "is reserved"
11
+ invalid: "is invalid"
12
+ confirmation: "doesn't match confirmation"
13
+ accepted: "must be accepted"
14
+ empty: "can't be empty"
15
+ blank: "can't be blank"
16
+ too_long: "is too long (maximum is %{count} characters)"
17
+ too_short: "is too short (minimum is %{count} characters)"
18
+ wrong_length: "is the wrong length (should be %{count} characters)"
19
+ not_a_number: "is not a number"
20
+ not_an_integer: "must be an integer"
21
+ greater_than: "must be greater than %{count}"
22
+ greater_than_or_equal_to: "must be greater than or equal to %{count}"
23
+ equal_to: "must be equal to %{count}"
24
+ less_than: "must be less than %{count}"
25
+ less_than_or_equal_to: "must be less than or equal to %{count}"
26
+ odd: "must be odd"
27
+ even: "must be even"
@@ -0,0 +1,19 @@
1
+ def test_icss
2
+ return <<EOF
3
+ ---
4
+ namespace: foo.bar
5
+ protocol: baz
6
+ types:
7
+
8
+ - name: place
9
+ doc: Foo bar place
10
+ type: record
11
+ fields:
12
+ - name: name
13
+ doc: Your name.
14
+ type: string
15
+ - name: website
16
+ doc: Your website.
17
+ type: url
18
+ EOF
19
+ end
@@ -0,0 +1,34 @@
1
+ module Icss
2
+ module ReceiverModel
3
+ # Recursively merges using receive
4
+ #
5
+ # Modifies the full receiver chain in-place.
6
+ #
7
+ # For each key in keys,
8
+ # * if self's value is nil, receive the attribute.
9
+ # * if self's attribute is an Array, append to it.
10
+ # * if self's value responds to tree_merge!, tree merge it.
11
+ # * if self's value responds_to merge!, merge! it.
12
+ # * otherwise, receive the value from other_hash
13
+ #
14
+ def tree_merge!(other_hash)
15
+ super(other_hash) do |key, self_val, other_val|
16
+ field = self.class.field_named(key)
17
+ if field && self_val.is_a?(Array) && field.has_key?(:indexed_on)
18
+ index_attr = field[:indexed_on]
19
+ other_val.each do |other_el|
20
+ other_el_name = other_el[index_attr] or next
21
+ self_el = self_val.find{|el| el[index_attr].to_s == other_el_name.to_s }
22
+ if self_el then self_el.tree_merge!(other_el)
23
+ else self_val << other_el
24
+ end
25
+ end
26
+ self_val
27
+ else
28
+ false
29
+ end
30
+ end
31
+ self
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,31 @@
1
+ module Icss
2
+ module ReceiverModel
3
+ module Validations
4
+ module ::Icss::ReceiverModel::ClassMethods
5
+ include ::Icss::ReceiverModel::Validations
6
+ end
7
+
8
+ #
9
+ # Sends the fields' validations on to Icss::Type::Validations.
10
+ # Uses syntax parallel to ActiveModel's:
11
+ #
12
+ # :presence => true
13
+ # :uniqueness => true
14
+ # :numericality => true
15
+ # :==, :>, :>=, :<, :<=, :odd?, :even?
16
+ # (and spelled out: :equal_to, :less_than_or_equal_to, :odd, etc)
17
+ # :length => { :minimum => 0, maximum => 2000 }
18
+ # :==, :>=, :<=, :is, :minimum, :maximum
19
+ # :format => { :with => /.*/ }
20
+ # :inclusion => { :in => [1,2,3] }
21
+ # :exclusion => { :in => [1,2,3] }
22
+ #
23
+ def add_validator(field_name)
24
+ field = field_named(field_name)
25
+ self.validates(field[:name], :presence => true ) if field[:required]
26
+ self.validates(field[:name], field[:validates] ) if field[:validates]
27
+ super(field_name) if defined?(super)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,51 @@
1
+ module Icss
2
+ module Meta
3
+ module RecordModel
4
+
5
+ def to_zaml(z=ZAML.new)
6
+ hsh = self.to_wire
7
+ hsh.to_zaml(z)
8
+ z.to_s
9
+ end
10
+
11
+ end
12
+ end
13
+ end
14
+
15
+ # module Icss
16
+ # module Meta
17
+ # class RecordField
18
+ # # def as_json()
19
+ # # { :name => name,
20
+ # # :type => expand_type,
21
+ # # :doc => doc,
22
+ # # :default => default,
23
+ # # :required => required,
24
+ # # :order => @order,
25
+ # # }.reject{|k,v| v.nil? }
26
+ # # end
27
+ #
28
+ # protected
29
+ # def expand_type
30
+ # case
31
+ # when is_reference? && type.respond_to?(:fullname) then type.fullname
32
+ # when is_reference? then '(_unspecified_)'
33
+ # when type.is_a?(Array) then type.map{|t| t.to_hash }
34
+ # when type.respond_to?(:to_hash) then type.to_hash
35
+ # else type.to_s
36
+ # end
37
+ # end
38
+ # end
39
+ # end
40
+ # end
41
+
42
+ # module Icss::ReceiverModel
43
+ # def as_json(*args)
44
+ # {}.tap{|hsh| self.each{|k,v| hsh[k] = (v.respond_to?(:as_json) ? v.as_json : v) } }
45
+ # # to_hash.compact_blank
46
+ # end
47
+ # def to_json(*args)
48
+ # as_json.to_json(*args)
49
+ # end
50
+ # end
51
+ #
@@ -0,0 +1,443 @@
1
+ # -*- encoding: utf-8 -*-
2
+ #
3
+ # ZAML -- A partial replacement for YAML, writen with speed and code clarity
4
+ # in mind. ZAML fixes one YAML bug (loading Exceptions) and provides
5
+ # a replacement for YAML.dump() unimaginatively called ZAML.dump(),
6
+ # which is faster on all known cases and an order of magnitude faster
7
+ # with complex structures.
8
+ #
9
+ # http://github.com/hallettj/zaml
10
+ #
11
+ # Authors: Markus Roberts, Jesse Hallett, Ian McIntosh, Igal Koshevoy, Simon Chiang
12
+ #
13
+
14
+ require 'yaml'
15
+
16
+ class ZAML
17
+ VERSION = "0.1.4m" unless defined?(::ZAML::VERSION)
18
+
19
+ attr_accessor :result, :indent
20
+ # line up simple value tokens at this vertical column
21
+ attr_accessor :valign
22
+
23
+ #
24
+ # Class Methods
25
+ #
26
+ def self.dump(stuff, where='', options={})
27
+ z = self.new(options)
28
+ stuff.to_zaml(z)
29
+ where << z.to_s
30
+ end
31
+
32
+ #
33
+ # Instance Methods
34
+ #
35
+ def initialize(options={})
36
+ reset!
37
+ self.valign = options[:valign]
38
+ end
39
+
40
+ def reset!
41
+ @result = []
42
+ @indent = nil
43
+ @structured_key_prefix = nil
44
+ Label.counter_reset
45
+ emit('--- ')
46
+ end
47
+
48
+ # for all code within the block, the cursor supplies
49
+ def nested(tail=' ')
50
+ old_indent = @indent
51
+ # @indent = "#{@indent || "\n"}#{tail}"
52
+ @indent = @indent ? "#{@indent}#{tail}" : "\n"
53
+ yield
54
+ @indent = old_indent
55
+ end
56
+
57
+ def vpad(sep, key)
58
+ return emit("#{sep} ") unless valign
59
+ if key.is_a?(Symbol) then str = key.inspect
60
+ elsif key.is_a?(String) then str = key
61
+ elsif key.is_a?(Numeric) then str = key.to_s
62
+ else return emit("#{sep} ") ; end
63
+ keylen = ((@indent||"\n").length - 1) + str.length + sep.length
64
+ vlen = valign - keylen
65
+ pad = (vlen > 1) ? (" "*vlen) : " "
66
+ emit(sep+pad)
67
+ end
68
+
69
+ def emit(s)
70
+ @result << s
71
+ @recent_nl = false unless s.kind_of?(Label)
72
+ self.to_s
73
+ end
74
+ def nl(s='')
75
+ emit(@indent || "\n") unless @recent_nl
76
+ emit(s)
77
+ @recent_nl = true
78
+ end
79
+ def prefix_structured_keys(x)
80
+ @structured_key_prefix = x
81
+ yield
82
+ nl unless @structured_key_prefix
83
+ @structured_key_prefix = nil
84
+ end
85
+
86
+ def new_label_for(obj)
87
+ Label.new(obj,(Hash === obj || Array === obj) ? "#{@indent || "\n"} " : ' ')
88
+ end
89
+ def first_time_only(obj)
90
+ if label = Label.for(obj)
91
+ emit(label.reference)
92
+ else
93
+ if @structured_key_prefix and not obj.is_a? String
94
+ emit(@structured_key_prefix)
95
+ @structured_key_prefix = nil
96
+ end
97
+ emit(new_label_for(obj))
98
+ yield
99
+ end
100
+ self.to_s
101
+ end
102
+
103
+ def to_s
104
+ @result.join.tap{|s| s << "\n" if s[-1..-1] != "\n" }
105
+ end
106
+ def inspect
107
+ res = to_s.inspect
108
+ res = res[0..29]+"..."+res[-20..-1] if res.length > 50
109
+ %Q{\#<ZAML ind=#{@indent.inspect} pfx=#{@structured_key_prefix} result='#{res}'>}
110
+ end
111
+
112
+ def self.padding(nlines)
113
+ Padding.new(nlines)
114
+ end
115
+
116
+ def no_comment(elt)
117
+ if elt.is_a?(ZAML::Comment) || elt.is_a?(ZAML::Padding)
118
+ elt.to_zaml(self)
119
+ else
120
+ yield
121
+ end
122
+ end
123
+
124
+ class Comment < String
125
+ def to_zaml(z=ZAML.new)
126
+ lines = self.split("\n",-1).map{|s| "# #{s}".strip }
127
+ lines.each{|line| z.nl ; z.emit(line) }
128
+ end
129
+ end
130
+
131
+ class Padding
132
+ def initialize(nlines)
133
+ @nlines = nlines
134
+ end
135
+ def to_zaml(z=ZAML.new)
136
+ @nlines.times{ z.nl ; z.emit('') }
137
+ z.to_s
138
+ end
139
+ end
140
+
141
+ #
142
+ # Label class -- resolves circular references
143
+ #
144
+ class Label
145
+ #
146
+ # YAML only wants objects in the datastream once; if the same object
147
+ # occurs more than once, we need to emit a label ("&idxxx") on the
148
+ # first occurrence and then emit a back reference (*idxxx") on any
149
+ # subsequent occurrence(s).
150
+ #
151
+ # To accomplish this we keeps a hash (by object id) of the labels of
152
+ # the things we serialize as we begin to serialize them. The labels
153
+ # initially serialize as an empty string (since most objects are only
154
+ # going to be be encountered once), but can be changed to a valid
155
+ # (by assigning it a number) the first time it is subsequently used,
156
+ # if it ever is. Note that we need to do the label setup BEFORE we
157
+ # start to serialize the object so that circular structures (in
158
+ # which we will encounter a reference to the object as we serialize
159
+ # it can be handled).
160
+ #
161
+ def self.counter_reset
162
+ @@previously_emitted_object = {}
163
+ @@next_free_label_number = 0
164
+ end
165
+ def initialize(obj,indent)
166
+ @indent = indent
167
+ @this_label_number = nil
168
+ @@previously_emitted_object[obj.object_id] = self
169
+ end
170
+ def to_s
171
+ @this_label_number ? ('&id%03d%s' % [@this_label_number, @indent]) : ''
172
+ end
173
+ def reference
174
+ @this_label_number ||= (@@next_free_label_number += 1)
175
+ @reference ||= '*id%03d' % @this_label_number
176
+ end
177
+ def self.for(obj)
178
+ @@previously_emitted_object[obj.object_id]
179
+ end
180
+ end
181
+
182
+ end
183
+
184
+ ################################################################
185
+ #
186
+ # Behavior for custom classes
187
+ #
188
+ ################################################################
189
+
190
+ class Object
191
+ def to_yaml_properties
192
+ instance_variables.sort # Default YAML behavior
193
+ end
194
+ def zamlized_class_name(root)
195
+ "!ruby/#{root.name.downcase}#{self.class == root ? '' : ":#{self.class.name}"}"
196
+ end
197
+ def to_zaml(z=ZAML.new)
198
+ z.first_time_only(self) {
199
+ z.emit(zamlized_class_name(Object))
200
+ z.nested {
201
+ instance_variables = to_yaml_properties
202
+ if instance_variables.empty?
203
+ z.emit(" {}")
204
+ else
205
+ instance_variables.each { |v|
206
+ z.nl
207
+ v[1..-1].to_zaml(z) # Remove leading '@'
208
+ z.emit(': ')
209
+ instance_variable_get(v).to_zaml(z)
210
+ }
211
+ end
212
+ }
213
+ }
214
+ end
215
+ end
216
+
217
+ ################################################################
218
+ #
219
+ # Behavior for built-in classes
220
+ #
221
+ ################################################################
222
+
223
+ class NilClass
224
+ def to_zaml(z=ZAML.new)
225
+ z.emit('') # NOTE: blank turns into nil in YAML.load
226
+ end
227
+ end
228
+
229
+ class Symbol
230
+ def to_zaml(z=ZAML.new)
231
+ z.emit(self.inspect)
232
+ end
233
+ end
234
+
235
+ class TrueClass
236
+ def to_zaml(z=ZAML.new)
237
+ z.emit('true')
238
+ end
239
+ end
240
+
241
+ class FalseClass
242
+ def to_zaml(z=ZAML.new)
243
+ z.emit('false')
244
+ end
245
+ end
246
+
247
+ class Numeric
248
+ def to_zaml(z=ZAML.new)
249
+ z.emit(self)
250
+ end
251
+ end
252
+
253
+ class Regexp
254
+ def to_zaml(z=ZAML.new)
255
+ z.first_time_only(self) { z.emit("#{zamlized_class_name(Regexp)} #{inspect}") }
256
+ end
257
+ end
258
+
259
+ class Exception
260
+ def to_zaml(z=ZAML.new)
261
+ z.emit(zamlized_class_name(Exception)+" ")
262
+ z.nested {
263
+ z.nl("message: ")
264
+ message.to_zaml(z)
265
+ }
266
+ z.to_s
267
+ end
268
+ #
269
+ # Monkey patch for buggy Exception restore in YAML
270
+ #
271
+ # This makes it work for now but is not very future-proof; if things
272
+ # change we'll most likely want to remove this. To mitigate the risks
273
+ # as much as possible, we test for the bug before appling the patch.
274
+ #
275
+ if respond_to? :yaml_new and yaml_new(self, :tag, "message" => "blurp").message != "blurp"
276
+ def self.yaml_new( klass, tag, val )
277
+ o = YAML.object_maker( klass, {} ).exception(val.delete( 'message'))
278
+ val.each_pair do |k,v|
279
+ o.instance_variable_set("@#{k}", v)
280
+ end
281
+ o
282
+ end
283
+ end
284
+ end
285
+
286
+ ZAML::NUM_RE = '[-+]?(0x)?\d+\.?\d*' unless defined?(::ZAML::NUM_RE)
287
+ ZAML::SIMPLE_STRING_RE = /\A(true|false|yes|no|on|null|off|#{ZAML::NUM_RE}(:#{ZAML::NUM_RE})*|!|=|~|>|\||\n+)\z/io # unless defined?(::ZAML::SIMPLE_STRING_RE)
288
+ ZAML::ZAML_ESCAPES = %w{\x00 \x01 \x02 \x03 \x04 \x05 \x06 \a \x08 \t \n \v \f \r \x0e \x0f \x10 \x11 \x12 \x13 \x14 \x15 \x16 \x17 \x18 \x19 \x1a \e \x1c \x1d \x1e \x1f } unless defined?(ZAML::ZAML_ESCAPES)
289
+
290
+ unless defined?(ZAML::HI_BIT_CHARS)
291
+ ZAML::HI_BIT_CHARS = '\x80-\xFF'
292
+ if RUBY_VERSION > "1.9" then ZAML::HI_BIT_CHARS.force_encoding('ASCII-8BIT') ; end
293
+ ZAML::HI_BIT_CHARS_RE = /([#{ZAML::HI_BIT_CHARS}])/o
294
+ end
295
+ ZAML::EXTENDED_CHARS_RE = /[\x00-\x08\x0B\x0C\x0E-\x1F]/o unless defined?(ZAML::EXTENDED_CHARS_RE)
296
+
297
+ class String
298
+ if RUBY_VERSION >= "1.9"
299
+ def escaped_for_zaml
300
+ gsub( /\x5C/, "\\\\\\" ). # Demi-kludge for Maglev/rubinius; the regexp should be /\\/ but parsetree chokes on that.
301
+ gsub( /"/, "\\\"" ).
302
+ gsub( /([\x00-\x1F])/ ){|x| ZAML::ZAML_ESCAPES[ x.unpack("C")[0] ] }
303
+ end
304
+ else
305
+ def escaped_for_zaml
306
+ gsub( /\x5C/, "\\\\\\" ). # Demi-kludge for Maglev/rubinius; the regexp should be /\\/ but parsetree chokes on that.
307
+ gsub( /"/, "\\\"" ).
308
+ gsub( /([\x00-\x1F])/ ){|x| ZAML::ZAML_ESCAPES[ x.unpack("C")[0] ] }.
309
+ gsub( ZAML::HI_BIT_CHARS_RE ){|x| "\\x#{x.unpack("C")[0].to_s(16)}" }
310
+ end
311
+ end
312
+
313
+ def classify_for_zaml
314
+ unless RUBY_VERSION >= "1.9"
315
+ return :escaped if (self =~ ZAML::HI_BIT_CHARS_RE)
316
+ end
317
+ case
318
+ when self == ''
319
+ :bare
320
+ when (self =~ ZAML::EXTENDED_CHARS_RE)
321
+ :escaped
322
+ when (
323
+ (self =~ ZAML::SIMPLE_STRING_RE) or
324
+ (self =~ /\A\n* /) or
325
+ (self =~ /[ \r]\s*\z/) or
326
+ (self =~ /^[>|][-+\d]*\s/i)
327
+ )
328
+ :escaped
329
+ when self =~ /\n/
330
+ :complex
331
+ when (
332
+ (self[-1..-1] =~ /\s/) or
333
+ (self =~ /[\s:]$/) or
334
+ (self =~ /[,\[\]\{\}\r\t]|:\s|\s#/) or
335
+ (self =~ /\A([-:?!#&*'"]|<<|%.+:.)/)
336
+ )
337
+ :escaped
338
+ else
339
+ :simple
340
+ end
341
+ end
342
+
343
+ def to_zaml(z=ZAML.new)
344
+ z.first_time_only(self) {
345
+ case cl = classify_for_zaml
346
+ when :bare then z.emit('""')
347
+ when :escaped then z.emit("\"#{escaped_for_zaml}\"")
348
+ when :simple then z.emit(self)
349
+ when :complex
350
+ lines = split("\n",-1)
351
+ self =~ /(\s+)\z/
352
+ if $1.nil? then z.emit('|-') ; lastline = ""
353
+ elsif $1 == "\n" then z.emit('|') ; lastline = "\n" ; lines.pop
354
+ else z.emit('|+') ; lastline = "" ; lines.pop ; end
355
+ z.nested{ lines.each{|line| z.nl; z.emit(line) } }
356
+ z.emit(lastline)
357
+ z.nl
358
+ else raise("Misclassified string: #{cl}!")
359
+ end
360
+ }
361
+ end
362
+
363
+ # z.emit("!binary |") ;
364
+ # z.nested{ z.nl; z.emit([self].pack("m72")) }
365
+ end
366
+
367
+ class Hash
368
+ def to_zaml(z=ZAML.new)
369
+ z.first_time_only(self) {
370
+ z.nested {
371
+ if empty? then z.emit('{}')
372
+ else
373
+ emitted = false
374
+ each_pair{|k, v|
375
+ z.no_comment(k){
376
+ emitted = true
377
+ z.nl
378
+ z.prefix_structured_keys('? '){ k.to_zaml(z) }
379
+ z.vpad(':', k)
380
+ v.to_zaml(z)
381
+ }
382
+ }
383
+ unless emitted then z.nl ; z.emit('{}') ; end
384
+ end
385
+ }
386
+ }
387
+ end
388
+ end
389
+
390
+
391
+ class Array
392
+ def to_zaml(z=ZAML.new)
393
+ z.first_time_only(self) {
394
+ z.nested {
395
+ if empty?
396
+ z.emit('[]')
397
+ else
398
+ emitted = false
399
+ each{|v| z.no_comment(v){
400
+ emitted = true
401
+ z.nl('- '); v.to_zaml(z)
402
+ } }
403
+ unless emitted then z.nl ; z.emit('[]') ; end
404
+ end
405
+ }
406
+ z.to_s
407
+ }
408
+ end
409
+ end
410
+
411
+ class Time
412
+ def to_zaml(z=ZAML.new)
413
+ # 2008-12-06 10:06:51.373758 -07:00
414
+ ms = ("%0.6f" % (usec * 1e-6)).sub(/^\d+\./,'')
415
+ offset = "%+0.2i:%0.2i" % [utc_offset / 3600, (utc_offset / 60) % 60]
416
+ z.emit(self.strftime("%Y-%m-%d %H:%M:%S.#{ms} #{offset}"))
417
+ end
418
+ end
419
+
420
+ class Date
421
+ def to_zaml(z=ZAML.new)
422
+ z.emit(strftime('%Y-%m-%d'))
423
+ end
424
+ end
425
+
426
+ class Range
427
+ def to_zaml(z=ZAML.new)
428
+ z.first_time_only(self) {
429
+ z.emit(zamlized_class_name(Range)+" ")
430
+ z.nested {
431
+ z.nl
432
+ z.emit('begin: ')
433
+ z.emit(first)
434
+ z.nl
435
+ z.emit('end: ')
436
+ z.emit(last)
437
+ z.nl
438
+ z.emit('excl: ')
439
+ z.emit(exclude_end?)
440
+ }
441
+ }
442
+ end
443
+ end