lib/linecook/package.rb in linecook-1.2.1 vs lib/linecook/package.rb in linecook-2.0.0
- old
+ new
@@ -1,396 +1,212 @@
-require 'linecook/cookbook'
-require 'linecook/recipe'
-require 'linecook/template'
require 'tempfile'
+require 'stringio'
module Linecook
class Package
- class << self
- def setup(env, cookbook=nil)
- unless env.kind_of?(Hash)
- env = Utils.load_config(env)
- end
-
- package = new(env)
-
- if cookbook
- manifest = package.manifest
- manifest.replace cookbook.manifest
- end
-
- package
- end
-
- def init(package_file=nil, project_dir=nil)
- cookbook = Cookbook.init(project_dir)
- package = setup(package_file, cookbook)
-
- if package_file
- package.context[PACKAGE_KEY] ||= begin
- name = File.basename(package_file).chomp(File.extname(package_file))
- {'recipes' => { 'run' => name }}
- end
- end
-
- package
- end
- end
-
- CONTEXT_KEY = 'linecook'
- COOKBOOK_KEY = 'cookbook'
- MANIFEST_KEY = 'manifest'
- PACKAGE_KEY = 'package'
- REGISTRY_KEY = 'registry'
-
- FILES_KEY = 'files'
- TEMPLATES_KEY = 'templates'
- RECIPES_KEY = 'recipes'
-
# The package environment
attr_reader :env
-
- # An array of tempfiles generated by self (used to cleanup on close)
- attr_reader :tempfiles
-
- # A hash of counters used by variable.
- attr_reader :counters
-
- # A hash of callbacks registered with self
- attr_reader :callbacks
-
+
+ # A registry of (path, source_path) pairs recording what files are
+ # included in the package.
+ attr_reader :registry
+
+ # A hash of (path, Hash) pairs identifing export options for a package
+ # file. See on_export.
+ attr_reader :export_options
+
+ # A hash of default export options.
+ attr_reader :default_export_options
+
def initialize(env={})
@env = env
- @tempfiles = []
- @counters = Hash.new(0)
- @callbacks = Hash.new {|hash, key| hash[key] = StringIO.new }
+ @registry = {}
+ @export_options = {}
+ @default_export_options = {}
end
-
- # Returns the linecook context in env, as keyed by CONTEXT_KEY. Defaults
- # to an empty hash.
- def context
- env[CONTEXT_KEY] ||= {}
+
+ # Registers a file into the package with the specified export options. The
+ # source path should be the path to a file or directory to include. To
+ # make an empty file or directory use :file or :dir as the source_path.
+ #
+ # Raises an error if a source is already registered at the path.
+ def register(path, source_path, options={})
+ package_path = path.to_s.strip
+
+ if package_path.empty?
+ raise "invalid package path: #{path.inspect}"
+ end
+
+ if registry.has_key?(package_path)
+ raise "already registered: #{path.inspect}"
+ end
+
+ source_path = resolve_source_path(source_path)
+ registry[package_path] = source_path
+ on_export(package_path, options)
+
+ source_path
end
-
- # Returns the manifest in config, as keyed by MANIFEST_KEY. Defaults to an
- # empty hash.
- def manifest
- context[MANIFEST_KEY] ||= {}
+
+ # Removes a file from the package. Returns the source path if one was
+ # registered.
+ def unregister(path)
+ registry.delete(path)
+ export_options.delete(path)
end
-
- # Returns the registry in config, as keyed by REGISTRY_KEY. Defaults to an
- # empty hash. A hash of (target_name, source_path) pairs identifying
- # files that should be included in a package
- def registry
- context[REGISTRY_KEY] ||= {}
- end
-
- # Returns the linecook configs in env, as keyed by CONFIG_KEY. Defaults
- # to an empty hash.
- def config
- context[PACKAGE_KEY] ||= {}
- end
-
- # Returns the hash of (source, target) pairs identifying which of the
- # files will be built into self by build. Files are identified by
- # FILES_KEY in config, and normalized the same way as recipes.
- def files
- config[FILES_KEY] = Utils.hashify(config[FILES_KEY])
- end
-
- # Returns the hash of (source, target) pairs identifying which templates
- # will be built into self by build. Templates are identified by
- # TEMPLATES_KEY in config, and normalized the same way as recipes.
- def templates
- config[TEMPLATES_KEY] = Utils.hashify(config[TEMPLATES_KEY])
- end
-
- # Returns the hash of (source, target) pairs identifying which recipes
- # will be built into self by build. Recipes are identified by RECIPES_KEY
- # in config.
+
+ # Sets export options for the package file. Available options (as
+ # symbols):
#
- # Non-hash recipes are normalized by expanding arrays into a redundant
- # hash, such that each entry has the same source and target (more
- # concretely, the 'example' recipe is registered as the 'example' script).
- # Strings are split along colons into an array and then expanded.
+ # move:: When set to true the source will be moved into place
+ # rather than copied (the default)
+ # mode:: Sets the mode of the package file
#
- # For example:
- #
- # package = Package.new('linecook' => {'package' => {'recipes' => 'a:b:c'}})
- # package.recipes # => {'a' => 'a', 'b' => 'b', 'c' => 'c'}
- #
- def recipes
- config[RECIPES_KEY] = Utils.hashify(config[RECIPES_KEY])
+ # Unless specified, the values in default_export_options will be used.
+ def on_export(path, options={})
+ export_options[path] = default_export_options.merge(options)
end
-
- # Registers the source_path to target_name in the registry and
- # revese_registry. Raises an error if the source_path is already
+
+ # Generates a tempfile and registers it into the package at the specified
+ # path. Returns the open tempfile.
+ def add(path, options={})
+ options = {
+ :move => true
+ }.merge(options)
+
+ # preserve a reference to tempfile in options so that it will not be
+ # unlinked before it can be moved into the package during export
+ tempfile = Tempfile.new File.basename(path)
+ options[:tempfile] = tempfile
+
+ if block_given?
+ begin
+ yield tempfile
+ ensure
+ tempfile.close
+ end
+ end
+
+ register path, tempfile.path, options
+ tempfile
+ end
+
+ # Adds an empty dir at path. Returns nil.
+ def add_dir(path, options={})
+ register path, :dir, options
+ end
+
+ alias rm unregister
+
+ # Returns the source path registered at the path, or nil if no source is
# registered.
- def register(target_name, source_path, mode=0600)
- source_path = File.expand_path(source_path)
-
- if registry.has_key?(target_name) && registry[target_name] != [source_path, mode]
- raise "already registered: #{target_name} (%s, %o)" % registry[target_name]
+ def source_path(path)
+ registry[path]
+ end
+
+ # Returns an array of paths that the source path is registered to.
+ def paths(source_path)
+ source = resolve_source_path(source_path)
+
+ paths = []
+ registry.each_pair do |path, current|
+ if current == source
+ paths << path
+ end
end
-
- registry[target_name] = [source_path, mode]
- target_name
+ paths
end
-
- # Increments target_name until an unregistered name is found and returns
- # the result.
- def next_target_name(target_name='file')
+
+ # Returns the content to be added to the package at the path. Returns nil
+ # if nothing is registered.
+ def content(path, length=nil, offset=nil)
+ source = source_path(path)
+ source ? File.read(source, length, offset) : nil
+ end
+
+ # Increments path until an unregistered path is found and returns the
+ # result in the format "path.count".
+ def next_path(path='file')
count = 0
- registry.each_key do |key|
- if key.index(target_name) == 0
+ registry.each_key do |current|
+ if current.index(path) == 0
count += 1
end
end
-
+
if count > 0
- target_name = "#{target_name}.#{count}"
+ path = "#{path}.#{count}"
end
-
- target_name
+
+ path
end
-
- # Returns a package-unique variable with base 'name'.
- def next_variable_name(context)
- context = context.to_s
-
- count = counters[context]
- counters[context] += 1
-
- "#{context}#{count}"
- end
-
- # Returns true if there is a path for the specified resource in manifest.
- def resource?(type, path)
- resources = manifest[type]
- resources && resources.has_key?(path)
- end
-
- # Returns the path to the resource in manfiest. Raises an error if there
- # is no such resource.
- def resource_path(type, path)
- resources = manifest[type]
- resource_path = resources ? resources[path] : nil
- resource_path or raise "no such resource in manifest: #{type.inspect} #{path.inspect}"
- end
-
- # Returns the resource_path the named attributes file (ex 'attributes/name.rb').
- def attributes_path(attributes_name)
- resource_path('attributes', attributes_name)
- end
-
- # Returns the resource_path the named file (ex 'files/name')
- def file_path(file_name)
- resource_path('files', file_name)
- end
-
- # Returns the resource_path the named template file (ex 'templates/name.erb').
- def template_path(template_name)
- resource_path('templates', template_name)
- end
-
- # Returns the resource_path the named recipe file (ex 'recipes/name.rb').
- def recipe_path(recipe_name)
- resource_path('recipes', recipe_name)
- end
-
- # Loads the named attributes file into and returns an instance of
- # Attributes. The loading mechanism depends on the attributes file
- # extname.
- #
- # .rb: evaluate in the context of attributes
- # .yml,.yaml,.json: load as YAML and merge into attributes
- #
- # All other file types raise an error. Simply returns a new Attributes
- # instance if no name is given.
- def load_attributes(attributes_name=nil)
- attributes = Attributes.new
-
- if attributes_name
- path = attributes_path(attributes_name)
-
- case File.extname(path)
- when '.rb'
- attributes.instance_eval(File.read(path), path)
- when '.yml', '.yaml', '.json'
- attributes.attrs.merge!(YAML.load_file(path))
- else
- raise "invalid attributes format: #{path.inspect}"
+
+ def export(dir)
+ registry.keys.sort.each do |path|
+ target_path = File.join(dir, path)
+ source_path = registry[path]
+ options = export_options[path] || default_export_options
+
+ if source_path != target_path
+ if File.exists?(target_path)
+ if block_given?
+ unless yield(source_path, target_path)
+ next
+ end
+ else
+ raise "already exists: #{target_path.inspect}"
+ end
+ end
+
+ target_dir = File.dirname(target_path)
+ FileUtils.mkdir_p(target_dir)
+
+ case source_path
+ when :file
+ FileUtils.touch target_path
+ when :dir
+ FileUtils.mkdir target_path
+ else
+ if File.directory?(source_path)
+ export_dir(source_path, target_path, options)
+ else
+ export_file(source_path, target_path, options)
+ end
+ end
end
+
+ if mode = options[:mode]
+ FileUtils.chmod(mode, target_path)
+ end
+
+ registry[path] = target_path
end
-
- attributes
+
+ registry
end
-
- # Load the template file with the specified name and wraps as a Template.
- # Returns the new Template object.
- def load_template(template_name)
- Template.new template_path(template_name)
- end
-
- # Loads and returns the helper constant specified by helper_name. The
- # helper_name is underscored to determine a require path and camelized to
- # determine the constant name.
- def load_helper(helper_name)
- require Utils.underscore(helper_name)
- Utils.constantize(helper_name)
- end
-
- # Returns a recipe bound to self.
- def setup_recipe(target_name = next_target_name, mode=0700)
- Recipe.new(self, target_name, mode)
- end
-
- # Generates a tempfile for the target path and registers it with self. As
- # with register, the target_name will be incremented as needed. Returns
- # the open tempfile.
- def setup_tempfile(target_name = next_target_name, mode=0600)
- tempfile = Tempfile.new File.basename(target_name)
-
- register(target_name, tempfile.path, mode)
- tempfiles << tempfile
-
- tempfile
- end
-
- # Returns true if the source_path is for a tempfile generated by self.
- def tempfile?(source_path)
- tempfiles.any? {|tempfile| tempfile.path == source_path }
- end
-
- # Looks up the file with the specified name using file_path and registers
- # it to target_name. Raises an error if the target is already registered.
- def build_file(target_name, file_name=target_name, mode=0600)
- register target_name, file_path(file_name), mode
- self
- end
-
- # Looks up the template with the specified name using template_path,
- # builds, and registers it to target_name. The locals will be set for
- # access in the template context. Raises an error if the target is
- # already registered. Returns self.
- def build_template(target_name, template_name=target_name, mode=0600, locals={'attrs' => env})
- content = load_template(template_name).build(locals)
-
- target = setup_tempfile(target_name, mode)
- target << content
- target.close
- self
- end
-
- # Looks up the recipe with the specified name using recipe_path, evaluates
- # it, and registers the result to target_name. Raises an error if the
- # target is already registered. Returns self.
- def build_recipe(target_name, recipe_name=target_name, mode=0700)
- path = recipe_path(recipe_name)
- recipe = setup_recipe(target_name, mode)
- recipe.instance_eval(File.read(path), path)
- recipe.close
-
- self
- end
-
- # Builds the files, templates, and recipes for self. Returns self.
- def build
- files.each do |target_name, file_name|
- build_file(target_name, *file_name)
+
+ private
+
+ def resolve_source_path(source_path) # :nodoc:
+ case source_path
+ when :file, :dir then source_path
+ else File.expand_path(source_path.to_s)
end
-
- templates.each do |target_name, template_name|
- build_template(target_name, *template_name)
- end
-
- recipes.each do |target_name, recipe_name|
- build_recipe(target_name, *recipe_name)
- end
-
- self
end
-
- # Returns the content of the source_path for target_name, as registered in
- # self. Returns nil if the target is not registered.
- def content(target_name, length=nil, offset=nil)
- path = source_path(target_name)
- path ? File.read(path, length, offset) : nil
- end
-
- # Returns the source_path for target_name, as registered in self. Returns
- # nil if the target is not registered.
- def source_path(target_name)
- entry = registry[target_name]
- entry ? entry[0] : nil
- end
-
- # Returns the mode for target_name, as registered in self. Returns nil if
- # the target is not registered.
- def mode(target_name)
- entry = registry[target_name]
- entry ? entry[1] : nil
- end
-
- # Closes all tempfiles and returns self.
- def close
- tempfiles.each do |tempfile|
- tempfile.close unless tempfile.closed?
+
+ def export_dir(source_path, target_path, options) # :nodoc:
+ if options[:move]
+ FileUtils.mv(source_path, target_path)
+ else
+ FileUtils.cp_r(source_path, target_path)
end
- self
end
-
- # Closes and clears all tempfiles, the registry, callbacks, and counters.
- def reset
- close
- registry.clear
- tempfiles.clear
- callbacks.clear
- counters.clear
- self
- end
-
- # Closes self and exports the registry to dir by copying or moving the
- # registered source paths to the target path under dir. By default
- # tempfiles are moved while all other files are copied.
- #
- # Returns registry, which is re-written to reflect the new source paths.
- def export(dir, options={})
- close
-
- options = {
- :allow_move => true
- }.merge(options)
-
- allow_move = options[:allow_move]
-
- if File.exists?(dir)
- FileUtils.rm_r(dir)
+
+ def export_file(source_path, target_path, options) # :nodoc:
+ if options[:move]
+ FileUtils.mv(source_path, target_path)
+ else
+ FileUtils.cp(source_path, target_path)
end
-
- registry.each_key do |target_name|
- export_path = File.join(dir, target_name)
- export_dir = File.dirname(export_path)
- source_path, mode = registry[target_name]
-
- next if source_path == export_path
-
- unless File.exists?(export_dir)
- FileUtils.mkdir_p(export_dir)
- end
-
- if allow_move && tempfile?(source_path)
- FileUtils.mv(source_path, export_path)
- else
- FileUtils.cp(source_path, export_path)
- end
-
- FileUtils.chmod(mode, export_path)
-
- registry[target_name] = [export_path, mode]
- end
-
- tempfiles.clear
- registry
end
end
end
\ No newline at end of file