1require 'rake/ext/core' 2 3###################################################################### 4# Rake extension methods for String. 5# 6class String 7 8 rake_extension("ext") do 9 # Replace the file extension with +newext+. If there is no extension on 10 # the string, append the new extension to the end. If the new extension 11 # is not given, or is the empty string, remove any existing extension. 12 # 13 # +ext+ is a user added method for the String class. 14 def ext(newext='') 15 return self.dup if ['.', '..'].include? self 16 if newext != '' 17 newext = (newext =~ /^\./) ? newext : ("." + newext) 18 end 19 self.chomp(File.extname(self)) << newext 20 end 21 end 22 23 rake_extension("pathmap") do 24 # Explode a path into individual components. Used by +pathmap+. 25 def pathmap_explode 26 head, tail = File.split(self) 27 return [self] if head == self 28 return [tail] if head == '.' || tail == '/' 29 return [head, tail] if head == '/' 30 return head.pathmap_explode + [tail] 31 end 32 protected :pathmap_explode 33 34 # Extract a partial path from the path. Include +n+ directories from the 35 # front end (left hand side) if +n+ is positive. Include |+n+| 36 # directories from the back end (right hand side) if +n+ is negative. 37 def pathmap_partial(n) 38 dirs = File.dirname(self).pathmap_explode 39 partial_dirs = 40 if n > 0 41 dirs[0...n] 42 elsif n < 0 43 dirs.reverse[0...-n].reverse 44 else 45 "." 46 end 47 File.join(partial_dirs) 48 end 49 protected :pathmap_partial 50 51 # Preform the pathmap replacement operations on the given path. The 52 # patterns take the form 'pat1,rep1;pat2,rep2...'. 53 def pathmap_replace(patterns, &block) 54 result = self 55 patterns.split(';').each do |pair| 56 pattern, replacement = pair.split(',') 57 pattern = Regexp.new(pattern) 58 if replacement == '*' && block_given? 59 result = result.sub(pattern, &block) 60 elsif replacement 61 result = result.sub(pattern, replacement) 62 else 63 result = result.sub(pattern, '') 64 end 65 end 66 result 67 end 68 protected :pathmap_replace 69 70 # Map the path according to the given specification. The specification 71 # controls the details of the mapping. The following special patterns are 72 # recognized: 73 # 74 # * <b>%p</b> -- The complete path. 75 # * <b>%f</b> -- The base file name of the path, with its file extension, 76 # but without any directories. 77 # * <b>%n</b> -- The file name of the path without its file extension. 78 # * <b>%d</b> -- The directory list of the path. 79 # * <b>%x</b> -- The file extension of the path. An empty string if there 80 # is no extension. 81 # * <b>%X</b> -- Everything *but* the file extension. 82 # * <b>%s</b> -- The alternate file separator if defined, otherwise use 83 # the standard file separator. 84 # * <b>%%</b> -- A percent sign. 85 # 86 # The %d specifier can also have a numeric prefix (e.g. '%2d'). If the 87 # number is positive, only return (up to) +n+ directories in the path, 88 # starting from the left hand side. If +n+ is negative, return (up to) 89 # |+n+| directories from the right hand side of the path. 90 # 91 # Examples: 92 # 93 # 'a/b/c/d/file.txt'.pathmap("%2d") => 'a/b' 94 # 'a/b/c/d/file.txt'.pathmap("%-2d") => 'c/d' 95 # 96 # Also the %d, %p, %f, %n, %x, and %X operators can take a 97 # pattern/replacement argument to perform simple string substitutions on a 98 # particular part of the path. The pattern and replacement are separated 99 # by a comma and are enclosed by curly braces. The replacement spec comes 100 # after the % character but before the operator letter. (e.g. 101 # "%{old,new}d"). Multiple replacement specs should be separated by 102 # semi-colons (e.g. "%{old,new;src,bin}d"). 103 # 104 # Regular expressions may be used for the pattern, and back refs may be 105 # used in the replacement text. Curly braces, commas and semi-colons are 106 # excluded from both the pattern and replacement text (let's keep parsing 107 # reasonable). 108 # 109 # For example: 110 # 111 # "src/org/onestepback/proj/A.java".pathmap("%{^src,bin}X.class") 112 # 113 # returns: 114 # 115 # "bin/org/onestepback/proj/A.class" 116 # 117 # If the replacement text is '*', then a block may be provided to perform 118 # some arbitrary calculation for the replacement. 119 # 120 # For example: 121 # 122 # "/path/to/file.TXT".pathmap("%X%{.*,*}x") { |ext| 123 # ext.downcase 124 # } 125 # 126 # Returns: 127 # 128 # "/path/to/file.txt" 129 # 130 def pathmap(spec=nil, &block) 131 return self if spec.nil? 132 result = '' 133 spec.scan(/%\{[^}]*\}-?\d*[sdpfnxX%]|%-?\d+d|%.|[^%]+/) do |frag| 134 case frag 135 when '%f' 136 result << File.basename(self) 137 when '%n' 138 result << File.basename(self).ext 139 when '%d' 140 result << File.dirname(self) 141 when '%x' 142 result << File.extname(self) 143 when '%X' 144 result << self.ext 145 when '%p' 146 result << self 147 when '%s' 148 result << (File::ALT_SEPARATOR || File::SEPARATOR) 149 when '%-' 150 # do nothing 151 when '%%' 152 result << "%" 153 when /%(-?\d+)d/ 154 result << pathmap_partial($1.to_i) 155 when /^%\{([^}]*)\}(\d*[dpfnxX])/ 156 patterns, operator = $1, $2 157 result << pathmap('%' + operator).pathmap_replace(patterns, &block) 158 when /^%/ 159 fail ArgumentError, "Unknown pathmap specifier #{frag} in '#{spec}'" 160 else 161 result << frag 162 end 163 end 164 result 165 end 166 end 167 168end 169