epitools 0.4.49 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|