coral_core 0.1.2
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/.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
|