Refactored how repositories are defined and loaded. We can now support probing tons of different types of repositories by loading them just in time.

This commit is contained in:
Adam Sanderson 2010-12-30 09:56:16 -07:00
parent 147f927f84
commit 506be7f192
5 changed files with 209 additions and 119 deletions

44
bin/qw
View File

@ -1,38 +1,33 @@
#!/usr/bin/env ruby
# Add qwandry's library to the load path
# Add Qwandry's library to the load path
$:.unshift File.dirname(__FILE__) + '/../lib'
# Require it
# Require Qwandry's library
require "qwandry.rb"
# Create launcher
@qwandry = Qwandry::Launcher.new
# Load Qwandry's configuration files
config = Qwandry::Configuration
config.load_configuration_files
# Create a launcher
@launcher = Qwandry::Launcher.new
opts = OptionParser.new do |opts|
opts.banner = "Usage: qwandry [options] name"
opts.separator ""
opts.on("-r", "--repo LABELS", Array, "Search in LABELS, default: #{@qwandry.active.to_a.join(',')}","Available Repositories:", *@qwandry.repositories.keys.map{|k| " #{k}"}) do |labels|
@qwandry.active.replace(labels)
opts.on("-r", "--repo LABELS", Array, "Search in LABELS, default: #{config.default.join(',')}","Available Repository Types:", *config.configurations.map{|c| " #{c}"}) do |names|
config.default *names
end
opts.separator ""
opts.on("-e", "--editor EDITOR", "Use EDITOR to open the package") do |editor|
@editor = editor
@launcher.editor = editor
end
opts.separator "Additional Commands"
opts.on("--paths", "Prints all repositories and their paths") do
@qwandry.repositories.each do |label, entries|
puts "#{label} #{"[default]" if @qwandry.active.include? label}"
entries.each do |repo|
puts "\t#{repo.path} (#{repo.class.to_s.split('::').last})"
end
puts ""
end
exit(0)
end
opts.on("--customize", "Create and edit files for customizing Qwandry") do
dir = Qwandry.config_dir
if !dir
@ -43,7 +38,7 @@ opts = OptionParser.new do |opts|
Dir[File.dirname(__FILE__) + '/../templates/*'].each do |path|
FileUtils.cp(path, dir, :verbose=>true) unless File.exist?(path)
end
@qwandry.launch dir
@launcher.launch dir
end
exit(0)
end
@ -60,13 +55,13 @@ if ARGV.length == 0
exit 1
end
# Configure default values
@qwandry.editor = @editor if @editor
# Find the packages:
name = ARGV.join(' ')
packages = @qwandry.find(*ARGV)
packages = @launcher.find(*ARGV)
ARGV.clear # for the gets below
# There may be 0, 1, or many matches. If many, then
# ask the user which one to launch.
package = nil
case packages.length
when 0
@ -84,8 +79,9 @@ else
package = packages[index]
end
# If there is a package, then launch it.
if package
if @qwandry.launch(package)
if @launcher.launch(package)
# Exit code 0 for success
exit 0
else

View File

@ -21,6 +21,7 @@ module Qwandry
autoload :FlatRepository, "qwandry/flat_repository"
autoload :LibraryRepository, "qwandry/library_repository"
autoload :Package, "qwandry/package"
autoload :Configuration, "qwandry/configuration"
end
# If defined, Qwandry will use XDG_CONFIG_HOME as the xdg spec. If not it

View File

