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,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
|