roger 1.1.3 → 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. data/.hound.yml +2 -0
  3. data/.rubocop.yml +47 -0
  4. data/.travis.yml +1 -5
  5. data/CHANGELOG.md +8 -0
  6. data/Gemfile +3 -3
  7. data/Rakefile +10 -4
  8. data/bin/roger +1 -1
  9. data/doc/mockupfile.md +97 -0
  10. data/doc/templating.md +5 -1
  11. data/examples/default_template/Gemfile +1 -1
  12. data/lib/roger/cli.rb +41 -36
  13. data/lib/roger/cli/command.rb +2 -4
  14. data/lib/roger/cli/generate.rb +1 -0
  15. data/lib/roger/cli/release.rb +2 -2
  16. data/lib/roger/cli/serve.rb +11 -11
  17. data/lib/roger/cli/test.rb +6 -5
  18. data/lib/roger/extractor.rb +42 -43
  19. data/lib/roger/generators.rb +27 -19
  20. data/lib/roger/generators/generator.rb +7 -10
  21. data/lib/roger/generators/new.rb +56 -41
  22. data/lib/roger/generators/templates/generator.tt +5 -5
  23. data/lib/roger/helpers/get_callable.rb +15 -14
  24. data/lib/roger/helpers/logging.rb +35 -13
  25. data/lib/roger/mockupfile.rb +13 -23
  26. data/lib/roger/project.rb +41 -34
  27. data/lib/roger/rack/roger.rb +28 -29
  28. data/lib/roger/rack/sleep.rb +4 -5
  29. data/lib/roger/release.rb +95 -72
  30. data/lib/roger/release/cleaner.rb +14 -13
  31. data/lib/roger/release/finalizers.rb +10 -10
  32. data/lib/roger/release/finalizers/dir.rb +17 -19
  33. data/lib/roger/release/finalizers/git_branch.rb +76 -38
  34. data/lib/roger/release/finalizers/rsync.rb +60 -49
  35. data/lib/roger/release/finalizers/zip.rb +32 -29
  36. data/lib/roger/release/injector.rb +43 -37
  37. data/lib/roger/release/processors.rb +24 -22
  38. data/lib/roger/release/processors/mockup.rb +97 -69
  39. data/lib/roger/release/processors/url_relativizer.rb +57 -30
  40. data/lib/roger/release/scm.rb +30 -27
  41. data/lib/roger/release/scm/git.rb +101 -92
  42. data/lib/roger/resolver.rb +86 -61
  43. data/lib/roger/server.rb +52 -27
  44. data/lib/roger/template.rb +102 -74
  45. data/lib/roger/test.rb +16 -13
  46. data/lib/roger/version.rb +3 -2
  47. data/roger.gemspec +9 -5
  48. data/test/helpers/cli.rb +17 -15
  49. data/test/project/Gemfile +2 -2
  50. data/test/project/html/formats/csv.rcsv +0 -0
  51. data/test/project/lib/generators/test.rb +2 -3
  52. data/test/project/lib/tests/fail/fail.rb +5 -6
  53. data/test/project/lib/tests/noop/lib/cli.rb +2 -1
  54. data/test/project/lib/tests/noop/lib/test.rb +5 -5
  55. data/test/project/lib/tests/noop/noop.rb +2 -1
  56. data/test/project/lib/tests/succeed/succeed.rb +5 -6
  57. data/test/unit/cli/cli_base_test.rb +2 -3
  58. data/test/unit/cli/cli_generate_test.rb +9 -10
  59. data/test/unit/cli/cli_serve_test.rb +22 -18
  60. data/test/unit/cli/cli_test_test.rb +13 -15
  61. data/test/unit/cli/cli_version_test.rb +4 -4
  62. data/test/unit/generators_test.rb +8 -10
  63. data/test/unit/helpers/logging_test.rb +64 -0
  64. data/test/unit/rack/roger_test.rb +21 -0
  65. data/test/unit/release/cleaner_test.rb +23 -19
  66. data/test/unit/release/finalizers/git_branch_test.rb +2 -1
  67. data/test/unit/release/finalizers/zip_test.rb +48 -0
  68. data/test/unit/release/mockup_test.rb +48 -0
  69. data/test/unit/release/processors_test.rb +19 -19
  70. data/test/unit/release_test.rb +15 -14
  71. data/test/unit/resolver_test.rb +21 -14
  72. data/test/unit/server_test.rb +31 -0
  73. data/test/unit/template_test.rb +58 -36
  74. data/test/unit/test_test.rb +3 -2
  75. metadata +35 -9
  76. data/test/Mockupfile-syntax.rb +0 -93
