diff --git a/bin/qw b/bin/qw index 33e90ef..47c0359 100755 --- a/bin/qw +++ b/bin/qw @@ -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 diff --git a/lib/qwandry.rb b/lib/qwandry.rb index ec0e398..d170cf6 100644 --- a/lib/qwandry.rb +++ b/lib/qwandry.rb @@ -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 diff --git a/lib/qwandry/configuration.rb b/lib/qwandry/configuration.rb new file mode 100644 index 0000000..067e9be --- /dev/null +++ b/lib/qwandry/configuration.rb @@ -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 \ No newline at end of file diff --git a/lib/qwandry/configuration/default.rb b/lib/qwandry/configuration/default.rb new file mode 100644 index 0000000..615e374 --- /dev/null +++ b/lib/qwandry/configuration/default.rb @@ -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 \ No newline at end of file diff --git a/lib/qwandry/launcher.rb b/lib/qwandry/launcher.rb index de814c5..4f3174d 100644 --- a/lib/qwandry/launcher.rb +++ b/lib/qwandry/launcher.rb @@ -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