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
data/lib/tap/constants.rb CHANGED
@@ -1,35 +1,8 @@
1
1
  module Tap
2
2
  MAJOR = 0
3
- MINOR = 9
4
- TINY = 1
3
+ MINOR = 10
4
+ TINY = 0
5
5
 
6
6
  VERSION="#{MAJOR}.#{MINOR}.#{TINY}"
7
7
  WEBSITE="http://tap.rubyforge.org"
8
-
9
- # Under Construction
10
- module Constants
11
- def try_constantize
12
- begin
13
- constantize
14
- rescue(NameError)
15
- yield
16
- end
17
- end
18
-
19
- def constants_split
20
- camel_cased_word = camelize
21
- unless /\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/ =~ camel_cased_word
22
- raise NameError, "#{camel_cased_word.inspect} is not a valid constant name!"
23
- end
24
-
25
- constants = $1.split(/::/)
26
- current = Object
27
- while !constants.empty?
28
- break unless current.const_defined?(constants[0])
29
- current = current.const_get(constants.shift)
30
- end
31
-
32
- [current, constants]
33
- end
34
- end
35
8
  end
data/lib/tap/env.rb CHANGED
@@ -1,213 +1,386 @@
1
1
  require 'tap/root'
2
- require 'singleton'
3
- autoload(:PP, "pp")
2
+ require 'tap/support/constant'
3
+ require 'tap/support/summary'
4
+ require 'tap/support/manifest'
4
5
 
5
6
  module Tap
6
7
 
7
- # == Under Construction
8
- #
9
- # Env manages configuration of the Tap execution environment, including the
10
- # specification of gems that should be available through the tap command.
8
+ #--
9
+ # Note that gems and env_paths reset envs -- custom modifications to envs will be lost
10
+ # whenever these configs are reset.
11
11
  class Env
12
+ include Support::Configurable
13
+ include Enumerable
12
14
 
13
- # A variety of configuration loading/handling methods for use in
14
- # conjuction with Tap::Env, to aid in configuring the running
15
- # environment for Tap.
16
- module Configuration
17
- module_function
18
-
19
- # Templates the input filepath using ERB then loads it as YAML.
20
- # Returns an empty hash if the file doesn't exist, or loads to
21
- # nil or false (as for an empty file). Raises an error if the
22
- # filepath doesn't load to a hash.
23
- def read_config(filepath)
24
- return {} if !File.exists?(filepath) || File.directory?(filepath)
15
+ @@instance = nil
16
+ @@instances = {}
17
+ @@manifests = {}
18
+
19
+ class << self
20
+ # Returns the active instance of Env.
21
+ def instance
22
+ @@instance
23
+ end
24
+
25
+ # A hash of (path, Env instance) pairs, generated by Env#instantiate. Used
26
+ # to prevent infinite loops of Env dependencies by assigning a single Env
27
+ # to a given path.
28
+ def instances
29
+ @@instances
30
+ end
31
+
32
+ # Creates a new Env for the specified path and adds it to Env#instances, or
33
+ # returns the existing instance for the path. Paths can point to an env config
34
+ # file, or to a directory. If a directory is provided, instantiate treats
35
+ # path as the DEFAULT_CONFIG_FILE in that directory. All paths are expanded.
36
+ #
37
+ # e1 = Env.instantiate("./path/to/config.yml")
38
+ # e2 = Env.instantiate("./path/to/dir")
39
+ #
40
+ # Env.instances
41
+ # # => {
42
+ # # File.expand_path("./path/to/config.yml") => e1,
43
+ # # File.expand_path("./path/to/dir/#{Tap::Env::DEFAULT_CONFIG_FILE}") => e2 }
44
+ #
45
+ # The Env is initialized using configurations read from the env config file using
46
+ # load_config, and a Root initialized to the config file directory. An instance
47
+ # will be initialized regardless of whether the config file or directory exists.
48
+ def instantiate(path_or_root, default_config={}, logger=nil)
49
+ path = path_or_root.kind_of?(Root) ? path_or_root.root : path_or_root
50
+ path = pathify(path)
25
51
 
