sparqcode-waz-storage 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. data/.gitignore +7 -0
  2. data/CHANGELOG.rdoc +72 -0
  3. data/Gemfile +4 -0
  4. data/Gemfile.lock +36 -0
  5. data/LICENSE +19 -0
  6. data/README.rdoc +299 -0
  7. data/lib/waz-blobs.rb +5 -0
  8. data/lib/waz-queues.rb +6 -0
  9. data/lib/waz-storage.rb +39 -0
  10. data/lib/waz-tables.rb +5 -0
  11. data/lib/waz/blobs/blob_object.rb +121 -0
  12. data/lib/waz/blobs/container.rb +160 -0
  13. data/lib/waz/blobs/exceptions.rb +11 -0
  14. data/lib/waz/blobs/service.rb +156 -0
  15. data/lib/waz/queues/exceptions.rb +29 -0
  16. data/lib/waz/queues/message.rb +65 -0
  17. data/lib/waz/queues/queue.rb +165 -0
  18. data/lib/waz/queues/service.rb +106 -0
  19. data/lib/waz/storage/base.rb +70 -0
  20. data/lib/waz/storage/core_service.rb +122 -0
  21. data/lib/waz/storage/exceptions.rb +33 -0
  22. data/lib/waz/storage/validation_rules.rb +26 -0
  23. data/lib/waz/storage/version.rb +11 -0
  24. data/lib/waz/tables/edm_type_helper.rb +45 -0
  25. data/lib/waz/tables/exceptions.rb +45 -0
  26. data/lib/waz/tables/service.rb +178 -0
  27. data/lib/waz/tables/table.rb +75 -0
  28. data/lib/waz/tables/table_array.rb +11 -0
  29. data/rakefile +23 -0
  30. data/tests/configuration.rb +14 -0
  31. data/tests/waz/blobs/blob_object_test.rb +80 -0
  32. data/tests/waz/blobs/container_test.rb +162 -0
  33. data/tests/waz/blobs/service_test.rb +282 -0
  34. data/tests/waz/queues/message_test.rb +33 -0
  35. data/tests/waz/queues/queue_test.rb +206 -0
  36. data/tests/waz/queues/service_test.rb +299 -0
  37. data/tests/waz/storage/base_tests.rb +81 -0
  38. data/tests/waz/storage/shared_key_core_service_test.rb +142 -0
  39. data/tests/waz/tables/service_test.rb +614 -0
  40. data/tests/waz/tables/table_test.rb +98 -0
  41. data/waz-storage.gemspec +29 -0
  42. metadata +187 -0
