lib/generators/templates/application/common/merb.thor in merb-gen-0.9.13 vs lib/generators/templates/application/common/merb.thor in merb-gen-1.0

- old
+ new

@@ -3,11 +3,11 @@ require 'thor' require 'fileutils' require 'yaml' # Important - don't change this line or its position -MERB_THOR_VERSION = '0.1.0' +MERB_THOR_VERSION = '0.2.1' ############################################################################## module ColorfulMessages @@ -333,11 +333,11 @@ require '#{then_req}' end # use gems dir if ../gems exists - eg. only for ./bin/#{bin_file_name} if File.directory?(gems_dir = File.join(File.dirname(__FILE__), '..', 'gems')) - $BUNDLE = true; Gem.clear_paths; Gem.path.replace([gems_dir]) + $BUNDLE = true; Gem.clear_paths; Gem.path.replace([File.expand_path(gems_dir)]) ENV["PATH"] = "\#{File.dirname(__FILE__)}:\#{gems_dir}/bin:\#{ENV["PATH"]}" if (local_gem = Dir[File.join(gems_dir, "specifications", "#{spec.name}-*.gemspec")].last) version = File.basename(local_gem)[/-([\\.\\d]+)\\.gemspec$/, 1] end end @@ -444,10 +444,14 @@ def self.included(base) base.send(:include, ColorfulMessages) base.extend ColorfulMessages end + def use_edge_gem_server + ::Gem.sources << 'http://edge.merbivore.com' + end + def source_manager @_source_manager ||= SourceManager.new(source_dir) end def extract_repositories(names) @@ -641,27 +645,542 @@ ############################################################################## $SILENT = true # don't output all the mess some rake package tasks spit out module Merb + + class Gem < Thor + include MerbThorHelper + extend GemManagement + + attr_accessor :system, :local, :missing + + global_method_options = { + "--merb-root" => :optional, # the directory to operate on + "--version" => :optional, # gather specific version of gem + "--ignore-dependencies" => :boolean # don't install sub-dependencies + } + + method_options global_method_options + def initialize(*args); super; end + + # List gems that match the specified criteria. + # + # By default all local gems are listed. When the first argument is 'all' the + # list is partitioned into system an local gems; specify 'system' to show + # only system gems. A second argument can be used to filter on a set of known + # components, like all merb-more gems for example. + # + # Examples: + # + # merb:gem:list # list all local gems - the default + # merb:gem:list all # list system and local gems + # merb:gem:list system # list only system gems + # merb:gem:list all merb-more # list only merb-more related gems + # merb:gem:list --version 0.9.8 # list gems that match the version + + desc 'list [all|local|system] [comp]', 'Show installed gems' + def list(filter = 'local', comp = nil) + deps = comp ? Merb::Stack.select_component_dependencies(dependencies, comp) : dependencies + self.system, self.local, self.missing = Merb::Gem.partition_dependencies(deps, gem_dir) + case filter + when 'all' + message 'Installed system gems:' + display_gemspecs(system) + message 'Installed local gems:' + display_gemspecs(local) + when 'system' + message 'Installed system gems:' + display_gemspecs(system) + when 'local' + message 'Installed local gems:' + display_gemspecs(local) + else + warning "Invalid listing filter '#{filter}'" + end + end + + # Install the specified gems. + # + # All arguments should be names of gems to install. + # + # When :force => true then any existing versions of the gems to be installed + # will be uninstalled first. It's important to note that so-called meta-gems + # or gems that exactly match a set of Merb::Stack.components will have their + # sub-gems uninstalled too. For example, uninstalling merb-more will install + # all contained gems: merb-action-args, merb-assets, merb-gen, ... + # + # Examples: + # + # merb:gem:install merb-core merb-slices # install all specified gems + # merb:gem:install merb-core --version 0.9.8 # install a specific version of a gem + # merb:gem:install merb-core --force # uninstall then subsequently install the gem + # merb:gem:install merb-core --cache # try to install locally from system gems + # merb:gem:install merb --merb-edge # install from edge.merbivore.com + + desc 'install GEM_NAME [GEM_NAME, ...]', 'Install a gem from rubygems' + method_options "--cache" => :boolean, + "--dry-run" => :boolean, + "--force" => :boolean, + "--merb-edge" => :boolean + def install(*names) + opts = { :version => options[:version], :cache => options[:cache] } + use_edge_gem_server if options[:"merb-edge"] + current_gem = nil + + # uninstall existing gems of the ones we're going to install + uninstall(*names) if options[:force] + + message "Installing #{names.length} #{names.length == 1 ? 'gem' : 'gems'}..." + puts "This may take a while..." + + names.each do |gem_name| + current_gem = gem_name + if dry_run? + note "Installing #{current_gem}..." + else + message "Installing #{current_gem}..." + self.class.install(gem_name, default_install_options.merge(opts)) + end + end + rescue => e + error "Failed to install #{current_gem ? current_gem : 'gem'} (#{e.message})" + end + + # Uninstall the specified gems. + # + # By default all specified gems are uninstalled. It's important to note that + # so-called meta-gems or gems that match a set of Merb::Stack.components will + # have their sub-gems uninstalled too. For example, uninstalling merb-more + # will install all contained gems: merb-action-args, merb-assets, ... + # + # Existing dependencies will be clobbered; when :force => true then all gems + # will be cleared, otherwise only existing local dependencies of the + # matching component set will be removed. + # + # Examples: + # + # merb:gem:uninstall merb-core merb-slices # uninstall all specified gems + # merb:gem:uninstall merb-core --version 0.9.8 # uninstall a specific version of a gem + + desc 'uninstall GEM_NAME [GEM_NAME, ...]', 'Unstall a gem' + method_options "--dry-run" => :boolean + def uninstall(*names) + opts = { :version => options[:version] } + current_gem = nil + if dry_run? + note "Uninstalling any existing gems of: #{names.join(', ')}" + else + message "Uninstalling any existing gems of: #{names.join(', ')}" + names.each do |gem_name| + current_gem = gem_name + Merb::Gem.uninstall(gem_name, default_uninstall_options) rescue nil + # if this gem is a meta-gem or a component set name, remove sub-gems + (Merb::Stack.components(gem_name) || []).each do |comp| + Merb::Gem.uninstall(comp, default_uninstall_options) rescue nil + end + end + end + rescue => e + error "Failed to uninstall #{current_gem ? current_gem : 'gem'} (#{e.message})" + end + + # Recreate all gems from gems/cache on the current platform. + # + # This task should be executed as part of a deployment setup, where the + # deployment system runs this after the app has been installed. + # Usually triggered by Capistrano, God... + # + # It will regenerate gems from the bundled gems cache for any gem that has + # C extensions - which need to be recompiled for the target deployment platform. + # + # Note: at least gems/cache and gems/specifications should be in your SCM. + + desc 'redeploy', 'Recreate all gems on the current platform' + method_options "--dry-run" => :boolean, "--force" => :boolean + def redeploy + require 'tempfile' # for Dir::tmpdir access + if gem_dir && File.directory?(cache_dir = File.join(gem_dir, 'cache')) + specs = local_gemspecs + message "Recreating #{specs.length} gems from cache..." + puts "This may take a while..." + specs.each do |gemspec| + if File.exists?(gem_file = File.join(cache_dir, "#{gemspec.full_name}.gem")) + gem_file_copy = File.join(Dir::tmpdir, File.basename(gem_file)) + if dry_run? + note "Recreating #{gemspec.full_name}" + else + message "Recreating #{gemspec.full_name}" + if options[:force] && File.directory?(gem = File.join(gem_dir, 'gems', gemspec.full_name)) + puts "Removing existing #{gemspec.full_name}" + FileUtils.rm_rf(gem) + end + # Copy the gem to a temporary file, because otherwise RubyGems/FileUtils + # will complain about copying identical files (same source/destination). + FileUtils.cp(gem_file, gem_file_copy) + Merb::Gem.install(gem_file_copy, :install_dir => gem_dir, :ignore_dependencies => true) + File.delete(gem_file_copy) + end + end + end + else + error "No application local gems directory found" + end + end + + private + + # Return dependencies for all installed gems; both system-wide and locally; + # optionally filters on :version requirement. + def dependencies + version_req = if options[:version] + ::Gem::Requirement.create(options[:version]) + else + ::Gem::Requirement.default + end + if gem_dir + ::Gem.clear_paths; ::Gem.path.unshift(gem_dir) + ::Gem.source_index.refresh! + end + deps = [] + ::Gem.source_index.each do |fullname, gemspec| + if version_req.satisfied_by?(gemspec.version) + deps << ::Gem::Dependency.new(gemspec.name, "= #{gemspec.version}") + end + end + ::Gem.clear_paths if gem_dir + deps.sort + end + + public + + # Install gem with some default options. + def self.install(name, options = {}) + defaults = {} + defaults[:cache] = false unless opts[:install_dir] + install_gem(name, defaults.merge(options)) + end + + # Uninstall gem with some default options. + def self.uninstall(name, options = {}) + defaults = { :ignore => true, :executables => true } + uninstall_gem(name, defaults.merge(options)) + end + + end + + class Tasks < Thor + + include MerbThorHelper + + # Show merb.thor version information + # + # merb:tasks:version # show the current version info + # merb:tasks:version --info # show extended version info + + desc 'version', 'Show verion info' + method_options "--info" => :boolean + def version + message "Currently installed merb.thor version: #{MERB_THOR_VERSION}" + if options[:version] + self.options = { :"dry-run" => true } + self.update # run update task with dry-run enabled + end + end + + # Update merb.thor tasks from remotely available version + # + # merb:tasks:update # update merb.thor + # merb:tasks:update --force # force-update merb.thor + # merb:tasks:update --dry-run # show version info only + + desc 'update [URL]', 'Fetch the latest merb.thor and install it locally' + method_options "--dry-run" => :boolean, "--force" => :boolean + def update(url = 'http://merbivore.com/merb.thor') + require 'open-uri' + require 'rubygems/version' + remote_file = open(url) + code = remote_file.read + + # Extract version information from the source code + if version = code[/^MERB_THOR_VERSION\s?=\s?('|")([\.\d]+)('|")/,2] + # borrow version comparison from rubygems' Version class + current_version = ::Gem::Version.new(MERB_THOR_VERSION) + remote_version = ::Gem::Version.new(version) + + if current_version >= remote_version + puts "currently installed: #{current_version}" + if current_version != remote_version + puts "available version: #{remote_version}" + end + info "No update of merb.thor necessary#{options[:force] ? ' (forced)' : ''}" + proceed = options[:force] + elsif current_version < remote_version + puts "currently installed: #{current_version}" + puts "available version: #{remote_version}" + proceed = true + end + + if proceed && !dry_run? + File.open(File.join(__FILE__), 'w') do |f| + f.write(code) + end + success "Installed the latest merb.thor (v#{version})" + end + else + raise "invalid source-code data" + end + rescue OpenURI::HTTPError + error "Error opening #{url}" + rescue => e + error "An error occurred (#{e.message})" + end + + end + + #### MORE LOW-LEVEL TASKS #### + + class Source < Thor + + group 'core' + + include MerbThorHelper + extend GemManagement + + attr_accessor :system, :local, :missing + + global_method_options = { + "--merb-root" => :optional, # the directory to operate on + "--ignore-dependencies" => :boolean, # don't install sub-dependencies + "--sources" => :optional # a yml config to grab sources from + } + + method_options global_method_options + def initialize(*args); super; end + + # List source repositories, of either local or known sources. + # + # Examples: + # + # merb:source:list # list all local sources + # merb:source:list available # list all known sources + + desc 'list [local|available]', 'Show git source repositories' + def list(mode = 'local') + if mode == 'available' + message 'Available source repositories:' + repos = self.class.repos(options[:sources]) + repos.keys.sort.each { |name| puts "- #{name}: #{repos[name]}" } + elsif mode == 'local' + message 'Current source repositories:' + Dir[File.join(source_dir, '*')].each do |src| + next unless File.directory?(src) + src_name = File.basename(src) + unless (repos = source_manager.existing_repos(src_name)).empty? + puts "#{src_name}" + repos.keys.sort.each { |b| puts "- #{b}: #{repos[b]}" } + end + end + else + error "Unknown listing: #{mode}" + end + end + + # Install the specified gems. + # + # All arguments should be names of gems to install. + # + # When :force => true then any existing versions of the gems to be installed + # will be uninstalled first. It's important to note that so-called meta-gems + # or gems that exactly match a set of Merb::Stack.components will have their + # sub-gems uninstalled too. For example, uninstalling merb-more will install + # all contained gems: merb-action-args, merb-assets, merb-gen, ... + # + # Examples: + # + # merb:source:install merb-core merb-slices # install all specified gems + # merb:source:install merb-core --force # uninstall then subsequently install the gem + # merb:source:install merb-core --wipe # clear repo then install the gem + + desc 'install GEM_NAME [GEM_NAME, ...]', 'Install a gem from git source/edge' + method_options "--dry-run" => :boolean, + "--force" => :boolean, + "--wipe" => :boolean + def install(*names) + use_edge_gem_server + # uninstall existing gems of the ones we're going to install + uninstall(*names) if options[:force] || options[:wipe] + + # We want dependencies instead of just names + deps = names.map { |n| ::Gem::Dependency.new(n, ::Gem::Requirement.default) } + + # Selectively update repositories for the matching dependencies + update_dependency_repositories(deps) unless dry_run? + + current_gem = nil + deps.each do |dependency| + current_gem = dependency.name + if dry_run? + note "Installing #{current_gem} from source..." + else + message "Installing #{current_gem} from source..." + puts "This may take a while..." + unless install_dependency_from_source(dependency) + raise "gem source not found" + end + end + end + rescue => e + error "Failed to install #{current_gem ? current_gem : 'gem'} (#{e.message})" + end + + # Uninstall the specified gems. + # + # By default all specified gems are uninstalled. It's important to note that + # so-called meta-gems or gems that match a set of Merb::Stack.components will + # have their sub-gems uninstalled too. For example, uninstalling merb-more + # will install all contained gems: merb-action-args, merb-assets, ... + # + # Existing dependencies will be clobbered; when :force => true then all gems + # will be cleared, otherwise only existing local dependencies of the + # matching component set will be removed. Additionally when :wipe => true, + # the matching git repositories will be removed from the source directory. + # + # Examples: + # + # merb:source:uninstall merb-core merb-slices # uninstall all specified gems + # merb:source:uninstall merb-core --wipe # force-uninstall a gem and clear repo + + desc 'uninstall GEM_NAME [GEM_NAME, ...]', 'Unstall a gem (specify --force to remove the repo)' + method_options "--version" => :optional, "--dry-run" => :boolean, "--wipe" => :boolean + def uninstall(*names) + # Remove the repos that contain the gem + if options[:wipe] + extract_repositories(names).each do |(name, url)| + if File.directory?(src = File.join(source_dir, name)) + if dry_run? + note "Removing #{src}..." + else + info "Removing #{src}..." + FileUtils.rm_rf(src) + end + end + end + end + + # Use the Merb::Gem#uninstall task to handle this + gem_tasks = Merb::Gem.new + gem_tasks.options = options + gem_tasks.uninstall(*names) + end + + # Update the specified source repositories. + # + # The arguments can be actual repository names (from Merb::Source.repos) + # or names of known merb stack gems. If the repo doesn't exist already, + # it will be created and cloned. + # + # merb:source:pull merb-core # update source of specified gem + # merb:source:pull merb-slices # implicitly updates merb-more + + desc 'pull REPO_NAME [GEM_NAME, ...]', 'Update git source repository from edge' + def pull(*names) + repos = extract_repositories(names) + update_repositories(repos) + unless repos.empty? + message "Updated the following repositories:" + repos.each { |name, url| puts "- #{name}: #{url}" } + else + warning "No repositories found to update!" + end + end + + # Clone a git repository into ./src. + + # The repository can be a direct git url or a known -named- repository. + # + # Examples: + # + # merb:source:clone merb-core + # merb:source:clone dm-core awesome-repo + # merb:source:clone dm-core --sources ./path/to/sources.yml + # merb:source:clone git://github.com/sam/dm-core.git + + desc 'clone (REPO_NAME|URL) [DIR_NAME]', 'Clone git source repository by name or url' + def clone(repository, name = nil) + if repository =~ /^git:\/\// + repository_url = repository + repository_name = File.basename(repository_url, '.git') + elsif url = Merb::Source.repo(repository, options[:sources]) + repository_url = url + repository_name = repository + end + source_manager.clone(name || repository_name, repository_url) + end + + # Git repository sources - pass source_config option to load a yaml + # configuration file - defaults to ./config/git-sources.yml and + # ~/.merb/git-sources.yml - which you need to create yourself. + # + # Example of contents: + # + # merb-core: git://github.com/myfork/merb-core.git + # merb-more: git://github.com/myfork/merb-more.git + + def self.repos(source_config = nil) + source_config ||= begin + local_config = File.join(Dir.pwd, 'config', 'git-sources.yml') + user_config = File.join(ENV["HOME"] || ENV["APPDATA"], '.merb', 'git-sources.yml') + File.exists?(local_config) ? local_config : user_config + end + if source_config && File.exists?(source_config) + default_repos.merge(YAML.load(File.read(source_config))) + else + default_repos + end + end + + def self.repo(name, source_config = nil) + self.repos(source_config)[name] + end + + # Default Git repositories + def self.default_repos + @_default_repos ||= { + 'merb' => "git://github.com/wycats/merb.git", + 'merb-plugins' => "git://github.com/wycats/merb-plugins.git", + 'extlib' => "git://github.com/sam/extlib.git", + 'dm-core' => "git://github.com/sam/dm-core.git", + 'dm-more' => "git://github.com/sam/dm-more.git", + 'sequel' => "git://github.com/wayneeseguin/sequel.git", + 'do' => "git://github.com/sam/do.git", + 'thor' => "git://github.com/wycats/thor.git", + 'rake' => "git://github.com/jimweirich/rake.git" + } + end + + end + class Dependencies < Thor + + group 'core' # The Dependencies tasks will install dependencies based on actual application # dependencies. For this, the application is queried for any dependencies. # All operations will be performed within this context. - attr_accessor :system, :local, :missing + attr_accessor :system, :local, :missing, :extract_dependencies include MerbThorHelper global_method_options = { "--merb-root" => :optional, # the directory to operate on - "--include-dependencies" => :boolean, # gather sub-dependencies + "--ignore-dependencies" => :boolean, # ignore sub-dependencies "--stack" => :boolean, # gather only stack dependencies "--no-stack" => :boolean, # gather only non-stack dependencies - "--config" => :boolean, # gather dependencies from yaml config + "--extract" => :boolean, # gather dependencies from the app itself "--config-file" => :optional, # gather from the specified yaml config "--version" => :optional # gather specific version of framework } method_options global_method_options @@ -679,11 +1198,11 @@ # merb:dependencies:list # list all dependencies - the default # merb:dependencies:list local # list only local gems # merb:dependencies:list all merb-more # list only merb-more related dependencies # merb:dependencies:list --stack # list framework dependencies # merb:dependencies:list --no-stack # list 3rd party dependencies - # merb:dependencies:list --config # list dependencies from the default config + # merb:dependencies:list --extract # list dependencies by extracting them # merb:dependencies:list --config-file file.yml # list from the specified config file desc 'list [all|local|system|missing] [comp]', 'Show application dependencies' def list(filter = 'all', comp = nil) deps = comp ? Merb::Stack.select_component_dependencies(dependencies, comp) : dependencies @@ -739,11 +1258,11 @@ # merb:dependencies:install stable --version 0.9.8 # install a specific version of the framework # merb:dependencies:install stable missing # install currently missing gems locally # merb:dependencies:install stable merb-more # install only merb-more related dependencies # merb:dependencies:install stable --stack # install framework dependencies # merb:dependencies:install stable --no-stack # install 3rd party dependencies - # merb:dependencies:install stable --config # read dependencies from the default config + # merb:dependencies:install stable --extract # extract dependencies from the actual app # merb:dependencies:install stable --config-file file.yml # read from the specified config file # # In addition to the options above, edge install uses the following: # # merb:dependencies:install edge # install all dependencies using edge strategy @@ -782,10 +1301,11 @@ if deps.empty? warning "No dependencies to install..." else puts "#{deps.length} dependencies to install..." + puts "This may take a while..." install_dependencies(strategy, deps, clobber) end # Show current dependency info now that we're done puts # Seperate output @@ -810,11 +1330,10 @@ # # Examples: # # merb:dependencies:uninstall # uninstall all dependencies - the default # merb:dependencies:uninstall merb-more # uninstall merb-more related gems locally - # merb:dependencies:uninstall --config # read dependencies from the default config desc 'uninstall [comp]', 'Uninstall application dependencies' method_options "--dry-run" => :boolean, "--force" => :boolean def uninstall(comp = nil) # If comp given, filter on known stack components @@ -822,46 +1341,21 @@ self.system, self.local, self.missing = Merb::Gem.partition_dependencies(deps, gem_dir) # Clobber existing local dependencies - based on self.local clobber_dependencies! end - # Recreate binary gems on the current platform. + # Recreate all gems from gems/cache on the current platform. # - # This task should be executed as part of a deployment setup, where the - # deployment system runs this after the app has been installed. - # Usually triggered by Capistrano, God... - # - # It will regenerate gems from the bundled gems cache for any gem that has - # C extensions - which need to be recompiled for the target deployment platform. - # - # Note: gems/cache should be in your SCM for this to work correctly. + # Note: use merb:gem:redeploy instead - desc 'redeploy', 'Recreate any binary gems on the target platform' - method_options "--dry-run" => :boolean + desc 'redeploy', 'Recreate all gems on the current platform' + method_options "--dry-run" => :boolean, "--force" => :boolean def redeploy - require 'tempfile' # for Dir::tmpdir access - if gem_dir && File.directory?(cache_dir = File.join(gem_dir, 'cache')) - local_gemspecs.each do |gemspec| - unless gemspec.extensions.empty? - if File.exists?(gem_file = File.join(cache_dir, "#{gemspec.full_name}.gem")) - gem_file_copy = File.join(Dir::tmpdir, File.basename(gem_file)) - if dry_run? - note "Recreating #{gemspec.full_name}" - else - message "Recreating #{gemspec.full_name}" - # Copy the gem to a temporary file, because otherwise RubyGems/FileUtils - # will complain about copying identical files (same source/destination). - FileUtils.cp(gem_file, gem_file_copy) - Merb::Gem.install(gem_file_copy, :install_dir => gem_dir) - File.delete(gem_file_copy) - end - end - end - end - else - error "No application local gems directory found" - end + warning 'Warning: merb:dependencies:redeploy has been deprecated - use merb:gem:redeploy instead' + gem = Merb::Gem.new + gem.options = options + gem.redeploy end # Create a dependencies configuration file. # # A configuration yaml file will be created from the extracted application @@ -880,10 +1374,11 @@ # merb:dependencies:configure --config-file file.yml # write to the specified config file desc 'configure [comp]', 'Create a dependencies config file' method_options "--dry-run" => :boolean, "--force" => :boolean, "--versions" => :boolean def configure(comp = nil) + self.extract_dependencies = true # of course we need to consult the app itself # If comp given, filter on known stack components deps = comp ? Merb::Stack.select_component_dependencies(dependencies, comp) : dependencies # If --versions is set, update the version_requirements with the actual version available if options[:versions] @@ -946,17 +1441,17 @@ end false end def dependencies - if use_config? - # Use preconfigured dependencies from yaml file - deps = config_dependencies - else + if extract_dependencies? # Extract dependencies from the current application deps = Merb::Stack.core_dependencies(gem_dir, ignore_dependencies?) - deps += Merb::Dependencies.extract_dependencies(working_dir) + deps += Merb::Dependencies.extract_dependencies(working_dir) + else + # Use preconfigured dependencies from yaml file + deps = config_dependencies end stack_components = Merb::Stack.components if options[:stack] @@ -988,16 +1483,17 @@ def config_dependencies if File.exists?(config_file) self.class.parse_dependencies_yaml(File.read(config_file)) else + warning "No dependencies.yml file found at: #{config_file}" [] end end - def use_config? - options[:config] || options[:"config-file"] + def extract_dependencies? + options[:extract] || extract_dependencies end def config_file @config_file ||= begin options[:"config-file"] || File.join(working_dir, 'config', 'dependencies.yml') @@ -1045,10 +1541,11 @@ end installed_from_rubygems end def edge_strategy(deps) + use_edge_gem_server installed_from_rubygems = [] # Selectively update repositories for the matching dependencies update_dependency_repositories(deps) unless dry_run? @@ -1105,12 +1602,11 @@ end Merb::BootLoader::Dependencies.dependencies rescue StandardError => e error "Couldn't extract dependencies from application!" error e.message - puts "Make sure you're executing the task from your app (--merb-root), or" - puts "specify a config option (--config or --config-file=YAML_FILE)" + puts "Make sure you're executing the task from your app (--merb-root)" return [] rescue SystemExit error "Couldn't extract dependencies from application!" error "application failed to run" puts "Please check if your application runs using 'merb'; for example," @@ -1132,14 +1628,16 @@ end end dependencies end - end + end class Stack < Thor + group 'core' + # The Stack tasks will install dependencies based on known sets of gems, # regardless of actual application dependency settings. DM_STACK = %w[ extlib @@ -1306,10 +1804,11 @@ "--sources" => :optional, "--force" => :boolean, "--dry-run" => :boolean, "--strategy" => :optional def install(*comps) + use_edge_gem_server if options[:edge] mngr = self.dependency_manager deps = gather_dependencies(comps) mngr.system, mngr.local, mngr.missing = Merb::Gem.partition_dependencies(deps, gem_dir) mngr.install_dependencies(strategy, deps) end @@ -1514,472 +2013,8 @@ items.include?(item) ? (set_name = set) : nil end set_name end - end - - class Tasks < Thor - - include MerbThorHelper - - # Show merb.thor version information - # - # merb:tasks:version # show the current version info - # merb:tasks:version --info # show extended version info - - desc 'version', 'Show verion info' - method_options "--info" => :boolean - def version - message "Currently installed merb.thor version: #{MERB_THOR_VERSION}" - if options[:version] - self.options = { :"dry-run" => true } - self.update # run update task with dry-run enabled - end - end - - # Update merb.thor tasks from remotely available version - # - # merb:tasks:update # update merb.thor - # merb:tasks:update --force # force-update merb.thor - # merb:tasks:update --dry-run # show version info only - - desc 'update [URL]', 'Fetch the latest merb.thor and install it locally' - method_options "--dry-run" => :boolean, "--force" => :boolean - def update(url = 'http://merbivore.com/merb.thor') - require 'open-uri' - require 'rubygems/version' - remote_file = open(url) - code = remote_file.read - - # Extract version information from the source code - if version = code[/^MERB_THOR_VERSION\s?=\s?('|")([\.\d]+)('|")/,2] - # borrow version comparison from rubygems' Version class - current_version = ::Gem::Version.new(MERB_THOR_VERSION) - remote_version = ::Gem::Version.new(version) - - if current_version >= remote_version - puts "currently installed: #{current_version}" - if current_version != remote_version - puts "available version: #{remote_version}" - end - info "No update of merb.thor necessary#{options[:force] ? ' (forced)' : ''}" - proceed = options[:force] - elsif current_version < remote_version - puts "currently installed: #{current_version}" - puts "available version: #{remote_version}" - proceed = true - end - - if proceed && !dry_run? - File.open(File.join(__FILE__), 'w') do |f| - f.write(code) - end - success "Installed the latest merb.thor (v#{version})" - end - else - raise "invalid source-code data" - end - rescue OpenURI::HTTPError - error "Error opening #{url}" - rescue => e - error "An error occurred (#{e.message})" - end - - end - - #### MORE LOW-LEVEL TASKS #### - - class Gem < Thor - - group 'core' - - include MerbThorHelper - extend GemManagement - - attr_accessor :system, :local, :missing - - global_method_options = { - "--merb-root" => :optional, # the directory to operate on - "--version" => :optional, # gather specific version of gem - "--ignore-dependencies" => :boolean # don't install sub-dependencies - } - - method_options global_method_options - def initialize(*args); super; end - - # List gems that match the specified criteria. - # - # By default all local gems are listed. When the first argument is 'all' the - # list is partitioned into system an local gems; specify 'system' to show - # only system gems. A second argument can be used to filter on a set of known - # components, like all merb-more gems for example. - # - # Examples: - # - # merb:gem:list # list all local gems - the default - # merb:gem:list all # list system and local gems - # merb:gem:list system # list only system gems - # merb:gem:list all merb-more # list only merb-more related gems - # merb:gem:list --version 0.9.8 # list gems that match the version - - desc 'list [all|local|system] [comp]', 'Show installed gems' - def list(filter = 'local', comp = nil) - deps = comp ? Merb::Stack.select_component_dependencies(dependencies, comp) : dependencies - self.system, self.local, self.missing = Merb::Gem.partition_dependencies(deps, gem_dir) - case filter - when 'all' - message 'Installed system gems:' - display_gemspecs(system) - message 'Installed local gems:' - display_gemspecs(local) - when 'system' - message 'Installed system gems:' - display_gemspecs(system) - when 'local' - message 'Installed local gems:' - display_gemspecs(local) - else - warning "Invalid listing filter '#{filter}'" - end - end - - # Install the specified gems. - # - # All arguments should be names of gems to install. - # - # When :force => true then any existing versions of the gems to be installed - # will be uninstalled first. It's important to note that so-called meta-gems - # or gems that exactly match a set of Merb::Stack.components will have their - # sub-gems uninstalled too. For example, uninstalling merb-more will install - # all contained gems: merb-action-args, merb-assets, merb-gen, ... - # - # Examples: - # - # merb:gem:install merb-core merb-slices # install all specified gems - # merb:gem:install merb-core --version 0.9.8 # install a specific version of a gem - # merb:gem:install merb-core --force # uninstall then subsequently install the gem - # merb:gem:install merb-core --cache # try to install locally from system gems - - desc 'install GEM_NAME [GEM_NAME, ...]', 'Install a gem from rubygems' - method_options "--cache" => :boolean, - "--dry-run" => :boolean, - "--force" => :boolean - def install(*names) - opts = { :version => options[:version], :cache => options[:cache] } - current_gem = nil - - # uninstall existing gems of the ones we're going to install - uninstall(*names) if options[:force] - - names.each do |gem_name| - current_gem = gem_name - if dry_run? - note "Installing #{current_gem}..." - else - message "Installing #{current_gem}..." - self.class.install(gem_name, default_install_options.merge(opts)) - end - end - rescue => e - error "Failed to install #{current_gem ? current_gem : 'gem'} (#{e.message})" - end - - # Uninstall the specified gems. - # - # By default all specified gems are uninstalled. It's important to note that - # so-called meta-gems or gems that match a set of Merb::Stack.components will - # have their sub-gems uninstalled too. For example, uninstalling merb-more - # will install all contained gems: merb-action-args, merb-assets, ... - # - # Existing dependencies will be clobbered; when :force => true then all gems - # will be cleared, otherwise only existing local dependencies of the - # matching component set will be removed. - # - # Examples: - # - # merb:gem:uninstall merb-core merb-slices # uninstall all specified gems - # merb:gem:uninstall merb-core --version 0.9.8 # uninstall a specific version of a gem - - desc 'uninstall GEM_NAME [GEM_NAME, ...]', 'Unstall a gem' - method_options "--dry-run" => :boolean - def uninstall(*names) - opts = { :version => options[:version] } - current_gem = nil - if dry_run? - note "Uninstalling any existing gems of: #{names.join(', ')}" - else - message "Uninstalling any existing gems of: #{names.join(', ')}" - names.each do |gem_name| - current_gem = gem_name - Merb::Gem.uninstall(gem_name, default_uninstall_options) rescue nil - # if this gem is a meta-gem or a component set name, remove sub-gems - (Merb::Stack.components(gem_name) || []).each do |comp| - Merb::Gem.uninstall(comp, default_uninstall_options) rescue nil - end - end - end - rescue => e - error "Failed to uninstall #{current_gem ? current_gem : 'gem'} (#{e.message})" - end - - private - - # Return dependencies for all installed gems; both system-wide and locally; - # optionally filters on :version requirement. - def dependencies - version_req = if options[:version] - ::Gem::Requirement.create(options[:version]) - else - ::Gem::Requirement.default - end - if gem_dir - ::Gem.clear_paths; ::Gem.path.unshift(gem_dir) - ::Gem.source_index.refresh! - end - deps = [] - ::Gem.source_index.each do |fullname, gemspec| - if version_req.satisfied_by?(gemspec.version) - deps << ::Gem::Dependency.new(gemspec.name, "= #{gemspec.version}") - end - end - ::Gem.clear_paths if gem_dir - deps.sort - end - - public - - # Install gem with some default options. - def self.install(name, options = {}) - defaults = {} - defaults[:cache] = false unless opts[:install_dir] - install_gem(name, defaults.merge(options)) - end - - # Uninstall gem with some default options. - def self.uninstall(name, options = {}) - defaults = { :ignore => true, :executables => true } - uninstall_gem(name, defaults.merge(options)) - end - - end - - class Source < Thor - - group 'core' - - include MerbThorHelper - extend GemManagement - - attr_accessor :system, :local, :missing - - global_method_options = { - "--merb-root" => :optional, # the directory to operate on - "--ignore-dependencies" => :boolean, # don't install sub-dependencies - "--sources" => :optional # a yml config to grab sources from - } - - method_options global_method_options - def initialize(*args); super; end - - # List source repositories, of either local or known sources. - # - # Examples: - # - # merb:source:list # list all local sources - # merb:source:list available # list all known sources - - desc 'list [local|available]', 'Show git source repositories' - def list(mode = 'local') - if mode == 'available' - message 'Available source repositories:' - repos = self.class.repos(options[:sources]) - repos.keys.sort.each { |name| puts "- #{name}: #{repos[name]}" } - elsif mode == 'local' - message 'Current source repositories:' - Dir[File.join(source_dir, '*')].each do |src| - next unless File.directory?(src) - src_name = File.basename(src) - unless (repos = source_manager.existing_repos(src_name)).empty? - puts "#{src_name}" - repos.keys.sort.each { |b| puts "- #{b}: #{repos[b]}" } - end - end - else - error "Unknown listing: #{mode}" - end - end - - # Install the specified gems. - # - # All arguments should be names of gems to install. - # - # When :force => true then any existing versions of the gems to be installed - # will be uninstalled first. It's important to note that so-called meta-gems - # or gems that exactly match a set of Merb::Stack.components will have their - # sub-gems uninstalled too. For example, uninstalling merb-more will install - # all contained gems: merb-action-args, merb-assets, merb-gen, ... - # - # Examples: - # - # merb:source:install merb-core merb-slices # install all specified gems - # merb:source:install merb-core --force # uninstall then subsequently install the gem - # merb:source:install merb-core --wipe # clear repo then install the gem - - desc 'install GEM_NAME [GEM_NAME, ...]', 'Install a gem from git source/edge' - method_options "--dry-run" => :boolean, - "--force" => :boolean, - "--wipe" => :boolean - def install(*names) - # uninstall existing gems of the ones we're going to install - uninstall(*names) if options[:force] || options[:wipe] - - # We want dependencies instead of just names - deps = names.map { |n| ::Gem::Dependency.new(n, ::Gem::Requirement.default) } - - # Selectively update repositories for the matching dependencies - update_dependency_repositories(deps) unless dry_run? - - current_gem = nil - deps.each do |dependency| - current_gem = dependency.name - if dry_run? - note "Installing #{current_gem} from source..." - else - message "Installing #{current_gem} from source..." - unless install_dependency_from_source(dependency) - raise "gem source not found" - end - end - end - rescue => e - error "Failed to install #{current_gem ? current_gem : 'gem'} (#{e.message})" - end - - # Uninstall the specified gems. - # - # By default all specified gems are uninstalled. It's important to note that - # so-called meta-gems or gems that match a set of Merb::Stack.components will - # have their sub-gems uninstalled too. For example, uninstalling merb-more - # will install all contained gems: merb-action-args, merb-assets, ... - # - # Existing dependencies will be clobbered; when :force => true then all gems - # will be cleared, otherwise only existing local dependencies of the - # matching component set will be removed. Additionally when :wipe => true, - # the matching git repositories will be removed from the source directory. - # - # Examples: - # - # merb:source:uninstall merb-core merb-slices # uninstall all specified gems - # merb:source:uninstall merb-core --wipe # force-uninstall a gem and clear repo - - desc 'uninstall GEM_NAME [GEM_NAME, ...]', 'Unstall a gem (specify --force to remove the repo)' - method_options "--version" => :optional, "--dry-run" => :boolean, "--wipe" => :boolean - def uninstall(*names) - # Remove the repos that contain the gem - if options[:wipe] - extract_repositories(names).each do |(name, url)| - if File.directory?(src = File.join(source_dir, name)) - if dry_run? - note "Removing #{src}..." - else - info "Removing #{src}..." - FileUtils.rm_rf(src) - end - end - end - end - - # Use the Merb::Gem#uninstall task to handle this - gem_tasks = Merb::Gem.new - gem_tasks.options = options - gem_tasks.uninstall(*names) - end - - # Update the specified source repositories. - # - # The arguments can be actual repository names (from Merb::Source.repos) - # or names of known merb stack gems. If the repo doesn't exist already, - # it will be created and cloned. - # - # merb:source:pull merb-core # update source of specified gem - # merb:source:pull merb-slices # implicitly updates merb-more - - desc 'pull REPO_NAME [GEM_NAME, ...]', 'Update git source repository from edge' - def pull(*names) - repos = extract_repositories(names) - update_repositories(repos) - unless repos.empty? - message "Updated the following repositories:" - repos.each { |name, url| puts "- #{name}: #{url}" } - else - warning "No repositories found to update!" - end - end - - # Clone a git repository into ./src. - - # The repository can be a direct git url or a known -named- repository. - # - # Examples: - # - # merb:source:clone merb-core - # merb:source:clone dm-core awesome-repo - # merb:source:clone dm-core --sources ./path/to/sources.yml - # merb:source:clone git://github.com/sam/dm-core.git - - desc 'clone (REPO_NAME|URL) [DIR_NAME]', 'Clone git source repository by name or url' - def clone(repository, name = nil) - if repository =~ /^git:\/\// - repository_url = repository - repository_name = File.basename(repository_url, '.git') - elsif url = Merb::Source.repo(repository, options[:sources]) - repository_url = url - repository_name = repository - end - source_manager.clone(name || repository_name, repository_url) - end - - # Git repository sources - pass source_config option to load a yaml - # configuration file - defaults to ./config/git-sources.yml and - # ~/.merb/git-sources.yml - which you need to create yourself. - # - # Example of contents: - # - # merb-core: git://github.com/myfork/merb-core.git - # merb-more: git://github.com/myfork/merb-more.git - - def self.repos(source_config = nil) - source_config ||= begin - local_config = File.join(Dir.pwd, 'config', 'git-sources.yml') - user_config = File.join(ENV["HOME"] || ENV["APPDATA"], '.merb', 'git-sources.yml') - File.exists?(local_config) ? local_config : user_config - end - if source_config && File.exists?(source_config) - default_repos.merge(YAML.load(File.read(source_config))) - else - default_repos - end - end - - def self.repo(name, source_config = nil) - self.repos(source_config)[name] - end - - # Default Git repositories - def self.default_repos - @_default_repos ||= { - 'merb' => "git://github.com/wycats/merb.git", - 'merb-plugins' => "git://github.com/wycats/merb-plugins.git", - 'extlib' => "git://github.com/sam/extlib.git", - 'dm-core' => "git://github.com/sam/dm-core.git", - 'dm-more' => "git://github.com/sam/dm-more.git", - 'sequel' => "git://github.com/wayneeseguin/sequel.git", - 'do' => "git://github.com/sam/do.git", - 'thor' => "git://github.com/wycats/thor.git", - 'rake' => "git://github.com/jimweirich/rake.git" - } - end - end end \ No newline at end of file