coral_core 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/Gemfile +13 -0
- data/Gemfile.lock +37 -0
- data/LICENSE.txt +674 -0
- data/README.rdoc +33 -0
- data/Rakefile +77 -0
- data/VERSION +1 -0
- data/lib/coral_core/command.rb +236 -0
- data/lib/coral_core/core.rb +200 -0
- data/lib/coral_core/event/regexp_event.rb +55 -0
- data/lib/coral_core/event.rb +168 -0
- data/lib/coral_core/interface.rb +179 -0
- data/lib/coral_core/memory.rb +222 -0
- data/lib/coral_core/repository.rb +156 -0
- data/lib/coral_core/util/data.rb +59 -0
- data/lib/coral_core/util/disk.rb +82 -0
- data/lib/coral_core/util/git/base.rb +58 -0
- data/lib/coral_core/util/git/lib.rb +82 -0
- data/lib/coral_core/util/git/remote.rb +12 -0
- data/lib/coral_core/util/git.rb +15 -0
- data/lib/coral_core/util/shell.rb +178 -0
- data/lib/coral_core.rb +49 -0
- data/spec/coral_core/interface_spec.rb +489 -0
- data/spec/coral_mock_input.rb +29 -0
- data/spec/coral_test_kernel.rb +22 -0
- data/spec/spec_helper.rb +15 -0
- metadata +251 -0
data/README.rdoc
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
= coral_core
|
2
|
+
|
3
|
+
This library provides core data elements and utilities used in other Coral gems.
|
4
|
+
|
5
|
+
The Coral core library contains functionality that is utilized by other
|
6
|
+
Coral gems by providing basic utilities like Git, Shell, Disk, and Data
|
7
|
+
manipulation libraries, a UI system, and a core data model that supports
|
8
|
+
Events, Commands, Repositories, and Memory (version controlled JSON
|
9
|
+
objects). This library is only used as a starting point for other systems.
|
10
|
+
|
11
|
+
Note: This library is still very early in development!
|
12
|
+
|
13
|
+
== Contributing to coral_core
|
14
|
+
|
15
|
+
* Check out the latest master to make sure the feature hasn't been implemented
|
16
|
+
or the bug hasn't been fixed yet.
|
17
|
+
* Check out the issue tracker to make sure someone already hasn't requested
|
18
|
+
it and/or contributed it.
|
19
|
+
* Fork the project.
|
20
|
+
* Start a feature/bugfix branch.
|
21
|
+
* Commit and push until you are happy with your contribution.
|
22
|
+
* Make sure to add tests for it. This is important so I don't break it in a
|
23
|
+
future version unintentionally.
|
24
|
+
* Please try not to mess with the Rakefile, version, or history. If you want
|
25
|
+
to have your own version, or is otherwise necessary, that is fine, but
|
26
|
+
please isolate to its own commit so I can cherry-pick around it.
|
27
|
+
|
28
|
+
== Copyright
|
29
|
+
|
30
|
+
Licensed under GPLv3. See LICENSE.txt for further details.
|
31
|
+
|
32
|
+
Copyright (c) 2013 Adrian Webb <adrian.webb@coraltech.net>
|
33
|
+
Coral Technology Group LLC
|
data/Rakefile
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'rake'
|
5
|
+
require 'bundler'
|
6
|
+
require 'jeweler'
|
7
|
+
require 'rspec/core/rake_task'
|
8
|
+
require 'rdoc/task'
|
9
|
+
require 'yard'
|
10
|
+
|
11
|
+
require './lib/coral_core.rb'
|
12
|
+
|
13
|
+
#-------------------------------------------------------------------------------
|
14
|
+
# Dependencies
|
15
|
+
|
16
|
+
begin
|
17
|
+
Bundler.setup(:default, :development)
|
18
|
+
rescue Bundler::BundlerError => e
|
19
|
+
$stderr.puts e.message
|
20
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
21
|
+
exit e.status_code
|
22
|
+
end
|
23
|
+
|
24
|
+
#-------------------------------------------------------------------------------
|
25
|
+
# Gem specification
|
26
|
+
|
27
|
+
Jeweler::Tasks.new do |gem|
|
28
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
29
|
+
gem.name = "coral_core"
|
30
|
+
gem.homepage = "http://github.com/coraltech/ruby-coral_core"
|
31
|
+
gem.rubyforge_project = 'coral_core'
|
32
|
+
gem.license = "GPLv3"
|
33
|
+
gem.email = "adrian.webb@coraltech.net"
|
34
|
+
gem.authors = ["Adrian Webb"]
|
35
|
+
gem.summary = %Q{Provides core data elements and utilities used in other Coral gems}
|
36
|
+
gem.description = File.read('README.rdoc')
|
37
|
+
gem.required_ruby_version = '>= 1.8.1'
|
38
|
+
gem.has_rdoc = true
|
39
|
+
gem.rdoc_options << '--title' << 'Coral Core library' <<
|
40
|
+
'--main' << 'README.rdoc' <<
|
41
|
+
'--line-numbers'
|
42
|
+
|
43
|
+
# Dependencies defined in Gemfile
|
44
|
+
end
|
45
|
+
|
46
|
+
Jeweler::RubygemsDotOrgTasks.new
|
47
|
+
|
48
|
+
#-------------------------------------------------------------------------------
|
49
|
+
# Testing
|
50
|
+
|
51
|
+
RSpec::Core::RakeTask.new(:spec, :tag) do |spec, task_args|
|
52
|
+
options = []
|
53
|
+
options << "--tag #{task_args[:tag]}" if task_args.is_a?(Array) && ! task_args[:tag].to_s.empty?
|
54
|
+
spec.rspec_opts = options.join(' ')
|
55
|
+
end
|
56
|
+
|
57
|
+
task :default => :spec
|
58
|
+
|
59
|
+
#-------------------------------------------------------------------------------
|
60
|
+
# Documentation
|
61
|
+
|
62
|
+
version = Coral::VERSION
|
63
|
+
doc_title = "coral_core #{version}"
|
64
|
+
|
65
|
+
Rake::RDocTask.new do |rdoc|
|
66
|
+
rdoc.rdoc_dir = 'rdoc'
|
67
|
+
rdoc.title = doc_title
|
68
|
+
rdoc.rdoc_files.include('README*')
|
69
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
70
|
+
end
|
71
|
+
|
72
|
+
#---
|
73
|
+
|
74
|
+
YARD::Rake::YardocTask.new do |ydoc|
|
75
|
+
ydoc.files = [ 'README*', 'lib/**/*.rb' ]
|
76
|
+
ydoc.options = [ "--output-dir yardoc", "--title '#{doc_title}'" ]
|
77
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.2
|
@@ -0,0 +1,236 @@
|
|
1
|
+
|
2
|
+
module Coral
|
3
|
+
class Command < Core
|
4
|
+
|
5
|
+
#-----------------------------------------------------------------------------
|
6
|
+
# Properties
|
7
|
+
|
8
|
+
attr_accessor :name
|
9
|
+
attr_reader :subcommand
|
10
|
+
|
11
|
+
#-----------------------------------------------------------------------------
|
12
|
+
# Constructor / Destructor
|
13
|
+
|
14
|
+
def initialize(options = {})
|
15
|
+
|
16
|
+
if options.is_a?(String) || options.is_a?(Symbol)
|
17
|
+
options = string(options)
|
18
|
+
options = { :name => options, :command => options }
|
19
|
+
end
|
20
|
+
|
21
|
+
super(options)
|
22
|
+
|
23
|
+
@properties = {}
|
24
|
+
|
25
|
+
self.subcommand = options[:subcommand] if options.has_key?(:subcommand)
|
26
|
+
|
27
|
+
@name = ( options.has_key?(:name) ? string(options[:name]) : '' )
|
28
|
+
|
29
|
+
@properties = options
|
30
|
+
@properties[:command] = executable(options)
|
31
|
+
end
|
32
|
+
|
33
|
+
#-----------------------------------------------------------------------------
|
34
|
+
# Property accessors / modifiers
|
35
|
+
|
36
|
+
def command_base
|
37
|
+
return @properties[:command]
|
38
|
+
end
|
39
|
+
|
40
|
+
#---
|
41
|
+
|
42
|
+
def command=command
|
43
|
+
@properties[:command] = executable({ :command => command })
|
44
|
+
end
|
45
|
+
|
46
|
+
#---
|
47
|
+
|
48
|
+
def vagrant=command
|
49
|
+
@properties[:command] = executable({ :vagrant => command })
|
50
|
+
end
|
51
|
+
|
52
|
+
#---
|
53
|
+
|
54
|
+
def coral=command
|
55
|
+
@properties[:command] = executable({ :coral => command })
|
56
|
+
end
|
57
|
+
|
58
|
+
#---
|
59
|
+
|
60
|
+
def args
|
61
|
+
return array(@properties[:args])
|
62
|
+
end
|
63
|
+
|
64
|
+
#---
|
65
|
+
|
66
|
+
def args=args
|
67
|
+
@properties[:args] = array(args)
|
68
|
+
end
|
69
|
+
|
70
|
+
#---
|
71
|
+
|
72
|
+
def flags
|
73
|
+
return array(@properties[:flags])
|
74
|
+
end
|
75
|
+
|
76
|
+
#---
|
77
|
+
|
78
|
+
def flags=flags
|
79
|
+
@properties[:flags] = array(flags)
|
80
|
+
end
|
81
|
+
|
82
|
+
#---
|
83
|
+
|
84
|
+
def data
|
85
|
+
return hash(@properties[:data])
|
86
|
+
end
|
87
|
+
|
88
|
+
#---
|
89
|
+
|
90
|
+
def data=data
|
91
|
+
@properties[:data] = hash(data)
|
92
|
+
end
|
93
|
+
|
94
|
+
#---
|
95
|
+
|
96
|
+
def subcommand=subcommand
|
97
|
+
@properties[:subcommand] = hash(subcommand)
|
98
|
+
@subcommand = Module.const_get("Coral").const_get(self.class).new(@properties[:subcommand])
|
99
|
+
end
|
100
|
+
|
101
|
+
#-----------------------------------------------------------------------------
|
102
|
+
# Import / Export
|
103
|
+
|
104
|
+
def export
|
105
|
+
return symbol_map(@properties)
|
106
|
+
end
|
107
|
+
|
108
|
+
#-----------------------------------------------------------------------------
|
109
|
+
# Command functions
|
110
|
+
|
111
|
+
def build(components = {}, overrides = nil, override_key = false)
|
112
|
+
|
113
|
+
command = string(components[:command])
|
114
|
+
flags = array( components.has_key?(:flags) ? components[:flags] : [] )
|
115
|
+
data = string_map(hash( components.has_key?(:data) ? components[:data] : {} ))
|
116
|
+
args = array( components.has_key?(:args) ? components[:args] : [] )
|
117
|
+
subcommand = hash( components.has_key?(:subcommand) ? components[:subcommand] : {} )
|
118
|
+
|
119
|
+
override_key = command unless override_key
|
120
|
+
override_key = override_key.to_sym
|
121
|
+
|
122
|
+
command_string = command.dup
|
123
|
+
subcommand_string = ''
|
124
|
+
|
125
|
+
escape_characters = /[\'\"]+/
|
126
|
+
escape_replacement = '\"'
|
127
|
+
|
128
|
+
dash_pattern = /^([\-]+)/
|
129
|
+
assignment_pattern = /\=$/
|
130
|
+
|
131
|
+
# Flags
|
132
|
+
if overrides && overrides.has_key?(:flags)
|
133
|
+
if overrides[:flags].is_a?(Hash)
|
134
|
+
if overrides[:flags].has_key?(override_key)
|
135
|
+
flags = array(overrides[:flags][override_key])
|
136
|
+
end
|
137
|
+
else
|
138
|
+
flags = array(overrides[:flags])
|
139
|
+
end
|
140
|
+
end
|
141
|
+
flags.each do |flag|
|
142
|
+
if flag && ! flag.empty?
|
143
|
+
flag = string(flag)
|
144
|
+
|
145
|
+
if flag.match(dash_pattern)
|
146
|
+
dashes = $1
|
147
|
+
else
|
148
|
+
dashes = ( flag.size == 1 ? '-' : '--' )
|
149
|
+
end
|
150
|
+
command_string << " #{dashes}#{flag}"
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
# Data
|
155
|
+
if overrides && overrides.has_key?(:data)
|
156
|
+
if overrides[:data].has_key?(override_key)
|
157
|
+
data = hash(overrides[:data][override_key])
|
158
|
+
else
|
159
|
+
override = true
|
160
|
+
overrides[:data].each do |key, value|
|
161
|
+
if ! value.is_a?(String)
|
162
|
+
override = false
|
163
|
+
end
|
164
|
+
end
|
165
|
+
data = hash(overrides[:data]) if override
|
166
|
+
end
|
167
|
+
end
|
168
|
+
data.each do |key, value|
|
169
|
+
key = string(key)
|
170
|
+
value = string(value).strip.sub(escape_characters, escape_replacement)
|
171
|
+
|
172
|
+
if key.match(dash_pattern)
|
173
|
+
dashes = $1
|
174
|
+
else
|
175
|
+
dashes = ( key.size == 1 ? '-' : '--' )
|
176
|
+
end
|
177
|
+
space = ( key.match(assignment_pattern) ? '' : ' ' )
|
178
|
+
|
179
|
+
command_string << " #{dashes}#{key}#{space}'#{value}'"
|
180
|
+
end
|
181
|
+
|
182
|
+
# Arguments
|
183
|
+
if overrides && overrides.has_key?(:args)
|
184
|
+
if overrides[:args].is_a?(Hash)
|
185
|
+
if overrides[:args].has_key?(override_key)
|
186
|
+
args = array(overrides[:args][override_key])
|
187
|
+
end
|
188
|
+
else
|
189
|
+
args = array(overrides[:args])
|
190
|
+
end
|
191
|
+
end
|
192
|
+
args.each do |arg|
|
193
|
+
arg = string(arg).sub(escape_characters, escape_replacement)
|
194
|
+
command_string << " '#{arg}'"
|
195
|
+
end
|
196
|
+
|
197
|
+
# Subcommand
|
198
|
+
subcommand_overrides = ( overrides ? overrides[:subcommand] : nil )
|
199
|
+
if subcommand && subcommand.is_a?(Hash) && ! subcommand.empty?
|
200
|
+
subcommand_string = build(subcommand, subcommand_overrides)
|
201
|
+
end
|
202
|
+
|
203
|
+
return (command_string + ' ' + subcommand_string).strip
|
204
|
+
end
|
205
|
+
|
206
|
+
#-----------------------------------------------------------------------------
|
207
|
+
|
208
|
+
def exec!(options = {}, overrides = nil)
|
209
|
+
success = Coral::Util::Shell.exec!(build(export, overrides), options) do |line|
|
210
|
+
block_given? ? yield(line) : true
|
211
|
+
end
|
212
|
+
return success
|
213
|
+
end
|
214
|
+
|
215
|
+
#---
|
216
|
+
|
217
|
+
def exec(options = {}, overrides = nil)
|
218
|
+
return exec!(options, overrides)
|
219
|
+
end
|
220
|
+
|
221
|
+
#-----------------------------------------------------------------------------
|
222
|
+
# Utilities
|
223
|
+
|
224
|
+
def executable(options)
|
225
|
+
if options.has_key?(:coral)
|
226
|
+
return 'vagrant coral ' + options[:coral]
|
227
|
+
|
228
|
+
elsif options.has_key?(:vagrant)
|
229
|
+
return 'vagrant ' + options[:vagrant]
|
230
|
+
|
231
|
+
elsif options.has_key?(:command)
|
232
|
+
return options[:command]
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
@@ -0,0 +1,200 @@
|
|
1
|
+
|
2
|
+
module Coral
|
3
|
+
class Core
|
4
|
+
|
5
|
+
#-----------------------------------------------------------------------------
|
6
|
+
# Constructor / Destructor
|
7
|
+
|
8
|
+
def initialize(options = {})
|
9
|
+
@ui = Coral::Interface.new(options)
|
10
|
+
end
|
11
|
+
|
12
|
+
#-----------------------------------------------------------------------------
|
13
|
+
# Accessor / Modifiers
|
14
|
+
|
15
|
+
attr_accessor :ui
|
16
|
+
|
17
|
+
#-----------------------------------------------------------------------------
|
18
|
+
|
19
|
+
def self.logger
|
20
|
+
return @ui.class.logger
|
21
|
+
end
|
22
|
+
|
23
|
+
#---
|
24
|
+
|
25
|
+
def logger
|
26
|
+
return @ui.logger
|
27
|
+
end
|
28
|
+
|
29
|
+
#---
|
30
|
+
|
31
|
+
def logger=logger
|
32
|
+
@ui.logger = logger
|
33
|
+
end
|
34
|
+
|
35
|
+
#-----------------------------------------------------------------------------
|
36
|
+
# General utilities
|
37
|
+
|
38
|
+
def self.symbol_map(data)
|
39
|
+
results = {}
|
40
|
+
return data unless data
|
41
|
+
|
42
|
+
case data
|
43
|
+
when Hash
|
44
|
+
data.each do |key, value|
|
45
|
+
results[key.to_sym] = symbol_map(value)
|
46
|
+
end
|
47
|
+
else
|
48
|
+
results = data
|
49
|
+
end
|
50
|
+
return results
|
51
|
+
end
|
52
|
+
|
53
|
+
#---
|
54
|
+
|
55
|
+
def symbol_map(data)
|
56
|
+
return self.class.symbol_map(data)
|
57
|
+
end
|
58
|
+
|
59
|
+
#---
|
60
|
+
|
61
|
+
def self.string_map(data)
|
62
|
+
results = {}
|
63
|
+
return data unless data
|
64
|
+
|
65
|
+
case data
|
66
|
+
when Hash
|
67
|
+
data.each do |key, value|
|
68
|
+
results[key.to_s] = string_map(value)
|
69
|
+
end
|
70
|
+
else
|
71
|
+
results = data
|
72
|
+
end
|
73
|
+
return results
|
74
|
+
end
|
75
|
+
|
76
|
+
#---
|
77
|
+
|
78
|
+
def string_map(data)
|
79
|
+
return self.class.string_map(data)
|
80
|
+
end
|
81
|
+
|
82
|
+
#-----------------------------------------------------------------------------
|
83
|
+
|
84
|
+
def self.filter(data, method = false)
|
85
|
+
if method && method.is_a?(Symbol) &&
|
86
|
+
[ :array, :hash, :string, :symbol, :test ].include?(method.to_sym)
|
87
|
+
return send(method, data)
|
88
|
+
end
|
89
|
+
return data
|
90
|
+
end
|
91
|
+
|
92
|
+
#---
|
93
|
+
|
94
|
+
def filter(data, method = false)
|
95
|
+
return self.class.filter(data, method)
|
96
|
+
end
|
97
|
+
|
98
|
+
#-----------------------------------------------------------------------------
|
99
|
+
|
100
|
+
def self.array(data, default = [], split_string = false)
|
101
|
+
result = default
|
102
|
+
if data && ! data.empty?
|
103
|
+
case data
|
104
|
+
when Array
|
105
|
+
result = data
|
106
|
+
when String
|
107
|
+
result = [ ( split_string ? data.split(/\s*,\s*/) : data ) ]
|
108
|
+
else
|
109
|
+
result = [ data ]
|
110
|
+
end
|
111
|
+
end
|
112
|
+
return result
|
113
|
+
end
|
114
|
+
|
115
|
+
#---
|
116
|
+
|
117
|
+
def array(data, default = [], split_string = false)
|
118
|
+
return self.class.array(data, default, split_string)
|
119
|
+
end
|
120
|
+
|
121
|
+
#---
|
122
|
+
|
123
|
+
def self.hash(data, default = {})
|
124
|
+
result = default
|
125
|
+
if data && ! data.empty?
|
126
|
+
case data
|
127
|
+
when Hash
|
128
|
+
result = data
|
129
|
+
else
|
130
|
+
result = {}
|
131
|
+
end
|
132
|
+
end
|
133
|
+
return result
|
134
|
+
end
|
135
|
+
|
136
|
+
#---
|
137
|
+
|
138
|
+
def hash(data, default = {})
|
139
|
+
return self.class.hash(data, default)
|
140
|
+
end
|
141
|
+
|
142
|
+
#---
|
143
|
+
|
144
|
+
def self.string(data, default = '')
|
145
|
+
result = default
|
146
|
+
if data && ! data.empty?
|
147
|
+
case data
|
148
|
+
when String
|
149
|
+
result = data
|
150
|
+
else
|
151
|
+
result = data.to_s
|
152
|
+
end
|
153
|
+
end
|
154
|
+
return result
|
155
|
+
end
|
156
|
+
|
157
|
+
#---
|
158
|
+
|
159
|
+
def string(data, default = '')
|
160
|
+
return self.class.string(data, default)
|
161
|
+
end
|
162
|
+
|
163
|
+
#---
|
164
|
+
|
165
|
+
def self.symbol(data, default = :undefined)
|
166
|
+
result = default
|
167
|
+
if data && ! data.empty?
|
168
|
+
case data
|
169
|
+
when Symbol
|
170
|
+
result = data
|
171
|
+
when String
|
172
|
+
result = data.to_sym
|
173
|
+
else
|
174
|
+
result = data.class.to_sym
|
175
|
+
end
|
176
|
+
end
|
177
|
+
return result
|
178
|
+
end
|
179
|
+
|
180
|
+
#---
|
181
|
+
|
182
|
+
def symbol(data, default = '')
|
183
|
+
return self.class.symbol(data, default)
|
184
|
+
end
|
185
|
+
|
186
|
+
#---
|
187
|
+
|
188
|
+
def self.test(data)
|
189
|
+
return false if ! data || data.empty?
|
190
|
+
return false if data.is_a?(String) && data =~ /^(FALSE|false|False|No|no|N|n)$/
|
191
|
+
return true
|
192
|
+
end
|
193
|
+
|
194
|
+
#---
|
195
|
+
|
196
|
+
def test(data)
|
197
|
+
return self.class.test(data)
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
|
2
|
+
module Coral
|
3
|
+
class RegexpEvent < Event
|
4
|
+
|
5
|
+
#-----------------------------------------------------------------------------
|
6
|
+
# Properties
|
7
|
+
|
8
|
+
TYPE = :regexp
|
9
|
+
|
10
|
+
#-----------------------------------------------------------------------------
|
11
|
+
# Constructor / Destructor
|
12
|
+
|
13
|
+
def initialize(options = {})
|
14
|
+
options[:type] = TYPE
|
15
|
+
|
16
|
+
super(options)
|
17
|
+
|
18
|
+
if options.has_key?(:string)
|
19
|
+
self.pattern = options[:string]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
#-----------------------------------------------------------------------------
|
24
|
+
# Property accessors / modifiers
|
25
|
+
|
26
|
+
def pattern
|
27
|
+
return property(:pattern, '', :string)
|
28
|
+
end
|
29
|
+
|
30
|
+
#---
|
31
|
+
|
32
|
+
def pattern=pattern
|
33
|
+
set_property(:pattern, string(pattern))
|
34
|
+
end
|
35
|
+
|
36
|
+
#-----------------------------------------------------------------------------
|
37
|
+
# Import / Export
|
38
|
+
|
39
|
+
def export
|
40
|
+
return "#{type}:#{pattern}"
|
41
|
+
end
|
42
|
+
|
43
|
+
#-----------------------------------------------------------------------------
|
44
|
+
# Event handling
|
45
|
+
|
46
|
+
def check(source)
|
47
|
+
if source.match(/#{pattern}/)
|
48
|
+
logger.debug("MATCH! -> #{pattern} matched #{source}")
|
49
|
+
return true
|
50
|
+
end
|
51
|
+
logger.debug("nothing -> #{pattern} - #{source}")
|
52
|
+
return false
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|