@@ -1,47 +1,74 @@
1
- require File.dirname(__FILE__) + '../../../resolver'
1
+ require File.dirname(__FILE__) + "../../../resolver"
2
2
 
3
3
  module Roger::Release::Processors
4
+ # URL relativizer processor
5
+ # The relativizer can be used to rewrite absolute paths in attributes to relative paths
6
+ # during release.
4
7
  class UrlRelativizer < Base
5
-
6
- def initialize(options={})
8
+ def initialize(options = {})
7
9
  @options = {
8
- :url_attributes => %w{src href action},
9
- :match => ["**/*.html"],
10
- :skip => []
10
+ url_attributes: %w(src href action),
11
+ match: ["**/*.html"],
12
+ skip: []
11
13
  }
12
-
13
- @options.update(options) if options
14
+
15
+ @options.update(options) if options
14
16
  end
15
17
 
16
- def call(release, options={})
18
+ def call(release, options = {})
17
19
  options = {}.update(@options).update(options)
18
-
19
- release.log(self, "Relativizing all URLS in #{options[:match].inspect} files in attributes #{options[:url_attributes].inspect}, skipping #{options[:skip].any? ? options[:skip].inspect : "none" }")
20
-
20
+
21
+ log_call(options)
22
+
21
23
  @resolver = Roger::Resolver.new(release.build_path)
22
- release.get_files(options[:match], options[:skip]).each do |file_path|
24
+
25
+ relativize_attributes_in_files(
26
+ options[:url_attributes],
27
+ release.get_files(options[:match], options[:skip])
28
+ )
29
+ end
30
+
31
+ protected
32
+
33
+ def log_call(options)
34
+ log_message = "Relativizing all URLS in #{options[:match].inspect}"
35
+ log_message << "files in attributes #{options[:url_attributes].inspect},"
36
+ log_message << "skipping #{options[:skip].any? ? options[:skip].inspect : 'none' }"
37
+ release.log(self, log_message)
38
+ end
39
+
40
+ def relativize_attributes_in_files(attributes, files)
41
+ files.each do |file_path|
23
42
  release.debug(self, "Relativizing URLS in #{file_path}") do
24
- orig_source = File.read(file_path)
25
- File.open(file_path,"w") do |f|
26
- doc = Hpricot(orig_source)
27
- options[:url_attributes].each do |attribute|
28
- (doc/"*[@#{attribute}]").each do |tag|
29
- converted_url = @resolver.url_to_relative_url(tag[attribute], file_path)
30
- release.debug(self, "Converting '#{tag[attribute]}' to '#{converted_url}'")
31
- case converted_url
32
- when String
33
- tag[attribute] = converted_url
34
- when nil
35
- release.log(self, "Could not resolve link #{tag[attribute]} in #{file_path}")
36
- end
37
- end
38
- end
39
- f.write(doc.to_original_html)
43
+ relativize_attributes_in_file(attributes, file_path)
44
+ end
45
+ end
46
+ end
47
+
48
+ def relativize_attributes_in_file(attributes, file_path)
49
+ orig_source = File.read(file_path)
50
+ File.open(file_path, "w") do |f|
51
+ f.write(relativize_attributes_in_source(attributes, orig_source, file_path))
52
+ end
53
+ end
54
+
55
+ def relativize_attributes_in_source(attributes, source, file_path)
56
+ doc = Hpricot(source)
57
+ attributes.each do |attribute|
58
+ (doc / "*[@#{attribute}]").each do |tag|
59
+ converted_url = @resolver.url_to_relative_url(tag[attribute], file_path)
60
+ release.debug(self, "Converting '#{tag[attribute]}' to '#{converted_url}'")
61
+ case converted_url
62
+ when String
63
+ tag[attribute] = converted_url
64
+ when nil
65
+ release.log(self, "Could not resolve link #{tag[attribute]} in #{file_path}")
40
66
  end
41
67
  end
42
68
  end
69
+ doc.to_original_html
43
70
  end
44
71
  end
45
72
  end
