lib/jim/installer.rb in jim-0.1.2 vs lib/jim/installer.rb in jim-0.2.0

- old
+ new

@@ -1,88 +1,204 @@ module Jim + # Installer is the workhorse of Jim. It handles taking an install path + # (a url, a local path, anything that Downlow.get can handle), staging it + # into a temporary directory and extracting the file(s) into a path for the + # specific name and version of the lib. names and versions are determined + # automatically or can be passed in as options. class Installer + IGNORE_DIRS = %w{ + vendor + external + test + tests + unit + site + examples + demo + min + \_([^\/]+) + \.([^\/]+) + } + + # get the tmp_root where files are staged def self.tmp_root @tmp_root ||= Pathname.new('/tmp/jim') end + # set the tmp_root where files are staged. Default: '/tmp/jim' def self.tmp_root=(new_tmp_root) @tmp_root = Pathname.new(new_tmp_root) end + + attr_reader :fetch_path, :install_path, :options, :fetched_path, :name, :version, :package_json - attr_reader :fetch_path, :install_path, :options, - :fetched_path, :name, :version - + # create an installer. fetch_path is anything that Downlow can understand. + # install path is the final directory def initialize(fetch_path, install_path, options = {}) @fetch_path = Pathname.new(fetch_path) @install_path = Pathname.new(install_path) @options = options end + # fetch the file at fetch_path with and stage into a tmp directory. + # returns the staged directory of fetched file(s). def fetch logger.info "fetching #{fetch_path}" - @fetched_path = Downlow.get(fetch_path, tmp_path) + @fetched_path = Downlow.get(fetch_path, tmp_path, :tmp_dir => tmp_root) logger.debug "fetched #{@fetched_path}" @fetched_path end + # fetch and install the files determining their name and version if not provided. + # if the fetch_path contains a directory of files, it itterates over the directory + # installing each file that isn't in IGNORE_DIRS and a name and version can be + # determined for. It also installs a package.json file along side the JS file + # that contains meta data including the name and version, also merging with the + # original package.json if found. + # + # If options[:shallow] == true it will just copy the single file without any leading + # directories or a package.json. 'shallow' installation is used for Bundle#vendor def install fetch + parse_package_json determine_name_and_version + + if !name || name.to_s =~ /^\s*$/ # blank + raise(Jim::InstallError, "could not determine name for #{@fetched_path}") + end + logger.info "installing #{name} #{version}" logger.debug "fetched_path #{@fetched_path}" + if options[:shallow] - final_path = install_path + "#{name}#{fetched_path.extname}" + shallow_filename = [name, (version == "0" ? nil : version)].compact.join('-') + final_path = install_path + "#{shallow_filename}#{fetched_path.extname}" else - final_dir = install_path + 'lib' + "#{name}-#{version}" - final_path = (fetched_path.to_s =~ /\.js$/) ? final_dir + "#{name}.js" : final_dir + final_path = install_path + 'lib' + "#{name}-#{version}" + "#{name}.js" end + + if @fetched_path.directory? + # install every js file + installed_paths = [] + sub_options = options.merge({ + :name => nil, + :version => nil, + :parent_version => version, + :package_json => package_json.merge("name" => nil) + }) + Jim.each_path_in_directories([@fetched_path], '.js', IGNORE_DIRS) do |subfile| + logger.info "found file #{subfile}" + installed_paths << Jim::Installer.new(subfile, install_path, sub_options).install + end + logger.info "extracted to #{install_path}, #{installed_paths.length} file(s)" + return installed_paths + end + logger.debug "installing to #{final_path}" if final_path.exist? logger.debug "#{final_path} already exists" - options[:force] ? FileUtils.rm_rf(final_path) : raise(Jim::FileExists.new(final_path)) + if options[:force] + FileUtils.rm_rf(final_path) + elsif Digest::MD5.hexdigest(File.read(final_path)) == Digest::MD5.hexdigest(File.read(@fetched_path)) + logger.info "duplicate file, skipping" + return final_path + else + raise(Jim::FileExists.new(final_path)) + end end - Downlow.extract(@fetched_path, :destination => final_path) + + Downlow.extract(@fetched_path, :destination => final_path, :tmp_dir => tmp_root) + # install json + install_package_json(final_path.dirname + 'package.json') if !options[:shallow] installed = final_path.directory? ? Dir.glob(final_path + '**/*').length : 1 - logger.info "Extracted to #{final_path}, #{installed} file(s)" + logger.info "extracted to #{final_path}, #{installed} file(s)" final_path ensure - FileUtils.rm_rf(fetched_path) if fetched_path.exist? + FileUtils.rm_rf(@fetched_path) if @fetched_path && @fetched_path.exist? final_path end - + + # determine the name and version of the @fetched_path. Tries a number of + # strategies in order until both name and version are found: + # + # * from options (options[:name] ...) + # * from comments (// name: ) + # * from a package.json ({"name": }) + # * from the filename (name-1.0.js) + # + # If no version can be found, version is set as "0" def determine_name_and_version (name && version) || name_and_version_from_options || name_and_version_from_comments || name_and_version_from_package_json || name_and_version_from_filename - @version ||= '0' + @version = (version == "0" && options[:parent_version]) ? options[:parent_version] : version end private def tmp_root - self.class.tmp_root + @tmp_root ||= make_tmp_root end def tmp_dir - dir = tmp_root + fetch_path.stem - dir.mkpath - dir + @tmp_dir ||= make_tmp_dir end def tmp_path tmp_dir + fetch_path.basename end def logger Jim.logger end + + def make_tmp_root + self.class.tmp_root + (Time.now.to_i + rand(10000)).to_s + end + + def make_tmp_dir + dir = tmp_root + fetch_path.stem + dir.mkpath + dir + end + + def parse_package_json + @package_json = @options[:package_json] || {} + package_json_path = if fetched_path.directory? + fetched_path + 'package.json' + elsif options[:shallow] && fetch_path.file? + fetch_path.dirname + 'package.json' + else + fetched_path.dirname + 'package.json' + end + logger.debug "package.json path: #{package_json_path}" + if package_json_path.readable? + @package_json = Yajl::Parser.parse(package_json_path.read) + end + end + def install_package_json(to_path, options = {}) + hash = @package_json.merge({ + "name" => name, + "version" => version, + "install" => { + "at" => Time.now.httpdate, + "from" => fetch_path, + "with" => "jim #{Jim::VERSION}" + } + }).merge(options) + Pathname.new(to_path).open('w') do |f| + Yajl::Encoder.encode(hash, f, :pretty => true) + end + end + def name_and_version_from_options @name = options[:name] if options[:name] && !name @version = options[:version] if options[:version] && !version + logger.debug "name and version from options: #{name} #{version}" name && version end def name_and_version_from_comments if fetched_path.file? @@ -95,25 +211,27 @@ if !version && /(\*|\/\/)\s+version:\s+([\d\w\.\-]+)/i.match(line) @version = $2 end end end + logger.debug "name and version from comments: #{name} #{version}" name && version end def name_and_version_from_package_json - if !fetched_path.file? && (fetched_path + 'package.json').readable? - sname, sversion = VersionParser.parse_package_json((fetched_path + "package.json").read) - @name ||= sname - @version ||= sversion - end + parse_package_json if !@package_json + sname, sversion = @package_json['name'], @package_json['version'] + @name ||= sname + @version ||= sversion + logger.debug "name and version from package.json: #{name} #{version}" name && version end def name_and_version_from_filename fname, fversion = VersionParser.parse_filename(fetched_path.basename.to_s) @name ||= fname @version ||= fversion + logger.debug "name and version from filename: #{name} #{version}" name && version end end end \ No newline at end of file