strokedb 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. data/CONTRIBUTORS +7 -0
  2. data/CREDITS +13 -0
  3. data/README +44 -0
  4. data/bin/sdbc +2 -0
  5. data/lib/config/config.rb +161 -0
  6. data/lib/data_structures/inverted_list.rb +297 -0
  7. data/lib/data_structures/point_query.rb +24 -0
  8. data/lib/data_structures/skiplist.rb +302 -0
  9. data/lib/document/associations.rb +107 -0
  10. data/lib/document/callback.rb +11 -0
  11. data/lib/document/coercions.rb +57 -0
  12. data/lib/document/delete.rb +28 -0
  13. data/lib/document/document.rb +684 -0
  14. data/lib/document/meta.rb +261 -0
  15. data/lib/document/slot.rb +199 -0
  16. data/lib/document/util.rb +27 -0
  17. data/lib/document/validations.rb +704 -0
  18. data/lib/document/versions.rb +106 -0
  19. data/lib/document/virtualize.rb +82 -0
  20. data/lib/init.rb +57 -0
  21. data/lib/stores/chainable_storage.rb +57 -0
  22. data/lib/stores/inverted_list_index/inverted_list_file_storage.rb +56 -0
  23. data/lib/stores/inverted_list_index/inverted_list_index.rb +49 -0
  24. data/lib/stores/remote_store.rb +172 -0
  25. data/lib/stores/skiplist_store/chunk.rb +119 -0
  26. data/lib/stores/skiplist_store/chunk_storage.rb +21 -0
  27. data/lib/stores/skiplist_store/file_chunk_storage.rb +44 -0
  28. data/lib/stores/skiplist_store/memory_chunk_storage.rb +37 -0
  29. data/lib/stores/skiplist_store/skiplist_store.rb +217 -0
  30. data/lib/stores/store.rb +5 -0
  31. data/lib/sync/chain_sync.rb +38 -0
  32. data/lib/sync/diff.rb +126 -0
  33. data/lib/sync/lamport_timestamp.rb +81 -0
  34. data/lib/sync/store_sync.rb +79 -0
  35. data/lib/sync/stroke_diff/array.rb +102 -0
  36. data/lib/sync/stroke_diff/default.rb +21 -0
  37. data/lib/sync/stroke_diff/hash.rb +186 -0
  38. data/lib/sync/stroke_diff/string.rb +116 -0
  39. data/lib/sync/stroke_diff/stroke_diff.rb +9 -0
  40. data/lib/util/blankslate.rb +42 -0
  41. data/lib/util/ext/blank.rb +50 -0
  42. data/lib/util/ext/enumerable.rb +36 -0
  43. data/lib/util/ext/fixnum.rb +16 -0
  44. data/lib/util/ext/hash.rb +22 -0
  45. data/lib/util/ext/object.rb +8 -0
  46. data/lib/util/ext/string.rb +35 -0
  47. data/lib/util/inflect.rb +217 -0
  48. data/lib/util/java_util.rb +9 -0
  49. data/lib/util/lazy_array.rb +54 -0
  50. data/lib/util/lazy_mapping_array.rb +64 -0
  51. data/lib/util/lazy_mapping_hash.rb +46 -0
  52. data/lib/util/serialization.rb +29 -0
  53. data/lib/util/trigger_partition.rb +136 -0
  54. data/lib/util/util.rb +38 -0
  55. data/lib/util/xml.rb +6 -0
  56. data/lib/view/view.rb +55 -0
  57. data/script/console +70 -0
  58. data/strokedb.rb +75 -0
  59. metadata +148 -0
