tap 0.9.1 → 0.10.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.
Files changed (244) hide show
  1. data/History +37 -30
  2. data/MIT-LICENSE +1 -1
  3. data/README +92 -44
  4. data/bin/tap +62 -75
  5. data/cmd/console.rb +42 -0
  6. data/cmd/destroy.rb +16 -0
  7. data/cmd/generate.rb +16 -0
  8. data/cmd/run.rb +126 -0
  9. data/doc/Class Reference +362 -0
  10. data/doc/Command Reference +153 -0
  11. data/doc/Tutorial +237 -0
  12. data/lib/tap.rb +6 -45
  13. data/lib/tap/app.rb +126 -500
  14. data/lib/tap/constants.rb +2 -29
  15. data/lib/tap/env.rb +555 -250
  16. data/lib/tap/file_task.rb +60 -103
  17. data/lib/tap/generator/base.rb +109 -0
  18. data/lib/tap/generator/destroy.rb +37 -0
  19. data/lib/tap/generator/generate.rb +61 -0
  20. data/lib/tap/generator/generators/command/command_generator.rb +16 -12
  21. data/lib/tap/generator/generators/command/templates/command.erb +13 -19
  22. data/lib/tap/generator/generators/config/config_generator.rb +18 -27
  23. data/lib/tap/generator/generators/config/templates/doc.erb +12 -0
  24. data/lib/tap/generator/generators/config/templates/nodoc.erb +8 -0
  25. data/lib/tap/generator/generators/file_task/file_task_generator.rb +16 -11
  26. data/lib/tap/generator/generators/file_task/templates/file.txt +11 -2
  27. data/lib/tap/generator/generators/file_task/templates/result.yml +6 -0
  28. data/lib/tap/generator/generators/file_task/templates/task.erb +24 -31
  29. data/lib/tap/generator/generators/file_task/templates/test.erb +18 -22
  30. data/lib/tap/generator/generators/root/root_generator.rb +45 -31
  31. data/lib/tap/generator/generators/root/templates/Rakefile +64 -41
  32. data/lib/tap/generator/generators/root/templates/gemspec +27 -0
  33. data/lib/tap/generator/generators/root/templates/tapfile +8 -0
  34. data/lib/tap/generator/generators/root/templates/test/tap_test_helper.rb +0 -0
  35. data/lib/tap/generator/generators/root/templates/test/tap_test_suite.rb +1 -1
  36. data/lib/tap/generator/generators/root/templates/test/tapfile_test.rb +15 -0
  37. data/lib/tap/generator/generators/task/task_generator.rb +21 -28
  38. data/lib/tap/generator/generators/task/templates/task.erb +13 -23
  39. data/lib/tap/generator/generators/task/templates/test.erb +15 -18
  40. data/lib/tap/generator/manifest.rb +14 -0
  41. data/lib/tap/patches/rake/rake_test_loader.rb +0 -0
  42. data/lib/tap/patches/rake/testtask.rb +0 -0
  43. data/lib/tap/patches/ruby19/backtrace_filter.rb +0 -0
  44. data/lib/tap/patches/ruby19/parsedate.rb +0 -0
  45. data/lib/tap/root.rb +260 -21
  46. data/lib/tap/support/aggregator.rb +11 -11
  47. data/lib/tap/support/assignments.rb +172 -0
  48. data/lib/tap/support/audit.rb +20 -18
  49. data/lib/tap/support/batchable.rb +21 -10
  50. data/lib/tap/support/batchable_class.rb +107 -0
  51. data/lib/tap/support/class_configuration.rb +154 -239
  52. data/lib/tap/support/command_line.rb +97 -102
  53. data/lib/tap/support/comment.rb +270 -0
  54. data/lib/tap/support/configurable.rb +86 -65
  55. data/lib/tap/support/configurable_class.rb +296 -0
  56. data/lib/tap/support/configuration.rb +122 -0
  57. data/lib/tap/support/constant.rb +70 -0
  58. data/lib/tap/support/constant_utils.rb +127 -0
  59. data/lib/tap/support/declarations.rb +111 -0
  60. data/lib/tap/support/executable.rb +30 -17
  61. data/lib/tap/support/executable_queue.rb +0 -0
  62. data/lib/tap/support/framework.rb +71 -0
  63. data/lib/tap/support/framework_class.rb +199 -0
  64. data/lib/tap/support/instance_configuration.rb +147 -0
  65. data/lib/tap/support/lazydoc.rb +428 -0
  66. data/lib/tap/support/manifest.rb +89 -0
  67. data/lib/tap/support/run_error.rb +0 -0
  68. data/lib/tap/support/shell_utils.rb +33 -9
  69. data/lib/tap/support/summary.rb +30 -0
  70. data/lib/tap/support/tdoc.rb +339 -134
  71. data/lib/tap/support/tdoc/tdoc_html_generator.rb +0 -0
  72. data/lib/tap/support/tdoc/tdoc_html_template.rb +0 -0
  73. data/lib/tap/support/templater.rb +180 -0
  74. data/lib/tap/support/validation.rb +409 -76
  75. data/lib/tap/support/versions.rb +5 -3
  76. data/lib/tap/task.rb +78 -174
  77. data/lib/tap/tasks/dump.rb +56 -0
  78. data/lib/tap/tasks/rake.rb +93 -0
  79. data/lib/tap/test.rb +3 -3
  80. data/lib/tap/test/env_vars.rb +2 -2
  81. data/lib/tap/test/file_methods.rb +19 -20
  82. data/lib/tap/test/script_methods.rb +144 -0
  83. data/lib/tap/test/subset_methods.rb +1 -1
  84. data/lib/tap/test/tap_methods.rb +28 -62
  85. data/lib/tap/workflow.rb +22 -39
  86. metadata +48 -179
  87. data/Basic Overview +0 -151
  88. data/Command Reference +0 -99
  89. data/Rakefile +0 -127
  90. data/Tutorial +0 -287
  91. data/lib/tap/cmd/console.rb +0 -31
  92. data/lib/tap/cmd/destroy.rb +0 -20
  93. data/lib/tap/cmd/generate.rb +0 -20
  94. data/lib/tap/cmd/run.rb +0 -151
  95. data/lib/tap/dump.rb +0 -57
  96. data/lib/tap/generator.rb +0 -91
  97. data/lib/tap/generator/generators/command/USAGE +0 -6
  98. data/lib/tap/generator/generators/config/USAGE +0 -21
  99. data/lib/tap/generator/generators/config/templates/config.erb +0 -1
  100. data/lib/tap/generator/generators/file_task/USAGE +0 -3
  101. data/lib/tap/generator/generators/file_task/templates/file.yml +0 -3
  102. data/lib/tap/generator/generators/generator/USAGE +0 -0
  103. data/lib/tap/generator/generators/generator/generator_generator.rb +0 -21
  104. data/lib/tap/generator/generators/generator/templates/generator.erb +0 -32
  105. data/lib/tap/generator/generators/generator/templates/usage.erb +0 -1
  106. data/lib/tap/generator/generators/root/USAGE +0 -0
  107. data/lib/tap/generator/generators/root/templates/ReadMe.txt +0 -0
  108. data/lib/tap/generator/generators/root/templates/tap.yml +0 -80
  109. data/lib/tap/generator/generators/task/USAGE +0 -3
  110. data/lib/tap/generator/generators/workflow/USAGE +0 -0
  111. data/lib/tap/generator/generators/workflow/templates/task.erb +0 -16
  112. data/lib/tap/generator/generators/workflow/templates/test.erb +0 -7
  113. data/lib/tap/generator/generators/workflow/workflow_generator.rb +0 -6
  114. data/lib/tap/generator/options.rb +0 -26
  115. data/lib/tap/generator/usage.rb +0 -26
  116. data/lib/tap/support/batchable_methods.rb +0 -34
  117. data/lib/tap/support/command_line_methods.rb +0 -76
  118. data/lib/tap/support/configurable_methods.rb +0 -224
  119. data/lib/tap/support/logger.rb +0 -88
  120. data/lib/tap/support/rake.rb +0 -43
  121. data/lib/tap/support/tdoc/config_attr.rb +0 -362
  122. data/test/app/config/another/task.yml +0 -1
  123. data/test/app/config/batch.yml +0 -2
  124. data/test/app/config/empty.yml +0 -0
  125. data/test/app/config/erb.yml +0 -2
  126. data/test/app/config/some/task.yml +0 -1
  127. data/test/app/config/template.yml +0 -2
  128. data/test/app/config/version-0.1.yml +0 -1
  129. data/test/app/config/version.yml +0 -1
  130. data/test/app/lib/app_test_task.rb +0 -3
  131. data/test/app_test.rb +0 -1849
  132. data/test/env/test_configure/recurse_a.yml +0 -2
  133. data/test/env/test_configure/recurse_b.yml +0 -2
  134. data/test/env/test_configure/tap.yml +0 -23
  135. data/test/env/test_load_env_config/dir/tap.yml +0 -3
  136. data/test/env/test_load_env_config/recurse_a.yml +0 -2
  137. data/test/env/test_load_env_config/recurse_b.yml +0 -2
  138. data/test/env/test_load_env_config/tap.yml +0 -3
  139. data/test/env_test.rb +0 -198
  140. data/test/file_task/config/batch.yml +0 -2
  141. data/test/file_task/config/configured.yml +0 -1
  142. data/test/file_task/old_file_one.txt +0 -0
  143. data/test/file_task/old_file_two.txt +0 -0
  144. data/test/file_task_test.rb +0 -1291
  145. data/test/root/alt_lib/alt_module.rb +0 -4
  146. data/test/root/file.txt +0 -0
  147. data/test/root/glob/one.txt +0 -0
  148. data/test/root/glob/two.txt +0 -0
  149. data/test/root/lib/absolute_alt_filepath.rb +0 -2
  150. data/test/root/lib/alternative_filepath.rb +0 -2
  151. data/test/root/lib/another_module.rb +0 -2
  152. data/test/root/lib/nested/some_module.rb +0 -4
  153. data/test/root/lib/no_module_included.rb +0 -0
  154. data/test/root/lib/some/module.rb +0 -4
  155. data/test/root/lib/some_class.rb +0 -2
  156. data/test/root/lib/some_module.rb +0 -3
  157. data/test/root/load_path/load_path_module.rb +0 -2
  158. data/test/root/load_path/skip_module.rb +0 -2
  159. data/test/root/mtime/older.txt +0 -0
  160. data/test/root/unload/full_path.rb +0 -2
  161. data/test/root/unload/loaded_by_nested.rb +0 -2
  162. data/test/root/unload/nested/nested_load.rb +0 -6
  163. data/test/root/unload/nested/nested_with_ext.rb +0 -4
  164. data/test/root/unload/nested/relative_path.rb +0 -4
  165. data/test/root/unload/older.rb +0 -2
  166. data/test/root/unload/unload_base.rb +0 -9
  167. data/test/root/versions/another.yml +0 -0
  168. data/test/root/versions/file-0.1.2.yml +0 -0
  169. data/test/root/versions/file-0.1.yml +0 -0
  170. data/test/root/versions/file.yml +0 -0
  171. data/test/root_test.rb +0 -718
  172. data/test/support/aggregator_test.rb +0 -99
  173. data/test/support/audit_test.rb +0 -445
  174. data/test/support/batchable_test.rb +0 -74
  175. data/test/support/class_configuration_test.rb +0 -331
  176. data/test/support/command_line_test.rb +0 -58
  177. data/test/support/configurable/config/configured.yml +0 -2
  178. data/test/support/configurable_test.rb +0 -295
  179. data/test/support/executable_queue_test.rb +0 -103
  180. data/test/support/executable_test.rb +0 -38
  181. data/test/support/logger_test.rb +0 -31
  182. data/test/support/rake_test.rb +0 -37
  183. data/test/support/shell_utils_test.rb +0 -24
  184. data/test/support/tdoc_test.rb +0 -370
  185. data/test/support/validation_test.rb +0 -54
  186. data/test/support/versions_test.rb +0 -103
  187. data/test/tap_test_helper.rb +0 -57
  188. data/test/tap_test_suite.rb +0 -7
  189. data/test/task/config/batch.yml +0 -2
  190. data/test/task/config/batched.yml +0 -2
  191. data/test/task/config/configured.yml +0 -1
  192. data/test/task/config/example.yml +0 -1
  193. data/test/task_base_test.rb +0 -24
  194. data/test/task_syntax_test.rb +0 -300
  195. data/test/task_test.rb +0 -320
  196. data/test/test/env_vars_test.rb +0 -48
  197. data/test/test/file_methods/test_assert_files/expected/one.txt +0 -1
  198. data/test/test/file_methods/test_assert_files/expected/two.txt +0 -1
  199. data/test/test/file_methods/test_assert_files/input/one.txt +0 -1
  200. data/test/test/file_methods/test_assert_files/input/two.txt +0 -1
  201. data/test/test/file_methods/test_assert_files_can_have_no_expected_files_if_specified/input/one.txt +0 -1
  202. data/test/test/file_methods/test_assert_files_can_have_no_expected_files_if_specified/input/two.txt +0 -1
  203. data/test/test/file_methods/test_assert_files_fails_for_different_content/expected/one.txt +0 -1
  204. data/test/test/file_methods/test_assert_files_fails_for_different_content/expected/two.txt +0 -1
  205. data/test/test/file_methods/test_assert_files_fails_for_different_content/input/one.txt +0 -1
  206. data/test/test/file_methods/test_assert_files_fails_for_different_content/input/two.txt +0 -1
  207. data/test/test/file_methods/test_assert_files_fails_for_missing_expected_file/expected/one.txt +0 -1
  208. data/test/test/file_methods/test_assert_files_fails_for_missing_expected_file/input/one.txt +0 -1
  209. data/test/test/file_methods/test_assert_files_fails_for_missing_expected_file/input/two.txt +0 -1
  210. data/test/test/file_methods/test_assert_files_fails_for_missing_output_file/expected/one.txt +0 -1
  211. data/test/test/file_methods/test_assert_files_fails_for_missing_output_file/expected/two.txt +0 -1
  212. data/test/test/file_methods/test_assert_files_fails_for_missing_output_file/input/one.txt +0 -1
  213. data/test/test/file_methods/test_assert_files_fails_for_missing_output_file/input/two.txt +0 -1
  214. data/test/test/file_methods/test_assert_files_fails_for_no_expected_files/input/one.txt +0 -1
  215. data/test/test/file_methods/test_assert_files_fails_for_no_expected_files/input/two.txt +0 -1
  216. data/test/test/file_methods/test_method_glob/expected/file.yml +0 -0
  217. data/test/test/file_methods/test_method_glob/expected/file_1.txt +0 -0
  218. data/test/test/file_methods/test_method_glob/expected/file_2.txt +0 -0
  219. data/test/test/file_methods_doc/test_sub/expected/one.txt +0 -1
  220. data/test/test/file_methods_doc/test_sub/expected/two.txt +0 -1
  221. data/test/test/file_methods_doc/test_sub/input/one.txt +0 -1
  222. data/test/test/file_methods_doc/test_sub/input/two.txt +0 -1
  223. data/test/test/file_methods_doc_test.rb +0 -29
  224. data/test/test/file_methods_test.rb +0 -275
  225. data/test/test/subset_methods_test.rb +0 -171
  226. data/test/test/tap_methods/test_assert_files/expected/task/name/a.txt +0 -1
  227. data/test/test/tap_methods/test_assert_files/expected/task/name/b.txt +0 -1
  228. data/test/test/tap_methods/test_assert_files/input/a.txt +0 -1
  229. data/test/test/tap_methods/test_assert_files/input/b.txt +0 -1
  230. data/test/test/tap_methods_test.rb +0 -399
  231. data/test/workflow_test.rb +0 -120
  232. data/vendor/rails_generator.rb +0 -56
  233. data/vendor/rails_generator/base.rb +0 -263
  234. data/vendor/rails_generator/commands.rb +0 -581
  235. data/vendor/rails_generator/generated_attribute.rb +0 -42
  236. data/vendor/rails_generator/lookup.rb +0 -209
  237. data/vendor/rails_generator/manifest.rb +0 -53
  238. data/vendor/rails_generator/options.rb +0 -143
  239. data/vendor/rails_generator/scripts.rb +0 -83
  240. data/vendor/rails_generator/scripts/destroy.rb +0 -7
  241. data/vendor/rails_generator/scripts/generate.rb +0 -7
  242. data/vendor/rails_generator/scripts/update.rb +0 -12
  243. data/vendor/rails_generator/simple_logger.rb +0 -46
  244. data/vendor/rails_generator/spec.rb +0 -44
