tap 0.10.1 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (124) hide show
  1. data/History +12 -0
  2. data/MIT-LICENSE +0 -2
  3. data/README +23 -32
  4. data/bin/rap +116 -0
  5. data/bin/tap +6 -9
  6. data/cgi/run.rb +67 -0
  7. data/cmd/console.rb +1 -1
  8. data/cmd/destroy.rb +4 -4
  9. data/cmd/generate.rb +4 -4
  10. data/cmd/manifest.rb +61 -53
  11. data/cmd/run.rb +8 -75
  12. data/doc/Class Reference +130 -121
  13. data/doc/Command Reference +76 -124
  14. data/doc/Syntax Reference +290 -0
  15. data/doc/Tutorial +305 -237
  16. data/lib/tap/app.rb +140 -467
  17. data/lib/tap/constants.rb +2 -2
  18. data/lib/tap/declarations.rb +211 -0
  19. data/lib/tap/env.rb +171 -193
  20. data/lib/tap/exe.rb +100 -21
  21. data/lib/tap/file_task.rb +3 -3
  22. data/lib/tap/generator/base.rb +1 -1
  23. data/lib/tap/generator/destroy.rb +10 -10
  24. data/lib/tap/generator/generate.rb +29 -18
  25. data/lib/tap/generator/generators/command/command_generator.rb +2 -2
  26. data/lib/tap/generator/generators/command/templates/command.erb +2 -2
  27. data/lib/tap/generator/generators/config/config_generator.rb +3 -3
  28. data/lib/tap/generator/generators/config/templates/doc.erb +1 -1
  29. data/lib/tap/generator/generators/file_task/file_task_generator.rb +1 -1
  30. data/lib/tap/generator/generators/file_task/templates/task.erb +1 -1
  31. data/lib/tap/generator/generators/file_task/templates/test.erb +1 -1
  32. data/lib/tap/generator/generators/generator/generator_generator.rb +27 -0
  33. data/lib/tap/generator/generators/generator/templates/task.erb +27 -0
  34. data/lib/tap/generator/generators/root/root_generator.rb +13 -13
  35. data/lib/tap/generator/generators/root/templates/README +0 -0
  36. data/lib/tap/generator/generators/root/templates/Rakefile +2 -2
  37. data/lib/tap/generator/generators/root/templates/gemspec +4 -5
  38. data/lib/tap/generator/generators/root/templates/tapfile +11 -8
  39. data/lib/tap/generator/generators/root/templates/test/tap_test_suite.rb +1 -1
  40. data/lib/tap/generator/generators/task/task_generator.rb +1 -3
  41. data/lib/tap/generator/generators/task/templates/test.erb +1 -3
  42. data/lib/tap/patches/optparse/summarize.rb +62 -0
  43. data/lib/tap/root.rb +41 -29
  44. data/lib/tap/support/aggregator.rb +16 -3
  45. data/lib/tap/support/assignments.rb +10 -9
  46. data/lib/tap/support/audit.rb +58 -64
  47. data/lib/tap/support/class_configuration.rb +33 -44
  48. data/lib/tap/support/combinator.rb +125 -0
  49. data/lib/tap/support/configurable.rb +13 -14
  50. data/lib/tap/support/configurable_class.rb +21 -43
  51. data/lib/tap/support/configuration.rb +55 -9
  52. data/lib/tap/support/constant.rb +87 -13
  53. data/lib/tap/support/constant_manifest.rb +116 -0
  54. data/lib/tap/support/dependencies.rb +54 -0
  55. data/lib/tap/support/dependency.rb +44 -0
  56. data/lib/tap/support/executable.rb +247 -32
  57. data/lib/tap/support/executable_queue.rb +1 -1
  58. data/lib/tap/support/gems/rake.rb +29 -8
  59. data/lib/tap/support/gems.rb +10 -30
  60. data/lib/tap/support/instance_configuration.rb +29 -3
  61. data/lib/tap/support/intern.rb +46 -0
  62. data/lib/tap/support/join.rb +143 -0
  63. data/lib/tap/support/joins/fork.rb +19 -0
  64. data/lib/tap/support/joins/merge.rb +22 -0
  65. data/lib/tap/support/joins/sequence.rb +21 -0
  66. data/lib/tap/support/joins/switch.rb +25 -0
  67. data/lib/tap/support/joins/sync_merge.rb +63 -0
  68. data/lib/tap/support/joins.rb +15 -0
  69. data/lib/tap/support/lazy_attributes.rb +17 -2
  70. data/lib/tap/support/lazydoc/comment.rb +503 -0
  71. data/lib/tap/support/lazydoc/config.rb +17 -0
  72. data/lib/tap/support/lazydoc/definition.rb +36 -0
  73. data/lib/tap/support/lazydoc/document.rb +152 -0
  74. data/lib/tap/support/lazydoc/method.rb +24 -0
  75. data/lib/tap/support/lazydoc.rb +269 -343
  76. data/lib/tap/support/manifest.rb +121 -103
  77. data/lib/tap/support/minimap.rb +90 -0
  78. data/lib/tap/support/node.rb +56 -0
  79. data/lib/tap/support/parser.rb +436 -0
  80. data/lib/tap/support/schema.rb +359 -0
  81. data/lib/tap/support/shell_utils.rb +3 -5
  82. data/lib/tap/support/string_ext.rb +60 -0
  83. data/lib/tap/support/tdoc.rb +7 -2
  84. data/lib/tap/support/templater.rb +30 -16
  85. data/lib/tap/support/validation.rb +77 -8
  86. data/lib/tap/task.rb +431 -143
  87. data/lib/tap/tasks/dump.rb +15 -10
  88. data/lib/tap/tasks/load.rb +112 -0
  89. data/lib/tap/tasks/rake.rb +4 -41
  90. data/lib/tap/test/assertions.rb +38 -0
  91. data/lib/tap/test/env_vars.rb +1 -1
  92. data/lib/tap/test/extensions.rb +79 -0
  93. data/lib/tap/test/file_test.rb +420 -0
  94. data/lib/tap/test/file_test_class.rb +12 -0
  95. data/lib/tap/test/regexp_escape.rb +87 -0
  96. data/lib/tap/test/script_test.rb +46 -0
  97. data/lib/tap/test/script_tester.rb +115 -0
  98. data/lib/tap/test/subset_test.rb +260 -0
  99. data/lib/tap/test/subset_test_class.rb +99 -0
  100. data/lib/tap/test/{tap_methods.rb → tap_test.rb} +45 -43
  101. data/lib/tap/test/utils.rb +231 -0
  102. data/lib/tap/test.rb +53 -26
  103. data/lib/tap.rb +3 -20
  104. metadata +50 -27
  105. data/lib/tap/generator/generators/root/templates/test/tapfile_test.rb +0 -15
  106. data/lib/tap/patches/rake/rake_test_loader.rb +0 -8
  107. data/lib/tap/patches/rake/testtask.rb +0 -57
  108. data/lib/tap/patches/ruby19/backtrace_filter.rb +0 -51
  109. data/lib/tap/patches/ruby19/parsedate.rb +0 -16
  110. data/lib/tap/support/batchable.rb +0 -47
  111. data/lib/tap/support/batchable_class.rb +0 -107
  112. data/lib/tap/support/command_line.rb +0 -98
  113. data/lib/tap/support/comment.rb +0 -270
  114. data/lib/tap/support/constant_utils.rb +0 -127
  115. data/lib/tap/support/declarations.rb +0 -111
  116. data/lib/tap/support/framework.rb +0 -83
  117. data/lib/tap/support/framework_class.rb +0 -180
  118. data/lib/tap/support/run_error.rb +0 -39
  119. data/lib/tap/support/summary.rb +0 -30
  120. data/lib/tap/test/file_methods.rb +0 -377
  121. data/lib/tap/test/script_methods/script_test.rb +0 -98
  122. data/lib/tap/test/script_methods.rb +0 -107
  123. data/lib/tap/test/subset_methods.rb +0 -420
  124. data/lib/tap/workflow.rb +0 -200