26
- input = ERB.new(File.read(filepath)).result
27
- config = YAML.load(input)
52
+ begin
53
+ root = path_or_root.kind_of?(Root) ? path_or_root : Root.new(File.dirname(path))
54
+ config = default_config.merge(load_config(path))
55
+
56
+ # note the assignment of env to instances MUST occur before
57
+ # reconfigure to prevent infinite looping
58
+ (instances[path] = Env.new({}, root, logger)).reconfigure(config) do |unhandled_configs|
59
+ yield(unhandled_configs) if block_given?
60
+ end
61
+ rescue(Exception)
62
+ raise Env::ConfigError.new($!, path)
63
+ end
64
+ end
28
65
 
29
- case config
30
- when Hash then config
31
- when nil, false then {}
32
- else
33
- raise "expected hash from config file: #{filepath}"
66
+ def pathify(path)
67
+ if File.directory?(path) || (!File.exists?(path) && File.extname(path) == "")
68
+ path = File.join(path, DEFAULT_CONFIG_FILE)
34
69
  end
70
+ File.expand_path(path)
35
71
  end
36
72
 
37
- # Partitions a configuration hash into environment, execution,
38
- # and application configurations, as determined by ENV_CONFIG_KEYS
39
- # and EXE_CONFIG_KEYS. All non-env, non-exe configurations are
40
- # considered application configurations.
41
- def partition_configs(hash, *sets)
42
- partitions = Array.new(sets.length + 1) { Hash.new }
73
+ def instance_for(path)
74
+ path = pathify(path)
75
+ instances.has_key?(path) ? instances[path] : instantiate(path)
76
+ end
43
77
 
44
- hash.each_pair do |key, value|
45
- index = 0
46
- sets.each do |keys|
47
- break if keys.include?(key)
48
- index += 1
49
- end
50
-
51
- partitions[index][key] = value
78
+ # Returns the gemspec for the specified gem. A gem version
79
+ # can be specified in the name, like 'gem >= 1.2'. The gem
80
+ # will be activated using +gem+ if necessary.
81
+ def gemspec(gem_name)
82
+ return gem_name if gem_name.kind_of?(Gem::Specification)
83
+
84
+ # figure the version of the gem, by default >= 0.0.0
85
+ gem_name.to_s =~ /^([^<=>]*)(.*)$/
86
+ name, version = $1.strip, $2
87
+ version = ">= 0.0.0" if version.empty?
88
+
89
+ return nil if name.empty?
90
+
91
+ # load the gem and get the spec
92
+ gem(name, version)
93
+ Gem.loaded_specs[name]
94
+ end
95
+
96
+ # Returns the gem name for all installed gems with a DEFAULT_CONFIG_FILE.
97
+ # If latest==true, then only the names for the most current gem specs
98
+ # will be returned.
99
+ def known_gems(latest=true)
100
+ index = latest ?
101
+ Gem.source_index.latest_specs :
102
+ Gem.source_index.gems.collect {|(name, spec)| spec }
103
+
104
+ index.select do |spec|
105
+ File.exists?(File.join(spec.full_gem_path, DEFAULT_CONFIG_FILE)) ||
106
+ File.exists?(File.join(spec.full_gem_path, DEFAULT_TASK_FILE))
107
+ end.sort
108
+ end
109
+
110
+ protected
111
+
112
+ # Defines a config that raises an error if set when the
113
+ # instance is active. static_config MUST take a block
114
+ # and raises an error if a block is not given.
115
+ def static_config(key, value=nil, &block)
116
+ raise ArgumentError.new("active config requires block") unless block_given?
117
+
118
+ instance_variable = "@#{key}".to_sym
119
+ config_attr(key, value) do |input|
120
+ check_configurable
121
+ instance_variable_set(instance_variable, block.call(input))
122
+ end
123
+ end
124
+
125
+ # Defines a config that collects the input into a unique,
126
+ # compact array where each member has been resolved using
127
+ # root[]. In short, ['lib', nil, 'lib', 'alt] becomes
128
+ # [root['lib'], root['alt']].
129
+ #
130
+ # Single and nil arguments are allowed; they are arrayified
131
+ # and handled as above. Path configs raise an error if
132
+ # modified when the instance is active.
133
+ def path_config(key, value=[])
134
+ instance_variable = "@#{key}".to_sym
135
+ config_attr(key, value) do |input|
136
+ check_configurable
137
+ instance_variable_set(instance_variable, [*input].compact.collect {|path| root[path]}.uniq)
52
138
  end
