linecook 0.6.2 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. data/History +139 -0
  2. data/HowTo/Control Virtual Machines +106 -0
  3. data/HowTo/Generate Scripts +263 -0
  4. data/HowTo/Run Scripts +87 -0
  5. data/HowTo/Setup Virtual Machines +76 -0
  6. data/License.txt +1 -1
  7. data/README +78 -59
  8. data/bin/linecook +12 -5
  9. data/bin/linecook_run +45 -0
  10. data/bin/linecook_scp +50 -0
  11. data/lib/linecook.rb +1 -3
  12. data/lib/linecook/attributes.rb +49 -12
  13. data/lib/linecook/commands.rb +9 -4
  14. data/lib/linecook/commands/build.rb +69 -0
  15. data/lib/linecook/commands/command.rb +13 -3
  16. data/lib/linecook/commands/command_error.rb +6 -0
  17. data/lib/linecook/commands/env.rb +74 -8
  18. data/lib/linecook/commands/helper.rb +271 -24
  19. data/lib/linecook/commands/init.rb +10 -6
  20. data/lib/linecook/commands/package.rb +36 -18
  21. data/lib/linecook/commands/run.rb +66 -0
  22. data/lib/linecook/commands/snapshot.rb +114 -0
  23. data/lib/linecook/commands/ssh.rb +39 -0
  24. data/lib/linecook/commands/start.rb +34 -0
  25. data/lib/linecook/commands/state.rb +32 -0
  26. data/lib/linecook/commands/stop.rb +22 -0
  27. data/lib/linecook/commands/vbox_command.rb +130 -0
  28. data/lib/linecook/cookbook.rb +112 -55
  29. data/lib/linecook/package.rb +293 -109
  30. data/lib/linecook/proxy.rb +19 -0
  31. data/lib/linecook/recipe.rb +321 -62
  32. data/lib/linecook/template.rb +7 -101
  33. data/lib/linecook/test.rb +196 -141
  34. data/lib/linecook/test/command_parser.rb +75 -0
  35. data/lib/linecook/test/file_test.rb +153 -35
  36. data/lib/linecook/test/shell_test.rb +176 -0
  37. data/lib/linecook/utils.rb +25 -7
  38. data/lib/linecook/version.rb +4 -4
  39. data/templates/Rakefile +44 -47
  40. data/templates/_gitignore +1 -1
  41. data/templates/attributes/project_name.rb +4 -4
  42. data/templates/config/ssh +15 -0
  43. data/templates/files/help.txt +1 -0
  44. data/templates/helpers/project_name/assert_content_equal.erb +15 -0
  45. data/templates/helpers/project_name/create_dir.erb +9 -0
  46. data/templates/helpers/project_name/create_file.erb +8 -0
  47. data/templates/helpers/project_name/install_file.erb +8 -0
  48. data/templates/packages/abox.yml +4 -0
  49. data/templates/recipes/abox.rb +22 -0
  50. data/templates/recipes/abox_test.rb +14 -0
  51. data/templates/templates/todo.txt.erb +3 -0
  52. data/templates/test/project_name_test.rb +19 -0
  53. data/templates/test/test_helper.rb +14 -0
  54. metadata +43 -41
  55. data/cookbook +0 -0
  56. data/lib/linecook/commands/helpers.rb +0 -28
  57. data/lib/linecook/commands/vbox.rb +0 -85
  58. data/lib/linecook/helper.rb +0 -117
  59. data/lib/linecook/shell.rb +0 -11
  60. data/lib/linecook/shell/posix.rb +0 -145
  61. data/lib/linecook/shell/test.rb +0 -254
  62. data/lib/linecook/shell/unix.rb +0 -117
  63. data/lib/linecook/shell/utils.rb +0 -138
  64. data/templates/README +0 -90
  65. data/templates/files/file.txt +0 -1
  66. data/templates/helpers/project_name/echo.erb +0 -5
  67. data/templates/recipes/project_name.rb +0 -20
  68. data/templates/scripts/project_name.yml +0 -7
  69. data/templates/templates/template.txt.erb +0 -3
  70. data/templates/vbox/setup/virtual_box +0 -86
  71. data/templates/vbox/ssh/id_rsa +0 -27
  72. data/templates/vbox/ssh/id_rsa.pub +0 -1