46
73
 
47
- Roger::Release::Processors.register(:url_relativizer, Roger::Release::Processors::UrlRelativizer)
74
+ Roger::Release::Processors.register(:url_relativizer, Roger::Release::Processors::UrlRelativizer)
@@ -1,31 +1,34 @@
1
- module Roger::Release::Scm
2
- class Base
3
-
4
- attr_reader :config
5
-
6
- def initialize(config={})
7
- @config = config
8
- end
9
-
10
- # Returns the release version string from the SCM
11
- #
12
- # @return String The current version string
13
- def version
14
- raise "Implement in subclass"
15
- end
16
-
17
- # Returns the release version date from the SCM
18
- def date
19
- raise "Implement in subclass"
20
- end
21
-
22
- # Returns a Release::Scm object with the previous version's data
23
- #
24
- # @return Roger::Release::Scm The previous version
25
- def previous
26
- raise "Implement in subclass"
1
+ module Roger
2
+ class Release
3
+ module Scm
4
+ # Abstract SCM base class
5
+ class Base
6
+ attr_reader :config
7
+
8
+ def initialize(config = {})
9
+ @config = config
10
+ end
11
+
12
+ # Returns the release version string from the SCM
13
+ #
14
+ # @return String The current version string
15
+ def version
16
+ fail "Implement in subclass"
17
+ end
18
+
19
+ # Returns the release version date from the SCM
20
+ def date
21
+ fail "Implement in subclass"
22
+ end
23
+
24
+ # Returns a Release::Scm object with the previous version's data
25
+ #
26
+ # @return Roger::Release::Scm The previous version
27
+ def previous
28
+ fail "Implement in subclass"
29
+ end
30
+ end
27
31
  end
28
-
29
32
  end
30
33
  end
31
34
 
@@ -1,106 +1,115 @@
1
- require 'pathname'
2
-
3
- module Roger::Release::Scm
4
- class Git < Base
5
-
6
- # @option config [String] :ref Ref to use for current tag
7
- # @option config [String, Pathname] :path Path to working dir
8
- def initialize(config={})
9
- super(config)
10
- @config[:ref] ||= "HEAD"
11
- end
12
-
13
- # Version is either:
14
- # - the tagged version number (first "v" will be stripped) or
15
- # - the return value of "git describe --tags HEAD"
16
- # - the short SHA1 if there hasn't been a previous tag
17
- def version
18
- get_scm_data if @_version.nil?
19
- @_version
20
- end
21
-
22
- # Date will be Time.now if it can't be determined from GIT repository
23
- def date
24
- get_scm_data if @_date.nil?
25
- @_date
26
- end
27
-
28
- def previous
29
- self.class.new(@config.dup.update(:ref => get_previous_tag_name))
30
- end
31
-
32
- protected
33
-
34
- def get_previous_tag_name
35
- # Get list of SHA1 that have a ref
36
- begin
37
- sha1s = `git --git-dir=#{safe_git_dir} log --pretty='%H' --simplify-by-decoration`.split("\n")
38
- tags = []
39
- while tags.size < 2 && sha1s.any?
40
- sha1 = sha1s.shift
41
- tag = `git --git-dir=#{safe_git_dir} describe --tags --exact-match #{sha1} 2>/dev/null`.strip
42
- tags << tag if !tag.empty?
1
+ require "pathname"
2
+
3
+ module Roger
4
+ class Release
5
+ module Scm
6
+ # The GIT SCM implementation for Roger release
7
+ class Git < Base
8
+ # @option config [String] :ref Ref to use for current tag
9
+ # @option config [String, Pathname] :path Path to working dir
10
+ def initialize(config = {})
11
+ super(config)
12
+ @config[:ref] ||= "HEAD"
43
13
  end
44
- tags.last
45
- rescue
46
- raise "Could not get previous tag"
47
- end
48
- end
49
-
50
- def git_dir
51
- @git_dir ||= find_git_dir(@config[:path])
52
- end
53
14
 
