├── 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 |
--------------------------------------------------------------------------------