@@ -1,18 +1,26 @@
1
- require 'linecook/package'
2
- require 'yaml'
1
+ require 'linecook/utils'
2
+ require 'lazydoc'
3
3
 
4
4
  module Linecook
5
5
  class Cookbook
6
6
  class << self
7
- def config_file(dir)
8
- Dir.glob(File.join(dir, '{C,c}ookbook')).first
7
+ def config_file(project_dir='.')
8
+ Dir.glob(File.join(project_dir, '{C,c}ookbook')).first
9
9
  end
10
+
11
+ def setup(config={}, project_dir='.')
12
+ unless config.kind_of?(Hash)
13
+ config = Utils.load_config(config)
14
+ end
10
15
 
11
- def init(dir)
12
- path = config_file(dir)
13
- config = path ? YAML.load_file(path) : nil
16
+ config[PATHS_KEY] ||= [project_dir]
17
+ config[GEMS_KEY] ||= gems
14
18
 
15
- new(dir, config || {})
19
+ new(config, project_dir)
20
+ end
21
+
22
+ def init(project_dir='.')
23
+ setup config_file(project_dir), project_dir
16
24
  end
17
25
 
18
26
  def gems
@@ -26,65 +34,44 @@ module Linecook
26
34
  end
27
35
  end
28
36
 
29
- PATTERNS = [
30
- File.join('attributes', '**', '*.rb'),
31
- File.join('files', '**', '*'),
32
- File.join('recipes', '**', '*.rb'),
33
- File.join('templates', '**', '*.erb')
34
- ]
37
+ MANIFEST_KEY = 'manifest'
38
+ PATHS_KEY = 'paths'
39
+ GEMS_KEY = 'gems'
40
+ REWRITE_KEY = 'rewrite'
41
+
42
+ PATTERNS = {
43
+ 'attributes' => ['attributes', '.rb'],
44
+ 'files' => ['files'],
45
+ 'recipes' => ['recipes', '.rb'],
46
+ 'templates' => ['templates', '.erb']
47
+ }
35
48
 
36
- attr_reader :dir
49
+ attr_reader :project_dir
37
50
  attr_reader :config
38
51
 
39
- def initialize(dir='.', config={})
40
- @dir = File.expand_path(dir)
41
- @config = {
42
- 'manifest' => {},
43
- 'paths' => ['.'],
44
- 'gems' => self.class.gems
45
- }.merge(config)
52
+ def initialize(config={}, project_dir='.')
53
+ @project_dir = project_dir
54
+ @config = config
46
55
  end
47
56
 
48
- def manifest
49
- @manifest ||= begin
50
- manifest = {}
51
-
52
- paths = split config['paths']
53
- gems = split config['gems']
54
- gems = resolve gems
55
-
56
- (gems + paths).each do |path|
57
- path = File.expand_path(path, dir)
58
- start = path.length + 1
59
-
60
- PATTERNS.each do |pattern|
61
- Dir.glob(File.join(path, pattern)).each do |full_path|
62
- next unless File.file?(full_path)
63
-
64
- rel_path = full_path[start, full_path.length - start]
65
- manifest[rel_path] = full_path
66
- end
67
- end
68
- end
69
-
70
- overrides = config['manifest']
71
- manifest.merge!(overrides)
72
- manifest
73
- end
57
+ def paths
58
+ Utils.arrayify config[PATHS_KEY]
74
59
  end
75
60
 
76
- def env(path=nil)
77
- Package.env(manifest, path)
61
+ def gems
62
+ Utils.arrayify config[GEMS_KEY]
78
63
  end
79
64
 
80
- private
65
+ def rewrites
66
+ config[REWRITE_KEY]
67
+ end
81
68
 
