Guest User

Untitled

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