filepath-0.7/0000755000004100000410000000000013315563111013203 5ustar www-datawww-datafilepath-0.7/COPYING0000600000004100000410000001561013315563111014231 0ustar www-datawww-dataCreative Commons Legal Code CC0 1.0 Universal CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER. Statement of Purpose The laws of most jurisdictions throughout the world automatically confer exclusive Copyright and Related Rights (defined below) upon the creator and subsequent owner(s) (each and all, an "owner") of an original work of authorship and/or a database (each, a "Work"). Certain owners wish to permanently relinquish those rights to a Work for the purpose of contributing to a commons of creative, cultural and scientific works ("Commons") that the public can reliably and without fear of later claims of infringement build upon, modify, incorporate in other works, reuse and redistribute as freely as possible in any form whatsoever and for any purposes, including without limitation commercial purposes. These owners may contribute to the Commons to promote the ideal of a free culture and the further production of creative, cultural and scientific works, or to gain reputation or greater distribution for their Work in part through the use and efforts of others. For these and/or other purposes and motivations, and without any expectation of additional consideration or compensation, the person associating CC0 with a Work (the "Affirmer"), to the extent that he or she is an owner of Copyright and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and publicly distribute the Work under its terms, with knowledge of his or her Copyright and Related Rights in the Work and the meaning and intended legal effect of CC0 on those rights. 1. Copyright and Related Rights. A Work made available under CC0 may be protected by copyright and related or neighboring rights ("Copyright and Related Rights"). Copyright and Related Rights include, but are not limited to, the following: i. the right to reproduce, adapt, distribute, perform, display, communicate, and translate a Work; ii. moral rights retained by the original author(s) and/or performer(s); iii. publicity and privacy rights pertaining to a person's image or likeness depicted in a Work; iv. rights protecting against unfair competition in regards to a Work, subject to the limitations in paragraph 4(a), below; v. rights protecting the extraction, dissemination, use and reuse of data in a Work; vi. database rights (such as those arising under Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, and under any national implementation thereof, including any amended or successor version of such directive); and vii. other similar, equivalent or corresponding rights throughout the world based on applicable law or treaty, and any national implementations thereof. 2. Waiver. To the greatest extent permitted by, but not in contravention of, applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and unconditionally waives, abandons, and surrenders all of Affirmer's Copyright and Related Rights and associated claims and causes of action, whether now known or unknown (including existing as well as future claims and causes of action), in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each member of the public at large and to the detriment of Affirmer's heirs and successors, fully intending that such Waiver shall not be subject to revocation, rescission, cancellation, termination, or any other legal or equitable action to disrupt the quiet enjoyment of the Work by the public as contemplated by Affirmer's express Statement of Purpose. 3. Public License Fallback. Should any part of the Waiver for any reason be judged legally invalid or ineffective under applicable law, then the Waiver shall be preserved to the maximum extent permitted taking into account Affirmer's express Statement of Purpose. In addition, to the extent the Waiver is so judged Affirmer hereby grants to each affected person a royalty-free, non transferable, non sublicensable, non exclusive, irrevocable and unconditional license to exercise Affirmer's Copyright and Related Rights in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "License"). The License shall be deemed effective as of the date CC0 was applied by Affirmer to the Work. Should any part of the License for any reason be judged legally invalid or ineffective under applicable law, such partial invalidity or ineffectiveness shall not invalidate the remainder of the License, and in such case Affirmer hereby affirms that he or she will not (i) exercise any of his or her remaining Copyright and Related Rights in the Work or (ii) assert any associated claims and causes of action with respect to the Work, in either case contrary to Affirmer's express Statement of Purpose. 4. Limitations and Disclaimers. a. No trademark or patent rights held by Affirmer are waived, abandoned, surrendered, licensed or otherwise affected by this document. b. Affirmer offers the Work as-is and makes no representations or warranties of any kind concerning the Work, express, implied, statutory or otherwise, including without limitation warranties of title, merchantability, fitness for a particular purpose, non infringement, or the absence of latent or other defects, accuracy, or the present or absence of errors, whether or not discoverable, all to the greatest extent permissible under applicable law. c. Affirmer disclaims responsibility for clearing rights of other persons that may apply to the Work or any use thereof, including without limitation any person's Copyright and Related Rights in the Work. Further, Affirmer disclaims responsibility for obtaining any necessary consents, permissions or other rights required for any use of the Work. d. Affirmer understands and acknowledges that Creative Commons is not a party to this document and has no duty or obligation with respect to this CC0 or use of the Work. filepath-0.7/.travis.yml0000600000004100000410000000014213315563111015301 0ustar www-datawww-datalanguage: ruby rvm: - "1.9.3" - "2.1.5" - "2.3.0" - "2.3.3" - jruby-9.1.9.0 sudo: false filepath-0.7/README.md0000600000004100000410000000617213315563111014460 0ustar www-datawww-datafilepath ======== filepath is a small library to manipulate paths; a modern replacement for the standard Pathname. filepath is built around two main classes: `Filepath`, that represents paths, and `FilepathList`, lists of paths. These classes provide immutable objects with dozens of convenience methods for common operations such as calculating relative paths, concatenating paths, finding all the files in a directory or modifying all the extensions of a list of filenames at once. Features and examples --------------------- The main purpose of Filepath is to able to write require __FILE__.as_path / 'spec' / 'tasks' instead of cumbersome code like require File.join(File.dirname(__FILE__), ['spec', 'tasks']) The main features of Filepath are… ### Path concatenation oauth_conf = ENV['HOME'].as_path / '.config' / 'myapp' / 'oauth.ini' oauth_conf.to_s #=> "/home/gioele/.config/myapp/oauth.ini" joe_home = ENV['HOME'].as_path / '..' / 'joe' joe_home.to_raw_string #=> "/home/gioele/../joe" joe_home.to_s #=> "/home/joe" rel1 = oauth_conf.relative_to(joe_home) rel1.to_s #=> "../gioele/.config/myapp/oauth.ini" rel2 = joe_home.relative_to(oauth_conf) rel2.to_s #=> "../../../joe" ### Path manipulation image = ENV['HOME'].as_path / 'Documents' / 'images' / 'cat.png' image.parent_dir.to_s #=> "/home/gioele/Documents/images" image.filename.to_s #=> "cat.png" image.extension #=> "png" converted_img = image.replace_extension("jpeg") converted_img.to_s #=> "/home/gioele/Documents/images/cat.jpeg" convert(image, converted_img) ### Path traversal file_dir = Filepath.new("/srv/example.org/web/html/") file_dir.descend do |path| is = path.readable? ? "is" : "is not!" puts "#{path} #{is} readable" end produces / is readable /srv is readable /srv/example.org is readable /srv/example.org/web is not! readable /srv/example.org/web/html is not! redable ### Shortcuts for file and directory operations home_dir = ENV['HOME'] files = home_dir.files files.count #=> 3 files.each { |path| puts path.filename.to_s } produces # .bashrc # .vimrc # TODO.txt Similarly, dirs = home_dir.directories dirs.count #=> 2 dirs.each { |path| puts path.filename.to_s + "/"} produces # .ssh/ # Documents/ Requirements ------------ The `filepath` library does not require any external library: it relies complitely on functionalities available in the Ruby's core classes. The `filepath` library has been tested and found compatible with Ruby 1.9.3, Ruby 2.3 and JRuby 9.1.x.x. Installation ------------ gem install filepath Authors ------- * Gioele Barabucci (initial author) Development ----------- Code : Report issues : Documentation : License ------- This is free software released into the public domain (CC0 license). See the `COPYING` file or for more details. filepath-0.7/spec/0000755000004100000410000000000013315563111014135 5ustar www-datawww-datafilepath-0.7/spec/fixtures.rb0000600000004100000410000000060713315563111016326 0ustar www-datawww-data# This is free software released into the public domain (CC0 license). FIXTURES_DIR = File.join(%w{spec fixtures}) FIXTURES_FAKE_ENTRIES = [ 'd1', ['d1', 'd11'], ['d1', 'd12'], ['d1', 'd13'], ['d1', 'f11'], ['d1', 'f12'], ['d1', 'l11'], 'd2', ['d2', 'd21'], ['d2', 'd22'], 'd3', 'f1', 'dx', 'p1', 'p2', 's1', ].map { |entry| File.join(FIXTURES_DIR, *Array(entry)) } filepath-0.7/spec/filepathlist_spec.rb0000600000004100000410000001534213315563111020161 0ustar www-datawww-data# This is free software released into the public domain (CC0 license). require File.join(File.dirname(__FILE__), 'spec_helper') describe FilepathList do describe "#initialize" do it "creates an empty FilepathList" do list = FilepathList.new() list.should be_empty end it "creates a FilepathList from an Array of Strings" do paths = %w{a/b c/d e/f} list = FilepathList.new(paths) list.should have(3).items list.each { |path| path.should be_a(Filepath) } end it "creates a FilepathList from an Array of Filepaths" do paths = %w{a/b c/d e/f}.map(&:as_path) list = FilepathList.new(paths) list.should have(3).items list.each { |path| path.should be_a(Filepath) } end it "creates a FilepathList from an Array of Arrays" do paths = [%w{a b}, %w{c d}, %w{e f}] list = FilepathList.new(paths) list.should have(3).items list.each { |path| path.should be_a(Filepath) } end end describe "#/" do it "adds the same string to all the paths" do list = FilepathList.new(%w{foo faa}) / 'bar' list[0].should eq 'foo/bar' list[1].should eq 'faa/bar' end end describe "#+" do it "concatenates two FilepathLists" do list1 = FilepathList.new(%w{a b c}) list2 = FilepathList.new(%w{d e}) list = list1 + list2 list.should have(5).items list[0].should eq('a') list[1].should eq('b') list[2].should eq('c') list[3].should eq('d') list[4].should eq('e') end end describe "#-" do it "removes a list (as array of strings) from another list" do list1 = FilepathList.new(%w{a/b /a/c e/d}) list2 = list1 - %w{a/b e/d} list2.should have(1).item list2[0].should eq('/a/c') end end describe "#<<" do it "adds a new to path to a existing FilepathList" do list1 = FilepathList.new(%w{a/b /c/d}) list2 = list1 << "e/f" list1.should have(2).items list2.should have(3).items list2[0].should eq('a/b') list2[1].should eq('/c/d') list2[2].should eq('e/f') end end describe "#*" do describe "calculates the cartesian product between" do it "two FilepathLists" do p1 = %w{a b c} p2 = %w{1 2} list1 = FilepathList.new(p1) list2 = FilepathList.new(p2) all_paths = p1.product(p2).map { |x| x.join('/') } list = list1 * list2 list.should have(6).items list.should include(*all_paths) end it "a FilepathList and a string" do p1 = %w{a b c} p2 = "abc" list = FilepathList.new(p1) * p2 list.should have(3).items list.should include(*%w{a/abc b/abc c/abc}) end it "a FilepathList and a Filepath" do p1 = %w{a b c} p2 = Filepath.new("x") list = FilepathList.new(p1) * p2 list.should have(3).items list.should include(*%w{a/x b/x c/x}) end it "a Filepath and an array of strings" do p1 = %w{a b c} p2 = ["1", "2"] list = FilepathList.new(p1) * p2 list.should have(6).items list.should include(*%w{a/1 b/1 a/2 b/2 c/1 c/2}) end end end describe "#remove_common_segments" do it "works on lists of files from the same dir" do paths = %w{a/b/x1 a/b/x2 a/b/x3} list = FilepathList.new(paths).remove_common_segments list.should have(3).items list.should include(*%w{x1 x2 x3}) end it "works on lists of files from different dirs" do list1 = FilepathList.new(%w{a/b/x1 a/b/c/x2 a/b/d/e/x3}) list2 = list1.remove_common_segments list2.should have(3).items list2.should include(*%w{x1 c/x2 d/e/x3}) end it "works on lists of files with no common segments" do paths = %w{a/b a/d g/f} list1 = FilepathList.new(paths) list2 = list1.remove_common_segments list1.should == list2 end it "works on lists that contain duplicates only" do paths = %w{a/b a/b a/b} list1 = FilepathList.new(paths) list2 = list1.remove_common_segments list2.should == FilepathList.new(['.', '.', '.']) end end describe "#include?" do it "says that 'a/c' is included in [, , ]" do list = FilepathList.new(%w{a/b a/c /a/d}) list.should include("a/c") end end describe "#to_s" do it "returns files separated by a comma`" do list = FilepathList.new(%w{a/b a/c /a/d}) list.to_s.should == "a/b:a/c:/a/d" end end describe "#==" do let(:list) { ['a/b', 'c/d', 'e/f'].as_path_list } it "compares a FilepathList to another FilepathList" do list2 = FilepathList.new << 'a/b' << 'c/d' << 'e/f' list3 = list2 << 'g/h' list.should eq(list2) list.should_not eq(list3) end it "compares a FilepathList to an Array of Strings" do list.should eq(%w{a/b c/d e/f}) list.should_not eq(%w{a/a b/b c/c}) end end describe FilepathList::ArrayMethods do let(:list) { FilepathList.new(%w{a.foo b.bar c.foo d.foo b.bar}) } describe "#all?" do it "checks whether a block applies to a list" do ok = list.all? { |path| path.extension? } ok.should be true end end describe "#any?" do it "check whether a block does not apply to any path" do ok = list.any? { |path| path.basename == "a.foo" } ok.should be true end end describe "#none?" do it "check whether a block does not apply to any path" do ok = list.none? { |path| path.absolute? } ok.should be true end end end describe FilepathList::EntriesMethods do let(:list) { FilepathList.new(%w{a.foo b.bar c.foo d.foo b.bar}) } describe "#select" do it "keeps paths matching a Regex" do remaining = list.select(/bar$/) remaining.should be_a FilepathList remaining.should have(2).items remaining.each { |path| path.extension.should == 'bar' } end it "keeps all the paths for which the block returns true" do remaining = list.select { |ph| ph.extension?('bar') } remaining.should have(2).items remaining.each { |ph| ph.extension.should == 'bar' } end end describe "#exclude" do it "excludes paths matching a Regex" do remaining = list.exclude(/bar$/) remaining.should be_a FilepathList remaining.should have(3).items remaining.each { |path| path.extension.should == 'foo' } end it "excludes all the paths for which the block returns true" do remaining = list.exclude { |path| path.extension?('bar') } remaining.should be_a FilepathList remaining.should have(3).items remaining.each { |path| path.extension.should == 'foo' } end end describe "#map" do it "applies a block to each path" do mapped = list.map { |path| path.remove_extension } mapped.should be_a FilepathList mapped.should have(list.size).items mapped.each { |path| path.extension?.should be false } end end end end describe Array do describe "#as_path_list" do it "generates a FilepathList from an Array" do paths = %w{/a/b c/d /f/g} list = paths.as_path_list list.should be_a(FilepathList) list.should include(*paths) end end end filepath-0.7/spec/filepath_spec.rb0000600000004100000410000005511513315563111017267 0ustar www-datawww-data# This is free software released into the public domain (CC0 license). require File.join(File.dirname(__FILE__), 'spec_helper') describe Filepath do before(:all) do @root = Filepath.new(FIXTURES_DIR) end it "can be created from a string" do Filepath.new("foo").should be_a Filepath end it "can be created from another Filepath" do orig = Filepath.new("foo") Filepath.new(orig).should be_a Filepath end describe "#/" do test_data = [ ['foo', 'bar', 'foo/bar'], ['foo', '.', 'foo'], ['foo', '..', '.'], ['foo/bar', 'baz', 'foo/bar/baz'], ['', 'foo/bar', './foo/bar'], ] test_data.each do |base, extra, result| it "concatenates `#{base}` and `#{extra}` (as String) into `#{result}`" do ph = Filepath.new(base) / extra ph.should == result end end test_data.each do |base, extra, result| it "concatenates `#{base}` and `#{extra}` (as Filepath) into `#{result}`" do ph = Filepath.new(base) / Filepath.new(extra) ph.should == result end end end describe "#+" do it "is deprecated but performs as Filepath#/" do p1 = Filepath.new("a") p2 = Filepath.new("b") p1.should_receive(:warn).with(/is deprecated/) (p1 + p2).should == (p1 / p2) end end describe "#join" do test_data = [ ['', ['bar'], './bar'], ['foo/quux', ['bar', 'baz'], 'foo/quux/bar/baz'], ['/', ['a', 'b', 'c'], '/a/b/c'], ] test_data.each do |base, extra, result| args = extra.map { |x| x.inspect }.join(',') it "appends #{args} to '#{base}' to get <#{result}>" do base.as_path.join(*extra).should == result end end end describe "filename" do test_data = [ ['/foo/bar', 'bar'], ['foo', 'foo'], ['/', ''], ['a/b/../../', ''], ['/foo/bar/.', 'bar'], ['a/b/../c', 'c'], ] test_data.each do |path, result| it "says that `#{result}` is the filename of `#{path}`" do ph = Filepath.new(path) ph.filename.should == result end end end describe "parent_dir" do test_data = [ ['/foo/bar', '/foo'], ['foo', '.'], ['/', '/'], ['/foo/bar/.', '/foo'], ['a/b/../c', 'a'], ] test_data.each do |path, result| it "says that `#{result}` is the parent dir of `#{path}`" do ph = Filepath.new(path) ph.parent_dir.should == result end end end describe "#relative_to" do test_data = [ ['/a/b/c', '/a/b', 'c'], ['/a/b/c', '/a/d', '../b/c'], ['/a/b/c', '/a/b/c/d', '..'], ['/a/b/c', '/a/b/c', '.'], ['a/d', 'a/b/c', '../../d'], ['a/e/f', 'a/b/c/d', '../../../e/f'], ['a/c', 'a/b/..', 'c'], ] test_data.each do |path, base, result| it "says that `#{path}` relative to `#{base}` is `#{result}`" do ph = Filepath.new(path) ph.relative_to(base).should == result end end test_data2 = [ # FIXME: testare /a/b/c con ../d (bisogna prima rendere assoluto quel path) ['../e', '/a/b/c'], ['g', '/a/b/c'], ['/a/b/c', 'm'], ] test_data2.each do |path, base| it "raise an exception because `#{path}` and `#{base}` have different prefixes" do ph = Filepath.new(path) expect { ph.relative_to(base) }.to raise_error(ArgumentError) end end end describe "#relative_to_file" do test_data = [ ['/a/b/c', '/a/d', 'b/c'], ['/a/b/c', '/a/b/c/d', '.'], ['/a/b/c', '/a/b/c', 'c'], ['a/d', 'a/b/c', '../d'], ['a/e/f', 'a/b/c/d', '../../e/f'], ] test_data.each do |path, base, result| it "says that `#{path}` relative to the file `#{base}` is `#{result}`" do ph = Filepath.new(path) ph.relative_to_file(base).should == result end end end describe "#with_filename" do test_data = [ ['foo/bar', 'quux', 'foo/quux'], ['foo/baz/..', 'quux', 'quux'], ['/', 'foo', '/foo'], ] test_data.each do |base, new, result| it "changes `#{base}` + `#{new}` into `#{result}`" do ph = Filepath.new(base) ph.with_filename(new).should == result end end end describe "#extension" do test_data = [ ['foo.bar', 'bar'], ['foo.', ''], ['foo', nil], ['foo.bar/baz.buz', 'buz'], ['foo.bar/baz', nil], ['.foo', nil], ['.foo.conf', 'conf'], ] test_data.each do |path, ext| it "says that `#{path}` has extension `#{ext}`" do Filepath.new(path).extension.should == ext end end end describe "#extension?" do with_extension = [ 'foo.bar', 'foo.', '.foo.conf', ] with_extension.each do |path| it "says that <#{path}> has an extension" do Filepath.new(path).extension?.should be true end end no_extension = [ 'foo', 'foo.bar/baz', '.foo', ] no_extension.each do |path| it "says that <#{path}> has no extension" do Filepath.new(path).extension?.should be false end end extension_data = [ ['foo.bar', 'bar'], ['/foo/bar.', ''], ['foo/bar.baz.conf', 'conf'], ['foo.bar.boom', /oo/], ] extension_data.each do |path, ext| it "says that <#{path}> extesions is #{ext.inspect}" do Filepath.new(path).extension?(ext).should be true end end it "says that `foo.bar` extension is not `baz`" do Filepath.new('foo.bar').extension?('baz').should be false end end describe "#with_extension(String)" do test_data = [ ['foo.bar', 'foo.baz'], ['foo.', 'foo.baz'], ['foo', 'foo.baz'], ['foo.bar/baz.buz', 'baz.baz'], ['foo.bar/baz', 'baz.baz'], ] test_data.each do |path, result| it "replaces `#{path}` with `baz` into `#{result}`" do new = Filepath.new(path).with_extension('baz') new.basename.to_s.should == result end end end describe "#without_extension" do test_data = [ ['foo.bar', 'foo'], ['foo.', 'foo'], ['foo', 'foo'], ['foo.bar/baz.buz', 'baz'], ['foo.bar/baz', 'baz'], ] test_data.each do |path, result| it "turns `#{path}` into `#{result}`" do new = Filepath.new(path).without_extension new.basename.to_s.should == result end end end describe "=~" do it "matches `/foo/bar` with /foo/" do Filepath.new('/foo/bar').should =~ /foo/ end it "does not match `/foo/bar` with /baz/" do Filepath.new('/foo/bar').should_not =~ /baz/ end it "matches `/foo/bar` with /o\\/ba" do Filepath.new('/foo/bar').should =~ /o\/b/ end it "matches `/foo/bar/../quux` with /foo\\/quux/" do Filepath.new('/foo/bar/../quux').should =~ /foo\/quux/ end end describe "#root?" do it "says that points to the root directory" do Filepath.new('/').should be_root end it "says that points to the root directory" do Filepath.new('/..').should be_root end it "says that does not point to the root directory" do Filepath.new('a/b').should_not be_root end it "says that does not point to the root directory" do Filepath.new('/foo/bar').should_not be_root end end describe "#absolute?" do it "says that `/foo/bar` is absolute" do Filepath.new('/foo/bar').should be_absolute end it "sasys that `foo/bar` is not absolute" do Filepath.new('foo/bar').should_not be_absolute end end describe "#normalized" do test_data = [ ['a', 'a'], ['a/b/c', 'a/b/c'], ['a/../c', 'c'], ['a/b/..', 'a'], ['../a', '../a'], ['../../a', '../../a'], ['../a/..', '..'], ['/', '/'], ['/..', '/'], ['/../../../a', '/a'], ['a/b/../..', '.'], ] test_data.each do |path, result| it "turns `#{path}` into `#{result}`" do Filepath.new(path).normalized.to_raw_string.should == result end end end describe "#each_segment" do it "goes through all the segments of an absolute path" do steps = [] Filepath.new("/a/b/c").each_segment do |seg| steps << seg end steps.should have(4).items steps[0].should eq("/") steps[1].should eq("a") steps[2].should eq("b") steps[3].should eq("c") end it "goes through all the segments of a relative path" do steps = [] Filepath.new("a/b/c").each_segment do |seg| steps << seg end steps.should have(3).items steps[0].should eq("a") steps[1].should eq("b") steps[2].should eq("c") end it "returns the path itself" do path = Filepath.new("/a/b/c/") path.each_segment { }.should be(path) end end describe "#ascend" do it "goes through all the segments of an absolute path" do steps = [] Filepath.new("/a/b/c").ascend do |seg| steps << seg end steps.should have(4).items steps[0].should eq("/a/b/c") steps[1].should eq("/a/b") steps[2].should eq("/a") steps[3].should eq("/") end it "goes through all the segments of a relative path" do steps = [] Filepath.new("a/b/c").ascend do |seg| steps << seg end steps.should have(3).items steps[0].should eq("a/b/c") steps[1].should eq("a/b") steps[2].should eq("a") end it "returns the path itself" do path = Filepath.new("/a/b/c/") path.ascend { }.should be(path) end end describe "#descend" do it "goes through all the segments of an absolute path" do steps = [] Filepath.new("/a/b/c").descend do |seg| steps << seg end steps.should have(4).items steps[0].should eq("/") steps[1].should eq("/a") steps[2].should eq("/a/b") steps[3].should eq("/a/b/c") end it "goes through all the segments of a relative path" do steps = [] Filepath.new("a/b/c").descend do |seg| steps << seg end steps.should have(3).items steps[0].should eq("a") steps[1].should eq("a/b") steps[2].should eq("a/b/c") end it "returns the path itself" do path = Filepath.new("/a/b/c/") path.descend { }.should be(path) end end describe "#to_s" do it "works on computed absolute paths" do (Filepath.new('/') / 'a' / 'b').to_s.should eql('/a/b') end it "works on computed relative paths" do (Filepath.new('a') / 'b').to_s.should eql('a/b') end it "returns normalized paths" do Filepath.new("/foo/bar/..").to_s.should eql('/foo') end it "returns '.' for empty paths" do Filepath.new('').to_s.should eql('.') end end describe "#as_path" do it "returns the path itself" do @root.as_path.should be(@root) end end describe "#==(String)" do test_data = [ ['./', '.'], ['a/../b', 'b'], ['a/.././b', 'b'], ['a/./../b', 'b'], ['./foo', 'foo'], ['a/./b/c', 'a/b/c'], ['a/b/.', 'a/b'], ['a/b/', 'a/b'], ['../a/../b/c/d/../../e', '../b/e'], ] test_data.each do |ver1, ver2| it "says that `#{ver1}` is equivalent to `#{ver2}`" do ph = Filepath.new(ver1) ph.should == ver2 end end end describe "#eql?" do it "is always true when an object is compared to itself" do ph = 'foo/bar/baz'.as_path ph.should eql(ph) end it "matches two different object representing the same path" do p1 = '/foo/bar'.as_path p2 = '/foo/bar'.as_path p1.should eql(p2) end it "does not match different objects representing different paths" do p1 = '/foo/bar'.as_path p2 = '/foo/bar/baz'.as_path p1.should_not eql(p2) end it "does not match objects that are not Filepaths" do p1 = '/foo/bar/baz'.as_path p2 = '/foo/bar/baz' p1.should eq(p2) p1.should_not eql(p2) end end describe "#<=>" do test_data = [ ['a/', 'b'], ['/a', 'a'], ['../b', 'a'], ] test_data.each do |path1, path2| it "says that `#{path1}` precedes `#{path2}`" do p1 = path1.as_path p2 = path2.as_path order = p1 <=> p2 order.should == -1 end end end describe "#hash" do it "has the same value for similar paths" do p1 = '/foo/bar'.as_path p2 = '/foo/bar'.as_path p1.hash.should == p2.hash end it "has different values for different paths" do p1 = '/foo/bar'.as_path p2 = 'foo/quuz'.as_path p1.hash.should_not == p2.hash end it "has different values for different paths with same normalized path" do p1 = '/foo/bar/..'.as_path p2 = '/foo'.as_path p1.should eq(p2) p1.hash.should_not eq(p2.hash) end end describe Filepath::MetadataInfo do describe "#stat" do it "returns a stat for the file" do (@root / 'd1').stat.should be_directory (@root / 'f1').stat.size.should be_zero end it "follows links" do (@root / 'd1' / 'l11').stat.should == '/dev/null'.as_path.stat end it "raises Errno::ENOENT for non-existing files" do expect { (@root / 'foobar').stat }.to raise_error(Errno::ENOENT) end end describe "#lstat" do it "does not follow links" do link_lstat = (@root / 'd1' / 'l11').lstat link_lstat.should_not eq('/dev/null'.as_path.stat) link_lstat.should be_symlink end end end describe Filepath::MetadataChanges do describe "#chtime" do it "change mtime" do ph = @root / 'f1' orig_mtime = ph.mtime ph.chtime(Time.now, 0) ph.mtime.to_i.should eq(0) ph.chtime(Time.now, orig_mtime) ph.mtime.should eq(orig_mtime) end end describe "#chmod" do it "changes file permissions" do ph = @root / 'f1' orig_mode = ph.stat.mode ph.should be_readable ph.chmod(000) ph.should_not be_readable ph.chmod(orig_mode) ph.should be_readable end end end describe Filepath::MetadataTests do describe "#file?" do it "says that `f1` is a file" do (@root / 'f1').should be_file end it "says that `d1/l11` is not a file" do (@root / 'd1' / 'l11').should_not be_file end it "says that the fixture root directory is not a file" do @root.should_not be_file end end describe "#link?" do it "says that `f1` is not a link" do (@root / 'f1').should_not be_link end it "says that `d1/l11` is a link" do (@root / 'd1' / 'l11').should be_link end it "says that the fixture root directory is not a link" do @root.should_not be_link end end describe "#directory?" do it "says that `f1` is not a directory" do (@root / 'f1').should_not be_directory end it "says that `d1/l11` is not a directory" do (@root / 'd1' / 'l11').should_not be_directory end it "says that the fixture root directory is a directory" do @root.should be_directory end end describe "#pipe?" do it "says that `p1` is a pipe" do (@root / 'p1').should be_pipe end it "says that `f1` is not a pipe" do (@root / 'f1').should_not be_pipe end it "says that the fixture root directory is not a pipe" do @root.should_not be_pipe end end describe "#socket?" do it "says that `s1` is a socket" do (@root / 's1').should be_socket end it "says that `f1` is not a socket" do (@root / 'f1').should_not be_socket end it "says that the fixture root directory is not a socket" do @root.should_not be_socket end end describe "#hidden?" do hidden_paths = [ '.foorc', 'foo/.bar', '.foo.bar', ] hidden_paths.each do |path| it "says that <#{path}> is an hidden file" do path.as_path.should be_hidden end end non_hidden_paths = [ 'foo.bar', 'foo/.bar/baz', ] non_hidden_paths.each do |path| it "says that <#{path}> not an hidden file" do path.as_path.should_not be_hidden end end end end describe Filepath::FilesystemInfo do describe "#absolute_path" do test_data = [ ['d1/l11', File.expand_path('d1/l11', FIXTURES_DIR), FIXTURES_DIR], ['/foo/bar', '/foo/bar', '.'], ] test_data.each do |path, abs_path, cwd| it "resolves <#{path}> to <#{abs_path}> (in #{cwd})" do Dir.chdir(cwd) do # FIXME Filepath.new(path).absolute_path.should == abs_path end end end end describe "#real_path" do it "resolves to " do (@root / 'd1' / 'l11').real_path.should == '/dev/null' end end end describe Filepath::FilesystemChanges do let(:ph) { @root / 'd1' / 'test-file' } before(:each) do ph.should_not exist end after(:each) do File.delete(ph) if File.exists?(ph) end describe "#touch" do it "creates an empty file" do ph.touch ph.should exist end it "updates the modification date of an existing file", :broken => true do File.open(ph, "w+") { |file| file << "abc" } File.utime(0, Time.now - 3200, ph) before_stat = File.stat(ph) before_time = Time.now #sleep(5) # let Ruby flush its stat buffer to the disk ph.touch after_time = Time.now after_stat = File.stat(ph) before_stat.should_not eq(after_stat) after_stat.size.should eq(before_stat.size) after_stat.mtime.should be_between(before_time, after_time) end end end describe Filepath::FilesystemTests do describe "mountpoint?" do it "says that is a mount point" do "/proc".as_path.should be_mountpoint end it "says that this RSpec file is not a mount point" do __FILE__.as_path.should_not be_mountpoint end it "says that an non-existing file is not a mount point" do "/foo/bar".as_path.should_not be_mountpoint end it "says that is a mount point" do "/".as_path.should be_mountpoint end end end describe Filepath::ContentInfo do let(:ph) { @root / 'd1' / 'test-file' } before(:each) do ph.should_not exist end after(:each) do File.delete(ph) if File.exists?(ph) end describe "#read" do let(:content) { "a"*20 + "b"*10 + "c"*5 } before(:each) do ph.open('w') { |f| f << content } end it "reads the complete content of a file" do c = ph.read c.should == content end it "reads the content in chunks of arbitrary sizes" do sum = "" len = 8 num_chunks = (content.length.to_f / len).ceil num_chunks.times do |i| c = ph.read(len, len*i) sum += c c.should == content[len*i, len] end sum.should == content end end describe "#readlines" do let(:line) { "abcd12" } let(:lines) { Array.new(3) { line } } it "reads all the lines in the file" do ph.open('w') { |file| file << lines.join("\n") } readlines = ph.readlines readlines.should have(3).lines readlines.all? { |l| l.chomp.should == line } end it "read lines separated by arbitrary separators" do sep = ',' ph.open('w') { |file| file << lines.join(sep) } readlines = ph.readlines(sep) readlines.should have(3).lines readlines[0..-2].all? { |l| l.should == line + sep} readlines.last.should == line end end describe "#size" do before(:each) do ph.touch end it "says that an empty file contains 0 bytes" do ph.size.should be_zero end it "reports the size of a non-empty file" do ph.size.should be_zero ph.open("a") { |f| f << "abc" } ph.size.should eq(3) ph.open("a") { |f| f << "defg" } ph.size.should eq(3+4) end end end describe Filepath::ContentChanges do let(:ph) { @root / 'd1' / 'test-file' } let(:content) { "a"*20 + "b"*10 + "c"*5 } before(:each) do ph.should_not exist end after(:each) do File.delete(ph) if File.exists?(ph) end describe "#open" do before(:each) do ph.touch end it "opens files" do file = ph.open file.should be_a(File) end it "opens files in read-only mode" do ph.open do |file| expect { file << "abc" }.to raise_error(IOError) end end it "opens files in read-write mode" do ph.open('w') do |file| file << "abc" end ph.size.should == 3 end end describe "#write" do it "writes data passed as argument" do ph.write(content) ph.read.should == content end it "overwrites an existing file" do ph.write(content * 2) ph.size.should eq(content.length * 2) ph.write(content) ph.size.should eq(content.length) ph.read.should == content end end describe "#append" do it "appends data to an existing file" do ph.write(content) ph.append(content) ph.size.should eq(content.length * 2) ph.read.should == content * 2 end end describe "#truncate" do before(:each) do ph.open('w') { |f| f << content } end it "truncates a file to 0 bytes" do ph.size.should_not be_zero ph.truncate ph.size.should be_zero end it "truncates a file to an arbitrary size" do ph.size.should_not be_zero ph.truncate(2) ph.size.should == 2 end end end describe Filepath::ContentTests do let(:ph) { @root / 'd1' / 'test-file' } before(:each) do ph.should_not exist end after(:each) do File.delete(ph) if File.exists?(ph) end describe "#empty?" do before(:each) do ph.touch end it "says that an empty file is empty" do ph.should be_empty end it "says that a non-empyt file is not empty" do ph.open('w') { |f| f << "abc" } ph.should_not be_empty end it "says that is empty" do '/dev/null'.as_path.should be_empty end end end describe Filepath::SearchMethods do describe "#entries" do it "raises when path is not a directory" do expect { (@root / 'f1').entries(:files) }.to raise_error(Errno::ENOTDIR) end end describe "#find" do it "finds all paths matching a glob string" do list = @root.find('*1') list.should have(8).items list.each { |path| path.should =~ /1/ } end it "finds all paths matching a Regex" do list = @root.find(/2/) list.should have(6).items list.each { |path| path.should =~ /2/ } end it "finds all paths for which the block returns true" do list = @root.find { |path| path.directory? } list.should have(9).items list.each { |path| path.filename.should =~ /^d/ } end end describe "#files" do it "finds 1 file in the root directory" do @root.files.should have(1).item end it "finds 3 files in the root directory and its sub directories" do @root.files(true).should have(3).item end it "finds 2 files in directory " do (@root / 'd1').files.should have(2).items end it "finds no files in directory " do (@root / 'd1' / 'd12').files.should have(0).items end end describe "#directories" do it "finds 4 directories in the root directory" do @root.directories.should have(4).items end it "finds 9 directories in the root directory and its sub directories" do @root.directories(true).should have(9).item end it "finds 2 directories in directory " do (@root / 'd2').directories.should have(2).items end it "finds no directories in directory " do (@root / 'd1' / 'd13').directories.should have(0).items end end describe "#links" do it "finds no links in the root directory" do @root.links.should have(0).items end it "finds 1 link in directory " do (@root / 'd1').links.should have(1).item end end end describe Filepath::EnvironmentInfo end describe String do describe "#as_path" do it "generates a Filepath from a String" do path = "/a/b/c".as_path path.should be_a(Filepath) path.should eq("/a/b/c") end end end describe Array do describe "#as_path" do it "generates a Filepath from a String" do path = ['/', 'a', 'b', 'c'].as_path path.should be_a(Filepath) path.should eq("/a/b/c") end end end filepath-0.7/spec/spec_helper.rb0000600000004100000410000000105613315563111016745 0ustar www-datawww-dataLIB_DIR = File.expand_path(File.join(File.dirname(__FILE__), %w[.. lib])) $LOAD_PATH.unshift(LIB_DIR) unless $LOAD_PATH.include?(LIB_DIR) require 'filepath' require File.join(File.dirname(__FILE__), 'fixtures') RSpec.configure do |config| config.filter_run_excluding :broken => true config.expect_with :rspec do |rspec| rspec.syntax = [:should, :expect] end config.mock_with :rspec do |mocks| mocks.syntax = [:should, :expect] end end require 'rspec/collection_matchers' # This is free software released into the public domain (CC0 license). filepath-0.7/spec/tasks.rb0000600000004100000410000000116113315563111015576 0ustar www-datawww-data# This is free software released into the public domain (CC0 license). require 'rake/clean' require File.join(File.dirname(__FILE__), 'fixtures') CLEAN.concat FIXTURES_FAKE_ENTRIES namespace :spec do namespace :fixtures do rule %r{/d[0-9x]+$} do |t| mkdir_p t.name end rule %r{/f[0-9]+$} do |t| touch t.name end rule %r{/l[0-9]+$} do |t| ln_s '/dev/null', t.name end rule %r{/p[0-9]+$} do |t| system "mkfifo #{t.name}" end rule %r{/s[0-9]+$} do |t| require 'socket' UNIXServer.new(t.name) end desc "Generate fake dirs and files" task :gen => FIXTURES_FAKE_ENTRIES end end filepath-0.7/.gitignore0000600000004100000410000000011113315563111015154 0ustar www-datawww-data/.bundle/ /Gemfile.lock /coverage/ /doc/ /pkg/ /.yardoc/ /spec/fixtures/ filepath-0.7/Rakefile0000600000004100000410000000045113315563111014640 0ustar www-datawww-data# This is free software released into the public domain (CC0 license). require 'bundler/gem_tasks' require 'rspec/core/rake_task' require File.join(File.dirname(__FILE__), 'spec/tasks') RSpec::Core::RakeTask.new task :default => :spec task :release => :spec task :spec => 'spec:fixtures:gen' filepath-0.7/lib/0000755000004100000410000000000013315563111013751 5ustar www-datawww-datafilepath-0.7/lib/filepath.rb0000600000004100000410000000032613315563111016063 0ustar www-datawww-data# This is free software released into the public domain (CC0 license). require 'filepath/filepath.rb' require 'filepath/filepathlist.rb' require 'filepath/core_ext/array.rb' require 'filepath/core_ext/string.rb' filepath-0.7/lib/filepath/0000755000004100000410000000000013315563111015545 5ustar www-datawww-datafilepath-0.7/lib/filepath/core_ext/0000755000004100000410000000000013315563111017355 5ustar www-datawww-datafilepath-0.7/lib/filepath/core_ext/array.rb0000600000004100000410000000222313315563111021007 0ustar www-datawww-data# This is free software released into the public domain (CC0 license). class Array # Generates a path using the elements of an array as path segments. # # `[a, b, c].as_path` is equivalent to `Filepath.join(a, b, c)`. # # @example Filepath from an array of strings # # ["/", "foo", "bar"].as_path #=> # # @example Filepath from an array of strings and other Filepaths # # server_dir = config["root_dir"] / "server" # ["..", config_dir, "secret"].as_path #=> <../config/server/secret> # # @return [Filepath] a new path generated using the element as path # segments # # @note FIXME: `#as_path` should be `#to_path` but that method name # is already used def as_path Filepath.join(self) end # Generates a path list from an array of paths. # # The elements of the array must respond to `#as_path`. # # `ary.as_path` is equivalent to `FilepathList.new(ary)`. # # @return [FilepathList] a new path list containing the elements of # the array as Filepaths # # @see String#as_path # @see Array#as_path # @see Filepath#as_path def as_path_list FilepathList.new(self) end end filepath-0.7/lib/filepath/core_ext/string.rb0000600000004100000410000000074613315563111021207 0ustar www-datawww-data# This is free software released into the public domain (CC0 license). class String # Generates a path from a String. # # `"/a/b/c".as_path` is equivalent to `Filepath.new("/a/b/c")`. # # @example Filepath from a string # # "/etc/ssl/certs".as_path #=> # # @return [Filepath] a new path generated from the string # # @note FIXME: `#as_path` should be `#to_path` but that method name # is already used def as_path Filepath.new(self) end end filepath-0.7/lib/filepath/filepath.rb0000600000004100000410000005051213315563111017661 0ustar www-datawww-data# This is free software released into the public domain (CC0 license). class Filepath SEPARATOR = '/'.freeze def initialize(path) if path.is_a? Filepath @segments = path.segments elsif path.is_a? Array @segments = path else @segments = split_path_string(path.to_str) end end # @private attr_reader :segments # Creates a Filepath joining the given segments. # # @return [Filepath] a Filepath created joining the given segments def Filepath.join(*raw_paths) if (raw_paths.count == 1) && (raw_paths.first.is_a? Array) raw_paths = raw_paths.first end paths = raw_paths.map { |p| p.as_path } segs = [] paths.each { |path| segs += path.segments } return Filepath.new(segs) end # Appends another path to the current path. # # @example Append a string # # "a/b".as_path / "c" #=> # # @example Append another Filepath # # home = (ENV["HOME"] || "/root").as_path # conf_dir = '.config'.as_path # # home / conf_dir #=> # # @param [Filepath, String] extra_path the path to be appended to the # current path # # @return [Filepath] a new path with the given path appended def /(extra_path) return Filepath.join(self, extra_path) end # Append multiple paths to the current path. # # @return [Filepath] a new path with all the paths appended def join(*extra_paths) return Filepath.join(self, *extra_paths) end # An alias for {Filepath#/}. # # @deprecated Use the {Filepath#/} (slash) method instead. This method # does not show clearly if a path is being added or if a # string should be added to the filename def +(extra_path) warn "Filepath#+ is deprecated, use Filepath#/ instead." return self / extra_path end # Calculates the relative path from a given directory. # # @example relative paths between relative paths # # posts_dir = "posts".as_path # images_dir = "static/images".as_path # # logo = images_dir / 'logo.png' # # logo.relative_to(posts_dir) #=> <../static/images/logo.png> # # @example relative paths between absolute paths # # home_dir = "/home/gioele".as_path # docs_dir = "/home/gioele/Documents".as_path # tmp_dir = "/tmp".as_path # # docs_dir.relative_to(home_dir) #=> # home_dir.relative_to(docs_dir) #=> <..> # # tmp_dir.relative_to(home_dir) #=> <../../tmp> # # @param [Filepath, String] base the directory to use as base for the # relative path # # @return [Filepath] the relative path # # @note this method operates on the normalized paths # # @see #relative_to_file def relative_to(base) base = base.as_path if self.absolute? != base.absolute? self_abs = self.absolute? ? "absolute" : "relative" base_abs = base.absolute? ? "absolute" : "relative" msg = "cannot compare: " msg += "`#{self}` is #{self_abs} while " msg += "`#{base}` is #{base_abs}" raise ArgumentError, msg end self_segs = self.normalized_segments base_segs = base.normalized_segments base_segs_tmp = base_segs.dup num_same = self_segs.find_index do |seg| base_segs_tmp.delete_at(0) != seg end # find_index returns nil if `self` is a subset of `base` num_same ||= self_segs.length num_parent_dirs = base_segs.length - num_same left_in_self = self_segs[num_same..-1] segs = [".."] * num_parent_dirs + left_in_self normalized_segs = normalized_relative_segs(segs) return Filepath.join(normalized_segs) end # Calculates the relative path from a given file. # # @example relative paths between relative paths # # post = "posts/2012-02-14-hello.html".as_path # images_dir = "static/images".as_path # # rel_img_dir = images_dir.relative_to_file(post) # rel_img_dir.to_s #=> "../static/images" # # logo = rel_img_dir / 'logo.png' #=> <../static/images/logo.png> # # @example relative paths between absolute paths # # rc_file = "/home/gioele/.bashrc".as_path # tmp_dir = "/tmp".as_path # # tmp_dir.relative_to_file(rc_file) #=> <../../tmp> # # @param [Filepath, String] base_file the file to use as base for the # relative path # # @return [Filepath] the relative path # # @see #relative_to def relative_to_file(base_file) return relative_to(base_file.as_path.parent_dir) end # The filename component of the path. # # The filename is the component of a path that appears after the last # path separator. # # @return [Filepath] the filename def filename segs = self.normalized_segments if self.root? || segs.empty? return ''.as_path end filename = segs.last return filename.as_path end alias :basename :filename # The dir that contains the file # # @return [Filepath] the path of the parent dir def parent_dir return self / '..' end # Replace the path filename with the supplied path. # # @example # # post = "posts/2012-02-16-hello-world/index.md".as_path # style = post.with_filename("style.css") # style.to_s #=> "posts/2012-02-16-hello-world/style.css" # # @param [Filepath, String] new_path the path to be put in place of # the current filename # # @return [Filepath] a path with the supplied path instead of the # current filename # # @see #filename # @see #with_extension def with_filename(new_path) dir = self.parent_dir return dir / new_path end alias :with_basename :with_filename alias :replace_filename :with_filename alias :replace_basename :with_filename # The extension of the file. # # The extension of a file are the characters after the last dot. # # @return [String] the extension of the file or nil if the file has no # extension # # @see #extension? def extension filename = @segments.last num_dots = filename.count('.') if num_dots.zero? ext = nil elsif filename.start_with?('.') && num_dots == 1 ext = nil elsif filename.end_with?('.') ext = '' else ext = filename.split('.').last end return ext end alias :ext :extension # @overload extension?(ext) # @param [String, Regexp] ext the extension to be matched # # @return whether the file extension matches the given extension # # @overload extension? # @return whether the file has an extension def extension?(ext = nil) cur_ext = self.extension if ext.nil? return !cur_ext.nil? else if ext.is_a? Regexp return !cur_ext.match(ext).nil? else return cur_ext == ext end end end alias :ext? :extension? # Replaces or removes the file extension. # # @see #extension # @see #extension? # @see #without_extension # @see #with_filename # # @overload with_extension(new_ext) # Replaces the file extension with the supplied one. If the file # has no extension it is added to the file name together with a dot. # # @example Extension replacement # # src_path = "pages/about.markdown".as_path # html_path = src_path.with_extension("html") # html_path.to_s #=> "pages/about.html" # # @example Extension addition # # base = "style/main-style".as_path # sass_style = base.with_extension("sass") # sass_style.to_s #=> "style/main-style.sass" # # @param [String] new_ext the new extension # # @return [Filepath] a new path with the replaced extension # # @overload with_extension # Removes the file extension if present. # # The {#without_extension} method provides the same functionality # but has a more meaningful name. # # @example # # post_file = "post/welcome.html" # post_url = post_file.with_extension(nil) # post_url.to_s #=> "post/welcome" # # @return [Filepath] a new path without the extension def with_extension(new_ext) # FIXME: accept block orig_filename = filename.to_s if !self.extension? if new_ext.nil? new_filename = orig_filename else new_filename = orig_filename + '.' + new_ext end else if new_ext.nil? pattern = /\.[^.]*?\Z/ new_filename = orig_filename.sub(pattern, '') else pattern = Regexp.new('.' + extension + '\\Z') new_filename = orig_filename.sub(pattern, '.' + new_ext) end end segs = @segments[0..-2] segs << new_filename return Filepath.new(segs) end alias :replace_extension :with_extension alias :replace_ext :with_extension alias :sub_ext :with_extension # Removes the file extension if present. # # @example # # post_file = "post/welcome.html" # post_url = post_file.without_extension # post_url.to_s #=> "post/welcome" # # @return [Filepath] a new path without the extension # # @see #with_extension def without_extension return with_extension(nil) end alias :remove_ext :without_extension alias :remove_extension :without_extension # Matches a pattern against this path. # # @param [Regexp, Object] pattern the pattern to match against # this path # # @return [Fixnum, nil] the position of the pattern in the path, or # nil if there is no match # # @note this method operates on the normalized path def =~(pattern) return self.to_s =~ pattern end # Is this path pointing to the root directory? # # @return whether the path points to the root directory # # @note this method operates on the normalized paths def root? return self.normalized_segments == [SEPARATOR] # FIXME: windows, mac end # Is this path absolute? # # @example # # "/tmp".absolute? #=> true # "tmp".absolute? #=> false # "../tmp".absolute? #=> false # # FIXME: document what an absolute path is. # # @return whether the current path is absolute # # @see #relative? def absolute? return @segments.first == SEPARATOR # FIXME: windows, mac end # Is this path relative? # # @example # # "/tmp".relative? #=> false # "tmp".relative? #=> true # "../tmp".relative? #=> true # # FIXME: document what a relative path is. # # @return whether the current path is relative # # @see #absolute? def relative? return !self.absolute? end # Simplify paths that contain `.` and `..`. # # The resulting path will be in normal form. # # @example # # path = $ENV["HOME"] / ".." / "jack" / "." # # path #=> # path.normalized #=> # # FIXME: document what normal form is. # # @return [Filepath] a new path that does not contain `.` or `..` # segments. def normalized return Filepath.join(self.normalized_segments) end alias :normalised :normalized # Iterates over all the path segments, from the leftmost to the # rightmost. # # @example # # web_dir = "/srv/example.org/web/html".as_path # web_dir.each_segment do |seg| # puts seg # end # # # produces # # # # / # # srv # # example.org # # web # # html # # @yield [path] TODO # # @return [Filepath] the path itself. # # @see #ascend # @see #descend def each_segment(&block) @segments.each(&block) return self end # Iterates over all the path directories, from the current path to # the root. # # @example # # web_dir = "/srv/example.org/web/html/".as_path # web_dir.ascend do |path| # is = path.readable? ? "is" : "is NOT" # # puts "#{path} #{is} readable" # end # # # produces # # # # /srv/example.org/web/html is NOT redable # # /srv/example.org/web is NOT readable # # /srv/example.org is readable # # /srv is readable # # / is readable # # @param max_depth the maximum depth to ascend to, nil to ascend # without limits. # # @yield [path] TODO # # @return [Filepath] the path itself. # # @see #each_segment # @see #descend def ascend(max_depth = nil, &block) iterate(max_depth, :reverse_each, &block) end # Iterates over all the directory that lead to the current path. # # @example # # web_dir = "/srv/example.org/web/html/".as_path # web_dir.descend do |path| # is = path.readable? ? "is" : "is NOT" # # puts "#{path} #{is} readable" # end # # # produces # # # # / is readable # # /srv is readable # # /srv/example.org is readable # # /srv/example.org/web is NOT readable # # /srv/example.org/web/html is NOT redable # # @param max_depth the maximum depth to descent to, nil to descend # without limits. # # @yield [path] TODO # # @return [Filepath] the path itself. # # @see #each_segment # @see #ascend def descend(max_depth = nil, &block) iterate(max_depth, :each, &block) end # @private def iterate(max_depth, method, &block) max_depth ||= @segments.length (1..max_depth).send(method) do |limit| segs = @segments.take(limit) yield Filepath.join(segs) end return self end # This path converted to a String. # # @example differences between #to_raw_string and #to_s # # path = "/home/gioele/.config".as_path / ".." / ".cache" # path.to_raw_string #=> "/home/gioele/config/../.cache" # path.to_s #=> "/home/gioele/.cache" # # @return [String] this path converted to a String # # @see #to_s def to_raw_string @to_raw_string ||= join_segments(@segments) end alias :to_raw_str :to_raw_string # @return [String] this path converted to a String # # @note this method operates on the normalized path def to_s to_str end # @private def to_str @to_str ||= join_segments(self.normalized_segments) end # @return [Filepath] the path itself. def as_path self end # @private def inspect return '<' + self.to_raw_string + '>' end # Checks whether two paths are equivalent. # # Two paths are equivalent when they have the same normalized segments. # # A relative and an absolute path will always be considered different. # To compare relative paths to absolute path, expand first the relative # path using {#absolute_path} or {#real_path}. # # @example # # path1 = "foo/bar".as_path # path2 = "foo/bar/baz".as_path # path3 = "foo/bar/baz/../../bar".as_path # # path1 == path2 #=> false # path1 == path2.parent_dir #=> true # path1 == path3 #=> true # # @param [Filepath, String] other the other path to compare # # @return [boolean] whether the other path is equivalent to the current path # # @note this method compares the normalized versions of the paths def ==(other) return self.normalized_segments == other.as_path.normalized_segments end # @private def eql?(other) if self.equal?(other) return true elsif self.class != other.class return false end return @segments == other.segments end # @private def <=>(other) return self.normalized_segments <=> other.normalized_segments end # @private def hash return @segments.hash end # @private def split_path_string(raw_path) segments = raw_path.split(SEPARATOR) # FIXME: windows, mac if raw_path == SEPARATOR segments << SEPARATOR end if !segments.empty? && segments.first.empty? segments[0] = SEPARATOR end return segments end # @private def normalized_segments @normalized_segments ||= normalized_relative_segs(@segments) end # @private def normalized_relative_segs(orig_segs) segs = orig_segs.dup i = 0 while (i < segs.length) if segs[i] == '..' && segs[i-1] == SEPARATOR # remove '..' segments following a root delimiter segs.delete_at(i) i -= 1 elsif segs[i] == '..' && segs[i-1] != '..' && i >= 1 # remove every segment followed by a ".." marker segs.delete_at(i) segs.delete_at(i-1) i -= 2 elsif segs[i] == '.' # remove "current dir" markers segs.delete_at(i) i -= 1 end i += 1 end return segs end # @private def join_segments(segs) # FIXME: windows, mac # FIXME: avoid string substitutions and regexen return segs.join(SEPARATOR).sub(%r{^//}, SEPARATOR).sub(/\A\Z/, '.') end module MethodDelegation # @private def define_io_method(filepath_method, io_method = nil) io_method ||= filepath_method define_method(filepath_method) do |*args, &block| return File.send(io_method, self, *args, &block) end end # @private def define_file_method(filepath_method, file_method = nil) file_method ||= filepath_method define_method(filepath_method) do |*args| all_args = args + [self] return File.send(file_method, *all_args) end end # @private def define_filetest_method(filepath_method, filetest_method = nil) filetest_method ||= filepath_method define_method(filepath_method) do return FileTest.send(filetest_method, self) end end end module MetadataInfo extend MethodDelegation define_file_method :stat define_file_method :lstat define_file_method :atime define_file_method :ctime define_file_method :mtime end module MetadataChanges extend MethodDelegation # utime(atime, mtime) define_file_method :utime alias :chtime :utime # chmod(mode) define_file_method :chmod # lchmod(mode) define_file_method :lchmod # chown(owner_id, group_id) define_file_method :chown # lchown(owner_id, group_id) define_file_method :lchown end module MetadataTests extend MethodDelegation define_filetest_method :file? define_filetest_method :link?, :symlink? alias :symlink? :link? define_filetest_method :directory? define_filetest_method :pipe? define_filetest_method :socket? define_filetest_method :blockdev? define_filetest_method :chardev? define_filetest_method :exists? alias :exist? :exists? define_filetest_method :readable? define_filetest_method :writeable? define_filetest_method :executable? define_filetest_method :setgid? define_filetest_method :setuid? define_filetest_method :sticky? def hidden? @segments.last.start_with?('.') # FIXME: windows, mac end end module FilesystemInfo def absolute_path(base_dir = Dir.pwd) # FIXME: rename to `#absolute`? if self.absolute? return self end return base_dir.as_path / self end def real_path(base_dir = Dir.pwd) path = absolute_path(base_dir) return path.resolve_link end alias :realpath :real_path def resolve_link return File.readlink(self).as_path end end module FilesystemChanges def touch self.open('a') do ; end File.utime(File.atime(self), Time.now, self) end end module FilesystemTests def mountpoint? if !directory? || !exists? return false end if root? return true end return self.lstat.dev != parent_dir.lstat.dev end end module ContentInfo extend MethodDelegation define_io_method :read if IO.respond_to? :binread define_io_method :binread else alias :binread :read end define_io_method :readlines define_io_method :size end module ContentChanges extend MethodDelegation define_io_method :open def write(content) open('w') do |file| file.write(content) end end def append(content) open('a') do |file| file.write(content) end end define_io_method :file_truncate, :truncate def truncate(*args) if args.empty? args << 0 end file_truncate(*args) end end module ContentTests extend MethodDelegation define_file_method :empty?, :zero? alias :zero? :empty? end module SearchMethods def entries(pattern = '*', recursive = false) if !self.directory? raise Errno::ENOTDIR.new(self) end glob = self glob /= '**' if recursive glob /= pattern raw_entries = Dir.glob(glob) entries = FilepathList.new(raw_entries) return entries end alias :glob :entries def find(pattern = nil, recursive = true, &block) if !pattern.nil? && pattern.respond_to?(:to_str) return entries(pattern, recursive) end if !block_given? block = proc { |e| e =~ pattern } end return entries('*', true).select { |e| block.call(e) } end def files(recursive = false) entries('*', recursive).select_entries(:file) end def links(recursive = false) entries('*', recursive).select_entries(:link) end def directories(recursive = false) entries('*', recursive).select_entries(:directory) end end module EnvironmentInfo def Filepath.getwd return Dir.getwd.as_path end end include MetadataInfo include MetadataChanges include MetadataTests include FilesystemInfo include FilesystemChanges include FilesystemTests include ContentInfo include ContentChanges include ContentTests include SearchMethods end filepath-0.7/lib/filepath/filepathlist.rb0000600000004100000410000000547413315563111020564 0ustar www-datawww-data# This is free software released into the public domain (CC0 license). class FilepathList include Enumerable SEPARATOR = ':'.freeze def initialize(raw_entries = nil) raw_entries ||= [] @entries = raw_entries.map { |e| e.as_path } end def select_entries(type) raw_entries = @entries.delete_if { |e| !e.send(type.to_s + '?') } return FilepathList.new(raw_entries) end def files return select_entries(:file) end def links return select_entries(:link) end def directories return select_entries(:directory) end def /(extra_path) return self.map { |path| path / extra_path } end def +(extra_entries) return FilepathList.new(@entries + extra_entries.to_a) end def -(others) remaining_entries = @entries - others.as_path_list.to_a return FilepathList.new(remaining_entries) end def <<(extra_path) return FilepathList.new(@entries + [extra_path.as_path]) end def *(other_list) if !other_list.is_a? FilepathList other_list = FilepathList.new(Array(other_list)) end other_entries = other_list.entries paths = @entries.product(other_entries).map { |p1, p2| p1 / p2 } return FilepathList.new(paths) end def remove_common_segments all_segs = @entries.map(&:segments) max_length = all_segs.map(&:length).min idx_different = nil (0..max_length).each do |i| segment = all_segs.first[i] different = all_segs.any? { |segs| segs[i] != segment } if different idx_different = i break end end idx_different ||= max_length remaining_segs = all_segs.map { |segs| segs[idx_different..-1] } return FilepathList.new(remaining_segs) end # @return [FilepathList] the path list itself def as_path_list self end def to_a @entries end def to_s @to_s ||= @entries.map(&:to_str).join(SEPARATOR) end # @private def inspect @entries.inspect end def ==(other) @entries == other.as_path_list.to_a end module ArrayMethods # @private def self.define_array_method(name) define_method(name) do |*args, &block| return @entries.send(name, *args, &block) end end define_array_method :[] define_array_method :empty? define_array_method :include? define_array_method :each define_array_method :all? define_array_method :any? define_array_method :none? define_array_method :size end module EntriesMethods def map(&block) mapped_entries = @entries.map(&block) return FilepathList.new(mapped_entries) end def select(pattern = nil, &block) if !block_given? block = proc { |e| e =~ pattern } end remaining_entries = @entries.select { |e| block.call(e) } return FilepathList.new(remaining_entries) end def exclude(pattern = nil, &block) if block_given? select { |e| !block.call(e) } else select { |e| !(e =~ pattern) } end end end include ArrayMethods include EntriesMethods end filepath-0.7/.yardopts0000600000004100000410000000004313315563111015036 0ustar www-datawww-data-m markdown --no-private - COPYING filepath-0.7/Gemfile0000600000004100000410000000004713315563111014467 0ustar www-datawww-datasource 'https://rubygems.org' gemspec filepath-0.7/filepath.gemspec0000600000004100000410000000235413315563111016340 0ustar www-datawww-data# coding: utf-8 lib = File.expand_path('../lib', __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) Gem::Specification.new do |spec| spec.name = "filepath" spec.version = "0.7" spec.authors = ["Gioele Barabucci"] spec.email = ["gioele@svario.it"] spec.summary = "A small library to manipulate paths; a modern replacement " + "for the standard Pathname." spec.description = "The Filepath class provides immutable objects with dozens " + "of convenience methods for common operations such as calculating " + "relative paths, concatenating paths, finding all the files in " + "a directory or modifying all the extensions of a list of " + "filenames at once." spec.homepage = "http://github.com/gioele/filepath" spec.license = "CC0" spec.files = `git ls-files`.split($/) spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) spec.require_paths = ["lib"] spec.add_development_dependency "bundler", "~> 1.3" spec.add_development_dependency "rake" spec.add_development_dependency "rspec" spec.add_development_dependency "rspec-collection_matchers" end