File without changes
@@ -0,0 +1,71 @@
1
+ require 'tap/support/batchable'
2
+ require 'tap/support/executable'
3
+ require 'tap/support/framework_class'
4
+
5
+ module Tap
6
+ module Support
7
+
8
+ # Framework encapsulates the basic framework functionality (batching,
9
+ # configuration, documentation, etc) used by Task and Workflow. Note
10
+ # that Framework does NOT encapsulate the functionality needed to
11
+ # make a class useful in workflows, such as enq and on_complete.
12
+ module Framework
13
+ include Batchable
14
+ include Configurable
15
+
16
+ def self.included(mod)
17
+ mod.extend Support::BatchableClass
18
+ mod.extend Support::ConfigurableClass
19
+ mod.extend Support::FrameworkClass
20
+ end
21
+
22
+ # The application used to load config_file templates
23
+ # (and hence, to initialize batched objects).
24
+ attr_reader :app
25
+
26
+ # The name of self.
27
+ attr_accessor :name
28
+
29
+ # Initializes a new instance and associated batch objects. Batch
30
+ # objects will be initialized for each configuration template
31
+ # specified by app.each_config_template(config_file) where
32
+ # config_file = app.config_filepath(name).
33
+ def initialize(config={}, name=nil, app=App.instance)
34
+ super()
35
+ @app = app
36
+ @name = name || self.class.default_name
37
+ initialize_config(config)
38
+ end
39
+
40
+ # Creates a new batched object and adds the object to batch. The batched object
41
+ # will be a duplicate of the current object but with a new name and/or
42
+ # configurations.
43
+ def initialize_batch_obj(overrides={}, name=nil)
44
+ obj = super().reconfigure(overrides)
45
+ obj.name = name if name
46
+ obj
47
+ end
48
+
49
+ # Logs the inputs to the application logger (via app.log)
50
+ def log(action, msg="", level=Logger::INFO)
51
+ # TODO - add a task identifier?
52
+ app.log(action, msg, level)
53
+ end
54
+
55
+ # Raises a TerminateError if app.state == State::TERMINATE.
56
+ # check_terminate may be called at any time to provide a
57
+ # breakpoint in long-running processes.
58
+ def check_terminate
59
+ if app.state == App::State::TERMINATE
60
+ raise App::TerminateError.new
61
+ end
62
+ end
63
+
64
+ # Returns self.name
65
+ def to_s
66
+ name
67
+ end
68
+
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,199 @@
1
+ require 'tap/support/command_line'
2
+
3
+ module Tap
4
+ module Support
5
+
6
+ # FrameworkClass encapsulates class methods related to Framework.
7
+ module FrameworkClass
8
+
9
+ # Returns the default name for the class: to_s.underscore
10
+ attr_accessor :default_name
11
+
12
+ def self.extended(base)
13
+ caller.each_with_index do |line, index|
14
+ case line
15
+ when /\/framework.rb/ then next
16
+ when /^(([A-z]:)?[^:]+):(\d+)/
17
+ base.instance_variable_set(:@source_file, File.expand_path($1))
18
+ break
19
+ end
20
+ end
21
+
22
+ base.instance_variable_set(:@default_name, base.to_s.underscore)
23
+ end
24
+
25
+ def inherited(child)
26
+ unless child.instance_variable_defined?(:@source_file)
27
+ caller.first =~ /^(([A-z]:)?[^:]+):(\d+)/
28
+ child.instance_variable_set(:@source_file, File.expand_path($1))
29
+ end
30
+
31
+ child.instance_variable_set(:@default_name, child.to_s.underscore)
32
+ super
33
+ end
34
+
35
+ def subclass(const_name, configs={}, options={}, &block)
36
+ # Generate the nesting module
37
+ current, constants = const_name.to_s.constants_split
38
+ raise ArgumentError, "#{current} is already defined!" if constants.empty?
39
+
40
+ subclass_const = constants.pop
41
+ constants.each {|const| current = current.const_set(const, Module.new)}
42
+
43
+ # Generate the subclass
44
+ subclass = Class.new(self)
45
+ configs = configs[0] if configs.kind_of?(Array) && configs.length == 1 && configs[0].kind_of?(Hash)
46
+
47
+ case configs
48
+ when Hash
49
+ subclass.send(:attr_accessor, *configs.keys)
50
+ configs.each_pair do |key, value|
51
+ subclass.configurations.add(key, value)
52
+ end
53
+ when Array
54
+ configs.each do |method, key, value, opts, config_block|
55
+ subclass.send(method, key, value, opts, &config_block)
56
+ end
57
+ end
58
+
59
+ block_method = options[:block_method] || :process
60
+ subclass.send(:define_method, block_method, &block)
61
+ subclass.default_name = const_name
62
+
63
+ const_name = current == Object ? subclass_const : "#{current}::#{subclass_const}"
64
+ caller.each_with_index do |line, index|
65
+ case line
66
+ when /\/tap\/support\/declarations.rb/ then next
67
+ when /^(([A-z]:)?[^:]+):(\d+)/
68
+ subclass.source_file = File.expand_path($1)
69
+ subclass.lazydoc[const_name, false]['manifest'] = subclass.lazydoc.register($3.to_i - 1)
70
+ break
71
+ end
72
+ end
73
+
74
+ arity = options[:arity] || block.arity
75
+ comment = Comment.new
76
+ comment.subject = case
77
+ when arity > 0
78
+ Array.new(arity, "INPUT").join(' ')
79
+ when arity < 0
80
+ array = Array.new(-1 * arity - 1, "INPUT")
81
+ array << "INPUTS..."
82
+ array.join(' ')
83
+ else ""
84
+ end
85
+ subclass.lazydoc[const_name, false]['args'] ||= comment
86
+
87
+ # Set the subclass constant
88
+ current.const_set(subclass_const, subclass)
89
+ end
90
+
91
+ DEFAULT_HELP_TEMPLATE = %Q{<%= task_class %><%= manifest.subject.to_s.strip.empty? ? '' : ' -- ' %><%= manifest.subject %>
92
+
93
+ <% unless manifest.empty? %>
94
+ <%= '-' * 80 %>
95
+
96
+ <% manifest.wrap(77, 2, nil).each do |line| %>
97
+ <%= line %>
98
+ <% end %>
99
+ <%= '-' * 80 %>
100
+ <% end %>
101
+
102
+ }
103
+
104
+ def instantiate(argv, app=Tap::App.instance) # => instance, argv
105
+ opts = OptionParser.new
106
+
107
+ # Add configurations
108
+ config = {}
109
+ unless configurations.empty?
110
+ opts.separator ""
111
+ opts.separator "configurations:"
112
+ end
113
+
114
+ configurations.each do |receiver, key, configuration|
115
+ opts.on(*CommandLine.configv(configuration)) do |value|
116
+ config[key] = value
117
+ end
118
+ end
119
+
120
+ # Add options on_tail, giving priority to configurations
121
+ opts.separator ""
122
+ opts.separator "options:"
123
+
124
+ opts.on_tail("-h", "--help", "Print this help") do
125
+ args = lazydoc(true)[to_s]['args'] || Tap::Support::Comment.new
126
+
127
+ opts.banner = "#{help}usage: tap run -- #{to_s.underscore} #{args.subject}"
128
+ puts opts
129
+ exit
130
+ end
131
+
132
+ # Add option for name
133
+ name = default_name
134
+ opts.on_tail('--name NAME', /^[^-].*/, 'Specify a name') do |value|
135
+ name = value
136
+ end
137
+
138
+ # Add option to add args
139
+ use_args = []
140
+ opts.on_tail('--use FILE', /^[^-].*/, 'Loads inputs from file') do |value|
141
+ obj = YAML.load_file(value)
142
+ case obj
143
+ when Hash
144
+ obj.values.each do |array|
145
+ # error if value isn't an array
146
+ use_args.concat(array)
147
+ end
148
+ when Array
149
+ use_args.concat(obj)
150
+ else
151
+ use_args << obj
152
+ end
153
+ end
154
+
155
+ opts.parse!(argv)
156
+ obj = new({}, name, app)
157
+
158
+ path_configs = load_config(app.config_filepath(name))
159
+ if path_configs.kind_of?(Array)
160
+ path_configs.each_with_index do |path_config, i|
161
+ obj.initialize_batch_obj(path_config, "#{name}_#{i}") unless i == 0
162
+ end
163
+ path_configs = path_configs[0]
164
+ end
165
+
166
+ [obj.reconfigure(path_configs).reconfigure(config), argv + use_args]
167
+ end
168
+
169
+ def lazydoc(resolve=false, args_method=:process)
170
+ if resolve
171
+ lazydoc = super(false)
172
+ lazydoc.resolve(nil, /^\s*def\s+#{args_method}(\((.*?)\))?/) do |comment, match|
173
+ comment.subject = match[2].to_s.split(',').collect do |arg|
174
+ arg = arg.strip.upcase
175
+ case arg
176
+ when /^&/ then nil
177
+ when /^\*/ then arg[1..-1] + "..."
178
+ else arg
179
+ end
180
+ end.join(', ')
181
+
182
+ lazydoc.default_attributes['args'] ||= comment
183
+ end
184
+
185
+ super(true)
186
+ else
187
+ super(false)
188
+ end
189
+ end
190
+
191
+ def help
192
+ Tap::Support::Templater.new(DEFAULT_HELP_TEMPLATE,
193
+ :task_class => self,
194
+ :manifest => lazydoc(true)[to_s]['manifest'] || Tap::Support::Comment.new
195
+ ).build
196
+ end
197
+ end
198
+ end
199
+ end
@@ -0,0 +1,147 @@
1
+ module Tap
2
+ module Support
3
+
4
+ # InstanceConfiguration serves as a forwarding hash, where get and set operations
5
+ # for configurations are sent to instance methods rather than to an underlying data
6
+ # store.
7
+ #
8
+ # class Sample
9
+ # attr_accessor :key
10
+ # end
11
+ # sample = Sample.new
12
+ #
13
+ # class_config = ClassConfiguration.new(Sample)
14
+ # class_config.add(:key)
15
+ #
16
+ # config = InstanceConfiguration.new(class_config)
17
+ # config.bind(sample)
18
+ #
19
+ # sample.key = 'value'
20
+ # config[:key] # => 'value'
21
+ #
22
+ # config[:key] = 'another'
23
+ # sample.key # => 'another'
24
+ #
25
+ # Non-config keys are simply stored:
26
+ #
27
+ # config[:not_a_key] = 'value'
28
+ # config[:not_a_key] # => 'value'
29
+ #
30
+ # config.store # => {:not_a_key => 'value'}
31
+ # config.to_hash # => {:key => 'another', :not_a_key => 'value'}
32
+ #
33
+ class InstanceConfiguration
34
+
35
+ # The bound receiver
36
+ attr_reader :receiver
37
+
38
+ # The underlying data store for non-config keys
39
+ attr_reader :store
40
+
41
+ # The ClassConfiguration specifying config keys
42
+ attr_reader :class_config
43
+
44
+ def initialize(class_config, receiver=nil)
45
+ @receiver = receiver
46
+ @store = {}
47
+ @class_config = class_config
48
+ end
49
+
50
+ # Binds self to the specified receiver. Mapped keys are
51
+ # removed from store and sent to their writer method on
52
+ # receiver.
53
+ def bind(receiver)
54
+ raise ArgumentError.new("receiver cannot be nil") if receiver == nil
55
+
56
+ class_config.each_pair do |key, config|
57
+ receiver.send(config.writer, store.delete(key))
58
+ end
59
+ @receiver = receiver
60
+
61
+ self
62
+ end
63
+
64
+ # Returns true if self is bound to a receiver
65
+ def bound?
66
+ receiver != nil
67
+ end
68
+
69
+ # Unbinds self from the specified receiver. Mapped values
70
+ # are stored in store. Returns the unbound receiver.
71
+ def unbind
72
+ class_config.each_pair do |key, config|
73
+ store[key] = receiver.send(config.reader)
74
+ end
75
+ r = receiver
76
+ @receiver = nil
77
+
78
+ r
79
+ end
80
+
81
+ # Duplicates self, returning an unbound InstanceConfiguration.
82
+ def dup
83
+ duplicate = super()
84
+ duplicate.instance_variable_set(:@receiver, nil)
85
+ duplicate.instance_variable_set(:@store, @store.dup)
86
+ duplicate
87
+ end
88
+
89
+ # Associates the value the key. If bound? and the key
90
+ # is a class_config key, then the value will be forwarded
91
+ # to the class_config.writer method on the receiver.
92
+ def []=(key, value)
93
+ case
94
+ when bound? && config = class_config.map[key.to_sym]
95
+ receiver.send(config.writer, value)
96
+ else store[key] = value
97
+ end
98
+ end
99
+
100
+ # Retrieves the value corresponding to the key. If bound?
101
+ # and the key is a class_config key, then the value is
102
+ # obtained from the :key method on the receiver.
103
+ def [](key)
104
+ case
105
+ when bound? && config = class_config.map[key.to_sym]
106
+ receiver.send(config.reader)
107
+ else store[key]
108
+ end
109
+ end
110
+
111
+ # True if the key is assigned in self.
112
+ def has_key?(key)
113
+ (bound? && class_config.key?(key)) || store.has_key?(key)
114
+ end
115
+
116
+ # Calls block once for each key-value pair stored in self.
117
+ def each_pair # :yields: key, value
118
+ class_config.each_pair do |key, config|
119
+ yield(key, receiver.send(config.reader))
120
+ end if bound?
121
+
122
+ store.each_pair do |key, value|
123
+ yield(key, value)
124
+ end
125
+ end
126
+
127
+ # Equal if the to_hash values of self and another are equal.
128
+ def ==(another)
129
+ to_hash == another.to_hash
130
+ end
131
+
132
+ # Returns self as a hash.
133
+ def to_hash
134
+ hash = store.dup
135
+ class_config.keys.each do |key|
136
+ hash[key] = self[key]
137
+ end if bound?
138
+ hash
139
+ end
140
+
141
+ # Overrides default inspect to show the to_hash values.
142
+ def inspect
143
+ "#<#{self.class}:#{object_id} to_hash=#{to_hash.inspect}>"
144
+ end
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,428 @@
1
+ require 'tap/support/comment'
2
+
3
+ module Tap
4
+ module Support
5
+
6
+ # Lazydoc scans source files to pull out documentation. Lazydoc can find two
7
+ # types of documentation, constant attributes and code comments.
8
+ #
9
+ # === Constant Attributes
10
+ #
11
+ # Constant attributes are designated the same as constants in Ruby, but with
12
+ # an extra 'key' constant that must consist of only lowercase letters and/or
13
+ # underscores. This format assures that attributes are sytactically invalid
14
+ # outside of comments.
15
+ #
16
+ # When Lazydoc finds an attribute it parses a Comment value where the subject
17
+ # is the remainder of the line, and comment lines are parsed down until a
18
+ # non-comment line, an end key, or a new attribute is reached.
19
+ #
20
+ # str = %Q{
21
+ # # Const::Name::key subject for key
22
+ # # comment for key
23
+ # # parsed until a non-comment line
24
+ #
25
+ # # Const::Name::another subject for another
26
+ # # comment for another
27
+ # # parsed to an end key
28
+ # # Const::Name::another-
29
+ # #
30
+ # # ignored comment
31
+ # }
32
+ #
33
+ # lazydoc = Lazydoc.new
34
+ # lazydoc.resolve(str)
35
+ #
36
+ # lazydoc.to_hash {|comment| [comment.subject, comment.to_s] }
37
+ # # => {'Const::Name' => {
38
+ # # 'key' => ['subject for key', 'comment for key parsed until a non-comment line'],
39
+ # # 'another' => ['subject for another', 'comment for another parsed to an end key']
40
+ # # }}
41
+ #
42
+ # A constant name does not need to be specified; when no constant name is
43
+ # specified, Lazydoc will store the key as a default for the document. To
44
+ # turn off attribute parsing for a section of documentation, use start/stop
45
+ # keys:
46
+ #
47
+ # str = %Q{
48
+ # # :::-
49
+ # # Const::Name::not_parsed
50
+ # # :::+
51
+ #
52
+ # # Const::Name::parsed subject
53
+ # }
54
+ #
55
+ # lazydoc = Lazydoc.new
56
+ # lazydoc.resolve(str)
57
+ # lazydoc.to_hash {|comment| comment.subject } # => {'Const::Name' => {'parsed' => 'subject'}}
58
+ #
59
+ # ==== startdoc
60
+ #
61
+ # Lazydoc is completely separate from RDoc, but the syntax of Lazydoc was developed
62
+ # with RDoc in mind. To hide attributes in one line, make use of the RDoc
63
+ # <tt>:startdoc:</tt> document modifier like this (spaces added to keep them in the
64
+ # example):
65
+ #
66
+ # # :start doc::Const::Name::one hidden in RDoc
67
+ # # * This line is visible in RDoc.
68
+ # # :start doc::Const::Name::one-
69
+ # #
70
+ # #--
71
+ # # Const::Name::two
72
+ # # You can hide attribute comments like this.
73
+ # # Const::Name::two-
74
+ # #++
75
+ # #
76
+ # # * This line is also visible in RDoc.
77
+ #
78
+ # Here is the same text, actually in RDoc:
79
+ #
80
+ # :startdoc::Const::Name::one hidden in RDoc
81
+ # * This line is visible in RDoc.
82
+ # :startdoc::Const::Name::one-
83
+ #
84
+ #--
85
+ # Const::Name::two
86
+ # You can hide attribute comments like this.
87
+ # Const::Name::two-
88
+ #++
89
+ #
90
+ # * This line is also visible in RDoc.
91
+ #
92
+ # === Code Comments
93
+ #
94
+ # Code comments are lines marked for parsing if and when a Lazydoc gets resolved.
95
+ # Unlike constant attributes, the line is the subject of a code comment and
96
+ # comment lines are parsed up from it (effectively mimicking the behavior of
97
+ # RDoc).
98
+ #
99
+ # str = %Q{
100
+ # # comment lines for
101
+ # # the method
102
+ # def method
103
+ # end
104
+ #
105
+ # # as in RDoc, the comment can be
106
+ # # separated from the method
107
+ #
108
+ # def another_method
109
+ # end
110
+ # }
111
+ #
112
+ # lazydoc = Lazydoc.new
113
+ # lazydoc.register(3)
114
+ # lazydoc.register(9)
115
+ # lazydoc.resolve(str)
116
+ #
117
+ # lazydoc.code_comments.collect {|comment| [comment.subject, comment.to_s] }
118
+ # # => [
119
+ # # ['def method', 'comment lines for the method'],
120
+ # # ['def another_method', 'as in RDoc, the comment can be separated from the method']]
121
+ #
122
+ class Lazydoc
123
+
124
+ # A regexp matching an attribute start or end. For the match:
125
+ #
126
+ # $1:: const_name
127
+ # $3:: key
128
+ # $4:: end flag
129
+ #
130
+ ATTRIBUTE_REGEXP = /(::|([A-Z][A-z]*::)+)([a-z_]+)(-?)/
131
+
132
+ # A regexp matching constants.
133
+ CONSTANT_REGEXP = /(::|([A-Z][A-z]*::)+)/
134
+
135
+ class << self
136
+
137
+ # A hash of (source_file, lazydoc) pairs tracking the
138
+ # Lazydoc instance for the given source file.
139
+ def registry
140
+ @registry ||= []
141
+ end
142
+
143
+ # Returns the lazydoc in registry for the specified source file.
144
+ # If no such lazydoc exists, one will be created for it.
145
+ def [](source_file)
146
+ source_file = File.expand_path(source_file.to_s)
147
+ lazydoc = registry.find {|doc| doc.source_file == source_file }
148
+ if lazydoc == nil
149
+ lazydoc = new(source_file)
150
+ registry << lazydoc
151
+ end
152
+ lazydoc
153
+ end
154
+
155
+ # Register the specified line numbers to the lazydoc for source_file.
156
+ # Returns a CodeComment corresponding to the line.
157
+ def register(source_file, line_number)
158
+ Lazydoc[source_file].register(line_number)
159
+ end
160
+
161
+ # Resolves all lazydocs which include the specified code comments.
162
+ def resolve(code_comments)
163
+ registry.each do |doc|
164
+ next if (code_comments & doc.code_comments).empty?
165
+ doc.resolve
166
+ end
167
+ end
168
+
169
+ # Scans the specified file for attributes keyed by key and stores
170
+ # the resulting comments in the corresponding lazydoc.
171
+ # Returns the lazydoc.
172
+ def scan_doc(source_file, key)
173
+ lazydoc = nil
174
+ scan(File.read(source_file), key) do |const_name, attr_key, comment|
175
+ lazydoc = self[source_file] unless lazydoc
176
+ lazydoc.attributes(const_name)[attr_key] = comment
177
+ end
178
+ lazydoc
179
+ end
180
+
181
+ # Scans the string or StringScanner for attributes matching the key;
182
+ # keys may be patterns, they are incorporated into a regexp. Yields
183
+ # each (const_name, key, value) triplet to the mandatory block and
184
+ # skips regions delimited by the stop and start keys <tt>:-</tt>
185
+ # and <tt>:+</tt>.
186
+ #
187
+ # str = %Q{
188
+ # Const::Name::key value
189
+ # ::alt alt_value
190
+ #
191
+ # Ignored::Attribute::not_matched value
192
+ # :::-
193
+ # Also::Ignored::key value
194
+ # :::+
195
+ # Another::key another value
196
+ # }
197
+ #
198
+ # results = []
199
+ # Lazydoc.scan(str, 'key|alt') do |const_name, key, value|
200
+ # results << [const_name, key, value]
201
+ # end
202
+ #
203
+ # results
204
+ # # => [
205
+ # # ['Const::Name', 'key', 'value'],
206
+ # # ['', 'alt', 'alt_value'],
207
+ # # ['Another', 'key', 'another value']]
208
+ #
209
+ # Returns the StringScanner used during scanning.
210
+ def scan(str, key) # :yields: const_name, key, value
211
+ scanner = case str
212
+ when StringScanner then str
213
+ when String then StringScanner.new(str)
214
+ else raise TypeError, "can't convert #{str.class} into StringScanner or String"
215
+ end
216
+
217
+ regexp = /(#{key})([ \r\t-].*$|$)/
218
+ while !scanner.eos?
219
+ break if scanner.skip_until(CONSTANT_REGEXP) == nil
220
+ const_name = scanner[1]
221
+
222
+ case
223
+ when scanner.scan(regexp)
224
+ yield(const_name.chomp('::'), scanner[1], scanner[2].strip)
225
+ when scanner.scan(/:-/)
226
+ scanner.skip_until(/:\+/)
227
+ end
228
+ end
229
+
230
+ scanner
231
+ end
232
+
233
+ # Parses constant attributes from the string or StringScanner. Yields
234
+ # each (const_name, key, comment) triplet to the mandatory block
235
+ # and skips regions delimited by the stop and start keys <tt>:-</tt>
236
+ # and <tt>:+</tt>.
237
+ #
238
+ # str = %Q{
239
+ # # Const::Name::key subject for key
240
+ # # comment for key
241
+ #
242
+ # # :::-
243
+ # # Ignored::key value
244
+ # # :::+
245
+ #
246
+ # # Ignored text before attribute ::another subject for another
247
+ # # comment for another
248
+ # }
249
+ #
250
+ # results = []
251
+ # Lazydoc.parse(str) do |const_name, key, comment|
252
+ # results << [const_name, key, comment.subject, comment.to_s]
253
+ # end
254
+ #
255
+ # results
256
+ # # => [
257
+ # # ['Const::Name', 'key', 'subject for key', 'comment for key'],
258
+ # # ['', 'another', 'subject for another', 'comment for another']]
259
+ #
260
+ # Returns the StringScanner used during scanning.
261
+ def parse(str) # :yields: const_name, key, comment
262
+ scanner = case str
263
+ when StringScanner then str
264
+ when String then StringScanner.new(str)
265
+ else raise TypeError, "can't convert #{str.class} into StringScanner or String"
266
+ end
267
+
268
+ scan(scanner, '[a-z_]+') do |const_name, key, value|
269
+ comment = Comment.parse(scanner, false) do |line|
270
+ if line =~ /::/ && line =~ ATTRIBUTE_REGEXP
271
+ # rewind to capture the next attribute unless an end is specified.
272
+ scanner.unscan unless !$4.empty? && $1.chomp("::") == const_name && $3 == key
273
+ true
274
+ else false
275
+ end
276
+ end
277
+ comment.subject = value
278
+ yield(const_name, key, comment)
279
+ end
280
+ end
281
+ end
282
+
283
+ include Enumerable
284
+
285
+ # The source file for self, used in resolving comments and
286
+ # attributes.
287
+ attr_reader :source_file
288
+
289
+ # An array of Comment objects identifying lines resolved or
290
+ # to-be-resolved for self.
291
+ attr_reader :code_comments
292
+
293
+ # A hash of (const_name, attributes) pairs tracking the constant
294
+ # attributes resolved or to-be-resolved for self. Attributes
295
+ # are hashes of (key, comment) pairs.
296
+ attr_reader :const_attrs
297
+
298
+ def initialize(source_file=nil)
299
+ self.source_file = source_file
300
+ @code_comments = []
301
+ @const_attrs = {}
302
+ @resolved = false
303
+ end
304
+
305
+ # Sets the source file for self. Expands the source file path if necessary.
306
+ def source_file=(source_file)
307
+ @source_file = source_file == nil ? nil : File.expand_path(source_file)
308
+ end
309
+
310
+ # Returns the attributes for the specified const_name.
311
+ def attributes(const_name)
312
+ const_attrs[const_name] ||= {}
313
+ end
314
+
315
+ # Returns default document attributes (ie attributes(''))
316
+ def default_attributes
317
+ attributes('')
318
+ end
319
+
320
+ # Returns the attributes for const_name merged to default_attributes.
321
+ # Set merge_defaults to false to get just the attributes for const_name.
322
+ def [](const_name, merge_defaults=true)
323
+ merge_defaults ? default_attributes.merge(attributes(const_name)) : attributes(const_name)
324
+ end
325
+
326
+ # Yields each (const_name, attributes) pair to the block; const_names where
327
+ # the attributes are empty are skipped.
328
+ def each
329
+ const_attrs.each_pair do |const_name, attrs|
330
+ yield(const_name, attrs) unless attrs.empty?
331
+ end
332
+ end
333
+
334
+ # Returns true if the attributes for const_name are not empty.
335
+ def has_const?(const_name)
336
+ const_attrs.each_pair do |constname, attrs|
337
+ next unless constname == const_name
338
+ return !attrs.empty?
339
+ end
340
+
341
+ false
342
+ end
343
+
344
+ # Returns an array of the constant names in self, for which
345
+ # the constant attributes are not empty.
346
+ def const_names
347
+ names = []
348
+ const_attrs.each_pair do |const_name, attrs|
349
+ names << const_name unless attrs.empty?
350
+ end
351
+ names
352
+ end
353
+
354
+ # Register the specified line number to self. Returns a
355
+ # Comment object corresponding to the line.
356
+ def register(line_number)
357
+ comment = code_comments.find {|c| c.line_number == line_number }
358
+
359
+ if comment == nil
360
+ comment = Comment.new(line_number)
361
+ code_comments << comment
362
+ end
363
+
364
+ comment
365
+ end
366
+
367
+ # Returns true if the code_comments for source_file are frozen.
368
+ def resolved?
369
+ @resolved
370
+ end
371
+
372
+ attr_writer :resolved
373
+
374
+ def resolve(str=nil, comment_regexp=nil) # :yields: comment, match
375
+ return(false) if resolved?
376
+
377
+ if str == nil
378
+ raise ArgumentError, "no source file specified" unless source_file && File.exists?(source_file)
379
+ str = File.read(source_file)
380
+ end
381
+
382
+ Lazydoc.parse(str) do |const_name, key, comment|
383
+ attributes(const_name)[key] = comment
384
+ end
385
+
386
+ lines = str.split(/\r?\n/)
387
+ lines.each_with_index do |line, line_number|
388
+ next unless line =~ comment_regexp
389
+ comment = register(line_number)
390
+ yield(comment, $~) if block_given?
391
+ end unless comment_regexp == nil
392
+
393
+ code_comments.collect! do |comment|
394
+ line_number = comment.line_number
395
+ comment.subject = lines[line_number] if comment.subject == nil
396
+
397
+ # remove whitespace lines
398
+ line_number -= 1
399
+ while lines[line_number].strip.empty?
400
+ line_number -= 1
401
+ end
402
+
403
+ # put together the comment
404
+ while line_number >= 0
405
+ break unless comment.prepend(lines[line_number])
406
+ line_number -= 1
407
+ end
408
+
409
+ comment
410
+ end
411
+
412
+ @resolved = true
413
+ end
414
+
415
+ def to_hash
416
+ const_hash = {}
417
+ const_names.sort.each do |const_name|
418
+ attr_hash = {}
419
+ self[const_name, false].each_pair do |key, comment|
420
+ attr_hash[key] = (block_given? ? yield(comment) : comment)
421
+ end
422
+ const_hash[const_name] = attr_hash
423
+ end
424
+ const_hash
425
+ end
426
+ end
427
+ end
428
+ end