@@ -0,0 +1,33 @@
1
+ module WAZ
2
+ module Storage
3
+ # This class is the base exception from where all the exceptions raised from this API
4
+ # inherit from. If you want to handle an exception that your code may throw and you don't
5
+ # know which specific type you should handle, handle this type of exception.
6
+ class StorageException < StandardError
7
+ end
8
+
9
+ # This exception raises whenever a required parameter for initializing any class isn't provided. From
10
+ # WAZ::Storage::Base up to WAZ::Queues::Queue all of them use this exception.
11
+ class InvalidOption < StorageException
12
+ def initialize(missing_option)
13
+ super("You did not provide one of the required parameters. Please provide the #{missing_option}.")
14
+ end
15
+ end
16
+
17
+ # This exception is raised when the user tries to perform an operation on any Storage API class
18
+ # without previously specificying the connection options.
19
+ class NotConnected < StorageException
20
+ def initialize
21
+ super("You should establish connection before using the services, the connection configuration is required.")
22
+ end
23
+ end
24
+
25
+ # This exception if raised when the value given for an argument doesn't fall into the permitted values. For example
26
+ # if values on the blocklisttype aren't [all|uncommitted|committed]
27
+ class InvalidParameterValue < StorageException
28
+ def initialize(args = {})
29
+ super("The value supplied for the parameter #{args[:name]} is invalid. Permitted values are #{args[:values].join(',')}")
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,26 @@
1
+ module WAZ
2
+ module Storage
3
+ class ValidationRules
4
+ class << self
5
+ # Validates that the Container/Queue name given matches with the requirements of Windows Azure.
6
+ #
7
+ # -Container/Queue names must start with a letter or number, and can contain only letters, numbers, and the dash (-) character.
8
+ # -Every dash (-) character must be immediately preceded and followed by a letter or number.
9
+ # -All letters in a container name must be lowercase.
10
+ # -Container/Queue names must be from 3 through 63 characters long.
11
+ def valid_name?(name)
12
+ name =~ /^[a-z0-9][a-z0-9\-]{1,}[^-]$/ && name.length < 64
13
+ end
14
+
15
+ # Validates that the Table name given matches with the requirements of Windows Azure.
16
+ #
17
+ # -Table names must start with at least one lower / upper character.
18
+ # -Table names can have character or any digit starting from the second position.
19
+ # -Table names must be from 3 through 63 characters long.
20
+ def valid_table_name?(name)
21
+ name =~ /^([a-z]|[A-Z]){1}([a-z]|[A-Z]|\d){2,62}$/
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,11 @@
1
+ module WAZ
2
+ module Storage
3
+ module VERSION #:nodoc:
4
+ MAJOR = '1'
5
+ MINOR = '1'
6
+ TINY = '1'
7
+ end
8
+
9
+ Version = [VERSION::MAJOR, VERSION::MINOR, VERSION::TINY].compact * '.'
10
+ end
11
+ end
@@ -0,0 +1,45 @@
1
+ module WAZ
2
+ module Tables
3
+ class EdmTypeHelper
4
+ class << self
5
+ def parse_from(item)
6
+ return nil if !item.attributes['m:null'].nil? and item.attributes['m:null'] == 'true'
7
+ case item.attributes['m:type']
8
+ when 'Edm.Int16', 'Edm.Int32', 'Edm.Int64'
9
+ item.text.to_i
10
+ when 'Edm.Single', 'Edm.Double'
11
+ item.text.to_f
12
+ when 'Edm.Boolean'
13
+ item.text == 'true'
14
+ when 'Edm.DateTime'
15
+ Time.parse(item.text)
16
+ when 'Edm.Binary'
17
+ StringIO.new(Base64.decode64(item.text))
18
+ else
19
+ item.text
20
+ end
21
+ end
22
+
23
+ def parse_to(item)
24
+ case item.class.name
25
+ when 'String'
26
+ [item, 'Edm.String']
27
+ when 'Fixnum'
28
+ [item, 'Edm.Int32']
29
+ when 'Float'
30
+ [item, 'Edm.Double']
31
+ when 'TrueClass', 'FalseClass'
32
+ [item, 'Edm.Boolean']
33
+ when 'Time'
34
+ [item.iso8601, 'Edm.DateTime']
35
+ when 'File', 'StringIO'
36
+ item.pos = 0
37
+ [Base64.encode64(item.read), 'Edm.Binary']
38
+ else
39
+ [item, 'Edm.String']
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,45 @@
1
+ module WAZ
2
+ module Tables
3
+ # This exception is raised while trying to create table that already exists.
4
+ class TableAlreadyExists < WAZ::Storage::StorageException
5
+ def initialize(name)
6
+ super("The table #{name} already exists on your account.")
7
+ end
8
+ end
9
+
10
+ # This exception is raised while trying to delete an unexisting table.
11
+ class TableDoesNotExist < WAZ::Storage::StorageException
12
+ def initialize(name)
13
+ super("The specified table #{name} does not exist.")
14
+ end
15
+ end
16
+
17
+ # This exception is raised when an invalid table name is provided.
18
+ class InvalidTableName < WAZ::Storage::StorageException
19
+ def initialize(name)
20
+ super("The table name #{name} is invalid, it must start with at least one lower/upper characted, must be from 3 through 63 characters long and can have character or any digit starting from the second position")
21
+ end
22
+ end
23
+
24
+ # This exception is raised when provided more than the 252 properties allowed by the Rest API.
25
+ class TooManyProperties < WAZ::Storage::StorageException
26
+ def initialize(total)
27
+ super("The entity contains more properties than allowed (252). The entity has #{total} properties.")
28
+ end
29
+ end
30
+
31
+ # This exception is raised when the specified entity already exists.
32
+ class EntityAlreadyExists < WAZ::Storage::StorageException
33
+ def initialize(row_key)
34
+ super("The specified entity already exists. RowKey: #{row_key}")
35
+ end
36
+ end
37
+
38
+ # This exception is raised while trying to delete an unexisting entity.
39
+ class EntityDoesNotExist < WAZ::Storage::StorageException
40
+ def initialize(key)
41
+ super("The specified entity with #{key} does not exist.")
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,178 @@
1
+ module WAZ
2
+ module Tables
3
+ # This is internally used by the waz-tables part of the gem and it exposes the Windows Azure Blob API REST methods
4
+ # implementation. You can use this class to perform an specific operation that isn't provided by the current API.
5
+ class Service
6
+ include WAZ::Storage::SharedKeyCoreService
7
+
8
+ DATASERVICES_NAMESPACE = "http://schemas.microsoft.com/ado/2007/08/dataservices"
9
+ DATASERVICES_METADATA_NAMESPACE = "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"
10
+
11
+ # Creates a table on the current Windows Azure Storage account.
12
+ def create_table(table_name)
13
+ raise WAZ::Tables::InvalidTableName, table_name unless WAZ::Storage::ValidationRules.valid_table_name?(table_name)
14
+
15
+ payload = "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>" \
16
+ "<entry xmlns:d=\"#{DATASERVICES_NAMESPACE}\" xmlns:m=\"#{DATASERVICES_METADATA_NAMESPACE}\" xmlns=\"http://www.w3.org/2005/Atom\">" \
17
+ "<title /><updated>#{Time.now.utc.iso8601}</updated><author><name/></author><id/>" \
18
+ "<content type=\"application/xml\"><m:properties><d:TableName>#{table_name}</d:TableName></m:properties></content></entry>"
19
+
20
+ begin
21
+ execute :post, 'Tables', {}, default_headers, payload
22
+ return {:name => table_name, :url => "#{self.base_url}/Tables('#{table_name}"}
23
+ rescue RestClient::RequestFailed
24
+ raise WAZ::Tables::TableAlreadyExists, table_name if $!.http_code == 409
25
+ end
26
+ end
27
+
28
+ # Delete a table on the current Windows Azure Storage account.
29
+ def delete_table(table_name)
30
+ raise WAZ::Tables::InvalidTableName, table_name unless WAZ::Storage::ValidationRules.valid_table_name?(table_name)
31
+ begin
32
+ execute :delete, "Tables('#{table_name}')", {}, default_headers
33
+ rescue RestClient::ResourceNotFound
34
+ raise WAZ::Tables::TableDoesNotExist, table_name if $!.http_code == 404
35
+ end
36
+ end
37
+
38
+ # Lists all existing tables on the current storage account.
39
+ # remove Content-Type if it's not working
40
+ def list_tables(next_table_name = nil)
41
+ query = { 'NextTableName' => next_table_name } unless next_table_name.nil?
42
+ content = execute :get, "Tables", query ||= {}, default_headers
43
+
44
+ doc = REXML::Document.new(content)
45
+ tables = REXML::XPath.each(doc, '/feed/entry').map do |item|
46
+ { :name => REXML::XPath.first(item.elements['content'], "m:properties/d:TableName", {"m" => DATASERVICES_METADATA_NAMESPACE, "d" => DATASERVICES_NAMESPACE}).text,
47
+ :url => REXML::XPath.first(item, "id").text }
48
+ end
49
+
50
+ return tables, content.headers[:x_ms_continuation_nexttablename]
51
+ end
52
+
53
+ # Retrieves an existing table on the current storage account.
54
+ # remove Content-Type if it's not working
55
+ def get_table(table_name)
56
+ raise WAZ::Tables::InvalidTableName, table_name unless WAZ::Storage::ValidationRules.valid_table_name?(table_name)
57
+
58
+ begin
59
+ content = execute :get, "Tables('#{table_name}')", {}, default_headers
60
+ doc = REXML::Document.new(content)
61
+ item = REXML::XPath.first(doc, "entry")
62
+ return { :name => REXML::XPath.first(item.elements['content'], "m:properties/d:TableName", {"m" => DATASERVICES_METADATA_NAMESPACE, "d" => DATASERVICES_NAMESPACE}).text,
63
+ :url => REXML::XPath.first(item, "id").text }
64
+ rescue RestClient::ResourceNotFound
65
+ raise WAZ::Tables::TableDoesNotExist, table_name if $!.http_code == 404
66
+ end
67
+ end
68
+
69
+ # Insert a new entity on the provided table for the current storage account
70
+ # TODO: catch all api errors as described on Table Service Error Codes on MSDN (http://msdn.microsoft.com/en-us/library/dd179438.aspx)
71
+ def insert_entity(table_name, entity)
72
+ raise WAZ::Tables::InvalidTableName, table_name unless WAZ::Storage::ValidationRules.valid_table_name?(table_name)
73
+ raise WAZ::Tables::TooManyProperties, entity.length if entity.length > 252
74
+
75
+ begin
76
+ response = execute(:post, table_name, {}, default_headers, generate_payload(table_name, entity))
77
+ return parse_response(response)
78
+ rescue RestClient::RequestFailed
79
+ raise WAZ::Tables::EntityAlreadyExists, entity[:row_key] if $!.http_code == 409 and $!.response.body.include?('EntityAlreadyExists')
80
+ end
81
+ end
82
+
83
+ # Update an existing entity on the current storage account.
84
+ # TODO: handle specific errors
85
+ def update_entity(table_name, entity)
86
+ raise WAZ::Tables::InvalidTableName, table_name unless WAZ::Storage::ValidationRules.valid_table_name?(table_name)
87
+ response = execute(:put, "#{table_name}(PartitionKey='#{entity[:partition_key]}',RowKey='#{entity[:row_key]}')", {}, default_headers.merge({'If-Match' => '*'}) , generate_payload(table_name, entity))
88
+ return parse_response(response)
89
+ end
90
+
91
+ # Merge an existing entity on the current storage account.
92
+ # The Merge Entity operation updates an existing entity by updating the entity's properties.
93
+ # This operation does not replace the existing entity, as the Update Entity operation does
94
+ # TODO: handle specific errors
95
+ def merge_entity(table_name, entity)
96
+ raise WAZ::Tables::InvalidTableName, table_name unless WAZ::Storage::ValidationRules.valid_table_name?(table_name)
97
+ response = execute(:merge, "#{table_name}(PartitionKey='#{entity[:partition_key]}',RowKey='#{entity[:row_key]}')", {}, default_headers.merge({'If-Match' => '*'}), generate_payload(table_name, entity))
98
+ return parse_response(response)
99
+ end
100
+
101
+ # Delete an existing entity in a table.
102
+ def delete_entity(table_name, partition_key, row_key)
103
+ raise WAZ::Tables::InvalidTableName, table_name unless WAZ::Storage::ValidationRules.valid_table_name?(table_name)
104
+
105
+ begin
106
+ execute :delete, "#{table_name}(PartitionKey='#{partition_key}',RowKey='#{row_key}')", {}, default_headers.merge({'If-Match' => '*'})
107
+ rescue RestClient::ResourceNotFound
108
+ raise WAZ::Tables::TableDoesNotExist, table_name if $!.http_code == 404 and $!.response.body.include?('TableNotFound')
109
+ raise WAZ::Tables::EntityDoesNotExist, "(PartitionKey='#{partition_key}',RowKey='#{row_key}')" if $!.http_code == 404
110
+ end
111
+ end
112
+
113
+ # Retrieves an existing entity on the current storage account.
114
+ # TODO: handle specific errors
115
+ def get_entity(table_name, partition_key, row_key)
116
+ raise WAZ::Tables::InvalidTableName, table_name unless WAZ::Storage::ValidationRules.valid_table_name?(table_name)
117
+ response = execute(:get, "#{table_name}(PartitionKey='#{partition_key}',RowKey='#{row_key}')", {}, default_headers)
118
+ return parse_response(response)
119
+ end
120
+
121
+ # Retrieves a set of entities on the current storage account for a given query.
122
+ # When the :top => n is passed it returns only the first n rows that match with the query
123
+ # Optional parameters:
124
+ # * :headers a hash containing the request headers
125
+ # * :expression the filter query that will be executed against the table (see http://msdn.microsoft.com/en-us/library/dd179421.aspx for more information),
126
+ # * :top limits the amount of fields for this query.
127
+ # * :continuation_token the hash obtained when you perform a query that has more than 1000 records or exceeds the allowed timeout (see http://msdn.microsoft.com/en-us/library/dd135718.aspx)
128
+ def query(table_name, options = {})
129
+ raise WAZ::Tables::InvalidTableName, table_name unless WAZ::Storage::ValidationRules.valid_table_name?(table_name)
130
+ query = {'$filter' => (options[:expression] or '') }
131
+ query.merge!({ '$top' => options[:top] }) unless options[:top].nil?
132
+ query.merge!(options[:continuation_token]) unless options[:continuation_token].nil?
133
+ response = execute :get, "#{table_name}()", query, default_headers
134
+ continuation_token = {'NextPartitionKey' => response.headers[:x_ms_continuation_nextpartitionkey], 'NextRowKey' => response.headers[:x_ms_continuation_nextrowkey]}
135
+ parse_response(response, continuation_token)
136
+ end
137
+
138
+ private
139
+ def generate_payload(table_name, entity)
140
+ payload = "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>" \
141
+ "<entry xmlns:d=\"#{DATASERVICES_NAMESPACE}\" xmlns:m=\"#{DATASERVICES_METADATA_NAMESPACE}\" xmlns=\"http://www.w3.org/2005/Atom\">" \
142
+ "<id>#{generate_request_uri "#{table_name}"}(PartitionKey='#{REXML::Text.new(entity[:partition_key], false, nil, false).to_s}',RowKey='#{REXML::Text.new(entity[:row_key], false, nil, false).to_s}')</id>" \
143
+ "<title /><updated>#{Time.now.utc.iso8601}</updated><author><name /></author><link rel=\"edit\" title=\"#{table_name}\" href=\"#{table_name}(PartitionKey='#{REXML::Text.new(entity[:partition_key], false, nil, false).to_s}',RowKey='#{REXML::Text.new(entity[:row_key], false, nil, false).to_s}')\" />" \
144
+ "<content type=\"application/xml\"><m:properties>"
145
+
146
+ entity.sort_by { |k| k.to_s }.each do |k,v|
147
+ value, type = EdmTypeHelper.parse_to(v)[0].to_s, EdmTypeHelper.parse_to(v)[1].to_s
148
+ payload << (!v.nil? ? "<d:#{k.to_s} m:type=\"#{k.edm_type || type}\">#{REXML::Text.new(value, false, nil, false).to_s}</d:#{k.to_s}>" : "<d:#{k.to_s} m:type=\"#{k.edm_type || type}\" m:null=\"true\" />") unless k.eql?(:partition_key) or k.eql?(:row_key)
149
+ end
150
+
151
+ payload << "<d:PartitionKey>#{REXML::Text.new(entity[:partition_key], false, nil, false).to_s}</d:PartitionKey>" \
152
+ "<d:RowKey>#{REXML::Text.new(entity[:row_key], false, nil, false).to_s}</d:RowKey>" \
153
+ "</m:properties></content></entry>"
154
+ return payload
155
+ end
156
+
157
+ def parse_response(response, continuation_token = nil)
158
+ doc = REXML::Document.new(response)
159
+ entities = REXML::XPath.each(doc, '//entry').map do |entry|
160
+ fields = REXML::XPath.each(entry.elements['content'], 'm:properties/*', {"m" => DATASERVICES_METADATA_NAMESPACE}).map do |f|
161
+ { f.name.gsub(/PartitionKey/i, 'partition_key').gsub(/RowKey/i, 'row_key').to_sym => EdmTypeHelper.parse_from(f) }
162
+ end
163
+ Hash[*fields.collect {|h| h.to_a}.flatten]
164
+ end
165
+ entities = WAZ::Tables::TableArray.new(entities)
166
+ entities.continuation_token = continuation_token
167
+ return (REXML::XPath.first(doc, '/feed')) ? entities : entities.first
168
+ end
169
+
170
+ def default_headers
171
+ { 'Date' => Time.new.httpdate,
172
+ 'Content-Type' => 'application/atom+xml',
173
+ 'DataServiceVersion' => '1.0;NetFx',
174
+ 'MaxDataServiceVersion' => '1.0;NetFx' }
175
+ end
176
+ end
177
+ end
178
+ end
@@ -0,0 +1,75 @@
1
+ module WAZ
2
+ module Tables
3
+ # This class represents a Table on Windows Azure Tables API. These are the methods implemented from Microsoft's API description
4
+ # available on MSDN at http://msdn.microsoft.com/en-us/library/dd179423.aspx
5
+ #
6
+ # # list available tables
7
+ # tables = WAZ::Tables::Table.list
8
+ #
9
+ # # list more tables
10
+ # WAZ::Tables::Table.list(tables.continuation_token)
11
+ #
12
+ # # get a specific table
13
+ # my_table = WAZ::Tables::Table.find('my-table')
14
+ #
15
+ # # delete table
16
+ # my_table.destroy!
17
+ #
18
+ # # create a new table
19
+ # WAZ::Tables::Table.create('new-table')
20
+ #
21
+ class Table
22
+ class << self
23
+ INVALID_TABLE_ERROR_MESSAGE = "must start with at least one lower/upper characted, can have character or any digit starting from the second position, must be from 3 through 63 characters long"
24
+
25
+ # Finds a table by name. It will return nil if no table was found.
26
+ def find(table_name)
27
+ raise WAZ::Storage::InvalidParameterValue, {:name => table_name, :values => [INVALID_TABLE_ERROR_MESSAGE]} unless WAZ::Storage::ValidationRules.valid_table_name?(table_name)
28
+ begin
29
+ WAZ::Tables::Table.new(service_instance.get_table(table_name))
30
+ rescue WAZ::Tables::TableDoesNotExist
31
+ return nil
32
+ end
33
+ end
34
+
35
+ # Returns an array of the existing tables (WAZ::Tables::Table) on the current
36
+ # Windows Azure Storage account.
37
+ def list(continuation_token = {})
38
+ table_list, next_table_name = service_instance.list_tables(continuation_token['NextTableName'])
39
+ tables = TableArray.new(table_list.map { |table| WAZ::Tables::Table.new({ :name => table[:name], :url => table[:url] }) })
40
+ tables.continuation_token = {'NextTableName' => next_table_name} unless next_table_name.nil?
41
+ return tables
42
+ end
43
+
44
+ # Creates a table on the current account.
45
+ def create(table_name)
46
+ raise WAZ::Storage::InvalidParameterValue, {:name => table_name, :values => [INVALID_TABLE_ERROR_MESSAGE]} unless WAZ::Storage::ValidationRules.valid_table_name?(table_name)
47
+ WAZ::Tables::Table.new(service_instance.create_table(table_name))
48
+ end
49
+
50
+ # This method is internally used by this class. It's the way we keep a single instance of the
51
+ # service that wraps the calls the Windows Azure Tables API. It's initialized with the values
52
+ # from the default_connection on WAZ::Storage::Base initialized thru establish_connection!
53
+ def service_instance
54
+ options = WAZ::Storage::Base.default_connection.merge(:type_of_service => "table")
55
+ (@service_instances ||= {})[options[:account_name]] ||= Service.new(options)
56
+ end
57
+ end
58
+
59
+ attr_accessor :name, :url
60
+
61
+ def initialize(options = {})
62
+ raise WAZ::Storage::InvalidOption, :name unless options.keys.include?(:name) and !options[:name].empty?
63
+ raise WAZ::Storage::InvalidOption, :url unless options.keys.include?(:url) and !options[:url].empty?
64
+ raise WAZ::Storage::InvalidParameterValue, {:name => options[:name], :values => [INVALID_TABLE_ERROR_MESSAGE]} unless WAZ::Storage::ValidationRules.valid_table_name?(options[:name])
65
+ self.name = options[:name]
66
+ self.url = options[:url]
67
+ end
68
+
69
+ # Removes the table from the current account.
70
+ def destroy!
71
+ self.class.service_instance.delete_table(self.name)
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,11 @@
1
+ module WAZ
2
+ module Tables
3
+ class TableArray < Array
4
+ attr_accessor :continuation_token
5
+
6
+ def initialize(array)
7
+ super(array)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env rake
2
+ require 'rspec/core/rake_task'
3
+ require 'rake/rdoctask'
4
+ require 'bundler/gem_tasks'
5
+
6
+ RSpec::Core::RakeTask.new do |t|
7
+ t.name = :specs
8
+ t.pattern = "tests/**/*_test.rb"
9
+ t.rcov = true
10
+ t.rcov_opts = ['--text-report', '--exclude', "exclude.*/.gem,test,Library,#{ENV['GEM_HOME']}", '--sort', 'coverage' ]
11
+ t.rspec_opts = ['-cfn']
12
+ end
13
+
14
+ namespace :docs do
15
+ Rake::RDocTask.new do |t|
16
+ t.rdoc_dir = 'rdoc'
17
+ t.title = "Windows Azure Storage library - simple gem for accessing WAZ's Storage REST API"
18
+ t.options << '--line-numbers' << '--inline-source' << '-A cattr_accessor=object'
19
+ t.options << '--charset' << 'utf-8'
20
+ t.rdoc_files.include('README.rdoc')
21
+ t.rdoc_files.include('lib/**/*.rb')
22
+ end
23
+ end