82
- def split(str) # :nodoc:
83
- str.kind_of?(String) ? str.split(':') : str
69
+ def overrides
70
+ config[MANIFEST_KEY]
84
71
  end
85
72
 
86
- def resolve(gems) # :nodoc:
87
- return gems if gems.empty?
73
+ def full_gem_paths
74
+ return [] if gems.empty?
88
75
  specs = latest_specs
89
76
 
90
77
  gems.collect do |name|
@@ -93,6 +80,76 @@ module Linecook
93
80
  end
94
81
  end
95
82
 
83
+ def rewrite(manifest)
84
+ replacements = {}
85
+
86
+ rewrites.each_pair do |pattern, substitution|
87
+ manifest.keys.each do |key|
88
+ replacement = key.sub(pattern, substitution)
89
+ next if key == replacement
90
+ raise "multiple replacements for: #{key}" if replacements.has_key?(key)
91
+
92
+ replacements[key] = replacement
93
+ end
94
+ end if rewrites
95
+
96
+ replacements.each do |key, replacement|
97
+ manifest[replacement] = manifest.delete(key)
98
+ end
99
+
100
+ manifest
101
+ end
102
+
103
+ def glob(*paths)
104
+ manifest = Hash.new {|hash, key| hash[key] = {} }
105
+
106
+ paths.each do |path|
107
+ PATTERNS.each_pair do |type, (dirname, extname)|
108
+ resource_dir = File.expand_path(File.join(path, dirname), project_dir)
109
+
110
+ pattern = File.join(resource_dir, "**/*#{extname}")
111
+
112
+ Dir.glob(pattern).each do |full_path|
113
+ next unless File.file?(full_path)
114
+
115
+ name = relative_path(resource_dir, full_path)
116
+ name.chomp!(extname) if extname
117
+
118
+ manifest[type][name] = full_path
119
+ end
120
+ end
121
+ end
122
+
123
+ manifest
124
+ end
125
+
126
+ def manifest
127
+ manifest = glob(*(full_gem_paths + paths))
128
+
129
+ if overrides
130
+ manifest = Utils.deep_merge(manifest, overrides)
131
+ end
132
+
133
+ manifest.each_key do |key|
134
+ manifest[key] = rewrite manifest[key]
135
+ end
136
+
137
+ manifest
138
+ end
139
+
140
+ def merge(config={})
141
+ duplicate = dup
142
+ dup.config.merge!(config)
143
+ dup
144
+ end
145
+
146
+ private
147
+
148
+ def relative_path(dir, path) # :nodoc:
149
+ start = dir.length + 1
150
+ path[start, path.length - start]
151
+ end
152
+
96
153
  def latest_specs # :nodoc:
97
154
  latest = {}
98
155
  Gem.source_index.latest_specs.each do |spec|
@@ -1,137 +1,344 @@
1
- require 'linecook/utils'
1
+ require 'linecook/cookbook'
2
+ require 'linecook/recipe'
3
+ require 'linecook/template'
2
4
  require 'tempfile'
3
5
 
4
6
  module Linecook
5
7
  class Package
6
8
  class << self
7
- def load_env(path)
8
- (path ? YAML.load_file(path) : nil) || {}
9
- end
10
-
11
- def env(manifest, path)
12
- default = {CONFIG_KEY => {MANIFEST_KEY => manifest}}
13
- overrides = load_env(path)
14
- Utils.serial_merge(default, overrides)
9
+ def setup(env, cookbook=nil)
10
+ unless env.kind_of?(Hash)
11
+ env = Utils.load_config(env)
12
+ end
13
+
14
+ package = new(env)
15
+
16
+ if cookbook
17
+ manifest = package.manifest
18
+ manifest.replace cookbook.manifest
19
+ end
20
+
21
+ package
15
22
  end
16
23
 
17
- def init(env={})
18
- env.kind_of?(Package) ? env : new(env)
24
+ def init(package_file=nil, project_dir=nil)
25
+ cookbook = Cookbook.init(project_dir)
26
+ package = setup(package_file, cookbook)
27
+
28
+ if package_file
29
+ package.context[PACKAGE_KEY] ||= begin
30
+ name = File.basename(package_file).chomp(File.extname(package_file))
31
+ {'recipes' => { 'run' => name }}
32
+ end
33
+ end
34
+
35
+ package
19
36
  end
