linecook 0.6.2 → 1.0.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 (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