@ -0,0 +1,135 @@
module Qwandry
class Configuration
class << self
# Regsisters a new Qwandry configuration. Use in conjunction with Configuration#add like so:
#
# register 'projects' do
# add '~/Projects/personal'
# add '~/Projects/work'
# add '~/Experiments'
# end
#
def register name, &block
name = name.to_sym
builders[name] << block
end
# Sets the default configuration to launch, if no `configurations` are passed
# in, it returns their names.
def default(*configurations)
if configurations.empty?
@default ||= []
else
@default = configurations
end
end
# Returns the registered configurations
def configurations
builders.keys
end
# Loads a configuration file, and executes it in the context of the Qwandry::Configuration
# class. See default_configuration.rb for an example, or run:
#
# qw --customize
#
# For a sample customization file.
def load_configuration(path)
if File.exist?(path)
begin
eval IO.read(path), nil, path, 1
rescue Exception=>ex
STDERR.puts "Warning: error in configuration file: #{path.inspect}"
STDERR.puts "Exception: #{ex.message}"
STDERR.puts ex.backtrace
end
end
end
# Loads the Qwandry default configuration and then the user's custom
# configuration file if it is present.
def load_configuration_files
# load default configuration files
system_config_dir = File.join(File.dirname(__FILE__), 'configuration')
Dir[system_config_dir+"/*.rb"].each do |path|
load_configuration path
end
# load user custom init files
if config_dir = Qwandry.config_dir
custom_path = File.join(config_dir, 'init.rb')
load_configuration(custom_path)
end
end
# Loads the repositories for `names` if no names are given, it loads the defaults
def repositories(*names)
names = default if names.empty?
repositories = []
names.each do |name|
name = name.to_sym
raise ArgumentError, "Unknown Repository type '#{name}'" unless builders.has_key? name
builder = builders[name]
repositories += builder.repositories
end
repositories
end
private
def builders
@builders ||= Hash.new{|h,name| h[name] = self.new(name) }
end
end
# Creates a new Configuration for building a set of Repositories, this
# should probably only be invoked by Configuration.build, it only exists
# to make the customization DSL relatively easy to work with.
def initialize(name)
@name = name
@blocks = []
@repositories = []
end
def << (block)
@blocks << block
end
def repositories
@blocks.each{|block| instance_eval(&block) }
@blocks.clear
@repositories
end
# Adds a new Repository to the current configuration.
#
# The `options` can be used to customize the repository.
#
# [:class] Repository class, defaults to Qwandry::FlatRepository
# [:accept] Filters paths, only keeping ones matching the accept option
# [:reject] Filters paths, rejecting any paths matching the reject option
#
# `:accept` and `:reject` take patterns such as '*.py[oc]', procs, and regular expressions.
#
# Examples:
# # Add all my little ruby scripts in the scratch directory
# add '~/scratch', :accept => '*.rb'
# # Add log files in common locations for easy access, but ignore the zipped ones
# add ['/var/log/', '/usr/local/var/log/'], :reject => '*.bz2'
#
def add(paths, options={})
paths = [paths] if paths.is_a?(String)
paths.each do |path|
repository_class = options[:class] || Qwandry::FlatRepository
@repositories << repository_class.new(@name, File.expand_path(path), options)
end
end
end
end

View File