20
37
  end
21
38
 
22
- CONFIG_KEY = 'linecook'
39
+ CONTEXT_KEY = 'linecook'
40
+ COOKBOOK_KEY = 'cookbook'
23
41
  MANIFEST_KEY = 'manifest'
42
+ PACKAGE_KEY = 'package'
24
43
  REGISTRY_KEY = 'registry'
25
- CACHE_KEY = 'cache'
44
+
26
45
  FILES_KEY = 'files'
27
46
  TEMPLATES_KEY = 'templates'
28
47
  RECIPES_KEY = 'recipes'
29
- PATHS_KEY = 'paths'
30
- GEMS_KEY = 'gems'
31
48
 
49
+ # The package environment
32
50
  attr_reader :env
33
51
 
52
+ # An array of tempfiles generated by self (used to cleanup on close)
53
+ attr_reader :tempfiles
54
+
55
+ # A hash of counters used by variable.
56
+ attr_reader :counters
57
+
58
+ # A hash of callbacks registered with self
59
+ attr_reader :callbacks
60
+
34
61
  def initialize(env={})
35
62
  @env = env
63
+ @tempfiles = []
64
+ @counters = Hash.new(0)
65
+ @callbacks = Hash.new {|hash, key| hash[key] = StringIO.new }
36
66
  end
37
67
 
38
- def config
39
- env[CONFIG_KEY] ||= {}
40
- end
41
-
42
- def cache
43
- config[CACHE_KEY] ||= {}
68
+ # Returns the linecook context in env, as keyed by CONTEXT_KEY. Defaults
69
+ # to an empty hash.
70
+ def context
71
+ env[CONTEXT_KEY] ||= {}
44
72
  end
45
73
 
74
+ # Returns the manifest in config, as keyed by MANIFEST_KEY. Defaults to an
75
+ # empty hash.
46
76
  def manifest
47
- config[MANIFEST_KEY] ||= {}
77
+ context[MANIFEST_KEY] ||= {}
48
78
  end
49
79
 
80
+ # Returns the registry in config, as keyed by REGISTRY_KEY. Defaults to an
81
+ # empty hash. A hash of (target_name, source_path) pairs identifying
82
+ # files that should be included in a package
50
83
  def registry
51
- config[REGISTRY_KEY] ||= {}
84
+ context[REGISTRY_KEY] ||= {}
52
85
  end
53
86
 
54
- def reverse_registry
55
- cache[:reverse_registry] ||= {}
87
+ # Returns the linecook configs in env, as keyed by CONFIG_KEY. Defaults
88
+ # to an empty hash.
89
+ def config
90
+ context[PACKAGE_KEY] ||= {}
56
91
  end
57
92
 
58
- def tempfiles
59
- cache[:tempfiles] ||= []
93
+ # Returns the hash of (source, target) pairs identifying which of the
94
+ # files will be built into self by build. Files are identified by
95
+ # FILES_KEY in config, and normalized the same way as recipes.
96
+ def files
97
+ config[FILES_KEY] = Utils.hashify(config[FILES_KEY])
60
98
  end
61
99
 