139
+ end
140
+
141
+ #--
142
+ # To manifest simply requires an glob_<name> method which
143
+ # yields each (key, path) pair for the manifested object in
144
+ # a predictable order.
145
+ #
146
+ #--
147
+ # Alternate implementation would create the manifest for each individual
148
+ # env, then merge the manifests. On the plus side, each env would then
149
+ # carry it's own slice of the manifest without having to recalculate.
150
+ # On the down side, the merging would have to occur in some separate
151
+ # method that cannot be defined here.
152
+ def manifest(name, paths_key, pattern, &block)
153
+ return manifest(name, paths_key, pattern) do |context, path|
154
+ [[path.chomp(File.extname(path)), path]]
155
+ end unless block_given?
53
156
 
54
- partitions
55
- end
56
-
57
- # Joins the input configuration hashes, concatenating
58
- # values for matching keys. Values will be made into
59
- # arrays if they are not so already; duplicate values
60
- # are removed from the result on a key-per-key basis.
61
- def join_configs(*configs)
62
- merge = {}
63
- configs.each do |hash|
64
- hash.each_pair do |key, values|
65
- values = [values] unless values.kind_of?(Array)
66
- (merge[key] ||= []).concat(values)
157
+ glob_method = Support::Manifest.glob_method(name)
158
+ module_eval %Q{
159
+ def #{glob_method}
160
+ paths = []
161
+ self.#{paths_key}.each do |manifest_path|
162
+ root.glob(manifest_path, "#{pattern}").each do |path|
163
+ next if File.directory?(path)
164
+ paths << [manifest_path, path]
165
+ end
166
+ end
167
+ paths.sort_by {|mp, p| File.basename(p)}
67
168
  end
68
- end
69
- merge.values.each {|values| values.uniq! }
70
- merge
169
+ }
170
+
171
+ map_method = Support::Manifest.map_method(name)
172
+ define_method(map_method, &block)
173
+
174
+ protected glob_method, map_method
71
175
  end
72
176
  end
73
177
 
74
- include Configuration
75
- include Singleton
178
+ # The global config file path
179
+ GLOBAL_CONFIG_FILE = File.join(Gem.user_home, ".tap.yml")
76
180
 
181
+ # The default config file path
77
182
  DEFAULT_CONFIG_FILE = "tap.yml"
78
183
 
79
- # Currently these are ALWAYS included.
80
- DEFAULT_CONFIG = {
81
- "load_paths" => ["lib"],
82
- "load_once_paths" => [],
83
- "config_paths" => [],
84
- "command_paths" => ["cmd"],
85
- "gems" => [],
86
- "generator_paths" => ["lib/generators"]
87
- }
88
-
89
- attr_reader :config
184
+ # The default task file path
185
+ DEFAULT_TASK_FILE = "tapfile.rb"
186
+
187
+ # The Root directory structure for self.
188
+ attr_reader :root
189
+
190
+ # Gets or sets the logger for self
90
191
  attr_accessor :logger
192
+
193
+ # A hash of the manifests for self.
194
+ attr_reader :manifests
195
+
196
+ # Specify gems to load as nested Envs. Gems may be specified
197
+ # by name and/or version, like 'gemname >= 1.2'; by default the
198
+ # latest version of the gem is selected.
199
+ #
200
+ # Gems are immediately loaded (via gem) through this method.
201
+ #--
202
+ # Note that the gems are resolved to gemspecs using Env.gemspec,
203
+ # so self.gems returns an array of gemspecs.
204
+ config_attr :gems, [] do |input|
205
+ check_configurable
206
+ @gems = [*input].compact.collect do |gem_name|
207
+ spec = Env.gemspec(gem_name)
208
+
209
+ case spec
210
+ when nil then log(:warn, "unknown gem: #{gem_name}", Logger::WARN)
211
+ else Env.instance_for(spec.full_gem_path)
212
+ end
213
+
214
+ spec
215
+ end.uniq
216
+ reset_envs
217
+ end
91
218
 
92
- def initialize
93
- @config = nil
94
- @logger = nil
95
- reset
219
+ # Specify configuration files to load as nested Envs.
220
+ config_attr :env_paths, [] do |input|
221
+ check_configurable
222
+ @env_paths = [*input].compact.collect do |path|
223
+ Env.instance_for(root[path]).env_path
224
+ end.uniq
225
+ reset_envs
96
226
  end
227
+
228
+ # Designate load paths. If use_dependencies == true, then
229
+ # load_paths will be used for automatic loading of modules
230
+ # through the active_support Dependencies module.
231
+ path_config :load_paths, ["lib"]
97
232
 
