tap 0.7.9 → 0.8.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/History +28 -0
- data/MIT-LICENSE +1 -1
- data/README +71 -43
- data/Rakefile +81 -64
- data/Tutorial +235 -0
- data/bin/tap +80 -44
- data/lib/tap.rb +41 -12
- data/lib/tap/app.rb +243 -246
- data/lib/tap/file_task.rb +357 -118
- data/lib/tap/generator.rb +88 -29
- data/lib/tap/generator/generators/config/config_generator.rb +4 -2
- data/lib/tap/generator/generators/config/templates/config.erb +1 -2
- data/lib/tap/generator/generators/file_task/file_task_generator.rb +3 -18
- data/lib/tap/generator/generators/file_task/templates/task.erb +22 -15
- data/lib/tap/generator/generators/file_task/templates/test.erb +13 -2
- data/{test/test/inference_methods/test_assert_files_exist/input/input_1.txt → lib/tap/generator/generators/generator/USAGE} +0 -0
- data/lib/tap/generator/generators/generator/generator_generator.rb +21 -0
- data/lib/tap/generator/generators/generator/templates/generator.erb +23 -0
- data/lib/tap/generator/generators/generator/templates/usage.erb +1 -0
- data/{test/test/inference_methods/test_assert_files_exist/input/input_2.txt → lib/tap/generator/generators/package/USAGE} +0 -0
- data/lib/tap/generator/generators/package/package_generator.rb +38 -0
- data/lib/tap/generator/generators/package/templates/package.erb +186 -0
- data/lib/tap/generator/generators/root/root_generator.rb +14 -9
- data/lib/tap/generator/generators/root/templates/Rakefile +20 -14
- data/{test/test/inference_methods/test_infer_glob/expected/file.yml → lib/tap/generator/generators/root/templates/ReadMe.txt} +0 -0
- data/lib/tap/generator/generators/root/templates/tap.yml +82 -0
- data/lib/tap/generator/generators/root/templates/test/tap_test_helper.rb +0 -1
- data/lib/tap/generator/generators/root/templates/test/tap_test_suite.rb +2 -1
- data/{test/test/inference_methods/test_infer_glob/expected/file_1.txt → lib/tap/generator/generators/script/USAGE} +0 -0
- data/lib/tap/generator/generators/script/script_generator.rb +17 -0
- data/lib/tap/generator/generators/script/templates/script.erb +42 -0
- data/lib/tap/generator/generators/task/task_generator.rb +1 -1
- data/lib/tap/generator/generators/task/templates/task.erb +24 -16
- data/lib/tap/generator/generators/task/templates/test.erb +13 -17
- data/lib/tap/generator/generators/workflow/templates/task.erb +10 -10
- data/lib/tap/generator/generators/workflow/templates/test.erb +1 -1
- data/lib/tap/generator/generators/workflow/workflow_generator.rb +3 -18
- data/lib/tap/root.rb +108 -146
- data/lib/tap/script.rb +362 -0
- data/lib/tap/script/console.rb +28 -0
- data/lib/tap/script/destroy.rb +13 -1
- data/lib/tap/script/generate.rb +13 -1
- data/lib/tap/script/run.rb +100 -57
- data/lib/tap/support/batch_queue.rb +0 -3
- data/lib/tap/support/logger.rb +6 -3
- data/lib/tap/support/rake.rb +54 -0
- data/lib/tap/support/task_configuration.rb +169 -0
- data/lib/tap/support/tdoc.rb +198 -0
- data/lib/tap/support/tdoc/config_attr.rb +338 -0
- data/lib/tap/support/tdoc/tdoc_html_generator.rb +38 -0
- data/lib/tap/support/tdoc/tdoc_html_template.rb +42 -0
- data/lib/tap/support/versions.rb +33 -1
- data/lib/tap/task.rb +339 -227
- data/lib/tap/test.rb +86 -128
- data/lib/tap/test/env_vars.rb +16 -5
- data/lib/tap/test/file_methods.rb +373 -0
- data/lib/tap/test/subset_methods.rb +299 -180
- data/lib/tap/version.rb +2 -1
- data/lib/tap/workflow.rb +2 -0
- data/test/app/lib/app_test_task.rb +1 -0
- data/test/app_test.rb +327 -83
- data/test/check/binding_eval.rb +23 -0
- data/test/check/define_method_check.rb +22 -0
- data/test/check/dependencies_check.rb +175 -0
- data/test/check/inheritance_check.rb +22 -0
- data/test/file_task_test.rb +524 -291
- data/test/{test/inference_methods/test_infer_glob/expected/file_2.txt → root/glob/one.txt} +0 -0
- data/test/root/glob/two.txt +0 -0
- data/test/root_test.rb +330 -262
- data/test/script_test.rb +194 -0
- data/test/support/audit_test.rb +5 -2
- data/test/support/combinator_test.rb +10 -10
- data/test/support/rake_test.rb +35 -0
- data/test/support/task_configuration_test.rb +272 -0
- data/test/support/tdoc_test.rb +363 -0
- data/test/support/templater_test.rb +2 -2
- data/test/support/versions_test.rb +32 -0
- data/test/tap_test_helper.rb +39 -0
- data/test/task_base_test.rb +115 -0
- data/test/task_class_test.rb +56 -4
- data/test/task_execute_test.rb +29 -0
- data/test/task_test.rb +89 -70
- data/test/test/env_vars_test.rb +48 -0
- data/test/test/{inference_methods → file_methods}/test_assert_expected/expected/file.txt +0 -0
- data/test/test/{inference_methods → file_methods}/test_assert_expected/expected/folder/file.txt +0 -0
- data/test/test/{inference_methods → file_methods}/test_assert_expected/input/file.txt +0 -0
- data/test/test/{inference_methods → file_methods}/test_assert_expected/input/folder/file.txt +0 -0
- data/test/test/file_methods/test_assert_files_exist/input/input_1.txt +0 -0
- data/test/test/file_methods/test_assert_files_exist/input/input_2.txt +0 -0
- data/test/test/file_methods/test_assert_output_files_equal/expected/one.txt +1 -0
- data/test/test/file_methods/test_assert_output_files_equal/expected/two.txt +1 -0
- data/test/test/file_methods/test_assert_output_files_equal/input/one.txt +1 -0
- data/test/test/file_methods/test_assert_output_files_equal/input/two.txt +1 -0
- data/test/test/{inference_methods → file_methods}/test_file_compare/expected/output_1.txt +0 -0
- data/test/test/{inference_methods → file_methods}/test_file_compare/expected/output_2.txt +0 -0
- data/test/test/{inference_methods → file_methods}/test_file_compare/input/input_1.txt +0 -0
- data/test/test/{inference_methods → file_methods}/test_file_compare/input/input_2.txt +0 -0
- data/test/test/file_methods/test_infer_glob/expected/file.yml +0 -0
- data/test/test/file_methods/test_infer_glob/expected/file_1.txt +0 -0
- data/test/test/file_methods/test_infer_glob/expected/file_2.txt +0 -0
- data/test/test/file_methods/test_method_glob/expected/file.yml +0 -0
- data/test/test/file_methods/test_method_glob/expected/file_1.txt +0 -0
- data/test/test/file_methods/test_method_glob/expected/file_2.txt +0 -0
- data/test/test/{inference_methods → file_methods}/test_yml_compare/expected/output_1.yml +0 -0
- data/test/test/{inference_methods → file_methods}/test_yml_compare/expected/output_2.yml +0 -0
- data/test/test/{inference_methods → file_methods}/test_yml_compare/input/input_1.yml +0 -0
- data/test/test/{inference_methods → file_methods}/test_yml_compare/input/input_2.yml +0 -0
- data/test/test/file_methods_test.rb +204 -0
- data/test/test/subset_methods_test.rb +93 -33
- data/test/test/test_assert_expected_result_files/expected/task/name/a.txt +1 -0
- data/test/test/test_assert_expected_result_files/expected/task/name/b.txt +1 -0
- data/test/test/test_assert_expected_result_files/input/a.txt +1 -0
- data/test/test/test_assert_expected_result_files/input/b.txt +1 -0
- data/test/test/test_file_task_test/expected/one.txt +1 -0
- data/test/test/test_file_task_test/expected/two.txt +1 -0
- data/test/test/test_file_task_test/input/one.txt +1 -0
- data/test/test/test_file_task_test/input/two.txt +1 -0
- data/test/test_test.rb +143 -3
- data/test/workflow_test.rb +2 -0
- data/vendor/rails_generator.rb +56 -0
- data/vendor/rails_generator/base.rb +263 -0
- data/vendor/rails_generator/commands.rb +581 -0
- data/vendor/rails_generator/generated_attribute.rb +42 -0
- data/vendor/rails_generator/lookup.rb +209 -0
- data/vendor/rails_generator/manifest.rb +53 -0
- data/vendor/rails_generator/options.rb +143 -0
- data/vendor/rails_generator/scripts.rb +83 -0
- data/vendor/rails_generator/scripts/destroy.rb +7 -0
- data/vendor/rails_generator/scripts/generate.rb +7 -0
- data/vendor/rails_generator/scripts/update.rb +12 -0
- data/vendor/rails_generator/simple_logger.rb +46 -0
- data/vendor/rails_generator/spec.rb +44 -0
- metadata +180 -196
- data/lib/tap/generator/generators/root/templates/app.yml +0 -19
- data/lib/tap/generator/generators/root/templates/config/process_tap_request.yml +0 -4
- data/lib/tap/generator/generators/root/templates/lib/process_tap_request.rb +0 -26
- data/lib/tap/generator/generators/root/templates/public/images/nav.jpg +0 -0
- data/lib/tap/generator/generators/root/templates/public/stylesheets/color.css +0 -57
- data/lib/tap/generator/generators/root/templates/public/stylesheets/layout.css +0 -108
- data/lib/tap/generator/generators/root/templates/public/stylesheets/normalize.css +0 -40
- data/lib/tap/generator/generators/root/templates/public/stylesheets/typography.css +0 -21
- data/lib/tap/generator/generators/root/templates/server/config/environment.rb +0 -60
- data/lib/tap/generator/generators/root/templates/server/lib/tasks/clear_database_prerequisites.rake +0 -5
- data/lib/tap/generator/generators/root/templates/server/test/test_helper.rb +0 -53
- data/lib/tap/script/server.rb +0 -12
- data/lib/tap/support/rap.rb +0 -38
- data/lib/tap/test/inference_methods.rb +0 -298
- data/test/task/config/task_with_config.yml +0 -1
- data/test/test/inference_methods_test.rb +0 -311
|
@@ -12,23 +12,28 @@ module Tap::Generator::Generators
|
|
|
12
12
|
record do |m|
|
|
13
13
|
# directories
|
|
14
14
|
m.directory "lib"
|
|
15
|
-
m.directory "config"
|
|
15
|
+
#m.directory "config"
|
|
16
16
|
m.directory "test"
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
m.directory "
|
|
21
|
-
m.directory "server/
|
|
22
|
-
m.directory "server/
|
|
23
|
-
m.directory "server/lib/tasks"
|
|
17
|
+
|
|
18
|
+
# remove these -- they will be created from
|
|
19
|
+
# a server generator in the future
|
|
20
|
+
#m.directory "server/config"
|
|
21
|
+
#m.directory "server/test"
|
|
22
|
+
#m.directory "server/lib/tasks"
|
|
24
23
|
|
|
25
24
|
# files
|
|
26
25
|
template_dir = File.dirname(__FILE__) + "/templates"
|
|
27
26
|
Dir.glob(template_dir + "/**/*").each do |fname|
|
|
28
27
|
next if File.directory?(fname)
|
|
28
|
+
|
|
29
|
+
# skip server files for now... later
|
|
30
|
+
# the files will simply be removed
|
|
31
|
+
next if fname =~ /server/
|
|
32
|
+
next if fname =~ /config/
|
|
33
|
+
next if fname =~ /process_tap_request/
|
|
29
34
|
|
|
30
35
|
fname = Tap::Root.relative_filepath(template_dir, fname)
|
|
31
|
-
m.
|
|
36
|
+
m.template fname, fname
|
|
32
37
|
end
|
|
33
38
|
end
|
|
34
39
|
end
|
|
@@ -9,17 +9,23 @@ task :default => :test
|
|
|
9
9
|
|
|
10
10
|
desc 'Run tests.'
|
|
11
11
|
Rake::TestTask.new(:test) do |t|
|
|
12
|
-
t.
|
|
13
|
-
t.
|
|
14
|
-
t.
|
|
12
|
+
t.pattern = 'test/**/*_test.rb'
|
|
13
|
+
t.verbose = true
|
|
14
|
+
t.warning = true
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
desc 'Generate documentation.'
|
|
18
|
-
Rake::RDocTask.new(:rdoc) do |rdoc|
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
|
19
|
+
# options to specify TDoc
|
|
20
|
+
require 'tap/support/tdoc'
|
|
21
|
+
rdoc.template = 'tap/support/tdoc/tdoc_html_template'
|
|
22
|
+
rdoc.options << '--fmt' << 'tdoc'
|
|
23
|
+
|
|
24
|
+
rdoc.rdoc_dir = 'rdoc'
|
|
25
|
+
rdoc.title = '<%= file_name %>'
|
|
26
|
+
rdoc.template = 'tap/support/tdoc/tdoc_html_template'
|
|
21
27
|
rdoc.options << '--line-numbers' << '--inline-source'
|
|
22
|
-
rdoc.rdoc_files.include('
|
|
28
|
+
rdoc.rdoc_files.include('ReadMe.txt')
|
|
23
29
|
rdoc.rdoc_files.include('lib/**/*.rb')
|
|
24
30
|
end
|
|
25
31
|
|
|
@@ -27,20 +33,20 @@ end
|
|
|
27
33
|
# Gem specification
|
|
28
34
|
#
|
|
29
35
|
spec = Gem::Specification.new do |s|
|
|
30
|
-
|
|
36
|
+
s.name = "<%= file_name %>"
|
|
31
37
|
s.version = "0.0.1"
|
|
32
|
-
|
|
38
|
+
s.author = "Your Name Here"
|
|
33
39
|
#s.email = "your.email@pubfactory.edu"
|
|
34
40
|
#s.homepage = "http://rubyforge.org/projects/gemname/"
|
|
35
41
|
s.platform = Gem::Platform::RUBY
|
|
36
|
-
|
|
37
|
-
s.files = Dir.glob("{test,lib
|
|
42
|
+
s.summary = "Add Description"
|
|
43
|
+
s.files = Dir.glob("{test,lib}/**/*") + ["Rakefile", "ReadMe.txt"]
|
|
38
44
|
s.require_path = "lib"
|
|
39
|
-
s.test_file = "test/tap_test_suite.rb"
|
|
45
|
+
s.test_file = "test/tap_test_suite.rb"
|
|
40
46
|
|
|
41
47
|
s.has_rdoc = true
|
|
42
|
-
s.extra_rdoc_files = ["
|
|
43
|
-
s.add_dependency("tap", ">=
|
|
48
|
+
s.extra_rdoc_files = ["ReadMe.txt"]
|
|
49
|
+
s.add_dependency("tap", ">= <%= Tap::VERSION %>")
|
|
44
50
|
end
|
|
45
51
|
|
|
46
52
|
Rake::GemPackageTask.new(spec) do |pkg|
|
|
File without changes
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
######################################################
|
|
2
|
+
# App Configurations:
|
|
3
|
+
#
|
|
4
|
+
# These are configurations that directly affect the
|
|
5
|
+
# application, including the directory structure
|
|
6
|
+
# options, and logging.
|
|
7
|
+
#
|
|
8
|
+
######################################################
|
|
9
|
+
|
|
10
|
+
# You can set an alternate root, but this is
|
|
11
|
+
# an odd and perhaps unwise thing to do
|
|
12
|
+
#root:
|
|
13
|
+
|
|
14
|
+
# Uncomment to specify aliases for directories relative to root.
|
|
15
|
+
# Access using: app['lib'] # => root/alt_lib
|
|
16
|
+
# By default: app['lib'] # => root/lib
|
|
17
|
+
#directories:
|
|
18
|
+
#lib: alt_lib
|
|
19
|
+
#...
|
|
20
|
+
|
|
21
|
+
# Uncomment to specify aliases for absolute paths
|
|
22
|
+
# Access using: app['log'] # => /path/to/logs
|
|
23
|
+
#absolute_paths:
|
|
24
|
+
#log: /path/to/logs
|
|
25
|
+
#...
|
|
26
|
+
|
|
27
|
+
# Uncomment to specify options.
|
|
28
|
+
# Access using: app.options.debug # => true
|
|
29
|
+
#options:
|
|
30
|
+
#debug: true # full error output, increased logging
|
|
31
|
+
#quiet: true # supresses logging
|
|
32
|
+
#... # you can add your own options
|
|
33
|
+
|
|
34
|
+
# Specify logging options here.
|
|
35
|
+
#logger:
|
|
36
|
+
#device: # STDOUT by default
|
|
37
|
+
#level: 1 # [DEBUG, INFO, WARN, ERROR, FATAL, ANY]
|
|
38
|
+
#datetime_format: %H:%M:%S # Hour:Minute:Second
|
|
39
|
+
|
|
40
|
+
# Map task names to task classes here. The example causes
|
|
41
|
+
# app.task('some/task_name') to intialize a Tap::FileTask
|
|
42
|
+
# with that name. Any number of mappings can be specified.
|
|
43
|
+
#map:
|
|
44
|
+
# some/task_name: Tap::FileTask
|
|
45
|
+
# CapsAreOk: Tap::Task
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
######################################################
|
|
49
|
+
# Script Configurations:
|
|
50
|
+
#
|
|
51
|
+
# These are configurations that affect the execution
|
|
52
|
+
# environment for Tap and are NOT passed to the
|
|
53
|
+
# application itself.
|
|
54
|
+
#
|
|
55
|
+
######################################################
|
|
56
|
+
|
|
57
|
+
# Designate load paths here for automatic loading and
|
|
58
|
+
# reloading of tasks through the active_support
|
|
59
|
+
# Dependencies module. The default is 'lib'. Paths
|
|
60
|
+
# are resolved to the aliases setup above,
|
|
61
|
+
# 'lib' => app['lib']
|
|
62
|
+
#load_paths:
|
|
63
|
+
# - 'lib'
|
|
64
|
+
|
|
65
|
+
# Designate script paths for discovering and executing
|
|
66
|
+
# commands. The default is 'script'. Paths are resolved
|
|
67
|
+
# to the aliases setup above, 'script' => app['script']
|
|
68
|
+
#script_paths:
|
|
69
|
+
# - 'script'
|
|
70
|
+
|
|
71
|
+
# Specify gems that contain load_paths and script_paths
|
|
72
|
+
# that you likewise want to be automatically loaded.
|
|
73
|
+
# Specify versions directly.
|
|
74
|
+
#gems:
|
|
75
|
+
# - gem_name
|
|
76
|
+
# - gem_with_version > 1.0.0
|
|
77
|
+
|
|
78
|
+
# Specify code to execute before and after Tap executes
|
|
79
|
+
#before: |
|
|
80
|
+
# puts "beginning..."
|
|
81
|
+
#after: |
|
|
82
|
+
# puts "done!"
|
|
File without changes
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module Tap::Generator::Generators
|
|
2
|
+
class ScriptGenerator < Rails::Generator::NamedBase # :nodoc:
|
|
3
|
+
def initialize(*args)
|
|
4
|
+
super(*args)
|
|
5
|
+
@destination_root = Tap::App.instance[:root]
|
|
6
|
+
@app = Tap::App.instance
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def manifest
|
|
10
|
+
record do |m|
|
|
11
|
+
script_path = @app.relative_filepath(:root, @app[:script])
|
|
12
|
+
m.directory class_path.empty? ? script_path : File.join(script_path, class_path)
|
|
13
|
+
m.template "script.erb", File.join(script_path, class_name.underscore + ".rb")
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# = Usage
|
|
2
|
+
# tap <%= file_name %> {options} OTHER_ARGS...
|
|
3
|
+
#
|
|
4
|
+
# = Description
|
|
5
|
+
# Some informative description...
|
|
6
|
+
#
|
|
7
|
+
# = Information
|
|
8
|
+
#
|
|
9
|
+
# Developer:: <your name>
|
|
10
|
+
# Homepage:: <your homepage>
|
|
11
|
+
# Copyright (c):: <%= Time.now.strftime("%Y") %>, <copyright holders>
|
|
12
|
+
# License:: MIT-LICENSE (http://www.opensource.org/licenses/mit-license.php) <or some other license...>
|
|
13
|
+
#
|
|
14
|
+
require 'tap/script'
|
|
15
|
+
|
|
16
|
+
app = Tap::App.instance
|
|
17
|
+
|
|
18
|
+
#
|
|
19
|
+
# handle options
|
|
20
|
+
#
|
|
21
|
+
|
|
22
|
+
opts = [
|
|
23
|
+
['--help', '-h', GetoptLong::NO_ARGUMENT, "Print this help."],
|
|
24
|
+
['--debug', nil, GetoptLong::NO_ARGUMENT, "Specifes debug mode."]]
|
|
25
|
+
|
|
26
|
+
Tap::Script.handle_options(*opts) do |opt, value|
|
|
27
|
+
case opt
|
|
28
|
+
when '--help'
|
|
29
|
+
puts Tap::Script.usage(__FILE__, "Usage", "Description", "Information", :keep_headers => false)
|
|
30
|
+
puts
|
|
31
|
+
puts Tap::Script.usage_options(opts)
|
|
32
|
+
exit
|
|
33
|
+
|
|
34
|
+
when '--debug'
|
|
35
|
+
app.options.debug = true
|
|
36
|
+
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
#
|
|
41
|
+
# add your script code here
|
|
42
|
+
#
|
|
@@ -10,7 +10,7 @@ module Tap::Generator::Generators
|
|
|
10
10
|
record do |m|
|
|
11
11
|
lib_path = @app.relative_filepath(:root, @app[:lib])
|
|
12
12
|
m.directory File.join(lib_path, class_path)
|
|
13
|
-
m.template "task.erb", File.join(lib_path, class_name.underscore + ".rb")
|
|
13
|
+
m.template "task.erb", File.join(lib_path, class_name.underscore + ".rb"), :class_nesting => class_nesting
|
|
14
14
|
|
|
15
15
|
test_path = @app.relative_filepath(:root, @app[:test])
|
|
16
16
|
m.directory File.join(test_path, class_path)
|
|
@@ -1,21 +1,29 @@
|
|
|
1
|
-
# ==
|
|
1
|
+
# == Description
|
|
2
|
+
# Replace with a description. The default task simply
|
|
3
|
+
# demonstrates the use of a config and logging.
|
|
4
|
+
# === Usage
|
|
5
|
+
# Replace with usage instructions
|
|
2
6
|
#
|
|
3
|
-
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
class <%= class_name_without_nesting %> < Tap::Task
|
|
8
|
+
# uncomment to:
|
|
9
|
+
# - pass all inputs to process at once
|
|
10
|
+
# - define a task that takes no inputs
|
|
11
|
+
|
|
12
|
+
#do_not_iterate
|
|
13
|
+
|
|
14
|
+
# use config to set task configurations
|
|
15
|
+
# configs have accessors by default
|
|
16
|
+
|
|
17
|
+
config :label, 'input' # a label for output
|
|
18
|
+
|
|
8
19
|
def process(input)
|
|
9
|
-
#
|
|
10
|
-
|
|
11
|
-
#
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
#
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
# The return of process is the task output
|
|
18
|
-
message
|
|
20
|
+
# the process logic goes here.
|
|
21
|
+
|
|
22
|
+
# use log to record information
|
|
23
|
+
log label, input
|
|
24
|
+
|
|
25
|
+
# the return of process is the task output
|
|
26
|
+
"#{label}: #{input} was processed"
|
|
19
27
|
end
|
|
20
28
|
|
|
21
29
|
end
|
|
@@ -1,28 +1,24 @@
|
|
|
1
1
|
require File.join(File.dirname(__FILE__), '<%= '../' * class_nesting_depth %>tap_test_helper.rb')
|
|
2
2
|
require '<%= class_path.empty? ? file_name : File.join(class_path, file_name) %>'
|
|
3
3
|
|
|
4
|
-
class <%=
|
|
4
|
+
class <%= class_name_without_nesting %>Test < Test::Unit::TestCase
|
|
5
5
|
acts_as_tap_test
|
|
6
6
|
|
|
7
|
-
def
|
|
8
|
-
t = <%= class_name %>.new nil, :
|
|
7
|
+
def test_<%= file_name %>
|
|
8
|
+
t = <%= class_name %>.new nil, :label => 'key'
|
|
9
9
|
|
|
10
|
+
# specify application options
|
|
10
11
|
with_options(:quiet => true) do
|
|
11
|
-
t.execute("from me", "to you")
|
|
12
|
-
|
|
13
|
-
assert_equal({:greeting => 'hello'}, t.config)
|
|
14
|
-
assert_outputs(t => ["hello from me!", "hello to you!"])
|
|
15
12
|
|
|
16
|
-
#
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
end
|
|
13
|
+
# run the task with some inputs
|
|
14
|
+
app.run(t, "one", "two")
|
|
15
|
+
|
|
16
|
+
# check the configuration and outputs
|
|
17
|
+
assert_equal({:label => 'key'}, t.config)
|
|
18
|
+
assert_outputs(t => [
|
|
19
|
+
"key: one was processed",
|
|
20
|
+
"key: two was processed"])
|
|
21
|
+
|
|
26
22
|
end
|
|
27
23
|
end
|
|
28
24
|
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
# ==
|
|
1
|
+
# == Description
|
|
2
2
|
#
|
|
3
|
-
# ===
|
|
3
|
+
# === Usage
|
|
4
4
|
# Replace with your command line usage instructions
|
|
5
5
|
#
|
|
6
|
-
class <%=
|
|
7
|
-
|
|
6
|
+
class <%= class_name_without_nesting %> < Tap::Workflow
|
|
7
|
+
protected
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
def workflow
|
|
10
|
+
# Define the workflow entry and exit points,
|
|
11
|
+
# as well as the workflow logic.
|
|
12
|
+
self.entry_point = Task.new
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
# app.sequence(entry_point, 'another/task')
|
|
15
|
+
end
|
|
16
16
|
end
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
require File.join(File.dirname(__FILE__), '<%= '../' * class_nesting_depth %>tap_test_helper.rb')
|
|
2
2
|
require '<%= class_path.empty? ? file_name : File.join(class_path, file_name) %>'
|
|
3
3
|
|
|
4
|
-
class <%=
|
|
4
|
+
class <%= class_name_without_nesting %>Test < Test::Unit::TestCase
|
|
5
5
|
acts_as_tap_test
|
|
6
6
|
|
|
7
7
|
end
|
|
@@ -1,21 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
class WorkflowGenerator < Rails::Generator::NamedBase # :nodoc:
|
|
3
|
-
def initialize(*args)
|
|
4
|
-
super(*args)
|
|
5
|
-
@destination_root = Tap::App.instance[:root]
|
|
6
|
-
@app = Tap::App.instance
|
|
7
|
-
end
|
|
1
|
+
require 'tap/generator/generators/task/task_generator'
|
|
8
2
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
lib_path = @app.relative_filepath(:root, @app[:lib])
|
|
12
|
-
m.directory File.join(lib_path, class_path)
|
|
13
|
-
m.template "task.erb", File.join(lib_path, class_name.underscore + ".rb")
|
|
14
|
-
|
|
15
|
-
test_path = @app.relative_filepath(:root, @app[:test])
|
|
16
|
-
m.directory File.join(test_path, class_path)
|
|
17
|
-
m.template "test.erb", File.join(test_path, class_name.underscore + "_test.rb")
|
|
18
|
-
end
|
|
19
|
-
end
|
|
3
|
+
module Tap::Generator::Generators
|
|
4
|
+
class WorkflowGenerator < TaskGenerator # :nodoc:
|
|
20
5
|
end
|
|
21
6
|
end
|
data/lib/tap/root.rb
CHANGED
|
@@ -1,60 +1,62 @@
|
|
|
1
|
-
require 'fileutils'
|
|
2
1
|
require 'tap/support/versions'
|
|
3
2
|
|
|
4
|
-
# string.rb is required for camelizing and underscoring strings
|
|
5
|
-
# requiring just this file rather than the full gem results in
|
|
6
|
-
# noticably faster startup
|
|
7
|
-
require 'active_support/core_ext/string'
|
|
8
|
-
|
|
9
3
|
module Tap
|
|
10
4
|
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
# files and filepaths within these directories. For example:
|
|
5
|
+
# Root allows you to define a root directory and alias subdirectories, so that
|
|
6
|
+
# you can conceptualize what filepaths you need without predefining the full
|
|
7
|
+
# filepaths. Root also simplifies operations on filepaths.
|
|
15
8
|
#
|
|
16
|
-
#
|
|
9
|
+
# # define a root directory with aliased subdirectories
|
|
10
|
+
# r = Root.new '/root_dir', :input => 'in', :output => 'out'
|
|
17
11
|
#
|
|
18
12
|
# # work with directories
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
13
|
+
# r[:in] # => '/root_dir/in'
|
|
14
|
+
# r[:out] # => '/root_dir/out'
|
|
15
|
+
# r[:implicit] # => '/root_dir/implicit'
|
|
22
16
|
#
|
|
23
17
|
# # work with filepaths
|
|
24
|
-
# fp =
|
|
25
|
-
#
|
|
26
|
-
#
|
|
18
|
+
# fp = r.filepath(:in, 'path/to/file.txt') # => '/root_dir/in/path/to/file.txt'
|
|
19
|
+
# r.relative_filepath(:in, fp) # => 'path/to/file.txt'
|
|
20
|
+
# r.translate(fp, :in, :out) # => '/root_dir/out/path/to/file.txt'
|
|
27
21
|
#
|
|
28
22
|
# # version filepaths
|
|
29
|
-
#
|
|
30
|
-
#
|
|
31
|
-
#
|
|
32
|
-
#
|
|
33
|
-
# #
|
|
34
|
-
#
|
|
35
|
-
#
|
|
36
|
-
# # loading them...
|
|
37
|
-
# Object.const_defined?(:Some) # => false
|
|
38
|
-
# root.lookup_module('Some::Module') # => Some::Module
|
|
39
|
-
# Some.const_defined?(:Module) # => true
|
|
23
|
+
# r.version('path/to/config.yml', 1.0) # => 'path/to/config-1.0.yml'
|
|
24
|
+
# r.increment('path/to/config-1.0.yml', 0.1) # => 'path/to/config-1.1.yml'
|
|
25
|
+
# r.deversion('path/to/config-1.1.yml') # => ['path/to/config.yml', "1.1"]
|
|
26
|
+
#
|
|
27
|
+
# # absolute paths can also be aliased
|
|
28
|
+
# r[:abs, true] = "/absolute/path"
|
|
29
|
+
# r.filepath(:abs, "to", "file.txt") # => '/absolute/path/to/file.txt'
|
|
40
30
|
#
|
|
41
|
-
#
|
|
42
|
-
#
|
|
43
|
-
#
|
|
31
|
+
# By default, Roots are initialized to the present working directory (Dir.pwd).
|
|
32
|
+
# As in the ':implicit' example, whenever Root needs to resolve an alias that is
|
|
33
|
+
# not explicitly set, Root infers a path relative to the root directory.
|
|
44
34
|
#
|
|
45
|
-
# === Notes
|
|
35
|
+
# === Implementation Notes
|
|
36
|
+
#
|
|
37
|
+
# Internally Root stores expanded paths for both subdirectories and absolute paths in a
|
|
38
|
+
# hash, 'paths', for quick lookup using the da (directory alias) keys. Expanding paths
|
|
39
|
+
# ensures they remain constant even when the present working directory (Dir.pwd) changes.
|
|
40
|
+
#
|
|
41
|
+
# Root keeps a separate hash mapping aliases to the subdirectory paths, 'directories'.
|
|
42
|
+
# These relative filepaths allow reassignment of the expanded paths if and when the
|
|
43
|
+
# root directory changes. By contrast, there is no separate data structure storing the
|
|
44
|
+
# absolute paths. An absolute path can be distinguished from a subdirectory path by
|
|
45
|
+
# aliases present in 'paths' but not 'directories'.
|
|
46
|
+
#
|
|
47
|
+
# These features may be important to note when subclassing Root:
|
|
48
|
+
# - root and all filepaths in 'paths' are expanded
|
|
49
|
+
# - directory aliases and subdirectory paths are stored in 'directories'
|
|
50
|
+
# - absolute paths are present in 'paths' but not in 'directories'
|
|
46
51
|
#
|
|
47
|
-
# Root internally stores paths for both relative directories and absolute paths in a
|
|
48
|
-
# hash for quick lookup using the dir keys. Root keeps a separate hash for the
|
|
49
|
-
# original directories used to create the paths, but this is not necessary for
|
|
50
|
-
# absolute paths. An absolute path can be distinguished from a directory by having
|
|
51
|
-
# an entry in paths but not directories.
|
|
52
52
|
class Root
|
|
53
53
|
class << self
|
|
54
54
|
include Support::Versions
|
|
55
55
|
|
|
56
|
-
# Returns the filepath of path relative to dir.
|
|
57
|
-
#
|
|
56
|
+
# Returns the filepath of path relative to dir. Both dir and path are
|
|
57
|
+
# expanded before the relative filepath is determined. If strict=true
|
|
58
|
+
# (the default), then relative_filepath raises an error if the path is
|
|
59
|
+
# not relative to dir. Otherwise, the expanded path will be returned.
|
|
58
60
|
#
|
|
59
61
|
# Root.relative_filepath('dir', "dir/path/to/file.txt") # => "path/to/file.txt"
|
|
60
62
|
#
|
|
@@ -64,7 +66,7 @@ module Tap
|
|
|
64
66
|
|
|
65
67
|
unless expanded[0...dir.length] == dir
|
|
66
68
|
raise "\n#{expanded}\nis not relative to:\n#{dir}" if strict
|
|
67
|
-
return
|
|
69
|
+
return expanded
|
|
68
70
|
end
|
|
69
71
|
|
|
70
72
|
# use dir.length + 1 to remove a leading '/'. If dir.length + 1 >= expanded.length
|
|
@@ -80,8 +82,8 @@ module Tap
|
|
|
80
82
|
end.flatten.uniq
|
|
81
83
|
end
|
|
82
84
|
|
|
83
|
-
# Lists all unique versions of path matching the glob version patterns.
|
|
84
|
-
# are specified, then all versions of path will be returned.
|
|
85
|
+
# Lists all unique versions of path matching the glob version patterns.
|
|
86
|
+
# If no patterns are specified, then all versions of path will be returned.
|
|
85
87
|
def vglob(path, *vpatterns)
|
|
86
88
|
vpatterns << "*" if vpatterns.empty?
|
|
87
89
|
vpatterns.collect do |vpattern|
|
|
@@ -92,30 +94,6 @@ module Tap
|
|
|
92
94
|
results
|
|
93
95
|
end.flatten.uniq
|
|
94
96
|
end
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
#def package(target, *filepaths)
|
|
99
|
-
# options = filepaths.pop if filepaths.last.kind_of?(Hash)
|
|
100
|
-
#end
|
|
101
|
-
|
|
102
|
-
# glob for dirs, remove if empty. removes dir if empty on complete
|
|
103
|
-
# OR
|
|
104
|
-
# remove files older than a certain date
|
|
105
|
-
# remove all files
|
|
106
|
-
#
|
|
107
|
-
#def cleanup(dir)
|
|
108
|
-
#end
|
|
109
|
-
|
|
110
|
-
# iotask?
|
|
111
|
-
def parse_by_line(path, options={}, &block)
|
|
112
|
-
str = read(path, options.merge(:as => :txt))
|
|
113
|
-
lines = str.split(/\r?\n/)
|
|
114
|
-
lines.each do |line|
|
|
115
|
-
# don't yield empty lines because they split to [] (ie [nil, nil] for the block inputs)
|
|
116
|
-
yield(line.split(/#/, 2)) unless line.empty?
|
|
117
|
-
end
|
|
118
|
-
end
|
|
119
97
|
end
|
|
120
98
|
|
|
121
99
|
include Support::Versions
|
|
@@ -123,7 +101,7 @@ module Tap
|
|
|
123
101
|
|
|
124
102
|
# Creates a new Root with the given root, directories, and absolute
|
|
125
103
|
# paths. By default root is the current working directory and no
|
|
126
|
-
# directories or absolute paths are specified.
|
|
104
|
+
# directories or absolute paths are specified.
|
|
127
105
|
def initialize(root=Dir.pwd, directories={}, absolute_paths={})
|
|
128
106
|
assign_paths(root, directories, absolute_paths)
|
|
129
107
|
end
|
|
@@ -133,44 +111,46 @@ module Tap
|
|
|
133
111
|
assign_paths(path, directories, absolute_paths)
|
|
134
112
|
end
|
|
135
113
|
|
|
136
|
-
# Sets the directories to those provided. 'root' and :root are reserved
|
|
137
|
-
# keys and cannot be set using this method (use root= instead).
|
|
114
|
+
# Sets the directories to those provided. 'root' and :root are reserved
|
|
115
|
+
# directory keys and cannot be set using this method (use root= instead).
|
|
138
116
|
def directories=(dirs)
|
|
139
117
|
assign_paths(root, dirs, absolute_paths)
|
|
140
118
|
end
|
|
141
119
|
|
|
142
|
-
# Sets the absolute paths to those provided. 'root' and :root are reserved
|
|
143
|
-
# keys and cannot be set using this method (use root= instead).
|
|
120
|
+
# Sets the absolute paths to those provided. 'root' and :root are reserved
|
|
121
|
+
# directory keys and cannot be set using this method (use root= instead).
|
|
144
122
|
def absolute_paths=(paths)
|
|
145
123
|
assign_paths(root, directories, paths)
|
|
146
124
|
end
|
|
147
125
|
|
|
148
|
-
# Returns the absolute paths registered with
|
|
126
|
+
# Returns the absolute paths registered with self.
|
|
149
127
|
def absolute_paths
|
|
150
128
|
abs_paths = {}
|
|
151
|
-
paths.each do |
|
|
152
|
-
abs_paths[
|
|
129
|
+
paths.each do |da, path|
|
|
130
|
+
abs_paths[da] = path unless directories.include?(da) || da.to_s == 'root'
|
|
153
131
|
end
|
|
154
132
|
abs_paths
|
|
155
133
|
end
|
|
156
134
|
|
|
157
|
-
# Sets
|
|
135
|
+
# Sets a directory alias (da) for the path relative to the root directory.
|
|
158
136
|
# The root directory cannot be set with this method (use root= instead).
|
|
159
|
-
# Absolute filepaths can be set
|
|
137
|
+
# Absolute filepaths can be set using the second syntax.
|
|
160
138
|
#
|
|
161
|
-
#
|
|
162
|
-
#
|
|
163
|
-
#
|
|
139
|
+
# r = Root.new '/root_dir'
|
|
140
|
+
# r[:dir] = 'path/to/dir'
|
|
141
|
+
# r[:dir] # => '/root_dir/path/to/dir'
|
|
164
142
|
#
|
|
165
|
-
#
|
|
166
|
-
#
|
|
143
|
+
# r[:abs, true] = '/abs/path/to/dir'
|
|
144
|
+
# r[:abs] # => '/abs/path/to/dir'
|
|
167
145
|
#
|
|
168
|
-
|
|
146
|
+
#--
|
|
147
|
+
# The syntax for setting an absolute filepath requires an odd use []=.
|
|
169
148
|
# In fact the method recieves the arguments (:dir, true, '/abs/path/to/dir')
|
|
170
149
|
# rather than (:dir, '/abs/path/to/dir', true), meaning that internally path
|
|
171
150
|
# and absolute are switched when setting an absolute filepath.
|
|
172
|
-
|
|
173
|
-
|
|
151
|
+
#++
|
|
152
|
+
def []=(da, path, absolute=false)
|
|
153
|
+
raise ArgumentError, "The directory key '#{da}' is reserved." if da.to_s == 'root'
|
|
174
154
|
|
|
175
155
|
# switch the paths if absolute was provided
|
|
176
156
|
unless absolute == false
|
|
@@ -181,94 +161,76 @@ module Tap
|
|
|
181
161
|
|
|
182
162
|
case
|
|
183
163
|
when path.nil?
|
|
184
|
-
@directories.delete(
|
|
185
|
-
@paths.delete(
|
|
164
|
+
@directories.delete(da)
|
|
165
|
+
@paths.delete(da)
|
|
186
166
|
when absolute
|
|
187
|
-
@directories.delete(
|
|
188
|
-
@paths[
|
|
167
|
+
@directories.delete(da)
|
|
168
|
+
@paths[da] = File.expand_path(path)
|
|
189
169
|
else
|
|
190
|
-
@directories[
|
|
191
|
-
@paths[
|
|
170
|
+
@directories[da] = path
|
|
171
|
+
@paths[da] = File.expand_path(File.join(root, path))
|
|
192
172
|
end
|
|
193
173
|
end
|
|
194
174
|
|
|
195
|
-
# Returns the path
|
|
196
|
-
# then the path is inferred to be 'root/
|
|
197
|
-
|
|
198
|
-
|
|
175
|
+
# Returns the expanded path for the specified alias. If the alias
|
|
176
|
+
# has not been set, then the path is inferred to be 'root/da'.
|
|
177
|
+
#
|
|
178
|
+
# r = Root.new '/root_dir'
|
|
179
|
+
# r[:dir] = 'path/to/dir'
|
|
180
|
+
# r[:dir] # => '/root_dir/path/to/dir'
|
|
181
|
+
#
|
|
182
|
+
# r[:unset] = nil
|
|
183
|
+
# r[:unset] # => '/root_dir/unset'
|
|
184
|
+
#
|
|
185
|
+
def [](da)
|
|
186
|
+
self.paths[da] || File.expand_path(File.join(root, da.to_s))
|
|
199
187
|
end
|
|
200
188
|
|
|
201
|
-
# Constructs filepaths relative to the path of the
|
|
202
|
-
def filepath(
|
|
203
|
-
File.join(self[
|
|
189
|
+
# Constructs expanded filepaths relative to the path of the specified alias.
|
|
190
|
+
def filepath(da, *filename)
|
|
191
|
+
File.expand_path(File.join(self[da], *filename))
|
|
204
192
|
end
|
|
205
193
|
|
|
206
|
-
# Retrieves filepath relative to the path of the specified
|
|
207
|
-
def relative_filepath(
|
|
208
|
-
Root.relative_filepath(self[
|
|
194
|
+
# Retrieves the filepath relative to the path of the specified alias.
|
|
195
|
+
def relative_filepath(da, filepath, strict=true)
|
|
196
|
+
Root.relative_filepath(self[da], filepath, strict)
|
|
209
197
|
end
|
|
210
198
|
|
|
211
|
-
# Generates a target filepath translated from the input
|
|
199
|
+
# Generates a target filepath translated from the aliased input dir to
|
|
200
|
+
# the aliased output dir. Raises an error if the filepath is not relative
|
|
201
|
+
# to the aliased input dir.
|
|
212
202
|
#
|
|
213
|
-
#
|
|
214
|
-
|
|
215
|
-
|
|
203
|
+
# fp = r.filepath(:in, 'path/to/file.txt') # => '/root_dir/in/path/to/file.txt'
|
|
204
|
+
# r.translate(fp, :in, :out) # => '/root_dir/out/path/to/file.txt'
|
|
205
|
+
def translate(filepath, input_da, output_da)
|
|
206
|
+
filepath(output_da, relative_filepath(input_da, filepath))
|
|
216
207
|
end
|
|
217
208
|
|
|
218
|
-
# Lists all files in the
|
|
219
|
-
# for +Dir.glob+. If no patterns are specified, lists
|
|
220
|
-
|
|
209
|
+
# Lists all files in the aliased dir matching the input patterns. Patterns
|
|
210
|
+
# should be valid inputs for +Dir.glob+. If no patterns are specified, lists
|
|
211
|
+
# all files/folders matching '**/*'.
|
|
212
|
+
def glob(da, *patterns)
|
|
221
213
|
patterns << "**/*" if patterns.empty?
|
|
222
|
-
patterns.collect! {|pattern| filepath(
|
|
214
|
+
patterns.collect! {|pattern| filepath(da, pattern)}
|
|
223
215
|
Root.glob(*patterns)
|
|
224
216
|
end
|
|
225
217
|
|
|
226
|
-
# Lists all versions of filename in the
|
|
218
|
+
# Lists all versions of filename in the aliased dir matching the version patterns.
|
|
227
219
|
# If no patterns are specified, then all versions of filename will be returned.
|
|
228
|
-
def vglob(
|
|
229
|
-
Root.vglob(filepath(
|
|
230
|
-
end
|
|
231
|
-
|
|
232
|
-
# Opens the file specfied by dir and filename, then passes the open file to the input block
|
|
233
|
-
def open(dir, filename, mode_string="r", &block)
|
|
234
|
-
File.open(filepath(dir, filename), mode_string, &block)
|
|
235
|
-
end
|
|
236
|
-
|
|
237
|
-
# Reads the specififed file
|
|
238
|
-
def read(dir, filename)
|
|
239
|
-
File.read( filepath(dir, filename) )
|
|
240
|
-
end
|
|
241
|
-
|
|
242
|
-
#
|
|
243
|
-
def parse_by_line(dir, filename, options={}, &block)
|
|
244
|
-
Root.parse_by_line( filepath(dir, filename), options, &block)
|
|
245
|
-
end
|
|
246
|
-
|
|
247
|
-
# The timestamp of the most recently modified file under dir. Returns nil
|
|
248
|
-
# if none of the listed files are relative to dir.
|
|
249
|
-
def mtime(dir, *filenames)
|
|
250
|
-
filenames.collect do |fname|
|
|
251
|
-
fname = relative_filepath(dir, fname, false)
|
|
252
|
-
path = filepath(dir, fname)
|
|
253
|
-
|
|
254
|
-
File.exists?(path) ? File.mtime(path) : nil
|
|
255
|
-
end.compact.max
|
|
256
|
-
end
|
|
257
|
-
|
|
258
|
-
# Makes all the directories specified in paths. Parent directories are created as needed.
|
|
259
|
-
def mkpaths
|
|
260
|
-
paths.values.each { |dir| FileUtils.mkdir_p(dir) }
|
|
220
|
+
def vglob(da, filename, *vpatterns)
|
|
221
|
+
Root.vglob(filepath(da, filename), *vpatterns)
|
|
261
222
|
end
|
|
262
223
|
|
|
263
224
|
private
|
|
264
225
|
|
|
226
|
+
# reassigns all paths with the input root, directories, and absolute_paths
|
|
265
227
|
def assign_paths(root, directories, absolute_paths)
|
|
266
|
-
@root = root
|
|
228
|
+
@root = File.expand_path(root)
|
|
267
229
|
@directories = {}
|
|
268
|
-
@paths = {'root' => root, :root => root}
|
|
230
|
+
@paths = {'root' => @root, :root => @root}
|
|
269
231
|
|
|
270
|
-
directories.each_pair {|
|
|
271
|
-
absolute_paths.each_pair {|
|
|
232
|
+
directories.each_pair {|da, path| self[da] = path }
|
|
233
|
+
absolute_paths.each_pair {|da, path| self[da, true] = path }
|
|
272
234
|
end
|
|
273
235
|
|
|
274
236
|
end
|