62
- def register(source_path, build_path=nil)
100
+ # Returns the hash of (source, target) pairs identifying which templates
101
+ # will be built into self by build. Templates are identified by
102
+ # TEMPLATES_KEY in config, and normalized the same way as recipes.
103
+ def templates
104
+ config[TEMPLATES_KEY] = Utils.hashify(config[TEMPLATES_KEY])
105
+ end
106
+
107
+ # Returns the hash of (source, target) pairs identifying which recipes
108
+ # will be built into self by build. Recipes are identified by RECIPES_KEY
109
+ # in config.
110
+ #
111
+ # Non-hash recipes are normalized by expanding arrays into a redundant
112
+ # hash, such that each entry has the same source and target (more
113
+ # concretely, the 'example' recipe is registered as the 'example' script).
114
+ # Strings are split along colons into an array and then expanded.
115
+ #
116
+ # For example:
117
+ #
118
+ # package = Package.new('linecook' => {'package' => {'recipes' => 'a:b:c'}})
119
+ # package.recipes # => {'a' => 'a', 'b' => 'b', 'c' => 'c'}
120
+ #
121
+ def recipes
122
+ config[RECIPES_KEY] = Utils.hashify(config[RECIPES_KEY])
123
+ end
124
+
125
+ # Registers the source_path to target_name in the registry and
126
+ # revese_registry. Raises an error if the source_path is already
127
+ # registered.
128
+ def register(target_name, source_path, mode=0600)
63
129
  source_path = File.expand_path(source_path)
64
- build_path ||= File.basename(source_path)
65
130
 
131
+ if registry.has_key?(target_name) && registry[target_name] != [source_path, mode]
132
+ raise "already registered: #{target_name} (%s, %o)" % registry[target_name]
133
+ end
134
+
135
+ registry[target_name] = [source_path, mode]
136
+ target_name
137
+ end
138
+
139
+ # Increments target_name until an unregistered name is found and returns
140
+ # the result.
141
+ def next_target_name(target_name='file')
66
142
  count = 0
67
- registry.each_key do |path|
68
- if path.kind_of?(String) && path.index(build_path) == 0
143
+ registry.each_key do |key|
144
+ if key.index(target_name) == 0
69
145
  count += 1
70
146
  end
71
147
  end
72
148
 
73
149
  if count > 0
74
- build_path = "#{build_path}.#{count}"
150
+ target_name = "#{target_name}.#{count}"
75
151
  end
76
152
 
77
- registry[build_path] = source_path
78
- reverse_registry[source_path.to_sym] = build_path
153
+ target_name
154
+ end
155
+
156
+ # Returns a package-unique variable with base 'name'.
157
+ def next_variable_name(context)
158
+ context = context.to_s
79
159
 
80
- build_path
160
+ count = counters[context]
161
+ counters[context] += 1
162
+
163
+ "#{context}#{count}"
81
164
  end
82
165
 
83
- def registered?(source_path)
84
- source_path = File.expand_path(source_path)
85
- reverse_registry.has_key?(source_path.to_sym)
166
+ # Returns true if there is a path for the specified resource in manifest.
167
+ def resource?(type, path)
168
+ resources = manifest[type]
169
+ resources && resources.has_key?(path)
170
+ end
171
+
172
+ # Returns the path to the resource in manfiest. Raises an error if there
173
+ # is no such resource.
174
+ def resource_path(type, path)
175
+ resources = manifest[type]
176
+ resource_path = resources ? resources[path] : nil
177
+ resource_path or raise "no such resource in manifest: #{type.inspect} #{path.inspect}"
178
+ end
179
+
180
+ # Returns the resource_path the named attributes file (ex 'attributes/name.rb').
181
+ def attributes_path(attributes_name)
182
+ resource_path('attributes', attributes_name)
183
+ end
184
+
185
+ # Returns the resource_path the named file (ex 'files/name')
186
+ def file_path(file_name)
187
+ resource_path('files', file_name)
188
+ end
189
+
190
+ # Returns the resource_path the named template file (ex 'templates/name.erb').
191
+ def template_path(template_name)
192
+ resource_path('templates', template_name)
86
193
  end
87
194
 
88
- def built?(build_path)
89
- registry.has_key?(build_path)
195
+ # Returns the resource_path the named recipe file (ex 'recipes/name.rb').
196
+ def recipe_path(recipe_name)
197
+ resource_path('recipes', recipe_name)
90
198
  end
91
199
 