@@ -0,0 +1,217 @@
1
+ # from the English gem (http://english.rubyforge.org/)
2
+ # http://english.rubyforge.org/rdoc/classes/English/Inflect.html
3
+ #
4
+ # last updated from http://english.rubyforge.org/svn/trunk/lib/english/inflect.rb on 3-31-2008
5
+
6
+ module English
7
+
8
+ # = English Nouns Number Inflection.
9
+ #
10
+ # This module provides english singular <-> plural noun inflections.
11
+ module Inflect
12
+
13
+ @singular_of = {}
14
+ @plural_of = {}
15
+
16
+ @singular_rules = []
17
+ @plural_rules = []
18
+
19
+ class << self
20
+ # Define a general exception.
21
+ def word(singular, plural=nil)
22
+ plural = singular unless plural
23
+ singular_word(singular, plural)
24
+ plural_word(singular, plural)
25
+ end
26
+
27
+ # Define a singularization exception.
28
+ def singular_word(singular, plural)
29
+ @singular_of[plural] = singular
30
+ end
31
+
32
+ # Define a pluralization exception.
33
+ def plural_word(singular, plural)
34
+ @plural_of[singular] = plural
35
+ end
36
+
37
+ # Define a general rule.
38
+ def rule(singular, plural)
39
+ singular_rule(singular, plural)
40
+ plural_rule(singular, plural)
41
+ end
42
+
43
+ # Define a singularization rule.
44
+ def singular_rule(singular, plural)
45
+ @singular_rules << [singular, plural]
46
+ end
47
+
48
+ # Define a plurualization rule.
49
+ def plural_rule(singular, plural)
50
+ @plural_rules << [singular, plural]
51
+ end
52
+
53
+ # Read prepared singularization rules.
54
+ def singularization_rules
55
+ return @singularization_rules if @singularization_rules
56
+ sorted = @singular_rules.sort_by{ |s, p| "#{p}".size }.reverse
57
+ @singularization_rules = sorted.collect do |s, p|
58
+ [ /#{p}$/, "#{s}" ]
59
+ end
60
+ end
61
+
62
+ # Read prepared pluralization rules.
63
+ def pluralization_rules
64
+ return @pluralization_rules if @pluralization_rules
65
+ sorted = @plural_rules.sort_by{ |s, p| "#{s}".size }.reverse
66
+ @pluralization_rules = sorted.collect do |s, p|
67
+ [ /#{s}$/, "#{p}" ]
68
+ end
69
+ end
70
+
71
+ #
72
+ def plural_of
73
+ @plural_of
74
+ end
75
+
76
+ #
77
+ def singular_of
78
+ @singular_of
79
+ end
80
+
81
+ # Convert an English word from plurel to singular.
82
+ #
83
+ # "boys".singular #=> boy
84
+ # "tomatoes".singular #=> tomato
85
+ #
86
+ def singular(word)
87
+ if result = singular_of[word]
88
+ return result.dup
89
+ end
90
+ result = word.dup
91
+ singularization_rules.each do |(match, replacement)|
92
+ break if result.gsub!(match, replacement)
93
+ end
94
+ return result
95
+ end
96
+
97
+ # Alias for #singular (a Railism).
98
+ #
99
+ alias_method(:singularize, :singular)
100
+
101
+ # Convert an English word from singular to plurel.
102
+ #
103
+ # "boy".plural #=> boys
104
+ # "tomato".plural #=> tomatoes
105
+ #
106
+ def plural(word)
107
+ if result = plural_of[word]
108
+ return result.dup
109
+ end
110
+ #return self.dup if /s$/ =~ self # ???
111
+ result = word.dup
112
+ pluralization_rules.each do |(match, replacement)|
113
+ break if result.gsub!(match, replacement)
114
+ end
115
+ return result
116
+ end
117
+
118
+ # Alias for #plural (a Railism).
119
+ alias_method(:pluralize, :plural)
120
+ end
121
+
122
+ # One argument means singular and plural are the same.
123
+
124
+ word 'equipment'
125
+ word 'information'
126
+ word 'money'
127
+ word 'species'
128
+ word 'series'
129
+ word 'fish'
130
+ word 'sheep'
131
+ word 'moose'
132
+ word 'hovercraft'
133
+
134
+ # Two arguments defines a singular and plural exception.
135
+
136
+ word 'Swiss' , 'Swiss'
137
+ word 'life' , 'lives'
138
+ word 'wife' , 'wives'
139
+ word 'virus' , 'viri'
140
+ word 'octopus' , 'octopi'
141
+ word 'goose' , 'geese'
142
+ word 'criterion' , 'criteria'
143
+ word 'alias' , 'aliases'
144
+ word 'status' , 'statuses'
145
+ word 'axis' , 'axes'
146
+ word 'crisis' , 'crises'
147
+ word 'testis' , 'testes'
148
+ word 'child' , 'children'
149
+ word 'person' , 'people'
150
+ word 'potato' , 'potatoes'
151
+ word 'tomato' , 'tomatoes'
152
+ word 'buffalo' , 'buffaloes'
153
+ word 'torpedo' , 'torpedoes'
154
+ word 'quiz' , 'quizes'
155
+ word 'matrix' , 'matrices'
156
+ word 'vertex' , 'vetices'
157
+ word 'index' , 'indices'
158
+ word 'ox' , 'oxen'
159
+ word 'mouse' , 'mice'
160
+ word 'louse' , 'lice'
161
+ word 'thesis' , 'theses'
162
+ word 'thief' , 'thieves'
163
+ word 'analysis' , 'analyses'
164
+
165
+ # One-way singularization exception (convert plural to singular).
166
+
167
+ singular_word 'cactus', 'cacti'
168
+
169
+ # General rules.
170
+
171
+ rule 'hive' , 'hives'
172
+ rule 'rf' , 'rves'
173
+ rule 'af' , 'aves'
174
+ rule 'ero' , 'eroes'
175
+ rule 'man' , 'men'
176
+ rule 'ch' , 'ches'
177
+ rule 'sh' , 'shes'
178
+ rule 'ss' , 'sses'
179
+ rule 'ta' , 'tum'
180
+ rule 'ia' , 'ium'
181
+ rule 'ra' , 'rum'
182
+ rule 'ay' , 'ays'
183
+ rule 'ey' , 'eys'
184
+ rule 'oy' , 'oys'
185
+ rule 'uy' , 'uys'
186
+ rule 'y' , 'ies'
187
+ rule 'x' , 'xes'
188
+ rule 'lf' , 'lves'
189
+ rule 'us' , 'uses'
190
+ rule '' , 's'
191
+
192
+ # One-way singular rules.
193
+
194
+ singular_rule 'of' , 'ofs' # proof
195
+ singular_rule 'o' , 'oes' # hero, heroes
196
+ singular_rule 'f' , 'ves'
197
+
198
+ # One-way plural rules.
199
+
200
+ plural_rule 'fe' , 'ves' # safe, wife
201
+ plural_rule 's' , 'ses'
202
+
203
+ end
204
+ end
205
+
206
+
207
+ class String
208
+ def singular
209
+ English::Inflect.singular(self)
210
+ end
211
+ alias_method(:singularize, :singular)
212
+
213
+ def plural
214
+ English::Inflect.plural(self)
215
+ end
216
+ alias_method(:pluralize, :plural)
217
+ end
@@ -0,0 +1,9 @@
1
+ require 'java'
2
+ # Some java overrides
3
+ module StrokeDB
4
+ module Util
5
+ def self.random_uuid
6
+ java.util.UUID.random_uuid.to_s
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,54 @@
1
+ module StrokeDB
2
+ # Lazy loads items from array.
3
+ #
4
+ # Lazy arrays are backed by Proc returning regular array
5
+ # on first call retrieving it from @load_with_proc.
6
+ #
7
+ # Example:
8
+ #
9
+ # ary = LazyArray.new.load_with { Time.now.to_s.split(/\s/) }
10
+ #
11
+ # On first attempt to access <tt>ary</tt> (including <tt>inspect</tt>) it will
12
+ # evaluate load_with's Proc and update own content with its result:
13
+ #
14
+ # ary
15
+ # # ==> ["Mon", "Mar", "17", "10:35:52", "+0200", "2008"]
16
+ #
17
+ class LazyArray < BlankSlate
18
+ def initialize(*args)
19
+ @load_with_proc = proc {|v| v}
20
+ @array = Array.new(*args)
21
+ end
22
+
23
+ # Proc to execute lazy loading
24
+ def load_with(&block)
25
+ @load_with_proc = block
26
+ self
27
+ end
28
+
29
+ # Make it look like array for outer world
30
+ def class
31
+ Array
32
+ end
33
+
34
+ def method_missing sym, *args, &blk
35
+ if @array.respond_to? sym
36
+ load!
37
+ @array.__send__ sym, *args, &blk
38
+ else
39
+ super
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ def load!
46
+ if @load_with_proc
47
+ @array.clear
48
+ @array.concat @load_with_proc.call(@array)
49
+ @load_with_proc = nil
50
+ end
51
+ end
52
+
53
+ end
54
+ end
@@ -0,0 +1,64 @@
1
+ module StrokeDB
2
+ # Lazy loads items from array applying procs on each read and write.
3
+ #
4
+ # Example:
5
+ #
6
+ # @ary[i] = 10
7
+ #
8
+ # applies "unmap proc" to new item value.
9
+ #
10
+ # @ary.at(i)
11
+ #
12
+ # applies "map proc" to value just have been read.
13
+ #
14
+ # StrokeDB uses this class to "follow" links to other documents
15
+ # found in slots in a lazy manner.
16
+ #
17
+ # player:
18
+ # model: [@#8b195509-f9c4-4fea-90c9-425b38bdda3e.ea5eda78-d410-44be-8b14-f4e33f6fa047]
19
+ # generation: 4
20
+ #
21
+ # when model collection item is fetched, reference followed and turned into document
22
+ # instance with mapping proc of lazy mapping array.
23
+ class LazyMappingArray < BlankSlate(Array)
24
+ def initialize(*args)
25
+ @map_proc = proc {|v| v}
26
+ @unmap_proc = proc {|v| v}
27
+ super(*args)
28
+ end
29
+
30
+ def map_with(&block)
31
+ @map_proc = block
32
+ self
33
+ end
34
+
35
+ def unmap_with(&block)
36
+ @unmap_proc = block
37
+ self
38
+ end
39
+
40
+ def class
41
+ Array
42
+ end
43
+
44
+ def method_missing sym, *args, &blk
45
+ super if sym.to_s =~ /^__/
46
+ mname = "__#{::BlankSlate::MethodMapping[sym.to_s] || sym}"
47
+
48
+ case sym
49
+ when :push, :unshift, :<<, :[]=, :index, :-
50
+ last = args.pop
51
+ last = last.is_a?(Array) ? last.map{|v| @unmap_proc.call(v) } : @unmap_proc.call(last)
52
+ args.push last
53
+
54
+ __send__(mname, *args, &blk)
55
+
56
+ when :[], :slice, :at, :map, :shift, :pop, :include?, :last, :first, :zip, :each, :inject, :each_with_index
57
+ __map{|v| @map_proc.call(v) }.__send__(sym, *args, &blk)
58
+
59
+ else
60
+ __send__(mname, *args, &blk)
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,46 @@
1
+ module StrokeDB
2
+ class LazyMappingHash < BlankSlate(Hash)
3
+ def initialize(original = {}, decoder = nil, encoder = nil)
4
+ @decoder = decoder || proc {|v| v}
5
+ @encoder = encoder || proc {|v| v}
6
+ super(default)
7
+ original.each {|k,v| self.__squarebracket_set(k,v) }
8
+ end
9
+
10
+ def map_with(&block)
11
+ @encoder = block
12
+ self
13
+ end
14
+
15
+ def unmap_with(&block)
16
+ @decoder = block
17
+ self
18
+ end
19
+
20
+ def class
21
+ Hash
22
+ end
23
+
24
+ def method_missing sym, *args, &blk
25
+ super if sym.to_s =~ /^__/
26
+ mname = "__#{::BlankSlate::MethodMapping[sym.to_s] || sym}"
27
+
28
+ case sym
29
+ when :keys, :values
30
+ __send__(mname, *args, &blk).map{|v| @encoder.call(v) }
31
+
32
+ when :each
33
+ self.__each do |k,v|
34
+ yield @encoder.call(k), @encoder.call(v)
35
+ end
36
+
37
+ when :[], :[]=
38
+ args.map!{|v| @decoder.call(v) }
39
+ @encoder.call __send__(mname, *args, &blk)
40
+
41
+ else
42
+ __send__(mname, *args, &blk)
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,29 @@
1
+ module StrokeDB
2
+ module JsonSerializationMethod
3
+ def serialize(x)
4
+ x.to_json
5
+ end
6
+ def deserialize(x)
7
+ JSON.parse(x)
8
+ end
9
+ end
10
+
11
+ module MarshalSerializationMethod
12
+ def serialize(x)
13
+ x = x.to_raw if x.respond_to?(:to_raw)
14
+ Marshal.dump(x)
15
+ end
16
+ def deserialize(x)
17
+ Marshal.load(x)
18
+ end
19
+ end
20
+
21
+
22
+ def self.serialization_method=(method_name)
23
+ StrokeDB.extend StrokeDB.const_get("#{method_name.to_s.camelize}SerializationMethod")
24
+ end
25
+
26
+ self.serialization_method = :marshal
27
+
28
+
29
+ end
@@ -0,0 +1,136 @@
1
+ module Enumerable
2
+ class TriggerPartitionContext
3
+ def initialize(enum, &block)
4
+ @enum = enum
5
+ @cont = block
6
+ end
7
+ def fill(&block)
8
+ @fill = block
9
+ self
10
+ end
11
+ def emit
12
+ partitions = []
13
+ cont = @cont
14
+ fill = @fill
15
+ p = @enum.inject(nil) do |part, elem|
16
+ if part && cont.call(part, elem)
17
+ fill.call(part, elem)
18
+ part
19
+ else
20
+ partitions << part if part
21
+ yield(elem)
22
+ end
23
+ end
24
+ partitions << p if p
25
+ partitions
26
+ end
27
+ end
28
+ def trigger_partition(&block)
29
+ TriggerPartitionContext.new(self, &block)
30
+ end
31
+
32
+ class TriggerPartitions
33
+ def self.partition(list)
34
+ partitions = []
35
+ p = list.inject(nil) do |part, elem|
36
+ if part && continue?(part, elem)
37
+ fill(part, elem)
38
+ part
39
+ else
40
+ partitions << part if part
41
+ emit(elem)
42
+ end
43
+ end
44
+ partitions << p if p
45
+ partitions
46
+ end
47
+ def self.continue?(p, e)
48
+ true
49
+ end
50
+ def self.emit(e)
51
+ [e]
52
+ end
53
+ def self.fill(p, e)
54
+ p << e
55
+ end
56
+ end
57
+ end
58
+
59
+ if __FILE__ == $0
60
+ arr = [1,2,3,4,5, -1, -4, -3, 5, 6, 7, 8, -6, -7]
61
+ parr = arr.trigger_partition do |partition, element|
62
+ partition[0] > 0 && element > 0 || partition[0] < 0 && element < 0
63
+ end.fill do |p, e|
64
+ p << e
65
+ end.emit do |e|
66
+ [e]
67
+ end
68
+
69
+ p arr
70
+ p parr
71
+
72
+ # Class might be faster
73
+ class SignPartitions < Enumerable::TriggerPartitions
74
+ def self.continue?(partition, element)
75
+ partition[0] > 0 && element > 0 || partition[0] < 0 && element < 0
76
+ end
77
+ end
78
+
79
+ p Enumerable::TriggerPartitions.partition(arr)
80
+ p SignPartitions.partition(arr)
81
+
82
+ require 'benchmark'
83
+ include Benchmark
84
+ n = 1000
85
+ bm(32) do |x|
86
+ x.report("#{n} times:" ) do
87
+ n.times do
88
+ arr.trigger_partition do |partition, element|
89
+ partition[0] > 0 && element > 0 || partition[0] < 0 && element < 0
90
+ end.fill do |p, e|
91
+ p << e
92
+ end.emit do |e|
93
+ [e]
94
+ end
95
+ end
96
+ end
97
+ arrL = arr*28
98
+ x.report("#{n} times (x28 larger data):" ) do
99
+ n.times do
100
+ arrL.trigger_partition do |partition, element|
101
+ partition[0] > 0 && element > 0 || partition[0] < 0 && element < 0
102
+ end.fill do |p, e|
103
+ p << e
104
+ end.emit do |e|
105
+ [e]
106
+ end
107
+ end
108
+ end
109
+ # 35% faster
110
+ x.report("#{n} times (SignPartitions):" ) do
111
+ (n/5).times do
112
+ SignPartitions.partition(arrL)
113
+ SignPartitions.partition(arrL)
114
+ SignPartitions.partition(arrL)
115
+ SignPartitions.partition(arrL)
116
+ SignPartitions.partition(arrL)
117
+ end
118
+ end
119
+ # + 17% faster (relative to SignPartitions)
120
+ x.report("#{n} times (raw code):" ) do
121
+ n.times do
122
+ parts = []
123
+ p = arrL.inject(nil) do |partition, element|
124
+ if partition && (partition[0] > 0 && element > 0 || partition[0] < 0 && element < 0)
125
+ partition << element
126
+ partition
127
+ else
128
+ parts << partition if partition
129
+ [element]
130
+ end
131
+ end
132
+ parts << p if p
133
+ end
134
+ end
135
+ end
136
+ end
data/lib/util/util.rb ADDED
@@ -0,0 +1,38 @@
1
+ module StrokeDB
2
+ module Util
3
+
4
+ class ::Object
5
+ # Uses references to documents (compared to to_raw using hashes instead)
6
+ def to_optimized_raw
7
+ case self
8
+ when Array
9
+ map{|v| v.to_optimized_raw }
10
+ when Hash
11
+ new_hash = {}
12
+ each_pair{|k,v| new_hash[k.to_optimized_raw] = v.to_optimized_raw}
13
+ new_hash
14
+ else
15
+ self
16
+ end
17
+ end
18
+ end
19
+
20
+ unless RUBY_PLATFORM =~ /java/
21
+ require 'uuidtools'
22
+ def self.random_uuid
23
+ ::UUID.random_create.to_s
24
+ end
25
+ end
26
+
27
+ class CircularReferenceCondition < Exception ; end
28
+ class << self
29
+ def catch_circular_reference(value,name = 'StrokeDB.reference_stack')
30
+ stack = Thread.current[name] ||= []
31
+ raise CircularReferenceCondition if stack.find{|v| value == v}
32
+ stack << value
33
+ yield
34
+ stack.pop
35
+ end
36
+ end
37
+ end
38
+ end
data/lib/util/xml.rb ADDED
@@ -0,0 +1,6 @@
1
+ class Object #:nodoc:
2
+ def to_xml(options = {})
3
+ xml = options[:builder]
4
+ xml.tag!(options[:root], options[:skip_types] ? {} : {:type => self.class.to_s.underscore},to_s)
5
+ end
6
+ end
data/lib/view/view.rb ADDED
@@ -0,0 +1,55 @@
1
+ module StrokeDB
2
+ View = Meta.new(:uuid => VIEW_UUID) do
3
+ attr_accessor :map_with_proc
4
+ attr_reader :reduce_with_proc
5
+
6
+ on_initialization do |view|
7
+ view.map_with_proc = proc {|doc, *args| doc }
8
+ end
9
+
10
+ def reduce_with(&block)
11
+ @reduce_with_proc = block
12
+ self
13
+ end
14
+
15
+ def map_with(&block)
16
+ @map_with_proc = block
17
+ self
18
+ end
19
+
20
+ def emit(*args)
21
+ ViewCut.new(store, :view => self, :args => args, :timestamp_state => LTS.zero.counter).emit
22
+ end
23
+
24
+ end
25
+ ViewCut = Meta.new(:uuid => VIEWCUT_UUID) do
26
+
27
+ on_new_document do |cut|
28
+ cut.instance_eval do
29
+ if view.is_a?(View)
30
+ @map_with_proc = view.map_with_proc
31
+ @reduce_with_proc = view.reduce_with_proc
32
+ end
33
+ end
34
+ end
35
+
36
+ before_save do |cut|
37
+ view = cut.view
38
+ view.last_cut = cut if view[:last_cut].nil? or (cut[:previous] && view.last_cut == cut.previous)
39
+ view.save!
40
+ end
41
+
42
+
43
+ def emit
44
+ mapped = []
45
+ store.each(:after_timestamp => timestamp_state, :include_versions => view[:include_versions]) do |doc|
46
+ mapped << @map_with_proc.call(doc,*args)
47
+ end
48
+ documents = (@reduce_with_proc ? mapped.select {|doc| @reduce_with_proc.call(doc,*args) } : mapped).map{|d| d.is_a?(Document) ? d.extend(VersionedDocument) : d}
49
+ ViewCut.new(store, :documents => documents, :view => view, :args => args, :timestamp_state => store.timestamp.counter, :previous => timestamp_state == 0 ? nil : self)
50
+ end
51
+ def to_a
52
+ documents
53
+ end
54
+ end
55
+ end