98
- def debug_setup
99
- $DEBUG = true
100
- logger.level = Logger::DEBUG
101
- end
233
+ # Designate paths for discovering and executing commands.
234
+ path_config :command_paths, ["cmd"]
235
+
236
+ # Designate paths for discovering generators.
237
+ path_config :generator_paths, ["lib"]
102
238
 
103
- def rails_setup(app=Tap::App.instance)
104
- Object.const_set('RAILS_ROOT', app.root)
105
- Object.const_set('RAILS_DEFAULT_LOGGER', app.logger)
106
- Dependencies.log_activity = app.debug?
239
+ manifest(:tasks, :load_paths, "**/*.rb") do |load_path, path|
240
+ next unless document = Support::Lazydoc.scan_doc(path, 'manifest')
241
+
242
+ document.const_names.collect do |const_name|
243
+ if const_name.empty?
244
+ key = root.relative_filepath(load_path, path).chomp('.rb')
245
+ [key, Support::Constant.new(key.camelize, path)]
246
+ else
247
+ [const_name.underscore, Support::Constant.new(const_name, path)]
248
+ end
249
+ end
107
250
  end
108
251
 
109
- def rake_setup(argv=ARGV, app=Tap::App.instance)
110
- Tap::Support.autoload(:Rake, 'tap/support/rake')
111
-
112
- # setup
113
- app.extend Tap::Support::Rake
114
- rake = Rake.application
115
- options = rake.options
252
+ manifest(:commands, :command_paths, "**/*.rb")
116
253
 
117
- # merge options down from app
118
- app.options.marshal_dump.each_pair do |key, value|
119
- options.send("#{key}=", value)
254
+ manifest(:generators, :generator_paths, '**/*_generator.rb') do |load_path, path|
255
+ dirname = File.dirname(path)
256
+ next unless "#{File.basename(dirname)}_generator.rb" == File.basename(path)
257
+
258
+ next unless document = Support::Lazydoc.scan_doc(path, 'generator')
259
+ document.const_names.collect do |const_name|
260
+ if const_name.empty?
261
+ key = root.relative_filepath(load_path, dirname)
262
+ [key, Support::Constant.new((key + '_generator').camelize, path)]
263
+ else
264
+ [const_name.underscore, Support::Constant.new(const_name, path)]
265
+ end
120
266
  end
121
- options.silent = true
267
+ end
122
268
 
123
- # run as if from command line using argv
124
- current_argv = ARGV.dup
125
- begin
126
- ARGV.concat(argv)
127
-
128
- # now follow the same protocol as
129
- # in run, handling options
130
- rake.init
131
- rake.load_rakefile
132
- ensure
133
- ARGV.clear
134
- ARGV.concat(current_argv)
135
- end
269
+ def initialize(config={}, root=Tap::Root.new, logger=nil)
270
+ @root = root
271
+ @logger = logger
272
+ @envs = []
273
+ @active = false
274
+ @manifests = {}
275
+ @manifested = []
276
+
277
+ # initialize these for reset_env
278
+ @gems = []
279
+ @env_paths = []
136
280
 
137
- rake
281
+ initialize_config(config)
138
282
  end
139
283
 
140
- # Resets Env. Load paths (load_paths and load_once_paths) are
141
- # not reset unless dependencies==true; in which case Dependencies
142
- # are cleared before load paths are cleared. The load paths added
143
- # to $LOAD_PATH are not cleared.
284
+ # Sets envs removing duplicates and instances of self.
285
+ def envs=(envs)
286
+ @envs = envs.uniq.delete_if {|e| e == self }
287
+ @envs.freeze
288
+ @flat_envs = nil
289
+ end
290
+
291
+ # An array of nested Envs, by default comprised of the
292
+ # env_path + gem environments (in that order). These
293
+ # nested Envs are activated/deactivated with self.
144
294
  #
145
- # Generally not recommended.
146
- def reset
147
- unless @config == nil
148
- $LOAD_PATH.delete_if {|path| config['load_paths'].include?(path) }
149
-
150
- Dependencies.clear
151
- Dependencies.load_paths.delete_if {|path| config['load_paths'].include?(path) }
152
- Dependencies.load_once_paths.delete_if {|path| config['load_once_paths'].include?(path) }
295
+ # Returns a flattened array of the unique nested envs
296
+ # when flat == true.
297
+ def envs(flat=false)
298
+ flat ? (@flat_envs ||= self.flatten_envs.freeze) : @envs
299
+ end
300
+
301
+ # Unshifts env onto envs, removing duplicates.
302
+ # Self cannot be unshifted onto self.
303
+ def unshift(env)
304
+ unless env == self || envs[0] == env
305
+ self.envs = envs.dup.unshift(env)
153
306
  end