54
- # Safely escaped git dir
55
- def safe_git_dir
56
- Shellwords.escape(self.git_dir.to_s)
57
- end
15
+ # Version is either:
16
+ # - the tagged version number (first "v" will be stripped) or
17
+ # - the return value of "git describe --tags HEAD"
18
+ # - the short SHA1 if there hasn't been a previous tag
19
+ def version
20
+ get_scm_data if @_version.nil?
21
+ @_version
22
+ end
23
+
24
+ # Date will be Time.now if it can't be determined from GIT repository
25
+ def date
26
+ get_scm_data if @_date.nil?
27
+ @_date
28
+ end
29
+
30
+ def previous
31
+ self.class.new(@config.dup.update(ref: get_previous_tag_name))
32
+ end
33
+
34
+ protected
35
+
36
+ def get_previous_tag_name
37
+ # Get list of SHA1 that have a ref
38
+ sha1s = `git --git-dir=#{safe_git_dir} log --pretty='%H' --simplify-by-decoration`
39
+ sha1s = sha1s.split("\n")
40
+ tags = []
41
+ while tags.size < 2 && sha1s.any?
42
+ sha1 = sha1s.shift
43
+ tag = `git --git-dir=#{safe_git_dir} describe --tags --exact-match #{sha1} 2>/dev/null`
44
+ tag = tag.strip
45
+ tags << tag unless tag.empty?
46
+ end
47
+ tags.last
48
+ rescue
49
+ raise "Could not get previous tag"
50
+ end
51
+
52
+ def git_dir
53
+ @git_dir ||= find_git_dir(@config[:path])
54
+ end
55
+
56
+ # Safely escaped git dir
57
+ def safe_git_dir
58
+ Shellwords.escape(git_dir.to_s)
59
+ end
60
+
61
+ # Preload version control data.
62
+ def get_scm_data(ref = @config[:ref])
63
+ @_version = scm_version(ref) || ""
64
+ @_date = scm_date(ref) || Time.now
65
+ end
66
+
67
+ # Some hackery to determine if ref is on a tagged version or not
68
+ # @return [String, nil] Will return version number if available, nil otherwise
69
+ def scm_version(ref)
70
+ return nil unless File.exist?(git_dir)
71
+
72
+ version = `git --git-dir=#{safe_git_dir} describe --tags #{ref} 2>&1`
58
73
 
59
- # Some hackery to determine if we're on a tagged version or not
60
- def get_scm_data(ref = @config[:ref])
61
- @_version = ""
62
- @_date = Time.now
63
- begin
64
- if File.exist?(git_dir)
65
- @_version = `git --git-dir=#{safe_git_dir} describe --tags #{ref} 2>&1`
66
-
67
- if $?.to_i > 0
68
- # HEAD is not a tagged verison, get the short SHA1 instead
69
- @_version = `git --git-dir=#{safe_git_dir} show #{ref} --format=format:"%h" -s 2>&1`
74
+ if $CHILD_STATUS.to_i > 0
75
+ # HEAD is not a tagged version, get the short SHA1 instead
76
+ version = `git --git-dir=#{safe_git_dir} show #{ref} --format=format:"%h" -s 2>&1`
70
77
  else
71
78
  # HEAD is a tagged version, if version is prefixed with "v" it will be stripped off
72
- @_version.gsub!(/^v/,"")
79
+ version.gsub!(/^v/, "")
73
80
  end
74
- @_version.strip!
75
-
81
+
82
+ version.strip!
83
+ rescue RuntimeError
84
+ nil
85
+ end
86
+
87
+ # Get date of ref from git
88
+ # @return [Time, nil] Returns time if available and parseable, nil otherwise
89
+ def scm_date(ref)
90
+ return nil unless File.exist?(git_dir)
91
+
76
92
  # Get the date in epoch time
77
93
  date = `git --git-dir=#{safe_git_dir} show #{ref} --format=format:"%ct" -s 2>&1`
78
- if date =~ /\d+/
79
- @_date = Time.at(date.to_i)
80
- else
81
- @_date = Time.now
82
- end
83
-
94
+ Time.at(date.to_i) if date =~ /\d+/
95
+ rescue RuntimeError
96
+ nil
84
97
  end
85
- rescue RuntimeError => e
86
- end
87
98
 
88
- end
89
-
90
- # Find the git dir
91
- def find_git_dir(path)
92
- path = Pathname.new(path).realpath
93
- while path.parent != path && !(path + ".git").directory?
94
- path = path.parent
95
- end
96
-
97
- path = path + ".git"
98
-
99
- raise "Could not find suitable .git dir in #{path}" if !path.directory?
99
+ # Find the git dir
100
+ def find_git_dir(path)
101
+ path = Pathname.new(path).realpath
102
+ while path.parent != path && !(path + ".git").directory?
103
+ path = path.parent
104
+ end
100
105
 
