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.
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