154
-
155
- @config = {}
156
- DEFAULT_CONFIG.keys.each do |key|
157
- @config[key] = []
307
+ envs
308
+ end
309
+
310
+ # Pushes env onto envs, removing duplicates.
311
+ # Self cannot be pushed onto self.
312
+ def push(env)
313
+ unless env == self || envs[-1] == env
314
+ envs = self.envs.reject {|e| e == env }
315
+ self.envs = envs.push(env)
158
316
  end
317
+ envs
159
318
  end
160
-
161
- # Logs the action and message at the input level (default INFO).
162
- # Logging is suppressed if no logger is set.
163
- def log(action, msg="", level=Logger::INFO)
164
- logger.add(level, msg, action.to_s) if logger
319
+
320
+ # Passes each nested env to the block in order, starting with self.
321
+ def each
322
+ envs(true).each {|e| yield(e) }
165
323
  end
166
324
 
167
- # Configures the specified App using the configurations in config_file.
168
- # Loading of environement configurations occcurs via load_env_config;
169
- # all environment paths are resolved using the app, after the app has
170
- # been configured.
325
+ # Passes each nested env to the block in reverse order, ending with self.
326
+ def reverse_each
327
+ envs(true).reverse_each {|e| yield(e) }
328
+ end
171
329
 
172
- # Loads environment configurations from the specified path. If a directory
173
- # is given as path, then the DEFAULT_CONFIG_FILE relative to that location
174
- # will be loaded. The loading cycle recurses as specified by the configurations.
330
+ # Returns the total number of unique envs nested in self (including self).
331
+ def count
332
+ envs(true).length
333
+ end
334
+
335
+ # Returns a list of arrays that receive load_paths on activate,
336
+ # by default [$LOAD_PATH]. If use_dependencies == true, then
337
+ # Dependencies.load_paths will also be included.
338
+ def load_path_targets
339
+ [$LOAD_PATH]
340
+ end
341
+
342
+ # Processes and resets the input configurations for both root
343
+ # and self. Reconfiguration consists of the following steps:
175
344
  #
176
- # Configuration paths are expanded relative to the parent directory
177
- # of the loaded file. Raises an error if non-env configuration are
178
- # found (as determined by Tap::Env::Configurtion::ENV_CONFIG_KEYS).
179
- def load_config(path, root=Tap::Root.new, &block) # :yields: non_env_configs
180
- path = File.join(path, DEFAULT_CONFIG_FILE) if File.directory?(path)
181
- path = File.expand_path(path)
182
-
183
- # prevent infinite looping
184
- config_paths = config['config_paths']
185
- return false if config_paths.include?(path)
186
-
187
- # load config
188
- log(:load_config, path, Logger::DEBUG)
189
- config_paths << path
345
+ # * partition overrides into env, root, and other configs
346
+ # * reconfigure root with the root configs
347
+ # * reconfigure self with the env configs
348
+ # * yield other configs to the block (if given)
349
+ #
350
+ # Reconfigure will always yields to the block, even if there
351
+ # are no non-root, non-env configurations. Unspecified
352
+ # configurations are NOT reconfigured. (Note this means
353
+ # that existing path configurations like load_paths will
354
+ # not automatically be reset using reconfigured root.)
355
+ def reconfigure(overrides={})
356
+ check_configurable
190
357
 
191
- config = read_config(path)
192
- config['root'] = File.dirname(path) unless config['root']
358
+ # partiton config into its parts
359
+ env_configs = {}
360
+ root_configs = {}
361
+ other_configs = {}
193
362
 
194
- configure(config, root, &block)
195
- end
363
+ env_configurations = self.class.configurations
364
+ root_configurations = root.class.configurations
365
+ overrides.each_pair do |key, value|
366
+ key = key.to_sym
196
367
 