@ -0,0 +1,46 @@
# Register the default ruby configuration:
register :ruby do
# Reject binary paths, and then find only the `/lib/ruby` sources:
paths = ($:).reject{|path| path =~ /#{RUBY_PLATFORM}$/}.grep(/lib\/ruby/)
# Add ruby standard libraries using the LibraryRepository:
add paths, :class=>Qwandry::LibraryRepository
end
# Register the default ruby gems configuration:
register :gem do
# Get the gem paths from the ruby load paths:
paths = ($:).grep(/gems/).map{|p| p[/.+\/gems\//]}.uniq
# Add all the rubygems' paths:
add paths
end
# Register a perl configuration:
register :perl do
# Execute a perl script to find all of the perl load paths:
perl_paths = `perl -e 'foreach $k (@INC){print $k,"\n";}'` rescue ''
# Split on new lines, rejecting blank paths and the current directory:
perl_paths = perl_paths.split("\n").reject{|path| path == '' || path == '.'}
# Add perl paths as a LibraryRepository
add perl_paths, :class=>Qwandry::LibraryRepository
end
# Add python repositories:
register :python do
# Execute a python script to find all of the python load paths:
python_paths = `python -c 'import sys;print(\"\\n\".join(sys.path))'` rescue ''
# Reject all the blank paths and the current directory. Also reject anything that looks like a binary
python_paths = python_paths.split("\n").reject{|path| path == '' || path == '.' || path =~ /\.zip$/ || path =~/lib-dynload$/}
# Add the python paths, instruct Qwandry to skip any compiled files when trying to match a file/library:
add python_paths, :class=>Qwandry::LibraryRepository, :reject => /\.(py[oc])|(egg-info)$/
end
# Qwandry is a ruby app after all, so activate ruby and gem by default. Other defaults can be set
# with a custom init.rb
default :ruby, :gem

View File

@ -5,57 +5,19 @@ module Qwandry
# The default editor to be used by Qwandry#launch.
attr_accessor :editor
# The set of active repositories
attr_reader :active
# Returns the repositories the Launcher will use.
attr_reader :repositories
def initialize
@repositories = Hash.new{|h,k| h[k] = []}
@active = Set.new
configure_repositories!
custom_configuration!
end
# Adds a repository path to Qwandry's Launcher. `label` is used to label packages residing in the folder `path`.
#
# The `options` can be used to customize the repository.
#
# [:class] Repository class, defaults to Qwandry::FlatRepository
# [:accept] Filters paths, only keeping ones matching the accept option
# [:reject] Filters paths, rejecting any paths matching the reject option
#
# `:accept` and `:reject` take patterns such as '*.py[oc]', procs, and regular expressions.
def add(label, path, options={})
if path.is_a?(Array)
path.each{|p| add label, p, options}
else
repository_class = options[:class] || Qwandry::FlatRepository
label = label.to_s
@repositories[label] << repository_class.new(label, File.expand_path(path), options)
end
end
def activate(*labels)
labels.each{|label| @active.add label.to_s}
end
def deactivate(*labels)
labels.each{|label| @active.delete label.to_s}
end
# Searches all of the loaded repositories for `name`
def find(*pattern)
# Create a glob pattern from the user's input, for instance
# ["rails","2.3"] => "rails*2.3*"
pattern = pattern.join('*')
pattern << '*' unless pattern =~ /\*$/
packages = []
@repositories.select{|label,_| @active.include? label }.each do |label, repos|
repos.each do |repo|
packages.concat(repo.scan(pattern))
end
repositories = Qwandry::Configuration.repositories
repositories.each do |repo|
packages.concat(repo.scan(pattern))
end
packages
end
@ -76,56 +38,6 @@ module Qwandry
# Launch the editor with its options and any paths that we have been passed
system(*(editor_and_options + paths))
end
private
def configure_repositories!
# Get all the paths on ruby's load path:
paths = $:
# Reject binary paths, we only want ruby sources:
paths = paths.reject{|path| path =~ /#{RUBY_PLATFORM}$/}
# Add ruby standard libraries:
paths.grep(/lib\/ruby/).each do |path|
add :ruby, path, :class=>Qwandry::LibraryRepository
end
# Add gem repositories:
($:).grep(/gems/).map{|p| p[/.+\/gems\//]}.uniq.each do |path|
add :gem, path
end
# Add perl repositories:
perl_paths = `perl -e 'foreach $k (@INC){print $k,"\n";}'` rescue ''
perl_paths.split("\n").reject{|path| path == '' || path == '.'}.each do |path|
add :perl, path, :class=>Qwandry::LibraryRepository
end
# add python repositories:
python_paths = `python -c 'import sys;print(\"\\n\".join(sys.path))'` rescue ''
python_paths.split("\n").reject{|path| path == '' || path == '.' || path =~ /\.zip$/ || path =~/lib-dynload$/}.each do |path|
add :python, path, :class=>Qwandry::LibraryRepository, :reject => /\.(py[oc])|(egg-info)$/
end
# Qwandry is a ruby app after all, so activate ruby and rubygems by default. Other defaults can be set
# with a custom init.rb
activate :ruby, :gem
end
def custom_configuration!
if config_dir = Qwandry.config_dir
custom_path = File.join(config_dir, 'init.rb')
if File.exist?(custom_path)
begin
eval IO.read(custom_path), nil, custom_path, 1
rescue Exception=>ex
STDERR.puts "Warning: error in custom file: #{custom_path.inspect}"
STDERR.puts "Exception: #{ex.message}"
STDERR.puts ex.backtrace
end
end
end
end
end
end