[DRE-commits] [ruby-filepath] 01/07: Imported Upstream version 0.6
Gioele Barabucci
gioele-guest at alioth.debian.org
Sun Sep 22 13:23:29 UTC 2013
This is an automated email from the git hooks/post-receive script.
gioele-guest pushed a commit to branch master
in repository ruby-filepath.
commit f81309203e98affa42c4faa5d088ede51ea64b0d
Author: Gioele Barabucci <gioele at svario.it>
Date: Sun Sep 22 11:33:45 2013 +0000
Imported Upstream version 0.6
---
.gitignore | 7 +
.travis.yml | 8 +
.yardopts | 4 +
COPYING | 121 +++++
Gemfile | 3 +
README.md | 138 +++++
Rakefile | 13 +
checksums.yaml.gz | Bin 0 -> 269 bytes
filepath.gemspec | 32 ++
lib/filepath.rb | 7 +
lib/filepath/core_ext/array.rb | 45 ++
lib/filepath/core_ext/string.rb | 21 +
lib/filepath/filepath.rb | 981 ++++++++++++++++++++++++++++++++++++
lib/filepath/filepathlist.rb | 157 ++++++
metadata.yml | 112 +++++
spec/filepath_spec.rb | 1054 +++++++++++++++++++++++++++++++++++++++
spec/filepathlist_spec.rb | 280 +++++++++++
spec/spec_helper.rb | 12 +
spec/tasks.rb | 53 ++
19 files changed, 3048 insertions(+)
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..0698218
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,7 @@
+/.bundle/
+/Gemfile.lock
+/coverage/
+/doc/
+/pkg/
+/.yardoc/
+/spec/fixtures/
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..a35b2ab
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,8 @@
+language: ruby
+rvm:
+ - "1.8.7"
+ - "1.9.3"
+ - "2.0.0"
+ - jruby-18mode
+ - jruby-19mode
+ - rbx-19mode
diff --git a/.yardopts b/.yardopts
new file mode 100644
index 0000000..f25383d
--- /dev/null
+++ b/.yardopts
@@ -0,0 +1,4 @@
+-m markdown
+--no-private
+-
+COPYING
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..0e259d4
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,121 @@
+Creative 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.
diff --git a/Gemfile b/Gemfile
new file mode 100644
index 0000000..fa75df1
--- /dev/null
+++ b/Gemfile
@@ -0,0 +1,3 @@
+source 'https://rubygems.org'
+
+gemspec
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..d39262b
--- /dev/null
+++ b/README.md
@@ -0,0 +1,138 @@
+FilePath
+========
+
+filepath is a small library that helps dealing with files, directories and
+paths in general; a modern replacement for the standard Pathname.
+
+filepath is built around two main classes: `FilePath`, that represents paths,
+and `FilePathList`, lists of paths. The instances of these classes are
+immutable objects with dozens of convience methods for common operations such
+as calculating relative paths, concatenating paths, finding all the files in
+a directory or modifing all the extensions of a list of file names at once.
+
+
+Features and examples
+---------------------
+
+The main purpose of FilePath is to able to write
+
+ require __FILE__.as_path / 'spec' / 'tasks'
+
+instad 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.8.7,
+Ruby 1.9.3 and JRuby 1.6.
+
+
+Installation
+------------
+
+ gem install filepath
+
+
+Authors
+-------
+
+* Gioele Barabucci <http://svario.it/gioele> (initial author)
+
+
+Development
+-----------
+
+Code
+: <https://github.com/gioele/filepath>
+
+Report issues
+: <https://github.com/gioele/filepath/issues>
+
+Documentation
+: <http://rubydoc.info/gems/filepath>
+
+
+License
+-------
+
+This is free software released into the public domain (CC0 license).
+
+See the `COPYING` file or <http://creativecommons.org/publicdomain/zero/1.0/>
+for more details.
diff --git a/Rakefile b/Rakefile
new file mode 100644
index 0000000..9e1a46b
--- /dev/null
+++ b/Rakefile
@@ -0,0 +1,13 @@
+# 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'
diff --git a/checksums.yaml.gz b/checksums.yaml.gz
new file mode 100644
index 0000000..912eccf
Binary files /dev/null and b/checksums.yaml.gz differ
diff --git a/filepath.gemspec b/filepath.gemspec
new file mode 100644
index 0000000..f1b20e8
--- /dev/null
+++ b/filepath.gemspec
@@ -0,0 +1,32 @@
+# 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.6"
+ spec.authors = ["Gioele Barabucci"]
+ spec.email = ["gioele at svario.it"]
+ spec.summary = "filepath is a small library that helps dealing with files, " +
+ "directories and paths in general; a modern replacement for " +
+ "the standard Pathname."
+ spec.description = "filepath is built around two main classes: `FilePath`, that " +
+ "represents paths, and `FilePathList`, lists of paths. The " +
+ "instances of these classes are immutable objects with dozens " +
+ "of convience methods for common operations such as calculating " +
+ "relative paths, concatenating paths, finding all the files in " +
+ "a directory or modifing all the extensions of a list of file "
+ "names 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"
+end
diff --git a/lib/filepath.rb b/lib/filepath.rb
new file mode 100644
index 0000000..f016f3e
--- /dev/null
+++ b/lib/filepath.rb
@@ -0,0 +1,7 @@
+# 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'
+
diff --git a/lib/filepath/core_ext/array.rb b/lib/filepath/core_ext/array.rb
new file mode 100644
index 0000000..8b11570
--- /dev/null
+++ b/lib/filepath/core_ext/array.rb
@@ -0,0 +1,45 @@
+# 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 #=> </foo/bar>
+ #
+ # @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
diff --git a/lib/filepath/core_ext/string.rb b/lib/filepath/core_ext/string.rb
new file mode 100644
index 0000000..6738177
--- /dev/null
+++ b/lib/filepath/core_ext/string.rb
@@ -0,0 +1,21 @@
+# 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 #=> </etc/ssl/certs>
+ #
+ # @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
diff --git a/lib/filepath/filepath.rb b/lib/filepath/filepath.rb
new file mode 100644
index 0000000..de3fbdb
--- /dev/null
+++ b/lib/filepath/filepath.rb
@@ -0,0 +1,981 @@
+# 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" #=> <a/b/c>
+ #
+ # @example Append another FilePath
+ #
+ # home = (ENV["HOME"] || "/root").as_path
+ # conf_dir = '.config'.as_path
+ #
+ # home / conf_dir #=> </home/user/.config>
+ #
+ # @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) #=> <Documents>
+ # 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 #=> </home/gioele/../jack/.>
+ # path.normalized #=> </home/jack>
+ #
+ # 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
diff --git a/lib/filepath/filepathlist.rb b/lib/filepath/filepathlist.rb
new file mode 100644
index 0000000..8925d25
--- /dev/null
+++ b/lib/filepath/filepathlist.rb
@@ -0,0 +1,157 @@
+# 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
diff --git a/metadata.yml b/metadata.yml
new file mode 100644
index 0000000..176f704
--- /dev/null
+++ b/metadata.yml
@@ -0,0 +1,112 @@
+--- !ruby/object:Gem::Specification
+name: filepath
+version: !ruby/object:Gem::Version
+ version: '0.6'
+platform: ruby
+authors:
+- Gioele Barabucci
+autorequire:
+bindir: bin
+cert_chain: []
+date: 2013-09-15 00:00:00.000000000 Z
+dependencies:
+- !ruby/object:Gem::Dependency
+ name: bundler
+ requirement: !ruby/object:Gem::Requirement
+ requirements:
+ - - ~>
+ - !ruby/object:Gem::Version
+ version: '1.3'
+ type: :development
+ prerelease: false
+ version_requirements: !ruby/object:Gem::Requirement
+ requirements:
+ - - ~>
+ - !ruby/object:Gem::Version
+ version: '1.3'
+- !ruby/object:Gem::Dependency
+ name: rake
+ requirement: !ruby/object:Gem::Requirement
+ requirements:
+ - - '>='
+ - !ruby/object:Gem::Version
+ version: '0'
+ type: :development
+ prerelease: false
+ version_requirements: !ruby/object:Gem::Requirement
+ requirements:
+ - - '>='
+ - !ruby/object:Gem::Version
+ version: '0'
+- !ruby/object:Gem::Dependency
+ name: rspec
+ requirement: !ruby/object:Gem::Requirement
+ requirements:
+ - - '>='
+ - !ruby/object:Gem::Version
+ version: '0'
+ type: :development
+ prerelease: false
+ version_requirements: !ruby/object:Gem::Requirement
+ requirements:
+ - - '>='
+ - !ruby/object:Gem::Version
+ version: '0'
+description: 'filepath is built around two main classes: `FilePath`, that represents
+ paths, and `FilePathList`, lists of paths. The instances of these classes are immutable
+ objects with dozens of convience methods for common operations such as calculating
+ relative paths, concatenating paths, finding all the files in a directory or modifing
+ all the extensions of a list of file '
+email:
+- gioele at svario.it
+executables: []
+extensions: []
+extra_rdoc_files: []
+files:
+- .gitignore
+- .travis.yml
+- .yardopts
+- COPYING
+- Gemfile
+- README.md
+- Rakefile
+- filepath.gemspec
+- lib/filepath.rb
+- lib/filepath/core_ext/array.rb
+- lib/filepath/core_ext/string.rb
+- lib/filepath/filepath.rb
+- lib/filepath/filepathlist.rb
+- spec/filepath_spec.rb
+- spec/filepathlist_spec.rb
+- spec/spec_helper.rb
+- spec/tasks.rb
+homepage: http://github.com/gioele/filepath
+licenses:
+- CC0
+metadata: {}
+post_install_message:
+rdoc_options: []
+require_paths:
+- lib
+required_ruby_version: !ruby/object:Gem::Requirement
+ requirements:
+ - - '>='
+ - !ruby/object:Gem::Version
+ version: '0'
+required_rubygems_version: !ruby/object:Gem::Requirement
+ requirements:
+ - - '>='
+ - !ruby/object:Gem::Version
+ version: '0'
+requirements: []
+rubyforge_project:
+rubygems_version: 2.0.4
+signing_key:
+specification_version: 4
+summary: filepath is a small library that helps dealing with files, directories and
+ paths in general; a modern replacement for the standard Pathname.
+test_files:
+- spec/filepath_spec.rb
+- spec/filepathlist_spec.rb
+- spec/spec_helper.rb
+- spec/tasks.rb
diff --git a/spec/filepath_spec.rb b/spec/filepath_spec.rb
new file mode 100644
index 0000000..a35e300
--- /dev/null
+++ b/spec/filepath_spec.rb
@@ -0,0 +1,1054 @@
+# 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 <a/b> does not point to the root directory" do
+ FilePath.new('a/b').should_not be_root
+ end
+
+ it "says that </foo> 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 <d1/l11> to </dev/null>" 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 </proc> 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 </dev/null> 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 <d1>" do
+ (@root / 'd1').files.should have(2).items
+ end
+
+ it "finds no files in directory <d1/d12>" 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 <d2>" do
+ (@root / 'd2').directories.should have(2).items
+ end
+
+ it "finds no directories in directory <d1/d13>" 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 <d1>" 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
diff --git a/spec/filepathlist_spec.rb b/spec/filepathlist_spec.rb
new file mode 100644
index 0000000..4ded8f0
--- /dev/null
+++ b/spec/filepathlist_spec.rb
@@ -0,0 +1,280 @@
+# 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 [<a/b>, <a/c>, </a/d>]" 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
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
new file mode 100644
index 0000000..059e2e4
--- /dev/null
+++ b/spec/spec_helper.rb
@@ -0,0 +1,12 @@
+# This is free software released into the public domain (CC0 license).
+
+LIB_DIR = File.expand_path(File.join(File.dirname(__FILE__), %w[.. lib]))
+$LOAD_PATH.unshift(LIB_DIR) unless $LOAD_PATH.include?(LIB_DIR)
+
+require 'filepath'
+
+RSpec.configure do |config|
+ config.filter_run_excluding :broken => true
+end
+
+FIXTURES_DIR = File.join(%w{spec fixtures})
diff --git a/spec/tasks.rb b/spec/tasks.rb
new file mode 100644
index 0000000..87a4377
--- /dev/null
+++ b/spec/tasks.rb
@@ -0,0 +1,53 @@
+# This is free software released into the public domain (CC0 license).
+
+require 'rake/clean'
+
+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)) }
+
+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
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-ruby-extras/ruby-filepath.git
More information about the Pkg-ruby-extras-commits
mailing list