tap 0.9.1 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
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