epitools 0.4.49 → 0.5.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/TODO +16 -0
- data/VERSION +1 -1
- data/epitools.gemspec +14 -7
- data/lib/epitools.rb +3 -2
- data/lib/epitools/autoloads.rb +11 -1
- data/lib/epitools/basetypes.rb +220 -71
- data/lib/epitools/browser.rb +1 -0
- data/lib/epitools/clitools.rb +7 -3
- data/lib/epitools/colored.rb +45 -23
- data/lib/epitools/ezdb.rb +100 -0
- data/lib/epitools/path.rb +354 -80
- data/lib/epitools/term.rb +147 -0
- data/lib/epitools/trie.rb +422 -0
- data/lib/epitools/zopen.rb +2 -2
- data/spec/basetypes_spec.rb +42 -2
- data/spec/ezdb_spec.rb +47 -0
- data/spec/path_spec.rb +49 -3
- data/spec/rash_spec.rb +1 -1
- data/spec/term_spec.rb +35 -0
- metadata +16 -11
data/lib/epitools/browser.rb
CHANGED
data/lib/epitools/clitools.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require 'epitools'
|
1
|
+
#require 'epitools'
|
2
2
|
|
3
3
|
class String
|
4
4
|
|
@@ -67,8 +67,9 @@ def lesspipe(*args)
|
|
67
67
|
yield less
|
68
68
|
end
|
69
69
|
end
|
70
|
-
|
71
|
-
rescue Errno::EPIPE
|
70
|
+
|
71
|
+
rescue Errno::EPIPE, Interrupt
|
72
|
+
# less just quit -- eat the exception.
|
72
73
|
end
|
73
74
|
|
74
75
|
|
@@ -175,3 +176,6 @@ def autoinstall(*packages)
|
|
175
176
|
cmd(["sudo apt-get install ?", packages.join(' ')])
|
176
177
|
end
|
177
178
|
end
|
179
|
+
|
180
|
+
|
181
|
+
|
data/lib/epitools/colored.rb
CHANGED
@@ -1,28 +1,49 @@
|
|
1
|
-
require 'Win32/Console/ANSI' if RUBY_PLATFORM =~ /win32/
|
2
|
-
require 'set'
|
3
|
-
|
4
1
|
#
|
5
|
-
# ANSI Colour-coding for terminals that support it.
|
2
|
+
# ANSI Colour-coding (for terminals that support it.)
|
3
|
+
#
|
6
4
|
# Originally by defunkt (Chris Wanstrath)
|
5
|
+
# Enhanced by epitron (Chris Gahan)
|
6
|
+
#
|
7
|
+
# It adds methods to String to allow easy coloring.
|
8
|
+
#
|
9
|
+
# (Note: Colors are automatically disabled if your program is piped to another program,
|
10
|
+
# ie: if STDOUT is not a TTY)
|
11
|
+
#
|
12
|
+
# Basic examples:
|
7
13
|
#
|
8
|
-
#
|
14
|
+
# >> "this is red".red
|
15
|
+
# >> "this is red with a blue background (read: ugly)".red_on_blue
|
16
|
+
# >> "this is light blue".light_blue
|
17
|
+
# >> "this is red with an underline".red.underline
|
18
|
+
# >> "this is really bold and really blue".bold.blue
|
9
19
|
#
|
10
|
-
#
|
20
|
+
# Color tags:
|
21
|
+
# (Note: You don't *need* to close color tags, but you can!)
|
11
22
|
#
|
12
|
-
#
|
13
|
-
#
|
14
|
-
#
|
23
|
+
# >> "<yellow>This is using <green>color tags</green> to colorize.".colorize
|
24
|
+
# >> "<1>N<9>u<11>m<15>eric tags!".colorize
|
25
|
+
# (For those who still remember the DOS color palette and want more terse tagged-colors.)
|
15
26
|
#
|
16
|
-
#
|
27
|
+
# Highlight search results:
|
17
28
|
#
|
18
|
-
#
|
29
|
+
# >> string.gsub(pattern) { |match| "<yellow>#{match}</yellow>" }.colorize
|
19
30
|
#
|
20
|
-
#
|
31
|
+
# Forcing colors:
|
21
32
|
#
|
22
|
-
#
|
33
|
+
# Since the presence of a terminal is detected automatically, the colors will be
|
34
|
+
# disabled when you pipe your program to another program. However, if you want to
|
35
|
+
# show colors when piped (eg: when you pipe to `less -R`), you can force it:
|
23
36
|
#
|
24
|
-
#
|
37
|
+
# >> Colored.enable!
|
38
|
+
# >> Colored.disable!
|
39
|
+
# >> Colored.enable_temporarily { puts "whee!".red }
|
25
40
|
#
|
41
|
+
|
42
|
+
require 'set'
|
43
|
+
require 'rbconfig'
|
44
|
+
require 'Win32/Console/ANSI' if RbConfig::CONFIG['host_os'] =~ /mswin|mingw/
|
45
|
+
#require 'Win32/Console/ANSI' if RUBY_PLATFORM =~ /win32/
|
46
|
+
|
26
47
|
module Colored
|
27
48
|
extend self
|
28
49
|
|
@@ -127,7 +148,7 @@ module Colored
|
|
127
148
|
end
|
128
149
|
|
129
150
|
#
|
130
|
-
#
|
151
|
+
# Colorize a string (this method is called by #red, #blue, #red_on_green, etc.)
|
131
152
|
#
|
132
153
|
# Accepts options:
|
133
154
|
# :foreground
|
@@ -137,6 +158,15 @@ module Colored
|
|
137
158
|
# :extra
|
138
159
|
# Extra styling, like 'bold', 'light', 'underline', 'reversed', or 'clear'.
|
139
160
|
#
|
161
|
+
#
|
162
|
+
# With no options, it uses tagged colors:
|
163
|
+
#
|
164
|
+
# puts "<light_green><magenta>*</magenta> Hey mom! I am <light_blue>SO</light_blue> colored right now.</light_green>".colorize
|
165
|
+
#
|
166
|
+
# Or numeric ANSI tagged colors (from the BBS days):
|
167
|
+
# puts "<10><5>*</5> Hey mom! I am <9>SO</9> colored right now.</10>".colorize
|
168
|
+
#
|
169
|
+
#
|
140
170
|
def colorize(string=nil, options = {})
|
141
171
|
if string == nil
|
142
172
|
return self.tagged_colors
|
@@ -227,14 +257,6 @@ module Colored
|
|
227
257
|
#
|
228
258
|
# Colorize a string that has "color tags".
|
229
259
|
#
|
230
|
-
# Examples:
|
231
|
-
#
|
232
|
-
# Colors as words:
|
233
|
-
# puts "<light_green><magenta>*</magenta> Hey mom! I am <light_blue>SO</light_blue> colored right now.</light_green>".colorize
|
234
|
-
#
|
235
|
-
# Numeric ANSI colors (from the BBS days):
|
236
|
-
# puts "<10><5>*</5> Hey mom! I am <9>SO</9> colored right now.</10>".colorize
|
237
|
-
#
|
238
260
|
def tagged_colors
|
239
261
|
stack = []
|
240
262
|
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'epitools'
|
2
|
+
require 'dbm'
|
3
|
+
require 'delegate'
|
4
|
+
|
5
|
+
class Ezdc < DelegateClass(Hash)
|
6
|
+
|
7
|
+
attr_reader :db, :path, :dirty
|
8
|
+
|
9
|
+
@@dirty = Set.new
|
10
|
+
|
11
|
+
def initialize(filename)
|
12
|
+
@path = Path[filename]
|
13
|
+
|
14
|
+
if @path.ext.nil?
|
15
|
+
@path.ext = "db"
|
16
|
+
else
|
17
|
+
@path.ext += ".db" if @path.ext != 'db'
|
18
|
+
end
|
19
|
+
|
20
|
+
@db = DBM::open(@path.with(:ext=>nil))
|
21
|
+
|
22
|
+
super
|
23
|
+
end
|
24
|
+
|
25
|
+
class Observed < BasicObject
|
26
|
+
MUTATORS = ::Set.new [
|
27
|
+
:<<, :push, :pop, :slice, :[]=
|
28
|
+
]
|
29
|
+
|
30
|
+
def __send__(meth, *args)
|
31
|
+
if MUTATORS.include? meth
|
32
|
+
@@dirty.add self
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def observed(obj)
|
39
|
+
obj.using(Observed)
|
40
|
+
end
|
41
|
+
|
42
|
+
def [](key)
|
43
|
+
observed(super[key])
|
44
|
+
end
|
45
|
+
|
46
|
+
def []=(key, val)
|
47
|
+
end
|
48
|
+
|
49
|
+
def keys
|
50
|
+
db.keys.map(&:unmarshal)
|
51
|
+
end
|
52
|
+
|
53
|
+
def delete!
|
54
|
+
@path.rm
|
55
|
+
end
|
56
|
+
|
57
|
+
def flush!
|
58
|
+
dirty.each do |key|
|
59
|
+
db[key.marshal] = super[key].marshal
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
class Ezdb
|
67
|
+
|
68
|
+
attr_reader :db, :path
|
69
|
+
|
70
|
+
def initialize(filename)
|
71
|
+
@path = Path[filename]
|
72
|
+
|
73
|
+
if @path.ext.nil?
|
74
|
+
@path.ext = "db"
|
75
|
+
else
|
76
|
+
@path.ext += ".db" if @path.ext != 'db'
|
77
|
+
end
|
78
|
+
|
79
|
+
@db = DBM::open(@path.with(:ext=>nil))
|
80
|
+
end
|
81
|
+
|
82
|
+
def [](key)
|
83
|
+
val = db[key.marshal]
|
84
|
+
val = val.unmarshal if val
|
85
|
+
val
|
86
|
+
end
|
87
|
+
|
88
|
+
def []=(key, val)
|
89
|
+
db[key.marshal] = val.marshal
|
90
|
+
end
|
91
|
+
|
92
|
+
def keys
|
93
|
+
db.keys.map(&:unmarshal)
|
94
|
+
end
|
95
|
+
|
96
|
+
def delete!
|
97
|
+
@path.rm
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
data/lib/epitools/path.rb
CHANGED
@@ -1,5 +1,58 @@
|
|
1
|
+
#
|
2
|
+
# TODOs:
|
3
|
+
# Relative paths
|
4
|
+
# Rename bugs
|
5
|
+
# tmp bugs
|
6
|
+
#
|
7
|
+
|
1
8
|
require 'epitools'
|
2
9
|
|
10
|
+
#
|
11
|
+
# Path: An object-oriented wrapper for files.
|
12
|
+
# (Combines useful methods from FileUtils, File, Dir, and more!)
|
13
|
+
#
|
14
|
+
# Each Path object has the following attributes:
|
15
|
+
#
|
16
|
+
# path => the entire path
|
17
|
+
# filename => just the name and extension
|
18
|
+
# basename => just the filename
|
19
|
+
# ext => just the extension
|
20
|
+
# dir => just the directory
|
21
|
+
# dirs => an array of directories
|
22
|
+
#
|
23
|
+
# Note: all of the above attributes can be modified to produce new paths!
|
24
|
+
# Here's a useful example:
|
25
|
+
#
|
26
|
+
# # Check if there's a '.git' directory in the current or parent directories.
|
27
|
+
# def inside_a_git_repository?
|
28
|
+
# path = Path.pwd # get the current directory
|
29
|
+
# while path.dirs.any?
|
30
|
+
# return true if (path/".git").exists?
|
31
|
+
# path.dirs.pop
|
32
|
+
# end
|
33
|
+
# false
|
34
|
+
# end
|
35
|
+
#
|
36
|
+
#
|
37
|
+
# Examples:
|
38
|
+
#
|
39
|
+
# Path["*.jpeg"].each { |path| path.rename(:ext=>"jpg") }
|
40
|
+
#
|
41
|
+
# Path["filename.txt"] << "Append data!"
|
42
|
+
#
|
43
|
+
# entries = Path["/etc"].ls
|
44
|
+
#
|
45
|
+
# Path
|
46
|
+
#
|
47
|
+
# Swap two files:
|
48
|
+
#
|
49
|
+
# a, b = Path["file_a", "file_b"]
|
50
|
+
# temp = a.with(:ext=>a.ext+".swapping")
|
51
|
+
# a.mv(temp)
|
52
|
+
# b.mv(a)
|
53
|
+
# temp.mv(b)
|
54
|
+
#
|
55
|
+
#
|
3
56
|
class Path
|
4
57
|
|
5
58
|
## initializers
|
@@ -13,7 +66,6 @@ class Path
|
|
13
66
|
end
|
14
67
|
|
15
68
|
def self.[](path)
|
16
|
-
|
17
69
|
case path
|
18
70
|
when Path
|
19
71
|
path
|
@@ -21,17 +73,22 @@ class Path
|
|
21
73
|
|
22
74
|
if path =~ %r{^[a-z\-]+://}i # URL?
|
23
75
|
Path::URL.new(path)
|
76
|
+
elsif path =~ /^javascript:/
|
77
|
+
Path::JS.new(path)
|
24
78
|
else
|
25
|
-
|
79
|
+
|
80
|
+
# todo: highlight backgrounds of codeblocks to show indent level & put boxes (or rules?) around (between?) double-spaced regions
|
81
|
+
|
82
|
+
path = Path.expand_path(path)
|
26
83
|
if path =~ /(^|[^\\])[\?\*\{\}]/ # contains unescaped glob chars?
|
27
84
|
glob(path)
|
28
85
|
else
|
29
86
|
new(path)
|
30
87
|
end
|
88
|
+
|
31
89
|
end
|
32
90
|
|
33
91
|
end
|
34
|
-
|
35
92
|
end
|
36
93
|
|
37
94
|
## setters
|
@@ -77,6 +134,8 @@ class Path
|
|
77
134
|
@dirs = File.expand_path(newdir).split(File::SEPARATOR)[1..-1]
|
78
135
|
end
|
79
136
|
|
137
|
+
# TODO: Figure out how to fix the 'path.with(:ext=>ext+".other")' problem (when 'ext == nil')...
|
138
|
+
|
80
139
|
def ext=(newext)
|
81
140
|
if newext.blank?
|
82
141
|
@ext = nil
|
@@ -107,6 +166,28 @@ class Path
|
|
107
166
|
end
|
108
167
|
end
|
109
168
|
|
169
|
+
def relative_to(anchor=nil)
|
170
|
+
anchor ||= Path.pwd
|
171
|
+
|
172
|
+
# operations to transform anchor into self
|
173
|
+
|
174
|
+
# stage 1: go ".." until we find a common dir prefix
|
175
|
+
# (discard everything and go '/' if there's no common dir)
|
176
|
+
# stage 2: append the rest of the other path
|
177
|
+
|
178
|
+
# find common prefix
|
179
|
+
smaller, bigger = [ anchor.dirs, self.dirs ].sort_by(&:size)
|
180
|
+
common_prefix_end = bigger.zip(smaller).index { |a,b | a != b }
|
181
|
+
common_prefix = bigger[0...common_prefix_end]
|
182
|
+
|
183
|
+
if common_prefix.any?
|
184
|
+
dots = nil
|
185
|
+
end
|
186
|
+
|
187
|
+
self.dirs & anchor.dirs
|
188
|
+
|
189
|
+
end
|
190
|
+
|
110
191
|
# The current directory (with a trailing /)
|
111
192
|
def dir
|
112
193
|
if dirs
|
@@ -168,6 +249,10 @@ class Path
|
|
168
249
|
File.symlink? path
|
169
250
|
end
|
170
251
|
|
252
|
+
def broken_symlink?
|
253
|
+
File.symlink?(path) and not File.exists?(path)
|
254
|
+
end
|
255
|
+
|
171
256
|
def uri?
|
172
257
|
false
|
173
258
|
end
|
@@ -177,32 +262,23 @@ class Path
|
|
177
262
|
end
|
178
263
|
|
179
264
|
|
180
|
-
## aliases
|
181
|
-
|
182
|
-
alias_method :to_path, :path
|
183
|
-
alias_method :to_str, :path
|
184
|
-
alias_method :to_s, :path
|
185
|
-
|
186
|
-
alias_method :pathname, :path
|
187
|
-
alias_method :basename, :base
|
188
|
-
alias_method :basename=, :base=
|
189
|
-
alias_method :extname, :ext
|
190
|
-
alias_method :extname=, :ext=
|
191
|
-
alias_method :dirname, :dir
|
192
|
-
alias_method :dirname=, :dir=
|
193
|
-
alias_method :extension, :ext
|
194
|
-
alias_method :extension=, :ext=
|
195
|
-
alias_method :directory, :dir
|
196
|
-
alias_method :directory=, :dir=
|
197
|
-
|
198
|
-
alias_method :directory?, :dir?
|
199
|
-
|
200
265
|
## comparisons
|
201
266
|
|
202
267
|
include Comparable
|
203
268
|
|
204
269
|
def <=>(other)
|
205
|
-
|
270
|
+
case other
|
271
|
+
when Path
|
272
|
+
self.path <=> other.path
|
273
|
+
when String
|
274
|
+
self.path == other
|
275
|
+
else
|
276
|
+
raise "Invalid comparison: Path to #{other.class}"
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
def ==(other)
|
281
|
+
self.path == other.to_s
|
206
282
|
end
|
207
283
|
|
208
284
|
|
@@ -212,7 +288,10 @@ class Path
|
|
212
288
|
# Path["/etc"]/"passwd" == Path["/etc/passwd"]
|
213
289
|
#
|
214
290
|
def /(other)
|
215
|
-
|
291
|
+
# / <- fixes jedit syntax highlighting bug.
|
292
|
+
# TODO: make it work for "/dir/dir"/"/dir/file"
|
293
|
+
#Path.new( File.join(self, other) )
|
294
|
+
Path[ File.join(self, other) ]
|
216
295
|
end
|
217
296
|
|
218
297
|
## opening/reading files
|
@@ -230,11 +309,31 @@ class Path
|
|
230
309
|
def read(length=nil, offset=nil)
|
231
310
|
File.read(path, length, offset)
|
232
311
|
end
|
312
|
+
|
313
|
+
#
|
314
|
+
# All the lines in this file, chomped.
|
315
|
+
#
|
316
|
+
def lines
|
317
|
+
io.lines.map(&:chomp)
|
318
|
+
end
|
319
|
+
|
320
|
+
def unmarshal
|
321
|
+
read.unmarshal
|
322
|
+
end
|
233
323
|
|
234
324
|
def ls; Path[File.join(path, "*")]; end
|
235
325
|
|
236
326
|
def ls_r; Path[File.join(path, "**/*")]; end
|
237
327
|
|
328
|
+
|
329
|
+
def siblings
|
330
|
+
ls - [self]
|
331
|
+
end
|
332
|
+
|
333
|
+
def touch
|
334
|
+
open("a") { }
|
335
|
+
self
|
336
|
+
end
|
238
337
|
|
239
338
|
## modifying files
|
240
339
|
|
@@ -248,7 +347,8 @@ class Path
|
|
248
347
|
else
|
249
348
|
yield f
|
250
349
|
end
|
251
|
-
end
|
350
|
+
end
|
351
|
+
self
|
252
352
|
end
|
253
353
|
alias_method :<<, :append
|
254
354
|
|
@@ -272,39 +372,68 @@ class Path
|
|
272
372
|
# Path["Songy Song.aac"].rename(:dir=>"/music2")
|
273
373
|
# Path["/music2/Songy Song.aac"].exists? #=> true
|
274
374
|
#
|
375
|
+
def rename!(options)
|
376
|
+
raise "Broken!"
|
377
|
+
|
378
|
+
dest = rename(options)
|
379
|
+
self.path = dest.path # become dest
|
380
|
+
self
|
381
|
+
end
|
382
|
+
|
275
383
|
def rename(options)
|
384
|
+
raise "Broken!"
|
385
|
+
|
276
386
|
raise "Options must be a Hash" unless options.is_a? Hash
|
277
387
|
dest = self.with(options)
|
278
388
|
|
279
389
|
raise "Error: destination (#{dest.inspect}) already exists" if dest.exists?
|
280
390
|
File.rename(path, dest)
|
281
391
|
|
282
|
-
|
392
|
+
dest
|
283
393
|
end
|
284
394
|
|
285
395
|
#
|
286
396
|
# Renames the file the specified full path (like Dir.rename.)
|
287
397
|
#
|
288
398
|
def rename_to(path)
|
289
|
-
|
399
|
+
raise "Broken!"
|
400
|
+
|
401
|
+
rename :path=>path.to_s
|
290
402
|
end
|
291
403
|
alias_method :mv, :rename_to
|
292
404
|
|
405
|
+
def rename_to!(path)
|
406
|
+
raise "Broken!"
|
407
|
+
rename! :path=>path.to_s
|
408
|
+
end
|
409
|
+
alias_method :mv!, :rename_to!
|
410
|
+
|
411
|
+
def reload!
|
412
|
+
self.path = to_s
|
413
|
+
end
|
414
|
+
|
415
|
+
#
|
416
|
+
# Generate two almost identical methods: mkdir and mkdir_p
|
417
|
+
#
|
293
418
|
{
|
294
419
|
:mkdir => "Dir.mkdir",
|
295
420
|
:mkdir_p =>"FileUtils.mkdir_p"
|
296
|
-
}.each do |method,
|
421
|
+
}.each do |method, command|
|
297
422
|
class_eval %{
|
298
423
|
def #{method}
|
299
424
|
if exists?
|
300
425
|
if directory?
|
301
|
-
|
426
|
+
Path[path]
|
302
427
|
else
|
303
|
-
raise "Error:
|
428
|
+
raise "Error: A file by this name already exists."
|
304
429
|
end
|
305
430
|
else
|
306
|
-
#{
|
307
|
-
|
431
|
+
#{command}(path)
|
432
|
+
#Path[path]
|
433
|
+
p [:path, path]
|
434
|
+
self.path = path # regenerate object
|
435
|
+
p [:path, path]
|
436
|
+
self
|
308
437
|
end
|
309
438
|
end
|
310
439
|
}
|
@@ -314,6 +443,10 @@ class Path
|
|
314
443
|
FileUtils.cp_r(path, dest) #if Path[dest].exists?
|
315
444
|
end
|
316
445
|
|
446
|
+
def mv(dest)
|
447
|
+
FileUtils.mv(path, dest)
|
448
|
+
end
|
449
|
+
|
317
450
|
def join(other)
|
318
451
|
if uri?
|
319
452
|
Path[URI.join(path, other).to_s]
|
@@ -322,11 +455,44 @@ class Path
|
|
322
455
|
end
|
323
456
|
end
|
324
457
|
|
458
|
+
|
325
459
|
def ln_s(dest)
|
326
460
|
dest = Path[dest]
|
327
461
|
FileUtils.ln_s self, dest
|
328
462
|
end
|
329
463
|
|
464
|
+
## Owners and permissions
|
465
|
+
|
466
|
+
def chmod(mode)
|
467
|
+
FileUtils.chmod(mode, self)
|
468
|
+
self
|
469
|
+
end
|
470
|
+
|
471
|
+
def chown(usergroup)
|
472
|
+
user, group = usergroup.split(":")
|
473
|
+
FileUtils.chown(user, group, self)
|
474
|
+
self
|
475
|
+
end
|
476
|
+
|
477
|
+
def chmod_R(mode)
|
478
|
+
if directory?
|
479
|
+
FileUtils.chmod_R(mode, self)
|
480
|
+
self
|
481
|
+
else
|
482
|
+
raise "Not a directory."
|
483
|
+
end
|
484
|
+
end
|
485
|
+
|
486
|
+
def chown_R(usergroup)
|
487
|
+
user, group = usergroup.split(":")
|
488
|
+
if directory?
|
489
|
+
FileUtils.chown_R(user, group, self)
|
490
|
+
self
|
491
|
+
else
|
492
|
+
raise "Not a directory."
|
493
|
+
end
|
494
|
+
end
|
495
|
+
|
330
496
|
## Dangerous methods.
|
331
497
|
|
332
498
|
def rm
|
@@ -341,7 +507,7 @@ class Path
|
|
341
507
|
alias_method :"remove!", :rm
|
342
508
|
|
343
509
|
def truncate(offset=0)
|
344
|
-
File.truncate(self, offset)
|
510
|
+
File.truncate(self, offset) if exists?
|
345
511
|
end
|
346
512
|
|
347
513
|
|
@@ -412,27 +578,25 @@ class Path
|
|
412
578
|
alias_method :stream, :io
|
413
579
|
|
414
580
|
def =~(pattern)
|
415
|
-
|
581
|
+
to_s =~ pattern
|
582
|
+
end
|
583
|
+
|
584
|
+
def lstat
|
585
|
+
#@lstat ||= File.lstat self
|
586
|
+
File.lstat self
|
416
587
|
end
|
417
588
|
|
418
|
-
|
589
|
+
def mode
|
590
|
+
lstat.mode
|
591
|
+
end
|
419
592
|
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
rm
|
427
|
-
truncate
|
428
|
-
].each do |method|
|
429
|
-
class_eval %{
|
430
|
-
def self.#{method}(path)
|
431
|
-
Path[path].#{method}
|
432
|
-
end
|
433
|
-
}
|
593
|
+
def parent
|
594
|
+
if file?
|
595
|
+
with(:filename=>nil)
|
596
|
+
else
|
597
|
+
with(:dirs=>dirs[0...-1])
|
598
|
+
end
|
434
599
|
end
|
435
|
-
|
436
600
|
|
437
601
|
# Mimetype finding and magic (requires 'mimemagic' gem)
|
438
602
|
|
@@ -458,6 +622,8 @@ class Path
|
|
458
622
|
open { |io| MimeMagic.by_magic(io) }
|
459
623
|
end
|
460
624
|
|
625
|
+
# TODO: rename type => magicext
|
626
|
+
|
461
627
|
#
|
462
628
|
# The filetype (as a standard file extension), verified with Magic.
|
463
629
|
#
|
@@ -467,31 +633,93 @@ class Path
|
|
467
633
|
# Note: Prefers long extensions (eg: jpeg over jpg)
|
468
634
|
#
|
469
635
|
def type
|
470
|
-
@
|
636
|
+
@cached_type ||= begin
|
471
637
|
|
472
|
-
|
473
|
-
magic = self.magic
|
638
|
+
if file? or symlink?
|
474
639
|
|
475
|
-
|
476
|
-
magic.
|
477
|
-
|
478
|
-
ext
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
640
|
+
ext = self.ext
|
641
|
+
magic = self.magic
|
642
|
+
|
643
|
+
if ext and magic
|
644
|
+
if magic.extensions.include? ext
|
645
|
+
ext
|
646
|
+
else
|
647
|
+
magic.ext # in case the supplied extension is wrong...
|
648
|
+
end
|
649
|
+
elsif !ext and magic
|
650
|
+
magic.ext
|
651
|
+
elsif ext and !magic
|
483
652
|
ext
|
484
|
-
else
|
485
|
-
|
653
|
+
else # !ext and !magic
|
654
|
+
:unknown
|
486
655
|
end
|
656
|
+
|
657
|
+
elsif dir?
|
658
|
+
:directory
|
487
659
|
end
|
488
660
|
|
489
|
-
end
|
661
|
+
end
|
490
662
|
end
|
491
663
|
|
664
|
+
## aliases
|
665
|
+
|
666
|
+
alias_method :to_path, :path
|
667
|
+
alias_method :to_str, :path
|
668
|
+
alias_method :to_s, :path
|
669
|
+
|
670
|
+
alias_method :pathname, :path
|
671
|
+
alias_method :basename, :base
|
672
|
+
alias_method :basename=, :base=
|
673
|
+
alias_method :extname, :ext
|
674
|
+
alias_method :extname=, :ext=
|
675
|
+
alias_method :dirname, :dir
|
676
|
+
alias_method :dirname=, :dir=
|
677
|
+
alias_method :extension, :ext
|
678
|
+
alias_method :extension=, :ext=
|
679
|
+
alias_method :directory, :dir
|
680
|
+
alias_method :directory=, :dir=
|
681
|
+
|
682
|
+
alias_method :directory?, :dir?
|
683
|
+
|
684
|
+
alias_method :exist?, :exists?
|
685
|
+
|
492
686
|
############################################################################
|
493
687
|
## Class Methods
|
494
688
|
|
689
|
+
#
|
690
|
+
# FileUtils-like class-method versions of instance methods
|
691
|
+
# (eg: `Path.mv(src, dest)`)
|
692
|
+
#
|
693
|
+
# Note: Methods with cardinality 1 (`method/1`) are instance methods that take
|
694
|
+
# one parameter, and hence, class methods that take two parameters.
|
695
|
+
#
|
696
|
+
AUTOGENERATED_CLASS_METHODS = %w[
|
697
|
+
mkdir
|
698
|
+
mkdir_p
|
699
|
+
sha1
|
700
|
+
sha2
|
701
|
+
md5
|
702
|
+
rm
|
703
|
+
truncate
|
704
|
+
mv/1
|
705
|
+
move/1
|
706
|
+
chmod/1
|
707
|
+
chown/1
|
708
|
+
chown_R/1
|
709
|
+
chmod_R/1
|
710
|
+
].each do |spec|
|
711
|
+
method, cardinality = spec.split("/")
|
712
|
+
cardinality = cardinality.to_i
|
713
|
+
|
714
|
+
class_eval %{
|
715
|
+
def self.#{method}(path#{", *args" if cardinality > 0})
|
716
|
+
Path[path].#{method}#{"(*args)" if cardinality > 0}
|
717
|
+
end
|
718
|
+
}
|
719
|
+
end
|
720
|
+
|
721
|
+
|
722
|
+
|
495
723
|
#
|
496
724
|
# Same as File.expand_path, except preserves the trailing '/'.
|
497
725
|
#
|
@@ -510,6 +738,7 @@ class Path
|
|
510
738
|
path
|
511
739
|
end
|
512
740
|
alias_class_method :tempfile, :tmpfile
|
741
|
+
alias_class_method :tmp, :tmpfile
|
513
742
|
|
514
743
|
def self.home
|
515
744
|
Path[ENV['HOME']]
|
@@ -548,13 +777,24 @@ class Path
|
|
548
777
|
PATH_SEPARATOR = ":"
|
549
778
|
BINARY_EXTENSION = ""
|
550
779
|
end
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
780
|
+
|
781
|
+
#
|
782
|
+
# A clone of `/usr/bin/which`: pass in the name of a binary, and it'll search the PATH
|
783
|
+
# returning the absolute location of the binary if it exists, or `nil` otherwise.
|
784
|
+
#
|
785
|
+
# (Note: If you pass more than one argument, it'll return an array of `Path`s instead of
|
786
|
+
# a single path.)
|
787
|
+
#
|
788
|
+
def self.which(bin, *extras)
|
789
|
+
if extras.empty?
|
790
|
+
ENV["PATH"].split(PATH_SEPARATOR).find do |path|
|
791
|
+
result = (Path[path] / (bin + BINARY_EXTENSION))
|
792
|
+
return result if result.exists?
|
793
|
+
end
|
794
|
+
nil
|
795
|
+
else
|
796
|
+
([bin] + extras).map { |bin| which(bin) }
|
556
797
|
end
|
557
|
-
nil
|
558
798
|
end
|
559
799
|
|
560
800
|
end
|
@@ -566,6 +806,10 @@ end
|
|
566
806
|
class Path::URL < Path
|
567
807
|
|
568
808
|
attr_reader :uri
|
809
|
+
|
810
|
+
#
|
811
|
+
# TODO: only include certain methods from Path (delegate style)
|
812
|
+
# (eg: remove commands that write)
|
569
813
|
|
570
814
|
def initialize(uri)
|
571
815
|
@uri = URI.parse(uri)
|
@@ -575,11 +819,42 @@ class Path::URL < Path
|
|
575
819
|
def uri?
|
576
820
|
true
|
577
821
|
end
|
822
|
+
|
823
|
+
#
|
824
|
+
# Example:
|
825
|
+
#
|
826
|
+
# When this is: http://host.com:port/path/filename.ext?param1=value1¶m2=value2&...
|
827
|
+
#
|
828
|
+
def to_s
|
829
|
+
uri.to_s
|
830
|
+
end
|
831
|
+
|
832
|
+
|
833
|
+
#
|
834
|
+
# ...this is: 'http'
|
835
|
+
#
|
836
|
+
def scheme
|
837
|
+
uri.scheme
|
838
|
+
end
|
839
|
+
alias_method :protocol, :scheme
|
578
840
|
|
841
|
+
#
|
842
|
+
# ...and this is: 'host.com'
|
843
|
+
#
|
579
844
|
def host
|
580
845
|
uri.host
|
581
846
|
end
|
582
847
|
|
848
|
+
#
|
849
|
+
# ...and this is: 80
|
850
|
+
#
|
851
|
+
def port
|
852
|
+
uri.host
|
853
|
+
end
|
854
|
+
|
855
|
+
#
|
856
|
+
# ...and this is: {param1: value1, param2: value2, ...etc... }
|
857
|
+
#
|
583
858
|
def query
|
584
859
|
if query = uri.query
|
585
860
|
query.to_params
|
@@ -588,9 +863,7 @@ class Path::URL < Path
|
|
588
863
|
end
|
589
864
|
end
|
590
865
|
|
591
|
-
|
592
|
-
uri.to_s
|
593
|
-
end
|
866
|
+
# ...and `path` is /path/filename.ext
|
594
867
|
|
595
868
|
end
|
596
869
|
|
@@ -602,10 +875,11 @@ def Path(*args)
|
|
602
875
|
Path[*args]
|
603
876
|
end
|
604
877
|
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
878
|
+
class String
|
879
|
+
def to_Path
|
880
|
+
Path.new self
|
881
|
+
end
|
882
|
+
|
883
|
+
alias_method :to_P, :to_Path
|
611
884
|
end
|
885
|
+
|