chirp 0.2.0 → 0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (100) hide show
  1. data/History.txt +0 -0
  2. data/Manifest.txt +44 -2
  3. data/README.txt +10 -2
  4. data/Rakefile +1 -1
  5. data/bin/chirp +6 -4
  6. data/lib/chirp.rb +7 -3
  7. data/lib/chirp/application.rb +248 -159
  8. data/lib/chirp/context.rb +101 -0
  9. data/lib/chirp/fs_expression.rb +180 -0
  10. data/lib/chirp/path_filter.rb +58 -0
  11. data/lib/chirp/pathmap.rb +200 -0
  12. data/lib/chirp/span.rb +38 -0
  13. data/test/account.dir/input +0 -0
  14. data/test/account.dir/output +0 -0
  15. data/test/account.dir/program.chirp +7 -4
  16. data/test/action.dir/input +0 -0
  17. data/test/action.dir/output +0 -0
  18. data/test/action.dir/program.chirp +2 -2
  19. data/test/beforeafter.dir/input +0 -0
  20. data/test/beforeafter.dir/output +0 -0
  21. data/test/beforeafter.dir/program.chirp +3 -3
  22. data/test/copy1.dir/input +0 -0
  23. data/test/copy1.dir/output +5 -0
  24. data/test/copy1.dir/program.chirp +9 -0
  25. data/test/copy1.dir/source +2 -0
  26. data/test/copy2.dir/input +0 -0
  27. data/test/copy2.dir/output +5 -0
  28. data/test/copy2.dir/program.chirp +9 -0
  29. data/test/copy2.dir/source +2 -0
  30. data/test/copy3.dir/input +0 -0
  31. data/test/copy3.dir/output +5 -0
  32. data/test/copy3.dir/program.chirp +7 -0
  33. data/test/copy3.dir/source +2 -0
  34. data/test/copy_file.dir/a.txt +1 -0
  35. data/test/copy_file.dir/b.txt +1 -0
  36. data/test/copy_file.dir/c.txt +1 -0
  37. data/test/copy_file.dir/input +0 -0
  38. data/test/copy_file.dir/output +2 -0
  39. data/test/copy_file.dir/program.chirp +5 -0
  40. data/test/fields.dir/input +0 -0
  41. data/test/fields.dir/output +0 -0
  42. data/test/fields.dir/program.chirp +2 -2
  43. data/test/fields_sep.dir/input +0 -0
  44. data/test/fields_sep.dir/output +0 -0
  45. data/test/fields_sep.dir/program.chirp +1 -2
  46. data/test/files.dir/a.ignore +0 -0
  47. data/test/files.dir/a.txt +0 -0
  48. data/test/files.dir/b.txt +0 -0
  49. data/test/files.dir/c.stuff +0 -0
  50. data/test/files.dir/input +0 -0
  51. data/test/files.dir/output +3 -0
  52. data/test/files.dir/program.chirp +7 -3
  53. data/test/fs_expr.dir/aa.txt +1 -0
  54. data/test/fs_expr.dir/bb.txt +9 -0
  55. data/test/fs_expr.dir/cc.txt +0 -0
  56. data/test/fs_expr.dir/dir1/dir2/dir3/a.txt +1 -0
  57. data/test/fs_expr.dir/input +0 -0
  58. data/test/fs_expr.dir/output +7 -0
  59. data/test/fs_expr.dir/program.chirp +17 -0
  60. data/test/inplace.dir/a.txt +0 -0
  61. data/test/inplace.dir/b.txt +0 -0
  62. data/test/inplace.dir/input +0 -0
  63. data/test/inplace.dir/output +0 -0
  64. data/test/inplace.dir/program.chirp +4 -6
  65. data/test/line_no.dir/input +0 -0
  66. data/test/line_no.dir/output +1 -0
  67. data/test/line_no.dir/program.chirp +5 -1
  68. data/test/match.dir/input +0 -0
  69. data/test/match.dir/output +0 -0
  70. data/test/match.dir/program.chirp +1 -1
  71. data/test/path.dir/dir1/dir2/dir3/a.txt +1 -0
  72. data/test/path.dir/input +0 -0
  73. data/test/path.dir/new +5 -0
  74. data/test/path.dir/output +20 -0
  75. data/test/path.dir/program.chirp +44 -0
  76. data/test/proc.dir/input +0 -0
  77. data/test/proc.dir/output +0 -0
  78. data/test/proc.dir/program.chirp +2 -2
  79. data/test/rename_file.dir/a.txt +4 -0
  80. data/test/rename_file.dir/b.txt +4 -0
  81. data/test/rename_file.dir/c.txt +4 -0
  82. data/test/rename_file.dir/input +0 -0
  83. data/test/rename_file.dir/output +2 -0
  84. data/test/rename_file.dir/program.chirp +7 -0
  85. data/test/requires.rb +6 -0
  86. data/test/span.dir/input +0 -0
  87. data/test/span.dir/output +0 -0
  88. data/test/span.dir/program.chirp +1 -1
  89. data/test/span2.dir/input +0 -0
  90. data/test/span2.dir/output +0 -0
  91. data/test/span2.dir/program.chirp +3 -1
  92. data/test/string.dir/input +0 -0
  93. data/test/string.dir/output +0 -0
  94. data/test/string.dir/program.chirp +1 -1
  95. data/test/test_application.rb +14 -9
  96. data/test/test_fs_expression.rb +99 -0
  97. data/test/test_span.rb +36 -0
  98. metadata +64 -7
  99. data/lib/chirp/statement.rb +0 -91
  100. 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