flex 0.4.2 → 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|