epath 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -17,6 +17,7 @@ Also, using a path library like this avoid to remember in which class the functi
17
17
 
18
18
  * [GitHub](https://github.com/eregon/epath)
19
19
  * [YARD Documentation](http://rubydoc.info/github/eregon/epath/master/file/README.md)
20
+ * [Changelog](https://github.com/eregon/epath/blob/master/Changelog.md)
20
21
 
21
22
  ## API
22
23
 
@@ -144,6 +145,30 @@ Path.backfind('.[.git]') # => the root of this repository
144
145
 
145
146
  * Path.require\_tree: require all .rb files recursively (in alphabetic order)
146
147
 
148
+ ### relocate
149
+
150
+ ``` ruby
151
+ from = Path('pictures')
152
+ to = Path('output/public/thumbnails')
153
+ earth = Path('pictures/nature/earth.jpg')
154
+
155
+ earth.relocate(from, to, '.png') { |rel| "#{rel}-200" }
156
+ # => #<Path output/public/thumbnails/nature/earth-200.png>
157
+ ```
158
+
159
+ ## Transition from String/Pathname
160
+
161
+ One aim of Path is to help the user make the transition coming from
162
+ String (not using a path library), Pathname, or another library.
163
+
164
+ To this intend, [`Path + config`](http://rubydoc.info/github/eregon/epath/master/Path#%2B-class_method) allows to configure the behavior of `Path#+`.
165
+
166
+ Coming from String, one should use `Path + :string`, and run ruby with the verbose option (`-w`),
167
+ which will show were `+` is used as String concatenation.
168
+
169
+ Coming from a path library using `+` as #join, one should just use the default (`Path + :warning`),
170
+ which will show were `+` is used as #join.
171
+
147
172
  ## Status
148
173
 
149
174
  This is still in the early development stage, you should expect many additions and some changes.
@@ -103,6 +103,41 @@ class Path
103
103
  result/path if result
104
104
  end
105
105
 
106
+ # Relocates this path somewhere else.
107
+ #
108
+ # Without a block, this method is a simple shorcut for a longer
109
+ # expression that proves difficult to remember in practice:
110
+ #
111
+ # to / (self.sub_ext(new_ext) % from)
112
+ #
113
+ # That is, it relocates the original path to a target folder +to+
114
+ # appended with the relative path from a source folder +from+. An
115
+ # optional new extension can also be specified, as it is a common
116
+ # use case.
117
+ #
118
+ # With a block, the relative path is passed to the block for user
119
+ # update, without the last extension. +new_ext+ is added after (or
120
+ # the original extension if not provided).
121
+ #
122
+ # from = Path('pictures')
123
+ # to = Path('output/public/thumbnails')
124
+ # earth = from / 'nature/earth.jpg'
125
+ #
126
+ # earth.relocate(from, to)
127
+ # # => #<Path output/public/thumbnails/nature/earth.jpg>
128
+ #
129
+ # earth.relocate(from, to, '.png') { |rel|
130
+ # "#{rel}-200"
131
+ # }
132
+ # # => #<Path output/public/thumbnails/nature/earth-200.png>
133
+ def relocate(from, to, new_ext = ext, &updater)
134
+ updater ||= lambda { |path| path }
135
+ renamer = lambda { |rel|
136
+ Path(updater.call(rel.rm_ext)).add_ext(new_ext)
137
+ }
138
+ to / renamer.call(self % from)
139
+ end
140
+
106
141
  # Setup
107
142
  register_loader 'yml', 'yaml' do |path|
108
143
  require 'yaml'
@@ -33,12 +33,6 @@ class Path
33
33
  end
34
34
  alias :safe_unlink :rm_f
35
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
41
-
42
36
  # Removes the file or directory recursively, ignoring errors,
43
37
  # using +FileUtils.rm_f+.
44
38
  def rm_rf
@@ -22,18 +22,27 @@ class Path
22
22
 
23
23
  # @!group Identity
24
24
 
25
+ # Returns the +path+ as a String.
26
+ # {#path} is implemented for better readability (+file.path+ instead of +file.to_s+) and as an accessor.
27
+ # {#to_path} is implemented so Path objects are usable with +open+, etc.
28
+ # {#to_str} is implemented so Path objects are usable with +open+, etc with Ruby 1.8 (it is not defined in Ruby 1.9).
29
+ attr_reader :path
30
+ alias :to_s :path
31
+ alias :to_path :path
32
+ alias :to_str :path if RUBY_VERSION < '1.9'
33
+
25
34
  # Compare this path with +other+. The comparison is string-based.
26
35
  # Be aware that two different paths (+foo.txt+ and +./foo.txt+)
27
36
  # can refer to the same file.
28
37
  def == other
29
- Path === other and @path == other.to_path
38
+ Path === other and @path == other.path
30
39
  end
31
40
  alias :eql? :==
32
41
 
33
42
  # Provides for comparing paths, case-sensitively.
34
43
  def <=>(other)
35
44
  return nil unless Path === other
36
- @path.tr('/', "\0") <=> other.to_s.tr('/', "\0")
45
+ @path.tr('/', "\0") <=> other.path.tr('/', "\0")
37
46
  end
38
47
 
39
48
  # The hash value of the +path+.
@@ -41,17 +50,6 @@ class Path
41
50
  @path.hash
42
51
  end
43
52
 
44
- # Returns the +path+ as a String.
45
- def to_s
46
- @path
47
- end
48
-
49
- # to_path is implemented so Path objects are usable with File.open, etc.
50
- alias :to_path :to_s
51
-
52
- # to_str is implemented so Path objects are usable with File.open, etc in Ruby 1.8.
53
- alias :to_str :to_s if RUBY_VERSION < '1.9'
54
-
55
53
  # Returns the +path+ as a Symbol.
56
54
  def to_sym
57
55
  @path.to_sym
@@ -39,6 +39,19 @@ class Path
39
39
  init
40
40
  end
41
41
 
42
+ # JSON dumping.
43
+ def to_json(*args)
44
+ {
45
+ 'json_class' => 'Path',
46
+ 'data' => @path
47
+ }.to_json(*args)
48
+ end
49
+
50
+ # JSON loading.
51
+ def self.json_create json
52
+ new json['data']
53
+ end
54
+
42
55
  # Marshal dumping.
43
56
  def marshal_dump
44
57
  @path
@@ -50,20 +63,17 @@ class Path
50
63
  init
51
64
  end
52
65
 
53
- # Returns clean path of +self+ with consecutive slashes and useless dots removed.
66
+ # Returns a cleaned version of +self+ with consecutive slashes and useless dots removed.
54
67
  # The filesystem is not accessed.
55
68
  #
56
69
  # If +consider_symlink+ is +true+, then a more conservative algorithm is used
57
70
  # to avoid breaking symbolic linkages. This may retain more +..+
58
71
  # entries than absolutely necessary, but without accessing the filesystem,
59
72
  # this can't be avoided. See {#realpath}.
60
- def cleanpath(consider_symlink=false)
61
- if consider_symlink
62
- cleanpath_conservative
63
- else
64
- cleanpath_aggressive
65
- end
73
+ def clean(consider_symlink = false)
74
+ consider_symlink ? cleanpath_conservative : cleanpath_aggressive
66
75
  end
76
+ alias :cleanpath :clean
67
77
 
68
78
  # #parent returns the parent directory.
69
79
  # This can be chained.
@@ -81,12 +91,58 @@ class Path
81
91
  def /(other)
82
92
  Path.new(plus(@path, other.to_s))
83
93
  end
84
- alias :+ :/
85
94
 
86
- # Path#join joins paths.
95
+ # Configures the behavior of {Path#+}. The default is +:warning+.
96
+ #
97
+ # Path + :defined # aliased to Path#/
98
+ # Path + :warning # calls Path#/ but warns
99
+ # Path + :error # not defined
100
+ # Path + :string # like String#+. Warns if $VERBOSE (-w)
101
+ #
102
+ # @param config [:defined, :warning, :error, :string] the configuration value
103
+ def Path.+(config)
104
+ unless [:defined, :warning, :error, :string].include? config
105
+ raise ArgumentError, "Invalid configuration: #{config.inspect}"
106
+ end
107
+ if @plus_configured
108
+ raise "Path.+ has already been called: #{@plus_configured}"
109
+ end
110
+ remove_method :+ if method_defined? :+
111
+ case config
112
+ when :defined
113
+ alias :+ :/
114
+ when :warning
115
+ def +(other)
116
+ warn 'Warning: use of deprecated Path#+ as Path#/: ' <<
117
+ "#{inspect} + #{other.inspect}\n#{caller.first}"
118
+ self / other
119
+ end
120
+ when :error
121
+ # nothing to do, the method has been removed
122
+ when :string
123
+ def +(other)
124
+ warn 'Warning: use of deprecated Path#+ as String#+: ' <<
125
+ "#{inspect} + #{other.inspect}\n#{caller.first}" if $VERBOSE
126
+ Path(to_s + other.to_s)
127
+ end
128
+ end
129
+ @plus_configured = caller.first
130
+ end
131
+
132
+ @plus_configured = nil # Initialization
133
+ Path + :warning
134
+ @plus_configured = nil # Let the user overrides this default configuration
135
+
136
+ # @!method +(other)
137
+ # The behavior depends on the configuration with Path.{Path.+}.
138
+ # It might behave as {Path#/}, String#+, give warnings,
139
+ # or not be defined at all.
140
+
141
+ # Joins paths.
87
142
  #
88
- # <tt>path0.join(path1, ..., pathN)</tt> is the same as
89
- # <tt>path0 / path1 / ... / pathN</tt>.
143
+ # path0.join(path1, ..., pathN)
144
+ # # is the same as
145
+ # path0 / path1 / ... / pathN
90
146
  def join(*args)
91
147
  args.unshift self
92
148
  result = Path.new(args.pop)
@@ -107,8 +163,8 @@ class Path
107
163
  #
108
164
  # ArgumentError is raised when it cannot find a relative path.
109
165
  def relative_path_from(base_directory)
110
- dest_directory = cleanpath.to_s
111
- base_directory = Path.new(base_directory).cleanpath.to_s
166
+ dest_directory = clean.path
167
+ base_directory = Path.new(base_directory).clean.path
112
168
  dest_prefix = dest_directory
113
169
  dest_names = []
114
170
  while r = chop_basename(dest_prefix)
@@ -148,12 +204,12 @@ class Path
148
204
 
149
205
  # remove the leading . of +ext+ if present.
150
206
  def pure_ext(ext)
151
- ext.start_with?('.') ? ext[1..-1] : ext
207
+ ext = ext.to_s and ext.start_with?('.') ? ext[1..-1] : ext
152
208
  end
153
209
 
154
210
  # add a leading . to +ext+ if missing. Returns '' if +ext+ is empty.
155
211
  def dotted_ext(ext)
156
- (ext.empty? or ext.start_with?('.')) ? ext : ".#{ext}"
212
+ ext = ext.to_s and (ext.empty? or ext.start_with?('.')) ? ext : ".#{ext}"
157
213
  end
158
214
  end
159
215
 
@@ -14,20 +14,22 @@ class Path
14
14
  end
15
15
  alias :lines :each_line
16
16
 
17
- # Returns all data from the file, or the first +N+ bytes if specified.
17
+ # Returns all data from the file, or the first +bytes+ bytes if specified.
18
18
  # See +IO.read+.
19
19
  def read(*args)
20
20
  IO.read(@path, *args)
21
21
  end
22
22
 
23
- # Returns all the bytes from the file, or the first +N+ if specified.
24
- # See +IO.binread+.
25
23
  if IO.respond_to? :binread
24
+ # Returns all the bytes from the file, or the first +N+ if specified.
25
+ # See +IO.binread+.
26
26
  def binread(*args)
27
27
  IO.binread(@path, *args)
28
28
  end
29
29
  else
30
- alias :binread :read
30
+ def binread(*args)
31
+ open('rb', &:read)
32
+ end
31
33
  end
32
34
 
33
35
  # Returns all the lines from the file. See +IO.readlines+.
@@ -51,6 +53,17 @@ class Path
51
53
  end
52
54
  end
53
55
 
56
+ if IO.respond_to? :binwrite
57
+ # Writes +contents+ to +self+. See +IO.binwrite+.
58
+ def binwrite(contents, *open_args)
59
+ IO.binwrite(@path, contents, *open_args)
60
+ end
61
+ else
62
+ def binwrite(contents, *open_args)
63
+ open('wb', *open_args) { |f| f.write(contents) }
64
+ end
65
+ end
66
+
54
67
  if IO.respond_to? :write and !RUBY_DESCRIPTION.start_with?('jruby')
55
68
  # Appends +contents+ to +self+. See +IO.write+ or +IO#write+.
56
69
  def append(contents, open_args = {})
@@ -62,4 +75,20 @@ class Path
62
75
  open('a', *open_args) { |f| f.write(contents) }
63
76
  end
64
77
  end
78
+
79
+ # Returns the first +bytes+ bytes of the file.
80
+ # If the file size is smaller than +bytes+, return the whole contents.
81
+ def head(bytes)
82
+ read(bytes)
83
+ end
84
+
85
+ # Returns the last +bytes+ bytes of the file.
86
+ # If the file size is smaller than +bytes+, return the whole contents.
87
+ def tail(bytes)
88
+ return read if size < bytes
89
+ open { |f|
90
+ f.seek(-bytes, IO::SEEK_END)
91
+ f.read
92
+ }
93
+ end
65
94
  end
@@ -44,7 +44,7 @@ class Path
44
44
  #
45
45
  # Path('file').add_extension('txt') # => #<Path file.txt>
46
46
  def add_extension(ext)
47
- return self if ext.empty?
47
+ return self if ext.to_s.empty?
48
48
  Path.new @path+dotted_ext(ext)
49
49
  end
50
50
  alias :add_ext :add_extension
@@ -64,7 +64,7 @@ class Path
64
64
  #
65
65
  # Path('main.c++').replace_extension('cc') # => #<Path main.cc>
66
66
  def replace_extension(ext)
67
- return without_extension if ext.empty?
67
+ return without_extension if ext.to_s.empty?
68
68
  Path.new(@path[0..-extname.size-1] << dotted_ext(ext))
69
69
  end
70
70
  alias :sub_ext :replace_extension
@@ -17,6 +17,6 @@ class Path
17
17
  # It is not a real private method because {Path.require_tree}
18
18
  # (so the {Path} class) needs to be able to call it.
19
19
  def require_tree(source = nil)
20
- glob('**/*.rb').sort.each { |file| require file.expand(dir).to_s unless file == source }
20
+ glob('**/*.rb').sort.each { |file| require file.expand(dir).path unless file == source }
21
21
  end
22
22
  end
@@ -1,5 +1,5 @@
1
1
  class Path
2
2
  # The version of the gem.
3
3
  # Set here to avoid duplication and allow introspection.
4
- VERSION = '0.2.0'
4
+ VERSION = '0.3.0'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: epath
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-06-01 00:00:00.000000000 Z
12
+ date: 2012-06-12 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec