flex 0.4.2 → 1.0.1
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.
- data/LICENSE +1 -1
- data/README.md +46 -7
- data/VERSION +1 -1
- data/flex.gemspec +13 -19
- data/lib/flex.rb +66 -392
- data/lib/flex/api_stubs.rb +1268 -0
- data/lib/flex/api_templates/cluster_api.yml +94 -0
- data/lib/flex/api_templates/core_api.yml +202 -0
- data/lib/flex/api_templates/indices_api.yml +304 -0
- data/lib/flex/class_proxy/base.rb +20 -6
- data/lib/flex/class_proxy/templates.rb +97 -0
- data/lib/flex/class_proxy/templates/doc.rb +91 -0
- data/lib/flex/class_proxy/templates/search.rb +72 -0
- data/lib/flex/configuration.rb +17 -49
- data/lib/flex/deprecation.rb +153 -0
- data/lib/flex/errors.rb +2 -1
- data/lib/flex/http_clients/base.rb +15 -0
- data/lib/flex/http_clients/loader.rb +51 -0
- data/lib/flex/http_clients/patron.rb +9 -7
- data/lib/flex/http_clients/rest_client.rb +6 -8
- data/lib/flex/logger.rb +24 -3
- data/lib/flex/prog_bar.rb +11 -6
- data/lib/flex/rails.rb +1 -13
- data/lib/flex/result.rb +8 -2
- data/lib/flex/result/document.rb +42 -13
- data/lib/flex/result/multi_get.rb +24 -0
- data/lib/flex/result/search.rb +1 -24
- data/lib/flex/struct/array.rb +25 -0
- data/lib/flex/struct/hash.rb +105 -0
- data/lib/flex/{result/collection.rb → struct/paginable.rb} +16 -9
- data/lib/flex/struct/prunable.rb +60 -0
- data/lib/flex/struct/symbolize.rb +27 -0
- data/lib/flex/tasks.rb +26 -86
- data/lib/flex/template.rb +60 -120
- data/lib/flex/template/common.rb +42 -0
- data/lib/flex/template/logger.rb +66 -0
- data/lib/flex/template/partial.rb +12 -15
- data/lib/flex/template/search.rb +6 -6
- data/lib/flex/template/slim_search.rb +1 -1
- data/lib/flex/template/tags.rb +19 -9
- data/lib/flex/templates.rb +20 -0
- data/lib/flex/utility_methods.rb +80 -89
- data/lib/flex/utils.rb +68 -25
- data/lib/flex/variables.rb +55 -4
- data/lib/tasks.rake +28 -0
- metadata +61 -85
- data/bin/flexes +0 -174
- data/lib/flex/api_methods.yml +0 -108
- data/lib/flex/class_proxy/loader.rb +0 -102
- data/lib/flex/class_proxy/model.rb +0 -45
- data/lib/flex/class_proxy/model_sync.rb +0 -23
- data/lib/flex/class_proxy/related_model.rb +0 -23
- data/lib/flex/instance_proxy/base.rb +0 -29
- data/lib/flex/instance_proxy/model.rb +0 -102
- data/lib/flex/instance_proxy/related_model.rb +0 -7
- data/lib/flex/loader.rb +0 -18
- data/lib/flex/manager.rb +0 -61
- data/lib/flex/model.rb +0 -24
- data/lib/flex/rails/engine.rb +0 -23
- data/lib/flex/rails/helper.rb +0 -16
- data/lib/flex/related_model.rb +0 -16
- data/lib/flex/result/indifferent_access.rb +0 -11
- data/lib/flex/result/source_document.rb +0 -63
- data/lib/flex/result/source_search.rb +0 -32
- data/lib/flex/structure/indifferent_access.rb +0 -44
- data/lib/flex/structure/mergeable.rb +0 -21
- data/lib/flex/template/base.rb +0 -41
- data/lib/flex/template/info.rb +0 -68
- data/lib/generators/flex/setup/setup_generator.rb +0 -48
- data/lib/generators/flex/setup/templates/flex_config.yml +0 -16
- data/lib/generators/flex/setup/templates/flex_dir/es.rb.erb +0 -18
- data/lib/generators/flex/setup/templates/flex_dir/es.yml.erb +0 -19
- data/lib/generators/flex/setup/templates/flex_dir/es_extender.rb.erb +0 -17
- data/lib/generators/flex/setup/templates/flex_initializer.rb.erb +0 -44
- data/lib/tasks/index.rake +0 -17
- data/test/flex.irt +0 -143
- data/test/flex/configuration.irt +0 -53
- data/test/irt_helper.rb +0 -12
@@ -0,0 +1,42 @@
|
|
1
|
+
module Flex
|
2
|
+
class Template
|
3
|
+
module Common
|
4
|
+
|
5
|
+
attr_reader :name, :partials, :tags, :data
|
6
|
+
|
7
|
+
def setup(host_flex, name=nil, *vars)
|
8
|
+
@host_flex = host_flex
|
9
|
+
@name = name
|
10
|
+
@source_vars = Vars.new(*vars) if is_a?(Flex::Template)
|
11
|
+
self
|
12
|
+
end
|
13
|
+
|
14
|
+
def interpolate_partials(vars)
|
15
|
+
@partials.each do |name|
|
16
|
+
partial_assigned_vars = vars[name]
|
17
|
+
next if Prunable::VALUES.include?(partial_assigned_vars)
|
18
|
+
vars[name] = case partial_assigned_vars
|
19
|
+
when Array
|
20
|
+
partial_assigned_vars.map {|v| @host_flex.partials[name].interpolate(vars, v)}
|
21
|
+
# other partial name (usable as a case output)
|
22
|
+
when Symbol
|
23
|
+
@host_flex.partials[partial_assigned_vars].interpolate(vars, vars[partial_assigned_vars])
|
24
|
+
# a partial object
|
25
|
+
when Template::Partial
|
26
|
+
partial_assigned_vars.interpolate(vars, vars)
|
27
|
+
# on-the-fly partial creation (an empty string would prune it before)
|
28
|
+
when String
|
29
|
+
Template::Partial.new(partial_assigned_vars).interpolate(vars, vars)
|
30
|
+
# switch to include the partial (a false value would prune it before)
|
31
|
+
when TrueClass
|
32
|
+
@host_flex.partials[name].interpolate(vars, vars)
|
33
|
+
else
|
34
|
+
@host_flex.partials[name].interpolate(vars, partial_assigned_vars)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
vars
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module Flex
|
2
|
+
class Template
|
3
|
+
module Logger
|
4
|
+
|
5
|
+
def caller_line
|
6
|
+
caller.find{|l| l !~ /(#{LIB_PATHS.join('|')})/}
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def template_name
|
12
|
+
@host_flex && @name && "#{@host_flex.context}.#@name" || 'template'
|
13
|
+
end
|
14
|
+
|
15
|
+
def log_render(int, path, encoded_data, result)
|
16
|
+
logger = Conf.logger
|
17
|
+
return unless logger.is_a?(Flex::Logger)
|
18
|
+
logger.info Dye.dye("Rendered #{template_name} from: #{caller_line}", :blue, :bold)
|
19
|
+
return unless logger.level == ::Logger::DEBUG
|
20
|
+
|
21
|
+
h = {}
|
22
|
+
if logger.debug_variables
|
23
|
+
h[:variables] = int[:vars] if int
|
24
|
+
end
|
25
|
+
if logger.debug_request
|
26
|
+
h[:request] = {}
|
27
|
+
h[:request][:method] = method
|
28
|
+
h[:request][:path] = path
|
29
|
+
h[:request][:data] = begin
|
30
|
+
MultiJson.decode(encoded_data) unless encoded_data.nil?
|
31
|
+
rescue MultiJson::DecodeError
|
32
|
+
encoded_data
|
33
|
+
end
|
34
|
+
h[:request].delete(:data) if h[:request][:data].nil?
|
35
|
+
end
|
36
|
+
if logger.debug_result
|
37
|
+
h[:result] = result if result
|
38
|
+
end
|
39
|
+
logger.debug logger.curl_format ? curl_format(h[:request]) : yaml_format(h)
|
40
|
+
end
|
41
|
+
|
42
|
+
def curl_format(h)
|
43
|
+
pretty = h[:path] =~ /\?/ ? '&pretty=1' : '?pretty=1'
|
44
|
+
curl = %(curl -X#{method} "#{Conf.base_uri}#{h[:path]}#{pretty}")
|
45
|
+
if h[:data]
|
46
|
+
data = h[:data].is_a?(String) ? h[:data] : MultiJson.encode(h[:data], :pretty => true)
|
47
|
+
curl << %( -d '\n#{data}\n')
|
48
|
+
end
|
49
|
+
curl
|
50
|
+
end
|
51
|
+
|
52
|
+
def yaml_format(hash)
|
53
|
+
hash.to_yaml.split("\n").map do |l|
|
54
|
+
case l
|
55
|
+
when /^---$/
|
56
|
+
when /^( |-)/
|
57
|
+
Dye.dye(l, :blue)
|
58
|
+
when /^:(variables|request|result)/
|
59
|
+
Dye.dye(l, :magenta, :bold) + (Dye.color ? Dye.sgr(:blue) : '')
|
60
|
+
end
|
61
|
+
end.compact.join("\n") + "\n"
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -2,28 +2,25 @@ module Flex
|
|
2
2
|
class Template
|
3
3
|
class Partial
|
4
4
|
|
5
|
-
include
|
5
|
+
include Common
|
6
6
|
|
7
|
-
def initialize(data
|
8
|
-
@data
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
@
|
13
|
-
@variables = tags.variables
|
7
|
+
def initialize(data)
|
8
|
+
@data = data
|
9
|
+
tags = Tags.new
|
10
|
+
stringified = tags.stringify(data)
|
11
|
+
@partials, @tags = tags.partial_and_tag_names
|
12
|
+
@tags_variables = tags.variables
|
14
13
|
instance_eval <<-ruby, __FILE__, __LINE__
|
15
|
-
def interpolate(
|
16
|
-
|
17
|
-
vars
|
18
|
-
main_vars.add(@variables, sym_vars)
|
19
|
-
vars = process_vars(main_vars)
|
14
|
+
def interpolate(vars={}, partial_assigned_vars={})
|
15
|
+
vars = Vars.new(vars, @tags_variables, partial_assigned_vars)
|
16
|
+
vars = interpolate_partials(vars)
|
20
17
|
#{stringified}
|
21
18
|
end
|
22
19
|
ruby
|
23
20
|
end
|
24
21
|
|
25
|
-
def
|
26
|
-
{name.to_s => @data}.to_yaml
|
22
|
+
def to_source
|
23
|
+
{@name.to_s => @data}.to_yaml
|
27
24
|
end
|
28
25
|
|
29
26
|
end
|
data/lib/flex/template/search.rb
CHANGED
@@ -3,17 +3,17 @@ module Flex
|
|
3
3
|
class Search < Template
|
4
4
|
|
5
5
|
def initialize(data, vars=nil)
|
6
|
-
super('GET',
|
6
|
+
super('GET', '/<<index>>/<<type>>/_search', data, vars)
|
7
7
|
end
|
8
8
|
|
9
|
-
def to_a(vars
|
10
|
-
|
11
|
-
a
|
12
|
-
a << @instance_vars unless @instance_vars.nil?
|
9
|
+
def to_a(*vars)
|
10
|
+
a = super
|
11
|
+
2.times{ a.delete_at 0 }
|
13
12
|
a
|
14
13
|
end
|
15
14
|
|
16
|
-
def to_msearch(vars
|
15
|
+
def to_msearch(*vars)
|
16
|
+
vars = Vars.new(*vars)
|
17
17
|
int = interpolate(vars, strict=true)
|
18
18
|
header = {}
|
19
19
|
header[:index] = int[:vars][:index] if int[:vars][:index]
|
data/lib/flex/template/tags.rb
CHANGED
@@ -2,11 +2,21 @@ module Flex
|
|
2
2
|
class Template
|
3
3
|
class Tags < Array
|
4
4
|
|
5
|
-
TAG_REGEXP = /<<\s*(\w+)\s*(?:=([^>]*))*>>/
|
5
|
+
TAG_REGEXP = /<<\s*([\w\.]+)\s*(?:=([^>]*))*>>/
|
6
6
|
|
7
|
+
# tag variables are the defaults defined with the tag
|
8
|
+
# a variable could be optional, and the default could be nil
|
7
9
|
def variables
|
8
|
-
tag_variables =
|
9
|
-
each
|
10
|
+
tag_variables = Vars.new
|
11
|
+
each do |t|
|
12
|
+
if t.default || t.optional
|
13
|
+
if t.name =~ /\./ # set default for nested var
|
14
|
+
tag_variables.store_nested(t.name, t.default)
|
15
|
+
else
|
16
|
+
tag_variables[t.name] = t.default
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
10
20
|
tag_variables
|
11
21
|
end
|
12
22
|
|
@@ -16,15 +26,19 @@ module Flex
|
|
16
26
|
match =~ TAG_REGEXP
|
17
27
|
t = Tag.new($1, $2)
|
18
28
|
push t unless find{|i| i.name == t.name}
|
19
|
-
|
29
|
+
(match !~ /^"/) ? "\#{vars.get_prunable(:'#{t.name}')}" : "vars.get_prunable(:'#{t.name}')"
|
20
30
|
end
|
21
31
|
end
|
22
32
|
|
33
|
+
def partial_and_tag_names
|
34
|
+
map(&:name).partition{|n| n.to_s =~ /^_/}
|
35
|
+
end
|
36
|
+
|
23
37
|
end
|
24
38
|
|
25
39
|
class Tag
|
26
40
|
|
27
|
-
RESERVED = [:path, :data, :params, :
|
41
|
+
RESERVED = [:context, :path, :data, :params, :no_pruning, :raw_result, :raise]
|
28
42
|
|
29
43
|
attr_reader :optional, :name, :default
|
30
44
|
|
@@ -36,10 +50,6 @@ module Flex
|
|
36
50
|
@default = YAML.load(default) unless default.nil?
|
37
51
|
end
|
38
52
|
|
39
|
-
def stringify(in_string)
|
40
|
-
in_string ? "\#{prunable(:#{name}, vars)}" : "prunable(:#{name}, vars)"
|
41
|
-
end
|
42
|
-
|
43
53
|
end
|
44
54
|
|
45
55
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Flex
|
2
|
+
module Templates
|
3
|
+
|
4
|
+
extend self
|
5
|
+
attr_accessor :contexts
|
6
|
+
@contexts = []
|
7
|
+
|
8
|
+
def self.included(context)
|
9
|
+
context.class_eval do
|
10
|
+
Flex::Templates.contexts |= [context]
|
11
|
+
@flex ||= ClassProxy::Base.new(context)
|
12
|
+
@flex.extend(ClassProxy::Templates).init
|
13
|
+
def self.flex; @flex end
|
14
|
+
def self.template_methods; flex.templates.keys end
|
15
|
+
eval "extend module #{context}::FlexTemplateMethods; self end"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
data/lib/flex/utility_methods.rb
CHANGED
@@ -1,18 +1,13 @@
|
|
1
1
|
module Flex
|
2
2
|
module UtilityMethods
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
# or a hash. It can contain interpolation tags as usual.
|
7
|
-
# You can pass an optional hash of interpolation arguments (or query string :params).
|
8
|
-
# See also the Flex::Template::Search documentation
|
9
|
-
def search(data, args={})
|
10
|
-
Template::Search.new(data).render(args)
|
4
|
+
def search(data, vars={})
|
5
|
+
Template::Search.new(data).setup(Flex.flex).render(vars)
|
11
6
|
end
|
12
7
|
|
13
8
|
# like Flex.search, but it will use the Flex::Template::SlimSearch instead
|
14
|
-
def slim_search(data,
|
15
|
-
Template::SlimSearch.new(data).render(
|
9
|
+
def slim_search(data, vars={})
|
10
|
+
Template::SlimSearch.new(data).setup(Flex.flex).render(vars)
|
16
11
|
end
|
17
12
|
|
18
13
|
%w[HEAD GET PUT POST DELETE].each do |m|
|
@@ -31,109 +26,105 @@ module Flex
|
|
31
26
|
MultiJson.encode(YAML.load(yaml))
|
32
27
|
end
|
33
28
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
def process_bulk(args)
|
39
|
-
raise ArgumentError, "Array expected as :collection (got #{args[:collection].inspect})" \
|
40
|
-
unless args[:collection].is_a?(Array)
|
41
|
-
|
42
|
-
index = args[:index] || Configuration.variables[:index]
|
43
|
-
type = args[:type] || Configuration.variables[:type]
|
44
|
-
action = args[:action] || 'index'
|
45
|
-
|
46
|
-
meta = {}
|
47
|
-
[:version, :routing, :percolate, :parent, :timestamp, :ttl].each do |opt|
|
48
|
-
meta["_#{opt}"] = args[opt] if args[opt]
|
49
|
-
end
|
50
|
-
lines = args[:collection].map do |d|
|
51
|
-
# skips indexing for objects that return nil as the indexed_json or are not flex_indexable?
|
52
|
-
unless action == 'delete'
|
53
|
-
next if d.respond_to?(:flex_indexable?) && !d.flex_indexable?
|
54
|
-
json = get_json(d) || next
|
55
|
-
end
|
56
|
-
m = {}
|
57
|
-
m['_index'] = get_index(d) || index
|
58
|
-
m['_type'] = get_type(d) || type
|
59
|
-
m['_id'] = get_id(d) || d # we could pass an array of ids to delete
|
60
|
-
parent = get_parent(d)
|
61
|
-
m['_parent'] = parent if parent
|
62
|
-
routing = get_routing(d)
|
63
|
-
m['_routing'] = routing if routing
|
64
|
-
line = MultiJson.encode({action => meta.merge(m)})
|
65
|
-
line << "\n#{json}" unless action == 'delete'
|
66
|
-
line
|
67
|
-
end.compact
|
68
|
-
|
69
|
-
bulk(args.merge(:lines => lines.join("\n") + "\n")) if lines.size > 0
|
29
|
+
def reload!
|
30
|
+
flex.variables.deep_merge! Conf.variables
|
31
|
+
Templates.contexts.each {|c| c.flex.reload!}
|
32
|
+
true
|
70
33
|
end
|
71
34
|
|
72
|
-
def
|
73
|
-
|
74
|
-
:action => 'index'}.merge(options) )
|
75
|
-
|
35
|
+
def doc(*args)
|
36
|
+
flex.doc(*args)
|
76
37
|
end
|
77
38
|
|
78
|
-
def
|
79
|
-
|
80
|
-
:action => 'delete'}.merge(options) )
|
39
|
+
def scan_search(*args, &block)
|
40
|
+
flex.scan_search(*args, &block)
|
81
41
|
end
|
82
42
|
|
83
|
-
|
43
|
+
def scan_all(*vars, &block)
|
44
|
+
flex.scan_search(:match_all, *vars) do |raw_result|
|
45
|
+
batch = raw_result['hits']['hits']
|
46
|
+
block.call(batch)
|
47
|
+
end
|
48
|
+
end
|
84
49
|
|
85
|
-
def
|
86
|
-
|
50
|
+
def dump_all(*vars, &block)
|
51
|
+
refresh_index(*vars)
|
52
|
+
scan_all({:params => {:fields => '*,_source'}}, *vars) do |batch|
|
53
|
+
batch.map!{|document| document.delete('_score'); document}
|
54
|
+
block.call(batch)
|
55
|
+
end
|
87
56
|
end
|
88
57
|
|
89
|
-
|
90
|
-
|
58
|
+
# refresh and pull the full document from the index
|
59
|
+
def dump_one(*vars)
|
60
|
+
refresh_index(*vars)
|
61
|
+
document = search_by_id({:params => {:fields => '*,_source'}}, *vars)
|
62
|
+
document.delete('_score')
|
63
|
+
document
|
91
64
|
end
|
92
65
|
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
66
|
+
# You should use Flex.post_bulk_string if you have an already formatted bulk data-string
|
67
|
+
def post_bulk_collection(collection, options={})
|
68
|
+
raise ArgumentError, "Array expected as :collection, got #{collection.inspect}" \
|
69
|
+
unless collection.is_a?(Array)
|
70
|
+
bulk_string = ''
|
71
|
+
collection.each do |d|
|
72
|
+
bulk_string << build_bulk_string(d, options)
|
100
73
|
end
|
74
|
+
post_bulk_string(:bulk_string => bulk_string) unless bulk_string.empty?
|
101
75
|
end
|
102
76
|
|
103
|
-
def
|
104
|
-
case
|
105
|
-
when
|
106
|
-
|
107
|
-
when
|
108
|
-
|
109
|
-
|
77
|
+
def build_bulk_string(document, options={})
|
78
|
+
case document
|
79
|
+
when Hash
|
80
|
+
bulk_string_from_hash(document, options)
|
81
|
+
when Flex::ModelIndexer, Flex::ActiveModel
|
82
|
+
bulk_string_from_flex(document, options)
|
83
|
+
else
|
84
|
+
raise NotImplementedError, "Unable to convert the document #{document.inspect} to a bulk string."
|
110
85
|
end
|
111
86
|
end
|
112
87
|
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
when d.respond_to?(:routing) then d.routing
|
118
|
-
when d.is_a?(Hash) then d.delete(:_routing) || d.delete('_routing') ||
|
119
|
-
d.delete(:routing) || d.delete('routing')
|
120
|
-
end
|
88
|
+
private
|
89
|
+
|
90
|
+
def perform(*args)
|
91
|
+
Template.new(*args).setup(Flex.flex).render
|
121
92
|
end
|
122
93
|
|
123
|
-
def
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
94
|
+
def bulk_string_from_hash(document, options)
|
95
|
+
meta = Utils.slice_hash(document, '_index', '_type', '_id')
|
96
|
+
if document.has_key?('fields')
|
97
|
+
document['fields'].each do |k, v|
|
98
|
+
meta[k] = v if k[0] == '_'
|
99
|
+
end
|
128
100
|
end
|
101
|
+
source = document['_source'] unless options[:action] == 'delete'
|
102
|
+
to_bulk_string(meta, source, options)
|
103
|
+
end
|
104
|
+
|
105
|
+
def bulk_string_from_flex(document, options)
|
106
|
+
flex = document.flex
|
107
|
+
return '' unless document.flex_indexable?
|
108
|
+
meta = { '_index' => flex.index,
|
109
|
+
'_type' => flex.type,
|
110
|
+
'_id' => flex.id }
|
111
|
+
meta['_parent'] = flex.parent if flex.parent
|
112
|
+
meta['_routing'] = flex.routing if flex.routing
|
113
|
+
source = document.flex_source unless options[:action] == 'delete'
|
114
|
+
to_bulk_string(meta, source, options)
|
129
115
|
end
|
130
116
|
|
131
|
-
def
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
117
|
+
def to_bulk_string(meta, source, options)
|
118
|
+
action = options[:action] || 'index'
|
119
|
+
return '' if source.nil? || source.empty? &&! (action == 'delete')
|
120
|
+
meta['_index'] = LiveReindex.prefix_index(meta['_index']) if LiveReindex.should_prefix_index?
|
121
|
+
bulk_string = MultiJson.encode(action => meta) + "\n"
|
122
|
+
unless action == 'delete'
|
123
|
+
source_line = source.is_a?(String) ? source : MultiJson.encode(source)
|
124
|
+
return '' if source.nil? || source.empty?
|
125
|
+
bulk_string << source_line + "\n"
|
136
126
|
end
|
127
|
+
bulk_string
|
137
128
|
end
|
138
129
|
|
139
130
|
end
|