icss 0.0.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.
Files changed (44) hide show
  1. data/.document +5 -0
  2. data/.rspec +3 -0
  3. data/.watchr +20 -0
  4. data/CHANGELOG.textile +8 -0
  5. data/Gemfile +17 -0
  6. data/Gemfile.lock +34 -0
  7. data/LICENSE.textile +20 -0
  8. data/README.textile +19 -0
  9. data/Rakefile +43 -0
  10. data/VERSION +1 -0
  11. data/examples/BulkData.avpr +21 -0
  12. data/examples/complicated.icss.yaml +158 -0
  13. data/examples/interop.avsc +32 -0
  14. data/examples/mail.avpr +20 -0
  15. data/examples/namespace.avpr +28 -0
  16. data/examples/org/apache/avro/ipc/HandshakeRequest.avsc +11 -0
  17. data/examples/org/apache/avro/ipc/HandshakeResponse.avsc +15 -0
  18. data/examples/org/apache/avro/ipc/trace/avroTrace.avdl +64 -0
  19. data/examples/org/apache/avro/ipc/trace/avroTrace.avpr +82 -0
  20. data/examples/org/apache/avro/mapred/tether/InputProtocol.avpr +59 -0
  21. data/examples/org/apache/avro/mapred/tether/OutputProtocol.avpr +75 -0
  22. data/examples/simple.avpr +70 -0
  23. data/examples/weather.avsc +9 -0
  24. data/icss.gemspec +104 -0
  25. data/icss_specification.textile +370 -0
  26. data/init.rb +3 -0
  27. data/lib/icss.rb +19 -0
  28. data/lib/icss/brevity.rb +136 -0
  29. data/lib/icss/code_asset.rb +16 -0
  30. data/lib/icss/core_ext.rb +4 -0
  31. data/lib/icss/data_asset.rb +22 -0
  32. data/lib/icss/message.rb +72 -0
  33. data/lib/icss/old.rb +96 -0
  34. data/lib/icss/protocol.rb +138 -0
  35. data/lib/icss/protocol_set.rb +48 -0
  36. data/lib/icss/sample_message_call.rb +140 -0
  37. data/lib/icss/target.rb +71 -0
  38. data/lib/icss/type.rb +517 -0
  39. data/lib/icss/type/factory.rb +196 -0
  40. data/lib/icss/validations.rb +16 -0
  41. data/lib/icss/view_helper.rb +28 -0
  42. data/spec/icss_spec.rb +7 -0
  43. data/spec/spec_helper.rb +31 -0
  44. metadata +218 -0
