From 91e081cd50653854ed7a254d09549a8b058a590d Mon Sep 17 00:00:00 2001 From: Chris Eppstein Date: Sun, 1 Feb 2009 14:23:16 -0800 Subject: [PATCH] New project creation implemented using manifests and installation strategies. --- .../blueprint/templates/project/manifest.rb | 5 + .../compass/templates/project/manifest.rb | 3 + frameworks/yui/templates/project/manifest.rb | 1 + lib/compass/commands/create_project.rb | 30 ++- lib/compass/installers.rb | 16 ++ lib/compass/installers/base.rb | 179 ++++++++++++++++++ lib/compass/installers/manifest.rb | 50 +++++ lib/compass/installers/stand_alone.rb | 51 +++++ 8 files changed, 316 insertions(+), 19 deletions(-) create mode 100644 frameworks/blueprint/templates/project/manifest.rb create mode 100644 frameworks/compass/templates/project/manifest.rb create mode 100644 frameworks/yui/templates/project/manifest.rb create mode 100644 lib/compass/installers.rb create mode 100644 lib/compass/installers/base.rb create mode 100644 lib/compass/installers/manifest.rb create mode 100644 lib/compass/installers/stand_alone.rb diff --git a/frameworks/blueprint/templates/project/manifest.rb b/frameworks/blueprint/templates/project/manifest.rb new file mode 100644 index 00000000..330c3e35 --- /dev/null +++ b/frameworks/blueprint/templates/project/manifest.rb @@ -0,0 +1,5 @@ +stylesheet 'screen.sass' +stylesheet 'print.sass' +stylesheet 'ie.sass' + +image 'grid.png' diff --git a/frameworks/compass/templates/project/manifest.rb b/frameworks/compass/templates/project/manifest.rb new file mode 100644 index 00000000..f5ba211f --- /dev/null +++ b/frameworks/compass/templates/project/manifest.rb @@ -0,0 +1,3 @@ +stylesheet 'screen.sass' +stylesheet 'print.sass' +stylesheet 'ie.sass' \ No newline at end of file diff --git a/frameworks/yui/templates/project/manifest.rb b/frameworks/yui/templates/project/manifest.rb new file mode 100644 index 00000000..dffc67d0 --- /dev/null +++ b/frameworks/yui/templates/project/manifest.rb @@ -0,0 +1 @@ +stylesheet 'screen.sass' diff --git a/lib/compass/commands/create_project.rb b/lib/compass/commands/create_project.rb index 75a205db..0b8d1f4c 100644 --- a/lib/compass/commands/create_project.rb +++ b/lib/compass/commands/create_project.rb @@ -1,38 +1,30 @@ require 'fileutils' require File.join(File.dirname(__FILE__), 'base') require File.join(File.dirname(__FILE__), 'update_project') +require File.join(Compass.lib_directory, 'compass', 'installers') module Compass module Commands class CreateProject < ProjectBase - + + include Compass::Installers + def initialize(working_directory, options) super(working_directory, options) end # all commands must implement perform def perform - begin - directory nil, options - rescue Compass::Exec::DirectoryExistsError - msg = "Project directory already exists. Run with -u to update or --force to force creation." - raise ::Compass::Exec::DirectoryExistsError.new(msg) - end - src_dir = options[:src_dir] || "src" - css_dir = options[:css_dir] || "stylesheets" - directory src_dir, options.merge(:force => true) - directory css_dir, options.merge(:force => true) - framework_templates.each do |t| - template "project/#{t}", "#{src_dir}/#{t}", options - end + installer.run UpdateProject.new(working_directory, options).perform end - def framework_templates - framework_project_dir = File.join(templates_directory, "project") - Dir.chdir(framework_project_dir) do - Dir.glob("*") - end + def installer + StandAloneInstaller.new(project_template_directory, project_directory, options) + end + + def project_template_directory + File.join(framework.templates_directory, "project") end def skip_project_directory_assertion? diff --git a/lib/compass/installers.rb b/lib/compass/installers.rb new file mode 100644 index 00000000..01d5216f --- /dev/null +++ b/lib/compass/installers.rb @@ -0,0 +1,16 @@ +module Compass + module Installers + + class InstallationError < Compass::Error + end + + class DirectoryExistsError < InstallationError + end + + end +end + +require File.join(File.dirname(__FILE__), 'installers', 'manifest') +require File.join(File.dirname(__FILE__), 'installers', 'base') +require File.join(File.dirname(__FILE__), 'installers', 'stand_alone') + diff --git a/lib/compass/installers/base.rb b/lib/compass/installers/base.rb new file mode 100644 index 00000000..bf2e328c --- /dev/null +++ b/lib/compass/installers/base.rb @@ -0,0 +1,179 @@ +module Compass + module Installers + + class Base + attr_accessor :template_path, :target_path, :working_path + attr_accessor :options + attr_accessor :manifest + attr_accessor :logger + attr_accessor :css_dir, :sass_dir, :images_dir, :javascripts_dir + + def initialize(template_path, target_path, options = {}) + @template_path = template_path + @target_path = target_path + @working_path = Dir.getwd + @options = options + @manifest = Manifest.new(manifest_file) + configure_option_with_default :logger + end + + def manifest_file + @manifest_file ||= File.join(template_path, "manifest.rb") + end + + # Runs the installer. + # Every installer must conform to the installation strategy of configure, prepare, install, and then finalize. + # A default implementation is provided for each step. + def run + configure + prepare + install + finalize + end + + # The default configure method -- it sets up directories from the options + # and corresponding default_* methods for those not found in the options hash. + # It can be overridden it or augmented for reading config files, + # prompting the user for more information, etc. + def configure + [:css_dir, :sass_dir, :images_dir, :javascripts_dir].each do |opt| + configure_option_with_default opt + end + end + + # The default prepare method -- it is a no-op. + # Generally you would create required directories, etc. + def prepare + end + + def configure_option_with_default(opt) + value = options[opt] + value ||= begin + default_method = "default_#{opt}".to_sym + send(default_method) if respond_to?(default_method) + end + send("#{opt}=", value) + end + + # The default install method. Calls install_ methods in the order specified by the manifest. + def install + manifest.each do |entry| + send("install_#{entry.type}", entry.from, entry.options) + end + end + + # The default finalize method -- it is a no-op. + # This could print out a message or something. + def finalize + end + + + def install_stylesheet(from, options) + to = options[:to] || from + copy from, "#{sass_dir}/#{to}" + end + + def install_image(from, options) + to = options[:to] || from + copy from, "#{images_dir}/#{to}" + end + + def install_script(from, options) + to = options[:to] || from + copy from, "#{javascripts_dir}/#{to}" + end + + def install_file(from, options) + to = options[:to] || from + copy from, to + end + + def default_logger + Compass::Logger.new + end + + # returns an absolute path given a path relative to the current installation target. + # Paths can use unix style "/" and will be corrected for the current platform. + def targetize(path) + File.join(target_path, separate(path)) + end + + # returns an absolute path given a path relative to the current template. + # Paths can use unix style "/" and will be corrected for the current platform. + def templatize(path) + File.join(template_path, separate(path)) + end + + # Write paths like we're on unix and then fix it + def separate(path) + path.gsub(%r{/}, File::SEPARATOR) + end + + # copy/process a template in the compass template directory to the project directory. + def copy(from, to, options = nil) + options ||= self.options + from = templatize(from) + to = targetize(to) + if File.exists?(to) && !options[:force] + #TODO: Detect differences & provide an overwrite prompt + msg = "#{basename(to)} already exists." + raise InstallationError.new(msg) + elsif File.exists?(to) + logger.record :overwrite, basename(to) + FileUtils.rm to unless options[:dry_run] + FileUtils.cp from, to unless options[:dry_run] + else + logger.record :create, basename(to) + FileUtils.cp from, to unless options[:dry_run] + end + end + + # create a directory and all the directories necessary to reach it. + def directory(dir, options = nil) + options ||= self.options + dir = targetize(dir) + if File.exists?(dir) && File.directory?(dir) + logger.record :exists, basename(dir) + elsif File.exists?(dir) + msg = "#{basename(dir)} already exists and is not a directory." + raise InstallationError.new(msg) + else + logger.record :directory, basename(dir) + FileUtils.mkdir_p(dir) unless options[:dry_run] + end + end + + def write_file(file_name, contents, options = nil) + options ||= self.options + file_name = targetize(file_name) + if File.exists?(file_name) && !options[:force] + msg = "File #{basename(file_name)} already exists. Run with --force to force creation." + raise InstallationError.new(msg) + end + if File.exists?(file_name) + logger.record :overwrite, basename(file_name) + else + logger.record :create, basename(file_name) + end + open(file_name,'w') do |file| + file.write(contents) + end + end + + def basename(file) + relativize(file) {|f| File.basename(file)} + end + + def relativize(path) + if path.index(working_path+File::SEPARATOR) == 0 + path[(working_path+File::SEPARATOR).length..-1] + elsif block_given? + yield path + else + path + end + end + + end + end +end diff --git a/lib/compass/installers/manifest.rb b/lib/compass/installers/manifest.rb new file mode 100644 index 00000000..6924730c --- /dev/null +++ b/lib/compass/installers/manifest.rb @@ -0,0 +1,50 @@ +module Compass + module Installers + + class Manifest + + # A Manifest entry + Entry = Struct.new(:type, :from, :options) + + def initialize(manifest_file = nil) + @entries = [] + parse(manifest_file) if manifest_file + end + + def self.type(t) + eval <<-END + def #{t}(from, options = {}) + @entries << Entry.new(:#{t}, from, options) + end + def has_#{t}? + @entries.detect {|e| e.type == :#{t}} + end + END + end + + type :stylesheet + type :image + type :javascript + type :file + + # Enumerates over the manifest files + def each + @entries.each {|e| yield e} + end + + + protected + # parses a manifest file which is a ruby script + # evaluated in a Manifest instance context + def parse(manifest_file) + open(manifest_file) do |f| + eval(f.read, instance_binding, manifest_file) + end + end + def instance_binding + binding + end + end + + end +end \ No newline at end of file diff --git a/lib/compass/installers/stand_alone.rb b/lib/compass/installers/stand_alone.rb new file mode 100644 index 00000000..5d643464 --- /dev/null +++ b/lib/compass/installers/stand_alone.rb @@ -0,0 +1,51 @@ +module Compass + module Installers + + class StandAloneInstaller < Base + + def configure + @config = Compass::Configuration.new + if File.exists?(config_file) + @config.parse(config_file) + elsif File.exists?(old_config_file) + @config.parse(old_config_file) + end + super + end + + def prepare + directory "" + directory css_dir + directory sass_dir + directory images_dir if manifest.has_image? + directory javascripts_dir if manifest.has_javascript? + end + + def default_css_dir + @config.css_dir || "stylesheets" + end + + def default_sass_dir + @config.sass_dir ||"src" + end + + def default_images_dir + @config.images_dir || "images" + end + + def default_javascripts_dir + @config.javascripts_dir || "javascripts" + end + + # Read the configuration file for this project + def config_file + @config_file ||= targetize('config.rb') + end + + def old_config_file + @old_config_file ||= targetize('src/config.rb') + end + end + + end +end