@@ -1,14 +1,15 @@
1
1
  require 'tap/support/class_configuration'
2
2
  require 'tap/support/validation'
3
3
  require 'tap/support/lazy_attributes'
4
+ require 'tap/support/lazydoc/config'
4
5
 
5
6
  module Tap
6
7
  module Support
7
8
  autoload(:Templater, 'tap/support/templater')
8
9
 
9
- # ConfigurableClass encapsulates class methods used to declare class configurations.
10
- # When configurations are declared using the config method, ConfigurableClass
11
- # generates accessors in the class, much like attr_accessor.
10
+ # ConfigurableClass defines class methods used by a Configurable class to
11
+ # declare configurations. In addition to registering key-value pairs,
12
+ # these methods generate readers and writers, much like attr_accessor.
12
13
  #
13
14
  # class ConfigurableClass
14
15
  # extend ConfigurableClass
@@ -21,31 +22,6 @@ module Tap
21
22
  # c.respond_to?('one') # => true
22
23
  # c.respond_to?('one=') # => true
23
24
  #
24
- # If a block is given, the block will be used to create the writer method
25
- # for the config. Used in this manner, config defines a <tt>config_key=</tt> method
26
- # wherein <tt>@config_key</tt> will be set to the return value of the block.
27
- #
28
- # class AnotherConfigurableClass
29
- # extend ConfigurableClass
30
- # config(:one, 'one') {|value| value.upcase }
31
- # end
32
- #
33
- # ac = AnotherConfigurableClass.new
34
- # ac.one = 'value'
35
- # ac.one # => 'VALUE'
36
- #
37
- # The block has class-context in this case. To have instance-context, use the
38
- # config_attr method which defines the writer method using the block directly.
39
- #
40
- # class YetAnotherConfigurableClass
41
- # extend ConfigurableClass
42
- # config_attr(:one, 'one') {|value| @one = value.reverse }
43
- # end
44
- #
45
- # ac = YetAnotherConfigurableClass.new
46
- # ac.one = 'value'
47
- # ac.one # => 'eulav'
48
- #
49
25
  module ConfigurableClass
