require 'rbbt/resource/util' require 'rbbt/util/misc/indiferent_hash' require 'yaml' module Path attr_accessor :resource, :pkgdir, :original, :search_paths, :search_order, :libdir def self.setup(string, pkgdir = nil, resource = nil, search_paths = nil, search_order = nil, libdir = nil) return string if string.nil? string = string.dup if string.frozen? string.extend Path string.pkgdir = pkgdir || 'rbbt' string.resource = resource string.search_paths = search_paths string.search_order = search_order string.libdir = libdir string end def search_order @search_order ||= STANDARD_SEARCH.dup.uniq end def search_paths @search_paths ||= SEARCH_PATHS.dup end def add_search_path(name, dir) search_paths[name.to_sym] = dir end def prepend_search_path(name, dir) add_search_path(name, dir) search_order.unshift(name.to_sym) end def append_search_path(name, dir) add_search_path(name, dir) search_order.push(name.to_sym) end def sub(*args) self.annotate super(*args) end def annotate(name) name = name.to_s name = Path.setup name, @pkgdir, @resource, @search_paths, @search_order, @libdir name end def join(name) raise "Invalid path: #{ self }" if self.nil? if self.empty? self.annotate name.to_s.dup else self.annotate File.join(self, name.to_s) end end def dirname Path.setup File.dirname(self), @pkgdir, @resource end def directory? return nil unless self.exists? File.directory? self.find end def glob(pattern = '*') if self.include? "*" self.glob_all else return [] unless self.exists? exp = File.join(self.find, pattern) Dir.glob(exp).collect{|f| Path.setup(f, self.resource, self.pkgdir)} end end def [](name, orig = false) return super(name) if orig join name end def byte(pos) send(:[], pos, true) end def method_missing(name, prev = nil, *args, &block) if block_given? super name, prev, *args, &block else # Fix problem with ruby 1.9 calling methods by its own initiative. ARG super(name, prev, *args) if name.to_s =~ /^to_/ if prev.nil? join name else join(prev).join(name) end end end SEARCH_PATHS = IndiferentHash.setup({ :current => File.join("{PWD}", "{TOPLEVEL}", "{SUBPATH}"), :user => File.join(ENV['HOME'], ".{PKGDIR}", "{TOPLEVEL}", "{SUBPATH}"), :global => File.join('/', "{TOPLEVEL}", "{PKGDIR}", "{SUBPATH}"), :local => File.join('/usr/local', "{TOPLEVEL}", "{PKGDIR}", "{SUBPATH}"), :fast => File.join('/fast', "{TOPLEVEL}", "{PKGDIR}", "{SUBPATH}"), :cache => File.join('/cache', "{TOPLEVEL}", "{PKGDIR}", "{SUBPATH}"), :bulk => File.join('/bulk', "{TOPLEVEL}", "{PKGDIR}", "{SUBPATH}"), :lib => File.join('{LIBDIR}', "{TOPLEVEL}", "{SUBPATH}"), :default => :user }) STANDARD_SEARCH = %w(current workflow user local global lib fast cache bulk) search_path_file = File.join(ENV['HOME'], '.rbbt/etc/search_paths') if File.exist?(search_path_file) begin YAML.load(File.open(search_path_file)).each do |where, location| SEARCH_PATHS[where.to_sym] = location end rescue Log.error "Error loading search_paths from #{search_path_file}: " << $!.message end end def find(where = nil, caller_lib = nil, paths = nil) @path ||= {} rsearch_paths = (resource and resource.respond_to?(:search_paths)) ? resource.search_paths : nil key_elems = [where, caller_lib, rsearch_paths, paths] key = Misc.obj2digest(key_elems.inspect) self.sub!('~/', Etc.getpwuid.dir + '/') if self.include? "~" return @path[key] if @path[key] if located? @path[key] = self return self end @path[key] ||= begin paths = [paths, rsearch_paths, self.search_paths, SEARCH_PATHS].reverse.compact.inject({}){|acc,h| acc.merge! h; acc } where = paths[:default] if where == :default if self.match(/(.*?)\/(.*)/) toplevel, subpath = self.match(/(.*?)\/(.*)/).values_at 1, 2 else toplevel, subpath = "{REMOVE}", self end path = nil search_order = self.search_order || [] res = nil if where.nil? (STANDARD_SEARCH - search_order).each do |w| w = w.to_sym break if res next unless paths.include? w path = find(w, caller_lib, paths) res = path if File.exist? path end search_order.each do |w| w = w.to_sym next if res next unless paths.include? w path = find(w, caller_lib, paths) res = path if File.exist? path end if res.nil? (paths.keys - STANDARD_SEARCH - search_order).each do |w| w = w.to_sym next if res next unless paths.include? w path = find(w, caller_lib, paths) res = path if File.exist? path end if res.nil? if paths.include? :default res = find((paths[:default] || :user), caller_lib, paths) else raise "Path '#{ path }' not found, and no default specified in search paths: #{paths.inspect}" end if res.nil? else where = where.to_sym raise "Did not recognize the 'where' tag: #{where}. Options: #{paths.keys}" unless paths.include? where if where == :lib libdir = @libdir || Path.caller_lib_dir(caller_lib) || "NOLIBDIR" else libdir = "NOLIBDIR" end pwd = FileUtils.pwd path = paths[where] path = File.join(path, "{PATH}") unless path.include? "PATH}" or path.include? "{BASENAME}" path = path. sub('{PKGDIR}', pkgdir). sub('{PWD}', pwd). sub('{TOPLEVEL}', toplevel). sub('{SUBPATH}', subpath). sub('{BASENAME}', File.basename(self)). sub('{PATH}', self). sub('{LIBDIR}', libdir). sub('{REMOVE}/', ''). sub('{REMOVE}', '') path = path + '.gz' if File.exist? path + '.gz' path = path + '.bgz' if File.exist? path + '.bgz' self.annotate path res = path end res.original = self res end @path[key] end def find_all(caller_lib = nil, search_paths = nil) search_paths ||= @search_paths || SEARCH_PATHS search_paths = search_paths.dup search_paths.keys. collect{|where| find(where, Path.caller_lib_dir, search_paths) }. compact.select{|file| file.exists? }.uniq end def glob_all(pattern = nil, caller_lib = nil, search_paths = nil) search_paths ||= @search_paths || SEARCH_PATHS search_paths = search_paths.dup search_paths.keys. collect{|where| pattern ? Dir.glob(File.join(find(where, Path.caller_lib_dir, search_paths), pattern)) : Dir.glob(find(where, Path.caller_lib_dir, search_paths)) }. compact.flatten.collect{|file| File.expand_path(file)}.uniq.collect{|path| Path.setup(path, self.resource, self.pkgdir)} end #{{{ Methods def in_dir?(dir) ! ! File.expand_path(self).match(/^#{Regexp.quote dir}/) end def to_s self.find end def filename self.find end def _exists? Open.exists? self.find.to_s end def exists? begin self.produce _exists? rescue Exception false end end def produce(force = false) return self if _exists? and not force raise "No resource defined to produce file: #{ self }" if resource.nil? resource.produce self, force self end def read(&block) Open.read(self.produce.find, &block) end def write(*args, &block) Open.write(self.produce.find, *args, &block) end def open(options = {}, &block) Open.open(self.produce.find, options, &block) end def to_s "" + self end def basename Path.setup(File.basename(self), self.resource, self.pkgdir) end def tsv(*args) TSV.open(self.produce, *args) end def tsv_options(options = {}) self.open do |stream| TSV::Parser.new(stream, options).options end end def traverse(options = {}, &block) TSV::Parser.traverse(self.open, options, &block) end def list Open.read(self.produce.find).split "\n" end def keys(field = 0, sep = "\t") Open.read(self.produce.find).split("\n").collect{|l| next if l =~ /^#/; l.split(sep, -1)[field]}.compact end def yaml self.open do |f| YAML.load f end end def pipe_to(cmd, options = {}) CMD.cmd(cmd, {:in => self.open, :pipe => true}.merge(options)) end def index(options = {}) TSV.index(self.produce, options) end def range_index(start, eend, options = {}) TSV.range_index(self.produce, start, eend, options) end def pos_index(pos, options = {}) TSV.pos_index(self.produce, pos, options) end def to_yaml(*args) self.to_s.to_yaml(*args) end def fields TSV.parse_header(self.open).fields end def all_fields self.open do |stream| TSV.parse_header(stream).all_fields end end def identifier_file_path if self.dirname.identifiers.exists? self.dirname.identifiers else nil end end def identifier_files if identifier_file_path.nil? [] else [identifier_file_path] end end def set_extension(new_extension = nil) new_path = self + "." + new_extension.to_s self.annotate(new_path) end def replace_extension(new_extension = nil, multiple = false) if multiple new_path = self.sub(/(\.[^\.\/]{2,4})+$/,'') else new_path = self.sub(/\.[^\.\/]{2,4}$/,'') end new_path = new_path + "." + new_extension.to_s self.annotate(new_path) end def doc_file(relative_to = 'lib') if located? lib_dir = Path.caller_lib_dir(self, relative_to) relative_file = File.join( 'doc', self.sub(lib_dir,'')) Path.setup File.join(lib_dir, relative_file) , @pkgdir, @resource else Path.setup File.join('doc', self) , @pkgdir, @resource end end def source_for_doc_file(relative_to = 'lib') if located? lib_dir = Path.caller_lib_dir(Path.caller_lib_dir(self, 'doc'), relative_to) relative_file = self.sub(/(.*\/)doc\//, '\1').sub(lib_dir + "/",'') file = File.join(lib_dir, relative_file) if not File.exist?(file) file= Dir.glob(file.sub(/\.[^\.\/]+$/, '.*')).first end Path.setup file, @pkgdir, @resource else relative_file = self.sub(/^doc\//, '\1') if not File.exist?(relative_file) relative_file = Dir.glob(relative_file.sub(/\.[^\.\/]+$/, '.*')).first end Path.setup relative_file , @pkgdir, @resource end end end