197
- #--
198
- # Note: always yields to the block, even if non_env_configs is empty
199
- def configure(config, root=Tap::Root.new, &block) # :yields: non_env_configs
200
- root_configs, env_configs, other_configs = partition_configs(config, ['root', 'directories', 'absolute_paths'], DEFAULT_CONFIG.keys)
201
- env_configs = join_configs(DEFAULT_CONFIG, env_configs)
202
-
203
- # assign root configs
204
- root.send(:assign_paths,
205
- root_configs['root'] || root.root,
206
- root_configs['directories'] || root.directories,
207
- root_configs['absolute_paths'] || root.absolute_paths)
208
-
209
- # handle unknown configs (handle before setting
210
- # env configs in case the configs modify root)
368
+ partition = case
369
+ when env_configurations.key?(key) then env_configs
370
+ when root_configurations.key?(key) then root_configs
371
+ else other_configs
372
+ end
373
+
374
+ partition[key] = value
375
+ end
376
+
377
+ # reconfigure root so it can resolve path_configs
378
+ root.reconfigure(root_configs)
379
+
380
+ # reconfigure self
381
+ super(env_configs)
382
+
383
+ # handle other configs
211
384
  case
212
385
  when block_given?
213
386
  yield(other_configs)
@@ -215,121 +388,253 @@ module Tap
215
388
  log(:warn, "ignoring non-env configs: #{other_configs.keys.join(',')}", Logger::DEBUG)
216
389
  end
217
390
 
218
- # load gems and configurations
219
- gem_paths = env_configs.delete('gems').collect do |gem_name|
220
- full_gem_path(gem_name)
221
- end
222
- config_paths = env_configs.delete('config_paths') + gem_paths
223
- config_paths.each {|path| load_config(root[path]) }
391
+ self
392
+ end
393
+
394
+ # Returns the path for self in Env.instances.
395
+ def env_path
396
+ Env.instances.each_pair {|path, env| return path if env == self }
397
+ nil
398
+ end
399
+
400
+ # Logs the action and message at the input level (default INFO).
401
+ # Logging is suppressed if no logger is set.
402
+ def log(action, msg="", level=Logger::INFO)
403
+ logger.add(level, msg, action.to_s) if logger
404
+ end
405
+
406
+ # Activates self by unshifting load_paths for self to the load_path_targets.
407
+ # Once active, self can be referenced from Env.instance and the current
408
+ # configurations are frozen. Env.instance is deactivated, if set, before
409
+ # self is activated. Returns true if activate succeeded, or false if self
410
+ # is already active.
411
+ def activate
412
+ return false if active?
224
413
 
225
- # assign env configs
226
- env_configs.each_pair do |key, value|
227
- case key
228
- when 'load_paths'
229
- assign_paths(root, value, self.config[key], $LOAD_PATH, Dependencies.load_paths)
230
- when 'load_once_paths'
231
- assign_paths(root, value, self.config[key], Dependencies.load_once_paths)
232
- when /_paths$/
233
- assign_paths(root, value, self.config[key])
234
- else
235
- handle_unknown_env_config(root, key, value)
414
+ @active = true
415
+ @@instance = self unless @@instance
416
+
417
+ # freeze array configs like load_paths
418
+ config.each_pair do |key, value|
419
+ case value
420
+ when Array then value.freeze
236
421
  end
237
422
  end
423
+
424
+ # activate nested envs
425
+ envs.reverse_each do |env|
426
+ env.activate
427
+ end
238
428
 
429
+ # add load paths to load_path_targets
430
+ load_path_targets.each do |target|
431
+ load_paths.reverse_each do |path|
432
+ target.unshift(path)
433
+ end
434
+
435
+ target.uniq!
436
+ end
437
+
239
438
  true
240
439
  end
241
440
 
242
- # Loads env configurations from a gem, specifically from
243
- # gemspec.full_gem_path. A gem version can be specified
244
- # in the name, like 'gem >= 1.2'.
245
- def full_gem_path(gem_name)
246
- # figure the version of the gem, by default >= 0.0.0
247
- gem_name =~ /^([^<=>]*)(.*)$/
248
- name, version = $1, $2
249
- version = ">= 0.0.0" if version.empty?
250
-
251
- # load the gem and get the spec
252
- gem(name, version)
253
- spec = Gem.loaded_specs[name]
441
+ # Deactivates self by clearing manifests and deleting load_paths for self
442
+ # from the load_path_targets. Env.instance will no longer reference self
443
+ # and the configurations are unfrozen (using duplication).
444
+ #
445
+ # Returns true if deactivate succeeded, or false if self is not active.
446
+ def deactivate
447
+ return false unless active?
448
+
449
+ # remove load paths from load_path_targets
450
+ load_path_targets.each do |target|
451
+ load_paths.each do |path|
452
+ target.delete(path)
453
+ end
454
+ end
254
455
 