200
+ # Loads the attributes file with the specified name and evaluates in the
201
+ # context of Attributes. Returns the new Attributes object.
202
+ def load_attributes(attributes_name=nil)
203
+ attributes = Attributes.new
204
+
205
+ if attributes_name
206
+ path = attributes_path(attributes_name)
207
+ attributes.instance_eval(File.read(path), path)
208
+ end
209
+
210
+ attributes
211
+ end
212
+
213
+ # Load the template file with the specified name and wraps as a Template.
214
+ # Returns the new Template object.
215
+ def load_template(template_name)
216
+ Template.new template_path(template_name)
217
+ end
218
+
219
+ # Loads and returns the helper constant specified by helper_name. The
220
+ # helper_name is underscored to determine a require path and camelized to
221
+ # determine the constant name.
222
+ def load_helper(helper_name)
223
+ require Utils.underscore(helper_name)
224
+ Utils.constantize(helper_name)
225
+ end
226
+
227
+ # Returns a recipe bound to self.
228
+ def setup_recipe(target_name = next_target_name, mode=0700)
229
+ Recipe.new(self, target_name, mode)
230
+ end
231
+
232
+ # Generates a tempfile for the target path and registers it with self. As
233
+ # with register, the target_name will be incremented as needed. Returns
234
+ # the open tempfile.
235
+ def setup_tempfile(target_name = next_target_name, mode=0600)
236
+ tempfile = Tempfile.new File.basename(target_name)
237
+
238
+ register(target_name, tempfile.path, mode)
239
+ tempfiles << tempfile
240
+
241
+ tempfile
242
+ end
243
+
244
+ # Returns true if the source_path is for a tempfile generated by self.
92
245
  def tempfile?(source_path)
93
- tempfiles.find {|tempfile| tempfile.path == source_path }
246
+ tempfiles.any? {|tempfile| tempfile.path == source_path }
94
247
  end
95
248
 
96
- def build(build_path, source_path=nil)
97
- case
98
- when built?(build_path)
99
- raise "already built: #{build_path}"
100
-
101
- when source_path
102
- register(source_path, build_path)
103
-
104
- else
105
- tempfile = Tempfile.new File.basename(build_path)
106
-
107
- register(tempfile.path, build_path)
108
- tempfiles << tempfile
109
-
110
- tempfile
249
+ # Looks up the file with the specified name using file_path and registers
250
+ # it to target_name. Raises an error if the target is already registered.
251
+ def build_file(target_name, file_name=target_name, mode=0600)
252
+ register target_name, file_path(file_name), mode
253
+ self
254
+ end
255
+
256
+ # Looks up the template with the specified name using template_path,
257
+ # builds, and registers it to target_name. The locals will be set for
258
+ # access in the template context. Raises an error if the target is
259
+ # already registered. Returns self.
260
+ def build_template(target_name, template_name=target_name, mode=0600, locals={'attrs' => env})
261
+ content = load_template(template_name).build(locals)
262
+
263
+ target = setup_tempfile(target_name, mode)
264
+ target << content
265
+ target.close
266
+ self
267
+ end
268
+
269
+ # Looks up the recipe with the specified name using recipe_path, evaluates
270
+ # it, and registers the result to target_name. Raises an error if the
271
+ # target is already registered. Returns self.
272
+ def build_recipe(target_name, recipe_name=target_name, mode=0700)
273
+ path = recipe_path(recipe_name)
274
+ recipe = setup_recipe(target_name, mode)
275
+ recipe.instance_eval(File.read(path), path)
276
+ recipe.close
277
+
278
+ self
279
+ end
280
+
281
+ # Builds the files, templates, and recipes for self. Returns self.
282
+ def build
283
+ files.each do |target_name, file_name|
284
+ build_file(target_name, *file_name)
285
+ end
286
+
287
+ templates.each do |target_name, template_name|
288
+ build_template(target_name, *template_name)
289
+ end
290
+
291
+ recipes.each do |target_name, recipe_name|
292
+ build_recipe(target_name, *recipe_name)
111
293
  end
294
+
295
+ self
112
296
  end
113
297
 
114
- def build_path(source_path)
115
- source_path = File.expand_path(source_path)
116
- reverse_registry[source_path.to_sym]
298
+ # Returns the content of the source_path for target_name, as registered in
299
+ # self. Returns nil if the target is not registered.
300
+ def content(target_name, length=nil, offset=nil)
301
+ path = source_path(target_name)
302
+ path ? File.read(path, length, offset) : nil
117
303
  end