50
26
  include Tap::Support::LazyAttributes
51
27
 
@@ -53,11 +29,11 @@ module Tap
53
29
  attr_reader :configurations
54
30
 
55
31
  # Sets the source_file for base and initializes base.configurations.
56
- def self.extended(base)
32
+ def self.extended(base) # :nodoc:
57
33
  caller.each_with_index do |line, index|
58
34
  case line
59
35
  when /\/configurable.rb/ then next
60
- when /^(([A-z]:)?[^:]+):(\d+)/
36
+ when Lazydoc::CALLER_REGEXP
61
37
  base.instance_variable_set(:@source_file, File.expand_path($1))
62
38
  break
63
39
  end
@@ -69,9 +45,9 @@ module Tap
69
45
  # When subclassed, the parent.configurations are duplicated and passed to
70
46
  # the child class where they can be extended/modified without affecting
71
47
  # the configurations of the parent class.
72
- def inherited(child)
48
+ def inherited(child) # :nodoc:
73
49
  unless child.instance_variable_defined?(:@source_file)
74
- caller.first =~ /^(([A-z]:)?[^:]+):(\d+)/
50
+ caller.first =~ Lazydoc::CALLER_REGEXP
75
51
  child.instance_variable_set(:@source_file, File.expand_path($1))
76
52
  end
77
53
 
@@ -79,6 +55,7 @@ module Tap
79
55
  super
80
56
  end
81
57
 
58
+ # Returns the lazydoc for self.
82
59
  def lazydoc(resolve=true)
83
60
  Lazydoc.resolve_comments(configurations.code_comments) if resolve
84
61
  super
@@ -87,8 +64,8 @@ module Tap
87
64
  # Loads the contents of path as YAML. Returns an empty hash if the path
88
65
  # is empty, does not exist, or is not a file.
89
66
  def load_config(path)
90
- return {} if path == nil || !File.file?(path)
91
-
67
+ # the last check prevents YAML from auto-loading itself for empty files
68
+ return {} if path == nil || !File.file?(path) || File.size(path) == 0
92
69
  YAML.load_file(path) || {}
93
70
  end
94
71
 
@@ -250,8 +227,8 @@ module Tap
250
227
  caller.each do |line|
251
228
  case line
252
229
  when /in .config.$/ then next
253
- when /^(([A-z]:)?[^:]+):(\d+)/
254
- options[:desc] = Lazydoc.register($1, $3.to_i - 1)
230
+ when Lazydoc::CALLER_REGEXP
231
+ options[:desc] = Lazydoc.register($1, $3.to_i - 1, Lazydoc::Config)
255
232
  break
256
233
  end
257
234
  end if options[:desc] == nil
@@ -270,10 +247,10 @@ module Tap
270
247
  # blocks, such as switch (Validation::SWITCH) and list
271
248
  # (Validation::LIST).
272
249
  def arg_type(block) # :nodoc:
