icss 0.1.3 → 0.3.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.watchr +35 -3
- data/CHANGELOG.md +38 -0
- data/Gemfile +19 -14
- data/README.md +296 -0
- data/Rakefile +2 -6
- data/TODO.md +13 -0
- data/VERSION +1 -1
- data/examples/avro_examples/complicated.icss.yaml +14 -13
- data/examples/bnc.icss.yaml +70 -0
- data/examples/chronic.icss.yaml +3 -3
- data/examples/license.icss.yaml +7 -0
- data/examples/source1.icss.yaml +4 -0
- data/examples/source2.icss.yaml +4 -0
- data/examples/test_icss.yaml +67 -0
- data/icss.gemspec +103 -43
- data/lib/icss.rb +37 -15
- data/lib/icss/core_types.rb +19 -0
- data/lib/icss/error.rb +4 -0
- data/{init.rb → lib/icss/init.rb} +0 -0
- data/lib/icss/message.rb +124 -66
- data/lib/icss/message/message_sample.rb +144 -0
- data/lib/icss/protocol.rb +184 -131
- data/lib/icss/protocol/code_asset.rb +18 -0
- data/lib/icss/protocol/data_asset.rb +23 -0
- data/lib/icss/protocol/license.rb +41 -0
- data/lib/icss/protocol/source.rb +37 -0
- data/lib/icss/protocol/target.rb +68 -0
- data/lib/icss/receiver_model.rb +24 -0
- data/lib/icss/receiver_model/active_model_shim.rb +36 -0
- data/lib/icss/receiver_model/acts_as_catalog.rb +170 -0
- data/lib/icss/receiver_model/acts_as_hash.rb +177 -0
- data/lib/icss/receiver_model/acts_as_loadable.rb +47 -0
- data/lib/icss/receiver_model/acts_as_tuple.rb +100 -0
- data/lib/icss/receiver_model/locale/en.yml +27 -0
- data/lib/icss/receiver_model/to_geo_json.rb +19 -0
- data/lib/icss/receiver_model/tree_merge.rb +34 -0
- data/lib/icss/receiver_model/validations.rb +31 -0
- data/lib/icss/serialization.rb +51 -0
- data/lib/icss/serialization/zaml.rb +443 -0
- data/lib/icss/type.rb +148 -501
- data/lib/icss/type/base_type.rb +0 -0
- data/lib/icss/type/named_type.rb +184 -0
- data/lib/icss/type/record_field.rb +77 -0
- data/lib/icss/type/record_model.rb +49 -0
- data/lib/icss/type/record_schema.rb +54 -0
- data/lib/icss/type/record_type.rb +325 -0
- data/lib/icss/type/simple_types.rb +72 -0
- data/lib/icss/type/structured_schema.rb +288 -0
- data/lib/icss/type/type_factory.rb +144 -0
- data/lib/icss/type/union_schema.rb +41 -0
- data/lib/icss/view_helper.rb +56 -19
- data/notes/named_array.md +32 -0
- data/notes/on_include_vs_extend_etc.rb +176 -0
- data/notes/technical_details.md +278 -0
- data/spec/core_types_spec.rb +119 -0
- data/spec/fixtures/zaml_complex_hash.yaml +35 -0
- data/spec/icss_spec.rb +86 -23
- data/spec/message/message_sample_spec.rb +4 -0
- data/spec/message_spec.rb +139 -0
- data/spec/protocol/license_spec.rb +67 -0
- data/spec/protocol/protocol_catalog_spec.rb +48 -0
- data/spec/protocol/protocol_validations_spec.rb +176 -0
- data/spec/protocol/source_spec.rb +65 -0
- data/spec/protocol_spec.rb +91 -37
- data/spec/receiver_model_spec.rb +111 -0
- data/spec/serialization/zaml_spec.rb +81 -0
- data/spec/serialization/zaml_test.rb +473 -0
- data/spec/serialization_spec.rb +63 -0
- data/spec/spec_helper.rb +24 -7
- data/spec/support/icss_test_helper.rb +67 -0
- data/spec/support/load_example_protocols.rb +17 -0
- data/spec/type/base_type_spec.rb +0 -0
- data/spec/type/named_type_spec.rb +75 -0
- data/spec/type/record_field_spec.rb +44 -0
- data/spec/type/record_model_spec.rb +206 -0
- data/spec/type/record_schema_spec.rb +161 -0
- data/spec/type/record_type_spec.rb +155 -0
- data/spec/type/simple_types_spec.rb +121 -0
- data/spec/type/structured_schema_spec.rb +300 -0
- data/spec/type/type_catalog_spec.rb +44 -0
- data/spec/type/type_factory_spec.rb +93 -0
- data/spec/type/union_schema_spec.rb +0 -0
- data/spec/type_spec.rb +63 -0
- metadata +205 -144
- data/CHANGELOG.textile +0 -9
- data/Gemfile.lock +0 -40
- data/README.textile +0 -29
- data/lib/icss/brevity.rb +0 -136
- data/lib/icss/code_asset.rb +0 -16
- data/lib/icss/core_ext.rb +0 -9
- data/lib/icss/data_asset.rb +0 -22
- data/lib/icss/old.rb +0 -96
- data/lib/icss/protocol_set.rb +0 -48
- data/lib/icss/sample_message_call.rb +0 -142
- data/lib/icss/target.rb +0 -72
- data/lib/icss/type/factory.rb +0 -196
- data/lib/icss/validations.rb +0 -16
- 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
|