icss 0.1.3 → 0.3.2
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.
- 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,18 @@
|
|
1
|
+
module Icss
|
2
|
+
module Meta
|
3
|
+
class CodeAsset
|
4
|
+
include Icss::ReceiverModel
|
5
|
+
|
6
|
+
field :name, String
|
7
|
+
field :location, String
|
8
|
+
|
9
|
+
def to_hash()
|
10
|
+
{ :name => name, :location => location}
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_json() to_hash.to_json ; end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Icss
|
2
|
+
module Meta
|
3
|
+
class DataAsset
|
4
|
+
include Icss::ReceiverModel
|
5
|
+
|
6
|
+
field :name, String
|
7
|
+
field :location, String
|
8
|
+
# overriding ruby's deprecated but still present type attr on objects
|
9
|
+
attr_accessor :type
|
10
|
+
field :type, String
|
11
|
+
field :doc, String
|
12
|
+
|
13
|
+
def named? nm
|
14
|
+
name == nm
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_hash()
|
18
|
+
{ :name => name, :location => location, :type => type, :doc => doc }
|
19
|
+
end
|
20
|
+
def to_json() to_hash.to_json ; end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Icss
|
2
|
+
module Meta
|
3
|
+
class License
|
4
|
+
include Icss::ReceiverModel
|
5
|
+
include Icss::ReceiverModel::ActsAsCatalog
|
6
|
+
|
7
|
+
field :license_id, String
|
8
|
+
field :title, String
|
9
|
+
field :url, String
|
10
|
+
field :description, String
|
11
|
+
field :summary, String
|
12
|
+
field :article_body, String
|
13
|
+
|
14
|
+
def fullname
|
15
|
+
license_id
|
16
|
+
end
|
17
|
+
|
18
|
+
def name
|
19
|
+
license_id.split('.').last
|
20
|
+
end
|
21
|
+
alias_method :basename, :name
|
22
|
+
|
23
|
+
def self.catalog_sections
|
24
|
+
['licenses']
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_hash()
|
28
|
+
{ :license_id => license_id,
|
29
|
+
:title => title,
|
30
|
+
:url => url,
|
31
|
+
:description => description,
|
32
|
+
:summary => summary,
|
33
|
+
:article_body => article_body }
|
34
|
+
end
|
35
|
+
|
36
|
+
def to_json() to_hash.to_json ; end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Icss
|
2
|
+
module Meta
|
3
|
+
class Source
|
4
|
+
include Icss::ReceiverModel
|
5
|
+
include Icss::ReceiverModel::ActsAsCatalog
|
6
|
+
|
7
|
+
field :source_id, String
|
8
|
+
field :title, String
|
9
|
+
field :description, String
|
10
|
+
field :url, String
|
11
|
+
|
12
|
+
def fullname
|
13
|
+
source_id
|
14
|
+
end
|
15
|
+
|
16
|
+
def name
|
17
|
+
source_id.split('.').last
|
18
|
+
end
|
19
|
+
alias_method :basename, :name
|
20
|
+
|
21
|
+
def self.catalog_sections
|
22
|
+
['sources']
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_hash()
|
26
|
+
{ :source_id => source_id,
|
27
|
+
:title => title,
|
28
|
+
:description => description,
|
29
|
+
:url => url }
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_json() to_hash.to_json ; end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Icss
|
2
|
+
|
3
|
+
#
|
4
|
+
# Instantiates an array of target objects
|
5
|
+
#
|
6
|
+
class TargetListFactory
|
7
|
+
def self.receive target_info_list, target_name
|
8
|
+
klass = "Icss::#{target_name.to_s.camelize}Target".constantize
|
9
|
+
target_info_list.map{|target_info| klass.receive(target_info)}
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class Target
|
14
|
+
include Icss::ReceiverModel
|
15
|
+
field :name, String
|
16
|
+
alias_method :basename, :name
|
17
|
+
end
|
18
|
+
|
19
|
+
class MysqlTarget < Target
|
20
|
+
field :data_assets, Array, :items => String
|
21
|
+
field :database, String
|
22
|
+
field :table_name, String
|
23
|
+
end
|
24
|
+
|
25
|
+
class ApeyeyeTarget < Target
|
26
|
+
field :code_assets, Array, :items => String
|
27
|
+
end
|
28
|
+
|
29
|
+
class HbaseTarget < Target
|
30
|
+
field :data_assets, Array, :items => String
|
31
|
+
field :table_name, String
|
32
|
+
field :column_families, Array, :items => String
|
33
|
+
field :column_family, String
|
34
|
+
field :loader, String
|
35
|
+
field :id_field, String
|
36
|
+
end
|
37
|
+
|
38
|
+
class ElasticSearchTarget < Target
|
39
|
+
field :data_assets, Array, :items => String
|
40
|
+
field :index_name, String
|
41
|
+
field :id_field, String
|
42
|
+
field :object_type, String
|
43
|
+
field :loader, String
|
44
|
+
end
|
45
|
+
|
46
|
+
class GeoIndexTarget < Target
|
47
|
+
field :data_assets, Array, :items => String
|
48
|
+
field :table_name, String
|
49
|
+
field :min_zoom, Integer
|
50
|
+
field :max_zoom, Integer
|
51
|
+
field :chars_per_page, Integer
|
52
|
+
field :sort_field, String
|
53
|
+
end
|
54
|
+
|
55
|
+
class CatalogTarget < Target
|
56
|
+
field :name, String
|
57
|
+
field :license, String
|
58
|
+
field :title, String
|
59
|
+
field :link, String
|
60
|
+
field :description, String
|
61
|
+
field :owner, String
|
62
|
+
field :price, Float
|
63
|
+
field :tags, Array, :items => String
|
64
|
+
field :messages, Array, :items => String
|
65
|
+
field :packages, Array, :items => { :type => Hash, :values => Object }
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Icss
|
2
|
+
module ReceiverModel
|
3
|
+
include Gorillib::Hashlike
|
4
|
+
include Icss::ReceiverModel::ActsAsHash
|
5
|
+
include Gorillib::Hashlike::TreeMerge
|
6
|
+
include Icss::ReceiverModel::ActsAsLoadable
|
7
|
+
include Icss::Meta::RecordModel
|
8
|
+
include Icss::ReceiverModel::ActsAsTuple
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
include Icss::Meta::RecordType
|
12
|
+
include Icss::ReceiverModel::ActsAsTuple::ClassMethods
|
13
|
+
end
|
14
|
+
|
15
|
+
# put all the things in ClassMethods at class level
|
16
|
+
def self.included base
|
17
|
+
base.class_eval do
|
18
|
+
include Icss::ReceiverModel::ActiveModelShim
|
19
|
+
extend Icss::ReceiverModel::ClassMethods
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'active_model'
|
2
|
+
# require 'active_model/deprecated_error_methods'
|
3
|
+
# require 'active_model/errors'
|
4
|
+
# require 'active_model/naming'
|
5
|
+
# require 'active_model/validator'
|
6
|
+
# require 'active_model/translation'
|
7
|
+
# require 'active_model/validations'
|
8
|
+
# require 'active_support/i18n'
|
9
|
+
# I18n.load_path << File.join(File.expand_path(File.dirname(__FILE__)), 'locale/en.yml')
|
10
|
+
|
11
|
+
module Icss
|
12
|
+
module ReceiverModel
|
13
|
+
|
14
|
+
module ActiveModelShim
|
15
|
+
|
16
|
+
def to_model
|
17
|
+
self
|
18
|
+
end
|
19
|
+
|
20
|
+
def new_record?() true end
|
21
|
+
|
22
|
+
def destroyed?() false end
|
23
|
+
|
24
|
+
def errors
|
25
|
+
@_errors ||= ActiveModel::Errors.new(self)
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.included(base)
|
29
|
+
base.class_eval do
|
30
|
+
extend ActiveModel::Naming
|
31
|
+
include ActiveModel::Validations
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,170 @@
|
|
1
|
+
require 'active_support/core_ext/module'
|
2
|
+
|
3
|
+
# including class is required to implement the following methods:
|
4
|
+
# * catalog_sections
|
5
|
+
# * fullname
|
6
|
+
|
7
|
+
# including module is required to additionally implement:
|
8
|
+
# * receive (if not implemented)
|
9
|
+
# * after_receiver (to register objects)
|
10
|
+
|
11
|
+
|
12
|
+
module Icss
|
13
|
+
module ReceiverModel
|
14
|
+
module ActsAsCatalog
|
15
|
+
def self.included(base)
|
16
|
+
base.extend(ClassMethods)
|
17
|
+
end
|
18
|
+
|
19
|
+
#
|
20
|
+
# Register object in class's registry
|
21
|
+
#
|
22
|
+
def register(obj)
|
23
|
+
self.class.register(obj)
|
24
|
+
end
|
25
|
+
|
26
|
+
module ClassMethods
|
27
|
+
include Icss::ReceiverModel::ActsAsLoadable::ClassMethods
|
28
|
+
|
29
|
+
|
30
|
+
#
|
31
|
+
# Include ActAsLoadable for file receivers
|
32
|
+
# Declare and initialize registry class/module variable to hash
|
33
|
+
# Set after_receiver(:register) for classes to register their objects
|
34
|
+
#
|
35
|
+
def self.extended(base)
|
36
|
+
base.class_eval do
|
37
|
+
if is_a?(Class)
|
38
|
+
include Icss::ReceiverModel::ActsAsLoadable::ClassMethods
|
39
|
+
class_attribute :registry
|
40
|
+
class_attribute :_catalog_loaded
|
41
|
+
after_receive(:register) do |hsh|
|
42
|
+
register(self)
|
43
|
+
end
|
44
|
+
elsif is_a?(Module)
|
45
|
+
mattr_accessor :registry
|
46
|
+
mattr_accessor :_catalog_loaded
|
47
|
+
end
|
48
|
+
self.registry = Hash.new
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
#
|
53
|
+
# Add object to registry using fullname method as the identifier
|
54
|
+
#
|
55
|
+
|
56
|
+
def register(obj)
|
57
|
+
registry[obj.fullname] = obj
|
58
|
+
end
|
59
|
+
|
60
|
+
#
|
61
|
+
# Basic ActiveRecord inspired methods
|
62
|
+
# Name can include wildcards(*) for simple searching
|
63
|
+
# - example: geo.*.location.*
|
64
|
+
# matches anything with a namespace starting with 'geo'
|
65
|
+
# and containing 'location'
|
66
|
+
#
|
67
|
+
|
68
|
+
def all(name='*')
|
69
|
+
find(:all, name)
|
70
|
+
end
|
71
|
+
|
72
|
+
def first(name='*')
|
73
|
+
find(:first, name)
|
74
|
+
end
|
75
|
+
|
76
|
+
def last(name='*')
|
77
|
+
find(:last, name)
|
78
|
+
end
|
79
|
+
|
80
|
+
#
|
81
|
+
# ActiveRecord inspired find method
|
82
|
+
# Params:
|
83
|
+
# 1. :all, :first, :last, or registry identifier(ignores wildcards to force exact match)
|
84
|
+
# 2. registry name with wildcards(*)
|
85
|
+
# - example: geo.*.location.*
|
86
|
+
#
|
87
|
+
|
88
|
+
def find(name_or_find_type, name='*')
|
89
|
+
if !self._catalog_loaded
|
90
|
+
self._catalog_loaded = true
|
91
|
+
load_catalog(true)
|
92
|
+
end
|
93
|
+
|
94
|
+
method_name = case name_or_find_type
|
95
|
+
when :all then :to_a
|
96
|
+
when :first then :first
|
97
|
+
when :last then :last
|
98
|
+
else
|
99
|
+
# If exact match not in registry, try looking in file catalog
|
100
|
+
result = (find_in_registry(name_or_find_type, :exact_match => true) || load_files_from_catalog(name_or_find_type, :exact_match => true))
|
101
|
+
raise Icss::NotFoundError, "Cannot find #{name_or_find_type}" if result.nil?
|
102
|
+
return result
|
103
|
+
end
|
104
|
+
|
105
|
+
# If not in registry, try looking in file catalog
|
106
|
+
(find_in_registry(name) || load_files_from_catalog(name)).send method_name
|
107
|
+
end
|
108
|
+
|
109
|
+
def load_from_catalog(fullname)
|
110
|
+
filepath = fullname.to_s.gsub(/(\.icss\.yaml)?$/,'').gsub(/\./, '/')
|
111
|
+
filenames = catalog_filenames(filepath, [''])
|
112
|
+
filenames.each{|filename| receive_from_file(filename) }.compact
|
113
|
+
end
|
114
|
+
|
115
|
+
private
|
116
|
+
#
|
117
|
+
# Search registry for matching objects
|
118
|
+
# Return single object for exact_match=true
|
119
|
+
#
|
120
|
+
def find_in_registry(name, args={})
|
121
|
+
return registry[name] if args.symbolize_keys[:exact_match]
|
122
|
+
|
123
|
+
name = name.to_s.gsub('*', '[^\./]*').gsub(/\.\//, '\.').gsub(/\*$/, '.+') if name.include?('*')
|
124
|
+
name_regexp = Regexp.new("^#{name}$", true)
|
125
|
+
registry.select{|k, v| k.match(name_regexp) }.values
|
126
|
+
end
|
127
|
+
|
128
|
+
#
|
129
|
+
# Load files from catalog files
|
130
|
+
# Namespaces used to correspond to directories
|
131
|
+
# Load single file for exact_match=true
|
132
|
+
#
|
133
|
+
def load_files_from_catalog(name, args={})
|
134
|
+
# don't do anything if name is invalid format
|
135
|
+
if args.symbolize_keys[:exact_match]
|
136
|
+
filename = catalog_filenames(name.to_s.gsub(/\./, '/'))[0]
|
137
|
+
receive_from_file(filename) if filename
|
138
|
+
elsif /\A([A-Za-z_\*]\w*\.?)+\Z/ === name
|
139
|
+
filename = name.to_s.gsub(/(\.icss\.yaml)?$/,'').gsub(/\./, '/').gsub(/\*$/, '**/*')
|
140
|
+
filenames = catalog_filenames(filename)
|
141
|
+
filenames.collect{|filename| receive_from_file(filename) } unless filenames.empty?
|
142
|
+
end
|
143
|
+
# Return object/s from registry after loading files
|
144
|
+
# Useful for unexpected file contents / protocols containing many types
|
145
|
+
find_in_registry(name, args)
|
146
|
+
end
|
147
|
+
|
148
|
+
#
|
149
|
+
# Expand filenames to full paths using catalog_root, catalog_sections, and provided filename
|
150
|
+
#
|
151
|
+
def catalog_filenames(filename='*',catalog_sections=catalog_sections)
|
152
|
+
catalog_sections.collect{ |section|
|
153
|
+
Dir[File.join(Settings[:catalog_root], section, filename + '.icss.yaml')] }.flatten
|
154
|
+
end
|
155
|
+
|
156
|
+
#
|
157
|
+
# Conditional empty registry and load all found files
|
158
|
+
#
|
159
|
+
def load_catalog(flush=false)
|
160
|
+
flush_registry if flush
|
161
|
+
load_files_from_catalog('*')
|
162
|
+
end
|
163
|
+
|
164
|
+
def flush_registry
|
165
|
+
registry.clear
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
@@ -0,0 +1,177 @@
|
|
1
|
+
require 'gorillib/hashlike'
|
2
|
+
|
3
|
+
module Icss
|
4
|
+
module ReceiverModel
|
5
|
+
|
6
|
+
#
|
7
|
+
# Makes an Icss::RecordModel behave mostly like a hash.
|
8
|
+
#
|
9
|
+
# By default, the hashlike methods iterate over the fields: instance #keys
|
10
|
+
# delegates to self.class.field_names. All methods are defined naturally on
|
11
|
+
# [], []= and has_key? so the interface is well-guarded.
|
12
|
+
#
|
13
|
+
# in addition to the below, by including Enumerable, this also adds
|
14
|
+
#
|
15
|
+
# :each_cons, :each_entry, :each_slice, :each_with_index, :each_with_object,
|
16
|
+
# :map, :collect, :collect_concat, :entries, :to_a, :flat_map, :inject, :reduce,
|
17
|
+
# :group_by, :chunk, :cycle, :partition, :reverse_each, :slice_before, :drop,
|
18
|
+
# :drop_while, :take, :take_while, :detect, :find, :find_all, :find_index, :grep,
|
19
|
+
# :all?, :any?, :none?, :one?, :first, :count, :zip :max, :max_by, :min, :min_by,
|
20
|
+
# :minmax, :minmax_by, :sort, :sort_by
|
21
|
+
#
|
22
|
+
# As opposed to hash, does *not* define
|
23
|
+
#
|
24
|
+
# default, default=, default_proc, default_proc=, shift, flatten, compare_by_identity
|
25
|
+
# compare_by_identity? rehash
|
26
|
+
#
|
27
|
+
module ActsAsHash
|
28
|
+
|
29
|
+
# Hashlike#[]
|
30
|
+
#
|
31
|
+
# Element Reference -- Retrieves the value stored for +key+.
|
32
|
+
#
|
33
|
+
# In a normal hash, a default value can be set; none is provided here.
|
34
|
+
#
|
35
|
+
# Delegates to self.send(key)
|
36
|
+
#
|
37
|
+
# @example
|
38
|
+
# hsh = { :a => 100, :b => 200 }
|
39
|
+
# hsh[:a] # => 100
|
40
|
+
# hsh[:c] # => nil
|
41
|
+
#
|
42
|
+
# @param key [Object] key to retrieve
|
43
|
+
# @return [Object] the value stored for key, nil if missing
|
44
|
+
#
|
45
|
+
def [](key)
|
46
|
+
key = convert_key(key)
|
47
|
+
self.send(key)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Hashlike#[]=
|
51
|
+
# Hashlike#store
|
52
|
+
#
|
53
|
+
# Element Assignment -- Associates the value given by +val+ with the key
|
54
|
+
# given by +key+.
|
55
|
+
#
|
56
|
+
# key should not have its value changed while it is in use as a key. In a
|
57
|
+
# normal hash, a String passed as a key will be duplicated and frozen. No such
|
58
|
+
# guarantee is provided here
|
59
|
+
#
|
60
|
+
# Delegates to self.send("key=", val)
|
61
|
+
#
|
62
|
+
# @example
|
63
|
+
# hsh = { :a => 100, :b => 200 }
|
64
|
+
# hsh[:a] = 9
|
65
|
+
# hsh[:c] = 4
|
66
|
+
# hsh # => { :a => 9, :b => 200, :c => 4 }
|
67
|
+
#
|
68
|
+
# hsh[key] = val -> val
|
69
|
+
# hsh.store(key, val) -> val
|
70
|
+
#
|
71
|
+
# @param key [Object] key to associate
|
72
|
+
# @param val [Object] value to associate it with
|
73
|
+
# @return [Object]
|
74
|
+
#
|
75
|
+
def []=(key, val)
|
76
|
+
key = convert_key(key)
|
77
|
+
self.send("#{key}=", val) if respond_to?("#{key}=")
|
78
|
+
val
|
79
|
+
end
|
80
|
+
|
81
|
+
# Hashlike#delete
|
82
|
+
#
|
83
|
+
# Deletes and returns the value from +hsh+ whose key is equal to +key+. If the
|
84
|
+
# optional code block is given and the key is not found, pass in the key and
|
85
|
+
# return the result of +block+.
|
86
|
+
#
|
87
|
+
# In a normal hash, a default value can be set; none is provided here.
|
88
|
+
#
|
89
|
+
# @example
|
90
|
+
# hsh = { :a => 100, :b => 200 }
|
91
|
+
# hsh.delete(:a) # => 100
|
92
|
+
# hsh.delete(:z) # => nil
|
93
|
+
# hsh.delete(:z){|el| "#{el} not found" } # => "z not found"
|
94
|
+
#
|
95
|
+
# @overload hsh.delete(key) -> val
|
96
|
+
# @param key [Object] key to remove
|
97
|
+
# @return [Object, Nil] the removed object, nil if missing
|
98
|
+
#
|
99
|
+
# @overload hsh.delete(key){|key| block } -> val
|
100
|
+
# @param key [Object] key to remove
|
101
|
+
# @yield [Object] called (with key) if key is missing
|
102
|
+
# @yieldparam key
|
103
|
+
# @return [Object, Nil] the removed object, or if missing, the return value
|
104
|
+
# of the block
|
105
|
+
#
|
106
|
+
def delete(key, &block)
|
107
|
+
key = convert_key(key)
|
108
|
+
if has_key?(key)
|
109
|
+
val = self[key]
|
110
|
+
self.send(:remove_instance_variable, "@#{key}")
|
111
|
+
val
|
112
|
+
elsif block_given?
|
113
|
+
block.call(key)
|
114
|
+
else
|
115
|
+
nil
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# Hashlike#keys
|
120
|
+
#
|
121
|
+
# Returns a new array populated with the keys from this hashlike.
|
122
|
+
#
|
123
|
+
# @see Hashlike#values.
|
124
|
+
#
|
125
|
+
# @example
|
126
|
+
# hsh = { :a => 100, :b => 200, :c => 300, :d => 400 }
|
127
|
+
# hsh.keys # => [:a, :b, :c, :d]
|
128
|
+
#
|
129
|
+
# @return [Array] list of keys
|
130
|
+
#
|
131
|
+
def keys
|
132
|
+
self.class.field_names & instance_variables.map{|s| convert_key(s[1..-1]) }
|
133
|
+
end
|
134
|
+
|
135
|
+
#
|
136
|
+
# Returns a hash with each key set to its associated value.
|
137
|
+
#
|
138
|
+
# @example
|
139
|
+
# my_hshlike = MyHashlike.new
|
140
|
+
# my_hshlike[:a] = 100; my_hshlike[:b] = 200
|
141
|
+
# my_hshlike.to_hash # => { :a => 100, :b => 200 }
|
142
|
+
#
|
143
|
+
# @return [Hash] a new Hash instance, with each key set to its associated value.
|
144
|
+
#
|
145
|
+
def attributes
|
146
|
+
{}.tap do |hsh|
|
147
|
+
each_pair do |key, val|
|
148
|
+
hsh[key] = val.respond_to?(:to_hash) ? val.to_hash : val
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
#
|
154
|
+
# Returns a hash with each key set to its associated value.
|
155
|
+
#
|
156
|
+
# Delegates to #attributes
|
157
|
+
#
|
158
|
+
def to_hash
|
159
|
+
attributes
|
160
|
+
end
|
161
|
+
|
162
|
+
def self.included(base)
|
163
|
+
base.class_eval do
|
164
|
+
include Gorillib::Hashlike
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
protected
|
169
|
+
|
170
|
+
def convert_key(key)
|
171
|
+
raise ArgumentError, "Keys for #{self.class} must be symbols, strings or respond to #to_sym" unless key.respond_to?(:to_sym)
|
172
|
+
key.to_sym
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
end
|
177
|
+
end
|