mortar 0.6.2 → 0.7.0
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/lib/mortar/auth.rb +4 -0
- data/lib/mortar/command/base.rb +16 -0
- data/lib/mortar/command/describe.rb +7 -1
- data/lib/mortar/command/help.rb +1 -22
- data/lib/mortar/command/illustrate.rb +8 -3
- data/lib/mortar/command/jobs.rb +41 -14
- data/lib/mortar/command/local.rb +87 -0
- data/lib/mortar/command/projects.rb +12 -11
- data/lib/mortar/command/validate.rb +6 -1
- data/lib/mortar/helpers.rb +12 -0
- data/lib/mortar/local/controller.rb +108 -0
- data/lib/mortar/local/installutil.rb +94 -0
- data/lib/mortar/local/java.rb +48 -0
- data/lib/mortar/local/pig.rb +309 -0
- data/lib/mortar/local/python.rb +184 -0
- data/lib/mortar/project.rb +35 -9
- data/lib/mortar/templates/project/gitignore +3 -1
- data/lib/mortar/templates/report/illustrate-report.html +96 -0
- data/lib/mortar/templates/script/runpig.sh +23 -0
- data/lib/mortar/version.rb +1 -1
- data/spec/mortar/auth_spec.rb +8 -0
- data/spec/mortar/command/describe_spec.rb +14 -1
- data/spec/mortar/command/illustrate_spec.rb +14 -1
- data/spec/mortar/command/jobs_spec.rb +125 -7
- data/spec/mortar/command/local_spec.rb +144 -0
- data/spec/mortar/command/validate_spec.rb +14 -1
- data/spec/mortar/local/controller_spec.rb +102 -0
- data/spec/mortar/local/installutil_spec.rb +70 -0
- data/spec/mortar/local/java_spec.rb +62 -0
- data/spec/mortar/local/pig_spec.rb +157 -0
- data/spec/mortar/project_spec.rb +11 -0
- data/spec/spec_helper.rb +1 -0
- metadata +152 -130
@@ -0,0 +1,94 @@
|
|
1
|
+
#
|
2
|
+
# Copyright 2012 Mortar Data Inc.
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
#
|
16
|
+
|
17
|
+
require 'zlib'
|
18
|
+
require 'excon'
|
19
|
+
require 'rbconfig'
|
20
|
+
require 'rubygems/package'
|
21
|
+
|
22
|
+
require 'mortar/helpers'
|
23
|
+
|
24
|
+
module Mortar
|
25
|
+
module Local
|
26
|
+
module InstallUtil
|
27
|
+
|
28
|
+
include Mortar::Helpers
|
29
|
+
|
30
|
+
def local_install_directory
|
31
|
+
# note: assumes that CWD is the project root, is
|
32
|
+
# this a safe assumption?
|
33
|
+
File.join(Dir.getwd, ".mortar-local")
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
# Drops a marker file for an installed package, used
|
38
|
+
# to help determine if updates should be performed
|
39
|
+
def note_install(subdirectory)
|
40
|
+
install_file = install_file_for(subdirectory)
|
41
|
+
File.open(install_file, "w") do |install_file|
|
42
|
+
# Write out the current epoch so we know when this
|
43
|
+
# dependency was installed
|
44
|
+
install_file.write("#{Time.now.to_i}\n")
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def install_date(subsection)
|
49
|
+
install_file = install_file_for(subsection)
|
50
|
+
if File.exists?(install_file)
|
51
|
+
File.open(install_file, "r") do |f|
|
52
|
+
file_contents = f.read()
|
53
|
+
file_contents.strip.to_i
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def install_file_for(subdirectory)
|
59
|
+
File.join(local_install_directory, subdirectory, "install-date.txt")
|
60
|
+
end
|
61
|
+
|
62
|
+
# Given a path to a foo.tgz or foo.tar.gz file, extracts its
|
63
|
+
# contents to the specified output directory
|
64
|
+
def extract_tgz(tgz_path, dest_dir)
|
65
|
+
FileUtils.mkdir_p(dest_dir)
|
66
|
+
Gem::Package::TarReader.new(Zlib::GzipReader.open(tgz_path)).each do |entry|
|
67
|
+
entry_path = File.join(dest_dir, entry.full_name)
|
68
|
+
if entry.directory?
|
69
|
+
FileUtils.mkdir_p(entry_path)
|
70
|
+
elsif entry.file?
|
71
|
+
File.open(entry_path, "wb") do |entry_file|
|
72
|
+
entry_file.write(entry.read)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Downloads the file at a specified url into the supplied director
|
79
|
+
def download_file(url, dest_dir)
|
80
|
+
dest_file_path = dest_dir + "/" + File.basename(url)
|
81
|
+
File.open(dest_file_path, "wb") do |dest_file|
|
82
|
+
contents = Excon.get(url).body
|
83
|
+
dest_file.write(contents)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def osx?
|
88
|
+
os_platform_name = RbConfig::CONFIG['target_os']
|
89
|
+
return os_platform_name.start_with?('darwin')
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
#
|
2
|
+
# Copyright 2012 Mortar Data Inc.
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
#
|
16
|
+
|
17
|
+
require "mortar/local/installutil"
|
18
|
+
|
19
|
+
class Mortar::Local::Java
|
20
|
+
include Mortar::Local::InstallUtil
|
21
|
+
|
22
|
+
@command = nil
|
23
|
+
|
24
|
+
def check_install
|
25
|
+
jbin = File.join(ENV['JAVA_HOME']||'', "bin", "java")
|
26
|
+
if ENV['JAVA_HOME'] and File.exists?(jbin)
|
27
|
+
@command = jbin
|
28
|
+
return true
|
29
|
+
elsif File.exists?("/usr/libexec/java_home")
|
30
|
+
# OSX has a nice little tool for finding this value, assuming
|
31
|
+
# that it won't give us a bad value
|
32
|
+
java_home = run_java_home
|
33
|
+
if java_home != ""
|
34
|
+
ENV['JAVA_HOME'] = java_home
|
35
|
+
@command = File.join(ENV['JAVA_HOME'], "bin", "java")
|
36
|
+
return true
|
37
|
+
end
|
38
|
+
end
|
39
|
+
return false
|
40
|
+
end
|
41
|
+
|
42
|
+
# Runs the java_home command line tool which on osx will
|
43
|
+
# return the appropriate JAVA_home value
|
44
|
+
def run_java_home
|
45
|
+
`/usr/libexec/java_home`.to_s.strip
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
@@ -0,0 +1,309 @@
|
|
1
|
+
#
|
2
|
+
# Copyright 2012 Mortar Data Inc.
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
#
|
16
|
+
|
17
|
+
require "erb"
|
18
|
+
require 'tempfile'
|
19
|
+
require "mortar/helpers"
|
20
|
+
require "mortar/local/installutil"
|
21
|
+
|
22
|
+
class Mortar::Local::Pig
|
23
|
+
include Mortar::Local::InstallUtil
|
24
|
+
|
25
|
+
PIG_LOG_FORMAT = "humanreadable"
|
26
|
+
PIG_TAR_DEFAULT_URL = "https://s3.amazonaws.com/mortar-public-artifacts/pig.tgz"
|
27
|
+
|
28
|
+
# Tempfile objects have a hook to delete the file when the object is
|
29
|
+
# destroyed by the garbage collector. In practice this means that a
|
30
|
+
# file we want sitting around could disappear out from under us. To
|
31
|
+
# prevent this behavior, we're keeping references to these objects so
|
32
|
+
# that the garbage collector will not destroy them until the program
|
33
|
+
# exits (and our files won't be deleted until we don't care about them
|
34
|
+
# any more).
|
35
|
+
@temp_file_objects
|
36
|
+
|
37
|
+
# We copy some resources to the user's illustrate-output directory
|
38
|
+
# for styling the output. This only happens if they are not already present.
|
39
|
+
@resource_locations
|
40
|
+
@resource_destinations
|
41
|
+
|
42
|
+
attr_accessor :resource_locations
|
43
|
+
attr_accessor :resource_destinations
|
44
|
+
|
45
|
+
def initialize
|
46
|
+
@temp_file_objects = []
|
47
|
+
|
48
|
+
@resource_locations = {
|
49
|
+
"illustrate_template" => File.expand_path("../../templates/report/illustrate-report.html", __FILE__),
|
50
|
+
"illustrate_css" => File.expand_path("../../../../css/illustrate.css", __FILE__),
|
51
|
+
"jquery" => File.expand_path("../../../../js/jquery-1.7.1.min.js", __FILE__),
|
52
|
+
"jquery_transit" => File.expand_path("../../../../js/jquery.transit.js", __FILE__),
|
53
|
+
"jquery_stylestack" => File.expand_path("../../../../js/jquery.stylestack.js", __FILE__),
|
54
|
+
"mortar_table" => File.expand_path("../../../../js/mortar-table.js", __FILE__),
|
55
|
+
"zeroclipboard" => File.expand_path("../../../../js/zero_clipboard.js", __FILE__),
|
56
|
+
"zeroclipboard_swf" => File.expand_path("../../../../flash/zeroclipboard.swf", __FILE__)
|
57
|
+
}
|
58
|
+
|
59
|
+
@resource_destinations = {
|
60
|
+
"illustrate_html" => "illustrate-output/illustrate-output.html",
|
61
|
+
"illustrate_css" => "illustrate-output/resources/css/illustrate-output.css",
|
62
|
+
"jquery" => "illustrate-output/resources/js/jquery-1.7.1.min.js",
|
63
|
+
"jquery_transit" => "illustrate-output/resources/js/jquery.transit.js",
|
64
|
+
"jquery_stylestack" => "illustrate-output/resources/js/jquery.stylestack.js",
|
65
|
+
"mortar_table" => "illustrate-output/resources/js/mortar-table.js",
|
66
|
+
"zeroclipboard" => "illustrate-output/resources/js/zero_clipboard.js",
|
67
|
+
"zeroclipboard_swf" => "illustrate-output/resources/flash/zeroclipboard.swf"
|
68
|
+
}
|
69
|
+
end
|
70
|
+
|
71
|
+
def command
|
72
|
+
return File.join(pig_directory, "bin", "pig")
|
73
|
+
end
|
74
|
+
|
75
|
+
def pig_directory
|
76
|
+
return File.join(local_install_directory, "pig")
|
77
|
+
end
|
78
|
+
|
79
|
+
def pig_archive_url
|
80
|
+
ENV.fetch('PIG_DISTRO_URL', PIG_TAR_DEFAULT_URL)
|
81
|
+
end
|
82
|
+
|
83
|
+
def pig_archive_file
|
84
|
+
File.basename(pig_archive_url)
|
85
|
+
end
|
86
|
+
|
87
|
+
# Determines if a pig install needs to occur, true if no
|
88
|
+
# pig install present or a newer version is available
|
89
|
+
def should_do_pig_install?
|
90
|
+
not (File.exists?(pig_directory))
|
91
|
+
end
|
92
|
+
|
93
|
+
# Installs pig for this project if it is not already present
|
94
|
+
def install
|
95
|
+
if should_do_pig_install?
|
96
|
+
FileUtils.mkdir_p(local_install_directory)
|
97
|
+
action "Installing pig" do
|
98
|
+
download_file(pig_archive_url, local_install_directory)
|
99
|
+
local_tgz = File.join(local_install_directory, pig_archive_file)
|
100
|
+
extract_tgz(local_tgz, local_install_directory)
|
101
|
+
|
102
|
+
# This has been seening coming out of the tgz w/o +x so we do
|
103
|
+
# here to be sure it has the necessary permissions
|
104
|
+
FileUtils.chmod(0755, command)
|
105
|
+
|
106
|
+
File.delete(local_tgz)
|
107
|
+
note_install("pig")
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
# run the pig script with user supplied pig parameters
|
113
|
+
def run_script(pig_script, pig_parameters)
|
114
|
+
run_pig_command(" -f #{pig_script.path}", pig_parameters)
|
115
|
+
end
|
116
|
+
|
117
|
+
# Create a temp file to be used for writing the illustrate
|
118
|
+
# json output, and return it's path. This data file will
|
119
|
+
# later be used to create the result html output. Tempfile
|
120
|
+
# will take care of cleaning up the file when we exit.
|
121
|
+
def create_illustrate_output_path
|
122
|
+
# Using Tempfile for the path generation and so that the
|
123
|
+
# file will be cleaned up on process exit
|
124
|
+
outfile = Tempfile.new("mortar-illustrate-output")
|
125
|
+
outfile.close(false)
|
126
|
+
outfile.path
|
127
|
+
end
|
128
|
+
|
129
|
+
# Given a file path, open it and decode the containing json
|
130
|
+
def decode_illustrate_input_file(illustrate_outpath)
|
131
|
+
data_raw = File.read(illustrate_outpath)
|
132
|
+
begin
|
133
|
+
data_encoded = data_raw.encode('UTF-8', 'binary', :invalid => :replace, :undef => :replace, :replace => '')
|
134
|
+
rescue NoMethodError
|
135
|
+
require 'iconv'
|
136
|
+
ic = Iconv.new('UTF-8//IGNORE', 'UTF-8')
|
137
|
+
data_encoded = ic.iconv(data_raw)
|
138
|
+
end
|
139
|
+
json_decode(data_encoded)
|
140
|
+
end
|
141
|
+
|
142
|
+
def show_illustrate_output(illustrate_outpath)
|
143
|
+
ensure_dir_exists("illustrate-output")
|
144
|
+
ensure_dir_exists("illustrate-output/resources")
|
145
|
+
ensure_dir_exists("illustrate-output/resources/css")
|
146
|
+
ensure_dir_exists("illustrate-output/resources/js")
|
147
|
+
ensure_dir_exists("illustrate-output/resources/flash")
|
148
|
+
|
149
|
+
["illustrate_css",
|
150
|
+
"jquery", "jquery_transit", "jquery_stylestack",
|
151
|
+
"mortar_table", "zeroclipboard", "zeroclipboard_swf"].each { |resource|
|
152
|
+
copy_if_not_present_at_dest(@resource_locations[resource], @resource_destinations[resource])
|
153
|
+
}
|
154
|
+
|
155
|
+
# Pull in the dumped json file
|
156
|
+
illustrate_data = decode_illustrate_input_file(illustrate_outpath)
|
157
|
+
|
158
|
+
# Render a template using it's values
|
159
|
+
template_params = create_illustrate_template_parameters(illustrate_data)
|
160
|
+
|
161
|
+
# template_params = {'tables' => []}
|
162
|
+
erb = ERB.new(File.read(@resource_locations["illustrate_template"]), 0, "%<>")
|
163
|
+
html = erb.result(BindingClazz.new(template_params).get_binding)
|
164
|
+
|
165
|
+
# Write the rendered template out to a file
|
166
|
+
File.open(@resource_destinations["illustrate_html"], 'w') { |f|
|
167
|
+
f.write(html)
|
168
|
+
}
|
169
|
+
|
170
|
+
# Open a browser pointing to the rendered template output file
|
171
|
+
action("Opening illustrate results from #{@resource_destinations["illustrate_html"]} ") do
|
172
|
+
require "launchy"
|
173
|
+
Launchy.open(File.expand_path(@resource_destinations["illustrate_html"]))
|
174
|
+
end
|
175
|
+
|
176
|
+
end
|
177
|
+
|
178
|
+
def create_illustrate_template_parameters(illustrate_data)
|
179
|
+
params = {}
|
180
|
+
params['tables'] = illustrate_data['tables']
|
181
|
+
params['udf_output'] = illustrate_data['udf_output']
|
182
|
+
return params
|
183
|
+
end
|
184
|
+
|
185
|
+
def illustrate_alias(pig_script, pig_alias, skip_pruning, pig_parameters)
|
186
|
+
cmd = "-e 'illustrate "
|
187
|
+
|
188
|
+
# Parameters have to be entered with the illustrate command (as
|
189
|
+
# apposed to as a command line argument) or it will result in an
|
190
|
+
# 'Undefined parameter' error.
|
191
|
+
param_file = make_pig_param_file(pig_parameters)
|
192
|
+
cmd += "-param_file #{param_file} "
|
193
|
+
|
194
|
+
# Now point us at the script/alias to illustrate
|
195
|
+
illustrate_outpath = create_illustrate_output_path()
|
196
|
+
cmd += "-script #{pig_script.path} -out #{illustrate_outpath} "
|
197
|
+
if skip_pruning
|
198
|
+
cmd += " -skipPruning "
|
199
|
+
end
|
200
|
+
cmd += " #{pig_alias} '"
|
201
|
+
|
202
|
+
run_pig_command(cmd, [])
|
203
|
+
show_illustrate_output(illustrate_outpath)
|
204
|
+
end
|
205
|
+
|
206
|
+
# Run pig with the specified command ('command' is anything that
|
207
|
+
# can be appended to the command line invocation of Pig that will
|
208
|
+
# get it to do something interesting, such as '-f some-file.pig'
|
209
|
+
def run_pig_command(cmd, parameters = nil)
|
210
|
+
# Generate the script for running the command, then
|
211
|
+
# write it to a temp script which will be exectued
|
212
|
+
script_text = script_for_command(cmd, parameters)
|
213
|
+
script = Tempfile.new("mortar-")
|
214
|
+
script.write(script_text)
|
215
|
+
script.close(false)
|
216
|
+
FileUtils.chmod(0755, script.path)
|
217
|
+
system(script.path)
|
218
|
+
script.unlink
|
219
|
+
end
|
220
|
+
|
221
|
+
# Generates a bash script which sets up the necessary environment and
|
222
|
+
# then runs the pig command
|
223
|
+
def script_for_command(cmd, parameters)
|
224
|
+
template_params = pig_command_script_template_parameters(cmd, parameters)
|
225
|
+
erb = ERB.new(File.read(pig_command_script_template_path), 0, "%<>")
|
226
|
+
erb.result(BindingClazz.new(template_params).get_binding)
|
227
|
+
end
|
228
|
+
|
229
|
+
# Path to the template which generates the bash script for running pig
|
230
|
+
def pig_command_script_template_path
|
231
|
+
File.expand_path("../../templates/script/runpig.sh", __FILE__)
|
232
|
+
end
|
233
|
+
|
234
|
+
# Parameters necessary for rendering the bash script template
|
235
|
+
def pig_command_script_template_parameters(cmd, pig_parameters)
|
236
|
+
template_params = {}
|
237
|
+
template_params['pig_params_file'] = make_pig_param_file(pig_parameters)
|
238
|
+
template_params['pig_home'] = pig_directory
|
239
|
+
template_params['pig_classpath'] = "#{pig_directory}/lib-pig/*"
|
240
|
+
template_params['classpath'] = "#{pig_directory}/lib/*:#{pig_directory}/conf/jets3t.properties"
|
241
|
+
template_params['project_home'] = File.expand_path("..", local_install_directory)
|
242
|
+
template_params['local_install_dir'] = local_install_directory
|
243
|
+
template_params['pig_sub_command'] = cmd
|
244
|
+
template_params['pig_opts'] = pig_options
|
245
|
+
template_params
|
246
|
+
end
|
247
|
+
|
248
|
+
# Returns a hash of settings that need to be passed
|
249
|
+
# in via pig options
|
250
|
+
def pig_options
|
251
|
+
opts = {}
|
252
|
+
opts['fs.s3n.awsAccessKeyId'] = ENV['AWS_ACCESS_KEY']
|
253
|
+
opts['fs.s3n.awsSecretAccessKey'] = ENV['AWS_SECRET_KEY']
|
254
|
+
opts['pig.events.logformat'] = PIG_LOG_FORMAT
|
255
|
+
return opts
|
256
|
+
end
|
257
|
+
|
258
|
+
# Pig Paramenters that are supplied directly from Mortar when
|
259
|
+
# running on the server side. We duplicate these here.
|
260
|
+
def automatic_pig_parameters
|
261
|
+
params = {}
|
262
|
+
if ENV['MORTAR_EMAIL_S3_ESCAPED']
|
263
|
+
params['MORTAR_EMAIL_S3_ESCAPED'] = ENV['MORTAR_EMAIL_S3_ESCAPED']
|
264
|
+
else
|
265
|
+
params['MORTAR_EMAIL_S3_ESCAPED'] = Mortar::Auth.user_s3_safe
|
266
|
+
end
|
267
|
+
# Coerce into the same format as pig parameters that were
|
268
|
+
# passed in via the command line or a parameter file
|
269
|
+
param_list = []
|
270
|
+
params.each{ |k,v|
|
271
|
+
param_list.push({"name" => k, "value" => v})
|
272
|
+
}
|
273
|
+
return param_list
|
274
|
+
end
|
275
|
+
|
276
|
+
# Given a set of user specified pig parameters, combine with the
|
277
|
+
# automatic mortar parameters and write out to a tempfile, returning
|
278
|
+
# it's path so it may be referenced later in the process
|
279
|
+
def make_pig_param_file(pig_parameters)
|
280
|
+
mortar_pig_params = automatic_pig_parameters
|
281
|
+
all_parameters = mortar_pig_params.concat(pig_parameters)
|
282
|
+
param_file = Tempfile.new("mortar-pig-parameters")
|
283
|
+
all_parameters.each { |p|
|
284
|
+
param_file.write("#{p['name']}=#{p['value']}\n")
|
285
|
+
}
|
286
|
+
param_file.close(false)
|
287
|
+
|
288
|
+
# Keep track a reference the tempfile object so that the
|
289
|
+
# garbage collector does not automatically delete the file
|
290
|
+
# out from under us
|
291
|
+
@temp_file_objects.push(param_file)
|
292
|
+
|
293
|
+
param_file.path
|
294
|
+
end
|
295
|
+
|
296
|
+
# Allows us to use a hash for template variables
|
297
|
+
class BindingClazz
|
298
|
+
def initialize(attrs)
|
299
|
+
attrs.each{ |k, v|
|
300
|
+
# set an instance variable with the key name so the binding will find it in scope
|
301
|
+
self.instance_variable_set("@#{k}".to_sym, v)
|
302
|
+
}
|
303
|
+
end
|
304
|
+
def get_binding()
|
305
|
+
binding
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
end
|