273
- case block
274
- when Validation::SWITCH then :switch
275
- when Validation::FLAG then :flag
276
- when Validation::LIST then :list
250
+ case
251
+ when block == Validation::SWITCH then :switch
252
+ when block == Validation::FLAG then :flag
253
+ when block == Validation::LIST then :list
277
254
  else nil
278
255
  end
279
256
  end
@@ -282,12 +259,13 @@ module Tap
282
259
  # blocks, such as switch (Validation::ARRAY) and list
283
260
  # (Validation::HASH).
284
261
  def arg_name(block) # :nodoc:
285
- case block
286
- when Validation::ARRAY then "'[a, b, c]'"
287
- when Validation::HASH then "'{one: 1, two: 2}'"
262
+ case
263
+ when block == Validation::ARRAY then "'[a, b, c]'"
264
+ when block == Validation::HASH then "'{one: 1, two: 2}'"
288
265
  else nil
289
266
  end
290
267
  end
268
+
291
269
  end
292
270
  end
293
271
  end
@@ -1,8 +1,12 @@
1
1
  module Tap
2
2
  module Support
3
+
4
+ # Represents a configuration declared by a Configurable class.
3
5
  class Configuration
4
6
  class << self
5
- SHORT_REGEXP = /^-[A-z]$/
7
+
8
+ # Matches a short option
9
+ SHORT_OPTION = /^-[A-z]$/
6
10
 
7
11
  # Turns the input string into a short-format option. Raises
8
12
  # an error if the option does not match SHORT_REGEXP.
@@ -13,11 +17,12 @@ module Tap
13
17
  def shortify(str)
14
18
  str = str.to_s
15
19
  str = "-#{str}" unless str[0] == ?-
16
- raise "invalid short option: #{str}" unless str =~ SHORT_REGEXP
20
+ raise "invalid short option: #{str}" unless str =~ SHORT_OPTION
17
21
  str
18
22
  end
19
-
20
- LONG_REGEXP = /^--(\[no-\])?([A-z][\w-]*)$/
23
+
24
+ # Matches a long option
25
+ LONG_OPTION = /^--(\[no-\])?([A-z][\w-]*)$/
21
26
 
22
27
  # Turns the input string into a long-format option. Raises
23
28
  # an error if the option does not match LONG_REGEXP.
@@ -33,7 +38,7 @@ module Tap
33
38
  str = "--#{str}" unless str.index("--")
34
39
  str.gsub!(/_/, '-') if hyphenize
35
40
 
36
- raise "invalid long option: #{str}" unless str =~ LONG_REGEXP
41
+ raise "invalid long option: #{str}" unless str =~ LONG_OPTION
37
42
 
38
43
  if switch_notation && $1.nil?
39
44
  str = "--[no-]#{$2}"
@@ -43,12 +48,24 @@ module Tap
43
48
  end
44
49
  end
45
50
 
51
+ # The name of the configuration
46
52
  attr_reader :name
53
+
54
+ # The reader method, by default name
47
55
  attr_reader :reader
56
+
57
+ # The writer method, by default name=
48
58
  attr_reader :writer
59
+
60
+ # True if the default value may be duplicated
49
61
  attr_reader :duplicable
62
+
63
+ # An array of optional metadata for self
50
64
  attr_reader :attributes
51
-
65
+
66
+ # Initializes a new Configuration with the specified name and default
67
+ # value. Options may specify an alternate reader/writer; any
68
+ # additional options are set as attributes.
52
69
  def initialize(name, default=nil, options={})
53
70
  @name = name
54
71
  self.default = default
@@ -59,11 +76,12 @@ module Tap
59
76
  end
60
77
 
61
78
  # Sets the default value for self and determines if the
62
- # default is duplicable (ie not nil, true, false, Symbol,
63
- # Numeric, and responds_to?(:dup)).
79
+ # default is duplicable. Non-duplicable values include
80
+ # nil, true, false, Symbol, Numeric, and any object that
81
+ # does not respond to dup.
64
82
  def default=(value)
65
83
  @duplicable = case value
66
- when nil, true, false, Symbol, Numeric then false
84
+ when nil, true, false, Symbol, Numeric, Method then false
67
85
  else value.respond_to?(:dup)
68
86
  end
69
87
 
@@ -88,22 +106,29 @@ module Tap
88
106
  @writer = value == nil ? value : value.to_sym
89
107
  end
