epath 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Path - a Path manipulation library
2
2
 
3
- Path is a library to manage paths.
3
+ [Path](http://rubydoc.info/github/eregon/epath/master/Path) is a library to manage paths.
4
4
  It is similar to Pathname, but has some extra goodness.
5
5
  The method names are intended to be short and explicit, and avoid too much duplication like having 'name' or 'path' in the method name.
6
6
 
@@ -20,6 +20,8 @@ Also, using a path library like this avoid to remember in which class the functi
20
20
 
21
21
  ## API
22
22
 
23
+ See the [Path](http://rubydoc.info/github/eregon/epath/master/Path) class documentation for details.
24
+
23
25
  All the useful methods of `File` (and so `IO`) and `Dir` should be included.
24
26
  Most methods of `FileUtils` should be there too.
25
27
 
@@ -30,6 +32,11 @@ Path.new('/usr/bin')
30
32
  Path['/usr/bin']
31
33
  Path('/usr/bin') # unless NO_EPATH_GLOBAL_FUNCTION is defined
32
34
 
35
+ Path.new('~myuser/path') # expanded if it begins with ~
36
+
37
+ # Separators are replaced by / on systems having File::ALT_SEPARATOR
38
+ Path.new('win\sepa\rator') # => #<Path win/sepa/rator>
39
+
33
40
  Path.new('/usr', 'bin')
34
41
  %w[foo bar].map(&Path) # => [Path('foo'), Path('bar')]
35
42
  ```
@@ -38,7 +45,8 @@ Path.new('/usr', 'bin')
38
45
  Path.file # == Path(__FILE__).expand
39
46
  Path.dir # == Path(File.dirname(__FILE__)).expand
40
47
  Path.relative(path) # == Path(File.expand_path("../#{path}", __FILE__))
41
- Path.home # == Path(File.expand_path('~'))
48
+ Path.home or Path.~ # == Path(File.expand_path('~'))
49
+ Path.~(user) # == Path(File.expand_path("~#{user}"))
42
50
  ```
43
51
 
44
52
  ### temporary
@@ -102,11 +110,12 @@ Path('/usr')/'bin'
102
110
 
103
111
  ### glob
104
112
 
105
- * entries: files under self, without . and ..
113
+ * children: files under self, without . and ..
106
114
  * glob: relative glob to self, yield absolute paths
107
115
 
108
116
  ### structure
109
117
 
118
+ * parent: parent directory (don't use #dirname more than once, use #parent instead)
110
119
  * ascend, ancestors: self and all the parent directories
111
120
  * descend: in the reverse order
112
121
  * backfind: ascends the parents until it finds the given path
@@ -6,6 +6,7 @@ require 'tempfile'
6
6
 
7
7
  class Path
8
8
  class << self
9
+ # {Path} to the current file +Path(__FILE__)+.
9
10
  def file(from = nil)
10
11
  from ||= caller # this can not be moved as a default argument, JRuby optimizes it
11
12
  # v This : is there to define a group without capturing
@@ -13,23 +14,31 @@ class Path
13
14
  end
14
15
  alias :here :file
15
16
 
17
+ # {Path} to the directory of this file: +Path(__FILE__).dir+.
16
18
  def dir(from = nil)
17
19
  from ||= caller # this can not be moved as a default argument, JRuby optimizes it
18
20
  file(from).dir
19
21
  end
20
22
 
21
- def home
22
- new(Dir.respond_to?(:home) ? Dir.home : new("~").expand)
23
+ # {Path} relative to the directory of this file.
24
+ def relative(path, from = nil)
25
+ from ||= caller # this can not be moved as a default argument, JRuby optimizes it
26
+ new(path).expand dir(from)
23
27
  end
24
28
 
25
- def relative(path)
26
- new(path).expand dir(caller)
29
+ # A {Path} to the home directory of +user+ (defaults to the current user).
30
+ # The form with an argument (+user+) is not supported on Windows.
31
+ def ~(user = '')
32
+ new("~#{user}")
27
33
  end
34
+ alias :home :~
28
35
 
36
+ # Same as +Path.file.backfind(path)+. See {#backfind}.
29
37
  def backfind(path)
30
38
  file(caller).backfind(path)
31
39
  end
32
40
 
41
+ # @yieldparam [Path] tmpfile
33
42
  def tmpfile(basename = '', tmpdir = nil, options = nil)
34
43
  tempfile = Tempfile.new(basename, *[tmpdir, options].compact)
35
44
  file = new tempfile
@@ -37,14 +46,14 @@ class Path
37
46
  begin
38
47
  yield file
39
48
  ensure
40
- tempfile.close
41
- tempfile.unlink if file.exist?
49
+ tempfile.close!
42
50
  end
43
51
  end
44
52
  file
45
53
  end
46
54
  alias :tempfile :tmpfile
47
55
 
56
+ # @yieldparam [Path] tmpdir
48
57
  def tmpdir(prefix_suffix = nil, *rest)
49
58
  require 'tmpdir'
50
59
  dir = new Dir.mktmpdir(prefix_suffix, *rest)
@@ -58,6 +67,7 @@ class Path
58
67
  dir
59
68
  end
60
69
 
70
+ # @yieldparam [Path] tmpdir
61
71
  def tmpchdir(prefix_suffix = nil, *rest)
62
72
  tmpdir do |dir|
63
73
  dir.chdir do
@@ -67,19 +77,24 @@ class Path
67
77
  end
68
78
  end
69
79
 
70
- alias :/ :+
71
-
72
- alias :relative_to :relative_path_from
73
- alias :% :relative_path_from
74
-
80
+ # Whether +self+ is inside +ancestor+, such that +ancestor+ is an ancestor of +self+.
81
+ # This is pure String manipulation. Paths should be absolute.
75
82
  def inside? ancestor
76
83
  @path == ancestor.to_s or @path.start_with?("#{ancestor}/")
77
84
  end
78
85
 
86
+ # The opposite of {#inside?}.
79
87
  def outside? ancestor
80
88
  !inside?(ancestor)
81
89
  end
82
90
 
91
+ # Ascends the parents until it finds the given +path+.
92
+ #
93
+ # Path.backfind('lib') # => the lib folder
94
+ #
95
+ # It accepts an XPath-like context:
96
+ #
97
+ # Path.backfind('.[.git]') # => the root of the repository
83
98
  def backfind(path)
84
99
  condition = path[/\[(.*)\]$/, 1] || ''
85
100
  path = $` unless condition.empty?
@@ -1,7 +1,10 @@
1
1
  class Path
2
2
  class << self
3
+ # @!group Directory
4
+
3
5
  # Returns or yields Path objects. See +Dir.glob+.
4
- def glob(*args) # :yield: path
6
+ # @yieldparam [Path] path
7
+ def glob(*args)
5
8
  if block_given?
6
9
  Dir.glob(*args) { |f| yield new(f) }
7
10
  else
@@ -16,9 +19,22 @@ class Path
16
19
  alias :pwd :getwd
17
20
  end
18
21
 
22
+ # @!group Directory
23
+
19
24
  # Iterates over the entries (files and subdirectories) in the directory.
20
- # It yields a Path object for each entry.
21
- def each_entry(&block) # :yield: path
25
+ #
26
+ # Path("/usr/local").each_entry { |entry| p entry } # =>
27
+ # #<Path .>
28
+ # #<Path ..>
29
+ # #<Path lib>
30
+ # #<Path share>
31
+ # # ...
32
+ #
33
+ # @deprecated Use {#each_child} instead.
34
+ # This method is deprecated since it is too low level and likely useless in Ruby.
35
+ # But it is there for the sake of compatibility with Dir.foreach and Pathname#each_entry.
36
+ # @yieldparam [Path] entry
37
+ def each_entry(&block)
22
38
  Dir.foreach(@path) { |f| yield Path.new(f) }
23
39
  end
24
40
 
@@ -34,29 +50,33 @@ class Path
34
50
  end
35
51
 
36
52
  # See +Dir.open+.
37
- def opendir(&block) # :yield: dir
53
+ # @yieldparam [Dir] dir
54
+ def opendir(&block)
38
55
  Dir.open(@path, &block)
39
56
  end
40
57
 
58
+ # Returns or yields Path objects. See +Dir.glob+.
59
+ # @yieldparam [Path] path
41
60
  def glob(pattern, flags = 0)
42
61
  Dir.glob(join(pattern), flags).map(&Path)
43
62
  end
44
63
 
45
- # [DEPRECATED] Return the entries (files and subdirectories) in the directory.
64
+ # Return the entries (files and subdirectories) in the directory.
46
65
  # Each Path only contains the filename.
47
66
  # The result may contain the current directory #<Path .> and the parent directory #<Path ..>.
48
67
  #
49
- # Path('/usr/local').entries
50
- # # => [#<Path share>, #<Path lib>, #<Path .>, #<Path ..>, <Path bin>, ...]
51
- #
52
- # This method is deprecated, since it is too low level and likely useless in Ruby.
53
- # But it is there for the sake of compatibility with Dir.entries (and Pathname#entries)
68
+ # Path('/usr/local').entries
69
+ # # => [#<Path share>, #<Path lib>, #<Path .>, #<Path ..>, <Path bin>, ...]
54
70
  #
55
- # Use #children instead.
71
+ # @deprecated Use {#children} instead.
72
+ # This method is deprecated since it is too low level and likely useless in Ruby.
73
+ # But it is there for the sake of compatibility with Dir.entries (and Pathname#entries).
56
74
  def entries
57
75
  Dir.entries(@path).map(&Path)
58
76
  end
59
77
 
78
+ # Changes the current working directory of the process to self. See Dir.chdir.
79
+ # The recommended way to use it is to use the block form, or not use it all!
60
80
  def chdir(&block)
61
81
  Dir.chdir(@path, &block)
62
82
  end
@@ -92,9 +112,7 @@ class Path
92
112
  result
93
113
  end
94
114
 
95
- # Iterates over the children of the directory
96
- # (files and subdirectories, not recursive).
97
- # It yields Path object for each child.
115
+ # Iterates over the children of the directory (files and subdirectories, not recursive).
98
116
  # By default, the yielded paths will have enough information to access the files.
99
117
  # If you set +with_directory+ to +false+, then the returned paths will contain the filename only.
100
118
  #
@@ -117,6 +135,8 @@ class Path
117
135
  # #<Path sbin>
118
136
  # #<Path src>
119
137
  # #<Path man>
138
+ #
139
+ # @yieldparam [Path] child
120
140
  def each_child(with_directory=true, &b)
121
141
  children(with_directory).each(&b)
122
142
  end
@@ -1,10 +1,13 @@
1
1
  class Path
2
+ # @!group File
3
+
2
4
  # Returns last access time. See +File.atime+.
3
5
  def atime
4
6
  File.atime(@path)
5
7
  end
6
8
 
7
- # Returns last (directory entry, not file) change time. See +File.ctime+.
9
+ # Returns last change time (of the directory entry, not the file itself).
10
+ # See +File.ctime+.
8
11
  def ctime
9
12
  File.ctime(@path)
10
13
  end
@@ -14,17 +17,18 @@ class Path
14
17
  File.mtime(@path)
15
18
  end
16
19
 
17
- # Changes permissions. See +File.chmod+.
20
+ # Changes permissions of +path+. See +File.chmod+.
18
21
  def chmod(mode) File.chmod(mode, @path) end
19
22
 
20
- # See +File.lchmod+.
23
+ # Changes permissions of +path+, not following symlink. See +File.lchmod+.
21
24
  def lchmod(mode) File.lchmod(mode, @path) end
22
25
 
23
- # Change owner and group of file. See +File.chown+.
26
+ # Changes the owner and group of the file. See +File.chown+.
24
27
  def chown(owner, group)
25
28
  File.chown(owner, group, @path)
26
29
  end
27
30
 
31
+ # Changes the owner and group of +path+, not following symlink.
28
32
  # See +File.lchown+.
29
33
  def lchown(owner, group)
30
34
  File.lchown(owner, group, @path)
@@ -44,28 +48,28 @@ class Path
44
48
  self
45
49
  end
46
50
 
47
- # Read symbolic link. See +File.readlink+.
51
+ # Reads the symbolic link. See +File.readlink+.
48
52
  def readlink
49
53
  Path.new(File.readlink(@path))
50
54
  end
51
55
 
52
- # Rename the file and returns the new Path. See +File.rename+.
56
+ # Renames the file and returns the new Path. See +File.rename+.
53
57
  def rename(to)
54
58
  File.rename(@path, to)
55
59
  Path(to)
56
60
  end
57
61
 
58
- # Returns a +File::Stat+ object. See +File.stat+.
62
+ # Returns the stat of +path+ as a +File::Stat+ object. See +File.stat+.
59
63
  def stat
60
64
  File.stat(@path)
61
65
  end
62
66
 
63
- # See +File.lstat+.
67
+ # Returns the stat of +path+ as a +File::Stat+ object, not following symlink. See +File.lstat+.
64
68
  def lstat
65
69
  File.lstat(@path)
66
70
  end
67
71
 
68
- # See +File.size+.
72
+ # Returns the file size in bytes. See +File.size+.
69
73
  def size
70
74
  File.size(@path)
71
75
  end
@@ -79,17 +83,19 @@ class Path
79
83
  self
80
84
  end
81
85
 
82
- # Truncate the file to +length+ bytes. See +File.truncate+.
86
+ # Truncates the file to +length+ bytes. See +File.truncate+.
83
87
  def truncate(length) File.truncate(@path, length) end
84
88
 
85
- # Update the access and modification times. See +File.utime+.
89
+ # Updates the access and modification times. See +File.utime+.
86
90
  def utime(atime, mtime) File.utime(atime, mtime, @path) end
87
91
 
88
- # See +File.expand_path+.
89
- def expand_path(*args)
92
+ # Expands +path+, making it absolute.
93
+ # If the path is relative, it is expanded with the current working directory,
94
+ # unless +dir+ is given as an argument. See +File.expand_path+.
95
+ def expand(*args)
90
96
  Path.new(File.expand_path(@path, *args))
91
97
  end
92
- alias :expand :expand_path
98
+ alias :expand_path :expand
93
99
 
94
100
  # Returns the real (absolute) path of +self+ in the actual
95
101
  # filesystem not containing symlinks or useless dots.
@@ -1,6 +1,8 @@
1
1
  # All methods from FileTest and all predicates from File are included
2
2
 
3
3
  class Path
4
+ # @!group File predicates
5
+
4
6
  # See +File.blockdev?+.
5
7
  def blockdev?
6
8
  File.blockdev?(@path)
@@ -128,7 +130,7 @@ class Path
128
130
  end
129
131
 
130
132
  # See +File.zero?+.
131
- # empty? is not defined in File/FileTest, but is is clearer
133
+ # empty? is not defined in File/FileTest, but is is clearer.
132
134
  def zero?
133
135
  File.zero?(@path)
134
136
  end
@@ -1,6 +1,8 @@
1
1
  require 'fileutils'
2
2
 
3
3
  class Path
4
+ # @!group File utilities
5
+
4
6
  # Creates a full path, including any intermediate directories that don't yet exist.
5
7
  # See +FileUtils.mkpath+.
6
8
  def mkpath
@@ -18,37 +20,96 @@ class Path
18
20
  end
19
21
  alias :rm_r :rmtree
20
22
 
23
+ # Removes the file using +FileUtils.rm+.
21
24
  def rm
22
25
  FileUtils.rm(@path)
23
26
  self
24
27
  end
25
28
 
29
+ # Removes the file, ignoring errors, using +FileUtils.rm_f+.
26
30
  def rm_f
27
31
  FileUtils.rm_f(@path)
28
32
  self
29
33
  end
34
+ alias :safe_unlink :rm_f
35
+
36
+ # Removes the file or directory recursively, using +FileUtils.rm_r+.
37
+ def rm_r
38
+ FileUtils.rm_r(@path)
39
+ self
40
+ end
30
41
 
42
+ # Removes the file or directory recursively, ignoring errors,
43
+ # using +FileUtils.rm_f+.
31
44
  def rm_rf
32
45
  FileUtils.rm_rf(@path)
33
46
  self
34
47
  end
35
48
 
49
+ # Copies the file to +to+. See +FileUtils.cp+.
36
50
  def cp(to)
37
- FileUtils.cp(@path, to)
51
+ # TODO: remove :preserve when all implement it correctly (r31123)
52
+ FileUtils.cp(@path, to, :preserve => true)
38
53
  end
39
- alias copy cp
54
+ alias :copy :cp
40
55
 
56
+ # Copies the file or directory recursively to the directory +to+.
57
+ # See +FileUtils.cp_r+.
41
58
  def cp_r(to)
42
59
  FileUtils.cp_r(@path, to)
43
60
  end
44
61
 
62
+ # Updates access and modification time or create an empty file.
45
63
  def touch
46
- FileUtils.touch(@path)
64
+ if exist?
65
+ now = Time.now
66
+ File.utime(now, now, @path)
67
+ else
68
+ open('w') {}
69
+ end
47
70
  self
48
71
  end
49
72
 
73
+ # {#touch} preceded by +dir.+{#mkpath}.
50
74
  def touch!
51
- dirname.mkpath
75
+ dir.mkpath
52
76
  touch
53
77
  end
78
+
79
+ # Moves +self+ to the +to+ directory.
80
+ def mv(to)
81
+ FileUtils.mv(@path, to)
82
+ to
83
+ end
84
+ alias :move :mv
85
+
86
+ # Install +file+ into +path+ (the "prefix", which should be a directory).
87
+ # If +file+ is not same as +path/file+, replaces it.
88
+ # See +FileUtils.install+ (arguments are swapped).
89
+ def install(file, options = {})
90
+ FileUtils.install(file, @path, options)
91
+ end
92
+
93
+ # Recusively changes permissions. See +FileUtils.chmod_R+ and +File.chmod+.
94
+ def chmod_r(mode)
95
+ FileUtils.chmod_R(mode, @path)
96
+ end
97
+
98
+ # Recusively changes owner and group. See +FileUtils.chown_R+ and +File.chown+.
99
+ def chown_r(owner, group)
100
+ FileUtils.chown_R(owner, group, @path)
101
+ end
102
+
103
+ # Whether the contents of +path+ and +file+ are identical.
104
+ # See +FileUtils.compare_file+.
105
+ def has_same_contents?(file)
106
+ FileUtils.compare_file(@path, file)
107
+ end
108
+
109
+ # Returns whether +self+ is newer than all +others+.
110
+ # Non-existent files are older than any file.
111
+ # See +FileUtils.uptodate?+.
112
+ def uptodate?(*others)
113
+ FileUtils.uptodate?(@path, others)
114
+ end
54
115
  end
@@ -9,7 +9,9 @@ class Path
9
9
  #
10
10
  # If +self+ is +.+, yielded paths begin with a filename in the
11
11
  # current directory, not +./+.
12
- def find # :yield: path
12
+ #
13
+ # @yieldparam [Path] path
14
+ def find
13
15
  return to_enum(__method__) unless block_given?
14
16
  require 'find'
15
17
  if @path == '.'
@@ -1,5 +1,8 @@
1
1
  class Path
2
2
  class << self
3
+ # @!group Identity
4
+
5
+ # Creates a new Path. See {#initialize}.
3
6
  def new(*args)
4
7
  if args.size == 1 and Path === args[0]
5
8
  args[0]
@@ -9,11 +12,16 @@ class Path
9
12
  end
10
13
  alias :[] :new
11
14
 
15
+ # A class constructor.
16
+ #
17
+ # %w[foo bar].map(&Path) # == [Path('foo'), Path('bar')]
12
18
  def to_proc
13
19
  lambda { |path| new(path) }
14
20
  end
15
21
  end
16
22
 
23
+ # @!group Identity
24
+
17
25
  # Compare this path with +other+. The comparison is string-based.
18
26
  # Be aware that two different paths (+foo.txt+ and +./foo.txt+)
19
27
  # can refer to the same file.
@@ -28,11 +36,12 @@ class Path
28
36
  @path.tr('/', "\0") <=> other.to_s.tr('/', "\0")
29
37
  end
30
38
 
39
+ # The hash value of the +path+.
31
40
  def hash
32
41
  @path.hash
33
42
  end
34
43
 
35
- # Return the path as a String.
44
+ # Returns the +path+ as a String.
36
45
  def to_s
37
46
  @path
38
47
  end
@@ -40,18 +49,22 @@ class Path
40
49
  # to_path is implemented so Path objects are usable with File.open, etc.
41
50
  alias :to_path :to_s
42
51
 
52
+ # to_str is implemented so Path objects are usable with File.open, etc in Ruby 1.8.
43
53
  alias :to_str :to_s if RUBY_VERSION < '1.9'
44
54
 
55
+ # Returns the +path+ as a Symbol.
45
56
  def to_sym
46
57
  @path.to_sym
47
58
  end
48
59
 
60
+ # A representation of the +path+.
49
61
  def inspect
50
62
  "#<Path #{@path}>"
51
63
  end
52
64
  end
53
65
 
54
66
  unless defined? NO_EPATH_GLOBAL_FUNCTION
67
+ # A shorthand method to create a {Path}. Same as {Path.new}.
55
68
  def Path(*args)
56
69
  Path.new(*args)
57
70
  end
@@ -8,8 +8,12 @@ class Path
8
8
  lambda { |a,b| a == b }
9
9
  end
10
10
 
11
+ # Creates a new Path.
12
+ # If multiple arguments are given, they are joined with File.join.
13
+ # The path will have File::ALT_SEPARATOR replaced with '/' and
14
+ # if it begins with a '~', it will be expanded (using File.expand_path).
11
15
  def initialize(*parts)
12
- path = parts.size > 1 ? File.join(parts) : parts.first
16
+ path = parts.size > 1 ? File.join(*parts) : parts.first
13
17
  @path = case path
14
18
  when Tempfile
15
19
  @_tmpfile = path # We would not want it to be GC'd
@@ -23,15 +27,24 @@ class Path
23
27
  init
24
28
  end
25
29
 
30
+ # YAML loading.
26
31
  def yaml_initialize(tag, ivars)
27
32
  @path = ivars['path']
28
33
  init
29
34
  end
30
35
 
36
+ # Psych loading.
37
+ def init_with(coder)
38
+ @path = coder['path']
39
+ init
40
+ end
41
+
42
+ # Marshal dumping.
31
43
  def marshal_dump
32
44
  @path
33
45
  end
34
46
 
47
+ # Marshal loading.
35
48
  def marshal_load path
36
49
  @path = path
37
50
  init
@@ -43,7 +56,7 @@ class Path
43
56
  # If +consider_symlink+ is +true+, then a more conservative algorithm is used
44
57
  # to avoid breaking symbolic linkages. This may retain more +..+
45
58
  # entries than absolutely necessary, but without accessing the filesystem,
46
- # this can't be avoided. See #realpath.
59
+ # this can't be avoided. See {#realpath}.
47
60
  def cleanpath(consider_symlink=false)
48
61
  if consider_symlink
49
62
  cleanpath_conservative
@@ -53,34 +66,34 @@ class Path
53
66
  end
54
67
 
55
68
  # #parent returns the parent directory.
56
- #
57
- # This is same as <tt>self + '..'</tt>.
69
+ # This can be chained.
58
70
  def parent
59
- self + '..'
71
+ self / '..'
60
72
  end
61
73
 
62
- # Path#+ appends a path fragment to this one to produce a new Path.
74
+ # Path#/ appends a path fragment to this one to produce a new Path.
63
75
  #
64
- # p1 = Path.new("/usr") # => #<Path /usr>
65
- # p2 = p1 + "bin/ruby" # => #<Path /usr/bin/ruby>
66
- # p3 = p1 + "/etc/passwd" # => #<Path /etc/passwd>
76
+ # p = Path.new("/usr") # => #<Path /usr>
77
+ # p / "bin/ruby" # => #<Path /usr/bin/ruby>
78
+ # p / "/etc/passwd" # => #<Path /etc/passwd>
67
79
  #
68
80
  # This method doesn't access the file system, it is pure string manipulation.
69
- def +(other)
81
+ def /(other)
70
82
  Path.new(plus(@path, other.to_s))
71
83
  end
84
+ alias :+ :/
72
85
 
73
86
  # Path#join joins paths.
74
87
  #
75
88
  # <tt>path0.join(path1, ..., pathN)</tt> is the same as
76
- # <tt>path0 + path1 + ... + pathN</tt>.
89
+ # <tt>path0 / path1 / ... / pathN</tt>.
77
90
  def join(*args)
78
91
  args.unshift self
79
92
  result = Path.new(args.pop)
80
93
  return result if result.absolute?
81
94
  args.reverse_each { |arg|
82
95
  arg = Path.new(arg)
83
- result = arg + result
96
+ result = arg / result
84
97
  return result if result.absolute?
85
98
  }
86
99
  result
@@ -126,6 +139,8 @@ class Path
126
139
  Path.new(*relpath_names)
127
140
  end
128
141
  end
142
+ alias :relative_to :relative_path_from
143
+ alias :% :relative_path_from
129
144
 
130
145
  # @private
131
146
  module Helpers
@@ -148,7 +163,7 @@ class Path
148
163
  private
149
164
 
150
165
  def init
151
- validate(@path)
166
+ @path = validate(@path)
152
167
 
153
168
  taint if @path.tainted?
154
169
  @path.freeze
@@ -158,6 +173,8 @@ class Path
158
173
  def validate(path)
159
174
  raise ArgumentError, "path contains a null byte: #{path.inspect}" if path.include? "\0"
160
175
  path.gsub!(File::ALT_SEPARATOR, '/') if File::ALT_SEPARATOR
176
+ path = File.expand_path(path) if path.start_with? '~'
177
+ path
161
178
  end
162
179
 
163
180
  # chop_basename(path) -> [pre-basename, basename] or nil
@@ -211,8 +228,8 @@ class Path
211
228
  if r = chop_basename(path)
212
229
  pre, basename = r
213
230
  pre + basename
214
- elsif /\/+\z/o =~ path
215
- $` + File.dirname(path)[/\/*\z/o]
231
+ elsif %r{/+\z} =~ path
232
+ $` + File.dirname(path)[%r{/*\z}]
216
233
  else
217
234
  path
218
235
  end
@@ -1,11 +1,15 @@
1
1
  class Path
2
+ # @!group IO
3
+
2
4
  # Opens the file for reading or writing. See +File.open+.
3
- def open(*args, &block) # :yield: file
5
+ # @yieldparam [File] file
6
+ def open(*args, &block)
4
7
  File.open(@path, *args, &block)
5
8
  end
6
9
 
7
10
  # Iterates over the lines in the file. See +IO.foreach+.
8
- def each_line(*args, &block) # :yield: line
11
+ # @yieldparam [String] line
12
+ def each_line(*args, &block)
9
13
  IO.foreach(@path, *args, &block)
10
14
  end
11
15
  alias :lines :each_line
@@ -37,6 +41,7 @@ class Path
37
41
  end
38
42
 
39
43
  if IO.respond_to? :write
44
+ # Writes +contents+ to +self+. See +IO.write+ or +IO#write+.
40
45
  def write(contents, *open_args)
41
46
  IO.write(@path, contents, *open_args)
42
47
  end
@@ -47,13 +52,14 @@ class Path
47
52
  end
48
53
 
49
54
  if IO.respond_to? :write and !RUBY_DESCRIPTION.start_with?('jruby')
55
+ # Appends +contents+ to +self+. See +IO.write+ or +IO#write+.
50
56
  def append(contents, open_args = {})
51
57
  open_args[:mode] = 'a'
52
58
  IO.write(@path, contents, open_args)
53
59
  end
54
60
  else
55
- def append(contents, open_args = nil)
56
- open('a') { |f| f.write(contents) }
61
+ def append(contents, *open_args)
62
+ open('a', *open_args) { |f| f.write(contents) }
57
63
  end
58
64
  end
59
65
  end
@@ -1,4 +1,5 @@
1
1
  class Path
2
+ # @!group Loading
2
3
 
3
4
  # The list of loaders. See {Path.register_loader}.
4
5
  LOADERS = {}
@@ -7,6 +8,8 @@ class Path
7
8
  # for the given extensions (either with the leading dot or not)
8
9
  #
9
10
  # Path.register_loader('.marshal') { |file| Marshal.load file.read }
11
+ #
12
+ # @yieldparam [Path] path
10
13
  def self.register_loader(*extensions, &loader)
11
14
  extensions.each { |ext|
12
15
  LOADERS[pure_ext(ext)] = loader
@@ -15,7 +18,7 @@ class Path
15
18
 
16
19
  # Path#load helps loading data from various files.
17
20
  # JSON and YAML loaders are provided by default.
18
- # See Path.register_loader.
21
+ # See {Path.register_loader}.
19
22
  def load
20
23
  if LOADERS.key? ext
21
24
  LOADERS[ext].call(self)
@@ -1,4 +1,6 @@
1
1
  class Path
2
+ # @!group Path parts
3
+
2
4
  # Returns the last component of the path. See +File.basename+.
3
5
  def basename(*args)
4
6
  Path.new(File.basename(@path, *args))
@@ -9,18 +11,23 @@ class Path
9
11
  basename(extname)
10
12
  end
11
13
 
12
- # Returns all but the last component of the path. See +File.dirname+.
14
+ # Returns all but the last component of the path.
15
+ #
16
+ # Don't chain this when the path is relative:
17
+ # Path('.').dir # => #<Path .>
18
+ # Use #parent instead.
19
+ # See +File.dirname+.
13
20
  def dirname
14
21
  Path.new(File.dirname(@path))
15
22
  end
16
23
  alias :dir :dirname
17
24
 
18
- # Returns the file's extension. See +File.extname+.
25
+ # Returns the extension, with a leading dot. See +File.extname+.
19
26
  def extname
20
27
  File.extname(@path)
21
28
  end
22
29
 
23
- # extname without leading .
30
+ # {#extname} without leading dot.
24
31
  def ext
25
32
  ext = extname
26
33
  ext.empty? ? ext : ext[1..-1]
@@ -31,17 +38,31 @@ class Path
31
38
  File.split(@path).map(&Path)
32
39
  end
33
40
 
41
+ # Adds +ext+ as an extension to +path+.
42
+ # Handle both extensions with or without leading dot.
43
+ # No-op if +ext+ is +empty?+.
44
+ #
45
+ # Path('file').add_extension('txt') # => #<Path file.txt>
34
46
  def add_extension(ext)
35
47
  return self if ext.empty?
36
48
  Path.new @path+dotted_ext(ext)
37
49
  end
38
50
  alias :add_ext :add_extension
39
51
 
52
+ # Removes the last extension of +path+.
53
+ #
54
+ # Path('script.rb').without_extension # => #<Path script>
55
+ # Path('archive.tar.gz').without_extension # => #<Path archive.tar>
40
56
  def without_extension
41
57
  Path.new @path[0..-extname.size-1]
42
58
  end
43
59
  alias :rm_ext :without_extension
44
60
 
61
+ # Replaces the last extension of +path+ with +ext+.
62
+ # Handle both extensions with or without leading dot.
63
+ # Removes last extension if +ext+ is +empty?+.
64
+ #
65
+ # Path('main.c++').replace_extension('cc') # => #<Path main.cc>
45
66
  def replace_extension(ext)
46
67
  return without_extension if ext.empty?
47
68
  Path.new(@path[0..-extname.size-1] << dotted_ext(ext))
@@ -52,15 +73,16 @@ class Path
52
73
  #
53
74
  # Path.new("/usr/bin/ruby").each_filename { |filename| ... }
54
75
  # # yields "usr", "bin", and "ruby".
55
- def each_filename # :yield: filename
76
+ #
77
+ # @yieldparam [String] filename
78
+ def each_filename
56
79
  return to_enum(__method__) unless block_given?
57
80
  _, names = split_names(@path)
58
81
  names.each { |filename| yield filename }
59
82
  nil
60
83
  end
61
84
 
62
- # Iterates over and yields a new Path object
63
- # for each element in the given path in descending order.
85
+ # Iterates over each element in the given path in descending order.
64
86
  #
65
87
  # Path.new('/path/to/some/file.rb').descend { |v| p v }
66
88
  # #<Path />
@@ -76,6 +98,7 @@ class Path
76
98
  # #<Path path/to/some/file.rb>
77
99
  #
78
100
  # It doesn't access actual filesystem.
101
+ # @yieldparam [Path] path
79
102
  def descend
80
103
  return to_enum(:descend) unless block_given?
81
104
  vs = []
@@ -84,8 +107,7 @@ class Path
84
107
  nil
85
108
  end
86
109
 
87
- # Iterates over and yields a new Path object
88
- # for each element in the given path in ascending order.
110
+ # Iterates over each element in the given path in ascending order.
89
111
  #
90
112
  # Path.new('/path/to/some/file.rb').ascend { |v| p v }
91
113
  # #<Path /path/to/some/file.rb>
@@ -101,6 +123,7 @@ class Path
101
123
  # #<Path path>
102
124
  #
103
125
  # It doesn't access actual filesystem.
126
+ # @yieldparam [Path] path
104
127
  def ascend
105
128
  return to_enum(:ascend) unless block_given?
106
129
  path = @path
@@ -1,4 +1,6 @@
1
1
  class Path
2
+ # @!group Path predicates
3
+
2
4
  # Whether a path is absolute.
3
5
  def absolute?
4
6
  !relative?
@@ -1,4 +1,8 @@
1
1
  class Path
2
+ # @!group Requiring
3
+
4
+ # Requires all .rb files recursively (in alphabetic order)
5
+ # under +directory+ (or this file's directory if not given).
2
6
  def self.require_tree(directory = nil)
3
7
  if directory
4
8
  new(directory).require_tree
@@ -8,7 +12,11 @@ class Path
8
12
  end
9
13
  end
10
14
 
15
+ # @api private
16
+ # See {Path.require_tree}.
17
+ # It is not a real private method because {Path.require_tree}
18
+ # (so the {Path} class) needs to be able to call it.
11
19
  def require_tree(source = nil)
12
- glob('**/*.rb').sort.each { |file| require file.expand(dir) unless file == source }
20
+ glob('**/*.rb').sort.each { |file| require file.expand(dir).to_s unless file == source }
13
21
  end
14
22
  end
@@ -1,3 +1,5 @@
1
1
  class Path
2
- VERSION = '0.1.1'
2
+ # The version of the gem.
3
+ # Set here to avoid duplication and allow introspection.
4
+ VERSION = '0.2.0'
3
5
  end
metadata CHANGED
@@ -1,45 +1,38 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: epath
3
- version: !ruby/object:Gem::Version
4
- hash: 25
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
5
  prerelease:
6
- segments:
7
- - 0
8
- - 1
9
- - 1
10
- version: 0.1.1
11
6
  platform: ruby
12
- authors:
7
+ authors:
13
8
  - eregon
14
9
  autorequire:
15
10
  bindir: bin
16
11
  cert_chain: []
17
-
18
- date: 2012-03-18 00:00:00 Z
19
- dependencies:
20
- - !ruby/object:Gem::Dependency
12
+ date: 2012-06-01 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
21
15
  name: rspec
22
- prerelease: false
23
- requirement: &id001 !ruby/object:Gem::Requirement
16
+ requirement: !ruby/object:Gem::Requirement
24
17
  none: false
25
- requirements:
26
- - - ">="
27
- - !ruby/object:Gem::Version
28
- hash: 3
29
- segments:
30
- - 0
31
- version: "0"
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
32
22
  type: :development
33
- version_requirements: *id001
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
34
30
  description:
35
31
  email: eregontp@gmail.com
36
32
  executables: []
37
-
38
33
  extensions: []
39
-
40
34
  extra_rdoc_files: []
41
-
42
- files:
35
+ files:
43
36
  - lib/epath/dir.rb
44
37
  - lib/epath/file.rb
45
38
  - lib/epath/file_dir.rb
@@ -60,37 +53,27 @@ files:
60
53
  - epath.gemspec
61
54
  homepage: https://github.com/eregon/epath
62
55
  licenses: []
63
-
64
56
  post_install_message:
65
57
  rdoc_options: []
66
-
67
- require_paths:
58
+ require_paths:
68
59
  - lib
69
- required_ruby_version: !ruby/object:Gem::Requirement
60
+ required_ruby_version: !ruby/object:Gem::Requirement
70
61
  none: false
71
- requirements:
72
- - - ">="
73
- - !ruby/object:Gem::Version
74
- hash: 3
75
- segments:
76
- - 0
77
- version: "0"
78
- required_rubygems_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ required_rubygems_version: !ruby/object:Gem::Requirement
79
67
  none: false
80
- requirements:
81
- - - ">="
82
- - !ruby/object:Gem::Version
83
- hash: 3
84
- segments:
85
- - 0
86
- version: "0"
68
+ requirements:
69
+ - - ! '>='
70
+ - !ruby/object:Gem::Version
71
+ version: '0'
87
72
  requirements: []
88
-
89
73
  rubyforge_project:
90
- rubygems_version: 1.8.15
74
+ rubygems_version: 1.8.23
91
75
  signing_key:
92
76
  specification_version: 3
93
77
  summary: a Path manipulation library
94
78
  test_files: []
95
-
96
79
  has_rdoc: