module Xmvc ## # A base thor-extension for which to build vendors/plugins for the Xmvc framework # Extensions must impmlement several methods including :install and :secretary. # Vendors are run in a root-dir named after themselves through the Xmvc::Vendor class-method # #vendor_name. A Vendor must at least provide a file named 'vendor.yml' in the root of its # template namespace # this file is used to define asset and meta-data # # name: some-vendor # javascripts: # - App.js # - app/view/**/*.js # - app/model/*.js # - app/controller/*.js # class Vendor < Thor include Thor::Actions class Error < StandardError end class SpecNotFound < Error end ## # all vendors must implement vendor.yml in the same directory their extension lives # CONFIG_FILENAME = "vendor.yml" attr_accessor :config attr_accessor :js attr_accessor :css class << self ## # get / set the vendor name # def vendor_name(name=nil) @name ||= name end ## # Install a vendor # @return {Xmvc::Vendor} # def install(options) unless vendor_name raise Vendor::Error.new("Vendors must specify a name using the class-method vendor_name") end prepare_vendor(options) do |vendor| vendor.say_status("install", vendor.class.to_s) vendor.invoke(:install) begin vendor.copy_file(CONFIG_FILENAME, CONFIG_FILENAME) unless File.exists? CONFIG_FILENAME rescue StandardError => e raise SpecNotFound.new("Vendors must have available in their source-directory, a file named 'vendor.yml' or the class-method #source_root defined, which provides the base-path to the location of vendor.yml. Xmvc::Vendor attempted to automatically copy this file into the installation directory.") end end end ## # Loads and configures an Xmvc::Vendor instance # @return {Xmvc::Vendor} # def load(options) prepare_vendor(options) end ## # @param {Xmvc::Vendor} vendor # @param {Symbol} environment # @param {Symbol} ext File-extension of requested resource, :js, :css, etc # @param {String} host # def asset_urls(vendor, environment = :development, ext = :js, host=nil) config = vendor.config host = config['host'] unless host type = Xmvc.asset_dir(ext) rs = [] # Note, we check config['host'] here, not host as defined above, since cachefly urls # override host requested as function param. Host as in http://extjs.cachefly.net/... if config['host'].include?("http://") config[type].each do |file| rs << cachefly_url(environment, config['host'], config['name'], file, config['version'], ext) end elsif host != Xmvc::PUBLIC_PATH rs << File.join("/#{host}", "#{config['name']}.#{ext.to_s}") else rs << Xmvc.build_path(environment, host, config['name'], config['version'], ext) end rs end def bundle_js(vendor, sec, root=Xmvc::PUBLIC_PATH) config = vendor.config vendor.destination_root = root path = "" vendor.inside "javascripts" do vendor.empty_directory(config['name']) unless File.exists? config['name'] vendor.inside config['name'] do |dir| path = File.expand_path(File.join(dir, Xmvc.build_name(vendor.options[:environment]||:development, config['name'], config['version'], :js))) sec.concatenation.save_to(path) end end path end def bundle_css(vendor) vendor.destination_root = Xmvc::PUBLIC_PATH vendor.inside "stylesheets" do Xmvc.ui.warn(' - bundle css -- no implementation') end end protected def cachefly_url(environment, host, name, file, version, ext) version = (version.nil? or version.empty?) ? "" : "-#{version}" File.join(host, "#{name}#{version}", "#{file}.#{ext.to_s}") end def prepare_vendor(options) vendor = new([], options) vendor.inside(File.expand_path(options[:root])) do vendor.inside vendor_name do |root| vendor.destination_root = root yield vendor if block_given? configure(vendor) end end vendor end def defaults @defaults ||= { "name" => "", "host" => "public", # <-- http://foo.cachefly.net, /sprockets, /public, (defaults to /public) "version" => "", "javascripts" => [], "stylesheets" => [] } end def configure(vendor) begin vendor.config = defaults.merge(YAML.load_file(File.join(vendor.destination_root, CONFIG_FILENAME))) # Enforce config name to that defined in the class, ignoring that # defined in YAML, which can't be trusted. #vendor_name in the class-extension is the only # way to ID a vendor. vendor.config.update("name" => vendor_name) rescue StandardError => e raise SpecNotFound.new("Tried to load vendor #{vendor_name} but found no #{CONFIG_FILENAME} in the directory #{vendor.destination_root}") end vendor end end # end Vendor class desc "secretary", "Return this vendor's Sprockets::Secretary" def secretary(type = :js) say_status "secretary", self.class.to_s case type when :js then @secretary ||= Sprockets::Secretary.new({ :root => destination_root, :source_files => config["javascripts"] }) end end desc "bundle", "Bundle a vendor's assets" def bundle(sec = secretary(:js)) # ugly hack for root in order to satisfy both Sprockts helper and CLI. root = (self.class.vendor_name == :app) ? Xmvc::PUBLIC_PATH : "../#{Xmvc::PUBLIC_PATH}" self.class.bundle_js(self, sec, root) unless config['host'].include?('http://') self.class.bundle_css(self) unless config['host'].include?('http://') end end end