Guest User

Untitled

a guest
Mar 3rd, 2018
107
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 17.83 KB | None | 0 0
  1. require 'fileutils'
  2. require 'iterator'
  3.  
  4. module Kernel
  5. def Path(str)
  6. Path === str ? str : Path.new(str)
  7. end
  8. end
  9.  
  10. class String
  11. def to_path(separator="/")
  12. Path.new(self, separator)
  13. end
  14. end
  15.  
  16. # you can use Path same as strings for hash-lookups, but not as keys
  17. # Path is immutable
  18. # FIXME: ::_load, #_dump for Marshal
  19. class Path
  20. TruePattern = Object.new
  21. def TruePattern.===(other); true; end
  22. def TruePattern.=~(other); true; end
  23.  
  24. include Comparable
  25.  
  26. class <<self
  27. # returns the current working directory
  28. def current; new(Dir.getwd); end
  29.  
  30. # returns the path of the current script (ENV["_"] if $0 doesn't exist)
  31. def program
  32. if File.exist?($0)
  33. new($0)
  34. elsif ENV.has_key?("_")
  35. new(ENV["_"])
  36. else
  37. nil
  38. end
  39. end
  40.  
  41. # home environment variable if set, else nil
  42. def home
  43. ENV.has_key?("HOME") ? new(ENV["HOME"].to_str) : nil
  44. end
  45. end
  46.  
  47. # always use "/" as separator, no matter on what platform
  48. def initialize(path, separator="/")
  49. raise ArgumentError, "Illegal null character in path #{path.inspect}" if /\0/ =~ path
  50. @pathname = path.to_str
  51. @separator = separator
  52. @segments = @pathname.scan(/[^#{Regexp.escape(@separator)}]+/)
  53. @top = (@segments.length == 0 ? @pathname : @pathname[0, @pathname.index(@segments[0])])
  54. taint if path.tainted?
  55. end
  56.  
  57. # returns a String
  58. def pathname
  59. @pathname.dup
  60. end
  61.  
  62. # returns a String
  63. def dirname
  64. File.dirname(@pathname)
  65. end
  66.  
  67. # returns a String, if without_suffix is given (can be an array), it is removed
  68. def filename(without_suffix=nil)
  69. File.basename(@pathname, without_suffix)
  70. end
  71. alias name filename
  72.  
  73. def extname
  74. File.extname(@pathname)
  75. end
  76.  
  77. # returns a Path
  78. def path
  79. dup
  80. end
  81.  
  82. # returns the Path of the containing directory
  83. def directory
  84. self.class.new(File.dirname(@pathname), @separator)
  85. end
  86.  
  87. # returns the Path of the last segment, if without_suffix is
  88. # given (can be an array), it is removed
  89. def file(without_suffix=nil)
  90. self.class.new(File.basename(@pathname, without_suffix), @separator)
  91. end
  92.  
  93. def segments
  94. @pathname.scan(/[^#{Regexp.escape(@separator)}]+/)
  95. end
  96.  
  97. # checks whether two paths are refering the same file,
  98. # basically a.same_file?(b) is a short for Path(a).expanded.clean == Path(b).expanded.clean.
  99. # does not access file system, also see real_same?
  100. def same?(path, separator=@separator)
  101. expanded.clean == self.class.new(path, separator).expanded.clean
  102. end
  103.  
  104. # checks if this path (assumed to be a directory) is base to other path
  105. # does not access file system, also see real_include?
  106. def include?(path, separator=@separator)
  107. self.class.new(path, separator).expanded.clean.index(expanded.clean) == 0
  108. end
  109.  
  110. # checks whether two paths are refering the same file,
  111. # basically a.real_same_file?(b) is a short for Path(a).real == Path(b).real.
  112. # accesses file system, also see same?
  113. def real_same?(path, separator=@separator)
  114. real == self.class.new(path, separator).real
  115. end
  116.  
  117. # checks if this path (must be a directory) is base to other path
  118. # accesses file system, also see include?
  119. def real_include?(path, separator=@separator)
  120. directory? && self.class.new(path, separator).real.index(real) == 0
  121. end
  122.  
  123. # Path#+ appends a path fragment to this one to produce a new Path object.
  124. # also see Path#join
  125. #
  126. # p1 = Path("/usr") # <Path /usr>
  127. # p2 = p1 + "bin/ruby" # <Path /usr/bin/ruby>
  128. # p3 = p1 + "../bin/ruby" # <Path /bin/ruby>
  129. # p4 = p1 + "bin/../ruby" # <Path /usr/bin/../ruby>
  130. # p5 = p1 + "/etc/passwd" # raises ArgumentError
  131. #
  132. # This method doesn't access the file system; it is pure string manipulation.
  133. def +(other)
  134. other = other.to_str
  135. segments = other.scan(/[^#{Regexp.escape(@separator)}]+/)
  136. top = (segments.length == 0 ? other : other[0, other.index(segments[0])])
  137.  
  138. raise ArgumentError, "Can't append absolute path #{other.inspect}" unless top.empty?
  139.  
  140. current = @segments.dup
  141. while segments.last =~ /\A\.\.?\z/
  142. current.pop if segments.shift == ".."
  143. end
  144. self.class.new(@top + (current + segments).join(@separator), @separator)
  145. end
  146.  
  147. def join(*others)
  148. self+others.join(@separator)
  149. end
  150.  
  151. # returns start of the path if absolute, else nil
  152. # absolute paths start with either the path separator or <drive> ":" <path sep>
  153. # (windows' "C:\")
  154. def absolute?
  155. if @pathname =~ /\A([^#{Regexp.escape(@separator)}]+:#{Regexp.escape(@separator)})/ ||
  156. @pathname =~ /\A(#{Regexp.escape(@separator)})/ then
  157. $1
  158. else
  159. nil
  160. end
  161. end
  162.  
  163. # returns true or false
  164. def relative?
  165. !absolute?
  166. end
  167.  
  168. # true if file/directory exists and its size is zero or it's entries only . and ..
  169. def empty?
  170. if File.directory?(@pathname)
  171. Dir.entries(@pathname).all? { |x| x == '.' || x == '..' }
  172. else
  173. File.size(@pathname) == 0
  174. end
  175. end
  176.  
  177. # prepends the current directory or home if path starts with ~
  178. def expanded
  179. absolute? ? dup : self.class.new(File.expand_path(@pathname), @separator)
  180. end
  181.  
  182. def relative(to=self.class.current)
  183. to = self.class.new(to.to_str) unless to.class == self.class #to.respond_to?(:to_a) - to_a at the moment is implemented in Object, thus a useless test
  184. return dup if relative? && to == self.class.current
  185. segments = self.expanded.segments
  186. other = to.expanded.segments
  187. while other[0] == segments[0] && other.length > 0
  188. other.shift
  189. segments.shift
  190. end
  191. segments.unshift(*([".."]*other.length))
  192. self.class.new(segments.join(@separator),@separator)
  193. end
  194.  
  195. # Clean the path simply by resolving and removing excess "." and ".." entries.
  196. # Pure stringmanipulation.
  197. def clean(consider_symlink=false)
  198. if consider_symlink then
  199. cleanpath_conservative
  200. else
  201. cleanpath_aggressive
  202. end
  203. end
  204.  
  205. # cleanpath_aggressive assumes:
  206. # * no symlink
  207. # * all path prefix contained in the path is existing directory
  208. def cleanpath_aggressive
  209. return self.class.new('', @separator) if @pathname == ''
  210. absolute = absolute?
  211. names = []
  212. @segments.each { |name|
  213. next if name == '.'
  214. if name == '..'
  215. if names.empty?
  216. next if absolute
  217. else
  218. if names.last != '..'
  219. names.pop
  220. next
  221. end
  222. end
  223. end
  224. names << name
  225. }
  226. return self.class.new(absolute ? absolute : '.', @separator) if names.empty?
  227. path = absolute ? absolute : ''
  228. path << names.join(@separator)
  229. self.class.new(path, @separator)
  230. end
  231. private :cleanpath_aggressive
  232.  
  233. def cleanpath_conservative
  234. return self.class.new('', @separator) if @pathname == ''
  235. names = @segments.dup
  236. absolute = absolute?
  237. last_dot = names.last == '.'
  238. names.delete('.')
  239. names.shift while names.first == '..' if absolute
  240. return self.class.new(absolute ? absolute : '.', @separator) if names.empty?
  241. path = absolute ? absolute : ''
  242. path << names.join(@separator)
  243. if names.last != '..'
  244. if last_dot
  245. path << @separator+'.'
  246. elsif %r{#{@separator}\z} =~ @pathname
  247. path << @separator
  248. end
  249. end
  250. self.class.new(path, @separator)
  251. end
  252. private :cleanpath_conservative
  253.  
  254. # helper for realpath, get's top + segments (top beeing "" for non absolutes and "/" on *nix,
  255. # something like "C:\" on win or whatever else absolute? returns. segments beeing a list
  256. # of segments besides top
  257. def top_segments #:nodoc:
  258. top = absolute? || ""
  259. if top then
  260. path = @pathname
  261. else
  262. path = self.class.current+@pathname
  263. top = path.absolute? || ""
  264. path = path.to_str
  265. end
  266. segments = path[top.length..-1].scan(/[^#{Regexp.escape(@separator)}]+/)
  267. return top, segments
  268. end
  269.  
  270. # Returns a real (absolute) path of +self+ in the actual filesystem.
  271. # The real path doesn't contain symlinks or useless dots.
  272. # Set resolve_nonexisting to true if you don't want it to raise an error
  273. # if a non existing path is resolved.
  274. def real(resolve_nonexisting=false)
  275. top, unresolved = top_segments
  276. resolved = []
  277. inexistent = []
  278. if resolve_nonexisting
  279. while !File.exist?(top + unresolved.join(@separator)) && !unresolved.empty?
  280. inexistent.unshift unresolved.pop
  281. end
  282. end
  283.  
  284. until unresolved.empty?
  285. case unresolved.last
  286. when '.'
  287. unresolved.pop
  288. when '..'
  289. resolved.unshift unresolved.pop
  290. else
  291. loop_check = {}
  292. while (stat = File.lstat(path = top + unresolved.join(@separator))).symlink?
  293. symlink_id = "#{stat.dev}:#{stat.ino}" #∆ won't work on windows (stat.ino always 0), OTOH, windows doesn't seem to have symlinks
  294. raise Errno::ELOOP.new(path) if loop_check[symlink_id]
  295. loop_check[symlink_id] = true
  296. top, unresolved = top_segments
  297. end
  298. next if (filename = unresolved.pop) == '.'
  299. if filename != '..' && resolved.first == '..'
  300. resolved.shift
  301. else
  302. resolved.unshift filename
  303. end
  304. end #case
  305. end
  306.  
  307. resolved.shift while resolved[0] == '..'
  308. resolved.push(*inexistent)
  309.  
  310. if resolved.empty? then
  311. self.class.new(top.empty? ? '.' : @separator, @separator)
  312. else
  313. self.class.new(top + resolved.join(@separator), @separator)
  314. end
  315. end
  316.  
  317. # creates a directory and all intermediary directories
  318. def create
  319. FileUtils.mkpath(@pathname)
  320. end
  321.  
  322. # creates a file in path, creates intermediary directories if necessary
  323. # warning, if the file already exists it will be deleted
  324. def create_file(delete_existing=false)
  325. directory.create
  326. delete if exists? and delete_existing
  327. FileUtils.touch(@pathname) unless exists?
  328. end
  329.  
  330. # removes file or directory (and all it's contents)
  331. def delete
  332. if File.directory?(@pathname)
  333. FileUtils.rm_r(@pathname)
  334. else
  335. File.unlink(@pathname)
  336. end
  337. end
  338.  
  339. # yield each line of a file, if no block is given
  340. # returns an Iterator over the lines.
  341. def lines(sep_string=$/, &block)
  342. if block then
  343. File.open(@pathname, "rb") { |fh| fh.each_line(sep_string, &block) }
  344. else
  345. Iterator.new { |iter|
  346. File.open(@pathname, "rb") { |fh|
  347. fh.each_line(sep_string) { |line| iter.yield(line) }
  348. }
  349. }
  350. end
  351. end
  352.  
  353. # yields chunks of size bytes, if no block is given returns
  354. # an Iterator over those chunks.
  355. def chunks(size=4096, &block)
  356. if block then
  357. File.open(@pathname, "rb") { |fh|
  358. while line = fh.read(size)
  359. yield(line)
  360. end
  361. }
  362. else
  363. Iterator.new { |iter|
  364. File.open(@pathname, "rb") { |fh|
  365. while line = fh.read(size)
  366. iter.yield(line)
  367. end
  368. }
  369. }
  370. end
  371. end
  372.  
  373. # See +IO.read+
  374. def read(*args, &block); File.read(@pathname, *args, &block); end
  375.  
  376. # See +IO.open+
  377. def open(*args, &block); File.open(@pathname, *args, &block); end
  378.  
  379. # Write text to file and close it
  380. def write(text); File.open(@pathname, "wb") { |fh| fh.write(text) }; end
  381.  
  382. # Append text to file and close it
  383. def append(text); File.open(@pathname, "ab") { |fh| fh.write(text) }; end
  384.  
  385. # Truncates the file _file_name_ to be at most _integer_ bytes long.
  386. # Not available on all platforms.
  387. def truncate(integer); File.truncate(@pathname, integer); end
  388.  
  389. # See +IO.sysopen+
  390. def sysopen(*args, &block); File.sysopen(@pathname, *args, &block); end
  391.  
  392. # all entries in a directory, except . and ..
  393. # if no block is given it will return an Iterator over the entries
  394. def entries(pattern=nil)
  395. pattern ||= TruePattern
  396. if block_given? then
  397. Dir.entries(@pathname).each { |entry|
  398. next if entry == "." or entry == ".."
  399. path = self+entry
  400. yield(path) if pattern =~ entry
  401. }
  402. else
  403. Iterator.new { |iter|
  404. Dir.entries(@pathname).each { |entry|
  405. next if entry == "." or entry == ".."
  406. path = self+entry
  407. iter.yield(path) if pattern =~ entry
  408. }
  409. }
  410. end
  411. end
  412.  
  413. # if a block is given it will yield all files
  414. # matching the optional pattern, else it will return an Iterator (Enumerable)
  415. # over those.
  416. def files(pattern=nil)
  417. pattern ||= TruePattern
  418. if block_given? then
  419. Dir.entries(@pathname).each { |entry|
  420. path = self+entry
  421. yield(path) if pattern =~ entry and path.file?
  422. }
  423. else
  424. Iterator.new { |iter|
  425. Dir.entries(@pathname).each { |entry|
  426. path = self+entry
  427. iter.yield(path) if pattern =~ entry and path.file?
  428. }
  429. }
  430. end
  431. end
  432.  
  433. # if a block is given it will yield all directories (except . and ..)
  434. # matching the optional pattern, else it will return an Iterator (Enumerable)
  435. # over those.
  436. def directories(pattern=nil)
  437. pattern ||= TruePattern
  438. if block_given? then
  439. Dir.entries(@pathname).each { |entry|
  440. next if entry == "." or entry == ".."
  441. path = self+entry
  442. yield(path) if pattern =~ entry and path.directory?
  443. }
  444. else
  445. Iterator.new { |iter|
  446. Dir.entries(@pathname).each { |entry|
  447. next if entry == "." or entry == ".."
  448. path = self+entry
  449. iter.yield(path) if pattern =~ entry and path.directory?
  450. }
  451. }
  452. end
  453. end
  454.  
  455. # Returns the last access time for the file as a Time object.
  456. # If parameter format is given, strftime is applied on the Time
  457. def accessed(format=nil)
  458. format ? File.atime(@pathname).strftime(format) : File.atime(@pathname)
  459. end
  460.  
  461. # Returns the change time for the file (the time at which directory
  462. # information about the file was changed, not the file itself).
  463. # If parameter format is given, strftime is applied on the Time
  464. def changed(format=nil)
  465. format ? File.ctime(@pathname).strftime(format) : File.ctime(@pathname)
  466. end
  467.  
  468. # Returns the modification time for the named file as a Time object.
  469. # If parameter format is given, strftime is applied on the Time
  470. def modified(format=nil)
  471. format ? File.mtime(@pathname).strftime(format) : File.mtime(@pathname)
  472. end
  473.  
  474. # See <tt>File.utime</tt>. Update the access and modification times.
  475. def utime(atime, mtime) File.utime(atime, mtime, @pathname) end
  476. # Returns a +File::Stat+ object for the named file (see +File::Stat+).
  477. def stat; File.stat(@pathname); end
  478. # Same as +Path::stat+, but does not follow the last symbolic link.
  479. # Instead, reports on the link itself.
  480. def lstat; File.lstat(@pathname); end
  481. # Identifies the type of the named file; the return string is one of
  482. # "+file+", "+directory+", "+characterSpecial+", "+blockSpecial+",
  483. # "+fifo+", "+link+", "+socket+", or "+unknown+".
  484. def type; File.ftype(@pathname); end
  485. # See <tt>File.chmod</tt>. Changes permissions.
  486. def chmod(mode) File.chmod(mode, @pathname) end
  487. # See <tt>File.lchmod</tt>.
  488. def lchmod(mode) File.lchmod(mode, @pathname) end
  489. # See <tt>File.chown</tt>. Change owner and group of file.
  490. def chown(owner, group) File.chown(owner, group, @pathname) end
  491. # See <tt>File.lchown</tt>.
  492. def lchown(owner, group) File.lchown(owner, group, @pathname) end
  493. # See <tt>File.fnmatch?</tt>. Return +true+ if the receiver matches the given
  494. # pattern.
  495. def fnmatch?(pattern, *args) File.fnmatch?(pattern, @pathname, *args) end
  496. # See <tt>File.link</tt>. Creates a hard link.
  497. def link(from) File.link(@pathname, from) end
  498. # See <tt>File.readlink</tt>. Read symbolic link.
  499. def readlink() self.class.new(File.readlink(@pathname), @separator) end
  500. # See <tt>File.symlink</tt>. Creates a symbolic link in from
  501. def symlink(from) File.symlink(@pathname, from) end
  502. # See <tt>File.blockdev?</tt>.
  503. def blockdev?; File.blockdev?(@pathname); end
  504. # See <tt>File.chardev?</tt>.
  505. def chardev?; File.chardev?(@pathname); end
  506. # See <tt>File.executable?</tt>.
  507. def executable?; File.executable?(@pathname); end
  508. # See <tt>File.executable_real?</tt>.
  509. def executable_real?; File.executable_real?(@pathname); end
  510. # See <tt>File.exist?</tt>.
  511. def exist?; File.exist?(@pathname); end
  512. # See <tt>File.exist?</tt>.
  513. def exists?; File.exist?(@pathname); end
  514. # See <tt>File.grpowned?</tt>.
  515. def grpowned?; File.grpowned?(@pathname); end
  516. # See <tt>File.directory?</tt>.
  517. def directory?; File.directory?(@pathname); end
  518. # See <tt>File.file?</tt>.
  519. def file?; File.file?(@pathname); end
  520. # See <tt>File.pipe?</tt>.
  521. def pipe?; File.pipe?(@pathname); end
  522. # See <tt>File.socket?</tt>.
  523. def socket?; File.socket?(@pathname); end
  524. # See <tt>File.readable?</tt>.
  525. def readable?; File.readable?(@pathname); end
  526. # See <tt>File.readable_real?</tt>.
  527. def readable_real?; File.readable_real?(@pathname); end
  528. # See <tt>File.setuid?</tt>.
  529. def setuid?; File.setuid?(@pathname); end
  530. # See <tt>File.setgid?</tt>.
  531. def setgid?; File.setgid?(@pathname); end
  532. # See <tt>File.size</tt>.
  533. def size; File.size(@pathname); end
  534. # See <tt>File.size?</tt>.
  535. def size?; File.size?(@pathname); end
  536. # See <tt>File.sticky?</tt>.
  537. def sticky?; File.sticky?(@pathname); end
  538. # See <tt>File.symlink?</tt>.
  539. def symlink?; File.symlink?(@pathname); end
  540. # See <tt>File.writable?</tt>.
  541. def writable?; File.writable?(@pathname); end
  542. # See <tt>File.writable_real?</tt>.
  543. def writable_real?; File.writable_real?(@pathname); end
  544.  
  545.  
  546. def to_str
  547. @pathname.dup
  548. end
  549.  
  550. def inspect # :nodoc:
  551. "<Path #{@pathname}>"
  552. end
  553. alias to_s inspect
  554.  
  555. def to_path
  556. dup
  557. end
  558.  
  559. def dup
  560. self.class.new(@pathname, @separator)
  561. end
  562.  
  563. def hash # :nodoc:
  564. @pathname.hash
  565. end
  566.  
  567. # compares on string basis, can be compared to a String
  568. def <=>(other)
  569. other.to_str.gsub(@separator, 0.chr) <=> @pathname.gsub(@separator, 0.chr)
  570. end
  571.  
  572. # compares on string basis, can be compared to a String
  573. def ==(other)
  574. other.to_str == @pathname
  575. end
  576.  
  577. def eql?(other) # :nodoc:
  578. other.to_str.eql?(@pathname)
  579. end
  580.  
  581. def taint # :nodoc:
  582. super
  583. @pathname.taint
  584. @segments.taint
  585. @segments.map { |e| e.taint }
  586. self
  587. end
  588.  
  589. def untaint # :nodoc:
  590. super
  591. @pathname.untaint
  592. @segments.untaint
  593. @segments.map { |e| e.untaint }
  594. end
  595. end
Add Comment
Please, Sign In to add comment