255
- if spec == nil
256
- log(:warn, "unknown gem: #{gem_name}", Logger::WARN)
456
+ # unfreeze array configs by duplicating
457
+ self.config.class_config.each_pair do |key, value|
458
+ value = send(key)
459
+ case value
460
+ when Array then instance_variable_set("@#{key}", value.dup)
461
+ end
257
462
  end
258
463
 
259
- spec.full_gem_path
464
+ @active = false
465
+ @manifests.clear
466
+ @@instance = nil if @@instance == self
467
+
468
+ # dectivate nested envs
469
+ envs.reverse_each do |env|
470
+ env.deactivate
471
+ end
472
+
473
+ true
260
474
  end
261
475
 
262
- # Loads the config for the specified gem. A gem version can be
263
- # specified in the name, see full_gem_path.
264
- def load_gem(gem_name)
265
- load_config(full_gem_path(gem_name))
476
+ # Return true if self has been activated.
477
+ def active?
478
+ @active
266
479
  end
267
480
 
268
- # Returns the path to all DEFAULT_CONFIG_FILEs for installed gems.
269
- # If latest==true, then only the config files for the latest gem
270
- # specs will be returned (ie for the most current version of a
271
- # gem).
272
- def gem_config_files(latest=true)
273
- if latest
274
- Gem.source_index.latest_specs.collect do |spec|
275
- config_file = File.join(spec.full_gem_path, DEFAULT_CONFIG_FILE)
276
- File.exists?(config_file) ? config_file : nil
277
- end.compact
278
- else
279
- Gem.path.collect do |dir|
280
- Dir.glob( File.join(dir, "gems/*", DEFAULT_CONFIG_FILE) )
281
- end.flatten.uniq
282
- end
481
+ # Cycles through all items yielded by the iterate_<name> method and
482
+ # adds each to the manifests[name] hash. Freezes the hash when complete.
483
+ # Simply returns the manifests[name] hash if frozen.
484
+ def manifest(name)
485
+ manifest = manifests[name] ||= Support::Manifest.new(name, self)
486
+
487
+ manifest.entries.each do |key, path|
488
+ yield(key, path)
489
+ end if block_given?
490
+
491
+ manifest.each_path do |context, path|
492
+ next unless keys = send(manifest.map_method, context, path)
493
+
494
+ keys.each {|entry| manifest.store(entry) }
495
+ keys.each {|key, value| yield(key, value) } if block_given?
496
+ end unless manifest.complete?
497
+
498
+ manifest
283
499
  end
284
500
 
285
- # Loads the config files discovered by gem_config_files(true).
286
- def discover_gems
287
- gem_config_files.collect do |config_file|
288
- load_config(config_file)
501
+ def find(name, pattern)
502
+ manifest(name) do |key, path|
503
+ return path if Root.minimal_match?(key, pattern)
289
504
  end
505
+ nil
290
506
  end
291
507
 
292
- # Searches for and returns all .rb files under each of the command_paths
293
- # as well as the default tap commands. Commands with conflicting names
294
- # raise an error; however, user commands are allowed to override the
295
- # default tap commands and will NOT raise an error.
296
- def commands
297
- commands = {}
298
- config['command_paths'].each do |path|
299
- pattern = File.join(path, "**/*.rb")
300
-
301
- Dir.glob(pattern).each do |file|
302
- cmd = Tap::App.relative_filepath(path, file).chomp(".rb")
303
- raise "command name confict: #{cmd}" if commands.include?(cmd)
304
- commands[cmd] = file
508
+ def search(name, pattern)
509
+ return find(name, pattern) if name == :envs
510
+
511
+ envs = case pattern
512
+ when /^(.*):([^:]+)$/
513
+ env_pattern = $1
514
+ pattern = $2
515
+ find(:envs, env_pattern) or raise(ArgumentError, "could not find env: #{env_pattern}")
516
+ else manifest(:envs).values
517
+ end
518
+
519
+ envs.each do |env|
520
+ if result = env.find(name, pattern)
521
+ return result
305
522
  end
306
523
  end