101
- path
106
+ path += ".git"
107
+
108
+ fail "Could not find suitable .git dir in #{path}" unless path.directory?
109
+
110
+ path
111
+ end
112
+ end
102
113
  end
103
-
104
114
  end
105
115
  end
106
-
@@ -1,92 +1,72 @@
1
1
  module Roger
2
+ # The resolver is here to resolve urls to paths and sometimes vice-versa
2
3
  class Resolver
3
-
4
4
  attr_reader :load_paths
5
-
5
+
6
6
  def initialize(paths)
7
- raise ArgumentError, "Resolver base path can't be nil" if paths.nil?
7
+ fail ArgumentError, "Resolver base path can't be nil" if paths.nil?
8
8
 
9
9
  # Convert to paths
10
- @load_paths = [paths].flatten.map{|p| Pathname.new(p) }
10
+ @load_paths = [paths].flatten.map { |p| Pathname.new(p) }
11
11
  end
12
-
12
+
13
13
  # @param [String] url The url to resolve to a path
14
14
  # @param [Hash] options Options
15
15
  #
16
- # @option options [true,false] :exact_match Wether or not to match exact paths, this is mainly used in the path_to_url method to match .js, .css, etc files.
17
- # @option options [String] :preferred_extension The part to chop off and re-add to search for more complex double-extensions. (Makes it possible to have context aware partials)
16
+ # @option options [true,false] :exact_match Wether or not to match exact paths,
17
+ # this is mainly used in the path_to_url method to match .js, .css, etc files.
18
+ # @option options [String] :preferred_extension The part to chop off
19
+ # and re-add to search for more complex double-extensions. (Makes it possible to have context
20
+ # aware partials)
18
21
  def find_template(url, options = {})
19
- orig_path, qs, anch = strip_query_string_and_anchor(url.to_s)
20
-
21
22
  options = {
22
- :exact_match => false,
23
- :preferred_extension => "html"
23
+ exact_match: false,
24
+ preferred_extension: "html"
24
25
  }.update(options)
25
26
 
