├── VERSION ├── test ├── test_pathlist.rb ├── test_pathshell.rb └── test_pathname.rb ├── work ├── deprecated │ ├── meta │ │ ├── name │ │ ├── version │ │ ├── authors │ │ ├── collection │ │ ├── loadpath │ │ ├── homepage │ │ ├── repository │ │ ├── summary │ │ ├── contact │ │ └── description │ ├── store.rb │ └── name.rb ├── consider │ ├── md5.rb │ ├── pathlist.rb │ ├── fileable.rb │ └── filelist.rb ├── pathname.qed └── reference │ └── name.rb ├── .gitignore ├── lib ├── path.rb └── path │ ├── glob.rb │ ├── shell │ ├── zip.rb │ ├── gzip.rb │ ├── xdg.rb │ ├── tar.rb │ └── ftp.rb │ ├── filetest.rb │ ├── fileutils.rb │ ├── console.rb │ ├── list.rb │ └── shell.rb ├── README.rdoc ├── HISTORY.rdoc ├── PROFILE ├── Syckfile ├── .ruby └── vendor └── path ├── xdg.rb └── minitar.rb /VERSION: -------------------------------------------------------------------------------- 1 | 1.2.0 2 | -------------------------------------------------------------------------------- /test/test_pathlist.rb: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /work/deprecated/meta/name: -------------------------------------------------------------------------------- 1 | path 2 | -------------------------------------------------------------------------------- /work/deprecated/meta/version: -------------------------------------------------------------------------------- 1 | 1.1 2 | -------------------------------------------------------------------------------- /work/deprecated/meta/authors: -------------------------------------------------------------------------------- 1 | Thomas Sawyer 2 | -------------------------------------------------------------------------------- /work/deprecated/meta/collection: -------------------------------------------------------------------------------- 1 | rubyworks 2 | -------------------------------------------------------------------------------- /work/deprecated/meta/loadpath: -------------------------------------------------------------------------------- 1 | lib 2 | vendor 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | log 2 | doc/rdoc 3 | doc/ri 4 | MANIFEST 5 | pkg 6 | -------------------------------------------------------------------------------- /work/deprecated/meta/homepage: -------------------------------------------------------------------------------- 1 | http://rubyworks.github.com/path 2 | -------------------------------------------------------------------------------- /work/deprecated/meta/repository: -------------------------------------------------------------------------------- 1 | git://github.com/rubyworks/path.git 2 | -------------------------------------------------------------------------------- /work/deprecated/meta/summary: -------------------------------------------------------------------------------- 1 | Integrated set of a path-related libraries. 2 | -------------------------------------------------------------------------------- /test/test_pathshell.rb: -------------------------------------------------------------------------------- 1 | require 'path/shell' 2 | require 'test/unit' 3 | 4 | 5 | -------------------------------------------------------------------------------- /work/deprecated/meta/contact: -------------------------------------------------------------------------------- 1 | http://googlegroups/group/rubyworks-mailinglist 2 | -------------------------------------------------------------------------------- /lib/path.rb: -------------------------------------------------------------------------------- 1 | module Path 2 | VERSION = "1.1" #:till: VERSION="<%= version %>" 3 | end 4 | 5 | require 'path/shell' 6 | 7 | -------------------------------------------------------------------------------- /work/deprecated/meta/description: -------------------------------------------------------------------------------- 1 | Path is all about paths. It's provides a reimplementation of the Ruby 2 | standard Pathname library, Path::Name, a superior globbing 3 | facility, Path::List and an isolated shell-evironment, Path::Shell. 4 | -------------------------------------------------------------------------------- /lib/path/glob.rb: -------------------------------------------------------------------------------- 1 | module Path 2 | require 'path/name' 3 | 4 | # = Glob 5 | # 6 | # A lazy resolution variation of Path::Name. 7 | # 8 | class Glob < Name 9 | 10 | def to_s 11 | Dir.glob(super).first 12 | end 13 | 14 | end 15 | 16 | end 17 | 18 | -------------------------------------------------------------------------------- /README.rdoc: -------------------------------------------------------------------------------- 1 | = Path 2 | 3 | * http://rubyworks.github.com/path 4 | 5 | 6 | == DESCRIPTION: 7 | 8 | Path is all about paths. It's provides a reimplementation of the Ruby 9 | standard Pathname library, Path::Name, a superior globbing 10 | facility, Path::List and an isolated shell-evironment, Path::Shell. 11 | 12 | 13 | == SYNOPSIS: 14 | 15 | Like Pathname: 16 | 17 | require 'path/name' 18 | 19 | 20 | == INSTALLATION: 21 | 22 | Using RubyGems: 23 | 24 | $ sudo gem install path 25 | 26 | 27 | == COPYRIGHT: 28 | 29 | Path, Copyright (c) 2009 Thomas Sawyer 30 | 31 | (LGPL License) 32 | 33 | -------------------------------------------------------------------------------- /HISTORY.rdoc: -------------------------------------------------------------------------------- 1 | = RELEASE HISTORY 2 | 3 | == 1.2.0 // 2010-01-07 4 | 5 | This release attends to some admin issues and a few 6 | light improvements. It also removes Path::Store which 7 | is being move to a new project. 8 | 9 | Changes: 10 | 11 | * Add Path::Name#first and #last methods. 12 | * Fix Path::Name#amass method. 13 | 14 | 15 | == 1.1.0 // 2009-10-21 16 | 17 | Changes: 18 | 19 | * Added Path::Name#amass which a more flexible #glob like method. 20 | 21 | 22 | == 1.0.0 // 2009-09-22 23 | 24 | This is the initial stand-alone release of Path, a collection 25 | of Path-related classes partially spun-off from Ruby Facets 26 | and briefly called Folio. 27 | 28 | Changes: 29 | 30 | * 1 Major Enhancement 31 | 32 | * Happy Birthday! 33 | 34 | -------------------------------------------------------------------------------- /PROFILE: -------------------------------------------------------------------------------- 1 | --- 2 | title : Path 3 | summary: Integrated set of a path-related libraries 4 | license: Apache 2.0 5 | authors: Thomas Sawyer 6 | created: 2009-09-22 7 | 8 | description: 9 | Path is all about paths. It's provides a reimplementation of the Ruby 10 | standard Pathname library, Path::Name, a superior globbing 11 | facility, Path::List and an isolated shell-evironment, Path::Shell. 12 | 13 | resources: 14 | home: http://rubyworks.github.com/path 15 | code: http://github.com/rubyworks/path 16 | mail: http://groups.gooogle.com/group/rubyworks-mailinglist 17 | 18 | repositories: 19 | public: git://github.com/rubyworks/path.git 20 | 21 | organization: RubyWorks 22 | copyright : Copyright (c) 2009 Thomas Sawyer 23 | 24 | loadpath: [lib, vendor] 25 | -------------------------------------------------------------------------------- /lib/path/shell/zip.rb: -------------------------------------------------------------------------------- 1 | #-- 2 | # TODO: Find way to zip without shelling-out. 3 | #++ 4 | module Path 5 | 6 | class Shell 7 | 8 | # Zip 9 | # 10 | # TODO: replace with a pure ruby zip library 11 | # 12 | def zip(folder, file=nil, options={}) 13 | noop, verbose = *util_options(options) 14 | 15 | raise ArgumentError if folder == '.*' 16 | 17 | file ||= File.basename(File.expand_path(folder)) + '.zip' 18 | 19 | folder = localize(folder) 20 | file = localize(file) 21 | 22 | cmd = "zip -rqu #{file} #{folder}" 23 | puts cmd if verbose 24 | system cmd if !noop 25 | 26 | return file 27 | end 28 | 29 | # Unzip 30 | # 31 | def unzip(file, options={}) 32 | noop, verbose = *util_options(options) 33 | 34 | file = localize(file) 35 | 36 | cmd = "unzip #{file}" 37 | puts cmd if verbose 38 | system cmd if !noop 39 | end 40 | 41 | end 42 | 43 | end 44 | 45 | -------------------------------------------------------------------------------- /lib/path/shell/gzip.rb: -------------------------------------------------------------------------------- 1 | module Path 2 | 3 | class Shell 4 | 5 | # Create a gzip file. 6 | # 7 | def gzip(file, tofile=nil, options={}) 8 | require 'zlib' 9 | 10 | noop, verbose = *util_options(options) 11 | 12 | tofile ||= File.basename(file) + '.gz' 13 | 14 | puts "gzip #{file}" if verbose 15 | 16 | file = localize(file) 17 | tofile = localize(tofile) 18 | 19 | Zlib::GzipWriter.open(tofile) do |gz| 20 | gz.write(File.read(file)) 21 | end unless noop 22 | 23 | return tofile 24 | end 25 | 26 | # Unpack a gzip file. 27 | # 28 | def ungzip(file, options={}) 29 | require 'zlib' 30 | 31 | noop, verbose = *util_options(options) 32 | 33 | fname = File.basename(file).chomp(File.extname(file)) 34 | 35 | puts "ungzip #{file}" if verbose 36 | 37 | fname = localize(fname) 38 | file = localize(file) 39 | 40 | Zlib::GzipReader.open(file) do |gz| 41 | File.open(fname, 'wb'){ |f| f << gz.read } 42 | end unless noop 43 | 44 | return fname 45 | end 46 | 47 | end 48 | 49 | end 50 | 51 | -------------------------------------------------------------------------------- /work/consider/md5.rb: -------------------------------------------------------------------------------- 1 | require 'fileutils' 2 | require 'digest/md5' 3 | 4 | module FileTest 5 | module_function 6 | 7 | BUF_SIZE = 1024*1024 8 | 9 | # Are two files identical? This compares size and then checksum. 10 | def identical?(file1, file2) 11 | size(file1) == size(file2) && md5(file1) == md5(file2) 12 | end 13 | 14 | # Return an md5 checkum. If a directory is given, will 15 | # return a nested array of md5 checksums for all entries. 16 | 17 | def md5(path) 18 | if File.directory?(path) 19 | md5_list = [] 20 | crt_dir = Dir.new(path) 21 | crt_dir.each do |file_name| 22 | next if file_name == '.' || file_name == '..' 23 | md5_list << md5("#{crt_dir.path}#{file_name}") 24 | end 25 | md5_list 26 | else 27 | hasher = Digest::MD5.new 28 | open(path, "r") do |io| 29 | counter = 0 30 | while (!io.eof) 31 | readBuf = io.readpartial(BUF_SIZE) 32 | counter+=1 33 | #putc '.' if ((counter+=1) % 3 == 0) 34 | hasher.update(readBuf) 35 | end 36 | end 37 | return hasher.hexdigest 38 | end 39 | end 40 | 41 | # Show diff of two files. 42 | 43 | def diff(file1, file2) 44 | `diff #{file1} #{file2}`.strip 45 | end 46 | 47 | end 48 | -------------------------------------------------------------------------------- /Syckfile: -------------------------------------------------------------------------------- 1 | --- 2 | email: 3 | service : Email 4 | file : ~ 5 | subject : ~ 6 | mailto : ruby-talk@ruby-lang.org 7 | active : true 8 | 9 | grancher: 10 | service: grancher 11 | active: true 12 | 13 | gemcutter: 14 | service: GemCutter 15 | active: true 16 | 17 | box: 18 | service: Box 19 | types : [gem] 20 | active : true 21 | 22 | ridoc: 23 | service: RIDoc 24 | include: ~ 25 | exclude: ~ 26 | active : true 27 | 28 | rdoc: 29 | service : RDoc 30 | template: newfish 31 | include : ~ 32 | exclude : [ Syckfile ] 33 | active : true 34 | 35 | syntax: 36 | service : Syntax 37 | loadpath : ~ 38 | exclude : ~ 39 | active : false 40 | 41 | testrb: 42 | service : testrb 43 | tests : ~ 44 | exclude : ~ 45 | loadpath : ~ 46 | requires : ~ 47 | live : false 48 | active : false 49 | 50 | dnote: 51 | service : DNote 52 | loadpath : ~ 53 | labels : ~ 54 | exclude : [work] 55 | output : ~ 56 | format : ~ 57 | active : true 58 | 59 | stats: 60 | service : Stats 61 | title : ~ 62 | loadpath : ~ 63 | exclude : ~ 64 | output : ~ 65 | active : true 66 | 67 | vclog: 68 | service : VClog 69 | formats : [json] 70 | #layout : rel # gnu 71 | typed : false 72 | output : ~ 73 | active : false 74 | 75 | -------------------------------------------------------------------------------- /.ruby: -------------------------------------------------------------------------------- 1 | --- 2 | name: path 3 | loadpath: 4 | - lib 5 | - vendor 6 | repositories: 7 | public: git://github.com/rubyworks/path.git 8 | title: Path 9 | resources: 10 | code: http://github.com/rubyworks/path 11 | mail: http://groups.gooogle.com/group/rubyworks-mailinglist 12 | home: http://rubyworks.github.com/path 13 | pom_verison: 1.0.0 14 | manifest: 15 | - .ruby 16 | - lib/path/console.rb 17 | - lib/path/filetest.rb 18 | - lib/path/fileutils.rb 19 | - lib/path/glob.rb 20 | - lib/path/list.rb 21 | - lib/path/pathname.rb 22 | - lib/path/shell/ftp.rb 23 | - lib/path/shell/gzip.rb 24 | - lib/path/shell/tar.rb 25 | - lib/path/shell/xdg.rb 26 | - lib/path/shell/zip.rb 27 | - lib/path/shell.rb 28 | - lib/path.rb 29 | - test/test_pathlist.rb 30 | - test/test_pathname.rb 31 | - test/test_pathshell.rb 32 | - vendor/path/minitar.rb 33 | - vendor/path/xdg.rb 34 | - HISTORY.rdoc 35 | - PACKAGE 36 | - LICENSE 37 | - README.rdoc 38 | - Syckfile 39 | version: "" 40 | copyright: Copyright (c) 2009 Thomas Sawyer 41 | licenses: 42 | - Apache 2.0 43 | description: Path is all about paths. It's provides a reimplementation of the Ruby standard Pathname library, Path::Name, a superior globbing facility, Path::List and an isolated shell-evironment, Path::Shell. 44 | organization: RubyWorks 45 | summary: Integrated set of a path-related libraries 46 | authors: 47 | - Thomas Sawyer 48 | created: 2009-09-22 49 | -------------------------------------------------------------------------------- /lib/path/shell/xdg.rb: -------------------------------------------------------------------------------- 1 | begin 2 | require 'xdg' 3 | rescue LoadError 4 | require 'path/xdg' 5 | end 6 | 7 | module Path 8 | 9 | class Shell 10 | 11 | # Look up a config file. 12 | def config(path) 13 | file(XDG.xdg_config_file(path)) 14 | end 15 | 16 | # Look up a data file. 17 | def data(path) 18 | file(XDG.xdg_data_file(path)) 19 | end 20 | 21 | # Look up a cache file. 22 | def cache(path) 23 | file(XDG.xdg_cache_file(path)) 24 | end 25 | 26 | # Return a enumertor of system config directories. 27 | def root_config() #_directories 28 | XDG.xdg_config_dirs.to_enum(:each){ |f| dir(f) } 29 | end 30 | 31 | # Return a enumertor of system data directories. 32 | def root_data() #_directories 33 | XDG.xdg_data_dirs.to_enum(:each){ |f| dir(f) } 34 | end 35 | 36 | # Return the home config directory. 37 | def home_config 38 | dir(XDG.xdg_config_home) 39 | end 40 | 41 | # Return the home data directory. 42 | def home_data 43 | dir(XDG.xdg_data_home) 44 | end 45 | 46 | # Return the home cache directory. 47 | def home_cache 48 | dir(XDG.xdg_cache_home) 49 | end 50 | 51 | # Return the work config directory. 52 | def work_config 53 | dir(XDG.xdg_config_work) 54 | end 55 | 56 | # Return the work cache directory. 57 | def work_cache 58 | dir(XDG.xdg_cache_work) 59 | end 60 | 61 | end 62 | 63 | end 64 | -------------------------------------------------------------------------------- /lib/path/filetest.rb: -------------------------------------------------------------------------------- 1 | module FileTest 2 | 3 | if File::ALT_SEPARATOR 4 | SEPARATOR_PAT = /[#{Regexp.quote File::ALT_SEPARATOR}#{Regexp.quote File::SEPARATOR}]/ 5 | else 6 | SEPARATOR_PAT = /#{Regexp.quote File::SEPARATOR}/ 7 | end 8 | 9 | ############### 10 | module_function 11 | ############### 12 | 13 | # Predicate method for testing whether a path is absolute. 14 | # It returns +true+ if the pathname begins with a slash. 15 | def absolute?(path) 16 | !relative?(path) 17 | end 18 | 19 | # The opposite of #absolute? 20 | def relative?(path) 21 | while r = chop_basename(path.to_s) 22 | path, basename = r 23 | end 24 | path == '' 25 | end 26 | 27 | # Return a cached list of the PATH environment variable. 28 | # This is a support method used by #bin? 29 | def command_paths 30 | @command_paths ||= ENV['PATH'].split(/[:;]/) 31 | end 32 | 33 | # Is a file a bin/ executable? 34 | # 35 | # TODO: Make more robust. Probably needs to be fixed for Windows. 36 | def bin?(fname) 37 | is_bin = command_paths.any? do |f| 38 | FileTest.exist?(File.join(f, fname)) 39 | end 40 | #is_bin ? File.basename(fname) : false 41 | is_bin ? fname : false 42 | end 43 | 44 | ## Is a file a task? 45 | # 46 | #def task?(path) 47 | # task = File.dirname($0) + "/#{path}" 48 | # task.chomp!('!') 49 | # task if FileTest.file?(task) && FileTest.executable?(task) 50 | #end 51 | 52 | # Is a path considered reasonably "safe"? 53 | # 54 | # TODO: Make more robust. 55 | def safe?(path) 56 | case path 57 | when *[ '/', '/*', '/**/*' ] 58 | return false 59 | end 60 | true 61 | end 62 | 63 | # Chop_basename(path) -> [pre-basename, basename] or nil 64 | def chop_basename(path) 65 | base = File.basename(path) 66 | if /\A#{SEPARATOR_PAT}?\z/ =~ base 67 | return nil 68 | else 69 | return path[0, path.rindex(base)], base 70 | end 71 | end 72 | #private :chop_basename 73 | 74 | # Does the +parent+ contain the +child+? 75 | def contains?(child, parent=Dir.pwd) 76 | parent = File.expand_path(parent) 77 | child = File.expand_path(child) 78 | child.sub(parent,'') != child 79 | end 80 | 81 | end 82 | 83 | -------------------------------------------------------------------------------- /lib/path/shell/tar.rb: -------------------------------------------------------------------------------- 1 | #-- 2 | # TODO: Add compress support ? 3 | # TODO: Add bzip support ? 4 | #++ 5 | 6 | module Path 7 | 8 | class Shell 9 | 10 | def self.require_minitar 11 | require 'zlib' 12 | begin 13 | require 'archive/tar/minitar' 14 | rescue LoadError 15 | require 'path/minitar' 16 | end 17 | end 18 | 19 | # Tar 20 | # 21 | def tar(folder, file=nil, options={}) 22 | Shell.require_minitar 23 | noop, verbose = *util_options(options) 24 | file ||= File.basename(File.expand_path(folder)) + '.tar' 25 | cmd = "tar -cf #{file} #{folder}" 26 | puts cmd if verbose 27 | unless noop 28 | locally do 29 | gzIO = File.open(file, 'wb') 30 | Archive::Tar::Minitar.pack(folder, gzIO) 31 | end 32 | end 33 | path(file) 34 | end 35 | 36 | # Untar 37 | # 38 | def untar(file, options={}) 39 | Shell.require_minitar 40 | noop, verbose = *util_options(options) 41 | #file ||= File.basename(File.expand_path(folder)) + '.tar' 42 | cmd = "untar #{file}" 43 | puts cmd if verbose 44 | unless noop 45 | locally do 46 | gzIO = File.open(file, 'wb') 47 | Archive::Tar::Minitar.unpack(gzIO) 48 | end 49 | end 50 | path(file) 51 | end 52 | 53 | # Tar Gzip 54 | # 55 | def tar_gzip(folder, file=nil, options={}) 56 | Shell.require_minitar 57 | noop, verbose = *util_options(options) 58 | file ||= File.basename(File.expand_path(folder)) + '.tar.gz' # '.tgz' which ? 59 | cmd = "tar --gzip -czf #{file} #{folder}" 60 | puts cmd if verbose 61 | unless noop 62 | locally do #folder, file = localize(folder), localize(file) 63 | gzIO = Zlib::GzipWriter.new(File.open(file, 'wb')) 64 | Archive::Tar::Minitar.pack(folder, gzIO) 65 | end 66 | end 67 | path(file) 68 | end 69 | 70 | alias_method :tar_z, :tar_gzip 71 | 72 | # Untar Gzip 73 | # 74 | # FIXME: Write unified untar_gzip function. 75 | def untar_gzip(file, options={}) 76 | Shell.require_minitar 77 | untar(ungzip(file, options), options) 78 | end 79 | 80 | alias_method :untar_z, :untar_gzip 81 | 82 | #def tgz(folder, file=nil, options={}) 83 | # file ||= File.basename(File.expand_path(folder)) + '.tgz' 84 | # tar_gzip(folder, file, options) 85 | #end 86 | 87 | end 88 | 89 | end 90 | 91 | -------------------------------------------------------------------------------- /work/consider/pathlist.rb: -------------------------------------------------------------------------------- 1 | # = PathList 2 | # 3 | # A PathList is an array containing 1..n paths. It is useful to regroup paths and 4 | # make lookups on them. 5 | # 6 | # == Usage 7 | # 8 | # path = PathList.new(ENV['PATH']) 9 | # path.find 'env' #=> "/usr/bin/env" 10 | # 11 | # # This is already done when including the library 12 | # $:.class #=> Array 13 | # $:.extend PathList::Finder 14 | # $:.find_ext = 'rb' 15 | # 16 | # $:.find 'uri' #=> "/usr/lib/ruby/1.8/uri.rb" 17 | # 18 | # == Authors 19 | # 20 | # * Jonas Pfenniger 21 | # 22 | # == Copying 23 | # 24 | # Copyright (c) 2005 Jonas Pfenniger 25 | # 26 | # Ruby License 27 | # 28 | # This module is free software. You may use, modify, and/or redistribute this 29 | # software under the same terms as Ruby. 30 | # 31 | # This program is distributed in the hope that it will be useful, but WITHOUT 32 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 33 | # FOR A PARTICULAR PURPOSE. 34 | 35 | 36 | # = PathList 37 | # 38 | # A PathList is an array containing 1..n paths. It is useful to regroup paths and 39 | # make lookups on them. 40 | # 41 | # == Usage 42 | # 43 | # path = PathList.new(ENV['PATH']) 44 | # path.find 'env' #=> "/usr/bin/env" 45 | # 46 | # # This is already done when including the library 47 | # $:.class #=> Array 48 | # $:.extend PathList::Finder 49 | # $:.find_ext = 'rb' 50 | # 51 | # $:.find 'uri' #=> "/usr/lib/ruby/1.8/uri.rb" 52 | # 53 | class PathList < Array 54 | 55 | def initialize(paths, default_ext = nil) 56 | @find_ext = default_ext 57 | if paths.kind_of? String 58 | paths = paths.split(File::PATH_SEPARATOR) 59 | end 60 | super(paths) 61 | end 62 | 63 | def to_s 64 | join(File::PATH_SEPARATOR) 65 | end 66 | 67 | module Finder 68 | 69 | attr_accessor :find_ext 70 | 71 | def find(filename, use_ext=true) 72 | filename += '.' + @find_ext if @find_ext and use_ext 73 | to_a.each do |path| 74 | filepath = File.join(path, filename) 75 | return filepath if File.exist?( filepath ) 76 | end 77 | return nil 78 | end 79 | alias :include? find 80 | 81 | end 82 | 83 | include Finder 84 | end 85 | 86 | $:.extend PathList::Finder 87 | $:.find_ext = 'rb' 88 | 89 | # Doesn't work 90 | #ENV['PATH'] = PathList.new(ENV['PATH']) 91 | 92 | 93 | =begin test 94 | 95 | require 'test/unit' 96 | 97 | # Needs a Mockup 98 | # It is not possible to test file lookups in a platform independent manner. 99 | 100 | class TC_PathList < Test::Unit::TestCase 101 | 102 | end 103 | 104 | =end 105 | 106 | -------------------------------------------------------------------------------- /lib/path/shell/ftp.rb: -------------------------------------------------------------------------------- 1 | module Path 2 | 3 | class Shell 4 | 5 | # Use ftp to upload files. 6 | # 7 | def ftp( keys ) 8 | keys = upload_parameters(keys) 9 | 10 | # set transfer rules 11 | if keys.stage 12 | trans = stage_transfer(keys.stage) 13 | else 14 | files(keys.dir, keys.copy).each do |from| 15 | trans << [from,from] 16 | end 17 | end 18 | 19 | # append location of publication dir to from 20 | dir = keys.dir 21 | trans.collect!{ |from,to| [File.join(dir,from), to] } 22 | 23 | if keys.dryrun 24 | puts "ftp open #{keys.user}@#{keys.host}:#{keys.root}/" 25 | keys.trans.each do |f, t| 26 | puts "ftp put #{f} #{t}" 27 | end 28 | else 29 | require 'net/ftp' 30 | Net::FTP.open(keys.host) do |ftp| 31 | ftp.login(keys.user) #password? 32 | ftp.chdir(keys.root) 33 | keys.trans.each do |f, t| 34 | puts "ftp #{f} #{t}" unless keys.quiet 35 | ftp.putbinaryfile( f, t, 1024 ) 36 | end 37 | end 38 | end 39 | end 40 | 41 | # Use sftp to upload files. 42 | # 43 | def sftp( keys ) 44 | keys = upload_parameters(keys) 45 | 46 | # set transfer rules 47 | if keys.stage 48 | trans = stage_transfer(keys.stage) 49 | else 50 | files(keys.dir, keys.copy).each do |from| 51 | trans << [from,from] 52 | end 53 | end 54 | 55 | # append location of publication dir to from 56 | dir = keys.dir 57 | trans.collect!{ |from,to| [File.join(dir,from), to] } 58 | 59 | if keys.dryrun 60 | puts "sftp open #{keys.user}@#{keys.host}:#{keys.root}/" 61 | keys.trans.each do |f,t| 62 | puts "sftp put #{f} #{t}" 63 | end 64 | else 65 | require 'net/sftp' 66 | Net::SFTP.start(keys.host, keys.user, keys.pass) do |sftp| 67 | #sftp.login( user ) 68 | sftp.chdir(keys.root) 69 | keys.trans.each do |f,t| 70 | puts "sftp #{f} #{t}" unless keys.quiet 71 | sftp.put_file(f,t) #, 1024 ) 72 | end 73 | end 74 | end 75 | end 76 | 77 | # Put together the list of files to copy. 78 | def files( dir, copy ) 79 | Dir.chdir(dir) do 80 | del, add = copy.partition{ |f| /^[-]/ =~ f } 81 | 82 | # remove - and + prefixes 83 | del.collect!{ |f| f.sub(/^[-]/,'') } 84 | add.collect!{ |f| f.sub(/^[+]/,'') } 85 | 86 | #del.concat(must_exclude) 87 | 88 | files = [] 89 | add.each{ |g| files += Dir.glob(g) } 90 | del.each{ |g| files -= Dir.glob(g) } 91 | 92 | files.collect!{ |f| f.sub(/^\//,'') } 93 | 94 | return files 95 | end 96 | end 97 | 98 | # Combine three part stage list into a two part from->to list. 99 | # 100 | # Using the stage list of three space separated fields. 101 | # 102 | # fromdir file todir 103 | # 104 | # This is used to generate a from -> to list of the form: 105 | # 106 | # fromdir/file todir/file 107 | # 108 | def stage_transfer( list ) 109 | trans = [] 110 | list.each do |line| 111 | trans << Shellwords.shellwords(line) 112 | end 113 | 114 | trans.collect! do |from, base, to| 115 | file = File.join(from,base) 116 | to = File.join(to,base) 117 | [from, to] 118 | end 119 | end 120 | 121 | end 122 | 123 | end 124 | 125 | -------------------------------------------------------------------------------- /work/deprecated/store.rb: -------------------------------------------------------------------------------- 1 | require 'fileutils' 2 | 3 | module Path 4 | 5 | # DEPRECATE: will move to Confectionery project. 6 | 7 | # TODO: I would like to remove the depth parameter, 8 | # which would be easy enough if we had a null/nack 9 | # object, ie. a type of nil that returns itself on 10 | # missing methods. 11 | # 12 | class Store 13 | 14 | def self.load(dir) 15 | o = new(dir) 16 | o.prime! 17 | o 18 | end 19 | 20 | # 21 | def initialize(dir, data={}, depth=0) 22 | require 'yaml' 23 | 24 | @dir = dir 25 | @data = data 26 | @depth = depth 27 | 28 | #@save = [] 29 | end 30 | 31 | # 32 | def [](name) 33 | name = name.to_s 34 | if @data.key?(name) 35 | @data[name] 36 | else 37 | @data[name] = read!(name) 38 | end 39 | end 40 | 41 | # TODO: what if name is a directory? 42 | def []=(name, value) 43 | name = name.to_s 44 | #@save << name 45 | @data[name] = value 46 | end 47 | 48 | # 49 | def to_h 50 | Dir.entries(@dir).each do |fname| 51 | self[fname] 52 | end 53 | @data.dup 54 | end 55 | 56 | # 57 | def each(&block) 58 | @data.each(&block) 59 | end 60 | 61 | # 62 | #def to_yaml(opts={}) 63 | # @data.to_yaml(opts={}) 64 | #end 65 | 66 | def to_yaml( opts = {} ) 67 | YAML::quick_emit(self, opts) do |out| 68 | out.map(@data.taguri, to_yaml_style) do |map| 69 | each do |k, v| 70 | map.add(k, v) 71 | end 72 | end 73 | end 74 | end 75 | 76 | 77 | # TODO: raise if arguments are wrong. 78 | def method_missing(name, *a, &b) 79 | name = name.to_s 80 | case name 81 | when /\=$/ 82 | self[name.chomp('=')] = a.first 83 | when /\?$/ 84 | self[name.chomp('?')] 85 | when /\!$/ 86 | super 87 | else 88 | self[name] 89 | end 90 | end 91 | 92 | def read!(name) 93 | file = File.join(@dir, name) 94 | if File.file?(file) 95 | YAML.load(File.new(file)) 96 | elsif File.directory?(file) 97 | if @depth.zero? 98 | self.class.new(file) 99 | else 100 | self.class.new(file, depth - 1) 101 | end 102 | else 103 | if @depth.zero? 104 | nil 105 | else 106 | self.class.new(file) 107 | end 108 | end 109 | end 110 | 111 | # 112 | def save! 113 | @data.each do |name, value| 114 | file = File.join(@dir,name) 115 | case value 116 | when self.class 117 | FileUtils.mkdir_p(@dir) 118 | value.save! 119 | else 120 | old = File.read(file) if File.exist?(file) 121 | new = value.to_yaml 122 | if old != new 123 | FileUtils.mkdir_p(@dir) 124 | File.open(file, 'w'){ |f| f << new } 125 | end 126 | end 127 | end 128 | end 129 | 130 | # 131 | def prime!(hash=nil) 132 | if hash 133 | hash.each do |k,v| 134 | case v 135 | when Hash 136 | self[k] = self.class.new(File.join(@dir,k),v) 137 | when self.class 138 | self[k] = self.class.new(File.join(@dir,k),v.to_h) 139 | else 140 | self[k] = v 141 | end 142 | end 143 | else 144 | entries = Dir.entries(@dir) - [ '.', '..' ] 145 | entries.each do |fname| 146 | v = self[fname] 147 | v.prime! if self.class === v 148 | end 149 | end 150 | end 151 | 152 | # Specify a node in the store as a branch with a fixed depth. 153 | def branch!(name, depth=1) 154 | path = File.join(@dir, name) 155 | self[name] = self.class.new(path, {}, depth) 156 | end 157 | 158 | end 159 | 160 | end 161 | 162 | -------------------------------------------------------------------------------- /vendor/path/xdg.rb: -------------------------------------------------------------------------------- 1 | # XDG Copyright (c) 2008 Thomas Sawyer 2 | 3 | # = XDG Base Directory Standard 4 | # 5 | # This provides a conveient library for conforming to the 6 | # XDG Base Directory Standard. 7 | # 8 | # http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html 9 | # 10 | # Some important clarifications, not made clear by the 11 | # above specification. 12 | # 13 | # The data directories are for "read-only" files. In other words once 14 | # something is put there, it should only be read, never written to 15 | # by a program. (Generally speaking only users or pacakge mangers should 16 | # be adding, changing or removing files from the data locations. 17 | # 18 | # The config locations are where you should store files that may change, 19 | # and effect operations depending one there content. This is like etc/ 20 | # in the FHS, but alterable by end users and and end user programs, 21 | # not just root and sudo admin scripts. 22 | # 23 | # The cache location stores files that could judt as well be deleted 24 | # and everyihtng still works find. This is for variable and temporary 25 | # files. Like var/ in FHS. 26 | # 27 | # This module returns paths as strings. 28 | # 29 | module XDG 30 | 31 | ############### 32 | module_function 33 | ############### 34 | 35 | def home 36 | ENV['HOME'] || File.expand_path('~') 37 | end 38 | 39 | # Location of user's personal config directory. 40 | def xdg_config_home 41 | File.expand_path( 42 | ENV['XDG_CONFIG_HOME'] || File.join(home, '.config') 43 | ) 44 | end 45 | 46 | # List of user's shared config directories. 47 | def xdg_config_dirs 48 | dirs = ENV['XDG_CONFIG_DIRS'].split(/[:;]/) 49 | if dirs.empty? 50 | dirs = %w{/etc/xdg} 51 | end 52 | dir.collect{ |d| File.expand_path(d) } 53 | end 54 | 55 | # Location of user's personal data directory. 56 | def xdg_data_home 57 | File.expand_path( 58 | ENV['XDG_DATA_HOME'] || File.join(home, '.local', 'share') 59 | ) 60 | end 61 | 62 | # List of user's shared data directores. 63 | def xdg_data_dirs 64 | dirs = ENV['XDG_DATA_DIRS'].split(/[:;]/) 65 | if dirs.empty? 66 | dirs = %w{/usr/local/share/ /usr/share/} 67 | end 68 | dir.collect{ |d| File.expand_path(d) } 69 | end 70 | 71 | # Location of user's personal cache directory. 72 | def xdg_cache_home 73 | File.expand_path( 74 | ENV['XDG_CACHE_HOME'] || File.join(home, '.cache') 75 | ) 76 | end 77 | 78 | # Find a file or directory in data dirs. 79 | def xdg_data_file(file) 80 | [data_home, *data_dirs].each do |dir| 81 | path = File.join(dir,file) 82 | break path if File.exist?(path) 83 | end 84 | end 85 | 86 | # Find a file or directory in config dirs. 87 | def xdg_config_file(file) 88 | [config_home, *config_dirs].each do |dir| 89 | path = File.join(dir,file) 90 | break path if File.exist?(path) 91 | end 92 | end 93 | 94 | # Find a file or directory in the user cache. 95 | def xdg_cache_file(file) 96 | File.join(cache_home,file) 97 | end 98 | 99 | ############################################ 100 | # The following are not strictly XDG spec, # 101 | # but are useful in the same respect. # 102 | ############################################ 103 | 104 | # Location of working config directory. 105 | def xdg_config_work 106 | File.expand_path( 107 | #ENV['XDG_CONFIG_WORK'] || File.join(Dir.pwd, '.config') 108 | File.join(Dir.pwd, '.config') 109 | ) 110 | end 111 | 112 | # Location of working data directory. 113 | def xdg_data_work 114 | File.expand_path( 115 | #ENV['XDG_DATA_WORK'] || File.join(Dir.pwd, '.share') 116 | File.join(Dir.pwd, '.share') 117 | ) 118 | end 119 | 120 | # Location of working cache directory. 121 | def xdg_cache_work 122 | File.expand_path( 123 | #ENV['XDG_CACHE_WORK'] || File.join(Dir.pwd, '.cache') 124 | File.join(Dir.pwd, '.cache') 125 | ) 126 | end 127 | 128 | end # module XDG 129 | 130 | 131 | -------------------------------------------------------------------------------- /lib/path/fileutils.rb: -------------------------------------------------------------------------------- 1 | require 'fileutils' 2 | 3 | module FileUtils 4 | 5 | ############### 6 | module_function 7 | ############### 8 | 9 | # Stage by hard linking included files to a stage directory. 10 | # 11 | # stage_directory Stage directory. 12 | # files Files to link to stage. 13 | # 14 | # TODO: Rename to linkstage or something less likely to name clash? 15 | # TODO: Add options for :verbose, :noop and :dryrun ? 16 | # 17 | def stage(stage_directory, source_directory, files) 18 | stage_directory, source_directory = stage_directory.to_s, source_directory.to_s 19 | # Ensure existance of staging area. 20 | rm_r(stage_directory) if File.directory?(stage_directory) 21 | mkdir_p(stage_directory) 22 | # Link files into staging area. 23 | files.each do |f| 24 | src = File.join(source_directory, f) 25 | file = File.join(stage_directory, f) 26 | if File.directory?(src) 27 | mkdir_p(file) unless File.exist?(file) 28 | else 29 | fdir = File.dirname(file) 30 | mkdir_p(fdir) unless File.exist?(fdir) 31 | unless File.exist?(file) and File.mtime(file) >= File.mtime(src) 32 | ln(src, file) #safe_ln ? 33 | end 34 | end 35 | end 36 | return stage_directory 37 | end 38 | 39 | # Opposite of uptodate? 40 | # 41 | def outofdate?(path, *sources) 42 | #return true unless File.exist?(path) 43 | ! uptodate?(path, sources.flatten) 44 | end 45 | 46 | # DEPRECATE 47 | # Does a path need updating, based on given +sources+? 48 | # This compares mtimes of give paths. Returns false 49 | # if the path needs to be updated. 50 | # 51 | def out_of_date?(path, *sources) 52 | return true unless File.exist?(path) 53 | 54 | sources = sources.collect{ |source| Dir.glob(source) }.flatten 55 | mtimes = sources.collect{ |file| File.mtime(file) } 56 | 57 | return true if mtimes.empty? # TODO: This the way to go here? 58 | 59 | File.mtime(path) < mtimes.max 60 | end 61 | 62 | # An intergrated glob like method that take a set of include globs, 63 | # exclude globs and ignore globs to produce a collection of paths. 64 | # 65 | # The ignore_globs differ from exclude_globs in that they match by 66 | # the basename of the path rather than the whole pathname. 67 | # 68 | # TODO: Should ignore be based on any portion of the path, not just the basename? 69 | # 70 | def amass(include_globs, exclude_globs=[], ignore=[]) 71 | include_files = include_globs.flatten.map{ |g| Dir.glob(g) }.flatten.uniq 72 | exclude_files = exclude_globs.flatten.map{ |g| Dir.glob(g) }.flatten.uniq 73 | 74 | include_globs = include_globs.map{ |f| File.directory?(f) ? File.join(f, '**/*') : f } # Recursive! 75 | exclude_globs = exclude_globs.map{ |f| File.directory?(f) ? File.join(f, '**/*') : f } # Recursive! 76 | 77 | include_files = include_globs.flatten.map{ |g| Dir.glob(g) }.flatten.uniq 78 | exclude_files = exclude_globs.flatten.map{ |g| Dir.glob(g) }.flatten.uniq 79 | 80 | files = include_files - exclude_files 81 | 82 | files = files.reject{ |f| ignore.any?{ |x| File.fnmatch?(x, File.basename(f)) } } 83 | 84 | files 85 | end 86 | 87 | module Verbose 88 | public :outofdate? 89 | #def stage(stage_directory, files, options={}) 90 | # options[:verbose] = true 91 | # FileUtils.stage(stage_directory, files, options={}) 92 | #end 93 | end 94 | 95 | module NoWrite 96 | public :outofdate? 97 | #def stage(stage_directory, files, options={}) 98 | # options[:noop] = true 99 | # FileUtils.stage(stage_directory, files, options={}) 100 | #end 101 | # Stage by hard linking included files to a stage directory. 102 | # 103 | # stage_directory Stage directory. 104 | # files Files to link to stage. 105 | # 106 | def stage(stage_directory, files) 107 | return stage_directory # Don't link to stage if dryrun. 108 | end 109 | end 110 | 111 | module DryRun 112 | public :outofdate? 113 | #def stage(stage_directory, files, options={}) 114 | # options[:dryrun] = true 115 | # FileUtils.stage(stage_directory, files, options={}) 116 | #end 117 | end 118 | 119 | end 120 | 121 | -------------------------------------------------------------------------------- /work/consider/fileable.rb: -------------------------------------------------------------------------------- 1 | # = Fileable 2 | # 3 | # Make File-esque classes. Fileable makes it easy to 4 | # create classes that can load from files. 5 | # 6 | # Class level mixin for loading/opening file classes. 7 | # You will generally want to use extend with this. 8 | # 9 | # NOTE: This is an expiremental library, and is still 10 | # undergoing revision. 11 | # 12 | # = Copying 13 | # 14 | # Copyright (c) 2007 Thomas Sawyer 15 | # 16 | # Ruby License 17 | # 18 | # This module is free software. You may use, modify, and/or redistribute this 19 | # software under the same terms as Ruby. 20 | # 21 | # This program is distributed in the hope that it will be useful, but WITHOUT 22 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 23 | # FOR A PARTICULAR PURPOSE. 24 | # 25 | # == Authors 26 | # 27 | # * Thomas Sawyer 28 | # 29 | # == Todo 30 | # 31 | # * Consider cachable version of Fileable. 32 | 33 | 34 | # = Fileable 35 | # 36 | # Make File-esque classes. Fileable makes it easy to 37 | # create classes that can load from files. 38 | # 39 | # Class level mixin for loading/opening file classes. 40 | # You will generally want to use extend with this. 41 | # 42 | # NOTE: This is an expiremental library, and is still 43 | # undergoing revision. 44 | # 45 | # 46 | module Fileable 47 | 48 | # When included extend DSL too. 49 | 50 | def self.included(base) 51 | base.extend DSL 52 | end 53 | 54 | # Store raw content of file. 55 | 56 | #attr_reader :content 57 | 58 | # New fileable object. By default this is called 59 | # by #read passing the file contents. Override it 60 | # if need is differnt. 61 | 62 | def initialize(content) 63 | @content = content 64 | end 65 | 66 | # Override this if reading is differnt. 67 | 68 | def read(file) 69 | self.file = file if defined?(:file=) 70 | initialize(File.read(file)) 71 | end 72 | 73 | # 74 | module DSL 75 | 76 | # While this doesn't allpy to classes, for modules 77 | # it is needed to keep the DSL inheritance going. 78 | 79 | def included(base) 80 | base.extend DSL 81 | end 82 | 83 | # Override this with the name or name-glob of 84 | # the default file. If no default, return nil. 85 | 86 | def filename; nil; end 87 | 88 | # Load from file(s). 89 | 90 | def open(path=nil) 91 | file = file(path) 92 | if file 93 | fobj = new 94 | fobj.send(:read, file) 95 | return fobj 96 | end 97 | end 98 | 99 | # An initializer that can take either a File, Pathname 100 | # or raw data. This works much like YAML::load does. 101 | # Unlike +open+, +load+ requires an exact path parameter. 102 | 103 | def load(path_or_data) 104 | case path_or_data 105 | when File 106 | open(path_or_data.path) 107 | when Pathname 108 | open(path_or_data.realpath) 109 | else 110 | new(path_or_data) 111 | end 112 | end 113 | 114 | # Lookup file. 115 | 116 | def lookup(name=nil) 117 | file = locate(name) 118 | file ? open(file) : nil #raise LoadError 119 | end 120 | 121 | # Locate file (case insensitive). 122 | 123 | def locate(name=nil) 124 | name ||= filename 125 | raise LoadError unless name 126 | Dir.ascend(Dir.pwd) do |dir| 127 | match = File.join(dir, name) 128 | files = Dir.glob(match, File::FNM_CASEFOLD) 129 | if file = files[0] 130 | return file 131 | end 132 | end 133 | return nil 134 | end 135 | 136 | # Find file. The +path+ has to be either the 137 | # exact path or the directory where a 138 | # standard-named file resides. 139 | 140 | def file(path=nil) 141 | if !path 142 | raise LoadError unless filename 143 | path = filename 144 | elsif File.directory?(path) 145 | raise LoadError unless filename 146 | path = File.join(path, filename) 147 | end 148 | if file = Dir.glob(path, File::FNM_CASEFOLD)[0] 149 | File.expand_path(file) 150 | else 151 | raise Errno::ENOENT 152 | end 153 | end 154 | 155 | # Load cache. PackageInfo is multiton when loaded by file. 156 | 157 | def load_cache 158 | @load_cache ||= {} 159 | end 160 | end 161 | end 162 | 163 | -------------------------------------------------------------------------------- /lib/path/console.rb: -------------------------------------------------------------------------------- 1 | require 'readline' 2 | 3 | module Ratch 4 | 5 | # Ratch Shell Console 6 | # 7 | class Console 8 | 9 | attr_accessor :suppress_output 10 | 11 | # Set up the user's environment, including a pure binding into which 12 | # env.rb and commands.rb are mixed. 13 | def initialize 14 | #root = Rush::Dir.new('/') 15 | #home = Rush::Dir.new(ENV['HOME']) if ENV['HOME'] 16 | #pwd = Rush::Dir.new(ENV['PWD']) if ENV['PWD'] 17 | 18 | #@config = Rush::Config.new 19 | 20 | @config.load_history.each do |item| 21 | Readline::HISTORY.push(item) 22 | end 23 | 24 | Readline.basic_word_break_characters = "" 25 | Readline.completion_append_character = nil 26 | Readline.completion_proc = completion_proc 27 | 28 | @shell = Ratch::Shell.new 29 | 30 | @pure_binding = @shell.instance_eval("binding") 31 | 32 | $last_res = nil 33 | 34 | #eval @config.load_env, @pure_binding 35 | 36 | #commands = @config.load_commands 37 | #Rush::Dir.class_eval commands 38 | #Array.class_eval commands 39 | end 40 | 41 | # Run the interactive shell using readline. 42 | def run 43 | loop do 44 | cmd = Readline.readline('ratch> ') 45 | 46 | finish if cmd.nil? or cmd == 'exit' 47 | 48 | next if cmd == "" 49 | 50 | Readline::HISTORY.push(cmd) 51 | 52 | execute(cmd) 53 | end 54 | end 55 | 56 | # Run a single command. 57 | def execute(cmd) 58 | res = eval(cmd, @pure_binding) 59 | $last_res = res 60 | eval("_ = $last_res", @pure_binding) 61 | print_result(res) 62 | #rescue Rush::Exception => e 63 | # puts "Exception #{e.class} -> #{e.message}" 64 | rescue ::Exception => e 65 | puts "Exception #{e.class} -> #{e.message}" 66 | e.backtrace.each do |t| 67 | puts " #{::File.expand_path(t)}" 68 | end 69 | end 70 | 71 | # Save history to ~/.config/ratch/history when the shell exists. 72 | def finish 73 | @config.save_history(Readline::HISTORY.to_a) 74 | puts 75 | exit 76 | end 77 | 78 | # TODO: FIX ALL BELOW 79 | 80 | # Nice printing of different return types, particularly Rush::SearchResults. 81 | def print_result(res) 82 | return if self.suppress_output 83 | if res.kind_of? String 84 | puts res 85 | elsif res.kind_of? Rush::SearchResults 86 | widest = res.entries.map { |k| k.full_path.length }.max 87 | res.entries_with_lines.each do |entry, lines| 88 | print entry.full_path 89 | print ' ' * (widest - entry.full_path.length + 2) 90 | print "=> " 91 | print res.colorize(lines.first.strip.head(30)) 92 | print "..." if lines.first.strip.length > 30 93 | if lines.size > 1 94 | print " (plus #{lines.size - 1} more matches)" 95 | end 96 | print "\n" 97 | end 98 | puts "#{res.entries.size} matching files with #{res.lines.size} matching lines" 99 | elsif res.respond_to? :each 100 | counts = {} 101 | res.each do |item| 102 | puts item 103 | counts[item.class] ||= 0 104 | counts[item.class] += 1 105 | end 106 | if counts == {} 107 | puts "=> (empty set)" 108 | else 109 | count_s = counts.map do |klass, count| 110 | "#{count} x #{klass}" 111 | end.join(', ') 112 | puts "=> #{count_s}" 113 | end 114 | else 115 | puts "=> #{res.inspect}" 116 | end 117 | end 118 | 119 | def path_parts(input) # :nodoc: 120 | case input 121 | when /((?:@{1,2}|\$|)\w+(?:\[[^\]]+\])*)([\[\/])(['"])([^\3]*)$/ 122 | $~.to_a.slice(1, 4).push($~.pre_match) 123 | when /((?:@{1,2}|\$|)\w+(?:\[[^\]]+\])*)(\.)(\w*)$/ 124 | $~.to_a.slice(1, 3).push($~.pre_match) 125 | when /((?:@{1,2}|\$|)\w+)$/ 126 | $~.to_a.slice(1, 1).push(nil).push($~.pre_match) 127 | else 128 | [ nil, nil, nil ] 129 | end 130 | end 131 | 132 | def complete_method(receiver, dot, partial_name, pre) 133 | path = eval("#{receiver}.full_path", @pure_binding) rescue nil 134 | box = eval("#{receiver}.box", @pure_binding) rescue nil 135 | if path and box 136 | (box[path].methods - Object.methods).select do |e| 137 | e.match(/^#{Regexp.escape(partial_name)}/) 138 | end.map do |e| 139 | (pre || '') + receiver + dot + e 140 | end 141 | end 142 | end 143 | 144 | def complete_path(possible_var, accessor, quote, partial_path, pre) # :nodoc: 145 | original_var, fixed_path = possible_var, '' 146 | if /^(.+\/)([^\/]*)$/ === partial_path 147 | fixed_path, partial_path = $~.captures 148 | possible_var += "['#{fixed_path}']" 149 | end 150 | full_path = eval("#{possible_var}.full_path", @pure_binding) rescue nil 151 | box = eval("#{possible_var}.box", @pure_binding) rescue nil 152 | if full_path and box 153 | Rush::Dir.new(full_path, box).entries.select do |e| 154 | e.name.match(/^#{Regexp.escape(partial_path)}/) 155 | end.map do |e| 156 | (pre || '') + original_var + accessor + quote + fixed_path + e.name + (e.dir? ? "/" : "") 157 | end 158 | end 159 | end 160 | 161 | def complete_variable(partial_name, pre) 162 | lvars = eval('local_variables', @pure_binding) 163 | gvars = eval('global_variables', @pure_binding) 164 | ivars = eval('instance_variables', @pure_binding) 165 | (lvars + gvars + ivars).select do |e| 166 | e.match(/^#{Regexp.escape(partial_name)}/) 167 | end.map do |e| 168 | (pre || '') + e 169 | end 170 | end 171 | 172 | # Try to do tab completion on dir square brackets and slash accessors. 173 | # 174 | # Example: 175 | # 176 | # dir['subd # presing tab here will produce dir['subdir/ if subdir exists 177 | # dir/'subd # presing tab here will produce dir/'subdir/ if subdir exists 178 | # 179 | # This isn't that cool yet, because it can't do multiple levels of subdirs. 180 | # It does work remotely, though, which is pretty sweet. 181 | def completion_proc 182 | proc do |input| 183 | receiver, accessor, *rest = path_parts(input) 184 | if receiver 185 | case accessor 186 | when /^[\[\/]$/ 187 | complete_path(receiver, accessor, *rest) 188 | when /^\.$/ 189 | complete_method(receiver, accessor, *rest) 190 | when nil 191 | complete_variable(receiver, *rest) 192 | end 193 | end 194 | end 195 | end 196 | 197 | end 198 | 199 | end 200 | -------------------------------------------------------------------------------- /lib/path/list.rb: -------------------------------------------------------------------------------- 1 | module Path 2 | 3 | class List < Array 4 | 5 | # 6 | attr :local 7 | 8 | # 9 | def initialize(local, *globs) 10 | @local = Pathname.new(local) 11 | end 12 | 13 | # 14 | def [](*entries) 15 | entries.each do |entry| 16 | case entry 17 | when Pathname 18 | push(@local + entry) 19 | else 20 | concat(Dir.glob(@local + entry).map{ |f| Pathname.new(f) }) 21 | end 22 | end 23 | return self 24 | end 25 | 26 | # relative to local 27 | def entries 28 | map{ |entry| entry.sub(local+'/','') } 29 | end 30 | 31 | #def each 32 | # super{ 33 | # yield(@local + entry) 34 | # } 35 | #end 36 | 37 | ############# 38 | # FileTest # 39 | ############# 40 | 41 | # 42 | def size 43 | inject(0){ |sum, path| sum + FileTest.size(path) } 44 | end 45 | alias_method :size?, :size 46 | 47 | def directory? ; all?{ |path| FileTest.directory?(path) } ; end 48 | def symlink? ; all?{ |path| FileTest.symlink?(path) } ; end 49 | def readable? ; all?{ |path| FileTest.readable?(path) } ; end 50 | def chardev? ; all?{ |path| FileTest.chardev?(path) } ; end 51 | def exist? ; all?{ |path| FileTest.exist?(path) } ; end 52 | def exists? ; all?{ |path| FileTest.exists?(path) } ; end 53 | def zero? ; all?{ |path| FileTest.zero?(path) } ; end 54 | def pipe? ; all?{ |path| FileTest.pipe?(path) } ; end 55 | def file? ; all?{ |path| FileTest.file?(path) } ; end 56 | def sticky? ; all?{ |path| FileTest.sticky?(path) } ; end 57 | def blockdev? ; all?{ |path| FileTest.blockdev?(path) } ; end 58 | def grpowned? ; all?{ |path| FileTest.grpowned?(path) } ; end 59 | def setgid? ; all?{ |path| FileTest.setgid?(path) } ; end 60 | def setuid? ; all?{ |path| FileTest.setuid?(path) } ; end 61 | def socket? ; all?{ |path| FileTest.socket?(path) } ; end 62 | def owned? ; all?{ |path| FileTest.owned?(path) } ; end 63 | def writable? ; all?{ |path| FileTest.writable?(path) } ; end 64 | def executable? ; all?{ |path| FileTest.executable?(path) } ; end 65 | def safe? ; all?{ |path| FileTest.safe?(path) } ; end 66 | 67 | # Will these work since all paths are localized? 68 | def relative? ; all?{ |path| FileTest.relative?(path) } ; end 69 | def absolute? ; all?{ |path| FileTest.absolute?(path) } ; end 70 | 71 | def writable_real? ; all?{ |path| FileTest.writable_real?(path) } ; end 72 | def executable_real? ; all?{ |path| FileTest.executable_real?(path) } ; end 73 | def readable_real? ; all?{ |path| FileTest.readable_real?(path) } ; end 74 | 75 | alias_method :dir?, :directory? 76 | 77 | def identical?(other) 78 | all?{ |path| FileTest.identical?(path, other) } 79 | end 80 | alias_method :compare_file, :identical? 81 | alias_method :cmp, :identical? 82 | 83 | 84 | ############# 85 | # FileUtils # 86 | ############# 87 | 88 | # Low-level Methods Omitted 89 | # ------------------------- 90 | # getwd -> pwd 91 | # compare_file -> cmp 92 | # remove_file -> rm 93 | # copy_file -> cp 94 | # remove_dir -> rmdir 95 | # safe_unlink -> rm_f 96 | # makedirs -> mkdir_p 97 | # rmtree -> rm_rf 98 | # copy_stream 99 | # remove_entry 100 | # copy_entry 101 | # remove_entry_secure 102 | # compare_stream 103 | 104 | # Present working directory. (?) 105 | def pwd 106 | @local 107 | end 108 | 109 | # 110 | def mkdir(options={}) 111 | map{ |dir| fileutils.mkdir(dir, options) } 112 | end 113 | 114 | def mkdir_p(options={}) 115 | map{ |dir| fileutils.mkdir_p(dir, options) } 116 | end 117 | alias_method :mkpath, :mkdir_p 118 | 119 | def rmdir(options={}) 120 | map{ |dir| fileutils.rmdir(dir, options) } 121 | end 122 | 123 | # ln(list, destdir, options={}) 124 | def ln(new, options={}) 125 | src = to_a 126 | #new = localize(new) 127 | fileutils.ln(src, new, options) 128 | end 129 | alias_method :link, :ln 130 | 131 | # ln_s(list, destdir, options={}) 132 | def ln_s(new, options={}) 133 | src = to_a 134 | #new = localize(new) 135 | fileutils.ln_s(src, new, options) 136 | end 137 | alias_method :symlink, :ln_s 138 | 139 | def ln_sf(new, options={}) 140 | src = to_a 141 | #new = localize(new) 142 | fileutils.ln_sf(src, new, options) 143 | end 144 | 145 | # cp(list, dir, options={}) 146 | def cp(dest, options={}) 147 | src = to_a 148 | #dest = localize(dest) 149 | fileutils.cp(src, dest, options) 150 | end 151 | alias_method :copy, :cp 152 | 153 | # cp_r(list, dir, options={}) 154 | def cp_r(dest, options={}) 155 | src = to_a 156 | #dest = localize(dest) 157 | fileutils.cp_r(src, dest, options) 158 | end 159 | 160 | # mv(list, dir, options={}) 161 | def mv(dest, options={}) 162 | src = to_a 163 | #dest = localize(dest) 164 | fileutils.mv(src, dest, options) 165 | end 166 | alias_method :move, :mv 167 | 168 | def rm(options={}) 169 | list = to_a 170 | fileutils.rm(list, options) 171 | end 172 | alias_method :remove, :rm 173 | 174 | def rm_r(options={}) 175 | list = to_a 176 | fileutils.rm_r(list, options) 177 | end 178 | 179 | def rm_f(list, options={}) 180 | list = to_a 181 | fileutils.rm_f(list, options) 182 | end 183 | 184 | def rm_rf(list, options={}) 185 | list = to_a 186 | fileutils.rm_rf(list, options) 187 | end 188 | 189 | def install(src, dest, mode, options={}) 190 | src = to_a 191 | #dest = localize(dest) 192 | fileutils.install(src, dest, mode, options) 193 | end 194 | 195 | def chmod(mode, list, options={}) 196 | list = to_a 197 | fileutils.chmod(mode, list, options) 198 | end 199 | 200 | def chmod_r(mode, list, options={}) 201 | list = to_a 202 | fileutils.chmod_r(mode, list, options) 203 | end 204 | #alias_method :chmod_R, :chmod_r 205 | 206 | def chown(user, group, list, options={}) 207 | list = to_a 208 | fileutils.chown(user, group, list, options) 209 | end 210 | 211 | def chown_r(user, group, list, options={}) 212 | list = to_a 213 | fileutils.chown_r(user, group, list, options) 214 | end 215 | #alias_method :chown_R, :chown_r 216 | 217 | def touch(list, options={}) 218 | list = to_a 219 | fileutils.touch(list, options) 220 | end 221 | 222 | # 223 | def stage(dir) 224 | #dir = localize(directory) 225 | #files = localize(files) 226 | #fileutils.stage(dir, work, entries) 227 | fileutils.stage(dir, local, entries) 228 | end 229 | 230 | # An intergrated glob like method that takes a set of include globs, 231 | # exclude globs and ignore globs to produce a collection of paths. 232 | # 233 | # Ignore_globs differ from exclude_globs in that they match by 234 | # the basename of the path rather than the whole pathname. 235 | # 236 | #def amass(include_globs, exclude_globs=[], ignore_globs=[]) 237 | # locally do 238 | # fileutils.amass(include_globs, exclude_globs, ignore_globs) 239 | # end 240 | #end 241 | 242 | # 243 | def outofdate?(path) 244 | fileutils.outofdate?(path, to_a) 245 | end 246 | 247 | # 248 | def uptodate?(path) 249 | fileutils.uptodate?(path, to_a) 250 | end 251 | 252 | # 253 | #def uptodate?(new, old_list, options=nil) 254 | # new = localize(new) 255 | # old = localize(old_list) 256 | # fileutils.uptodate?(new, old, options) 257 | #end 258 | 259 | # 260 | #def method_missing(s, *a, &b) 261 | # entries.map do |e| 262 | # e.__send__(s, *a, &b) 263 | # end 264 | #end 265 | 266 | private 267 | 268 | # Returns FileUtils module based on mode. 269 | def fileutils 270 | if dryrun? 271 | ::FileUtils::DryRun 272 | elsif noop? 273 | ::FileUtils::Noop 274 | elsif verbose? 275 | ::FileUtils::Verbose 276 | else 277 | ::FileUtils 278 | end 279 | end 280 | 281 | end 282 | 283 | end 284 | 285 | -------------------------------------------------------------------------------- /work/pathname.qed: -------------------------------------------------------------------------------- 1 | require 'path/name' 2 | 3 | testcase Path::Name do 4 | 5 | # Support Classes 6 | 7 | class AnotherStringLike # :nodoc: 8 | def initialize(s) @s = s end 9 | def to_str() @s end 10 | def ==(other) @s == other end 11 | end 12 | 13 | testunit :initialize do 14 | p1 = Path::Name.new('a') 15 | p1.to_s.assert == 'a' 16 | 17 | p2 = Path::Name.new(p1) 18 | p1.assert == p2 19 | end 20 | 21 | testunit :==, :===, :eql? do 22 | obj = Path::Name.new("a") 23 | str = "a" 24 | sym = :a 25 | ano = AnotherStringLike.new("a") 26 | 27 | obj.assert == str 28 | str.assert == obj 29 | obj.assert == ano 30 | ano.assert == obj 31 | obj.assert == sym 32 | sym.assert == obj 33 | 34 | obj2 = Path::Name.new("a") 35 | obj.assert == obj2 36 | obj.assert === obj2 37 | obj.assert.eql? obj2 38 | end 39 | 40 | testunit :hash do 41 | h = {} 42 | h[Path::Name.new("a")] = 1 43 | h[Path::Name.new("a")] = 2 44 | h.size.assert == 1 45 | end 46 | 47 | testunit :<=> do 48 | 49 | testsect "vs. Path::Name" do 50 | 51 | def assert_pathname_cmp(e, s1, s2) 52 | p1 = Path::Name.new(s1) 53 | p2 = Path::Name.new(s2) 54 | r = p1 <=> p2 55 | assert(e == r, 56 | "#{p1.inspect} <=> #{p2.inspect}: <#{e}> expected but was <#{r}>") 57 | end 58 | 59 | assert_pathname_cmp( 0, "a", "a") 60 | assert_pathname_cmp( 1, "b", "a") 61 | assert_pathname_cmp(-1, "a", "b") 62 | ss = %w( 63 | a 64 | a/ 65 | a/b 66 | a. 67 | a0 68 | ) 69 | s1 = ss.shift 70 | ss.each {|s2| 71 | assert_pathname_cmp(-1, s1, s2) 72 | s1 = s2 73 | } 74 | 75 | end 76 | 77 | testsect "vs. String" do 78 | (Path::Name.new("a") <=> "a").assert.nil? 79 | ("a" <=> Path::Name.new("a")).assert.nil? 80 | end 81 | 82 | end 83 | 84 | 85 | == syntactical 86 | 87 | assert Path::Name.new("/").root? 88 | assert Path::Name.new("//").root? 89 | assert Path::Name.new("///").root? 90 | 91 | assert! Path::Name.new("").root? 92 | assert! Path::Name.new("a").root? 93 | 94 | == paths 95 | 96 | Path::Name.new('/').to_s.assert == '/' 97 | Path::Name.new('.').to_s.assert == '.' 98 | 99 | Path::Name.new('..').to_s.assert == '..' 100 | Path::Name.new('a/').to_s.assert == 'a/' 101 | Path::Name.new('/a').to_s.assert == '/a' 102 | 103 | == cleanpath 104 | 105 | table = { 106 | '/' => '/', 107 | '//' => '/', 108 | '' => '', 109 | '.' => '.', 110 | '..' => '..', 111 | 112 | 'a' => 'a', 113 | '/.' => '/', 114 | '/..' => '/', 115 | '/a' => '/a', 116 | './' => '.', 117 | '../' => '..', 118 | 'a/' => 'a/', 119 | 120 | 'a//b' => 'a/b', 121 | 'a/.' => 'a/.', 122 | 'a/./' => 'a/.', 123 | 'a/../' => 'a/..', 124 | '/a/.' => '/a/.', 125 | './..' => '..', 126 | '../.' => '..', 127 | './../' => '..', 128 | '.././' => '..', 129 | '/./..' => '/', 130 | '/../.' => '/', 131 | '/./../' => '/', 132 | '/.././' => '/', 133 | 134 | 'a/b/c' => 'a/b/c', 135 | './b/c' => 'b/c', 136 | 'a/./c' => 'a/c', 137 | 'a/b/.' => 'a/b/.', 138 | 'a/../.' => 'a/..', 139 | 140 | '/../.././../a' => '/a', 141 | 142 | 'a/b/../../../../c/../d' => 'a/b/../../../../c/../d' 143 | } 144 | 145 | table.each do |path, result| 146 | Path::Name(path).cleanpath(true).to_s.assert == result 147 | end 148 | 149 | == cleanpath_no_symlink 150 | 151 | Path::Name.new('').cleanpath.to_s.assert == '' 152 | 153 | # given result 154 | table = Hash.new(*%w{ 155 | / / 156 | / // 157 | / /./.. 158 | / /../. 159 | / /./../ 160 | / /.././ 161 | / /. 162 | / /.. 163 | 164 | . . 165 | . ./ 166 | . a/../ 167 | . a/../. 168 | 169 | .. .. 170 | .. ./.. 171 | .. ../. 172 | .. ./../ 173 | .. .././ 174 | .. ../ 175 | 176 | a a 177 | a a/ 178 | a a/. 179 | a a/./ 180 | 181 | /a /a 182 | /a /a/. 183 | /a /../.././../a 184 | 185 | a/b a//b 186 | a/b a/b/. 187 | 188 | b/c ./b/c 189 | a/c a/./c 190 | 191 | a/b/c a/b/c 192 | 193 | ../../d a/b/../../../../c/../d 194 | }) 195 | 196 | table.each do |expect, given| 197 | Path::Name.new(given).cleanpath.to_s.assert == expect 198 | end 199 | 200 | == destructive update 201 | 202 | path = Path::Name.new("a") 203 | path.to_s.replace("b") 204 | path.assert == Path::Name.new("a") 205 | 206 | == nul character 207 | 208 | # assert_raise(ArgumentError) { Path::Name.new("\0") } 209 | 210 | == relative_path_from 211 | 212 | def assert_relpath(result, dest, base) 213 | assert_equal(Path::Name.new(result), 214 | Path::Name.new(dest).relative_path_from(Path::Name.new(base))) 215 | end 216 | 217 | assert_relpath("../a", "a", "b") 218 | assert_relpath("../a", "a", "b/") 219 | assert_relpath("../a", "a/", "b") 220 | assert_relpath("../a", "a/", "b/") 221 | assert_relpath("../a", "/a", "/b") 222 | assert_relpath("../a", "/a", "/b/") 223 | assert_relpath("../a", "/a/", "/b") 224 | assert_relpath("../a", "/a/", "/b/") 225 | 226 | assert_relpath("../b", "a/b", "a/c") 227 | assert_relpath("../a", "../a", "../b") 228 | 229 | assert_relpath("a", "a", ".") 230 | assert_relpath("..", ".", "a") 231 | 232 | assert_relpath(".", ".", ".") 233 | assert_relpath(".", "..", "..") 234 | assert_relpath("..", "..", ".") 235 | 236 | assert_relpath("c/d", "/a/b/c/d", "/a/b") 237 | assert_relpath("../..", "/a/b", "/a/b/c/d") 238 | assert_relpath("../../../../e", "/e", "/a/b/c/d") 239 | assert_relpath("../b/c", "a/b/c", "a/d") 240 | 241 | assert_relpath("../a", "/../a", "/b") 242 | assert_relpath("../../a", "../a", "b") 243 | assert_relpath(".", "/a/../../b", "/b") 244 | assert_relpath("..", "a/..", "a") 245 | assert_relpath(".", "a/../b", "b") 246 | 247 | assert_relpath("a", "a", "b/..") 248 | assert_relpath("b/c", "b/c", "b/..") 249 | 250 | == relative_path_from error 251 | 252 | def assert_relpath_err(dest, base) 253 | assert_raise(ArgumentError) { 254 | Path::Name.new(dest).relative_path_from(Path::Name.new(base)) 255 | } 256 | end 257 | 258 | assert_relpath_err("/", ".") 259 | assert_relpath_err(".", "/") 260 | assert_relpath_err("a", "..") 261 | assert_relpath_err(".", "..") 262 | 263 | == plus 264 | 265 | def assert_pathname_plus(a, b, c) 266 | a = Path::Name.new(a) 267 | b = Path::Name.new(b) 268 | c = Path::Name.new(c) 269 | d = b + c 270 | assert(a == d, 271 | "#{b.inspect} + #{c.inspect}: #{a.inspect} expected but was #{d.inspect}") 272 | end 273 | 274 | assert_pathname_plus('a/b', 'a', 'b') 275 | assert_pathname_plus('a', 'a', '.') 276 | assert_pathname_plus('b', '.', 'b') 277 | assert_pathname_plus('.', '.', '.') 278 | assert_pathname_plus('/b', 'a', '/b') 279 | 280 | assert_pathname_plus('/', '/', '..') 281 | assert_pathname_plus('.', 'a', '..') 282 | assert_pathname_plus('a', 'a/b', '..') 283 | assert_pathname_plus('../..', '..', '..') 284 | assert_pathname_plus('/c', '/', '../c') 285 | assert_pathname_plus('c', 'a', '../c') 286 | assert_pathname_plus('a/c', 'a/b', '../c') 287 | assert_pathname_plus('../../c', '..', '../c') 288 | 289 | == taint 290 | 291 | obj = Path::Name.new("a"); assert_same(obj, obj.taint) 292 | obj = Path::Name.new("a"); assert_same(obj, obj.untaint) 293 | 294 | assert! Path::Name.new("a" ) .tainted? 295 | assert! Path::Name.new("a" ) .to_s.tainted? 296 | 297 | assert Path::Name.new("a" ).taint .tainted? 298 | assert Path::Name.new("a" ).taint.to_s.tainted? 299 | assert Path::Name.new("a".taint) .tainted? 300 | assert Path::Name.new("a".taint) .to_s.tainted? 301 | assert Path::Name.new("a".taint).taint .tainted? 302 | assert Path::Name.new("a".taint).taint.to_s.tainted? 303 | 304 | str = "a" 305 | path = Path::Name.new(str) 306 | str.taint 307 | 308 | assert! path .tainted? 309 | assert! path.to_s.tainted? 310 | 311 | == untaint 312 | 313 | obj = Path::Name.new("a"); assert_same(obj, obj.untaint) 314 | 315 | assert! Path::Name.new("a").taint.untaint .tainted? 316 | assert! Path::Name.new("a").taint.untaint.to_s.tainted? 317 | 318 | str = "a".taint 319 | path = Path::Name.new(str) 320 | str.untaint 321 | 322 | assert path .tainted? 323 | assert path.to_s.tainted? 324 | 325 | == freeze 326 | 327 | obj = Path::Name.new("a"); assert_same(obj, obj.freeze) 328 | 329 | assert! Path::Name.new("a" ) .frozen? 330 | assert! Path::Name.new("a".freeze) .frozen? 331 | 332 | assert Path::Name.new("a" ).freeze .frozen? 333 | assert Path::Name.new("a".freeze).freeze .frozen? 334 | 335 | assert! Path::Name.new("a" ) .to_s.frozen? 336 | assert! Path::Name.new("a".freeze) .to_s.frozen? 337 | assert! Path::Name.new("a" ).freeze.to_s.frozen? 338 | assert! Path::Name.new("a".freeze).freeze.to_s.frozen? 339 | 340 | == to_s 341 | 342 | str = "a" 343 | obj = Path::Name.new(str) 344 | obj.to_s.assert == str 345 | obj.to_s.assert!.equal?(str) 346 | obj.to_a.assert!.equal?(obj.to_s) 347 | 348 | == kernel_open 349 | 350 | count = 0 351 | stat1 = File.stat(__FILE__) 352 | result = Kernel.open(Path::Name.new(__FILE__)) do |f| 353 | stat2 = f.stat 354 | stat2.dev.assert == stat1.dev 355 | stat2.ino.assert == stat1.ino 356 | stat2.size.assert == stat1.size 357 | count += 1 358 | 2 359 | end 360 | count.assert == 1 361 | result.assert == 2 362 | 363 | QED. 364 | -------------------------------------------------------------------------------- /test/test_pathname.rb: -------------------------------------------------------------------------------- 1 | require 'test/unit' 2 | require 'path/name' 3 | 4 | class PathNameTest < Test::Unit::TestCase # :nodoc: 5 | 6 | def test_initialize 7 | p1 = Path::Name.new('a') 8 | assert_equal('a', p1.to_s) 9 | p2 = Path::Name.new(p1) 10 | assert_equal(p1, p2) 11 | end 12 | 13 | class AnotherStringLike # :nodoc: 14 | def initialize(s) @s = s end 15 | def to_str() @s end 16 | def ==(other) @s == other end 17 | end 18 | 19 | def test_equality 20 | obj = Path::Name.new("a") 21 | str = "a" 22 | sym = :a 23 | ano = AnotherStringLike.new("a") 24 | assert_equal(false, obj == str) 25 | assert_equal(false, str == obj) 26 | assert_equal(false, obj == ano) 27 | assert_equal(false, ano == obj) 28 | assert_equal(false, obj == sym) 29 | assert_equal(false, sym == obj) 30 | 31 | obj2 = Path::Name.new("a") 32 | assert_equal(true, obj == obj2) 33 | assert_equal(true, obj === obj2) 34 | assert_equal(true, obj.eql?(obj2)) 35 | end 36 | 37 | def test_hashkey 38 | h = {} 39 | h[Path::Name.new("a")] = 1 40 | h[Path::Name.new("a")] = 2 41 | assert_equal(1, h.size) 42 | end 43 | 44 | def assert_pathname_cmp(e, s1, s2) 45 | p1 = Path::Name.new(s1) 46 | p2 = Path::Name.new(s2) 47 | r = p1 <=> p2 48 | assert(e == r, 49 | "#{p1.inspect} <=> #{p2.inspect}: <#{e}> expected but was <#{r}>") 50 | end 51 | def test_comparison 52 | assert_pathname_cmp( 0, "a", "a") 53 | assert_pathname_cmp( 1, "b", "a") 54 | assert_pathname_cmp(-1, "a", "b") 55 | ss = %w( 56 | a 57 | a/ 58 | a/b 59 | a. 60 | a0 61 | ) 62 | s1 = ss.shift 63 | ss.each {|s2| 64 | assert_pathname_cmp(-1, s1, s2) 65 | s1 = s2 66 | } 67 | end 68 | 69 | def test_comparison_string 70 | assert_equal(nil, Path::Name.new("a") <=> "a") 71 | assert_equal(nil, "a" <=> Path::Name.new("a")) 72 | end 73 | 74 | def test_syntactical 75 | assert_equal(true, Path::Name.new("/").root?) 76 | assert_equal(true, Path::Name.new("//").root?) 77 | assert_equal(true, Path::Name.new("///").root?) 78 | assert_equal(false, Path::Name.new("").root?) 79 | assert_equal(false, Path::Name.new("a").root?) 80 | end 81 | 82 | def test_paths 83 | assert_equal('/', Path::Name.new('/').to_s) 84 | assert_equal('a/', Path::Name.new('a/').to_s) 85 | assert_equal('/a', Path::Name.new('/a').to_s) 86 | assert_equal('.', Path::Name.new('.').to_s) 87 | assert_equal('..', Path::Name.new('..').to_s) 88 | end 89 | 90 | def test_cleanpath 91 | assert_equal('/', Path::Name.new('/').cleanpath(true).to_s) 92 | assert_equal('/', Path::Name.new('//').cleanpath(true).to_s) 93 | assert_equal('', Path::Name.new('').cleanpath(true).to_s) 94 | 95 | assert_equal('.', Path::Name.new('.').cleanpath(true).to_s) 96 | assert_equal('..', Path::Name.new('..').cleanpath(true).to_s) 97 | assert_equal('a', Path::Name.new('a').cleanpath(true).to_s) 98 | assert_equal('/', Path::Name.new('/.').cleanpath(true).to_s) 99 | assert_equal('/', Path::Name.new('/..').cleanpath(true).to_s) 100 | assert_equal('/a', Path::Name.new('/a').cleanpath(true).to_s) 101 | assert_equal('.', Path::Name.new('./').cleanpath(true).to_s) 102 | assert_equal('..', Path::Name.new('../').cleanpath(true).to_s) 103 | assert_equal('a/', Path::Name.new('a/').cleanpath(true).to_s) 104 | 105 | assert_equal('a/b', Path::Name.new('a//b').cleanpath(true).to_s) 106 | assert_equal('a/.', Path::Name.new('a/.').cleanpath(true).to_s) 107 | assert_equal('a/.', Path::Name.new('a/./').cleanpath(true).to_s) 108 | assert_equal('a/..', Path::Name.new('a/../').cleanpath(true).to_s) 109 | assert_equal('/a/.', Path::Name.new('/a/.').cleanpath(true).to_s) 110 | assert_equal('..', Path::Name.new('./..').cleanpath(true).to_s) 111 | assert_equal('..', Path::Name.new('../.').cleanpath(true).to_s) 112 | assert_equal('..', Path::Name.new('./../').cleanpath(true).to_s) 113 | assert_equal('..', Path::Name.new('.././').cleanpath(true).to_s) 114 | assert_equal('/', Path::Name.new('/./..').cleanpath(true).to_s) 115 | assert_equal('/', Path::Name.new('/../.').cleanpath(true).to_s) 116 | assert_equal('/', Path::Name.new('/./../').cleanpath(true).to_s) 117 | assert_equal('/', Path::Name.new('/.././').cleanpath(true).to_s) 118 | 119 | assert_equal('a/b/c', Path::Name.new('a/b/c').cleanpath(true).to_s) 120 | assert_equal('b/c', Path::Name.new('./b/c').cleanpath(true).to_s) 121 | assert_equal('a/c', Path::Name.new('a/./c').cleanpath(true).to_s) 122 | assert_equal('a/b/.', Path::Name.new('a/b/.').cleanpath(true).to_s) 123 | assert_equal('a/..', Path::Name.new('a/../.').cleanpath(true).to_s) 124 | 125 | assert_equal('/a', Path::Name.new('/../.././../a').cleanpath(true).to_s) 126 | assert_equal('a/b/../../../../c/../d', 127 | Path::Name.new('a/b/../../../../c/../d').cleanpath(true).to_s) 128 | end 129 | 130 | def test_cleanpath_no_symlink 131 | assert_equal('/', Path::Name.new('/').cleanpath.to_s) 132 | assert_equal('/', Path::Name.new('//').cleanpath.to_s) 133 | assert_equal('', Path::Name.new('').cleanpath.to_s) 134 | 135 | assert_equal('.', Path::Name.new('.').cleanpath.to_s) 136 | assert_equal('..', Path::Name.new('..').cleanpath.to_s) 137 | assert_equal('a', Path::Name.new('a').cleanpath.to_s) 138 | assert_equal('/', Path::Name.new('/.').cleanpath.to_s) 139 | assert_equal('/', Path::Name.new('/..').cleanpath.to_s) 140 | assert_equal('/a', Path::Name.new('/a').cleanpath.to_s) 141 | assert_equal('.', Path::Name.new('./').cleanpath.to_s) 142 | assert_equal('..', Path::Name.new('../').cleanpath.to_s) 143 | assert_equal('a', Path::Name.new('a/').cleanpath.to_s) 144 | 145 | assert_equal('a/b', Path::Name.new('a//b').cleanpath.to_s) 146 | assert_equal('a', Path::Name.new('a/.').cleanpath.to_s) 147 | assert_equal('a', Path::Name.new('a/./').cleanpath.to_s) 148 | assert_equal('.', Path::Name.new('a/../').cleanpath.to_s) 149 | assert_equal('/a', Path::Name.new('/a/.').cleanpath.to_s) 150 | assert_equal('..', Path::Name.new('./..').cleanpath.to_s) 151 | assert_equal('..', Path::Name.new('../.').cleanpath.to_s) 152 | assert_equal('..', Path::Name.new('./../').cleanpath.to_s) 153 | assert_equal('..', Path::Name.new('.././').cleanpath.to_s) 154 | assert_equal('/', Path::Name.new('/./..').cleanpath.to_s) 155 | assert_equal('/', Path::Name.new('/../.').cleanpath.to_s) 156 | assert_equal('/', Path::Name.new('/./../').cleanpath.to_s) 157 | assert_equal('/', Path::Name.new('/.././').cleanpath.to_s) 158 | 159 | assert_equal('a/b/c', Path::Name.new('a/b/c').cleanpath.to_s) 160 | assert_equal('b/c', Path::Name.new('./b/c').cleanpath.to_s) 161 | assert_equal('a/c', Path::Name.new('a/./c').cleanpath.to_s) 162 | assert_equal('a/b', Path::Name.new('a/b/.').cleanpath.to_s) 163 | assert_equal('.', Path::Name.new('a/../.').cleanpath.to_s) 164 | 165 | assert_equal('/a', Path::Name.new('/../.././../a').cleanpath.to_s) 166 | assert_equal('../../d', Path::Name.new('a/b/../../../../c/../d').cleanpath.to_s) 167 | end 168 | 169 | def test_destructive_update 170 | path = Path::Name.new("a") 171 | path.to_s.replace "b" 172 | assert_equal(Path::Name.new("a"), path) 173 | end 174 | 175 | #def test_null_character 176 | # assert_raise(ArgumentError) { Path::Name.new("\0") } 177 | #end 178 | 179 | def assert_relpath(result, dest, base) 180 | assert_equal(Path::Name.new(result), 181 | Path::Name.new(dest).relative_path_from(Path::Name.new(base))) 182 | end 183 | 184 | def assert_relpath_err(dest, base) 185 | assert_raise(ArgumentError) { 186 | Path::Name.new(dest).relative_path_from(Path::Name.new(base)) 187 | } 188 | end 189 | 190 | def test_relative_path_from 191 | assert_relpath("../a", "a", "b") 192 | assert_relpath("../a", "a", "b/") 193 | assert_relpath("../a", "a/", "b") 194 | assert_relpath("../a", "a/", "b/") 195 | assert_relpath("../a", "/a", "/b") 196 | assert_relpath("../a", "/a", "/b/") 197 | assert_relpath("../a", "/a/", "/b") 198 | assert_relpath("../a", "/a/", "/b/") 199 | 200 | assert_relpath("../b", "a/b", "a/c") 201 | assert_relpath("../a", "../a", "../b") 202 | 203 | assert_relpath("a", "a", ".") 204 | assert_relpath("..", ".", "a") 205 | 206 | assert_relpath(".", ".", ".") 207 | assert_relpath(".", "..", "..") 208 | assert_relpath("..", "..", ".") 209 | 210 | assert_relpath("c/d", "/a/b/c/d", "/a/b") 211 | assert_relpath("../..", "/a/b", "/a/b/c/d") 212 | assert_relpath("../../../../e", "/e", "/a/b/c/d") 213 | assert_relpath("../b/c", "a/b/c", "a/d") 214 | 215 | assert_relpath("../a", "/../a", "/b") 216 | assert_relpath("../../a", "../a", "b") 217 | assert_relpath(".", "/a/../../b", "/b") 218 | assert_relpath("..", "a/..", "a") 219 | assert_relpath(".", "a/../b", "b") 220 | 221 | assert_relpath("a", "a", "b/..") 222 | assert_relpath("b/c", "b/c", "b/..") 223 | 224 | assert_relpath_err("/", ".") 225 | assert_relpath_err(".", "/") 226 | assert_relpath_err("a", "..") 227 | assert_relpath_err(".", "..") 228 | end 229 | 230 | def assert_pathname_plus(a, b, c) 231 | a = Path::Name.new(a) 232 | b = Path::Name.new(b) 233 | c = Path::Name.new(c) 234 | d = b + c 235 | assert(a == d, 236 | "#{b.inspect} + #{c.inspect}: #{a.inspect} expected but was #{d.inspect}") 237 | end 238 | 239 | def test_plus 240 | assert_pathname_plus('a/b', 'a', 'b') 241 | assert_pathname_plus('a', 'a', '.') 242 | assert_pathname_plus('b', '.', 'b') 243 | assert_pathname_plus('.', '.', '.') 244 | 245 | assert_pathname_plus('/', '/', '..') 246 | assert_pathname_plus('.', 'a', '..') 247 | assert_pathname_plus('a', 'a/b', '..') 248 | assert_pathname_plus('../..', '..', '..') 249 | assert_pathname_plus('/c', '/', '../c') 250 | assert_pathname_plus('c', 'a', '../c') 251 | assert_pathname_plus('a/c', 'a/b', '../c') 252 | assert_pathname_plus('../../c', '..', '../c') 253 | 254 | # TODO: Is this really what we want? 255 | assert_pathname_plus('/b', 'a', '/b') 256 | end 257 | 258 | def test_taint 259 | obj = Path::Name.new("a"); assert_same(obj, obj.taint) 260 | obj = Path::Name.new("a"); assert_same(obj, obj.untaint) 261 | 262 | assert_equal(false, Path::Name.new("a" ) .tainted?) 263 | assert_equal(false, Path::Name.new("a" ) .to_s.tainted?) 264 | assert_equal(true, Path::Name.new("a" ).taint .tainted?) 265 | assert_equal(true, Path::Name.new("a" ).taint.to_s.tainted?) 266 | assert_equal(true, Path::Name.new("a".taint) .tainted?) 267 | assert_equal(true, Path::Name.new("a".taint) .to_s.tainted?) 268 | assert_equal(true, Path::Name.new("a".taint).taint .tainted?) 269 | assert_equal(true, Path::Name.new("a".taint).taint.to_s.tainted?) 270 | 271 | str = "a" 272 | path = Path::Name.new(str) 273 | str.taint 274 | assert_equal(false, path .tainted?) 275 | assert_equal(false, path.to_s.tainted?) 276 | end 277 | 278 | def test_untaint 279 | obj = Path::Name.new("a"); assert_same(obj, obj.untaint) 280 | 281 | assert_equal(false, Path::Name.new("a").taint.untaint .tainted?) 282 | assert_equal(false, Path::Name.new("a").taint.untaint.to_s.tainted?) 283 | 284 | str = "a".taint 285 | path = Path::Name.new(str) 286 | str.untaint 287 | assert_equal(true, path .tainted?) 288 | assert_equal(true, path.to_s.tainted?) 289 | end 290 | 291 | def test_freeze 292 | obj = Path::Name.new("a"); assert_same(obj, obj.freeze) 293 | 294 | assert_equal(false, Path::Name.new("a" ) .frozen?) 295 | assert_equal(false, Path::Name.new("a".freeze) .frozen?) 296 | assert_equal(true, Path::Name.new("a" ).freeze .frozen?) 297 | assert_equal(true, Path::Name.new("a".freeze).freeze .frozen?) 298 | assert_equal(false, Path::Name.new("a" ) .to_s.frozen?) 299 | assert_equal(false, Path::Name.new("a".freeze) .to_s.frozen?) 300 | assert_equal(false, Path::Name.new("a" ).freeze.to_s.frozen?) 301 | assert_equal(false, Path::Name.new("a".freeze).freeze.to_s.frozen?) 302 | end 303 | 304 | def test_to_s 305 | str = "a" 306 | obj = Path::Name.new(str) 307 | assert_equal(str, obj.to_s) 308 | assert_not_same(str, obj.to_s) 309 | assert_not_same(obj.to_s, obj.to_s) 310 | end 311 | 312 | def test_kernel_open 313 | count = 0 314 | stat1 = File.stat(__FILE__) 315 | result = Kernel.open(Path::Name.new(__FILE__)) {|f| 316 | stat2 = f.stat 317 | assert_equal(stat1.dev, stat2.dev) 318 | assert_equal(stat1.ino, stat2.ino) 319 | assert_equal(stat1.size, stat2.size) 320 | count += 1 321 | 2 322 | } 323 | assert_equal(1, count) 324 | assert_equal(2, result) 325 | end 326 | 327 | end 328 | 329 | -------------------------------------------------------------------------------- /work/consider/filelist.rb: -------------------------------------------------------------------------------- 1 | # = FileList 2 | # 3 | # A FileList is essentially an array with helper methods 4 | # to make file manipulation easier. 5 | # 6 | # FileLists are lazy. When given a list of glob patterns for 7 | # possible files to be included in the file list, instead of 8 | # searching the file structures to find the files, a FileList holds 9 | # the pattern for latter use. 10 | # 11 | # This allows us to define a number of FileList to match any number of 12 | # files, but only search out the actual files when then FileList 13 | # itself is actually used. The key is that the first time an 14 | # element of the FileList/Array is requested, the pending patterns 15 | # are resolved into a real list of file names. 16 | # 17 | # fl = FileList.new 18 | # fl.include('./**/*') 19 | # fl.exclude('./*~') 20 | # 21 | # == History 22 | # 23 | # FileList was ported from Jim Weirich's Rake. 24 | # 25 | # == Authors 26 | # 27 | # * Jim Weirich 28 | # 29 | # == Todo 30 | # 31 | # * Should the exclusions really be the default? 32 | # Maybe have #exclude_typical instead. 33 | # 34 | # == Copying 35 | # 36 | # Copyright (C) 2002 Jim Weirich 37 | # 38 | # General Public License (GPL) 39 | # 40 | # Permission is hereby granted, free of charge, to any person obtaining 41 | # a copy of this software and associated documentation files (the 42 | # "Software"), to deal in the Software without restriction, including 43 | # without limitation the rights to use, copy, modify, merge, publish, 44 | # distribute, sublicense, and/or sell copies of the Software, and to 45 | # permit persons to whom the Software is furnished to do so, subject to 46 | # the following conditions: 47 | # 48 | # The above copyright notice and this permission notice shall be 49 | # included in all copies or substantial portions of the Software. 50 | # 51 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 52 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 53 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 54 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 55 | # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 56 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 57 | # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 58 | 59 | # = FileList 60 | # 61 | # A FileList is essentially an array with helper methods 62 | # to make file manipulation easier. 63 | # 64 | # FileLists are lazy. When given a list of glob patterns for 65 | # possible files to be included in the file list, instead of 66 | # searching the file structures to find the files, a FileList holds 67 | # the pattern for latter use. 68 | # 69 | # This allows us to define a number of FileList to match any number of 70 | # files, but only search out the actual files when then FileList 71 | # itself is actually used. The key is that the first time an 72 | # element of the FileList/Array is requested, the pending patterns 73 | # are resolved into a real list of file names. 74 | # 75 | # fl = FileList.new 76 | # fl.include('./**/*') 77 | # fl.exclude('./*~') 78 | # 79 | class FileList 80 | 81 | # TODO: Add glob options. 82 | #attr :glob_options 83 | 84 | #include Cloneable 85 | def clone 86 | sibling = self.class.new 87 | instance_variables.each do |ivar| 88 | value = self.instance_variable_get(ivar) 89 | sibling.instance_variable_set(ivar, value.rake_dup) 90 | end 91 | sibling 92 | end 93 | alias_method :dup, :clone 94 | 95 | # == Method Delegation 96 | # 97 | # The lazy evaluation magic of FileLists happens by implementing 98 | # all the array specific methods to call +resolve+ before 99 | # delegating the heavy lifting to an embedded array object 100 | # (@items). 101 | # 102 | # In addition, there are two kinds of delegation calls. The 103 | # regular kind delegates to the @items array and returns the 104 | # result directly. Well, almost directly. It checks if the 105 | # returned value is the @items object itself, and if so will 106 | # return the FileList object instead. 107 | # 108 | # The second kind of delegation call is used in methods that 109 | # normally return a new Array object. We want to capture the 110 | # return value of these methods and wrap them in a new FileList 111 | # object. We enumerate these methods in the +SPECIAL_RETURN+ list 112 | # below. 113 | 114 | # List of array methods (that are not in +Object+) that need to be 115 | # delegated. 116 | ARRAY_METHODS = (Array.instance_methods - Object.instance_methods).map(&:to_s) 117 | 118 | # List of additional methods that must be delegated. 119 | MUST_DEFINE = %w[to_a inspect].map(&:to_s) 120 | 121 | # List of methods that should not be delegated here (we define 122 | # special versions of them explicitly below). 123 | MUST_NOT_DEFINE = %w[to_a to_ary partition *].map(&:to_s) 124 | 125 | # List of delegated methods that return new array values which 126 | # need wrapping. 127 | SPECIAL_RETURN = %w[ 128 | map collect sort sort_by select find_all reject grep 129 | compact flatten uniq values_at 130 | + - & | 131 | ].map(&:to_s) 132 | 133 | DELEGATING_METHODS = (ARRAY_METHODS + MUST_DEFINE - MUST_NOT_DEFINE).sort.uniq 134 | 135 | # Now do the delegation. 136 | DELEGATING_METHODS.each_with_index do |sym, i| 137 | if SPECIAL_RETURN.include?(sym) 138 | ln = __LINE__+1 139 | class_eval %{ 140 | def #{sym}(*args, &block) 141 | resolve if @pending 142 | result = @items.send(:#{sym}, *args, &block) 143 | FileList.new.import(result) 144 | end 145 | }, __FILE__, ln 146 | else 147 | ln = __LINE__+1 148 | class_eval %{ 149 | def #{sym}(*args, &block) 150 | resolve if @pending 151 | result = @items.send(:#{sym}, *args, &block) 152 | result.object_id == @items.object_id ? self : result 153 | end 154 | }, __FILE__, ln 155 | end 156 | end 157 | 158 | # Create a file list from the globbable patterns given. If you 159 | # wish to perform multiple includes or excludes at object build 160 | # time, use the "yield self" pattern. 161 | # 162 | # Example: 163 | # file_list = FileList.new['lib/**/*.rb', 'test/test*.rb'] 164 | # 165 | # pkg_files = FileList.new['lib/**/*'] do |fl| 166 | # fl.exclude(/\bCVS\b/) 167 | # end 168 | # 169 | def initialize(*patterns) 170 | @pending_add = [] 171 | @pending = false 172 | @exclude_patterns = DEFAULT_IGNORE_PATTERNS.dup 173 | @exclude_re = nil 174 | @items = [] 175 | patterns.each { |pattern| include(pattern) } 176 | yield self if block_given? 177 | end 178 | 179 | # Add file names defined by glob patterns to the file list. If an 180 | # array is given, add each element of the array. 181 | # 182 | # Example: 183 | # file_list.include("*.java", "*.cfg") 184 | # file_list.include %w( math.c lib.h *.o ) 185 | # 186 | def include(*filenames) 187 | # TODO: check for pending 188 | filenames.each do |fn| 189 | if fn.respond_to? :to_ary 190 | include(*fn.to_ary) 191 | else 192 | @pending_add << fn 193 | end 194 | end 195 | @pending = true 196 | self 197 | end 198 | alias :add :include 199 | 200 | # Register a list of file name patterns that should be excluded 201 | # from the list. Patterns may be regular expressions, glob 202 | # patterns or regular strings. 203 | # 204 | # Note that glob patterns are expanded against the file system. 205 | # If a file is explicitly added to a file list, but does not exist 206 | # in the file system, then an glob pattern in the exclude list 207 | # will not exclude the file. 208 | # 209 | # Examples: 210 | # FileList['a.c', 'b.c'].exclude("a.c") => ['b.c'] 211 | # FileList['a.c', 'b.c'].exclude(/^a/) => ['b.c'] 212 | # 213 | # If "a.c" is a file, then ... 214 | # FileList['a.c', 'b.c'].exclude("a.*") => ['b.c'] 215 | # 216 | # If "a.c" is not a file, then ... 217 | # FileList['a.c', 'b.c'].exclude("a.*") => ['a.c', 'b.c'] 218 | # 219 | def exclude(*patterns) 220 | patterns.each do |pat| @exclude_patterns << pat end 221 | if ! @pending 222 | calculate_exclude_regexp 223 | reject! { |fn| fn =~ @exclude_re } 224 | end 225 | self 226 | end 227 | 228 | # Clear all the exclude patterns so that we exclude nothing. 229 | def clear_exclude 230 | @exclude_patterns = [] 231 | calculate_exclude_regexp if ! @pending 232 | end 233 | 234 | # Define equality. 235 | def ==(array) 236 | to_ary == array 237 | end 238 | 239 | # Return the internal array object. 240 | def to_a 241 | resolve 242 | @items 243 | end 244 | 245 | # Return the internal array object. 246 | def to_ary 247 | resolve 248 | @items 249 | end 250 | 251 | # Redefine * to return either a string or a new file list. 252 | def *(other) 253 | result = @items * other 254 | case result 255 | when Array 256 | FileList.new.import(result) 257 | else 258 | result 259 | end 260 | end 261 | 262 | # Resolve all the pending adds now. 263 | def resolve 264 | if @pending 265 | @pending = false 266 | @pending_add.each do |fn| resolve_add(fn) end 267 | @pending_add = [] 268 | resolve_exclude 269 | end 270 | self 271 | end 272 | 273 | def calculate_exclude_regexp 274 | ignores = [] 275 | @exclude_patterns.each do |pat| 276 | case pat 277 | when Regexp 278 | ignores << pat 279 | when /[*.]/ 280 | Dir[pat].each do |p| ignores << p end 281 | else 282 | ignores << Regexp.quote(pat) 283 | end 284 | end 285 | if ignores.empty? 286 | @exclude_re = /^$/ 287 | else 288 | re_str = ignores.collect { |p| "(" + p.to_s + ")" }.join("|") 289 | @exclude_re = Regexp.new(re_str) 290 | end 291 | end 292 | 293 | def resolve_add(fn) 294 | case fn 295 | when Array 296 | fn.each { |f| self.resolve_add(f) } 297 | when %r{[*?]} 298 | add_matching(fn) 299 | else 300 | self << fn 301 | end 302 | end 303 | 304 | def resolve_exclude 305 | @exclude_patterns.each do |pat| 306 | case pat 307 | when Regexp 308 | reject! { |fn| fn =~ pat } 309 | when /[*.]/ 310 | reject_list = Dir[pat] 311 | reject! { |fn| reject_list.include?(fn) } 312 | else 313 | reject! { |fn| fn == pat } 314 | end 315 | end 316 | self 317 | end 318 | 319 | # Return a new FileList with the results of running +sub+ against 320 | # each element of the oringal list. 321 | # 322 | # Example: 323 | # FileList['a.c', 'b.c'].sub(/\.c$/, '.o') => ['a.o', 'b.o'] 324 | # 325 | def sub(pat, rep) 326 | inject(FileList.new) { |res, fn| res << fn.sub(pat,rep) } 327 | end 328 | 329 | # Return a new FileList with the results of running +gsub+ against 330 | # each element of the original list. 331 | # 332 | # Example: 333 | # FileList['lib/test/file', 'x/y'].gsub(/\//, "\\") 334 | # => ['lib\\test\\file', 'x\\y'] 335 | # 336 | def gsub(pat, rep) 337 | inject(FileList.new) { |res, fn| res << fn.gsub(pat,rep) } 338 | end 339 | 340 | # Same as +sub+ except that the oringal file list is modified. 341 | def sub!(pat, rep) 342 | each_with_index { |fn, i| self[i] = fn.sub(pat,rep) } 343 | self 344 | end 345 | 346 | # Same as +gsub+ except that the original file list is modified. 347 | def gsub!(pat, rep) 348 | each_with_index { |fn, i| self[i] = fn.gsub(pat,rep) } 349 | self 350 | end 351 | 352 | # Return a new array with String#ext method applied to 353 | # each member of the array. 354 | # 355 | # This method is a shortcut for: 356 | # 357 | # array.collect { |item| item.ext(newext) } 358 | # 359 | # +ext+ is a user added method for the Array class. 360 | def ext(newext='') 361 | collect { |fn| fn.ext(newext) } 362 | end 363 | 364 | # Grep each of the files in the filelist using the given pattern. 365 | # If a block is given, call the block on each matching line, 366 | # passing the file name, line number, and the matching line of 367 | # text. If no block is given, a standard emac style 368 | # file:linenumber:line message will be printed to standard out. 369 | def egrep(pattern) 370 | each do |fn| 371 | open(fn) do |inf| 372 | count = 0 373 | 374 | inf.each do |line| 375 | count += 1 376 | if pattern.match(line) 377 | if block_given? 378 | yield fn, count, line 379 | else 380 | puts "#{fn}:#{count}:#{line}" 381 | end 382 | end 383 | end 384 | 385 | end 386 | end 387 | end 388 | 389 | # FileList version of partition. Needed because the nested arrays 390 | # should be FileLists in this version. 391 | def partition(&block) # :nodoc: 392 | resolve 393 | result = @items.partition(&block) 394 | [ 395 | FileList.new.import(result[0]), 396 | FileList.new.import(result[1]), 397 | ] 398 | end 399 | 400 | # Convert a FileList to a string by joining all elements with a space. 401 | def to_s 402 | resolve if @pending 403 | self.join(' ') 404 | end 405 | 406 | # Add matching glob patterns. 407 | def add_matching(pattern) 408 | Dir[pattern].each do |fn| 409 | self << fn unless exclude?(fn) 410 | end 411 | end 412 | private :add_matching 413 | 414 | # Should the given file name be excluded? 415 | def exclude?(fn) 416 | calculate_exclude_regexp unless @exclude_re 417 | fn =~ @exclude_re 418 | end 419 | 420 | DEFAULT_IGNORE_PATTERNS = [ 421 | /(^|[\/\\])CVS([\/\\]|$)/, 422 | /(^|[\/\\])\.svn([\/\\]|$)/, 423 | /(^|[\/\\])_darcs([\/\\]|$)/, 424 | /\.bak$/, 425 | /~$/, 426 | /(^|[\/\\])core$/ 427 | ] 428 | @exclude_patterns = DEFAULT_IGNORE_PATTERNS.dup 429 | 430 | def import(array) 431 | @items = array 432 | self 433 | end 434 | 435 | class << self 436 | # Create a new file list including the files listed. Similar to: 437 | # 438 | # FileList.new(*args) 439 | def [](*args) 440 | new(*args) 441 | end 442 | 443 | # Set the ignore patterns back to the default value. The 444 | # default patterns will ignore files 445 | # * containing "CVS" in the file path 446 | # * containing ".svn" in the file path 447 | # * containing "_darcs" in the file path 448 | # * ending with ".bak" 449 | # * ending with "~" 450 | # * named "core" 451 | # 452 | # Note that file names beginning with "." are automatically 453 | # ignored by Ruby's glob patterns and are not specifically 454 | # listed in the ignore patterns. 455 | def select_default_ignore_patterns 456 | @exclude_patterns = DEFAULT_IGNORE_PATTERNS.dup 457 | end 458 | 459 | # Clear the ignore patterns. 460 | def clear_ignore_patterns 461 | @exclude_patterns = [ /^$/ ] 462 | end 463 | end 464 | 465 | end # FileList 466 | 467 | 468 | 469 | # = TEST 470 | # 471 | # TODO This test needs a mock File class. 472 | 473 | # FileList can't be tested without a FIXTURE setup. 474 | # So don't bother running it directly from here, but 475 | # transfer it over to the test directory. 476 | # Need a better solution, is there a way to fake the 477 | # filesystem? 478 | 479 | =begin #no test 480 | 481 | require 'test/unit' 482 | 483 | class TC_FileList < Test::Unit::TestCase 484 | 485 | def test_filelist 486 | Dir.chdir File.join( $TESTDIR, 'filelist' ) do 487 | fl = FileList.new 488 | fl.include('*') 489 | assert_equal( ['testfile2.txt', 'testfile.txt'], fl.to_a ) 490 | fl.exclude('*2.txt') 491 | assert_equal( ['testfile.txt'], fl.to_a ) 492 | end 493 | end 494 | 495 | end 496 | 497 | =end 498 | -------------------------------------------------------------------------------- /lib/path/shell.rb: -------------------------------------------------------------------------------- 1 | require 'thread' 2 | require 'path/filetest' 3 | require 'path/fileutils' 4 | #require 'ratch/core_ext' 5 | #require 'ratch/ziputils' 6 | #require 'ratch/xdg' 7 | 8 | module Path 9 | 10 | # = Shell Prompt class 11 | # 12 | # Ratch Shell object provides a limited file system shell in code. 13 | # It is similar to having a shell prompt available to you in Ruby. 14 | # 15 | class Shell 16 | 17 | # require shell commands 18 | Dir[File.dirname(__FILE__) + '/shell/*.rb'].each do |file| 19 | require(file) #instance_eval(File.read(file)) 20 | end 21 | 22 | # New Shell object. 23 | def initialize(*path_opts) 24 | opts = (Hash===path_opts.last ? path_opts.pop : {}) 25 | path = path_opts 26 | 27 | @quiet = opts[:quiet] 28 | @noop = opts[:noop] 29 | @verbose = opts[:verbose] 30 | @debug = opts[:debug] 31 | 32 | if path.empty? 33 | path = Dir.pwd 34 | else 35 | path = File.join(*path) 36 | end 37 | 38 | raise FileNotFound, "#{path}" unless ::File.exist?(path) 39 | raise FileNotFound, "#{path}" unless ::File.directory?(path) 40 | 41 | @work = dir(path) 42 | end 43 | 44 | # 45 | def mutex 46 | @mutex ||= Mutex.new 47 | end 48 | 49 | # Opertaton mode. This can be :noop, :verbose or :dryrun. 50 | # The later is the same as the first two combined. 51 | #def mode(opts=nil) 52 | # return @mode unless opts 53 | # opts.each do |key, val| 54 | # next unless val 55 | # case key 56 | # when :noop 57 | # @mode = (@mode == :verbose ? :dryrun : :noop) 58 | # when :verbose 59 | # @mode = (@mode == :noop ? :dryrun : :verbose) 60 | # when :dryrun 61 | # @mode = :dryrun 62 | # end 63 | # end 64 | #end 65 | 66 | def quiet? ; @quiet ; end 67 | def debug? ; @debug ; end 68 | 69 | def noop? ; @noop ; end 70 | def verbose? ; @verbose ; end 71 | 72 | def dryrun? ; @noop && @verbose ; end 73 | def trace? ; @debug && @verbose ; end 74 | 75 | # String representation is work directory path. 76 | def to_s ; work.to_s ; end 77 | 78 | # 79 | def ==(other) 80 | return false unless self.class===other 81 | return true if @work == other.work 82 | false 83 | end 84 | 85 | # Present working directory. 86 | #attr :work 87 | 88 | # TODO: Should these take the *args? 89 | 90 | # Root location. 91 | def root(*args) 92 | dir('/', *args) 93 | end 94 | 95 | # Current home path. 96 | def home(*args) 97 | dir('~', *args) 98 | end 99 | 100 | # Current working path. 101 | def work(*args) 102 | return @work if args.empty? 103 | return dir(@work, *args) 104 | end 105 | 106 | # Alias for #work. 107 | alias_method :pwd, :work 108 | 109 | # Return a new prompt with the same location. 110 | # NOTE: Use #dup or #clone ? 111 | #def new ; Shell.new(work) ; end 112 | 113 | def parent 114 | dir('..') 115 | end 116 | 117 | # Get the FileObject for the given file name. 118 | # 119 | def [](name) 120 | #FileObject[localize(name)] 121 | #Pathname.new(localize(path)) 122 | Pathlist.new(localize(path)) 123 | end 124 | 125 | # should #file and this be the same? 126 | def file(path) 127 | #FileObject[name] 128 | raise unless File.file?(path) 129 | Pathname.new(localize(path)) 130 | end 131 | 132 | #def doc(name) 133 | # Document.new(name) 134 | #end 135 | 136 | def dir(path) 137 | #Directory.new(name) 138 | raise unless File.directory?(path) 139 | Pathname.new(localize(path)) 140 | end 141 | 142 | def path(path) 143 | Pathname.new(localize(path)) 144 | end 145 | alias_method :pathname, :path 146 | 147 | # Lists all entries. 148 | def entries 149 | work.entries 150 | end 151 | alias_method :ls, :entries 152 | 153 | # Lists directory entries. 154 | def directory_entries 155 | work.entries.select{ |d| d.directory? } 156 | end 157 | 158 | # Lists directory entries. 159 | def file_entries 160 | work.entries.select{ |f| f.file? } 161 | end 162 | 163 | # Returns list of files objects. 164 | #def files ; work.files ; end 165 | 166 | # Returns list of directories. 167 | #def documents ; work.documents ; end 168 | 169 | # Returns list of documents. 170 | def directories ; work.directories ; end 171 | 172 | # Glob pattern. Returns matches as strings. 173 | def glob(*patterns, &block) 174 | opts = (::Integer===patterns.last ? patterns.pop : 0) 175 | matches = [] 176 | locally do 177 | matches = patterns.map{ |pattern| ::Dir.glob(pattern, opts) }.flatten 178 | end 179 | if block_given? 180 | matches.each(&block) 181 | else 182 | matches 183 | end 184 | end 185 | 186 | # TODO: Ultimately merge #glob and #multiglob. 187 | def multiglob(*a) 188 | Dir.multiglob(*a) 189 | end 190 | 191 | def multiglob_r(*a) 192 | Dir.multiglob_r(*a) 193 | end 194 | 195 | # Match pattern. Like #glob but returns file objects. 196 | def match(*patterns, &block) 197 | opts = (::Integer===patterns.last ? patterns.pop : 0) 198 | patterns = localize(patterns) 199 | matches = patterns.map{ |pattern| ::Dir.glob(pattern, opts) }.flatten 200 | matches = matches.map{ |f| FileObject[f] } 201 | if block_given? 202 | matches.each(&block) 203 | else 204 | matches 205 | end 206 | end 207 | 208 | # Join paths. 209 | # TODO: Should this return a new directory object? Or should it change directories? 210 | def /(path) 211 | #@work += dir # did not work, why? 212 | @work = dir(localize(path)) 213 | self 214 | end 215 | 216 | # Alias for #/. 217 | alias_method '+', '/' 218 | 219 | # 220 | def system(cmd) 221 | locally do 222 | super(cmd) 223 | end 224 | end 225 | 226 | # Shell runner. 227 | def sh(cmd) 228 | #puts "--> system call: #{cmd}" if verbose? 229 | puts cmd if verbose? 230 | return true if noop? 231 | #locally do 232 | if quiet? 233 | silently{ system(cmd) } 234 | else 235 | system(cmd) 236 | end 237 | #end 238 | end 239 | 240 | # Shell runner. 241 | #def sh(cmd) 242 | # if dryrun? 243 | # puts cmd 244 | # true 245 | # else 246 | # puts "--> system call: #{cmd}" if trace? 247 | # if quiet? 248 | # silently{ system(cmd) } 249 | # else 250 | # system(cmd) 251 | # end 252 | # end 253 | #end 254 | 255 | # Change working directory. 256 | # 257 | # TODO: Make thread safe. 258 | # 259 | def cd(path, &block) 260 | if block 261 | work_old = @work 262 | begin 263 | @work = dir(localize(path)) 264 | locally(&block) 265 | #mutex.synchronize do 266 | # Dir.chdir(@work){ block.call } 267 | #end 268 | ensure 269 | @work = work_old 270 | end 271 | else 272 | @work = dir(localize(path)) 273 | end 274 | end 275 | 276 | alias_method :chdir, :cd 277 | 278 | # -- File IO Shortcuts ----------------------------------------------- 279 | 280 | # Read file. 281 | def read(path) 282 | File.read(localize(path)) 283 | end 284 | 285 | # Write file. 286 | def write(path, text) 287 | puts "write #{path}" if verbose? 288 | File.open(localize(path), 'w'){ |f| f << text } unless noop? 289 | end 290 | 291 | # Append to file. 292 | def append(path, text) 293 | puts "append #{path}" if verbose? 294 | File.open(localize(path), 'a'){ |f| f << text } unless noop? 295 | end 296 | 297 | 298 | ############# 299 | # FileTest # 300 | ############# 301 | 302 | # 303 | def size(path) ; FileTest.size(localize(path)) ; end 304 | def size?(path) ; FileTest.size?(localize(path)) ; end 305 | def directory?(path) ; FileTest.directory?(localize(path)) ; end 306 | def symlink?(path) ; FileTest.symlink?(localize(path)) ; end 307 | def readable?(path) ; FileTest.readable?(localize(path)) ; end 308 | def chardev?(path) ; FileTest.chardev?(localize(path)) ; end 309 | def exist?(path) ; FileTest.exist?(localize(path)) ; end 310 | def exists?(path) ; FileTest.exists?(localize(path)) ; end 311 | def zero?(path) ; FileTest.zero?(localize(path)) ; end 312 | def pipe?(path) ; FileTest.pipe?(localize(path)) ; end 313 | def file?(path) ; FileTest.file?(localize(path)) ; end 314 | def sticky?(path) ; FileTest.sticky?(localize(path)) ; end 315 | def blockdev?(path) ; FileTest.blockdev?(localize(path)) ; end 316 | def grpowned?(path) ; FileTest.grpowned?(localize(path)) ; end 317 | def setgid?(path) ; FileTest.setgid?(localize(path)) ; end 318 | def setuid?(path) ; FileTest.setuid?(localize(path)) ; end 319 | def socket?(path) ; FileTest.socket?(localize(path)) ; end 320 | def owned?(path) ; FileTest.owned?(localize(path)) ; end 321 | def writable?(path) ; FileTest.writable?(localize(path)) ; end 322 | def executable?(path) ; FileTest.executable?(localize(path)) ; end 323 | 324 | def safe?(path) ; FileTest.safe?(localize(path)) ; end 325 | 326 | def relative?(path) ; FileTest.relative?(path) ; end 327 | def absolute?(path) ; FileTest.absolute?(path) ; end 328 | 329 | def writable_real?(path) ; FileTest.writable_real?(localize(path)) ; end 330 | def executable_real?(path) ; FileTest.executable_real?(localize(path)) ; end 331 | def readable_real?(path) ; FileTest.readable_real?(localize(path)) ; end 332 | 333 | def identical?(path, other) 334 | FileTest.identical?(localize(path), localize(other)) 335 | end 336 | alias_method :compare_file, :identical? 337 | 338 | # Assert that a path exists. 339 | #def exists?(path) 340 | # paths = Dir.glob(path) 341 | # paths.not_empty? 342 | #end 343 | #alias_method :exist?, :exists? #; module_function :exist? 344 | #alias_method :path?, :exists? #; module_function :path? 345 | 346 | # Is a given path a regular file? If +path+ is a glob 347 | # then checks to see if all matches are regular files. 348 | #def file?(path) 349 | # paths = Dir.glob(path) 350 | # paths.not_empty? && paths.all?{ |f| FileTest.file?(f) } 351 | #end 352 | 353 | # Is a given path a directory? If +path+ is a glob 354 | # checks to see if all matches are directories. 355 | #def dir?(path) 356 | # paths = Dir.glob(path) 357 | # paths.not_empty? && paths.all?{ |f| FileTest.directory?(f) } 358 | #end 359 | #alias_method :directory?, :dir? #; module_function :directory? 360 | 361 | 362 | ############# 363 | # FileUtils # 364 | ############# 365 | 366 | # Low-level Methods Omitted 367 | # ------------------------- 368 | # getwd -> pwd 369 | # compare_file -> cmp 370 | # remove_file -> rm 371 | # copy_file -> cp 372 | # remove_dir -> rmdir 373 | # safe_unlink -> rm_f 374 | # makedirs -> mkdir_p 375 | # rmtree -> rm_rf 376 | # copy_stream 377 | # remove_entry 378 | # copy_entry 379 | # remove_entry_secure 380 | # compare_stream 381 | 382 | # Present working directory. 383 | def pwd 384 | work.to_s 385 | end 386 | 387 | # Same as #identical? 388 | def cmp(a,b) 389 | fileutils.compare_file(a,b) 390 | end 391 | 392 | # 393 | def mkdir(dir, options={}) 394 | dir = localize(dir) 395 | fileutils.mkdir(dir, options) 396 | end 397 | 398 | def mkdir_p(dir, options={}) 399 | dir = localize(dir) 400 | unless File.directory?(dir) 401 | fileutils.mkdir_p(dir, options) 402 | end 403 | end 404 | alias_method :mkpath, :mkdir_p 405 | 406 | def rmdir(dir, options={}) 407 | dir = localize(dir) 408 | fileutils.rmdir(dir, options) 409 | end 410 | 411 | # ln(list, destdir, options={}) 412 | def ln(old, new, options={}) 413 | old = localize(old) 414 | new = localize(new) 415 | fileutils.ln(old, new, options) 416 | end 417 | alias_method :link, :ln 418 | 419 | # ln_s(list, destdir, options={}) 420 | def ln_s(old, new, options={}) 421 | old = localize(old) 422 | new = localize(new) 423 | fileutils.ln_s(old, new, options) 424 | end 425 | alias_method :symlink, :ln_s 426 | 427 | def ln_sf(old, new, options={}) 428 | old = localize(old) 429 | new = localize(new) 430 | fileutils.ln_sf(old, new, options) 431 | end 432 | 433 | # cp(list, dir, options={}) 434 | def cp(src, dest, options={}) 435 | src = localize(src) 436 | dest = localize(dest) 437 | fileutils.cp(src, dest, options) 438 | end 439 | alias_method :copy, :cp 440 | 441 | # cp_r(list, dir, options={}) 442 | def cp_r(src, dest, options={}) 443 | src = localize(src) 444 | dest = localize(dest) 445 | fileutils.cp_r(src, dest, options) 446 | end 447 | 448 | # mv(list, dir, options={}) 449 | def mv(src, dest, options={}) 450 | src = localize(src) 451 | dest = localize(dest) 452 | fileutils.mv(src, dest, options) 453 | end 454 | alias_method :move, :mv 455 | 456 | def rm(list, options={}) 457 | list = localize(list) 458 | fileutils.rm(list, options) 459 | end 460 | alias_method :remove, :rm 461 | 462 | def rm_r(list, options={}) 463 | list = localize(list) 464 | fileutils.rm_r(list, options) 465 | end 466 | 467 | def rm_f(list, options={}) 468 | list = localize(list) 469 | fileutils.rm_f(list, options) 470 | end 471 | 472 | def rm_rf(list, options={}) 473 | list = localize(list) 474 | fileutils.rm_rf(list, options) 475 | end 476 | 477 | def install(src, dest, mode, options={}) 478 | src = localize(src) 479 | dest = localize(dest) 480 | fileutils.install(src, dest, mode, options) 481 | end 482 | 483 | def chmod(mode, list, options={}) 484 | list = localize(list) 485 | fileutils.chmod(mode, list, options) 486 | end 487 | 488 | def chmod_r(mode, list, options={}) 489 | list = localize(list) 490 | fileutils.chmod_r(mode, list, options) 491 | end 492 | #alias_method :chmod_R, :chmod_r 493 | 494 | def chown(user, group, list, options={}) 495 | list = localize(list) 496 | fileutils.chown(user, group, list, options) 497 | end 498 | 499 | def chown_r(user, group, list, options={}) 500 | list = localize(list) 501 | fileutils.chown_r(user, group, list, options) 502 | end 503 | #alias_method :chown_R, :chown_r 504 | 505 | def touch(list, options={}) 506 | list = localize(list) 507 | fileutils.touch(list, options) 508 | end 509 | 510 | # 511 | # TODO: should this have SOURCE diectory? 512 | # stage(directory, source_dir, files) 513 | # 514 | def stage(stage_dir, files) 515 | #dir = localize(directory) 516 | #files = localize(files) 517 | locally do 518 | fileutils.stage(stage_dir, work, files) 519 | end 520 | end 521 | 522 | # An intergrated glob like method that takes a set of include globs, 523 | # exclude globs and ignore globs to produce a collection of paths. 524 | # 525 | # Ignore_globs differ from exclude_globs in that they match by 526 | # the basename of the path rather than the whole pathname. 527 | # 528 | def amass(include_globs, exclude_globs=[], ignore_globs=[]) 529 | locally do 530 | fileutils.amass(include_globs, exclude_globs, ignore_globs) 531 | end 532 | end 533 | 534 | # 535 | def outofdate?(path, *sources) 536 | #fileutils.outofdate?(localize(path), localize(sources)) # DIDN'T WORK, why? 537 | locally do 538 | fileutils.outofdate?(path, sources.flatten) 539 | end 540 | end 541 | 542 | # 543 | def uptodate?(path, *sources) 544 | locally do 545 | fileutils.uptodate?(path, sources.flatten) 546 | end 547 | end 548 | 549 | # 550 | #def uptodate?(new, old_list, options=nil) 551 | # new = localize(new) 552 | # old = localize(old_list) 553 | # fileutils.uptodate?(new, old, options) 554 | #end 555 | 556 | =begin 557 | ############# 558 | # ZipUtils # 559 | ############# 560 | 561 | # Compress directory to file. Format is determined 562 | # by file extension. 563 | #def compress(folder, file, options={}) 564 | # #folder = localize(file) 565 | # #file = localize(file) 566 | # locally do 567 | # doc(ziputils.compress(folder, file, options)) 568 | # end 569 | #end 570 | 571 | # 572 | def gzip(file, tofile=nil, options={}) 573 | #file = localize(file) 574 | #tofile = localize(tofile) if tofile 575 | locally do 576 | file(ziputils.gzip(file, tofile, options)) 577 | end 578 | end 579 | 580 | # 581 | def bzip(file, tofile=nil, options={}) 582 | #file = localize(file) 583 | #tofile = localize(tofile) if tofile 584 | locally do 585 | doc(ziputils.bzip(file, tofile, options)) 586 | end 587 | end 588 | 589 | # Create a zip file of a directory. 590 | def zip(folder, file=nil, options={}) 591 | #folder = localize(folder) 592 | #file = localize(file) 593 | locally do 594 | doc(ziputils.zip(folder, file, options)) 595 | end 596 | end 597 | 598 | # 599 | def tar(folder, file=nil, options={}) 600 | #folder = localize(folder) 601 | #file = localize(file) 602 | locally do 603 | doc(ziputils.tar_gzip(folder, file, options)) 604 | end 605 | end 606 | 607 | # Create a tgz file of a directory. 608 | def tar_gzip(folder, file=nil, options={}) 609 | #folder = localize(folder) 610 | #file = localize(file) 611 | locally do 612 | doc(ziputils.tar_gzip(folder, file, options)) 613 | end 614 | end 615 | alias_method :tgz, :tar_gzip 616 | 617 | # Create a tar.bz2 file of a directory. 618 | def tar_bzip2(folder, file=nil, options={}) 619 | #folder = localize(folder) 620 | #file = localize(file) 621 | locally do 622 | doc(ziputils.tar_bzip2(folder, file, options)) 623 | end 624 | end 625 | 626 | def ungzip(file, options) 627 | #file = localize(file) 628 | locally do 629 | ziputils.ungzip(file, options) 630 | end 631 | end 632 | 633 | def unbzip2(file, options) 634 | #file = localize(file) 635 | locally do 636 | ziputils.unbzip2(file, options) 637 | end 638 | end 639 | 640 | def unzip(file, options) 641 | #file = localize(file) 642 | locally do 643 | ziputils.unzip(file, options) 644 | end 645 | end 646 | 647 | def untar(file, options) 648 | #file = localize(file) 649 | locally do 650 | ziputils.untar(file, options) 651 | end 652 | end 653 | 654 | def untar_gzip(file, options) 655 | #file = localize(file) 656 | locally do 657 | ziputils.untar_gzip(file, options) 658 | end 659 | end 660 | 661 | def untar_bzip2(file, options) 662 | #file = localize(file) 663 | locally do 664 | ziputils.untar_bzip2(file, options) 665 | end 666 | end 667 | =end 668 | 669 | #private ? 670 | 671 | # Returns a path local to the current working path. 672 | def localize(local_path) 673 | # some path arguments are optional 674 | return local_path unless local_path 675 | # 676 | case local_path 677 | when Array 678 | local_path.collect do |lp| 679 | if absolute?(lp) 680 | lp 681 | else 682 | File.expand_path(File.join(work.to_s, lp)) 683 | end 684 | end 685 | else 686 | # do not localize an absolute path 687 | return local_path if absolute?(local_path) 688 | File.expand_path(File.join(work.to_s, local_path)) 689 | end 690 | end 691 | 692 | # 693 | def locally(&block) 694 | if work.to_s == Dir.pwd 695 | block.call 696 | else 697 | mutex.synchronize do 698 | #work.chdir(&block) 699 | Dir.chdir(work, &block) 700 | end 701 | end 702 | end 703 | 704 | private 705 | 706 | # Returns FileUtils module based on mode. 707 | def fileutils 708 | if dryrun? 709 | ::FileUtils::DryRun 710 | elsif noop? 711 | ::FileUtils::Noop 712 | elsif verbose? 713 | ::FileUtils::Verbose 714 | else 715 | ::FileUtils 716 | end 717 | end 718 | 719 | =begin 720 | # Returns ZipUtils module based on mode. 721 | def ziputils 722 | if dryrun? 723 | ::ZipUtils::DryRun 724 | elsif noop? 725 | ::ZipUtils::Noop 726 | elsif verbose? 727 | ::ZipUtils::Verbose 728 | else 729 | ::ZipUtils 730 | end 731 | end 732 | =end 733 | 734 | # This may be used by script commands to allow for per command 735 | # noop and verbose options. Global options have precedence. 736 | def util_options(options) 737 | noop = noop? || options[:noop] || options[:dryrun] 738 | verbose = verbose? || options[:verbose] || options[:dryrun] 739 | return noop, verbose 740 | end 741 | 742 | public#class 743 | 744 | def self.[](path) 745 | new(path) 746 | end 747 | 748 | end 749 | 750 | end 751 | 752 | 753 | # Could the prompt act as a delegate to file objects? 754 | # If we did this then the file prompt could actually "cd" into a file. 755 | # 756 | =begin :nodoc: 757 | def initialize(path) 758 | @path = path = ::File.join(*path) 759 | 760 | raise FileNotFound unless ::File.exist?(path) 761 | 762 | if ::File.blockdev?(path) or chardev?(path) 763 | @delegate = Device.new(path) 764 | elsif ::File.link?(path) 765 | @delegate = Link.new(path) 766 | elsif ::File.directory?(path) 767 | @delegate = Directory.new(path) 768 | else 769 | @delegate = Document.new(path) 770 | end 771 | end 772 | 773 | def delete 774 | @delegate.delete 775 | @delegate = nil 776 | end 777 | =end 778 | #++ 779 | 780 | -------------------------------------------------------------------------------- /vendor/path/minitar.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | #-- 3 | # Archive::Tar::Minitar 0.5.2 4 | # Copyright 2004 Mauricio Julio Ferna'ndez Pradier and Austin Ziegler 5 | # 6 | # This program is based on and incorporates parts of RPA::Package from 7 | # rpa-base (lib/rpa/package.rb and lib/rpa/util.rb) by Mauricio and has been 8 | # adapted to be more generic by Austin. 9 | # 10 | # It is licensed under the GNU General Public Licence or Ruby's licence. 11 | # 12 | # $Id: minitar.rb 213 2008-02-26 22:32:11Z austin $ 13 | #++ 14 | 15 | module Archive; end 16 | module Archive::Tar; end 17 | 18 | # = Archive::Tar::PosixHeader 19 | # Implements the POSIX tar header as a Ruby class. The structure of 20 | # the POSIX tar header is: 21 | # 22 | # struct tarfile_entry_posix 23 | # { // pack/unpack 24 | # char name[100]; // ASCII (+ Z unless filled) a100/Z100 25 | # char mode[8]; // 0 padded, octal, null a8 /A8 26 | # char uid[8]; // ditto a8 /A8 27 | # char gid[8]; // ditto a8 /A8 28 | # char size[12]; // 0 padded, octal, null a12 /A12 29 | # char mtime[12]; // 0 padded, octal, null a12 /A12 30 | # char checksum[8]; // 0 padded, octal, null, space a8 /A8 31 | # char typeflag[1]; // see below a /a 32 | # char linkname[100]; // ASCII + (Z unless filled) a100/Z100 33 | # char magic[6]; // "ustar\0" a6 /A6 34 | # char version[2]; // "00" a2 /A2 35 | # char uname[32]; // ASCIIZ a32 /Z32 36 | # char gname[32]; // ASCIIZ a32 /Z32 37 | # char devmajor[8]; // 0 padded, octal, null a8 /A8 38 | # char devminor[8]; // 0 padded, octal, null a8 /A8 39 | # char prefix[155]; // ASCII (+ Z unless filled) a155/Z155 40 | # }; 41 | # 42 | # The +typeflag+ may be one of the following known values: 43 | # 44 | # "0":: Regular file. NULL should be treated as a synonym, for 45 | # compatibility purposes. 46 | # "1":: Hard link. 47 | # "2":: Symbolic link. 48 | # "3":: Character device node. 49 | # "4":: Block device node. 50 | # "5":: Directory. 51 | # "6":: FIFO node. 52 | # "7":: Reserved. 53 | # 54 | # POSIX indicates that "A POSIX-compliant implementation must treat any 55 | # unrecognized typeflag value as a regular file." 56 | class Archive::Tar::PosixHeader 57 | FIELDS = %w(name mode uid gid size mtime checksum typeflag linkname) + 58 | %w(magic version uname gname devmajor devminor prefix) 59 | 60 | FIELDS.each { |field| attr_reader field.intern } 61 | 62 | HEADER_PACK_FORMAT = "a100a8a8a8a12a12a7aaa100a6a2a32a32a8a8a155" 63 | HEADER_UNPACK_FORMAT = "Z100A8A8A8A12A12A8aZ100A6A2Z32Z32A8A8Z155" 64 | 65 | # Creates a new PosixHeader from a data stream. 66 | def self.new_from_stream(stream) 67 | data = stream.read(512) 68 | fields = data.unpack(HEADER_UNPACK_FORMAT) 69 | name = fields.shift 70 | mode = fields.shift.oct 71 | uid = fields.shift.oct 72 | gid = fields.shift.oct 73 | size = fields.shift.oct 74 | mtime = fields.shift.oct 75 | checksum = fields.shift.oct 76 | typeflag = fields.shift 77 | linkname = fields.shift 78 | magic = fields.shift 79 | version = fields.shift.oct 80 | uname = fields.shift 81 | gname = fields.shift 82 | devmajor = fields.shift.oct 83 | devminor = fields.shift.oct 84 | prefix = fields.shift 85 | 86 | empty = (data == "\0" * 512) 87 | 88 | new(:name => name, :mode => mode, :uid => uid, :gid => gid, 89 | :size => size, :mtime => mtime, :checksum => checksum, 90 | :typeflag => typeflag, :magic => magic, :version => version, 91 | :uname => uname, :gname => gname, :devmajor => devmajor, 92 | :devminor => devminor, :prefix => prefix, :empty => empty) 93 | end 94 | 95 | # Creates a new PosixHeader. A PosixHeader cannot be created unless the 96 | # #name, #size, #prefix, and #mode are provided. 97 | def initialize(vals) 98 | unless vals[:name] && vals[:size] && vals[:prefix] && vals[:mode] 99 | raise ArgumentError 100 | end 101 | 102 | vals[:mtime] ||= 0 103 | vals[:checksum] ||= "" 104 | vals[:typeflag] ||= "0" 105 | vals[:magic] ||= "ustar" 106 | vals[:version] ||= "00" 107 | 108 | FIELDS.each do |field| 109 | instance_variable_set("@#{field}", vals[field.intern]) 110 | end 111 | @empty = vals[:empty] 112 | end 113 | 114 | def empty? 115 | @empty 116 | end 117 | 118 | def to_s 119 | update_checksum 120 | header(@checksum) 121 | end 122 | 123 | # Update the checksum field. 124 | def update_checksum 125 | hh = header(" " * 8) 126 | @checksum = oct(calculate_checksum(hh), 6) 127 | end 128 | 129 | private 130 | def oct(num, len) 131 | if num.nil? 132 | "\0" * (len + 1) 133 | else 134 | "%0#{len}o" % num 135 | end 136 | end 137 | 138 | def calculate_checksum(hdr) 139 | hdr.unpack("C*").inject { |aa, bb| aa + bb } 140 | end 141 | 142 | def header(chksum) 143 | arr = [name, oct(mode, 7), oct(uid, 7), oct(gid, 7), oct(size, 11), 144 | oct(mtime, 11), chksum, " ", typeflag, linkname, magic, version, 145 | uname, gname, oct(devmajor, 7), oct(devminor, 7), prefix] 146 | str = arr.pack(HEADER_PACK_FORMAT) 147 | str + "\0" * ((512 - str.size) % 512) 148 | end 149 | end 150 | 151 | require 'fileutils' 152 | require 'find' 153 | 154 | # = Archive::Tar::Minitar 0.5.2 155 | # Archive::Tar::Minitar is a pure-Ruby library and command-line 156 | # utility that provides the ability to deal with POSIX tar(1) archive 157 | # files. The implementation is based heavily on Mauricio Ferna'ndez's 158 | # implementation in rpa-base, but has been reorganised to promote 159 | # reuse in other projects. 160 | # 161 | # This tar class performs a subset of all tar (POSIX tape archive) 162 | # operations. We can only deal with typeflags 0, 1, 2, and 5 (see 163 | # Archive::Tar::PosixHeader). All other typeflags will be treated as 164 | # normal files. 165 | # 166 | # NOTE::: support for typeflags 1 and 2 is not yet implemented in this 167 | # version. 168 | # 169 | # This release is version 0.5.2. The library can only handle files and 170 | # directories at this point. A future version will be expanded to 171 | # handle symbolic links and hard links in a portable manner. The 172 | # command line utility, minitar, can only create archives, extract 173 | # from archives, and list archive contents. 174 | # 175 | # == Synopsis 176 | # Using this library is easy. The simplest case is: 177 | # 178 | # require 'zlib' 179 | # require 'archive/tar/minitar' 180 | # include Archive::Tar 181 | # 182 | # # Packs everything that matches Find.find('tests') 183 | # File.open('test.tar', 'wb') { |tar| Minitar.pack('tests', tar) } 184 | # # Unpacks 'test.tar' to 'x', creating 'x' if necessary. 185 | # Minitar.unpack('test.tar', 'x') 186 | # 187 | # A gzipped tar can be written with: 188 | # 189 | # tgz = Zlib::GzipWriter.new(File.open('test.tgz', 'wb')) 190 | # # Warning: tgz will be closed! 191 | # Minitar.pack('tests', tgz) 192 | # 193 | # tgz = Zlib::GzipReader.new(File.open('test.tgz', 'rb')) 194 | # # Warning: tgz will be closed! 195 | # Minitar.unpack(tgz, 'x') 196 | # 197 | # As the case above shows, one need not write to a file. However, it 198 | # will sometimes require that one dive a little deeper into the API, 199 | # as in the case of StringIO objects. Note that I'm not providing a 200 | # block with Minitar::Output, as Minitar::Output#close automatically 201 | # closes both the Output object and the wrapped data stream object. 202 | # 203 | # begin 204 | # sgz = Zlib::GzipWriter.new(StringIO.new("")) 205 | # tar = Output.new(sgz) 206 | # Find.find('tests') do |entry| 207 | # Minitar.pack_file(entry, tar) 208 | # end 209 | # ensure 210 | # # Closes both tar and sgz. 211 | # tar.close 212 | # end 213 | # 214 | # == Copyright 215 | # Copyright 2004 Mauricio Julio Ferna'ndez Pradier and Austin Ziegler 216 | # 217 | # This program is based on and incorporates parts of RPA::Package from 218 | # rpa-base (lib/rpa/package.rb and lib/rpa/util.rb) by Mauricio and 219 | # has been adapted to be more generic by Austin. 220 | # 221 | # 'minitar' contains an adaptation of Ruby/ProgressBar by Satoru 222 | # Takabayashi , copyright 2001 - 2004. 223 | # 224 | # This program is free software. It may be redistributed and/or 225 | # modified under the terms of the GPL version 2 (or later) or Ruby's 226 | # licence. 227 | module Archive::Tar::Minitar 228 | VERSION = "0.5.2" 229 | 230 | # The exception raised when a wrapped data stream class is expected to 231 | # respond to #rewind or #pos but does not. 232 | class NonSeekableStream < StandardError; end 233 | # The exception raised when a block is required for proper operation of 234 | # the method. 235 | class BlockRequired < ArgumentError; end 236 | # The exception raised when operations are performed on a stream that has 237 | # previously been closed. 238 | class ClosedStream < StandardError; end 239 | # The exception raised when a filename exceeds 256 bytes in length, 240 | # the maximum supported by the standard Tar format. 241 | class FileNameTooLong < StandardError; end 242 | # The exception raised when a data stream ends before the amount of data 243 | # expected in the archive's PosixHeader. 244 | class UnexpectedEOF < StandardError; end 245 | 246 | # The class that writes a tar format archive to a data stream. 247 | class Writer 248 | # A stream wrapper that can only be written to. Any attempt to read 249 | # from this restricted stream will result in a NameError being thrown. 250 | class RestrictedStream 251 | def initialize(anIO) 252 | @io = anIO 253 | end 254 | 255 | def write(data) 256 | @io.write(data) 257 | end 258 | end 259 | 260 | # A RestrictedStream that also has a size limit. 261 | class BoundedStream < Archive::Tar::Minitar::Writer::RestrictedStream 262 | # The exception raised when the user attempts to write more data to 263 | # a BoundedStream than has been allocated. 264 | class FileOverflow < RuntimeError; end 265 | 266 | # The maximum number of bytes that may be written to this data 267 | # stream. 268 | attr_reader :limit 269 | # The current total number of bytes written to this data stream. 270 | attr_reader :written 271 | 272 | def initialize(io, limit) 273 | @io = io 274 | @limit = limit 275 | @written = 0 276 | end 277 | 278 | def write(data) 279 | raise FileOverflow if (data.size + @written) > @limit 280 | @io.write(data) 281 | @written += data.size 282 | data.size 283 | end 284 | end 285 | 286 | # With no associated block, +Writer::open+ is a synonym for 287 | # +Writer::new+. If the optional code block is given, it will be 288 | # passed the new _writer_ as an argument and the Writer object will 289 | # automatically be closed when the block terminates. In this instance, 290 | # +Writer::open+ returns the value of the block. 291 | def self.open(anIO) 292 | writer = Writer.new(anIO) 293 | 294 | return writer unless block_given? 295 | 296 | begin 297 | res = yield writer 298 | ensure 299 | writer.close 300 | end 301 | 302 | res 303 | end 304 | 305 | # Creates and returns a new Writer object. 306 | def initialize(anIO) 307 | @io = anIO 308 | @closed = false 309 | end 310 | 311 | # Adds a file to the archive as +name+. +opts+ must contain the 312 | # following values: 313 | # 314 | # :mode:: The Unix file permissions mode value. 315 | # :size:: The size, in bytes. 316 | # 317 | # +opts+ may contain the following values: 318 | # 319 | # :uid: The Unix file owner user ID number. 320 | # :gid: The Unix file owner group ID number. 321 | # :mtime:: The *integer* modification time value. 322 | # 323 | # It will not be possible to add more than opts[:size] bytes 324 | # to the file. 325 | def add_file_simple(name, opts = {}) # :yields BoundedStream: 326 | raise Archive::Tar::Minitar::BlockRequired unless block_given? 327 | raise Archive::Tar::ClosedStream if @closed 328 | 329 | name, prefix = split_name(name) 330 | 331 | header = { :name => name, :mode => opts[:mode], :mtime => opts[:mtime], 332 | :size => opts[:size], :gid => opts[:gid], :uid => opts[:uid], 333 | :prefix => prefix } 334 | header = Archive::Tar::PosixHeader.new(header).to_s 335 | @io.write(header) 336 | 337 | os = BoundedStream.new(@io, opts[:size]) 338 | yield os 339 | # FIXME: what if an exception is raised in the block? 340 | 341 | min_padding = opts[:size] - os.written 342 | @io.write("\0" * min_padding) 343 | remainder = (512 - (opts[:size] % 512)) % 512 344 | @io.write("\0" * remainder) 345 | end 346 | 347 | # Adds a file to the archive as +name+. +opts+ must contain the 348 | # following value: 349 | # 350 | # :mode:: The Unix file permissions mode value. 351 | # 352 | # +opts+ may contain the following values: 353 | # 354 | # :uid: The Unix file owner user ID number. 355 | # :gid: The Unix file owner group ID number. 356 | # :mtime:: The *integer* modification time value. 357 | # 358 | # The file's size will be determined from the amount of data written 359 | # to the stream. 360 | # 361 | # For #add_file to be used, the Archive::Tar::Minitar::Writer must be 362 | # wrapping a stream object that is seekable (e.g., it responds to 363 | # #pos=). Otherwise, #add_file_simple must be used. 364 | # 365 | # +opts+ may be modified during the writing to the stream. 366 | def add_file(name, opts = {}) # :yields RestrictedStream, +opts+: 367 | raise Archive::Tar::Minitar::BlockRequired unless block_given? 368 | raise Archive::Tar::Minitar::ClosedStream if @closed 369 | raise Archive::Tar::Minitar::NonSeekableStream unless @io.respond_to?(:pos=) 370 | 371 | name, prefix = split_name(name) 372 | init_pos = @io.pos 373 | @io.write("\0" * 512) # placeholder for the header 374 | 375 | yield RestrictedStream.new(@io), opts 376 | # FIXME: what if an exception is raised in the block? 377 | 378 | size = @io.pos - (init_pos + 512) 379 | remainder = (512 - (size % 512)) % 512 380 | @io.write("\0" * remainder) 381 | 382 | final_pos = @io.pos 383 | @io.pos = init_pos 384 | 385 | header = { :name => name, :mode => opts[:mode], :mtime => opts[:mtime], 386 | :size => size, :gid => opts[:gid], :uid => opts[:uid], 387 | :prefix => prefix } 388 | header = Archive::Tar::PosixHeader.new(header).to_s 389 | @io.write(header) 390 | @io.pos = final_pos 391 | end 392 | 393 | # Creates a directory in the tar. 394 | def mkdir(name, opts = {}) 395 | raise ClosedStream if @closed 396 | name, prefix = split_name(name) 397 | header = { :name => name, :mode => opts[:mode], :typeflag => "5", 398 | :size => 0, :gid => opts[:gid], :uid => opts[:uid], 399 | :mtime => opts[:mtime], :prefix => prefix } 400 | header = Archive::Tar::PosixHeader.new(header).to_s 401 | @io.write(header) 402 | nil 403 | end 404 | 405 | # Passes the #flush method to the wrapped stream, used for buffered 406 | # streams. 407 | def flush 408 | raise ClosedStream if @closed 409 | @io.flush if @io.respond_to?(:flush) 410 | end 411 | 412 | # Closes the Writer. 413 | def close 414 | return if @closed 415 | @io.write("\0" * 1024) 416 | @closed = true 417 | end 418 | 419 | private 420 | def split_name(name) 421 | raise FileNameTooLong if name.size > 256 422 | if name.size <= 100 423 | prefix = "" 424 | else 425 | parts = name.split(/\//) 426 | newname = parts.pop 427 | 428 | nxt = "" 429 | 430 | loop do 431 | nxt = parts.pop 432 | break if newname.size + 1 + nxt.size > 100 433 | newname = "#{nxt}/#{newname}" 434 | end 435 | 436 | prefix = (parts + [nxt]).join("/") 437 | 438 | name = newname 439 | 440 | raise FileNameTooLong if name.size > 100 || prefix.size > 155 441 | end 442 | return name, prefix 443 | end 444 | end 445 | 446 | # The class that reads a tar format archive from a data stream. The data 447 | # stream may be sequential or random access, but certain features only work 448 | # with random access data streams. 449 | class Reader 450 | # This marks the EntryStream closed for reading without closing the 451 | # actual data stream. 452 | module InvalidEntryStream 453 | def read(len = nil); raise ClosedStream; end 454 | def getc; raise ClosedStream; end 455 | def rewind; raise ClosedStream; end 456 | end 457 | 458 | # EntryStreams are pseudo-streams on top of the main data stream. 459 | class EntryStream 460 | Archive::Tar::PosixHeader::FIELDS.each do |field| 461 | attr_reader field.intern 462 | end 463 | 464 | def initialize(header, anIO) 465 | @io = anIO 466 | @name = header.name 467 | @mode = header.mode 468 | @uid = header.uid 469 | @gid = header.gid 470 | @size = header.size 471 | @mtime = header.mtime 472 | @checksum = header.checksum 473 | @typeflag = header.typeflag 474 | @linkname = header.linkname 475 | @magic = header.magic 476 | @version = header.version 477 | @uname = header.uname 478 | @gname = header.gname 479 | @devmajor = header.devmajor 480 | @devminor = header.devminor 481 | @prefix = header.prefix 482 | @read = 0 483 | @orig_pos = @io.pos 484 | end 485 | 486 | # Reads +len+ bytes (or all remaining data) from the entry. Returns 487 | # +nil+ if there is no more data to read. 488 | def read(len = nil) 489 | return nil if @read >= @size 490 | len ||= @size - @read 491 | max_read = [len, @size - @read].min 492 | ret = @io.read(max_read) 493 | @read += ret.size 494 | ret 495 | end 496 | 497 | # Reads one byte from the entry. Returns +nil+ if there is no more data 498 | # to read. 499 | def getc 500 | return nil if @read >= @size 501 | ret = @io.getc 502 | @read += 1 if ret 503 | ret 504 | end 505 | 506 | # Returns +true+ if the entry represents a directory. 507 | def directory? 508 | @typeflag == "5" 509 | end 510 | alias_method :directory, :directory? 511 | 512 | # Returns +true+ if the entry represents a plain file. 513 | def file? 514 | @typeflag == "0" 515 | end 516 | alias_method :file, :file? 517 | 518 | # Returns +true+ if the current read pointer is at the end of the 519 | # EntryStream data. 520 | def eof? 521 | @read >= @size 522 | end 523 | 524 | # Returns the current read pointer in the EntryStream. 525 | def pos 526 | @read 527 | end 528 | 529 | # Sets the current read pointer to the beginning of the EntryStream. 530 | def rewind 531 | raise NonSeekableStream unless @io.respond_to?(:pos=) 532 | @io.pos = @orig_pos 533 | @read = 0 534 | end 535 | 536 | def bytes_read 537 | @read 538 | end 539 | 540 | # Returns the full and proper name of the entry. 541 | def full_name 542 | if @prefix != "" 543 | File.join(@prefix, @name) 544 | else 545 | @name 546 | end 547 | end 548 | 549 | # Closes the entry. 550 | def close 551 | invalidate 552 | end 553 | 554 | private 555 | def invalidate 556 | extend InvalidEntryStream 557 | end 558 | end 559 | 560 | # With no associated block, +Reader::open+ is a synonym for 561 | # +Reader::new+. If the optional code block is given, it will be passed 562 | # the new _writer_ as an argument and the Reader object will 563 | # automatically be closed when the block terminates. In this instance, 564 | # +Reader::open+ returns the value of the block. 565 | def self.open(anIO) 566 | reader = Reader.new(anIO) 567 | 568 | return reader unless block_given? 569 | 570 | begin 571 | res = yield reader 572 | ensure 573 | reader.close 574 | end 575 | 576 | res 577 | end 578 | 579 | # Creates and returns a new Reader object. 580 | def initialize(anIO) 581 | @io = anIO 582 | @init_pos = anIO.pos 583 | end 584 | 585 | # Iterates through each entry in the data stream. 586 | def each(&block) 587 | each_entry(&block) 588 | end 589 | 590 | # Resets the read pointer to the beginning of data stream. Do not call 591 | # this during a #each or #each_entry iteration. This only works with 592 | # random access data streams that respond to #rewind and #pos. 593 | def rewind 594 | if @init_pos == 0 595 | raise NonSeekableStream unless @io.respond_to?(:rewind) 596 | @io.rewind 597 | else 598 | raise NonSeekableStream unless @io.respond_to?(:pos=) 599 | @io.pos = @init_pos 600 | end 601 | end 602 | 603 | # Iterates through each entry in the data stream. 604 | def each_entry 605 | loop do 606 | return if @io.eof? 607 | 608 | header = Archive::Tar::PosixHeader.new_from_stream(@io) 609 | return if header.empty? 610 | 611 | entry = EntryStream.new(header, @io) 612 | size = entry.size 613 | 614 | yield entry 615 | 616 | skip = (512 - (size % 512)) % 512 617 | 618 | if @io.respond_to?(:seek) 619 | # avoid reading... 620 | @io.seek(size - entry.bytes_read, IO::SEEK_CUR) 621 | else 622 | pending = size - entry.bytes_read 623 | while pending > 0 624 | bread = @io.read([pending, 4096].min).size 625 | raise UnexpectedEOF if @io.eof? 626 | pending -= bread 627 | end 628 | end 629 | @io.read(skip) # discard trailing zeros 630 | # make sure nobody can use #read, #getc or #rewind anymore 631 | entry.close 632 | end 633 | end 634 | 635 | def close 636 | end 637 | end 638 | 639 | # Wraps a Archive::Tar::Minitar::Reader with convenience methods and 640 | # wrapped stream management; Input only works with random access data 641 | # streams. See Input::new for details. 642 | class Input 643 | include Enumerable 644 | 645 | # With no associated block, +Input::open+ is a synonym for 646 | # +Input::new+. If the optional code block is given, it will be passed 647 | # the new _writer_ as an argument and the Input object will 648 | # automatically be closed when the block terminates. In this instance, 649 | # +Input::open+ returns the value of the block. 650 | def self.open(input) 651 | stream = Input.new(input) 652 | return stream unless block_given? 653 | 654 | begin 655 | res = yield stream 656 | ensure 657 | stream.close 658 | end 659 | 660 | res 661 | end 662 | 663 | # Creates a new Input object. If +input+ is a stream object that responds 664 | # to #read), then it will simply be wrapped. Otherwise, one will be 665 | # created and opened using Kernel#open. When Input#close is called, the 666 | # stream object wrapped will be closed. 667 | def initialize(input) 668 | if input.respond_to?(:read) 669 | @io = input 670 | else 671 | @io = open(input, "rb") 672 | end 673 | @tarreader = Archive::Tar::Minitar::Reader.new(@io) 674 | end 675 | 676 | # Iterates through each entry and rewinds to the beginning of the stream 677 | # when finished. 678 | def each(&block) 679 | @tarreader.each { |entry| yield entry } 680 | ensure 681 | @tarreader.rewind 682 | end 683 | 684 | # Extracts the current +entry+ to +destdir+. If a block is provided, it 685 | # yields an +action+ Symbol, the full name of the file being extracted 686 | # (+name+), and a Hash of statistical information (+stats+). 687 | # 688 | # The +action+ will be one of: 689 | # :dir:: The +entry+ is a directory. 690 | # :file_start:: The +entry+ is a file; the extract of the 691 | # file is just beginning. 692 | # :file_progress:: Yielded every 4096 bytes during the extract 693 | # of the +entry+. 694 | # :file_done:: Yielded when the +entry+ is completed. 695 | # 696 | # The +stats+ hash contains the following keys: 697 | # :current:: The current total number of bytes read in the 698 | # +entry+. 699 | # :currinc:: The current number of bytes read in this read 700 | # cycle. 701 | # :entry:: The entry being extracted; this is a 702 | # Reader::EntryStream, with all methods thereof. 703 | def extract_entry(destdir, entry) # :yields action, name, stats: 704 | stats = { 705 | :current => 0, 706 | :currinc => 0, 707 | :entry => entry 708 | } 709 | 710 | if entry.directory? 711 | dest = File.join(destdir, entry.full_name) 712 | 713 | yield :dir, entry.full_name, stats if block_given? 714 | 715 | if Archive::Tar::Minitar.dir?(dest) 716 | begin 717 | FileUtils.chmod(entry.mode, dest) 718 | rescue Exception 719 | nil 720 | end 721 | else 722 | FileUtils.mkdir_p(dest, :mode => entry.mode) 723 | FileUtils.chmod(entry.mode, dest) 724 | end 725 | 726 | fsync_dir(dest) 727 | fsync_dir(File.join(dest, "..")) 728 | return 729 | else # it's a file 730 | destdir = File.join(destdir, File.dirname(entry.full_name)) 731 | FileUtils.mkdir_p(destdir, :mode => 0755) 732 | 733 | destfile = File.join(destdir, File.basename(entry.full_name)) 734 | FileUtils.chmod(0600, destfile) rescue nil # Errno::ENOENT 735 | 736 | yield :file_start, entry.full_name, stats if block_given? 737 | 738 | File.open(destfile, "wb", entry.mode) do |os| 739 | loop do 740 | data = entry.read(4096) 741 | break unless data 742 | 743 | stats[:currinc] = os.write(data) 744 | stats[:current] += stats[:currinc] 745 | 746 | yield :file_progress, entry.full_name, stats if block_given? 747 | end 748 | os.fsync 749 | end 750 | 751 | FileUtils.chmod(entry.mode, destfile) 752 | fsync_dir(File.dirname(destfile)) 753 | fsync_dir(File.join(File.dirname(destfile), "..")) 754 | 755 | yield :file_done, entry.full_name, stats if block_given? 756 | end 757 | end 758 | 759 | # Returns the Reader object for direct access. 760 | def tar 761 | @tarreader 762 | end 763 | 764 | # Closes the Reader object and the wrapped data stream. 765 | def close 766 | @io.close 767 | @tarreader.close 768 | end 769 | 770 | private 771 | def fsync_dir(dirname) 772 | # make sure this hits the disc 773 | dir = open(dirname, 'rb') 774 | dir.fsync 775 | rescue # ignore IOError if it's an unpatched (old) Ruby 776 | nil 777 | ensure 778 | dir.close if dir rescue nil 779 | end 780 | end 781 | 782 | # Wraps a Archive::Tar::Minitar::Writer with convenience methods and 783 | # wrapped stream management; Output only works with random access data 784 | # streams. See Output::new for details. 785 | class Output 786 | # With no associated block, +Output::open+ is a synonym for 787 | # +Output::new+. If the optional code block is given, it will be passed 788 | # the new _writer_ as an argument and the Output object will 789 | # automatically be closed when the block terminates. In this instance, 790 | # +Output::open+ returns the value of the block. 791 | def self.open(output) 792 | stream = Output.new(output) 793 | return stream unless block_given? 794 | 795 | begin 796 | res = yield stream 797 | ensure 798 | stream.close 799 | end 800 | 801 | res 802 | end 803 | 804 | # Creates a new Output object. If +output+ is a stream object that 805 | # responds to #read), then it will simply be wrapped. Otherwise, one will 806 | # be created and opened using Kernel#open. When Output#close is called, 807 | # the stream object wrapped will be closed. 808 | def initialize(output) 809 | if output.respond_to?(:write) 810 | @io = output 811 | else 812 | @io = ::File.open(output, "wb") 813 | end 814 | @tarwriter = Archive::Tar::Minitar::Writer.new(@io) 815 | end 816 | 817 | # Returns the Writer object for direct access. 818 | def tar 819 | @tarwriter 820 | end 821 | 822 | # Closes the Writer object and the wrapped data stream. 823 | def close 824 | @tarwriter.close 825 | @io.close 826 | end 827 | end 828 | 829 | class << self 830 | # Tests if +path+ refers to a directory. Fixes an apparently 831 | # corrupted stat() call on Windows. 832 | def dir?(path) 833 | File.directory?((path[-1] == ?/) ? path : "#{path}/") 834 | end 835 | 836 | # A convenience method for wrapping Archive::Tar::Minitar::Input.open 837 | # (mode +r+) and Archive::Tar::Minitar::Output.open (mode +w+). No other 838 | # modes are currently supported. 839 | def open(dest, mode = "r", &block) 840 | case mode 841 | when "r" 842 | Input.open(dest, &block) 843 | when "w" 844 | Output.open(dest, &block) 845 | else 846 | raise "Unknown open mode for Archive::Tar::Minitar.open." 847 | end 848 | end 849 | 850 | # A convenience method to packs the file provided. +entry+ may either be 851 | # a filename (in which case various values for the file (see below) will 852 | # be obtained from File#stat(entry) or a Hash with the fields: 853 | # 854 | # :name:: The filename to be packed into the tarchive. 855 | # *REQUIRED*. 856 | # :mode:: The mode to be applied. 857 | # :uid:: The user owner of the file. (Ignored on Windows.) 858 | # :gid:: The group owner of the file. (Ignored on Windows.) 859 | # :mtime:: The modification Time of the file. 860 | # 861 | # During packing, if a block is provided, #pack_file yields an +action+ 862 | # Symol, the full name of the file being packed, and a Hash of 863 | # statistical information, just as with 864 | # Archive::Tar::Minitar::Input#extract_entry. 865 | # 866 | # The +action+ will be one of: 867 | # :dir:: The +entry+ is a directory. 868 | # :file_start:: The +entry+ is a file; the extract of the 869 | # file is just beginning. 870 | # :file_progress:: Yielded every 4096 bytes during the extract 871 | # of the +entry+. 872 | # :file_done:: Yielded when the +entry+ is completed. 873 | # 874 | # The +stats+ hash contains the following keys: 875 | # :current:: The current total number of bytes read in the 876 | # +entry+. 877 | # :currinc:: The current number of bytes read in this read 878 | # cycle. 879 | # :name:: The filename to be packed into the tarchive. 880 | # *REQUIRED*. 881 | # :mode:: The mode to be applied. 882 | # :uid:: The user owner of the file. (+nil+ on Windows.) 883 | # :gid:: The group owner of the file. (+nil+ on Windows.) 884 | # :mtime:: The modification Time of the file. 885 | def pack_file(entry, outputter) #:yields action, name, stats: 886 | outputter = outputter.tar if outputter.kind_of?(Archive::Tar::Minitar::Output) 887 | 888 | stats = {} 889 | 890 | if entry.kind_of?(Hash) 891 | name = entry[:name] 892 | 893 | entry.each { |kk, vv| stats[kk] = vv unless vv.nil? } 894 | else 895 | name = entry 896 | end 897 | 898 | name = name.sub(%r{\./}, '') 899 | stat = File.stat(name) 900 | stats[:mode] ||= stat.mode 901 | stats[:mtime] ||= stat.mtime 902 | stats[:size] = stat.size 903 | 904 | if RUBY_PLATFORM =~ /win32/ 905 | stats[:uid] = nil 906 | stats[:gid] = nil 907 | else 908 | stats[:uid] ||= stat.uid 909 | stats[:gid] ||= stat.gid 910 | end 911 | 912 | case 913 | when File.file?(name) 914 | outputter.add_file_simple(name, stats) do |os| 915 | stats[:current] = 0 916 | yield :file_start, name, stats if block_given? 917 | File.open(name, "rb") do |ff| 918 | until ff.eof? 919 | stats[:currinc] = os.write(ff.read(4096)) 920 | stats[:current] += stats[:currinc] 921 | yield :file_progress, name, stats if block_given? 922 | end 923 | end 924 | yield :file_done, name, stats if block_given? 925 | end 926 | when dir?(name) 927 | yield :dir, name, stats if block_given? 928 | outputter.mkdir(name, stats) 929 | else 930 | raise "Don't yet know how to pack this type of file." 931 | end 932 | end 933 | 934 | # A convenience method to pack files specified by +src+ into +dest+. If 935 | # +src+ is an Array, then each file detailed therein will be packed into 936 | # the resulting Archive::Tar::Minitar::Output stream; if +recurse_dirs+ 937 | # is true, then directories will be recursed. 938 | # 939 | # If +src+ is an Array, it will be treated as the argument to Find.find; 940 | # all files matching will be packed. 941 | def pack(src, dest, recurse_dirs = true, &block) 942 | Output.open(dest) do |outp| 943 | if src.kind_of?(Array) 944 | src.each do |entry| 945 | pack_file(entry, outp, &block) 946 | if dir?(entry) and recurse_dirs 947 | Dir["#{entry}/**/**"].each do |ee| 948 | pack_file(ee, outp, &block) 949 | end 950 | end 951 | end 952 | else 953 | Find.find(src) do |entry| 954 | pack_file(entry, outp, &block) 955 | end 956 | end 957 | end 958 | end 959 | 960 | # A convenience method to unpack files from +src+ into the directory 961 | # specified by +dest+. Only those files named explicitly in +files+ 962 | # will be extracted. 963 | def unpack(src, dest, files = [], &block) 964 | Input.open(src) do |inp| 965 | if File.exist?(dest) and (not dir?(dest)) 966 | raise "Can't unpack to a non-directory." 967 | elsif not File.exist?(dest) 968 | FileUtils.mkdir_p(dest) 969 | end 970 | 971 | inp.each do |entry| 972 | if files.empty? or files.include?(entry.full_name) 973 | inp.extract_entry(dest, entry, &block) 974 | end 975 | end 976 | end 977 | end 978 | end 979 | end 980 | -------------------------------------------------------------------------------- /work/reference/name.rb: -------------------------------------------------------------------------------- 1 | module Path 2 | 3 | # == Path::Name 4 | # 5 | # Pathname represents a pathname which locates a file in a filesystem. 6 | # The pathname depends on OS: Unix, Windows, etc. 7 | # Pathname library works with pathnames of local OS. 8 | # However non-Unix pathnames are supported experimentally. 9 | # 10 | # It does not represent the file itself. 11 | # A Pathname can be relative or absolute. It's not until you try to 12 | # reference the file that it even matters whether the file exists or not. 13 | # 14 | # Pathname is immutable. It has no method for destructive update. 15 | # 16 | # The value of this class is to manipulate file path information in a neater 17 | # way than standard Ruby provides. The examples below demonstrate the 18 | # difference. *All* functionality from File, FileTest, and some from Dir and 19 | # FileUtils is included, in an unsurprising way. It is essentially a facade for 20 | # all of these, and more. 21 | # 22 | # == Examples 23 | # 24 | # === Example 1: Using Pathname 25 | # 26 | # require 'pathname' 27 | # pn = Pathname.new("/usr/bin/ruby") 28 | # size = pn.size # 27662 29 | # isdir = pn.directory? # false 30 | # dir = pn.dirname # Pathname:/usr/bin 31 | # base = pn.basename # Pathname:ruby 32 | # dir, base = pn.split # [Pathname:/usr/bin, Pathname:ruby] 33 | # data = pn.read 34 | # pn.open { |f| _ } 35 | # pn.each_line { |line| _ } 36 | # 37 | # === Example 2: Using standard Ruby 38 | # 39 | # pn = "/usr/bin/ruby" 40 | # size = File.size(pn) # 27662 41 | # isdir = File.directory?(pn) # false 42 | # dir = File.dirname(pn) # "/usr/bin" 43 | # base = File.basename(pn) # "ruby" 44 | # dir, base = File.split(pn) # ["/usr/bin", "ruby"] 45 | # data = File.read(pn) 46 | # File.open(pn) { |f| _ } 47 | # File.foreach(pn) { |line| _ } 48 | # 49 | # === Example 3: Special features 50 | # 51 | # p1 = Pathname.new("/usr/lib") # Pathname:/usr/lib 52 | # p2 = p1 + "ruby/1.8" # Pathname:/usr/lib/ruby/1.8 53 | # p3 = p1.parent # Pathname:/usr 54 | # p4 = p2.relative_path_from(p3) # Pathname:lib/ruby/1.8 55 | # pwd = Pathname.pwd # Pathname:/home/gavin 56 | # pwd.absolute? # true 57 | # p5 = Pathname.new "." # Pathname:. 58 | # p5 = p5 + "music/../articles" # Pathname:music/../articles 59 | # p5.cleanpath # Pathname:articles 60 | # p5.realpath # Pathname:/home/gavin/articles 61 | # p5.children # [Pathname:/home/gavin/articles/linux, ...] 62 | # 63 | # == Breakdown of functionality 64 | # 65 | # === Core methods 66 | # 67 | # These methods are effectively manipulating a String, because that's 68 | # all a path is. Except for #mountpoint?, #children, #each_child, 69 | # #realdirpath and #realpath, they don't access the filesystem. 70 | # 71 | # - + 72 | # - #join 73 | # - #parent 74 | # - #root? 75 | # - #absolute? 76 | # - #relative? 77 | # - #relative_path_from 78 | # - #each_filename 79 | # - #cleanpath 80 | # - #realpath 81 | # - #realdirpath 82 | # - #children 83 | # - #each_child 84 | # - #mountpoint? 85 | # 86 | # === File status predicate methods 87 | # 88 | # These methods are a facade for FileTest: 89 | # - #blockdev? 90 | # - #chardev? 91 | # - #directory? 92 | # - #executable? 93 | # - #executable_real? 94 | # - #exist? 95 | # - #file? 96 | # - #grpowned? 97 | # - #owned? 98 | # - #pipe? 99 | # - #readable? 100 | # - #world_readable? 101 | # - #readable_real? 102 | # - #setgid? 103 | # - #setuid? 104 | # - #size 105 | # - #size? 106 | # - #socket? 107 | # - #sticky? 108 | # - #symlink? 109 | # - #writable? 110 | # - #world_writable? 111 | # - #writable_real? 112 | # - #zero? 113 | # 114 | # === File property and manipulation methods 115 | # 116 | # These methods are a facade for File: 117 | # - #atime 118 | # - #ctime 119 | # - #mtime 120 | # - #chmod(mode) 121 | # - #lchmod(mode) 122 | # - #chown(owner, group) 123 | # - #lchown(owner, group) 124 | # - #fnmatch(pattern, *args) 125 | # - #fnmatch?(pattern, *args) 126 | # - #ftype 127 | # - #make_link(old) 128 | # - #open(*args, &block) 129 | # - #readlink 130 | # - #rename(to) 131 | # - #stat 132 | # - #lstat 133 | # - #make_symlink(old) 134 | # - #truncate(length) 135 | # - #utime(atime, mtime) 136 | # - #basename(*args) 137 | # - #dirname 138 | # - #extname 139 | # - #expand_path(*args) 140 | # - #split 141 | # 142 | # === Directory methods 143 | # 144 | # These methods are a facade for Dir: 145 | # - Pathname.glob(*args) 146 | # - Pathname.getwd / Pathname.pwd 147 | # - #rmdir 148 | # - #entries 149 | # - #each_entry(&block) 150 | # - #mkdir(*args) 151 | # - #opendir(*args) 152 | # 153 | # === IO 154 | # 155 | # These methods are a facade for IO: 156 | # - #each_line(*args, &block) 157 | # - #read(*args) 158 | # - #binread(*args) 159 | # - #readlines(*args) 160 | # - #sysopen(*args) 161 | # 162 | # === Utilities 163 | # 164 | # These methods are a mixture of Find, FileUtils, and others: 165 | # - #find(&block) 166 | # - #mkpath 167 | # - #rmtree 168 | # - #unlink / #delete 169 | # 170 | # 171 | # == Method documentation 172 | # 173 | # As the above section shows, most of the methods in Pathname are facades. The 174 | # documentation for these methods generally just says, for instance, "See 175 | # FileTest.writable?", as you should be familiar with the original method 176 | # anyway, and its documentation (e.g. through +ri+) will contain more 177 | # information. In some cases, a brief description will follow. 178 | # 179 | # = path/name.rb 180 | # 181 | # Object-Oriented Pathname Class 182 | # 183 | # Author:: Tanaka Akira 184 | # Documentation:: Author and Gavin Sinclair 185 | # 186 | # pathname.rb is distributed with Ruby since 1.8.0. 187 | # 188 | class Name 189 | 190 | # :stopdoc: 191 | if RUBY_VERSION < "1.9" 192 | TO_PATH = :to_str 193 | else 194 | # to_path is implemented so Pathname objects are usable with File.open, etc. 195 | TO_PATH = :to_path 196 | end 197 | 198 | SAME_PATHS = if File::FNM_SYSCASE 199 | proc {|a, b| a.casecmp(b).zero?} 200 | else 201 | proc {|a, b| a == b} 202 | end 203 | 204 | # :startdoc: 205 | 206 | # 207 | # Create a Pathname object from the given String (or String-like object). 208 | # If +path+ contains a NUL character (\0), an ArgumentError is raised. 209 | # 210 | def initialize(path) 211 | path = path.__send__(TO_PATH) if path.respond_to? TO_PATH 212 | @path = path.dup 213 | 214 | if /\0/ =~ @path 215 | raise ArgumentError, "pathname contains \\0: #{@path.inspect}" 216 | end 217 | 218 | self.taint if @path.tainted? 219 | end 220 | 221 | def freeze() super; @path.freeze; self end 222 | def taint() super; @path.taint; self end 223 | def untaint() super; @path.untaint; self end 224 | 225 | # 226 | # Compare this pathname with +other+. The comparison is string-based. 227 | # Be aware that two different paths (foo.txt and ./foo.txt) 228 | # can refer to the same file. 229 | # 230 | def ==(other) 231 | return false unless Pathname === other 232 | other.to_s == @path 233 | end 234 | alias === == 235 | alias eql? == 236 | 237 | # Provides for comparing pathnames, case-sensitively. 238 | def <=>(other) 239 | return nil unless Pathname === other 240 | @path.tr('/', "\0") <=> other.to_s.tr('/', "\0") 241 | end 242 | 243 | def hash # :nodoc: 244 | @path.hash 245 | end 246 | 247 | # Return the path as a String. 248 | def to_s 249 | @path.dup 250 | end 251 | 252 | # to_path is implemented so Pathname objects are usable with File.open, etc. 253 | alias_method TO_PATH, :to_s 254 | 255 | def inspect # :nodoc: 256 | "#<#{self.class}:#{@path}>" 257 | end 258 | 259 | # Return a pathname which is substituted by String#sub. 260 | def sub(pattern, *rest, &block) 261 | if block 262 | path = @path.sub(pattern, *rest) {|*args| 263 | begin 264 | old = Thread.current[:pathname_sub_matchdata] 265 | Thread.current[:pathname_sub_matchdata] = $~ 266 | eval("$~ = Thread.current[:pathname_sub_matchdata]", block.binding) 267 | ensure 268 | Thread.current[:pathname_sub_matchdata] = old 269 | end 270 | yield(*args) 271 | } 272 | else 273 | path = @path.sub(pattern, *rest) 274 | end 275 | self.class.new(path) 276 | end 277 | 278 | if File::ALT_SEPARATOR 279 | SEPARATOR_LIST = "#{Regexp.quote File::ALT_SEPARATOR}#{Regexp.quote File::SEPARATOR}" 280 | SEPARATOR_PAT = /[#{SEPARATOR_LIST}]/ 281 | else 282 | SEPARATOR_LIST = "#{Regexp.quote File::SEPARATOR}" 283 | SEPARATOR_PAT = /#{Regexp.quote File::SEPARATOR}/ 284 | end 285 | 286 | # Return a pathname which the extension of the basename is substituted by 287 | # repl. 288 | # 289 | # If self has no extension part, repl is appended. 290 | def sub_ext(repl) 291 | ext = File.extname(@path) 292 | self.class.new(@path.chomp(ext) + repl) 293 | end 294 | 295 | # chop_basename(path) -> [pre-basename, basename] or nil 296 | def chop_basename(path) 297 | base = File.basename(path) 298 | if /\A#{SEPARATOR_PAT}?\z/o =~ base 299 | return nil 300 | else 301 | return path[0, path.rindex(base)], base 302 | end 303 | end 304 | private :chop_basename 305 | 306 | # split_names(path) -> prefix, [name, ...] 307 | def split_names(path) 308 | names = [] 309 | while r = chop_basename(path) 310 | path, basename = r 311 | names.unshift basename 312 | end 313 | return path, names 314 | end 315 | private :split_names 316 | 317 | def prepend_prefix(prefix, relpath) 318 | if relpath.empty? 319 | File.dirname(prefix) 320 | elsif /#{SEPARATOR_PAT}/o =~ prefix 321 | prefix = File.dirname(prefix) 322 | prefix = File.join(prefix, "") if File.basename(prefix + 'a') != 'a' 323 | prefix + relpath 324 | else 325 | prefix + relpath 326 | end 327 | end 328 | private :prepend_prefix 329 | 330 | # Returns clean pathname of +self+ with consecutive slashes and useless dots 331 | # removed. The filesystem is not accessed. 332 | # 333 | # If +consider_symlink+ is +true+, then a more conservative algorithm is used 334 | # to avoid breaking symbolic linkages. This may retain more .. 335 | # entries than absolutely necessary, but without accessing the filesystem, 336 | # this can't be avoided. See #realpath. 337 | # 338 | def cleanpath(consider_symlink=false) 339 | if consider_symlink 340 | cleanpath_conservative 341 | else 342 | cleanpath_aggressive 343 | end 344 | end 345 | 346 | # 347 | # Clean the path simply by resolving and removing excess "." and ".." entries. 348 | # Nothing more, nothing less. 349 | # 350 | def cleanpath_aggressive 351 | path = @path 352 | names = [] 353 | pre = path 354 | while r = chop_basename(pre) 355 | pre, base = r 356 | case base 357 | when '.' 358 | when '..' 359 | names.unshift base 360 | else 361 | if names[0] == '..' 362 | names.shift 363 | else 364 | names.unshift base 365 | end 366 | end 367 | end 368 | if /#{SEPARATOR_PAT}/o =~ File.basename(pre) 369 | names.shift while names[0] == '..' 370 | end 371 | self.class.new(prepend_prefix(pre, File.join(*names))) 372 | end 373 | private :cleanpath_aggressive 374 | 375 | # has_trailing_separator?(path) -> bool 376 | def has_trailing_separator?(path) 377 | if r = chop_basename(path) 378 | pre, basename = r 379 | pre.length + basename.length < path.length 380 | else 381 | false 382 | end 383 | end 384 | private :has_trailing_separator? 385 | 386 | # add_trailing_separator(path) -> path 387 | def add_trailing_separator(path) 388 | if File.basename(path + 'a') == 'a' 389 | path 390 | else 391 | File.join(path, "") # xxx: Is File.join is appropriate to add separator? 392 | end 393 | end 394 | private :add_trailing_separator 395 | 396 | def del_trailing_separator(path) 397 | if r = chop_basename(path) 398 | pre, basename = r 399 | pre + basename 400 | elsif /#{SEPARATOR_PAT}+\z/o =~ path 401 | $` + File.dirname(path)[/#{SEPARATOR_PAT}*\z/o] 402 | else 403 | path 404 | end 405 | end 406 | private :del_trailing_separator 407 | 408 | def cleanpath_conservative 409 | path = @path 410 | names = [] 411 | pre = path 412 | while r = chop_basename(pre) 413 | pre, base = r 414 | names.unshift base if base != '.' 415 | end 416 | if /#{SEPARATOR_PAT}/o =~ File.basename(pre) 417 | names.shift while names[0] == '..' 418 | end 419 | if names.empty? 420 | self.class.new(File.dirname(pre)) 421 | else 422 | if names.last != '..' && File.basename(path) == '.' 423 | names << '.' 424 | end 425 | result = prepend_prefix(pre, File.join(*names)) 426 | if /\A(?:\.|\.\.)\z/ !~ names.last && has_trailing_separator?(path) 427 | self.class.new(add_trailing_separator(result)) 428 | else 429 | self.class.new(result) 430 | end 431 | end 432 | end 433 | private :cleanpath_conservative 434 | 435 | def realpath_rec(prefix, unresolved, h, strict, last = true) 436 | resolved = [] 437 | until unresolved.empty? 438 | n = unresolved.shift 439 | if n == '.' 440 | next 441 | elsif n == '..' 442 | resolved.pop 443 | else 444 | path = prepend_prefix(prefix, File.join(*(resolved + [n]))) 445 | if h.include? path 446 | if h[path] == :resolving 447 | raise Errno::ELOOP.new(path) 448 | else 449 | prefix, *resolved = h[path] 450 | end 451 | else 452 | begin 453 | s = File.lstat(path) 454 | rescue Errno::ENOENT => e 455 | raise e if strict || !last || !unresolved.empty? 456 | resolved << n 457 | break 458 | end 459 | if s.symlink? 460 | h[path] = :resolving 461 | link_prefix, link_names = split_names(File.readlink(path)) 462 | if link_prefix == '' 463 | prefix, *resolved = h[path] = realpath_rec(prefix, resolved + link_names, h, strict, unresolved.empty?) 464 | else 465 | prefix, *resolved = h[path] = realpath_rec(link_prefix, link_names, h, strict, unresolved.empty?) 466 | end 467 | else 468 | resolved << n 469 | h[path] = [prefix, *resolved] 470 | end 471 | end 472 | end 473 | end 474 | return prefix, *resolved 475 | end 476 | private :realpath_rec 477 | 478 | def real_path_internal(strict = false) 479 | path = @path 480 | prefix, names = split_names(path) 481 | if prefix == '' 482 | prefix, names2 = split_names(Dir.pwd) 483 | names = names2 + names 484 | end 485 | prefix, *names = realpath_rec(prefix, names, {}, strict) 486 | self.class.new(prepend_prefix(prefix, File.join(*names))) 487 | end 488 | private :real_path_internal 489 | 490 | # 491 | # Returns the real (absolute) pathname of +self+ in the actual 492 | # filesystem not containing symlinks or useless dots. 493 | # 494 | # All components of the pathname must exist when this method is 495 | # called. 496 | # 497 | def realpath 498 | real_path_internal(true) 499 | end 500 | 501 | # 502 | # Returns the real (absolute) pathname of +self+ in the actual filesystem. 503 | # The real pathname doesn't contain symlinks or useless dots. 504 | # 505 | # The last component of the real pathname can be nonexistent. 506 | # 507 | def realdirpath 508 | real_path_internal(false) 509 | end 510 | 511 | # #parent returns the parent directory. 512 | # 513 | # This is same as self + '..'. 514 | def parent 515 | self + '..' 516 | end 517 | 518 | # #mountpoint? returns +true+ if self points to a mountpoint. 519 | def mountpoint? 520 | begin 521 | stat1 = self.lstat 522 | stat2 = self.parent.lstat 523 | stat1.dev == stat2.dev && stat1.ino == stat2.ino || 524 | stat1.dev != stat2.dev 525 | rescue Errno::ENOENT 526 | false 527 | end 528 | end 529 | 530 | # 531 | # #root? is a predicate for root directories. I.e. it returns +true+ if the 532 | # pathname consists of consecutive slashes. 533 | # 534 | # It doesn't access actual filesystem. So it may return +false+ for some 535 | # pathnames which points to roots such as /usr/... 536 | # 537 | def root? 538 | !!(chop_basename(@path) == nil && /#{SEPARATOR_PAT}/o =~ @path) 539 | end 540 | 541 | # Predicate method for testing whether a path is absolute. 542 | # It returns +true+ if the pathname begins with a slash. 543 | def absolute? 544 | !relative? 545 | end 546 | 547 | # The opposite of #absolute? 548 | def relative? 549 | path = @path 550 | while r = chop_basename(path) 551 | path, basename = r 552 | end 553 | path == '' 554 | end 555 | 556 | # 557 | # Iterates over each component of the path. 558 | # 559 | # Pathname.new("/usr/bin/ruby").each_filename {|filename| ... } 560 | # # yields "usr", "bin", and "ruby". 561 | # 562 | def each_filename # :yield: filename 563 | return to_enum(__method__) unless block_given? 564 | prefix, names = split_names(@path) 565 | names.each {|filename| yield filename } 566 | nil 567 | end 568 | 569 | # Iterates over and yields a new Pathname object 570 | # for each element in the given path in descending order. 571 | # 572 | # Pathname.new('/path/to/some/file.rb').descend {|v| p v} 573 | # # 574 | # # 575 | # # 576 | # # 577 | # # 578 | # 579 | # Pathname.new('path/to/some/file.rb').descend {|v| p v} 580 | # # 581 | # # 582 | # # 583 | # # 584 | # 585 | # It doesn't access actual filesystem. 586 | # 587 | # This method is available since 1.8.5. 588 | # 589 | def descend 590 | vs = [] 591 | ascend {|v| vs << v } 592 | vs.reverse_each {|v| yield v } 593 | nil 594 | end 595 | 596 | # Iterates over and yields a new Pathname object 597 | # for each element in the given path in ascending order. 598 | # 599 | # Pathname.new('/path/to/some/file.rb').ascend {|v| p v} 600 | # # 601 | # # 602 | # # 603 | # # 604 | # # 605 | # 606 | # Pathname.new('path/to/some/file.rb').ascend {|v| p v} 607 | # # 608 | # # 609 | # # 610 | # # 611 | # 612 | # It doesn't access actual filesystem. 613 | # 614 | # This method is available since 1.8.5. 615 | # 616 | def ascend 617 | path = @path 618 | yield self 619 | while r = chop_basename(path) 620 | path, name = r 621 | break if path.empty? 622 | yield self.class.new(del_trailing_separator(path)) 623 | end 624 | end 625 | 626 | # 627 | # Pathname#+ appends a pathname fragment to this one to produce a new Pathname 628 | # object. 629 | # 630 | # p1 = Pathname.new("/usr") # Pathname:/usr 631 | # p2 = p1 + "bin/ruby" # Pathname:/usr/bin/ruby 632 | # p3 = p1 + "/etc/passwd" # Pathname:/etc/passwd 633 | # 634 | # This method doesn't access the file system; it is pure string manipulation. 635 | # 636 | def +(other) 637 | other = Pathname.new(other) unless Pathname === other 638 | Pathname.new(plus(@path, other.to_s)) 639 | end 640 | 641 | def plus(path1, path2) # -> path 642 | prefix2 = path2 643 | index_list2 = [] 644 | basename_list2 = [] 645 | while r2 = chop_basename(prefix2) 646 | prefix2, basename2 = r2 647 | index_list2.unshift prefix2.length 648 | basename_list2.unshift basename2 649 | end 650 | return path2 if prefix2 != '' 651 | prefix1 = path1 652 | while true 653 | while !basename_list2.empty? && basename_list2.first == '.' 654 | index_list2.shift 655 | basename_list2.shift 656 | end 657 | break unless r1 = chop_basename(prefix1) 658 | prefix1, basename1 = r1 659 | next if basename1 == '.' 660 | if basename1 == '..' || basename_list2.empty? || basename_list2.first != '..' 661 | prefix1 = prefix1 + basename1 662 | break 663 | end 664 | index_list2.shift 665 | basename_list2.shift 666 | end 667 | r1 = chop_basename(prefix1) 668 | if !r1 && /#{SEPARATOR_PAT}/o =~ File.basename(prefix1) 669 | while !basename_list2.empty? && basename_list2.first == '..' 670 | index_list2.shift 671 | basename_list2.shift 672 | end 673 | end 674 | if !basename_list2.empty? 675 | suffix2 = path2[index_list2.first..-1] 676 | r1 ? File.join(prefix1, suffix2) : prefix1 + suffix2 677 | else 678 | r1 ? prefix1 : File.dirname(prefix1) 679 | end 680 | end 681 | private :plus 682 | 683 | # 684 | # Pathname#join joins pathnames. 685 | # 686 | # path0.join(path1, ..., pathN) is the same as 687 | # path0 + path1 + ... + pathN. 688 | # 689 | def join(*args) 690 | args.unshift self 691 | result = args.pop 692 | result = Pathname.new(result) unless Pathname === result 693 | return result if result.absolute? 694 | args.reverse_each {|arg| 695 | arg = Pathname.new(arg) unless Pathname === arg 696 | result = arg + result 697 | return result if result.absolute? 698 | } 699 | result 700 | end 701 | 702 | # 703 | # Returns the children of the directory (files and subdirectories, not 704 | # recursive) as an array of Pathname objects. By default, the returned 705 | # pathnames will have enough information to access the files. If you set 706 | # +with_directory+ to +false+, then the returned pathnames will contain the 707 | # filename only. 708 | # 709 | # For example: 710 | # pn = Pathname("/usr/lib/ruby/1.8") 711 | # pn.children 712 | # # -> [ Pathname:/usr/lib/ruby/1.8/English.rb, 713 | # Pathname:/usr/lib/ruby/1.8/Env.rb, 714 | # Pathname:/usr/lib/ruby/1.8/abbrev.rb, ... ] 715 | # pn.children(false) 716 | # # -> [ Pathname:English.rb, Pathname:Env.rb, Pathname:abbrev.rb, ... ] 717 | # 718 | # Note that the result never contain the entries . and .. in 719 | # the directory because they are not children. 720 | # 721 | # This method has existed since 1.8.1. 722 | # 723 | def children(with_directory=true) 724 | with_directory = false if @path == '.' 725 | result = [] 726 | Dir.foreach(@path) {|e| 727 | next if e == '.' || e == '..' 728 | if with_directory 729 | result << self.class.new(File.join(@path, e)) 730 | else 731 | result << self.class.new(e) 732 | end 733 | } 734 | result 735 | end 736 | 737 | # Iterates over the children of the directory 738 | # (files and subdirectories, not recursive). 739 | # It yields Pathname object for each child. 740 | # By default, the yielded pathnames will have enough information to access the files. 741 | # If you set +with_directory+ to +false+, then the returned pathnames will contain the filename only. 742 | # 743 | # Pathname("/usr/local").each_child {|f| p f } 744 | # #=> # 745 | # # # 746 | # # # 747 | # # # 748 | # # # 749 | # # # 750 | # # # 751 | # # # 752 | # 753 | # Pathname("/usr/local").each_child(false) {|f| p f } 754 | # #=> # 755 | # # # 756 | # # # 757 | # # # 758 | # # # 759 | # # # 760 | # # # 761 | # # # 762 | # 763 | def each_child(with_directory=true, &b) 764 | children(with_directory).each(&b) 765 | end 766 | 767 | # 768 | # #relative_path_from returns a relative path from the argument to the 769 | # receiver. If +self+ is absolute, the argument must be absolute too. If 770 | # +self+ is relative, the argument must be relative too. 771 | # 772 | # #relative_path_from doesn't access the filesystem. It assumes no symlinks. 773 | # 774 | # ArgumentError is raised when it cannot find a relative path. 775 | # 776 | # This method has existed since 1.8.1. 777 | # 778 | def relative_path_from(base_directory) 779 | dest_directory = self.cleanpath.to_s 780 | base_directory = base_directory.cleanpath.to_s 781 | dest_prefix = dest_directory 782 | dest_names = [] 783 | while r = chop_basename(dest_prefix) 784 | dest_prefix, basename = r 785 | dest_names.unshift basename if basename != '.' 786 | end 787 | base_prefix = base_directory 788 | base_names = [] 789 | while r = chop_basename(base_prefix) 790 | base_prefix, basename = r 791 | base_names.unshift basename if basename != '.' 792 | end 793 | unless SAME_PATHS[dest_prefix, base_prefix] 794 | raise ArgumentError, "different prefix: #{dest_prefix.inspect} and #{base_directory.inspect}" 795 | end 796 | while !dest_names.empty? && 797 | !base_names.empty? && 798 | SAME_PATHS[dest_names.first, base_names.first] 799 | dest_names.shift 800 | base_names.shift 801 | end 802 | if base_names.include? '..' 803 | raise ArgumentError, "base_directory has ..: #{base_directory.inspect}" 804 | end 805 | base_names.fill('..') 806 | relpath_names = base_names + dest_names 807 | if relpath_names.empty? 808 | Pathname.new('.') 809 | else 810 | Pathname.new(File.join(*relpath_names)) 811 | end 812 | end 813 | end 814 | 815 | class Pathname # * IO * 816 | # 817 | # #each_line iterates over the line in the file. It yields a String object 818 | # for each line. 819 | # 820 | # This method has existed since 1.8.1. 821 | # 822 | def each_line(*args, &block) # :yield: line 823 | IO.foreach(@path, *args, &block) 824 | end 825 | 826 | # See IO.read. Returns all data from the file, or the first +N+ bytes 827 | # if specified. 828 | def read(*args) IO.read(@path, *args) end 829 | 830 | # See IO.binread. Returns all the bytes from the file, or the first +N+ 831 | # if specified. 832 | def binread(*args) IO.binread(@path, *args) end 833 | 834 | # See IO.readlines. Returns all the lines from the file. 835 | def readlines(*args) IO.readlines(@path, *args) end 836 | 837 | # See IO.sysopen. 838 | def sysopen(*args) IO.sysopen(@path, *args) end 839 | end 840 | 841 | 842 | class Pathname # * File * 843 | 844 | # See File.atime. Returns last access time. 845 | def atime() File.atime(@path) end 846 | 847 | # See File.ctime. Returns last (directory entry, not file) change time. 848 | def ctime() File.ctime(@path) end 849 | 850 | # See File.mtime. Returns last modification time. 851 | def mtime() File.mtime(@path) end 852 | 853 | # See File.chmod. Changes permissions. 854 | def chmod(mode) File.chmod(mode, @path) end 855 | 856 | # See File.lchmod. 857 | def lchmod(mode) File.lchmod(mode, @path) end 858 | 859 | # See File.chown. Change owner and group of file. 860 | def chown(owner, group) File.chown(owner, group, @path) end 861 | 862 | # See File.lchown. 863 | def lchown(owner, group) File.lchown(owner, group, @path) end 864 | 865 | # See File.fnmatch. Return +true+ if the receiver matches the given 866 | # pattern. 867 | def fnmatch(pattern, *args) File.fnmatch(pattern, @path, *args) end 868 | 869 | # See File.fnmatch? (same as #fnmatch). 870 | def fnmatch?(pattern, *args) File.fnmatch?(pattern, @path, *args) end 871 | 872 | # See File.ftype. Returns "type" of file ("file", "directory", 873 | # etc). 874 | def ftype() File.ftype(@path) end 875 | 876 | # See File.link. Creates a hard link. 877 | def make_link(old) File.link(old, @path) end 878 | 879 | # See File.open. Opens the file for reading or writing. 880 | def open(*args, &block) # :yield: file 881 | File.open(@path, *args, &block) 882 | end 883 | 884 | # See File.readlink. Read symbolic link. 885 | def readlink() self.class.new(File.readlink(@path)) end 886 | 887 | # See File.rename. Rename the file. 888 | def rename(to) File.rename(@path, to) end 889 | 890 | # See File.stat. Returns a File::Stat object. 891 | def stat() File.stat(@path) end 892 | 893 | # See File.lstat. 894 | def lstat() File.lstat(@path) end 895 | 896 | # See File.symlink. Creates a symbolic link. 897 | def make_symlink(old) File.symlink(old, @path) end 898 | 899 | # See File.truncate. Truncate the file to +length+ bytes. 900 | def truncate(length) File.truncate(@path, length) end 901 | 902 | # See File.utime. Update the access and modification times. 903 | def utime(atime, mtime) File.utime(atime, mtime, @path) end 904 | 905 | # See File.basename. Returns the last component of the path. 906 | def basename(*args) self.class.new(File.basename(@path, *args)) end 907 | 908 | # See File.dirname. Returns all but the last component of the path. 909 | def dirname() self.class.new(File.dirname(@path)) end 910 | 911 | # See File.extname. Returns the file's extension. 912 | def extname() File.extname(@path) end 913 | 914 | # See File.expand_path. 915 | def expand_path(*args) self.class.new(File.expand_path(@path, *args)) end 916 | 917 | # See File.split. Returns the #dirname and the #basename in an 918 | # Array. 919 | def split() File.split(@path).map {|f| self.class.new(f) } end 920 | end 921 | 922 | 923 | class Pathname # * FileTest * 924 | 925 | # See FileTest.blockdev?. 926 | def blockdev?() FileTest.blockdev?(@path) end 927 | 928 | # See FileTest.chardev?. 929 | def chardev?() FileTest.chardev?(@path) end 930 | 931 | # See FileTest.executable?. 932 | def executable?() FileTest.executable?(@path) end 933 | 934 | # See FileTest.executable_real?. 935 | def executable_real?() FileTest.executable_real?(@path) end 936 | 937 | # See FileTest.exist?. 938 | def exist?() FileTest.exist?(@path) end 939 | 940 | # See FileTest.grpowned?. 941 | def grpowned?() FileTest.grpowned?(@path) end 942 | 943 | # See FileTest.directory?. 944 | def directory?() FileTest.directory?(@path) end 945 | 946 | # See FileTest.file?. 947 | def file?() FileTest.file?(@path) end 948 | 949 | # See FileTest.pipe?. 950 | def pipe?() FileTest.pipe?(@path) end 951 | 952 | # See FileTest.socket?. 953 | def socket?() FileTest.socket?(@path) end 954 | 955 | # See FileTest.owned?. 956 | def owned?() FileTest.owned?(@path) end 957 | 958 | # See FileTest.readable?. 959 | def readable?() FileTest.readable?(@path) end 960 | 961 | # See FileTest.world_readable?. 962 | def world_readable?() FileTest.world_readable?(@path) end 963 | 964 | # See FileTest.readable_real?. 965 | def readable_real?() FileTest.readable_real?(@path) end 966 | 967 | # See FileTest.setuid?. 968 | def setuid?() FileTest.setuid?(@path) end 969 | 970 | # See FileTest.setgid?. 971 | def setgid?() FileTest.setgid?(@path) end 972 | 973 | # See FileTest.size. 974 | def size() FileTest.size(@path) end 975 | 976 | # See FileTest.size?. 977 | def size?() FileTest.size?(@path) end 978 | 979 | # See FileTest.sticky?. 980 | def sticky?() FileTest.sticky?(@path) end 981 | 982 | # See FileTest.symlink?. 983 | def symlink?() FileTest.symlink?(@path) end 984 | 985 | # See FileTest.writable?. 986 | def writable?() FileTest.writable?(@path) end 987 | 988 | # See FileTest.world_writable?. 989 | def world_writable?() FileTest.world_writable?(@path) end 990 | 991 | # See FileTest.writable_real?. 992 | def writable_real?() FileTest.writable_real?(@path) end 993 | 994 | # See FileTest.zero?. 995 | def zero?() FileTest.zero?(@path) end 996 | end 997 | 998 | 999 | class Pathname # * Dir * 1000 | # See Dir.glob. Returns or yields Pathname objects. 1001 | def Pathname.glob(*args) # :yield: pathname 1002 | if block_given? 1003 | Dir.glob(*args) {|f| yield self.new(f) } 1004 | else 1005 | Dir.glob(*args).map {|f| self.new(f) } 1006 | end 1007 | end 1008 | 1009 | # See Dir.getwd. Returns the current working directory as a Pathname. 1010 | def Pathname.getwd() self.new(Dir.getwd) end 1011 | class << self; alias pwd getwd end 1012 | 1013 | # Return the entries (files and subdirectories) in the directory, each as a 1014 | # Pathname object. 1015 | def entries() Dir.entries(@path).map {|f| self.class.new(f) } end 1016 | 1017 | # Iterates over the entries (files and subdirectories) in the directory. It 1018 | # yields a Pathname object for each entry. 1019 | # 1020 | # This method has existed since 1.8.1. 1021 | def each_entry(&block) # :yield: pathname 1022 | Dir.foreach(@path) {|f| yield self.class.new(f) } 1023 | end 1024 | 1025 | # See Dir.mkdir. Create the referenced directory. 1026 | def mkdir(*args) Dir.mkdir(@path, *args) end 1027 | 1028 | # See Dir.rmdir. Remove the referenced directory. 1029 | def rmdir() Dir.rmdir(@path) end 1030 | 1031 | # See Dir.open. 1032 | def opendir(&block) # :yield: dir 1033 | Dir.open(@path, &block) 1034 | end 1035 | end 1036 | 1037 | 1038 | class Pathname # * Find * 1039 | # 1040 | # Pathname#find is an iterator to traverse a directory tree in a depth first 1041 | # manner. It yields a Pathname for each file under "this" directory. 1042 | # 1043 | # Since it is implemented by find.rb, Find.prune can be used 1044 | # to control the traverse. 1045 | # 1046 | # If +self+ is ., yielded pathnames begin with a filename in the 1047 | # current directory, not ./. 1048 | # 1049 | def find(&block) # :yield: pathname 1050 | require 'find' 1051 | if @path == '.' 1052 | Find.find(@path) {|f| yield self.class.new(f.sub(%r{\A\./}, '')) } 1053 | else 1054 | Find.find(@path) {|f| yield self.class.new(f) } 1055 | end 1056 | end 1057 | end 1058 | 1059 | 1060 | class Pathname # * FileUtils * 1061 | # See FileUtils.mkpath. Creates a full path, including any 1062 | # intermediate directories that don't yet exist. 1063 | def mkpath 1064 | require 'fileutils' 1065 | FileUtils.mkpath(@path) 1066 | nil 1067 | end 1068 | 1069 | # See FileUtils.rm_r. Deletes a directory and all beneath it. 1070 | def rmtree 1071 | # The name "rmtree" is borrowed from File::Path of Perl. 1072 | # File::Path provides "mkpath" and "rmtree". 1073 | require 'fileutils' 1074 | FileUtils.rm_r(@path) 1075 | nil 1076 | end 1077 | end 1078 | 1079 | 1080 | class Pathname # * mixed * 1081 | # Removes a file or directory, using File.unlink or 1082 | # Dir.unlink as necessary. 1083 | def unlink() 1084 | begin 1085 | Dir.unlink @path 1086 | rescue Errno::ENOTDIR 1087 | File.unlink @path 1088 | end 1089 | end 1090 | alias delete unlink 1091 | end 1092 | 1093 | class Pathname 1094 | undef =~ 1095 | end 1096 | 1097 | module Kernel 1098 | # create a pathname object. 1099 | # 1100 | # This method is available since 1.8.5. 1101 | def Pathname(path) # :doc: 1102 | Pathname.new(path) 1103 | end 1104 | private :Pathname 1105 | end 1106 | 1107 | end 1108 | -------------------------------------------------------------------------------- /work/deprecated/name.rb: -------------------------------------------------------------------------------- 1 | #-- 2 | # = path/name.rb 3 | # 4 | # Object-Oriented Pathname Class 5 | # 6 | # Author:: Tanaka Akira 7 | # Documentation:: Author and Gavin Sinclair 8 | # 9 | # For documentation, see class Pathname. 10 | # 11 | # pathname.rb is distributed with Ruby since 1.8.0. 12 | # 13 | # THIS IS AN IMPROVED VERSION THAT USES AN ARRAY INSTEAD OF A STRING FOR PATH. 14 | # 15 | # A modified version of Pathname that uses an array internal rather then a string. 16 | # This gave ~20% performance boot. Unfortunately no one cared. 17 | #++ 18 | 19 | module Path 20 | require 'path/list' 21 | 22 | # == Path::Name 23 | # 24 | # Path::Name represents a pathname which locates a file in a filesystem. 25 | # It supports only Unix style pathnames. It does not represent the file 26 | # itself. A Path::Name can be relative or absolute. It's not until you try to 27 | # reference the file that it even matters whether the file exists or not. 28 | # 29 | # Path::Name is immutable. It has no method for destructive update. 30 | # 31 | # The value of this class is to manipulate file path information in a neater 32 | # way than standard Ruby provides. The examples below demonstrate the 33 | # difference. *All* functionality from File, FileTest, and some from Dir and 34 | # FileUtils is included, in an unsurprising way. It is essentially a facade for 35 | # all of these, and more. 36 | # 37 | # == Examples 38 | # 39 | # === Example 1: Using Path::Name 40 | # 41 | # require 'pathname' 42 | # p = Path::Name.new("/usr/bin/ruby") 43 | # size = p.size # 27662 44 | # isdir = p.directory? # false 45 | # dir = p.dirname # Path::Name:/usr/bin 46 | # base = p.basename # Path::Name:ruby 47 | # dir, base = p.split # [Path::Name:/usr/bin, Path::Name:ruby] 48 | # data = p.read 49 | # p.open { |f| _ } 50 | # p.each_line { |line| _ } 51 | # 52 | # === Example 2: Using standard Ruby 53 | # 54 | # p = "/usr/bin/ruby" 55 | # size = File.size(p) # 27662 56 | # isdir = File.directory?(p) # false 57 | # dir = File.dirname(p) # "/usr/bin" 58 | # base = File.basename(p) # "ruby" 59 | # dir, base = File.split(p) # ["/usr/bin", "ruby"] 60 | # data = File.read(p) 61 | # File.open(p) { |f| _ } 62 | # File.foreach(p) { |line| _ } 63 | # 64 | # === Example 3: Special features 65 | # 66 | # p1 = Path::Name.new("/usr/lib") # Path::Name:/usr/lib 67 | # p2 = p1 + "ruby/1.8" # Path::Name:/usr/lib/ruby/1.8 68 | # p3 = p1.parent # Path::Name:/usr 69 | # p4 = p2.relative_path_from(p3) # Path::Name:lib/ruby/1.8 70 | # pwd = Path::Name.pwd # Path::Name:/home/gavin 71 | # pwd.absolute? # true 72 | # p5 = Path::Name.new "." # Path::Name:. 73 | # p5 = p5 + "music/../articles" # Path::Name:music/../articles 74 | # p5.cleanpath # Path::Name:articles 75 | # p5.realpath # Path::Name:/home/gavin/articles 76 | # p5.children # [Path::Name:/home/gavin/articles/linux, ...] 77 | # 78 | # == Breakdown of functionality 79 | # 80 | # === Core methods 81 | # 82 | # These methods are effectively manipulating a String, because that's all a path 83 | # is. Except for #mountpoint?, #children, and #realpath, they don't access the 84 | # filesystem. 85 | # 86 | # - + 87 | # - #join 88 | # - #parent 89 | # - #root? 90 | # - #absolute? 91 | # - #relative? 92 | # - #relative_path_from 93 | # - #each_filename 94 | # - #cleanpath 95 | # - #realpath 96 | # - #children 97 | # - #mountpoint? 98 | # 99 | # === File status predicate methods 100 | # 101 | # These methods are a facade for FileTest: 102 | # - #blockdev? 103 | # - #chardev? 104 | # - #directory? 105 | # - #executable? 106 | # - #executable_real? 107 | # - #exist? 108 | # - #file? 109 | # - #grpowned? 110 | # - #owned? 111 | # - #pipe? 112 | # - #readable? 113 | # - #readable_real? 114 | # - #setgid? 115 | # - #setuid? 116 | # - #size 117 | # - #size? 118 | # - #socket? 119 | # - #sticky? 120 | # - #symlink? 121 | # - #writable? 122 | # - #writable_real? 123 | # - #zero? 124 | # 125 | # === File property and manipulation methods 126 | # 127 | # These methods are a facade for File: 128 | # - #atime 129 | # - #ctime 130 | # - #mtime 131 | # - #chmod(mode) 132 | # - #lchmod(mode) 133 | # - #chown(owner, group) 134 | # - #lchown(owner, group) 135 | # - #fnmatch(pattern, *args) 136 | # - #fnmatch?(pattern, *args) 137 | # - #ftype 138 | # - #make_link(old) 139 | # - #open(*args, &block) 140 | # - #readlink 141 | # - #rename(to) 142 | # - #stat 143 | # - #lstat 144 | # - #make_symlink(old) 145 | # - #truncate(length) 146 | # - #utime(atime, mtime) 147 | # - #basename(*args) 148 | # - #dirname 149 | # - #extname 150 | # - #expand_path(*args) 151 | # - #split 152 | # 153 | # === Directory methods 154 | # 155 | # These methods are a facade for Dir: 156 | # - Path::Name.glob(*args) 157 | # - Path::Name.getwd / Path::Name.pwd 158 | # - #rmdir 159 | # - #entries 160 | # - #each_entry(&block) 161 | # - #mkdir(*args) 162 | # - #opendir(*args) 163 | # 164 | # === IO 165 | # 166 | # These methods are a facade for IO: 167 | # - #each_line(*args, &block) 168 | # - #read(*args) 169 | # - #readlines(*args) 170 | # - #sysopen(*args) 171 | # 172 | # === Utilities 173 | # 174 | # These methods are a mixture of Find, FileUtils, and others: 175 | # - #find(&block) 176 | # - #mkpath 177 | # - #rmtree 178 | # - #unlink / #delete 179 | # 180 | # 181 | # == Method documentation 182 | # 183 | # As the above section shows, most of the methods in Path::Name are facades. The 184 | # documentation for these methods generally just says, for instance, "See 185 | # FileTest.writable?", as you should be familiar with the original method 186 | # anyway, and its documentation (e.g. through +ri+) will contain more 187 | # information. In some cases, a brief description will follow. 188 | # 189 | class Name 190 | 191 | class << self 192 | 193 | def create( path=[], abs=false, trail=false ) 194 | o = self.allocate 195 | o.instance_variable_set("@path", path.dup) 196 | o.instance_variable_set("@absolute", abs ? true : false) 197 | o.instance_variable_set("@trail", trail ? true : false) 198 | o 199 | end 200 | 201 | def [](path) 202 | new(path) 203 | end 204 | end 205 | 206 | # Create a Path::Name object from the given String (or String-like object). 207 | # If +path+ contains a NUL character (\0), an ArgumentError is raised. 208 | # 209 | def initialize(*path) 210 | path = path.join('/') 211 | @absolute = path[0,1] == '/' ? true : false 212 | @trail = (path.size > 1 and path[-1,1] == '/') ? true : false 213 | @path = path.split(%r{/}) 214 | @path.delete('') 215 | 216 | #case path 217 | #when Path::Name 218 | # @path = path.pathlist.dup 219 | # @absolute = path.absolute? 220 | #when Array 221 | # @path = path.collect{|e| e.to_str} 222 | # @absolute = absolute 223 | #else 224 | # path = path.to_str if path.respond_to?(:to_str) 225 | # raise ArgumentError, "pathname contains \\0: #{@path.inspect}" if /\0/ =~ @path 226 | # @path = path.split(%r{/}) 227 | # @path.unshift('') if path[0,1] == '/' # absolute path 228 | #end 229 | 230 | self.taint if path.tainted? 231 | end 232 | 233 | def freeze() super ; @path.freeze ; self end 234 | def taint() super ; @path.taint ; self end 235 | def untaint() super ; @path.untaint ; self end 236 | 237 | # Convertion method for path names. This returns self. 238 | def to_path 239 | self 240 | end 241 | 242 | # 243 | def [](*globs) 244 | Path::List.new(self)[*globs] 245 | end 246 | 247 | # Stores the array of the componet parts of the pathname. 248 | # 249 | # protected 250 | def pathlist 251 | @path 252 | end 253 | 254 | # Compare this pathname with +other+. The comparison is string-based. 255 | # Be aware that two different paths (foo.txt and ./foo.txt) 256 | # can refer to the same file. 257 | # 258 | def ==(other) 259 | return false unless Path::Name === other 260 | if @absolute 261 | return false unless other.absolute? 262 | end 263 | @path == other.pathlist 264 | end 265 | alias_method :===, :== 266 | alias_method :eql?, :== 267 | 268 | # Provides for comparing pathnames, case-sensitively. 269 | def <=>(other) 270 | return nil unless Path::Name === other 271 | return 1 if absolute? and not other.absolute? 272 | return -1 if other.absolute? and not absolute? 273 | r = @path <=> other.pathlist 274 | return r unless r == 0 275 | return 1 if trail? and not other.trail? 276 | return -1 if other.trail? and not trail? 277 | 0 278 | end 279 | 280 | def hash # :nodoc: 281 | to_s.hash 282 | end 283 | 284 | # Return the path as a String (same as to_s). 285 | def path 286 | s = '' 287 | s << '/' if @absolute 288 | s << @path.join('/') 289 | s << '/' if @trail 290 | s << '.' if s.empty? 291 | s 292 | end 293 | 294 | # Return the path as a String (same as pathname). 295 | def to_s 296 | s = '' 297 | s << '/' if @absolute 298 | s << @path.join('/') 299 | s << '/' if @trail 300 | s << '.' if s.empty? 301 | s 302 | end 303 | 304 | # to_str is implemented so Path::Name objects are usable with File.open, etc. 305 | alias_method :to_str, :to_s 306 | 307 | def inspect # :nodoc: 308 | "#<#{self.class}: #{to_s}>" 309 | end 310 | 311 | # append directory or file name to path 312 | def <<(name) 313 | name = name.to_str 314 | @path << name unless name.strip.empty? 315 | @path 316 | end 317 | 318 | # 319 | # Returns clean pathname of +self+ with consecutive slashes and useless dots 320 | # removed. The filesystem is not accessed. 321 | # 322 | # If +consider_symlink+ is +true+, then a more conservative algorithm is used 323 | # to avoid breaking symbolic linkages. This may retain more .. 324 | # entries than absolutely necessary, but without accessing the filesystem, 325 | # this can't be avoided. See #realpath. 326 | # 327 | def cleanpath(consider_symlink=false) 328 | if consider_symlink 329 | cleanpath_conservative 330 | else 331 | cleanpath_aggressive 332 | end 333 | end 334 | 335 | # Clean the path simply by resolving and removing excess "." and ".." entries. 336 | # Nothing more, nothing less. 337 | # 338 | def cleanpath_aggressive 339 | # cleanpath_aggressive assumes: 340 | # * no symlink 341 | # * all pathname prefix contained in the pathname is existing directory 342 | return Path::Name.create([],@absolute,@trail) if path.empty? 343 | absolute = absolute? 344 | trail = trail? 345 | names = [] 346 | @path.each {|name| 347 | next if name == '.' 348 | if name == '..' 349 | if names.empty? 350 | next if absolute 351 | else 352 | if names.last != '..' 353 | names.pop 354 | next 355 | end 356 | end 357 | end 358 | names << name 359 | } 360 | return Path::Name.new(absolute ? '/' : '.') if names.empty? 361 | #path = [] 362 | #path << '' if absolute 363 | #path.concat(names) 364 | Path::Name.create(names, absolute) #, trail) 365 | end 366 | private :cleanpath_aggressive 367 | 368 | def cleanpath_conservative 369 | return Path::Name.create([],@absolute) if @path.empty? 370 | names = @path.dup #.scan(%r{[^/]+}) 371 | last_dot = (names.last == '.') 372 | names.delete('.') 373 | names.shift while names.first == '..' if absolute? 374 | return Path::Name.new(absolute? ? '/' : '.') if names.empty? 375 | newpath = [] 376 | trail = false 377 | #newpath << '' if absolute? 378 | newpath.concat(names) 379 | if names.last != '..' 380 | if last_dot 381 | newpath << '.' 382 | elsif trail? #%r{/\z} =~ @path #HOW TO DEAL WITH TRAILING '/' ? 383 | trail = true 384 | end 385 | end 386 | Path::Name.create(newpath, absolute?, trail) 387 | end 388 | private :cleanpath_conservative 389 | 390 | # 391 | # Returns a real (absolute) pathname of +self+ in the actual filesystem. 392 | # The real pathname doesn't contain symlinks or useless dots. 393 | # 394 | # No arguments should be given; the old behaviour is *obsoleted*. 395 | # 396 | def realpath(*args) 397 | unless args.empty? 398 | warn "The argument for Path::Name#realpath is obsoleted." 399 | end 400 | force_absolute = args.fetch(0, true) 401 | 402 | if @path.first == '' 403 | top = '/' 404 | unresolved = @path.slice(1..-1) #.scan(%r{[^/]+}) 405 | elsif force_absolute 406 | # Although POSIX getcwd returns a pathname which contains no symlink, 407 | # 4.4BSD-Lite2 derived getcwd may return the environment variable $PWD 408 | # which may contain a symlink. 409 | # So the return value of Dir.pwd should be examined. 410 | top = '/' 411 | unresolved = Dir.pwd.split(%r{/}) + @path 412 | else 413 | top = '' 414 | unresolved = @path.dup #.scan(%r{[^/]+}) 415 | end 416 | resolved = [] 417 | 418 | until unresolved.empty? 419 | case unresolved.last 420 | when '.' 421 | unresolved.pop 422 | when '..' 423 | resolved.unshift unresolved.pop 424 | else 425 | loop_check = {} 426 | while (stat = File.lstat(path = top + unresolved.join('/'))).symlink? 427 | symlink_id = "#{stat.dev}:#{stat.ino}" 428 | raise Errno::ELOOP.new(path) if loop_check[symlink_id] 429 | loop_check[symlink_id] = true 430 | if %r{\A/} =~ (link = File.readlink(path)) 431 | top = '/' 432 | unresolved = link.split(%r{/}) #scan(%r{[^/]+}) 433 | else 434 | unresolved[-1,1] = link.split(%r{/}) #.scan(%r{[^/]+}) 435 | end 436 | end 437 | next if (filename = unresolved.pop) == '.' 438 | if filename != '..' && resolved.first == '..' 439 | resolved.shift 440 | else 441 | resolved.unshift filename 442 | end 443 | end 444 | end 445 | 446 | if top == '/' 447 | resolved.shift while resolved[0] == '..' 448 | end 449 | 450 | if resolved.empty? 451 | Path::Name.new(top.empty? ? '.' : '/') 452 | else 453 | if top.empty? 454 | Path::Name.new(resolved) 455 | else 456 | Path::Name.new(resolved, true) 457 | end 458 | end 459 | end 460 | 461 | # #parent returns the parent directory. 462 | # 463 | # This is same as self + '..'. 464 | def parent 465 | self << '..' 466 | end 467 | 468 | # #mountpoint? returns +true+ if self points to a mountpoint. 469 | def mountpoint? 470 | begin 471 | stat1 = self.lstat 472 | stat2 = self.parent.lstat 473 | stat1.dev == stat2.dev && stat1.ino == stat2.ino || 474 | stat1.dev != stat2.dev 475 | rescue Errno::ENOENT 476 | false 477 | end 478 | end 479 | 480 | # 481 | # #root? is a predicate for root directories. I.e. it returns +true+ if the 482 | # pathname consists of consecutive slashes. 483 | # 484 | # It doesn't access actual filesystem. So it may return +false+ for some 485 | # pathnames which points to roots such as /usr/... 486 | # 487 | def root? 488 | #%r{\A/+\z} =~ @path ? true : false 489 | @absolute and @path.empty? 490 | end 491 | 492 | def current? 493 | #%r{\A/+\z} =~ @path ? true : false 494 | (!@absolute) and @path.empty? 495 | end 496 | 497 | # Predicate method for testing whether a path is absolute. 498 | # It returns +true+ if the pathname begins with a slash. 499 | def absolute? 500 | #%r{\A/} =~ @path ? true : false 501 | @absolute 502 | end 503 | 504 | # The opposite of #absolute? 505 | def relative? 506 | !@absolute 507 | end 508 | 509 | def trail? 510 | @trail 511 | end 512 | 513 | # 514 | # Iterates over each component of the path. 515 | # 516 | # Path::Name.new("/usr/bin/ruby").each_filename {|filename| ... } 517 | # # yields "usr", "bin", and "ruby". 518 | # 519 | def each_filename # :yield: e 520 | #@path.scan(%r{[^/]+}) { yield $& } 521 | @path.each { |e| yield e } 522 | end 523 | 524 | # 525 | # Path::Name#+ appends a pathname fragment to this one to produce a new Path::Name 526 | # object. 527 | # 528 | # p1 = Path::Name.new("/usr") # Path::Name:/usr 529 | # p2 = p1 + "bin/ruby" # Path::Name:/usr/bin/ruby 530 | # p3 = p1 + "/etc/passwd" # Path::Name:/etc/passwd 531 | # 532 | # This method doesn't access the file system; it is pure string manipulation. 533 | # 534 | def +(other) 535 | path = self.class.new(File.join(to_s, other.to_s)) 536 | path.cleanpath 537 | 538 | #other = Path::Name.new(other) unless Path::Name === other 539 | #return other if other.absolute? 540 | #pth = (@path + other.pathlist) 541 | #pth.delete('.') 542 | #Path::Name.create(pth, @absolute, other.trail?) 543 | 544 | # path1 = @path# 545 | # path2 = other.to_s 546 | # while m2 = %r{\A\.\.(?:/+|\z)}.match(path2) and 547 | # m1 = %r{(\A|/+)([^/]+)\z}.match(path1) and 548 | # %r{\A(?:\.|\.\.)\z} !~ m1[2] 549 | # path1 = m1[1].empty? ? '.' : '/' if (path1 = m1.pre_match).empty? 550 | # path2 = '.' if (path2 = m2.post_match).empty? 551 | # end 552 | # if %r{\A/+\z} =~ path1 553 | # while m2 = %r{\A\.\.(?:/+|\z)}.match(path2) 554 | # path2 = '.' if (path2 = m2.post_match).empty? 555 | # end 556 | # end 557 | # 558 | # return Path::Name.new(path2) if path1 == '.' 559 | # return Path::Name.new(path1) if path2 == '.' 560 | # 561 | # if %r{/\z} =~ path1 562 | # Path::Name.new(path1 + path2) 563 | # else 564 | # Path::Name.new(path1 + '/' + path2) 565 | # end 566 | end 567 | 568 | # 569 | alias_method :/, :+ #/ kate highlight fix 570 | 571 | # 572 | # Path::Name#join joins pathnames. 573 | # 574 | # path0.join(path1, ..., pathN) is the same as 575 | # path0 + path1 + ... + pathN. 576 | # 577 | # HELP ME!!! 578 | # 579 | def join(*args) 580 | Path::Name.new(*args) 581 | #args.unshift self 582 | #result = args.pop 583 | #result = Path::Name.new(result) unless Path::Name === result 584 | #return result if result.absolute? 585 | #args.reverse_each {|arg| 586 | # arg = Path::Name.new(arg) unless Path::Name === arg 587 | # result = arg + result 588 | # return result if result.absolute? 589 | #} 590 | #result 591 | end 592 | 593 | # 594 | # Returns the children of the directory (files and subdirectories, not 595 | # recursive) as an array of Path::Name objects. By default, the returned 596 | # pathnames will have enough information to access the files. If you set 597 | # +with_directory+ to +false+, then the returned pathnames will contain the 598 | # filename only. 599 | # 600 | # For example: 601 | # p = Path::Name("/usr/lib/ruby/1.8") 602 | # p.children 603 | # # -> [ Path::Name:/usr/lib/ruby/1.8/English.rb, 604 | # Path::Name:/usr/lib/ruby/1.8/Env.rb, 605 | # Path::Name:/usr/lib/ruby/1.8/abbrev.rb, ... ] 606 | # p.children(false) 607 | # # -> [ Path::Name:English.rb, Path::Name:Env.rb, Path::Name:abbrev.rb, ... ] 608 | # 609 | # Note that the result never contain the entries . and .. in 610 | # the directory because they are not children. 611 | # 612 | # This method has existed since 1.8.1. 613 | # 614 | def children(with_directory=true) 615 | with_directory = false if @path == ['.'] 616 | result = [] 617 | Dir.foreach(to_s) {|e| 618 | next if e == '.' || e == '..' 619 | if with_directory 620 | result << Path::Name.create(@path + [e], absolute?) 621 | else 622 | result << Path::Name.new(e) 623 | end 624 | } 625 | result 626 | end 627 | 628 | # 629 | # #relative_path_from returns a relative path from the argument to the 630 | # receiver. If +self+ is absolute, the argument must be absolute too. If 631 | # +self+ is relative, the argument must be relative too. 632 | # 633 | # #relative_path_from doesn't access the filesystem. It assumes no symlinks. 634 | # 635 | # ArgumentError is raised when it cannot find a relative path. 636 | # 637 | # This method has existed since 1.8.1. 638 | # 639 | def relative_path_from(base_directory) 640 | if self.absolute? != base_directory.absolute? 641 | raise ArgumentError, 642 | "relative path between absolute and relative path: #{self.inspect}, #{base_directory.inspect}" 643 | end 644 | 645 | dest = [] 646 | self.cleanpath.each_filename {|f| 647 | next if f == '.' 648 | dest << f 649 | } 650 | 651 | base = [] 652 | base_directory.cleanpath.each_filename {|f| 653 | next if f == '.' 654 | base << f 655 | } 656 | 657 | while !base.empty? && !dest.empty? && base[0] == dest[0] 658 | base.shift 659 | dest.shift 660 | end 661 | 662 | if base.include? '..' 663 | raise ArgumentError, "base_directory has ..: #{base_directory.inspect}" 664 | end 665 | 666 | base.fill '..' 667 | relpath = base + dest 668 | if relpath.empty? 669 | Path::Name.new('.') 670 | else 671 | Path::Name.create(relpath, false) #.join('/')) 672 | end 673 | end 674 | 675 | # 676 | def rootname 677 | # this should be fairly robust 678 | path_re = Regexp.new('[' + Regexp.escape(File::Separator + %q{\/}) + ']') 679 | head, tail = path.split(path_re, 2) 680 | return '.' if path == head 681 | return '/' if head.empty? 682 | self.class.new(head) 683 | end 684 | 685 | # Calls the _block_ for every successive parent directory of the 686 | # directory path until the root (absolute path) or +.+ (relative path) 687 | # is reached. 688 | def ascend(inclusive=false,&block) # :yield: 689 | cur_dir = self 690 | yield( cur_dir.cleanpath ) if inclusive 691 | until cur_dir.root? or cur_dir == self.class.new(".") 692 | cur_dir = cur_dir.parent 693 | yield cur_dir 694 | end 695 | end 696 | 697 | # Calls the _block_ for every successive subdirectory of the 698 | # directory path from the root (absolute path) until +.+ 699 | # (relative path) is reached. 700 | def descend() 701 | @path.scan(%r{[^/]*/?})[0...-1].inject('') do |path, dir| 702 | yield self.class.new(path << dir) 703 | path 704 | end 705 | end 706 | 707 | # 708 | def split_root 709 | path_re = Regexp.new('[' + Regexp.escape(File::Separator + %q{\/}) + ']') 710 | head, tail = *path.split(path_re, 2) 711 | [self.class.new(head), self.class.new(tail)] 712 | end 713 | end 714 | 715 | 716 | class Path::Name # * IO * 717 | # 718 | # #each_line iterates over the line in the file. It yields a String object 719 | # for each line. 720 | # 721 | # This method has existed since 1.8.1. 722 | # 723 | def each_line(*args, &block) # :yield: line 724 | IO.foreach(path, *args, &block) 725 | end 726 | 727 | # Path::Name#foreachline is *obsoleted* at 1.8.1. Use #each_line. 728 | def foreachline(*args, &block) 729 | warn "Path::Name#foreachline is obsoleted. Use Path::Name#each_line." 730 | each_line(*args, &block) 731 | end 732 | 733 | # See IO.read. Returns all the bytes from the file, or the first +N+ 734 | # if specified. 735 | def read(*args) IO.read(path, *args) end 736 | 737 | # See IO.readlines. Returns all the lines from the file. 738 | def readlines(*args) IO.readlines(path, *args) end 739 | 740 | # See IO.sysopen. 741 | def sysopen(*args) IO.sysopen(path, *args) end 742 | end 743 | 744 | 745 | class Path::Name # * File * 746 | 747 | # See File.atime. Returns last access time. 748 | def atime() File.atime(path) end 749 | 750 | # See File.ctime. Returns last (directory entry, not file) change time. 751 | def ctime() File.ctime(path) end 752 | 753 | # See File.mtime. Returns last modification time. 754 | def mtime() File.mtime(path) end 755 | 756 | # See File.chmod. Changes permissions. 757 | def chmod(mode) File.chmod(mode, path) end 758 | 759 | # See File.lchmod. 760 | def lchmod(mode) File.lchmod(mode, path) end 761 | 762 | # See File.chown. Change owner and group of file. 763 | def chown(owner, group) File.chown(owner, group, path) end 764 | 765 | # See File.lchown. 766 | def lchown(owner, group) File.lchown(owner, group, path) end 767 | 768 | # See File.fnmatch. Return +true+ if the receiver matches the given 769 | # pattern. 770 | def fnmatch(pattern, *args) File.fnmatch(pattern, path, *args) end 771 | 772 | # See File.fnmatch? (same as #fnmatch). 773 | def fnmatch?(pattern, *args) File.fnmatch?(pattern, path, *args) end 774 | 775 | # See File.ftype. Returns "type" of file ("file", "directory", 776 | # etc). 777 | def ftype() File.ftype(path) end 778 | 779 | # See File.link. Creates a hard link. 780 | def make_link(old) File.link(old, path) end 781 | 782 | # See File.open. Opens the file for reading or writing. 783 | def open(*args, &block) # :yield: file 784 | File.open(path, *args, &block) 785 | end 786 | 787 | # See File.readlink. Read symbolic link. 788 | def readlink() Path::Name.new(File.readlink(path)) end 789 | 790 | # See File.rename. Rename the file. 791 | def rename(to) File.rename(path, to) end 792 | 793 | # See File.stat. Returns a File::Stat object. 794 | def stat() File.stat(path) end 795 | 796 | # See File.lstat. 797 | def lstat() File.lstat(path) end 798 | 799 | # See File.symlink. Creates a symbolic link. 800 | def make_symlink(old) File.symlink(old, path) end 801 | 802 | # See File.truncate. Truncate the file to +length+ bytes. 803 | def truncate(length) File.truncate(path, length) end 804 | 805 | # See File.utime. Update the access and modification times. 806 | def utime(atime, mtime) File.utime(atime, mtime, path) end 807 | 808 | # See File.basename. Returns the last component of the path. 809 | def basename(*args) Path::Name.new(File.basename(path, *args)) end 810 | 811 | # See File.dirname. Returns all but the last component of the path. 812 | def dirname() Path::Name.new(File.dirname(path)) end 813 | 814 | # See File.extname. Returns the file's extension. 815 | def extname() File.extname(path) end 816 | 817 | # See File.expand_path. 818 | def expand_path(*args) Path::Name.new(File.expand_path(path, *args)) end 819 | 820 | # See File.split. Returns the #dirname and the #basename in an 821 | # Array. 822 | def split() File.split(path).map {|f| Path::Name.new(f) } end 823 | 824 | # Path::Name#link is confusing and *obsoleted* because the receiver/argument 825 | # order is inverted to corresponding system call. 826 | def link(old) 827 | warn 'Path::Name#link is obsoleted. Use Path::Name#make_link.' 828 | File.link(old, path) 829 | end 830 | 831 | # Path::Name#symlink is confusing and *obsoleted* because the receiver/argument 832 | # order is inverted to corresponding system call. 833 | def symlink(old) 834 | warn 'Path::Name#symlink is obsoleted. Use Path::Name#make_symlink.' 835 | File.symlink(old, path) 836 | end 837 | end 838 | 839 | 840 | class Path::Name # * FileTest * 841 | 842 | # See FileTest.blockdev?. 843 | def blockdev?() FileTest.blockdev?(path) end 844 | 845 | # See FileTest.chardev?. 846 | def chardev?() FileTest.chardev?(path) end 847 | 848 | # See FileTest.executable?. 849 | def executable?() FileTest.executable?(path) end 850 | 851 | # See FileTest.executable_real?. 852 | def executable_real?() FileTest.executable_real?(path) end 853 | 854 | # See FileTest.exist?. 855 | def exist?() FileTest.exist?(path) end 856 | 857 | # See FileTest.grpowned?. 858 | def grpowned?() FileTest.grpowned?(path) end 859 | 860 | # See FileTest.directory?. 861 | def directory?() FileTest.directory?(path) end 862 | 863 | # Like directory? but return self if true, otherwise nil. 864 | def dir? ; directory? ? self : nil ; end 865 | 866 | # See FileTest.file?. 867 | def file?() FileTest.file?(path) end 868 | 869 | # See FileTest.pipe?. 870 | def pipe?() FileTest.pipe?(path) end 871 | 872 | # See FileTest.socket?. 873 | def socket?() FileTest.socket?(path) end 874 | 875 | # See FileTest.owned?. 876 | def owned?() FileTest.owned?(path) end 877 | 878 | # See FileTest.readable?. 879 | def readable?() FileTest.readable?(path) end 880 | 881 | # See FileTest.readable_real?. 882 | def readable_real?() FileTest.readable_real?(path) end 883 | 884 | # See FileTest.setuid?. 885 | def setuid?() FileTest.setuid?(path) end 886 | 887 | # See FileTest.setgid?. 888 | def setgid?() FileTest.setgid?(path) end 889 | 890 | # See FileTest.size. 891 | def size() FileTest.size(path) end 892 | 893 | # See FileTest.size?. 894 | def size?() FileTest.size?(path) end 895 | 896 | # See FileTest.sticky?. 897 | def sticky?() FileTest.sticky?(path) end 898 | 899 | # See FileTest.symlink?. 900 | def symlink?() FileTest.symlink?(path) end 901 | 902 | # See FileTest.writable?. 903 | def writable?() FileTest.writable?(path) end 904 | 905 | # See FileTest.writable_real?. 906 | def writable_real?() FileTest.writable_real?(path) end 907 | 908 | # See FileTest.zero?. 909 | def zero?() FileTest.zero?(path) end 910 | end 911 | 912 | 913 | class Path::Name # * Dir * 914 | # See Dir.glob. Returns or yields Path::Name objects. 915 | def self.glob(*args) # :yield: p 916 | if block_given? 917 | Dir.glob(*args) {|f| yield Path::Name.new(f) } 918 | else 919 | Dir.glob(*args).map {|f| Path::Name.new(f) } 920 | end 921 | end 922 | 923 | # See Dir.getwd. Returns the current working directory as a Path::Name. 924 | def self.getwd() Path::Name.new(Dir.getwd) end 925 | class << self; alias pwd getwd end 926 | 927 | # Path::Name#chdir is *obsoleted* at 1.8.1. 928 | def chdir(&block) 929 | warn "Path::Name#chdir is obsoleted. Use Dir.chdir." 930 | Dir.chdir(path, &block) 931 | end 932 | 933 | # Path::Name#chroot is *obsoleted* at 1.8.1. 934 | def chroot 935 | warn "Path::Name#chroot is obsoleted. Use Dir.chroot." 936 | Dir.chroot(path) 937 | end 938 | 939 | # Return the entries (files and subdirectories) in the directory, each as a 940 | # Path::Name object. 941 | def entries() Dir.entries(path).map {|f| Path::Name.new(f) } end 942 | 943 | # Iterates over the entries (files and subdirectories) in the directory. It 944 | # yields a Path::Name object for each entry. 945 | # 946 | # This method has existed since 1.8.1. 947 | def each_entry(&block) # :yield: p 948 | Dir.foreach(path) {|f| yield Path::Name.new(f) } 949 | end 950 | 951 | # Path::Name#dir_foreach is *obsoleted* at 1.8.1. 952 | def dir_foreach(*args, &block) 953 | warn "Path::Name#dir_foreach is obsoleted. Use Path::Name#each_entry." 954 | each_entry(*args, &block) 955 | end 956 | 957 | # See Dir.mkdir. Create the referenced directory. 958 | def mkdir(*args) Dir.mkdir(path, *args) end 959 | 960 | # See Dir.rmdir. Remove the referenced directory. 961 | def rmdir() Dir.rmdir(path) end 962 | 963 | # See Dir.open. 964 | def opendir(&block) # :yield: dir 965 | Dir.open(path, &block) 966 | end 967 | 968 | # 969 | def glob(match, *opts) 970 | flags = 0 971 | opts.each do |opt| 972 | case opt when Symbol, String 973 | flags += ::File.const_get("FNM_#{opt}".upcase) 974 | else 975 | flags += opt 976 | end 977 | end 978 | Dir.glob(::File.join(self.to_s, match), flags).collect{ |m| self.class.new(m) } 979 | end 980 | 981 | # Like #glob but returns the first match. 982 | def first(match, *opts) 983 | flags = 0 984 | opts.each do |opt| 985 | case opt when Symbol, String 986 | flags += ::File.const_get("FNM_#{opt}".upcase) 987 | else 988 | flags += opt 989 | end 990 | end 991 | file = ::Dir.glob(::File.join(self.to_s, match), flags).first 992 | file ? self.class.new(file) : nil 993 | end 994 | 995 | # DEPRECATE 996 | alias_method :glob_first, :first 997 | 998 | # Like #glob but returns the last match. 999 | def last(match, *opts) 1000 | flags = 0 1001 | opts.each do |opt| 1002 | case opt when Symbol, String 1003 | flags += ::File.const_get("FNM_#{opt}".upcase) 1004 | else 1005 | flags += opt 1006 | end 1007 | end 1008 | file = ::Dir.glob(::File.join(self.to_s, match), flags).last 1009 | file ? self.class.new(file) : nil 1010 | end 1011 | 1012 | # DEPRECATE 1013 | alias_method :glob_last, :last 1014 | 1015 | # 1016 | def empty? 1017 | Dir.glob(::File.join(self.to_s, '*')).empty? 1018 | end 1019 | end 1020 | 1021 | 1022 | class Path::Name # * Find * 1023 | # Path::Name#find is an iterator to traverse a directory tree in a depth first 1024 | # manner. It yields a Path::Name for each file under "this" directory. 1025 | # 1026 | # Since it is implemented by find.rb, Find.prune can be used 1027 | # to control the traverse. 1028 | # 1029 | # If +self+ is ., yielded pathnames begin with a filename in the 1030 | # current directory, not ./. 1031 | # 1032 | # TODO: This is more like and #each method, though also like #descend. 1033 | # I would rather use the method name #find in place of #first. This would be a 1034 | # non-compatability with Pathname, but I think an acceptable one. Need to 1035 | # compare #descend to #find. We may not even need both. 1036 | 1037 | def find(&block) # :yield: p 1038 | require 'find' 1039 | if @path == ['.'] 1040 | Find.find(path) {|f| yield Path::Name.new(f.sub(%r{\A\./}, '')) } 1041 | else 1042 | Find.find(path) {|f| yield Path::Name.new(f) } 1043 | end 1044 | end 1045 | end 1046 | 1047 | 1048 | class Path::Name # * FileUtils * 1049 | # See FileUtils.mkpath. Creates a full path, including any 1050 | # intermediate directories that don't yet exist. 1051 | def mkpath 1052 | require 'fileutils' 1053 | FileUtils.mkpath(path) 1054 | nil 1055 | end 1056 | 1057 | # See FileUtils.rm_r. Deletes a directory and all beneath it. 1058 | def rmtree 1059 | # The name "rmtree" is borrowed from File::Path of Perl. 1060 | # File::Path provides "mkpath" and "rmtree". 1061 | require 'fileutils' 1062 | FileUtils.rm_r(path) 1063 | nil 1064 | end 1065 | 1066 | # 1067 | def uptodate?(*sources) 1068 | ::FileUtils.uptodate?(to_s, sources.flatten) 1069 | end 1070 | 1071 | # 1072 | def outofdate?(*sources) 1073 | ! uptodate?(*sources) 1074 | end 1075 | end 1076 | 1077 | 1078 | class Path::Name # * mixed * 1079 | # Removes a file or directory, using File.unlink or 1080 | # Dir.unlink as necessary. 1081 | def unlink() 1082 | begin 1083 | Dir.unlink path 1084 | rescue Errno::ENOTDIR 1085 | File.unlink path 1086 | end 1087 | end 1088 | alias delete unlink 1089 | 1090 | # This method is *obsoleted* at 1.8.1. Use #each_line or #each_entry. 1091 | def foreach(*args, &block) 1092 | warn "Path::Name#foreach is obsoleted. Use each_line or each_entry." 1093 | if FileTest.directory? path 1094 | # For polymorphism between Dir.foreach and IO.foreach, 1095 | # Path::Name#foreach doesn't yield Path::Name object. 1096 | Dir.foreach(path, *args, &block) 1097 | else 1098 | IO.foreach(path, *args, &block) 1099 | end 1100 | end 1101 | end 1102 | 1103 | class Path::Name # extensions 1104 | 1105 | # Alternate to Pathname#new. 1106 | # 1107 | # Pathname['/usr/share'] 1108 | # 1109 | def self.[](path) 1110 | new(path) 1111 | end 1112 | 1113 | # Active path separator. 1114 | # 1115 | # p1 = Pathname.new('/') 1116 | # p2 = p1 / 'usr' / 'share' #=> Pathname:/usr/share 1117 | # 1118 | def self./(path) #/ 1119 | new(path) 1120 | end 1121 | 1122 | # Root constant for building paths from root directory onward. 1123 | def self.root 1124 | self.class.new('/') 1125 | end 1126 | 1127 | # Home constant for building paths from root directory onward. 1128 | # 1129 | # TODO: Pathname#home needs to be more robust. 1130 | # 1131 | def self.home 1132 | self.class.new('~') 1133 | end 1134 | 1135 | # Work constant for building paths from root directory onward. 1136 | # 1137 | def self.work 1138 | self.class.new('.') 1139 | end 1140 | 1141 | # Platform dependent null device. 1142 | # 1143 | def self.null 1144 | case RUBY_PLATFORM 1145 | when /mswin/i 1146 | 'NUL' 1147 | when /amiga/i 1148 | 'NIL:' 1149 | when /openvms/i 1150 | 'NL:' 1151 | else 1152 | '/dev/null' 1153 | end 1154 | end 1155 | 1156 | end 1157 | 1158 | end 1159 | 1160 | class String 1161 | def to_path 1162 | Path::Name.new(self) 1163 | end 1164 | end 1165 | 1166 | class NilClass 1167 | # Provide platform dependent null path. 1168 | def to_path 1169 | Path::Name.null 1170 | end 1171 | end 1172 | --------------------------------------------------------------------------------