90
108
 
109
+ # The argument name for self: either attributes[:arg_name]
110
+ # or name.to_s.upcase
91
111
  def arg_name
92
112
  attributes[:arg_name] || name.to_s.upcase
93
113
  end
94
114
 
115
+ # The argument type for self: either attributes[:arg_type]
116
+ # or :mandatory
95
117
  def arg_type
96
118
  attributes[:arg_type] || :mandatory
97
119
  end
98
120
 
121
+ # The long version of name.
99
122
  def long(switch_notation=false, hyphenize=true)
100
123
  Configuration.longify(attributes[:long] || name.to_s, switch_notation, hyphenize)
101
124
  end
102
125
 
126
+ # The short version of name.
103
127
  def short
104
128
  attributes[:short] ? Configuration.shortify(attributes[:short]) : nil
105
129
  end
106
130
 
131
+ # The description for self: attributes[:desc]
107
132
  def desc
108
133
  attributes[:desc]
109
134
  end
@@ -119,6 +144,27 @@ module Tap
119
144
  self.default(false) == another.default(false)
120
145
  end
121
146
 
147
+ # Returns self as an argv that can be used to register
148
+ # an option with OptionParser.
149
+ def to_optparse_argv
150
+ argtype = case arg_type
151
+ when :optional
152
+ "#{long} [#{arg_name}]"
153
+ when :switch
154
+ long(true)
155
+ when :flag
156
+ long
157
+ when :list
158
+ "#{long} a,b,c"
159
+ when :mandatory, nil
160
+ "#{long} #{arg_name}"
161
+ else
162
+ raise "unknown arg_type: #{arg_type}"
163
+ end
164
+
165
+ [short, argtype, desc].compact
166
+ end
167
+
122
168
  end
123
169
  end
124
170
  end
@@ -1,18 +1,79 @@
1
- require 'tap/support/constant_utils'
2
- class String # :nodoc:
3
- include Tap::Support::ConstantUtils
4
- end
1
+ require 'tap/support/string_ext'
5
2
 
6
3
  module Tap
7
4
  module Support
5
+
6
+ # A Constant serves as a placeholder for an actual constant, sort of like
7
+ # autoload. Use the constantize method to retrieve the actual constant;
8
+ # if it doesn't exist, constantize requires require_path and tries again.
9
+ #
10
+ # Object.const_defined?(:Net) # => false
11
+ # $".include?('net/http') # => false
12
+ #
13
+ # http = Constant.new('Net::HTTP', 'net/http')
14
+ # http.constantize # => Net::HTTP
15
+ # $".include?('net/http') # => true
16
+ #
8
17
  class Constant
18
+ class << self
19
+
20
+ # Tries to find a declared constant under base with the specified
21
+ # const_name. When a constant is missing, constantize yields
22
+ # the current base and any non-existant constant names the block,
23
+ # if given, or raises a NameError. The block is expected
24
+ # to return the proper constant.
25
+ #
26
+ # module ConstName; end
27
+ #
28
+ # Constant.constantize('ConstName') # => ConstName
29
+ # Constant.constantize('Non::Existant') { ConstName } # => ConstName
30
+ #
31
+ def constantize(const_name, base=Object) # :yields: base, missing_const_names
32
+ unless /\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/ =~ const_name
33
+ raise NameError, "#{const_name.inspect} is not a valid constant name!"
34
+ end
35
+
36
+ constants = $1.split(/::/)
37
+ while !constants.empty?
38
+ unless const_is_defined?(base, constants[0])
39
+ if block_given?
40
+ return yield(base, constants)
41
+ else
42
+ raise NameError.new("uninitialized constant #{const_name}", constants[0])
43
+ end
44
+ end
45
+ base = base.const_get(constants.shift)
46
+ end
47
+ base
48
+ end
49
+
50
+ private
51
+
52
+ # helper method. Determines if a constant named
53
+ # name is defined in const. The implementation
54
+ # (annoyingly) has to be different for ruby 1.9
55
+ # due to changes in the API.
56
+ case RUBY_VERSION
57
+ when /^1.9/
58
+ def const_is_defined?(const, name) # :nodoc:
59
+ const.const_defined?(name, false)
60
+ end
61
+ else
62
+ def const_is_defined?(const, name) # :nodoc:
63
+ const.const_defined?(name)
64
+ end
65
+ end
66
+ end
9
67
 
10
- # The camelized name for self.
68
+ # The constant name
11
69
  attr_reader :name
12
70
 
13
- # The path to load to initialize the constant name.
71
+ # The path to load to initialize a missing constant
14
72
  attr_reader :require_path
15
-
73
+
74
+ # Initializes a new Constant with the specified constant
75
+ # name and require_path. The name should be a valid
76
+ # constant name.
16
77
  def initialize(name, require_path=nil)
17
78
  @name = name
18
79
  @require_path = require_path
@@ -48,23 +109,36 @@ module Tap
48
109
  @nesting_depth ||= nesting.split(/::/).length
49
110
  end
50
111
 
51
- # Returns the document for require_path, if set, or nil otherwise.
112
+ # Returns the Lazydoc document for require_path.
52
113
  def document
53
- require_path ? Support::Lazydoc[require_path] : nil
114
+ require_path ? Lazydoc[require_path] : nil
54
115
  end
55
116
 
117
+ # True if another is a Constant with the same name
118
+ # and require_path as self.
56
119
  def ==(another)
57
120
  another.kind_of?(Constant) &&
58
121
  another.name == self.name &&
59
122
  another.require_path == self.require_path
60
123
  end
61
-
124
+
125
+ # Looks up and returns the constant indicated by name.
126
+ # If the constant cannot be found, the constantize
127
+ # requires require_path and tries again.
128
+ #
129
+ # Raises a NameError if the constant cannot be found.
62
130
  def constantize
63
- name.try_constantize do |const_name|
64
- require require_path
65
- name.constantize
131
+ Constant.constantize(name) do
132
+ require require_path if require_path
133
+ Constant.constantize(name)
66
134
  end
67
135
  end