118
304
 
119
- def source_path(build_path)
120
- registry[build_path]
305
+ # Returns the source_path for target_name, as registered in self. Returns
306
+ # nil if the target is not registered.
307
+ def source_path(target_name)
308
+ entry = registry[target_name]
309
+ entry ? entry[0] : nil
121
310
  end
122
311
 
123
- def files
124
- normalize(FILES_KEY)
312
+ # Returns the mode for target_name, as registered in self. Returns nil if
313
+ # the target is not registered.
314
+ def mode(target_name)
315
+ entry = registry[target_name]
316
+ entry ? entry[1] : nil
125
317
  end
126
318
 
127
- def templates
128
- normalize(TEMPLATES_KEY)
319
+ # Closes all tempfiles and returns self.
320
+ def close
321
+ tempfiles.each do |tempfile|
322
+ tempfile.close unless tempfile.closed?
323
+ end
324
+ self
129
325
  end
130
326
 
131
- def recipes
132
- normalize(RECIPES_KEY)
327
+ # Closes and clears all tempfiles, the registry, callbacks, and counters.
328
+ def reset
329
+ close
330
+ registry.clear
331
+ tempfiles.clear
332
+ callbacks.clear
333
+ counters.clear
334
+ self
133
335
  end
134
336
 
337
+ # Closes self and exports the registry to dir by copying or moving the
338
+ # registered source paths to the target path under dir. By default
339
+ # tempfiles are moved while all other files are copied.
340
+ #
341
+ # Returns registry, which is re-written to reflect the new source paths.
135
342
  def export(dir, options={})
136
343
  close
137
344
 
@@ -141,57 +348,34 @@ module Linecook
141
348
 
142
349
  allow_move = options[:allow_move]
143
350
 
144
- results = {}
145
- registry.each_pair do |build_path, source_path|
146
- target_path = File.join(dir, build_path)
147
- target_dir = File.dirname(target_path)
351
+ if File.exists?(dir)
352
+ FileUtils.rm_r(dir)
353
+ end
354
+
355
+ registry.each_key do |target_name|
356
+ export_path = File.join(dir, target_name)
357
+ export_dir = File.dirname(export_path)
358
+ source_path, mode = registry[target_name]
359
+
360
+ next if source_path == export_path
148
361
 
149
- unless File.exists?(target_dir)
150
- FileUtils.mkdir_p(target_dir)
362
+ unless File.exists?(export_dir)
363
+ FileUtils.mkdir_p(export_dir)
151
364
  end
152
365
 
153
366
  if allow_move && tempfile?(source_path)
154
- FileUtils.mv(source_path, target_path)
367
+ FileUtils.mv(source_path, export_path)
155
368
  else
156
- FileUtils.cp(source_path, target_path)
369
+ FileUtils.cp(source_path, export_path)
157
370
  end
158
371
 
159
- results[build_path] = target_path
160
- end
161
- results
162
- end
163
-
164
- def close
165
- tempfiles.each do |tempfile|
166
- tempfile.close unless tempfile.closed?
167
- end
168
- self
169
- end
170
-
171
- private
172
-
173
- def normalize(key)
174
- obj = config[key]
175
-
176
- case obj
177
- when Hash
178
- obj
179
-
180
- when nil
181
- config[key] = {}
372
+ FileUtils.chmod(mode, export_path)
182
373
 
183
- when Array
184
- hash = {}
185
- obj.each {|entry| hash[entry] = entry }
186
- config[key] = hash
187
-
188
- when String
189
- config[key] = obj.split(':')
190
- normalize(key)
191
-
192
- else
193
- raise "invalid #{key}: #{obj.inspect}"
374
+ registry[target_name] = [export_path, mode]
194
375
  end
376
+
377
+ tempfiles.clear
378
+ registry
195
379
  end
196
380
  end
197
381
  end