ashikawa-core 0.7.2 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.coveralls.yml +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +0 -4
- data/Gemfile +1 -1
- data/Gemfile.devtools +24 -18
- data/README.md +6 -11
- data/ashikawa-core.gemspec +7 -6
- data/config/flay.yml +2 -2
- data/config/flog.yml +2 -1
- data/config/reek.yml +68 -84
- data/config/rubocop.yml +99 -0
- data/config/yardstick.yml +1 -1
- data/lib/ashikawa-core/collection.rb +74 -21
- data/lib/ashikawa-core/configuration.rb +26 -0
- data/lib/ashikawa-core/connection.rb +5 -2
- data/lib/ashikawa-core/cursor.rb +28 -16
- data/lib/ashikawa-core/database.rb +89 -12
- data/lib/ashikawa-core/document.rb +32 -5
- data/lib/ashikawa-core/edge.rb +3 -0
- data/lib/ashikawa-core/exceptions/client_error.rb +3 -3
- data/lib/ashikawa-core/exceptions/server_error.rb +3 -3
- data/lib/ashikawa-core/figure.rb +17 -5
- data/lib/ashikawa-core/index.rb +4 -0
- data/lib/ashikawa-core/key_options.rb +54 -0
- data/lib/ashikawa-core/query.rb +39 -74
- data/lib/ashikawa-core/request_preprocessor.rb +2 -2
- data/lib/ashikawa-core/response_preprocessor.rb +21 -12
- data/lib/ashikawa-core/transaction.rb +113 -0
- data/lib/ashikawa-core/version.rb +1 -1
- data/spec/acceptance/basic_spec.rb +40 -12
- data/spec/acceptance/index_spec.rb +2 -1
- data/spec/acceptance/query_spec.rb +18 -17
- data/spec/acceptance/transactions_spec.rb +30 -0
- data/spec/fixtures/collections/all.json +90 -30
- data/spec/fixtures/cursor/edges.json +23 -0
- data/spec/setup/arangodb.sh +7 -6
- data/spec/unit/collection_spec.rb +89 -13
- data/spec/unit/connection_spec.rb +23 -14
- data/spec/unit/cursor_spec.rb +15 -4
- data/spec/unit/database_spec.rb +58 -17
- data/spec/unit/document_spec.rb +24 -4
- data/spec/unit/edge_spec.rb +1 -1
- data/spec/unit/exception_spec.rb +4 -2
- data/spec/unit/figure_spec.rb +17 -10
- data/spec/unit/index_spec.rb +1 -1
- data/spec/unit/key_options_spec.rb +25 -0
- data/spec/unit/query_spec.rb +1 -1
- data/spec/unit/spec_helper.rb +20 -2
- data/spec/unit/transaction_spec.rb +153 -0
- data/tasks/adjustments.rake +23 -14
- metadata +31 -41
- data/.rvmrc +0 -1
- data/config/roodi.yml +0 -17
- data/spec/spec_helper.rb +0 -27
data/config/yardstick.yml
CHANGED
@@ -1,2 +1,2 @@
|
|
1
1
|
---
|
2
|
-
threshold:
|
2
|
+
threshold: 100
|
@@ -5,7 +5,9 @@ require "ashikawa-core/cursor"
|
|
5
5
|
require "ashikawa-core/query"
|
6
6
|
require "ashikawa-core/status"
|
7
7
|
require "ashikawa-core/figure"
|
8
|
+
require "ashikawa-core/key_options"
|
8
9
|
require "forwardable"
|
10
|
+
require "equalizer"
|
9
11
|
|
10
12
|
module Ashikawa
|
11
13
|
module Core
|
@@ -13,6 +15,8 @@ module Ashikawa
|
|
13
15
|
class Collection
|
14
16
|
extend Forwardable
|
15
17
|
|
18
|
+
include Equalizer.new(:id, :name, :content_type, :database)
|
19
|
+
|
16
20
|
CONTENT_TYPES = {
|
17
21
|
2 => :document,
|
18
22
|
3 => :edge
|
@@ -205,6 +209,17 @@ module Ashikawa
|
|
205
209
|
send_information_to_server(:properties, :waitForSync, new_value)
|
206
210
|
end
|
207
211
|
|
212
|
+
# Get information about the type of keys of this collection
|
213
|
+
#
|
214
|
+
# @return [KeyOptions]
|
215
|
+
# @api public
|
216
|
+
# @example Check if this collection has autoincrementing keys
|
217
|
+
# collection = Ashikawa::Core::Collection.new(database, raw_collection)
|
218
|
+
# collection.key_options.type # => "autoincrement"
|
219
|
+
def key_options
|
220
|
+
KeyOptions.new(get_information_from_server(:properties, :keyOptions))
|
221
|
+
end
|
222
|
+
|
208
223
|
# Returns the number of documents in the collection
|
209
224
|
#
|
210
225
|
# @return [Fixnum] Number of documents
|
@@ -326,29 +341,56 @@ module Ashikawa
|
|
326
341
|
send_command_to_server(:truncate)
|
327
342
|
end
|
328
343
|
|
329
|
-
# Fetch a certain document by its
|
344
|
+
# Fetch a certain document by its key
|
330
345
|
#
|
331
|
-
# @param [Integer]
|
346
|
+
# @param [Integer] document_key the key of the document
|
332
347
|
# @raise [DocumentNotFoundException] If the requested document was not found
|
333
348
|
# @return Document
|
334
349
|
# @api public
|
335
|
-
# @example Fetch the document with the
|
336
|
-
# document = collection
|
337
|
-
def
|
338
|
-
response =
|
350
|
+
# @example Fetch the document with the key 12345
|
351
|
+
# document = collection.fetch(12345)
|
352
|
+
def fetch(document_key)
|
353
|
+
response = send_request_for_content_key(document_key)
|
339
354
|
@content_class.new(@database, response)
|
340
355
|
end
|
341
356
|
|
342
|
-
#
|
357
|
+
# Fetch a certain document by its key, return nil if the document does not exist
|
343
358
|
#
|
344
|
-
# @param [Integer]
|
359
|
+
# @param [Integer] document_key the id of the document
|
360
|
+
# @return Document
|
361
|
+
# @api public
|
362
|
+
# @example Fetch the document with the key 12345
|
363
|
+
# document = collection[12345]
|
364
|
+
def [](document_key)
|
365
|
+
fetch(document_key)
|
366
|
+
rescue DocumentNotFoundException
|
367
|
+
nil
|
368
|
+
end
|
369
|
+
|
370
|
+
# Replace a document by its key
|
371
|
+
#
|
372
|
+
# @param [Integer] document_key the key of the document
|
345
373
|
# @param [Hash] raw_document the data you want to replace it with
|
346
374
|
# @return [Hash] parsed JSON response from the server
|
347
375
|
# @api public
|
348
|
-
# @example Replace the document with the
|
349
|
-
# collection
|
350
|
-
def
|
351
|
-
|
376
|
+
# @example Replace the document with the key 12345
|
377
|
+
# collection.replace(12345, document)
|
378
|
+
def replace(document_key, raw_document)
|
379
|
+
send_request_for_content_key(document_key, :put => raw_document)
|
380
|
+
end
|
381
|
+
|
382
|
+
# Replace a document by its key
|
383
|
+
#
|
384
|
+
# @param [Integer] document_key the key of the document
|
385
|
+
# @param [Hash] raw_document the data you want to replace it with
|
386
|
+
# @return [Hash] parsed JSON response from the server
|
387
|
+
# @api public
|
388
|
+
# @deprecated Use {#replace} instead.
|
389
|
+
# @example Replace the document with the key 12345
|
390
|
+
# collection.replace(12345, document)
|
391
|
+
def []=(document_key, raw_document)
|
392
|
+
warn "`[]=` is deprecated, please use `replace`"
|
393
|
+
replace(document_key, raw_document)
|
352
394
|
end
|
353
395
|
|
354
396
|
# Create a new document with given attributes
|
@@ -361,10 +403,21 @@ module Ashikawa
|
|
361
403
|
def create_document(attributes)
|
362
404
|
raise "Can't create a document in an edge collection" if @content_type == :edge
|
363
405
|
response = send_request_for_content(:post => attributes)
|
364
|
-
Document.new(@database, response)
|
406
|
+
Document.new(@database, response).refresh!
|
365
407
|
end
|
366
408
|
|
367
|
-
|
409
|
+
# Create a new document with given attributes
|
410
|
+
#
|
411
|
+
# @param [Hash] attributes
|
412
|
+
# @return [Document] The created document
|
413
|
+
# @api public
|
414
|
+
# @deprecated Use {#create_document} instead.
|
415
|
+
# @example Create a new document from raw data
|
416
|
+
# collection << attributes
|
417
|
+
def <<(attributes)
|
418
|
+
warn "`<<` is deprecated, please use `create_document`"
|
419
|
+
create_document(attributes)
|
420
|
+
end
|
368
421
|
|
369
422
|
# Create a new edge between two documents with certain attributes
|
370
423
|
#
|
@@ -378,7 +431,7 @@ module Ashikawa
|
|
378
431
|
def create_edge(from, to, attributes)
|
379
432
|
raise "Can't create an edge in a document collection" if @content_type == :document
|
380
433
|
response = send_request("edge?collection=#{@id}&from=#{from.id}&to=#{to.id}", :post => attributes)
|
381
|
-
Edge.new(@database, response)
|
434
|
+
Edge.new(@database, response).refresh!
|
382
435
|
end
|
383
436
|
|
384
437
|
# Add an index to the collection
|
@@ -408,8 +461,8 @@ module Ashikawa
|
|
408
461
|
# people = database['people']
|
409
462
|
# people.index(1244) #=> #<Index: id=1244...>
|
410
463
|
def index(id)
|
411
|
-
|
412
|
-
Index.new(self,
|
464
|
+
response = send_request("index/#{@name}/#{id}")
|
465
|
+
Index.new(self, response)
|
413
466
|
end
|
414
467
|
|
415
468
|
# Get all indices
|
@@ -486,7 +539,7 @@ module Ashikawa
|
|
486
539
|
#
|
487
540
|
# @return [String] Response from the server
|
488
541
|
# @api private
|
489
|
-
def send_request_for_this_collection(path, method={})
|
542
|
+
def send_request_for_this_collection(path, method = {})
|
490
543
|
send_request("collection/#{id}/#{path}", method)
|
491
544
|
end
|
492
545
|
|
@@ -503,14 +556,14 @@ module Ashikawa
|
|
503
556
|
self
|
504
557
|
end
|
505
558
|
|
506
|
-
# Send a request for the content with the given
|
559
|
+
# Send a request for the content with the given key
|
507
560
|
#
|
508
561
|
# @param [Integer] document_id The id of the document
|
509
562
|
# @param [Hash] opts The options for the request
|
510
563
|
# @return [Hash] parsed JSON response from the server
|
511
564
|
# @api private
|
512
|
-
def
|
513
|
-
send_request("#{@content_type}/#{@id}/#{
|
565
|
+
def send_request_for_content_key(document_key, opts = {})
|
566
|
+
send_request("#{@content_type}/#{@id}/#{document_key}", opts)
|
514
567
|
end
|
515
568
|
|
516
569
|
# Send a request for the content of this collection
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Ashikawa
|
2
|
+
module Core
|
3
|
+
# Configuration of Ashikawa::Core
|
4
|
+
class Configuration < Struct.new(:url, :connection, :logger, :adapter)
|
5
|
+
# The URL of the database instance
|
6
|
+
# @api private
|
7
|
+
# @return String
|
8
|
+
attr_accessor :url
|
9
|
+
|
10
|
+
# The Connection object
|
11
|
+
# @api private
|
12
|
+
# @return Connection
|
13
|
+
attr_accessor :connection
|
14
|
+
|
15
|
+
# The logger instance
|
16
|
+
# @api private
|
17
|
+
# @return Object
|
18
|
+
attr_accessor :logger
|
19
|
+
|
20
|
+
# The HTTP adapter instance
|
21
|
+
# @api private
|
22
|
+
# @return Object
|
23
|
+
attr_accessor :adapter
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -2,6 +2,7 @@ require "forwardable"
|
|
2
2
|
require "faraday"
|
3
3
|
require "null_logger"
|
4
4
|
require "uri"
|
5
|
+
require "equalizer"
|
5
6
|
require "ashikawa-core/request_preprocessor"
|
6
7
|
require "ashikawa-core/response_preprocessor"
|
7
8
|
|
@@ -11,6 +12,8 @@ module Ashikawa
|
|
11
12
|
class Connection
|
12
13
|
extend Forwardable
|
13
14
|
|
15
|
+
include Equalizer.new(:host, :scheme, :port)
|
16
|
+
|
14
17
|
# The host part of the connection
|
15
18
|
#
|
16
19
|
# @!method host
|
@@ -55,7 +58,7 @@ module Ashikawa
|
|
55
58
|
@connection = Faraday.new("#{api_string}/_api") do |connection|
|
56
59
|
connection.request :ashikawa_request, logger
|
57
60
|
connection.response :ashikawa_response, logger
|
58
|
-
connection.adapter
|
61
|
+
connection.adapter(*adapter)
|
59
62
|
end
|
60
63
|
end
|
61
64
|
|
@@ -113,7 +116,7 @@ module Ashikawa
|
|
113
116
|
# @return [Symbol] The HTTP verb used
|
114
117
|
# @api private
|
115
118
|
def http_verb(params)
|
116
|
-
[:post, :put, :delete].
|
119
|
+
[:post, :put, :delete].detect { |method_name|
|
117
120
|
params.has_key?(method_name)
|
118
121
|
} || :get
|
119
122
|
end
|
data/lib/ashikawa-core/cursor.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
require 'ashikawa-core/document'
|
2
|
+
require 'ashikawa-core/edge'
|
3
|
+
require 'equalizer'
|
2
4
|
|
3
5
|
module Ashikawa
|
4
6
|
module Core
|
@@ -7,6 +9,8 @@ module Ashikawa
|
|
7
9
|
class Cursor
|
8
10
|
include Enumerable
|
9
11
|
|
12
|
+
include Equalizer.new(:id)
|
13
|
+
|
10
14
|
# The ID of the cursor
|
11
15
|
# @return [String]
|
12
16
|
# @api public
|
@@ -54,7 +58,7 @@ module Ashikawa
|
|
54
58
|
|
55
59
|
begin
|
56
60
|
@current.each do |raw_document|
|
57
|
-
yield
|
61
|
+
yield parse_raw_document(raw_document)
|
58
62
|
end
|
59
63
|
end while next_batch
|
60
64
|
nil
|
@@ -72,6 +76,28 @@ module Ashikawa
|
|
72
76
|
|
73
77
|
private
|
74
78
|
|
79
|
+
# Parse a raw document and return a Document or Edge for it
|
80
|
+
#
|
81
|
+
# @param [Hash] raw_document
|
82
|
+
# @return Document | Edge
|
83
|
+
# @api private
|
84
|
+
def parse_raw_document(raw_document)
|
85
|
+
detect_document_class_for(raw_document).new(@database, raw_document)
|
86
|
+
end
|
87
|
+
|
88
|
+
# Detect if a raw document is a document or edge and return the class
|
89
|
+
#
|
90
|
+
# @param [Hash] raw_document
|
91
|
+
# @return class
|
92
|
+
# @api private
|
93
|
+
def detect_document_class_for(raw_document)
|
94
|
+
if raw_document.has_key?("_from") && raw_document.has_key?("_to")
|
95
|
+
Edge
|
96
|
+
else
|
97
|
+
Document
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
75
101
|
# Pull the raw data from the cursor into this object
|
76
102
|
#
|
77
103
|
# @param [Hash] raw_cursor
|
@@ -80,11 +106,7 @@ module Ashikawa
|
|
80
106
|
def parse_raw_cursor(raw_cursor)
|
81
107
|
@id = raw_cursor['id']
|
82
108
|
@has_more = raw_cursor['hasMore']
|
83
|
-
|
84
|
-
parse_documents_cursor(raw_cursor)
|
85
|
-
elsif raw_cursor['document']
|
86
|
-
parse_document_cursor(raw_cursor)
|
87
|
-
end
|
109
|
+
parse_documents_cursor(raw_cursor)
|
88
110
|
self
|
89
111
|
end
|
90
112
|
|
@@ -98,16 +120,6 @@ module Ashikawa
|
|
98
120
|
@length = raw_cursor['count'].to_i if raw_cursor.has_key?('count')
|
99
121
|
end
|
100
122
|
|
101
|
-
# Parse the cursor for a single document
|
102
|
-
#
|
103
|
-
# @param [Hash] raw_cursor
|
104
|
-
# @return self
|
105
|
-
# @api private
|
106
|
-
def parse_document_cursor(raw_cursor)
|
107
|
-
@current = [raw_cursor['document']]
|
108
|
-
@length = 1
|
109
|
-
end
|
110
|
-
|
111
123
|
# Get a new batch from the server
|
112
124
|
#
|
113
125
|
# @return [Boolean] Is there a next batch?
|
@@ -2,13 +2,13 @@ require "ashikawa-core/exceptions/client_error/resource_not_found/collection_not
|
|
2
2
|
require "ashikawa-core/collection"
|
3
3
|
require "ashikawa-core/connection"
|
4
4
|
require "ashikawa-core/cursor"
|
5
|
+
require "ashikawa-core/configuration"
|
6
|
+
require "ashikawa-core/transaction"
|
5
7
|
require "forwardable"
|
8
|
+
require "equalizer"
|
6
9
|
|
7
10
|
module Ashikawa
|
8
11
|
module Core
|
9
|
-
# Configuration of Ashikawa::Core
|
10
|
-
Configuration = Struct.new(:url, :connection, :logger, :adapter)
|
11
|
-
|
12
12
|
# An ArangoDB database
|
13
13
|
class Database
|
14
14
|
COLLECTION_TYPES = {
|
@@ -18,6 +18,8 @@ module Ashikawa
|
|
18
18
|
|
19
19
|
extend Forwardable
|
20
20
|
|
21
|
+
include Equalizer.new(:host, :port, :scheme)
|
22
|
+
|
21
23
|
# Delegate sending requests to the connection
|
22
24
|
def_delegator :@connection, :send_request
|
23
25
|
def_delegator :@connection, :host
|
@@ -43,13 +45,13 @@ module Ashikawa
|
|
43
45
|
# config.adapter = my_adapter
|
44
46
|
# config.logger = my_logger
|
45
47
|
# end
|
46
|
-
def initialize
|
48
|
+
def initialize
|
47
49
|
configuration = Ashikawa::Core::Configuration.new
|
48
50
|
yield(configuration)
|
49
51
|
@connection = configuration.connection || setup_new_connection(configuration.url, configuration.logger, configuration.adapter)
|
50
52
|
end
|
51
53
|
|
52
|
-
# Returns a list of all collections defined in the database
|
54
|
+
# Returns a list of all non-system collections defined in the database
|
53
55
|
#
|
54
56
|
# @return [Array<Collection>]
|
55
57
|
# @api public
|
@@ -59,8 +61,18 @@ module Ashikawa
|
|
59
61
|
# database["b"]
|
60
62
|
# database.collections # => [ #<Collection name="a">, #<Collection name="b">]
|
61
63
|
def collections
|
62
|
-
|
63
|
-
|
64
|
+
all_collections_where { |collection| !collection["name"].start_with?("_") }
|
65
|
+
end
|
66
|
+
|
67
|
+
# Returns a list of all system collections defined in the database
|
68
|
+
#
|
69
|
+
# @return [Array<Collection>]
|
70
|
+
# @api public
|
71
|
+
# @example Get an Array containing the Collections in the database
|
72
|
+
# database = Ashikawa::Core::Database.new("http://localhost:8529")
|
73
|
+
# database.system_collections # => [ #<Collection name="_a">, #<Collection name="_b">]
|
74
|
+
def system_collections
|
75
|
+
all_collections_where { |collection| collection["name"].start_with?("_") }
|
64
76
|
end
|
65
77
|
|
66
78
|
# Create a Collection based on name
|
@@ -73,11 +85,8 @@ module Ashikawa
|
|
73
85
|
# @example Create a new, volatile collection
|
74
86
|
# database = Ashikawa::Core::Database.new("http://localhost:8529")
|
75
87
|
# database.create_collection("a", :isVolatile => true) # => #<Collection name="a">
|
76
|
-
def create_collection(collection_identifier, opts={})
|
77
|
-
|
78
|
-
params[:isVolatile] = true if opts[:is_volatile] == true
|
79
|
-
params[:type] = COLLECTION_TYPES[opts[:content_type]] if opts.has_key?(:content_type)
|
80
|
-
response = send_request("collection", :post => params)
|
88
|
+
def create_collection(collection_identifier, opts = {})
|
89
|
+
response = send_request("collection", :post => translate_params(collection_identifier, opts))
|
81
90
|
Ashikawa::Core::Collection.new(self, response)
|
82
91
|
end
|
83
92
|
|
@@ -115,6 +124,22 @@ module Ashikawa
|
|
115
124
|
Query.new(self)
|
116
125
|
end
|
117
126
|
|
127
|
+
# Create a new Transaction for this database
|
128
|
+
#
|
129
|
+
# @param [String] action The JS action you want to execute
|
130
|
+
# @options collections [Array<String>] :read The collections you want to read from
|
131
|
+
# @options collections [Array<String>] :write The collections you want to write to
|
132
|
+
# @return [Object] The result of the transaction
|
133
|
+
# @api public
|
134
|
+
# @example Create a new Transaction
|
135
|
+
# transaction = database.create_transaction("function () { return 5; }", :read => ["collection_1"])
|
136
|
+
# transaction.execute #=> 5
|
137
|
+
def create_transaction(action, collections)
|
138
|
+
Ashikawa::Core::Transaction.new(self, action, collections)
|
139
|
+
end
|
140
|
+
|
141
|
+
private
|
142
|
+
|
118
143
|
# Setup the connection object
|
119
144
|
#
|
120
145
|
# @param [String] url
|
@@ -129,6 +154,58 @@ module Ashikawa
|
|
129
154
|
:adapter => adapter
|
130
155
|
})
|
131
156
|
end
|
157
|
+
|
158
|
+
# Parse a raw collection
|
159
|
+
#
|
160
|
+
# @param [Array] raw_collections
|
161
|
+
# @return [Array]
|
162
|
+
# @api private
|
163
|
+
def parse_raw_collections(raw_collections)
|
164
|
+
raw_collections.map { |collection|
|
165
|
+
Ashikawa::Core::Collection.new(self, collection)
|
166
|
+
}
|
167
|
+
end
|
168
|
+
|
169
|
+
# Translate the key options into the required format
|
170
|
+
#
|
171
|
+
# @param [Hash] key_options
|
172
|
+
# @return [Hash]
|
173
|
+
# @api private
|
174
|
+
def translate_key_options(key_options)
|
175
|
+
{
|
176
|
+
:type => key_options[:type].to_s,
|
177
|
+
:offset => key_options[:offset],
|
178
|
+
:increment => key_options[:increment],
|
179
|
+
:allowUserKeys => key_options[:allow_user_keys]
|
180
|
+
}
|
181
|
+
end
|
182
|
+
|
183
|
+
# Translate the params into the required format
|
184
|
+
#
|
185
|
+
# @param [String] collection_identifier
|
186
|
+
# @param [Hash] opts
|
187
|
+
# @return [Hash]
|
188
|
+
# @api private
|
189
|
+
def translate_params(collection_identifier, opts)
|
190
|
+
params = { :name => collection_identifier }
|
191
|
+
params[:isVolatile] = true if opts[:is_volatile] == true
|
192
|
+
params[:type] = COLLECTION_TYPES[opts[:content_type]] if opts.has_key?(:content_type)
|
193
|
+
params[:keyOptions] = translate_key_options(opts[:key_options]) if opts.has_key?(:key_options)
|
194
|
+
params
|
195
|
+
end
|
196
|
+
|
197
|
+
# Get all collections that fulfill a certain criteria
|
198
|
+
#
|
199
|
+
# @yield [raw_collection] Yields the raw collections so you can decide which to keep
|
200
|
+
# @yieldparam [raw_collection] A raw collection
|
201
|
+
# @yieldreturn [Boolean] Should the collection be kept
|
202
|
+
# @return [Array<Collection>]
|
203
|
+
# @api private
|
204
|
+
def all_collections_where(&block)
|
205
|
+
raw_collections = send_request("collection")["collections"]
|
206
|
+
raw_collections.keep_if(&block)
|
207
|
+
parse_raw_collections(raw_collections)
|
208
|
+
end
|
132
209
|
end
|
133
210
|
end
|
134
211
|
end
|