26
- paths = self.load_paths.map{|base| File.join(base, orig_path) }
27
-
28
- paths.find do |path|
29
- if options[:exact_match] && File.exist?(path)
30
- return Pathname.new(path)
31
- end
32
-
33
- # It's a directory, add "/index"
34
- if File.directory?(path)
35
- path = File.join(path, "index")
36
- end
37
-
38
- # 2. If it's preferred_extension, we strip of the extension
39
- if path =~ /\.#{options[:preferred_extension]}\Z/
40
- path.sub!(/\.#{options[:preferred_extension]}\Z/, "")
41
- end
42
-
43
- extensions = Tilt.default_mapping.template_map.keys + Tilt.default_mapping.lazy_map.keys
27
+ orig_path, _qs, _anch = strip_query_string_and_anchor(url.to_s)
44
28
 
45
- # We have to re-add preferred_extension again as we have stripped it in in step 2!
46
- extensions += extensions.map{|ext| "#{options[:preferred_extension]}.#{ext}"}
29
+ output = nil
47
30
 
48
- if found_extension = extensions.find { |ext| File.exist?(path + "." + ext) }
49
- return Pathname.new(path + "." + found_extension)
50
- end
31
+ load_paths.find do |load_path|
32
+ path = File.join(load_path, orig_path)
51
33
 
52
- false #Next iteration
34
+ # If it's an exact match we're done
35
+ if options[:exact_match] && File.exist?(path)
36
+ output = Pathname.new(path)
37
+ else
38
+ output = find_file_with_extension(path, options[:preferred_extension])
39
+ end
53
40
  end
41
+
42
+ output
54
43
  end
55
- alias :url_to_path :find_template
56
-
57
-
44
+ alias_method :url_to_path, :find_template
45
+
58
46
  # Convert a disk path on file to an url
59
47
  def path_to_url(path, relative_to = nil)
60
-
61
48
  # Find the parent path we're in
62
49
  path = Pathname.new(path).realpath
63
- base = self.load_paths.find{|lp| path.to_s =~ /\A#{Regexp.escape(lp.realpath.to_s)}/ }
50
+ base = load_paths.find { |lp| path.to_s =~ /\A#{Regexp.escape(lp.realpath.to_s)}/ }
64
51
 
65
52
  path = path.relative_path_from(base).cleanpath
66
-
53
+
67
54
  if relative_to
68
- if relative_to.to_s =~ /\A\//
69
- relative_to = Pathname.new(File.dirname(relative_to.to_s)).relative_path_from(base).cleanpath
70
- else
71
- relative_to = Pathname.new(File.dirname(relative_to.to_s))
72
- end
73
- path = Pathname.new("/" + path.to_s).relative_path_from(Pathname.new("/" + relative_to.to_s))
74
- path.to_s
55
+ relative_path_to_url(path, relative_to, base).to_s
75
56
  else
76
- "/" + path.to_s
57
+ "/#{path}"
77
58
  end
78
-
79
59
  end
80
-
60
+
81
61
  def url_to_relative_url(url, relative_to_path)
82
62
  # Skip if the url doesn't start with a / (but not with //)
83
- return false unless url =~ /\A\/[^\/]/
84
-
63
+ return false unless url =~ %r{\A/[^/]}
64
+
85
65
  path, qs, anch = strip_query_string_and_anchor(url)
86
66
 
87
67
  # Get disk path
88
- if true_path = self.url_to_path(path, :exact_match => true)
89
- path = self.path_to_url(true_path, relative_to_path)
68
+ if true_path = url_to_path(path, exact_match: true)
69
+ path = path_to_url(true_path, relative_to_path)
90
70
  path += qs if qs
91
71
  path += anch if anch
92
72
  path
@@ -94,26 +74,71 @@ module Roger
94
74
  false
95
75
  end
96
76
  end
97
-
77
+
98
78
  def strip_query_string_and_anchor(url)
99
79
  url = url.dup
100
-
80
+
101
81
  # Strip off anchors
102
82
  anchor = nil
103
83
  url.gsub!(/(#.+)\Z/) do |r|
104
84
  anchor = r
105
85
  ""
106
86
  end
107
-
87
+
108
88
  # Strip off query strings
109
89
  query = nil
110
90
  url.gsub!(/(\?.+)\Z/) do |r|
111
91
  query = r
112
92
  ""
113
93
  end
114
-
94
+
115
95
  [url, query, anchor]
116
- end
117
-
96
+ end
97
+
98
+ protected
99
+
100
+ # Tries all extensions on path to see what file exists
101
+ # @return [Pathname,nil] returns a pathname of the full file path if found. nil otherwise
102
+ def find_file_with_extension(path, preferred_extension)
103
+ output = nil
104
+
105
+ file_path = path
106
+
107
+ # If it's a directory, add "/index"
108
+ file_path = File.join(file_path, "index") if File.directory?(file_path)
109
+
110
+ # Strip of extension
111
+ if path =~ /\.#{preferred_extension}\Z/
112
+ file_path.sub!(/\.#{preferred_extension}\Z/, "")
113
+ end
114
+
115
+ possible_extensions(preferred_extension).find do |ext|
116
+ path_with_extension = file_path + "." + ext
117
+ if File.exist?(path_with_extension)
118
+ output = Pathname.new(path_with_extension)
119
+ end
120
+ end
121
+ output
122
+ end
123
+
124
+ # Makes a list of all Tilt extensions
125
+ # Also adds a list of double extensions. Example:
126
+ # tilt_extensions = %w(erb md); second_extension = "html"
127
+ # return %w(erb md html.erb html.md)
128
+ def possible_extensions(second_extension)
129
+ extensions = Tilt.default_mapping.template_map.keys + Tilt.default_mapping.lazy_map.keys
130
+ extensions + extensions.map { |ext| "#{second_extension}.#{ext}" }
131
+ end
132
+
133
+ def relative_path_to_url(path, relative_to, base)
134
+ relative_to = Pathname.new(File.dirname(relative_to.to_s))
135
+
136
+ # If relative_to is an absolute path
137
+ if relative_to.to_s =~ %r{\A/}
138
+ relative_to = relative_to.relative_path_from(base).cleanpath
139
+ end
140
+
141
+ Pathname.new("/" + path.to_s).relative_path_from(Pathname.new("/" + relative_to.to_s))
142
+ end
118
143
  end
119
144
  end