epath 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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