136
+
137
+ # Returns a string like:
138
+ # "#<Tap::Support::Constant:object_id Const::Name (require_path)>"
139
+ def inspect
140
+ "#<#{self.class}:#{object_id} #{name}#{@require_path == nil ? "" : " (#{@require_path})"}>"
141
+ end
68
142
  end
69
143
  end
70
144
  end
@@ -0,0 +1,116 @@
1
+ require 'tap/support/manifest'
2
+ require 'tap/support/constant'
3
+
4
+ module Tap
5
+ module Support
6
+
7
+ # ConstantManifest builds a manifest of Constant entries using Lazydoc.
8
+ # The idea is that Lazydoc can find files with resouces of a specific type
9
+ # (ex tasks) and Constant can reference those resouces and load them as
10
+ # necessary. ConstantManifest registers paths so that they may be lazily
11
+ # scanned when searching for a specific resource.
12
+ class ConstantManifest < Support::Manifest
13
+
14
+ # The attribute identifying resources in a file
15
+ attr_reader :const_attr
16
+
17
+ # Registered [root, [paths]] pairs that will be searched
18
+ # for the const_attr
19
+ attr_reader :search_paths
20
+
21
+ # The current index of search_paths
22
+ attr_reader :search_path_index
23
+
24
+ # The current index of paths
25
+ attr_reader :path_index
26
+
27
+ # Initializes a new ConstantManifest
28
+ def initialize(const_attr)
29
+ @const_attr = const_attr
30
+ @search_paths = []
31
+ @search_path_index = 0
32
+ @path_index = 0
33
+ super([])
34
+ end
35
+
36
+ # Registers the files matching pattern under dir. Returns self.
37
+ def register(dir, pattern)
38
+ paths = Dir.glob(File.join(dir, pattern)).select {|file| File.file?(file) }
39
+ search_paths << [dir, paths.sort]
40
+ self
41
+ end
42
+
43
+ # Searches all paths for entries and adds them to self. Returns self.
44
+ def build
45
+ each {|entry| } unless built?
46
+ self
47
+ end
48
+
49
+ # True if there are no more paths to search
50
+ # (ie search_path_index == search_paths.length)
51
+ def built?
52
+ search_path_index == search_paths.length
53
+ end
54
+
55
+ # Sets search_path_index and path_index to zero and clears entries.
56
+ # Returns self.
57
+ def reset
58
+ # Support::Lazydoc[path].resolved = false
59
+ @entries.clear
60
+ @search_path_index = 0
61
+ @path_index = 0
62
+ super
63
+ end
64
+
65
+ # Yields each entry to the block. Unless built?, each lazily
66
+ # iterates over search_paths to look for new entries.
67
+ def each
68
+ entries.each do |entry|
69
+ yield(entry)
70
+ end
71
+
72
+ search_paths[search_path_index, search_paths.length - search_path_index].each do |(path_root, paths)|
73
+ paths[path_index, paths.length - path_index].each do |path|
74
+ new_entries = resolve(path_root, path) - entries
75
+ entries.concat(new_entries)
76
+
77
+ @path_index += 1
78
+ new_entries.each {|entry| yield(entry) }
79
+ end
80
+
81
+ @search_path_index += 1
82
+ @path_index = 0
83
+ end unless built?
84
+ end
85
+
86
+ protected
87
+
88
+ def minikey(const) # :nodoc:
89
+ const.path
90
+ end
91
+
92
+ # Scans path for constants having const_attr, and initializes Constant
93
+ # objects for each. If the document has no default_const_name set,
94
+ # resolve will set the default_const_name based on the relative
95
+ # filepath from path_root to path.
96
+ def resolve(path_root, path)
97
+ entries = []
98
+ if document = Lazydoc.scan_doc(path, const_attr)
99
+ if document.default_const_name.empty?
100
+ relative_path = Root.relative_filepath(path_root, path).chomp(File.extname(path))
101
+ document.default_const_name = relative_path.camelize
102
+ end
103
+
104
+ document.const_attrs.each_pair do |const_name, attrs|
105
+ if attrs.has_key?(const_attr)
106
+ entries << Constant.new(const_name, path)
107
+ end
108
+ end
109
+ end
110
+
111
+ entries
112
+ end
113
+
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,54 @@
1
+ require 'tap/support/dependency'
2
+
3
+ module Tap
4
+ module Support
5
+
6
+ # Dependencies tracks Executable dependencies and results, and provides
7
+ # for thread-safe resolution of dependencies.
8
+ class Dependencies < Monitor
9
+
10
+ # Initializes a new Dependencies
11
+ def initialize
12
+ super
13
+ @resolve_stack = []
14
+ end
15
+
16
+ # Thread-safe registration of instance as a dependency. During
17
+ # registration, instance is extended with the Dependency module.
18
+ # Returns self.
19
+ def register(instance)
20
+ synchronize do
21
+ unless instance.kind_of?(Dependency)
22
+ instance.extend Dependency
23
+ end
24
+ end
25
+ self
26
+ end
27
+
28
+ # Thread-safe resolution of the instance. Resolve checks for
29
+ # circular dependencies, then yields control to the block,
30
+ # which is responsible for the actual resolution.
31
+ def resolve(instance)
32
+ synchronize do
33
+ if @resolve_stack.include?(instance)
34
+ raise CircularDependencyError.new(@resolve_stack)
35
+ end
36
+
37
+ # mark the results at the index to prevent
38
+ # infinite loops with circular dependencies
39
+ @resolve_stack.push instance
40
+ yield()
41
+ @resolve_stack.pop
42
+ end
43
+ self
44
+ end
45
+
46
+ # Raised when Dependencies#resolve detects a circular dependency.
47
+ class CircularDependencyError < StandardError
48
+ def initialize(resolve_stack)
49
+ super "circular dependency: [#{resolve_stack.join(', ')}]"
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,44 @@
1
+ module Tap
2
+ module Support
3
+
4
+ # Constrains an Executable to only _execute once, and provides several
5
+ # methods making the Executable behave like a Dependency.
6
+ module Dependency
7
+
8
+ # The audited result of self
9
+ attr_accessor :_result
10
+
11
+ def self.extended(base) # :nodoc:
12
+ base.instance_variable_set(:@_result, nil)
13
+ end
14
+
15
+ # Conditional _execute; only calls _method_name if
16
+ # resolved? is false (thus assuring self will only
17
+ # be executed once).
18
+ #
19
+ # Returns _result.
20
+ def _execute(*args)
21
+ app.dependencies.resolve(self) do
22
+ @_result = super
23
+ end unless resolved?
24
+ _result
25
+ end
26
+
27
+ # Alias for _execute().
28
+ def resolve
29
+ _execute
30
+ end
31
+
32
+ # True if _result is non-nil.
33
+ def resolved?
34
+ @_result != nil
35
+ end
36
+
37
+ # Resets the dependency by setting _result to nil.
38
+ def reset
39
+ @_result = nil
40
+ end
41
+
42
+ end
43
+ end
44
+ end