307
-
308
- # allow all other scripts to override default scripts
309
- # (hence do this second)
310
- tap_command_dir = File.expand_path(File.join( File.dirname(__FILE__), "cmd"))
311
- Dir.glob( tap_command_dir + "/**/*.rb" ).each do |file|
312
- cmd = Tap::App.relative_filepath(tap_command_dir, file).chomp(".rb")
313
- commands[cmd] = file unless commands.include?(cmd)
524
+
525
+ nil
526
+ end
527
+
528
+ def summary(name)
529
+ summary = Support::Summary.new
530
+ manifest(:envs).mini_map.each do |(key, env)|
531
+ summary.add(key, env, env.manifest(name).mini_map)
314
532
  end
533
+ summary
534
+ end
535
+
536
+ def summarize(name, &block)
537
+ lines = summary(name).lines(&block)
538
+ lines << "=== no #{name} found" if lines.empty?
539
+ lines.join("\n")
540
+ end
315
541
 
316
- commands
542
+ def inspect(brief=false)
543
+ brief ? "#<#{self.class}:#{object_id} root='#{root.root}'>" : super()
544
+ end
545
+
546
+ def to_s
547
+ inspect(true)
548
+ end
549
+
550
+ #--
551
+ # Under construction
552
+ #++
553
+
554
+ def handle_error(err)
555
+ case
556
+ when $DEBUG
557
+ puts err.message
558
+ puts
559
+ puts err.backtrace
560
+ else puts err.message
561
+ end
317
562
  end
318
563
 
319
564
  protected
320
565
 
321
- def assign_paths(root, paths, *targets)
322
- paths = paths.collect {|path| root[path]}
323
- targets.each do |array|
324
- paths.reverse_each do |path|
325
- array.unshift(path)
326
- end
327
- array.uniq!
566
+ # Iterates over each nested env, yielding the root path and env.
567
+ # This is the manifest method for envs.
568
+ def manifest_glob_envs
569
+ collect {|env| [env.root.root, env] }.sort_by {|root, env| File.basename(root) }
570
+ end
571
+
572
+ def manifest_map(context, path)
573
+ [[context, path]]
574
+ end
575
+
576
+ alias default_manifest_glob_tasks manifest_glob_tasks
577
+
578
+ def manifest_glob_tasks
579
+ paths = default_manifest_glob_tasks
580
+
581
+ # very odd behaviors --
582
+ # * OS X is case-insensitive, apparently. Tapfile.rb and tapfile.rb are the same.
583
+ # * require 'tapfile' does not work
584
+ # * require 'tapfile.rb' works
585
+ # * load 'tapfile' works
586
+ #
587
+ root.glob(:root, DEFAULT_TASK_FILE).each do |path|
588
+ next if File.directory?(path)
589
+ paths.unshift [root.root, path]
590
+ end
591
+ paths
592
+ end
593
+
594
+ # Raises an error if self is already active (and hence, configurations
595
+ # should not be modified)
596
+ def check_configurable
597
+ raise "path configurations are disabled when active" if active?
598
+ end
599
+
600
+ # Resets envs using the current env_paths and gems.
601
+ def reset_envs
602
+ self.envs = env_paths.collect do |path|
603
+ Env.instance_for(path)
604
+ end + gems.collect do |spec|
605
+ Env.instance_for(spec.full_gem_path)
328
606
  end
329
607
  end
330
608
 
331
- def handle_unknown_env_config(key, value)
332
- raise "unknown env config: #{key}"
609
+ # Recursively iterates through envs collecting all envs into
610
+ # the target. The result is a unique array of all nested
611
+ # envs, in order, beginning with self.
612
+ def flatten_envs(target=[])
613
+ unless target.include?(self)
614
+ target << self
615
+ envs.each do |env|
616
+ env.flatten_envs(target)
617
+ end
618
+ end
619
+
620
+ target
621
+ end
622
+
623
+ # Raised when there is a Env-level configuration error.
624
+ class ConfigError < StandardError
625
+ attr_reader :original_error, :env_path
626
+
627
+ def initialize(original_error, env_path)
628
+ @original_error = original_error
629
+ @env_path = env_path
630
+ super()
631
+ end
632
+
633
+ def message
634
+ "Configuration error: #{original_error.message}\n" +
635
+ ($DEBUG ? "#{original_error.backtrace}\n" : "") +
636
+ "Check '#{env_path}' configurations"
637
+ end
333
638
  end
334
639
  end
335
640
  end