chirp 0.2.0 → 0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/History.txt +0 -0
- data/Manifest.txt +44 -2
- data/README.txt +10 -2
- data/Rakefile +1 -1
- data/bin/chirp +6 -4
- data/lib/chirp.rb +7 -3
- data/lib/chirp/application.rb +248 -159
- data/lib/chirp/context.rb +101 -0
- data/lib/chirp/fs_expression.rb +180 -0
- data/lib/chirp/path_filter.rb +58 -0
- data/lib/chirp/pathmap.rb +200 -0
- data/lib/chirp/span.rb +38 -0
- data/test/account.dir/input +0 -0
- data/test/account.dir/output +0 -0
- data/test/account.dir/program.chirp +7 -4
- data/test/action.dir/input +0 -0
- data/test/action.dir/output +0 -0
- data/test/action.dir/program.chirp +2 -2
- data/test/beforeafter.dir/input +0 -0
- data/test/beforeafter.dir/output +0 -0
- data/test/beforeafter.dir/program.chirp +3 -3
- data/test/copy1.dir/input +0 -0
- data/test/copy1.dir/output +5 -0
- data/test/copy1.dir/program.chirp +9 -0
- data/test/copy1.dir/source +2 -0
- data/test/copy2.dir/input +0 -0
- data/test/copy2.dir/output +5 -0
- data/test/copy2.dir/program.chirp +9 -0
- data/test/copy2.dir/source +2 -0
- data/test/copy3.dir/input +0 -0
- data/test/copy3.dir/output +5 -0
- data/test/copy3.dir/program.chirp +7 -0
- data/test/copy3.dir/source +2 -0
- data/test/copy_file.dir/a.txt +1 -0
- data/test/copy_file.dir/b.txt +1 -0
- data/test/copy_file.dir/c.txt +1 -0
- data/test/copy_file.dir/input +0 -0
- data/test/copy_file.dir/output +2 -0
- data/test/copy_file.dir/program.chirp +5 -0
- data/test/fields.dir/input +0 -0
- data/test/fields.dir/output +0 -0
- data/test/fields.dir/program.chirp +2 -2
- data/test/fields_sep.dir/input +0 -0
- data/test/fields_sep.dir/output +0 -0
- data/test/fields_sep.dir/program.chirp +1 -2
- data/test/files.dir/a.ignore +0 -0
- data/test/files.dir/a.txt +0 -0
- data/test/files.dir/b.txt +0 -0
- data/test/files.dir/c.stuff +0 -0
- data/test/files.dir/input +0 -0
- data/test/files.dir/output +3 -0
- data/test/files.dir/program.chirp +7 -3
- data/test/fs_expr.dir/aa.txt +1 -0
- data/test/fs_expr.dir/bb.txt +9 -0
- data/test/fs_expr.dir/cc.txt +0 -0
- data/test/fs_expr.dir/dir1/dir2/dir3/a.txt +1 -0
- data/test/fs_expr.dir/input +0 -0
- data/test/fs_expr.dir/output +7 -0
- data/test/fs_expr.dir/program.chirp +17 -0
- data/test/inplace.dir/a.txt +0 -0
- data/test/inplace.dir/b.txt +0 -0
- data/test/inplace.dir/input +0 -0
- data/test/inplace.dir/output +0 -0
- data/test/inplace.dir/program.chirp +4 -6
- data/test/line_no.dir/input +0 -0
- data/test/line_no.dir/output +1 -0
- data/test/line_no.dir/program.chirp +5 -1
- data/test/match.dir/input +0 -0
- data/test/match.dir/output +0 -0
- data/test/match.dir/program.chirp +1 -1
- data/test/path.dir/dir1/dir2/dir3/a.txt +1 -0
- data/test/path.dir/input +0 -0
- data/test/path.dir/new +5 -0
- data/test/path.dir/output +20 -0
- data/test/path.dir/program.chirp +44 -0
- data/test/proc.dir/input +0 -0
- data/test/proc.dir/output +0 -0
- data/test/proc.dir/program.chirp +2 -2
- data/test/rename_file.dir/a.txt +4 -0
- data/test/rename_file.dir/b.txt +4 -0
- data/test/rename_file.dir/c.txt +4 -0
- data/test/rename_file.dir/input +0 -0
- data/test/rename_file.dir/output +2 -0
- data/test/rename_file.dir/program.chirp +7 -0
- data/test/requires.rb +6 -0
- data/test/span.dir/input +0 -0
- data/test/span.dir/output +0 -0
- data/test/span.dir/program.chirp +1 -1
- data/test/span2.dir/input +0 -0
- data/test/span2.dir/output +0 -0
- data/test/span2.dir/program.chirp +3 -1
- data/test/string.dir/input +0 -0
- data/test/string.dir/output +0 -0
- data/test/string.dir/program.chirp +1 -1
- data/test/test_application.rb +14 -9
- data/test/test_fs_expression.rb +99 -0
- data/test/test_span.rb +36 -0
- metadata +64 -7
- data/lib/chirp/statement.rb +0 -91
- data/test/test_statement.rb +0 -118
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright 2006-2007 Russell Olsen
|
|
3
|
+
# Chirp is distributed under the same license as Ruby itself.
|
|
4
|
+
#
|
|
5
|
+
|
|
6
|
+
require 'pathname'
|
|
7
|
+
require 'ftools'
|
|
8
|
+
require 'fileutils'
|
|
9
|
+
#require 'erb'
|
|
10
|
+
|
|
11
|
+
module Chirp
|
|
12
|
+
class Context
|
|
13
|
+
include FileUtils
|
|
14
|
+
|
|
15
|
+
attr_accessor :field_separator, :line_no, :output_path, :paths, :dir
|
|
16
|
+
attr_reader :path, :line
|
|
17
|
+
attr_writer :fields, :contents
|
|
18
|
+
attr_accessor :verbose
|
|
19
|
+
|
|
20
|
+
def initialize
|
|
21
|
+
@field_separator = /[ \t]/
|
|
22
|
+
@verbose = false
|
|
23
|
+
@dir = '.'
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def execute(action)
|
|
27
|
+
instance_eval(&action)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def path=(new_path)
|
|
33
|
+
@path = new_path
|
|
34
|
+
@full_path = nil
|
|
35
|
+
@line = nil
|
|
36
|
+
@fields = nil
|
|
37
|
+
@path_array = nil
|
|
38
|
+
@path_name = nil
|
|
39
|
+
@full_path_name = nil
|
|
40
|
+
@contents = nil
|
|
41
|
+
@dirname = nil
|
|
42
|
+
@extname = nil
|
|
43
|
+
@name = nil
|
|
44
|
+
@basename = nil
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def line=(new_line)
|
|
48
|
+
@line = new_line
|
|
49
|
+
@fields = nil
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def full_path
|
|
53
|
+
@full_path || @full_path = File.join(dir, path)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def fields
|
|
57
|
+
@fields || @fields = line.split(@field_separator)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def path_name
|
|
61
|
+
@path_name || @path_name = Pathname.new(path)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def full_path_name
|
|
65
|
+
@full_path_name || @full_path_name = Pathname.new(File.join(dir,path))
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def dirname
|
|
69
|
+
@dirname || @dirname = path_name.dirname.to_s
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def basename
|
|
73
|
+
@basename || @basename = path_name.basename.to_s
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def extname
|
|
77
|
+
@extname || @extname = path_name.extname.to_s
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def name
|
|
81
|
+
return @name if @name
|
|
82
|
+
@name = basename[0..-extname.size-1]
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def contents
|
|
86
|
+
@contents || @contents = File.read(full_path)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def comment(*msg)
|
|
90
|
+
STDERR.puts(msg.join(' '))
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def debug(*msg)
|
|
94
|
+
STDERR.puts(msg.join(' ')) if @verbose
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def to_s
|
|
98
|
+
return "Context: #{@path}"
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
|
|
2
|
+
module Chirp
|
|
3
|
+
|
|
4
|
+
class FSExpression
|
|
5
|
+
def &(other)
|
|
6
|
+
And.new(self, other)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def |(other)
|
|
10
|
+
Or.new(self, other)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def ~
|
|
14
|
+
Not.new(self)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
class Named < FSExpression
|
|
19
|
+
def initialize(name)
|
|
20
|
+
@name = name
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def evaluate(path)
|
|
24
|
+
return true if @name.kind_of?(Regexp) and (@name =~ path)
|
|
25
|
+
return true if @name.kind_of?(String) and (File.fnmatch(@name, path))
|
|
26
|
+
false
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
class All < FSExpression
|
|
31
|
+
def evaluate(path)
|
|
32
|
+
true
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
class None < FSExpression
|
|
37
|
+
def evaluate(path)
|
|
38
|
+
false
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
class Contains < FSExpression
|
|
43
|
+
def initialize(re)
|
|
44
|
+
@re = re
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def evaluate(path)
|
|
48
|
+
return false unless File.file?(path)
|
|
49
|
+
File.open(path) do |f|
|
|
50
|
+
until f.eof?
|
|
51
|
+
return true if @re =~ f.readline
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
false
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
class IsDir < FSExpression
|
|
59
|
+
def evaluate(path)
|
|
60
|
+
File.directory?(path)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
class IsFile < FSExpression
|
|
65
|
+
def evaluate(path)
|
|
66
|
+
File.file?(path)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
class Bigger < FSExpression
|
|
71
|
+
def initialize(size)
|
|
72
|
+
@size = size
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def evaluate(path)
|
|
76
|
+
File.size(path) > @size
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
class Smaller < FSExpression
|
|
81
|
+
def initialize(size)
|
|
82
|
+
@size = size
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def evaluate(path)
|
|
86
|
+
File.size(path) < @size
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
class SameSize < FSExpression
|
|
91
|
+
def initialize(size)
|
|
92
|
+
@size = size
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def evaluate(path)
|
|
96
|
+
File.size(path) == @size
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class And < FSExpression
|
|
102
|
+
def initialize(exp1, exp2)
|
|
103
|
+
@exp1 = exp1
|
|
104
|
+
@exp2 = exp2
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def evaluate(path)
|
|
108
|
+
@exp1.evaluate(path) && @exp2.evaluate(path)
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
class Or < FSExpression
|
|
113
|
+
def initialize(exp1, exp2)
|
|
114
|
+
@exp1 = exp1
|
|
115
|
+
@exp2 = exp2
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def evaluate(path)
|
|
119
|
+
@exp1.evaluate(path) || @exp2.evaluate(path)
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
class Not < FSExpression
|
|
124
|
+
def initialize(exp)
|
|
125
|
+
@exp1 = exp
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def evaluate(path)
|
|
129
|
+
not @exp1.evaluate(path)
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
module FSSugar
|
|
134
|
+
|
|
135
|
+
def all
|
|
136
|
+
All.new
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def none
|
|
140
|
+
None.new
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def bigger(size)
|
|
144
|
+
Bigger.new(size)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def smaller(size)
|
|
148
|
+
Smaller.new(size)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def named(name)
|
|
152
|
+
Named.new(name)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def contains(pattern)
|
|
156
|
+
Contains.new(pattern)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def length(n)
|
|
160
|
+
SameSize.new(n)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def dir?
|
|
164
|
+
IsDir.new
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def file?
|
|
168
|
+
IsFile.new
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def except(expr)
|
|
172
|
+
Not.new(expr)
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
#require 'pp'
|
|
179
|
+
#include Chirp::FSSugar
|
|
180
|
+
#pp (bigger(100) & contains('foobar')) | (dir? | named('xxx.txt'))
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
module Chirp
|
|
2
|
+
class PathFilter
|
|
3
|
+
def initialize(options={})
|
|
4
|
+
@regexp = @glob = nil
|
|
5
|
+
@regexp = options[:path] if options[:path].kind_of?(Regexp)
|
|
6
|
+
@glob = options[:path] if options[:path].kind_of?(String)
|
|
7
|
+
@fsexp = options[:fsexp]
|
|
8
|
+
@contains = options[:contains]
|
|
9
|
+
@name = options[:name]
|
|
10
|
+
@dir=options[:dir]
|
|
11
|
+
@type = options[:type]
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def passes?(path)
|
|
15
|
+
#puts "trying filter #{self} on #{path}"
|
|
16
|
+
if @type == :file
|
|
17
|
+
return false unless File.file?(path)
|
|
18
|
+
end
|
|
19
|
+
if @type == :dir
|
|
20
|
+
return false unless File.directory?(path)
|
|
21
|
+
end
|
|
22
|
+
if @name
|
|
23
|
+
return false unless File.fnmatch(@name, File.basename(path))
|
|
24
|
+
end
|
|
25
|
+
if @fsexp
|
|
26
|
+
return false unless @fsexp.evaluate(path)
|
|
27
|
+
end
|
|
28
|
+
if @glob
|
|
29
|
+
return false unless File.fnmatch(@glob, path, File::FNM_PATHNAME | File::FNM_DOTMATCH )
|
|
30
|
+
end
|
|
31
|
+
if @regexp
|
|
32
|
+
return false unless @regexp =~ path
|
|
33
|
+
end
|
|
34
|
+
if @dir
|
|
35
|
+
return false unless File.dirname(path) == @dir
|
|
36
|
+
end
|
|
37
|
+
if @contains
|
|
38
|
+
return false unless file_contains(path, @contains)
|
|
39
|
+
end
|
|
40
|
+
#puts "passed filter: #{path}"
|
|
41
|
+
true
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def file_contains(path, re)
|
|
45
|
+
return false unless File.file?(path)
|
|
46
|
+
File.open(path) do |f|
|
|
47
|
+
until f.eof?
|
|
48
|
+
return true if re =~ f.readline
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
false
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def to_s
|
|
55
|
+
"Filter: #{@glob} #{@dir} #{@type}"
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
#--
|
|
4
|
+
|
|
5
|
+
# Copyright (c) 2003, 2004, 2005, 2006, 2007 Jim Weirich
|
|
6
|
+
#
|
|
7
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
+
# of this software and associated documentation files (the "Software"), to
|
|
9
|
+
# deal in the Software without restriction, including without limitation the
|
|
10
|
+
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
11
|
+
# sell copies of the Software, and to permit persons to whom the Software is
|
|
12
|
+
# furnished to do so, subject to the following conditions:
|
|
13
|
+
#
|
|
14
|
+
# The above copyright notice and this permission notice shall be included in
|
|
15
|
+
# all copies or substantial portions of the Software.
|
|
16
|
+
#
|
|
17
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
18
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
19
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
20
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
21
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
22
|
+
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
23
|
+
# IN THE SOFTWARE.
|
|
24
|
+
#++
|
|
25
|
+
#
|
|
26
|
+
# = Rake -- Ruby Make
|
|
27
|
+
#
|
|
28
|
+
# This is the main file for the Rake application. Normally it is referenced
|
|
29
|
+
# as a library via a require statement, but it can be distributed
|
|
30
|
+
# independently as an application.
|
|
31
|
+
|
|
32
|
+
module Pathmap
|
|
33
|
+
# Replace the file extension with +newext+. If there is no extenson on
|
|
34
|
+
# the string, append the new extension to the end. If the new extension
|
|
35
|
+
# is not given, or is the empty string, remove any existing extension.
|
|
36
|
+
#
|
|
37
|
+
# +ext+ is a user added method for the String class.
|
|
38
|
+
def ext(newext='')
|
|
39
|
+
return self.dup if ['.', '..'].include? self
|
|
40
|
+
if newext != ''
|
|
41
|
+
newext = (newext =~ /^\./) ? newext : ("." + newext)
|
|
42
|
+
end
|
|
43
|
+
dup.sub!(%r(([^/\\])\.[^./\\]*$)) { $1 + newext } || self + newext
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Explode a path into individual components. Used by +pathmap+.
|
|
47
|
+
def pathmap_explode
|
|
48
|
+
head, tail = File.split(self)
|
|
49
|
+
return [self] if head == self
|
|
50
|
+
return [tail] if head == '.' || tail == '/'
|
|
51
|
+
return [head, tail] if head == '/'
|
|
52
|
+
return head.pathmap_explode + [tail]
|
|
53
|
+
end
|
|
54
|
+
protected :pathmap_explode
|
|
55
|
+
|
|
56
|
+
# Extract a partial path from the path. Include +n+ directories from the
|
|
57
|
+
# front end (left hand side) if +n+ is positive. Include |+n+|
|
|
58
|
+
# directories from the back end (right hand side) if +n+ is negative.
|
|
59
|
+
def pathmap_partial(n)
|
|
60
|
+
target = File.dirname(self)
|
|
61
|
+
dirs = target.pathmap_explode
|
|
62
|
+
if n > 0
|
|
63
|
+
File.join(dirs[0...n])
|
|
64
|
+
elsif n < 0
|
|
65
|
+
partial = dirs[n..-1]
|
|
66
|
+
if partial.nil? || partial.empty?
|
|
67
|
+
target
|
|
68
|
+
else
|
|
69
|
+
File.join(partial)
|
|
70
|
+
end
|
|
71
|
+
else
|
|
72
|
+
"."
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
protected :pathmap_partial
|
|
76
|
+
|
|
77
|
+
# Preform the pathmap replacement operations on the given path. The
|
|
78
|
+
# patterns take the form 'pat1,rep1;pat2,rep2...'.
|
|
79
|
+
def pathmap_replace(patterns, &block)
|
|
80
|
+
result = self
|
|
81
|
+
patterns.split(';').each do |pair|
|
|
82
|
+
pattern, replacement = pair.split(',')
|
|
83
|
+
pattern = Regexp.new(pattern)
|
|
84
|
+
if replacement == '*' && block_given?
|
|
85
|
+
result = result.sub(pattern, &block)
|
|
86
|
+
elsif replacement
|
|
87
|
+
result = result.sub(pattern, replacement)
|
|
88
|
+
else
|
|
89
|
+
result = result.sub(pattern, '')
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
result
|
|
93
|
+
end
|
|
94
|
+
protected :pathmap_replace
|
|
95
|
+
|
|
96
|
+
# Map the path according to the given specification. The specification
|
|
97
|
+
# controls the details of the mapping. The following special patterns are
|
|
98
|
+
# recognized:
|
|
99
|
+
#
|
|
100
|
+
# * <b>%p</b> -- The complete path.
|
|
101
|
+
# * <b>%f</b> -- The base file name of the path, with its file extension,
|
|
102
|
+
# but without any directories.
|
|
103
|
+
# * <b>%n</b> -- The file name of the path without its file extension.
|
|
104
|
+
# * <b>%d</b> -- The directory list of the path.
|
|
105
|
+
# * <b>%x</b> -- The file extension of the path. An empty string if there
|
|
106
|
+
# is no extension.
|
|
107
|
+
# * <b>%X</b> -- Everything *but* the file extension.
|
|
108
|
+
# * <b>%s</b> -- The alternate file separater if defined, otherwise use
|
|
109
|
+
# the standard file separator.
|
|
110
|
+
# * <b>%%</b> -- A percent sign.
|
|
111
|
+
#
|
|
112
|
+
# The %d specifier can also have a numeric prefix (e.g. '%2d'). If the
|
|
113
|
+
# number is positive, only return (up to) +n+ directories in the path,
|
|
114
|
+
# starting from the left hand side. If +n+ is negative, return (up to)
|
|
115
|
+
# |+n+| directories from the right hand side of the path.
|
|
116
|
+
#
|
|
117
|
+
# Examples:
|
|
118
|
+
#
|
|
119
|
+
# 'a/b/c/d/file.txt'.pathmap("%2d") => 'a/b'
|
|
120
|
+
# 'a/b/c/d/file.txt'.pathmap("%-2d") => 'c/d'
|
|
121
|
+
#
|
|
122
|
+
# Also the %d, %p, $f, $n, %x, and %X operators can take a
|
|
123
|
+
# pattern/replacement argument to perform simple string substititions on a
|
|
124
|
+
# particular part of the path. The pattern and replacement are speparated
|
|
125
|
+
# by a comma and are enclosed by curly braces. The replacement spec comes
|
|
126
|
+
# after the % character but before the operator letter. (e.g.
|
|
127
|
+
# "%{old,new}d"). Muliple replacement specs should be separated by
|
|
128
|
+
# semi-colons (e.g. "%{old,new;src,bin}d").
|
|
129
|
+
#
|
|
130
|
+
# Regular expressions may be used for the pattern, and back refs may be
|
|
131
|
+
# used in the replacement text. Curly braces, commas and semi-colons are
|
|
132
|
+
# excluded from both the pattern and replacement text (let's keep parsing
|
|
133
|
+
# reasonable).
|
|
134
|
+
#
|
|
135
|
+
# For example:
|
|
136
|
+
#
|
|
137
|
+
# "src/org/onestepback/proj/A.java".pathmap("%{^src,bin}X.class")
|
|
138
|
+
#
|
|
139
|
+
# returns:
|
|
140
|
+
#
|
|
141
|
+
# "bin/org/onestepback/proj/A.class"
|
|
142
|
+
#
|
|
143
|
+
# If the replacement text is '*', then a block may be provided to perform
|
|
144
|
+
# some arbitrary calculation for the replacement.
|
|
145
|
+
#
|
|
146
|
+
# For example:
|
|
147
|
+
#
|
|
148
|
+
# "/path/to/file.TXT".pathmap("%X%{.*,*}x") { |ext|
|
|
149
|
+
# ext.downcase
|
|
150
|
+
# }
|
|
151
|
+
#
|
|
152
|
+
# Returns:
|
|
153
|
+
#
|
|
154
|
+
# "/path/to/file.txt"
|
|
155
|
+
#
|
|
156
|
+
def pathmap(spec=nil, &block)
|
|
157
|
+
return self if spec.nil?
|
|
158
|
+
result = ''
|
|
159
|
+
spec.scan(/%\{[^}]*\}-?\d*[sdpfnxX%]|%-?\d+d|%.|[^%]+/) do |frag|
|
|
160
|
+
case frag
|
|
161
|
+
when '%f'
|
|
162
|
+
result << File.basename(self)
|
|
163
|
+
when '%n'
|
|
164
|
+
result << File.basename(self).ext
|
|
165
|
+
when '%d'
|
|
166
|
+
result << File.dirname(self)
|
|
167
|
+
when '%x'
|
|
168
|
+
result << $1 if self =~ /[^\/](\.[^.]+)$/
|
|
169
|
+
when '%X'
|
|
170
|
+
if self =~ /^(.+[^\/])(\.[^.]+)$/
|
|
171
|
+
result << $1
|
|
172
|
+
else
|
|
173
|
+
result << self
|
|
174
|
+
end
|
|
175
|
+
when '%p'
|
|
176
|
+
result << self
|
|
177
|
+
when '%s'
|
|
178
|
+
result << (File::ALT_SEPARATOR || File::SEPARATOR)
|
|
179
|
+
when '%-'
|
|
180
|
+
# do nothing
|
|
181
|
+
when '%%'
|
|
182
|
+
result << "%"
|
|
183
|
+
when /%(-?\d+)d/
|
|
184
|
+
result << pathmap_partial($1.to_i)
|
|
185
|
+
when /^%\{([^}]*)\}(\d*[dpfnxX])/
|
|
186
|
+
patterns, operator = $1, $2
|
|
187
|
+
result << pathmap('%' + operator).pathmap_replace(patterns, &block)
|
|
188
|
+
when /^%/
|
|
189
|
+
fail ArgumentError, "Unknown pathmap specifier #{frag} in '#{spec}'"
|
|
190
|
+
else
|
|
191
|
+
result << frag
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
result
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
class String
|
|
199
|
+
include Pathmap
|
|
200
|
+
end
|