@@ -0,0 +1,16 @@
1
+ module Icss
2
+ class CodeAsset
3
+ include Receiver
4
+ include Receiver::ActsAsHash
5
+
6
+ rcvr_accessor :name, String
7
+ rcvr_accessor :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
@@ -0,0 +1,4 @@
1
+ require 'gorillib/string/inflections'
2
+ require 'gorillib/string/constantize'
3
+ require 'gorillib/hash/keys'
4
+ require 'gorillib/metaprogramming/class_attribute'
@@ -0,0 +1,22 @@
1
+ module Icss
2
+ class DataAsset
3
+ include Receiver
4
+ include Receiver::ActsAsHash
5
+
6
+ rcvr_accessor :name, String
7
+ rcvr_accessor :location, String
8
+ #overriding ruby's deprecated but still present type attr on objects
9
+ attr_accessor :type
10
+ rcvr_accessor :type, String
11
+ rcvr_accessor :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
@@ -0,0 +1,72 @@
1
+ module Icss
2
+
3
+ #
4
+ # Describes an Avro Message
5
+ #
6
+ # A message has attributes:
7
+ #
8
+ # * doc: an optional description of the message,
9
+ # * request: a list of named, typed parameter schemas (this has the same form as the fields of a record declaration);
10
+ # * response: a valid schema for the response
11
+ # * errors: an optional union of error schemas.
12
+ #
13
+ # A request parameter list is processed equivalently to an anonymous
14
+ # record. Since record field lists may vary between reader and writer, request
15
+ # parameters may also differ between the caller and responder, and such
16
+ # differences are resolved in the same manner as record field differences.
17
+ #
18
+ class Message
19
+ include Receiver
20
+ include Receiver::ActsAsHash
21
+ rcvr_accessor :name, String
22
+ rcvr_accessor :doc, String
23
+
24
+ #we're starting to attach a lot of pork to this lib...
25
+ rcvr_accessor :initial_free_qty, Integer
26
+ rcvr_accessor :price_per_k_in_cents, Integer
27
+
28
+ rcvr_accessor :request, Array, :of => Icss::RecordField, :default => []
29
+ rcvr_accessor :response, Icss::TypeFactory
30
+ rcvr_accessor :errors, Icss::UnionType, :default => []
31
+ attr_accessor :protocol
32
+ # this is defined in sample_message_call.rb -- since we don't do referenced types yet
33
+ # rcvr_accessor :samples, Array, :of => Icss::SampleMessageCall, :default => []
34
+
35
+ after_receive do |hsh|
36
+ # track recursion of type references
37
+ @response_is_reference = true if hsh['response'].is_a?(String) || hsh['response'].is_a?(Symbol)
38
+ # tie each sample back to this, its parent message
39
+ (self.samples ||= []).each{|sample| sample.message = self }
40
+ end
41
+
42
+ def path
43
+ File.join(protocol.path, name)
44
+ end
45
+
46
+ def first_sample_request_param
47
+ req = samples.first.request.first rescue nil
48
+ req || {}
49
+ end
50
+
51
+ #
52
+ # Conversion
53
+ #
54
+ def to_hash()
55
+ {
56
+ :doc => doc,
57
+ :request => summary_of_request_attr,
58
+ :response => summary_of_response_attr,
59
+ :errors => errors,
60
+ }.reject{|k,v| v.nil? }
61
+ end
62
+ def to_json(*args) to_hash.to_json(*args) ; end
63
+
64
+ private
65
+ def summary_of_response_attr
66
+ case when response.blank? then response when @response_is_reference then response.name else response.to_hash end
67
+ end
68
+ def summary_of_request_attr
69
+ request.map(&:to_hash)
70
+ end
71
+ end
72
+ end
data/lib/icss/old.rb ADDED
@@ -0,0 +1,96 @@
1
+ module Icss
2
+ Protocol.class_eval do
3
+
4
+ #
5
+ # Returns the asset hash with the passed in asset_name
6
+ #
7
+ def asset_for_name nm
8
+ warn "asset_for_name obsolete, please rewrite"
9
+ data_assets.find{|asset| asset.named?(nm) }
10
+ end
11
+
12
+ #
13
+ # Returns the full type for the passed in asset hash
14
+ #
15
+ def type_for_asset asset
16
+ warn "type_for_asset obsolete, please rewrite"
17
+ # types.find{|type| type.name == asset['type']}
18
+ raise 'use asset.type_obj'
19
+ end
20
+
21
+ #
22
+ # Given an asset name, returns the the record it points to
23
+ #
24
+ def type_for_asset_name asset_name
25
+ warn "type_for_asset_name obsolete, please rewrite"
26
+ asset = asset_for_name(asset_name)
27
+ type_for_asset(asset)
28
+ end
29
+
30
+ #
31
+ # Given an asset name, return the name of the type it points to
32
+ #
33
+ def type_name_for_asset_name asset_name
34
+ warn "type_name_for_asset_name obsolete, please rewrite"
35
+ asset = asset_for_name(asset_name)
36
+ asset['type']
37
+ end
38
+
39
+ #
40
+ # Return the avro record with the given name
41
+ #
42
+ def type_for_name type_name
43
+ warn "type_for_name obsolete, please rewrite"
44
+ # types.find{|record| record.name == type_name}
45
+ Icss::Type.find(type_name)
46
+ end
47
+
48
+ #
49
+ # Fetch the avro fields for a named data asset
50
+ #
51
+ def fields_for_asset asset_name
52
+ warn "fields_for_asset obsolete, please rewrite"
53
+ asset = asset_for_name(asset_name)
54
+ asset_type = type_for_asset(asset)
55
+ asset_type.fields
56
+ end
57
+
58
+ #
59
+ # Fetch the locations on disk of each data asset. WARNING: Assuming relative paths
60
+ #
61
+ def location_for_asset asset_name
62
+ warn "location_for_asset obsolete, please rewrite"
63
+ asset = asset_for_name(asset_name)
64
+ File.join(dirname, asset['location'])
65
+ end
66
+
67
+ #
68
+ # Return the index of the named field for the named type. Returns (nil) if field
69
+ # does not exist
70
+ #
71
+ def index_of_fieldname asset_name, field_name
72
+ warn "index_of_fieldname obsolete, please rewrite"
73
+ type = type_for_asset_name(asset_name)
74
+ type.index_of_fieldname(field_name)
75
+ end
76
+
77
+ AVRO_PIG_MAPPING = {
78
+ 'string' => 'chararray',
79
+ 'int' => 'int',
80
+ 'long' => 'long',
81
+ 'float' => 'float',
82
+ 'double' => 'double',
83
+ 'bytes' => 'bytearray',
84
+ 'fixed' => 'bytearray'
85
+ }
86
+
87
+ #
88
+ # Add pig fields to a passed in array of avro fields
89
+ #
90
+ def augment_with_pig_fields fields
91
+ warn "augment_with_pig_fields obsolete, please rewrite"
92
+ fields.map{|field| field.body['pig_type'] = AVRO_PIG_MAPPING[field.type]; field }
93
+ end
94
+ end
95
+
96
+ end
@@ -0,0 +1,138 @@
1
+ module Icss
2
+
3
+ #
4
+ # Describes an Avro Protocol Declaration
5
+ #
6
+ # Avro protocols describe RPC interfaces. The Protocol class will receive an
7
+ # Avro JSON
8
+ #
9
+ # A Protocol has the following attributes:
10
+ #
11
+ # * protocol, a string, the name of the protocol (required). +name+ is
12
+ # provided as an alias for +protocol+.
13
+ #
14
+ # * namespace, a string that qualifies the name (optional).
15
+ #
16
+ # * doc, a string describing this protocol (optional).
17
+ #
18
+ # * types, an optional list of definitions of named types (records, enums,
19
+ # fixed and errors). An error definition is just like a record definition
20
+ # except it uses "error" instead of "record". Note that forward references
21
+ # to named types are not permitted.
22
+ #
23
+ # * messages, an optional JSON object whose keys are message names and whose
24
+ # values are objects whose attributes are described below. No two messages
25
+ # may have the same name.
26
+ #
27
+ # The name and namespace qualification rules defined for schema objects apply
28
+ # to protocols as well: see the documentation for Icss::Type.
29
+ #
30
+ # For example, one may define a simple HelloWorld protocol with:
31
+ #
32
+ # {
33
+ # "namespace": "com.acme",
34
+ # "protocol": "HelloWorld",
35
+ # "doc": "Protocol Greetings",
36
+ # "types": [
37
+ # { "name": "Greeting",
38
+ # "type": "record",
39
+ # "fields": [ {"name": "message", "type": "string"} ]},
40
+ # { "name": "Curse",
41
+ # "type": "error",
42
+ # "fields": [ {"name": "message", "type": "string"} ]}
43
+ # ],
44
+ # "messages": {
45
+ # "hello": {
46
+ # "doc": "Say hello.",
47
+ # "request": [{"name": "greeting", "type": "Greeting" }],
48
+ # "response": "Greeting",
49
+ # "errors": ["Curse"]
50
+ # }
51
+ # }
52
+ # }
53
+ #
54
+ class Protocol
55
+ include Receiver
56
+ include Receiver::ActsAsHash
57
+ include Receiver::ActsAsLoadable
58
+ include Icss::Validations
59
+
60
+ rcvr_accessor :protocol, String, :required => true
61
+ alias_method :name, :protocol
62
+ rcvr_accessor :namespace, String # must be *dotted* ("foo.bar"), not slashed ("foo/bar")
63
+ rcvr_accessor :doc, String
64
+ #
65
+ rcvr_accessor :types, Array, :of => Icss::TypeFactory, :default => []
66
+ rcvr_accessor :messages, Hash, :of => Icss::Message, :default => {}
67
+ # extensions to avro
68
+ rcvr_accessor :data_assets, Array, :of => Icss::DataAsset, :default => []
69
+ rcvr_accessor :code_assets, Array, :of => Icss::CodeAsset, :default => []
70
+ rcvr_accessor :targets, Hash, :of => Icss::TargetListFactory, :default => {}, :merge_as => :hash_of_arrays
71
+ rcvr_accessor :under_consideration, Boolean
72
+ rcvr_accessor :update_frequency, String # must be of the form daily, weekly, monthly, quarterly, never
73
+
74
+ # attr_accessor :body
75
+ after_receive do |hsh|
76
+ # Set each message's protocol to self, and if the name wasn't given, set
77
+ # it using the message's hash key.
78
+ self.messages.each{|msg_name, msg| msg.protocol = self; msg.name ||= msg_name }
79
+ # Set all the type's parent to self (for namespace resolution)
80
+ self.types.each{|type| type.parent = self }
81
+ validate_name
82
+ validate_namespace
83
+ end
84
+
85
+ # String: namespace.name
86
+ def fullname
87
+ [namespace, name].compact.join(".")
88
+ end
89
+
90
+ def path
91
+ fullname.gsub('.', '/')
92
+ end
93
+
94
+ def find_message nm
95
+ return if messages.blank?
96
+ nm = nm.to_s.gsub("/", ".").split(".").last
97
+ messages[nm]
98
+ end
99
+
100
+ def receive_protocol nm
101
+ namespace_and_name = nm.to_s.gsub("/", ".").split(".")
102
+ self.protocol = namespace_and_name.pop
103
+ self.namespace = namespace_and_name.join('.')
104
+ end
105
+
106
+ def receive_targets hsh
107
+ self.targets = hsh.inject({}) do |target_obj_hsh, (target_name, target_info_list)|
108
+ target_obj_hsh[target_name] = TargetListFactory.receive(target_info_list, target_name) # returns an arry of targets
109
+ target_obj_hsh
110
+ end
111
+ end
112
+
113
+ def to_hash()
114
+ {
115
+ :namespace => @namespace, # use accessor so unset namespace isn't given
116
+ :protocol => protocol,
117
+ :doc => doc,
118
+ :under_consideration => under_consideration,
119
+ :update_frequency => update_frequency,
120
+ :types => (types && types.map(&:to_hash)),
121
+ :messages => messages.inject({}){|h,(k,v)| h[k] = v.to_hash; h },
122
+ :data_assets => data_assets.map(&:to_hash),
123
+ :code_assets => code_assets.map(&:to_hash),
124
+ :targets => targets_to_hash,
125
+ }.reject{|k,v| v.nil? }.tree_merge! self.message_samples_hash
126
+ end
127
+
128
+ def targets_to_hash
129
+ return unless targets
130
+ targets.inject({}) do |hsh,(k,targs)|
131
+ hsh[k] = targs.map{|t| t.respond_to?(:to_hash) ? t.to_hash : t } ; hsh
132
+ end
133
+ end
134
+
135
+ # This will cause funny errors when it is an element of something that's to_json'ed
136
+ def to_json(*args) to_hash.to_json(*args) ; end
137
+ end
138
+ end
@@ -0,0 +1,48 @@
1
+ # you must require 'icss/protocol_set' explicitly to use it.
2
+
3
+ module Icss
4
+ #
5
+ # Holds a set of icss protocols, with helper methods to load them from a
6
+ # directory tree, to merge in a protocol set yaml file, and so forth
7
+ #
8
+ class ProtocolSet
9
+ attr_accessor :protocols
10
+
11
+ def initialize
12
+ self.protocols = {}
13
+ end
14
+
15
+
16
+ def register pr
17
+ if protocols[pr.fullname]
18
+ protocols[pr.fullname].tree_merge!(pr)
19
+ else
20
+ protocols[pr.fullname] = pr
21
+ end
22
+ end
23
+
24
+ # load and register the protocols in the given filenames
25
+ def load_protocols *icss_filenames
26
+ icss_filenames = icss_filenames.flatten.compact
27
+ icss_filenames.each do |icss_filename|
28
+ register Icss::Protocol.receive_from_file(icss_filename)
29
+ end
30
+ end
31
+
32
+ # load and register the set of protocols in the given yaml file.
33
+ # it must be a hash with element 'protocol_set', holding an array of
34
+ # protocol hashes.
35
+ #
36
+ # protocol_set:
37
+ # - namespace: "..."
38
+ # protocol: "..."
39
+ def load_protocol_set protocol_set_filename
40
+ protocol_set_hsh = YAML.load(File.open(protocol_set_filename))
41
+ protocol_set_hsh['protocol_set'].each do |hsh|
42
+ register Icss::Protocol.receive(hsh)
43
+ end
44
+ end
45
+ end
46
+ end
47
+
48
+
@@ -0,0 +1,140 @@
1
+ module Icss
2
+ #
3
+ # Holds a sample call for a message and its expected response
4
+ #
5
+ # You may define the request parameters using an array of parameters
6
+ # or with the corresponding URL it would render to.
7
+ #
8
+ # This file also decorates Icss::Message and Icss::Protocol with helper methods for sample calls.
9
+ #
10
+ class SampleMessageCall
11
+ include Receiver
12
+ rcvr_accessor :name, String
13
+ rcvr_accessor :doc, String
14
+ rcvr_accessor :request, Array, :default => []
15
+ rcvr_accessor :response, Object # a hash suitable for populating the message's @response@ type
16
+ attr_accessor :raw_response # the raw http response from fetching
17
+ rcvr_accessor :error, String
18
+ rcvr_accessor :url, String
19
+ attr_accessor :message
20
+
21
+ # The URL implied by the given hostname and the sample request parameters.
22
+ #
23
+ # @param [String] hostname The hostname or hostname:port to include in the URL
24
+ # @param [Hash] extra_query_params A hash of extra params to in
25
+ #
26
+ # The URI expects string values in the hash used to build the query -- if
27
+ # calling #to_s on a field won't do what you want, clobber the value beforehand.
28
+ #
29
+ def full_url hostname, extra_query_params={}
30
+ host, port = hostname.split(':', 2)
31
+ u = Addressable::URI.new(:host => host, :port => port, :path => self.path, :scheme => 'http')
32
+ u.query_values = query_hash(extra_query_params)
33
+ u
34
+ end
35
+
36
+ def query_hash extra_query_params={}
37
+ hsh = (@url.present? ? @url.query_values : request.first.to_hash) rescue {}
38
+ hsh = hsh.merge extra_query_params
39
+ hsh.each{|k,v| hsh[k] = v.to_s }
40
+ hsh
41
+ end
42
+
43
+ def path
44
+ ((@url && @url.path).present? ? @url.path : "/#{message.path}" )
45
+ end
46
+
47
+ # @param [String, Addressable::URI]
48
+ # the URL can be fully-qualified (htttp://api.infochimps.com/this/that?the=other) or relative (this/that?the=other)
49
+ # and the path must match that of the message.
50
+ #
51
+ def url= new_url
52
+ if new_url.is_a?(String)
53
+ unless new_url.include?('?') then warn "sample request url should have a '?' introducing its query parameters: {#{new_url}}" ; end
54
+ new_url = Addressable::URI.parse(new_url)
55
+ end
56
+ @url = new_url
57
+ end
58
+
59
+ # Whips up the class implied by the ICSS type of this message's response,
60
+ # and populates it using the response hash.
61
+ def response_obj
62
+ return if response.blank?
63
+ klass = message.response.ruby_klass
64
+ klass.receive(response.compact_blank!)
65
+ end
66
+
67
+ # retrieve the response from the given host, storing it in response. this
68
+ # catches all server errors and constructs a dummy response hash if the call
69
+ # fails.
70
+ def fetch_response! hostname="", extra_query_params={}
71
+ self.raw_response = fetch_raw_response( full_url(hostname, extra_query_params) )
72
+ begin
73
+ resp_hsh = JSON.load(raw_response.body)
74
+ rescue StandardError => e
75
+ warn [" error parsing response: #{e}"].join("\n")
76
+ self.response = nil
77
+ self.error = "JsonParseError"
78
+ return
79
+ end
80
+ if raw_response.code == 200
81
+ self.response = resp_hsh
82
+ self.error = nil
83
+ else
84
+ self.response = nil
85
+ self.error = resp_hsh["error"]
86
+ end
87
+ end
88
+
89
+ private
90
+
91
+ def fetch_raw_response full_url
92
+ RestClient.get(full_url.to_s) do |response, request, result|
93
+ response
94
+ end
95
+ end
96
+ end
97
+ end
98
+
99
+ class Icss::Message
100
+ rcvr_accessor :samples, Array, :of => Icss::SampleMessageCall, :default => []
101
+ end
102
+
103
+ class Icss::Protocol
104
+
105
+ #
106
+ # a hash for dumping to file:
107
+ # @example: from the whole thing, would dump only this:
108
+ #
109
+ # namespace: util.time
110
+ # protocol: chronic
111
+ # messages:
112
+ # parse:
113
+ # samples:
114
+ # - url: "?now=5%3A06%3A07%202010-08-08&time_str=Yesterday"
115
+ # response: { "time": "2010-08-07 05:06:07 UTC", "epoch_seconds": 1281225967 }
116
+ #
117
+ def message_samples_hash
118
+ hsh = { "namespace" => namespace, "protocol" => protocol, "messages" => {} }
119
+ messages.each do |msg_name, msg|
120
+ hsh["messages"][msg_name] = { "samples" => [] }
121
+ msg.samples.each do |sample_req|
122
+ sample_hsh = {
123
+ "name" => sample_req.name,
124
+ "doc" => sample_req.doc,
125
+ }
126
+ if sample_req.response.present?
127
+ then sample_hsh['response'] = sample_req.response
128
+ else sample_hsh['error'] = sample_req.error
129
+ end
130
+ if sample_req.url.present?
131
+ then sample_hsh['url'] = sample_req.url.to_s
132
+ else sample_hsh['request'] = sample_req.request
133
+ end
134
+ hsh["messages"][msg_name]["samples"] << sample_hsh.compact_blank!
135
+ end
136
+ end
137
+ return hsh
138
+ end
139
+
140
+ end