chirp 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. data/History.txt +10 -3
  2. data/Manifest.txt +49 -1
  3. data/README.txt +4 -4
  4. data/Rakefile +3 -3
  5. data/bin/chirp +42 -0
  6. data/lib/chirp.rb +5 -223
  7. data/lib/chirp/application.rb +252 -0
  8. data/lib/chirp/statement.rb +91 -0
  9. data/test/account.dir/input +7 -0
  10. data/test/account.dir/output +1 -0
  11. data/test/account.dir/program.chirp +20 -0
  12. data/test/action.dir/input +7 -0
  13. data/test/action.dir/output +1 -0
  14. data/test/action.dir/program.chirp +3 -0
  15. data/test/beforeafter.dir/input +7 -0
  16. data/test/beforeafter.dir/output +11 -0
  17. data/test/beforeafter.dir/program.chirp +7 -0
  18. data/test/fields.dir/input +3 -0
  19. data/test/fields.dir/output +3 -0
  20. data/test/fields.dir/program.chirp +5 -0
  21. data/test/fields_sep.dir/input +7 -0
  22. data/test/fields_sep.dir/output +7 -0
  23. data/test/fields_sep.dir/program.chirp +6 -0
  24. data/test/files.dir/a.ignore +1 -0
  25. data/test/files.dir/a.txt +1 -0
  26. data/test/files.dir/b.txt +1 -0
  27. data/test/files.dir/c.stuff +1 -0
  28. data/test/files.dir/input +7 -0
  29. data/test/files.dir/output +2 -0
  30. data/test/files.dir/program.chirp +6 -0
  31. data/test/inplace.dir/a.txt +1 -0
  32. data/test/inplace.dir/b.txt +1 -0
  33. data/test/inplace.dir/input +7 -0
  34. data/test/inplace.dir/output +3 -0
  35. data/test/inplace.dir/program.chirp +9 -0
  36. data/test/line_no.dir/input +7 -0
  37. data/test/line_no.dir/output +1 -0
  38. data/test/line_no.dir/program.chirp +1 -0
  39. data/test/match.dir/input +14 -0
  40. data/test/match.dir/output +6 -0
  41. data/test/match.dir/program.chirp +1 -0
  42. data/test/proc.dir/input +8 -0
  43. data/test/proc.dir/output +3 -0
  44. data/test/proc.dir/program.chirp +3 -0
  45. data/test/span.dir/input +13 -0
  46. data/test/span.dir/output +7 -0
  47. data/test/span.dir/program.chirp +1 -0
  48. data/test/span2.dir/input +7 -0
  49. data/test/span2.dir/output +5 -0
  50. data/test/span2.dir/program.chirp +1 -0
  51. data/test/string.dir/input +14 -0
  52. data/test/string.dir/output +6 -0
  53. data/test/string.dir/program.chirp +1 -0
  54. data/test/test_application.rb +42 -0
  55. data/test/test_statement.rb +118 -0
  56. metadata +60 -7
  57. data/test/test_chirp.rb +0 -0
@@ -1,5 +1,12 @@
1
- == 1.0.0 / 2007-11-09
1
+ == 0.1.0 / 2007-11-09
2
2
 
3
- * 1 major enhancement
4
- * Birthday!
3
+ * Revive the project
4
+ * Initial update, very basic features
5
+
6
+ === 0.2.0 / 2007-11-21:
7
+
8
+ * Added in ability to process multiple files
9
+ * Redid the event model
10
+ * Added tests
11
+ * Added examples
5
12
 
@@ -4,4 +4,52 @@ README.txt
4
4
  Rakefile
5
5
  bin/chirp
6
6
  lib/chirp.rb
7
- test/test_chirp.rb
7
+ lib/chirp/application.rb
8
+ lib/chirp/statement.rb
9
+ test/test_statement.rb
10
+ test/test_application.rb
11
+ test/span.dir/output
12
+ test/span.dir/input
13
+ test/span.dir/program.chirp
14
+ test/match.dir/output
15
+ test/match.dir/program.chirp
16
+ test/match.dir/input
17
+ test/span2.dir/output
18
+ test/span2.dir/input
19
+ test/span2.dir/program.chirp
20
+ test/line_no.dir/output
21
+ test/line_no.dir/input
22
+ test/line_no.dir/program.chirp
23
+ test/inplace.dir/output
24
+ test/inplace.dir/input
25
+ test/inplace.dir/program.chirp
26
+ test/inplace.dir/a.txt
27
+ test/inplace.dir/b.txt
28
+ test/beforeafter.dir/output
29
+ test/beforeafter.dir/input
30
+ test/beforeafter.dir/program.chirp
31
+ test/action.dir/output
32
+ test/action.dir/input
33
+ test/action.dir/program.chirp
34
+ test/fields.dir/output
35
+ test/fields.dir/input
36
+ test/fields.dir/program.chirp
37
+ test/fields_sep.dir/output
38
+ test/fields_sep.dir/input
39
+ test/fields_sep.dir/program.chirp
40
+ test/string.dir/output
41
+ test/string.dir/input
42
+ test/string.dir/program.chirp
43
+ test/files.dir/output
44
+ test/files.dir/input
45
+ test/files.dir/program.chirp
46
+ test/files.dir/b.txt
47
+ test/files.dir/a.txt
48
+ test/files.dir/c.stuff
49
+ test/files.dir/a.ignore
50
+ test/proc.dir/output
51
+ test/proc.dir/input
52
+ test/proc.dir/program.chirp
53
+ test/account.dir/program.chirp
54
+ test/account.dir/output
55
+ test/account.dir/input
data/README.txt CHANGED
@@ -1,14 +1,14 @@
1
1
  chirp
2
- by FIX (your name)
3
- FIX (url)
2
+ by Russ Olsen
3
+ http://chirp.rubyforge.org
4
4
 
5
5
  == DESCRIPTION:
6
6
 
7
- FIX (describe your package)
7
+ Chirp is a Ruby based DSL inspiried by awk.
8
8
 
9
9
  == FEATURES/PROBLEMS:
10
10
 
11
- * FIX (list of features or problems)
11
+ * Need to add documentation
12
12
 
13
13
  == SYNOPSIS:
14
14
 
data/Rakefile CHANGED
@@ -6,9 +6,9 @@ require './lib/chirp.rb'
6
6
 
7
7
  Hoe.new('chirp', Chirp::VERSION) do |p|
8
8
  p.rubyforge_name = 'chirp'
9
- # p.author = 'FIX'
10
- # p.email = 'FIX'
11
- # p.summary = 'FIX'
9
+ p.author = 'Russ Olsen'
10
+ p.email = 'russ@russolsen.com'
11
+ p.summary = 'Chirp is a Ruby based text processing DSL similar to awk'
12
12
  # p.description = p.paragraphs_of('README.txt', 2..5).join("\n\n")
13
13
  # p.url = p.paragraphs_of('README.txt', 0).first.split(/\n/)[1..-1]
14
14
  p.changes = p.paragraphs_of('History.txt', 0..1).join("\n\n")
data/bin/chirp CHANGED
@@ -0,0 +1,42 @@
1
+ require 'optparse'
2
+ require 'pp'
3
+
4
+ begin
5
+ require 'chirp'
6
+ rescue LoadError
7
+ require 'rubygems'
8
+ require 'chirp'
9
+ end
10
+
11
+ program_text=nil
12
+ verbose=false
13
+
14
+ option_parser = OptionParser.new do |op|
15
+ op.banner = "Usage: $0 [-v] [-e chirp_program_text] [chirp_program_file] [input_file ...]"
16
+
17
+ op.on('-p', '--program [chirp-program-text]',
18
+ 'Execute this Chirp program') do |string|
19
+ program_text = string
20
+ end
21
+
22
+ op.on('-v', '--verbose',
23
+ 'Produce lots out debugging output') do
24
+ verbose=true
25
+ end
26
+ end
27
+
28
+ files=option_parser.parse(ARGV)
29
+
30
+ if files.empty? and program_text.nil?
31
+ STDERR.puts option_parser.banner
32
+ Kernel.exit(1)
33
+ end
34
+
35
+ program_text = File.read(files.shift) unless program_text
36
+
37
+ app = Chirp::Application.new(program_text, verbose)
38
+ app.files(files)
39
+ app.evaluate
40
+
41
+
42
+
@@ -4,229 +4,11 @@
4
4
  # Chirp is distributed under the same license as Ruby itself.
5
5
  #
6
6
 
7
- require 'pathname'
8
- require 'pp'
9
- require 'ftools'
10
- require 'tempfile'
11
- require 'forwardable'
7
+ ChirpLibDir=File.dirname(__FILE__)
12
8
 
13
- module Chirp
14
-
15
- VERSION = '0.1.0'
16
-
17
- def Chirp.application
18
- @application ||= Chirp::Application.new
19
- end
20
-
21
- class PatternAction
22
- attr_reader :block
23
-
24
- def initialize(re, block)
25
- @re = re
26
- @block = block
27
- end
28
-
29
- def matches(file, line, line_no)
30
- @re =~ line
31
- end
32
- end
33
-
34
- class LineNumberAction
35
- attr_reader :block
36
-
37
- def initialize(number, block)
38
- @number = number
39
- @block = block
40
- end
41
-
42
- def matches(file, line, line_no)
43
- line_no == @number
44
- end
45
- end
46
-
47
-
48
- def Chirp.field_separator=(value)
49
- application.field_separator=value
50
- end
51
-
52
- def Chirp.field_separator
53
- application.field_separator
54
- end
55
-
56
- def Chirp.line
57
- application.line
58
- end
59
-
60
- def Chirp.fields
61
- application.fields
62
- end
63
-
64
- def Chirp.line_no
65
- application.line_no
66
- end
67
-
68
- def Chirp.file
69
- application.file
70
- end
71
-
72
- def Chirp.edit_in_place=(value)
73
- application.edit_in_place = value
74
- end
75
-
76
- def Chirp.files=(value)
77
- application.files=value
78
- end
79
-
80
- def Chirp.pattern( re, &block )
81
- application.pattern(re, block)
82
- end
9
+ require "#{ChirpLibDir}/chirp/statement"
10
+ require "#{ChirpLibDir}/chirp/application"
83
11
 
84
- def Chirp.line_number( n, &block )
85
- application.line_number(n, block)
86
- end
87
-
88
- def Chirp.before &block
89
- application.before(block)
90
- end
91
-
92
- def Chirp.before_file &block
93
- application.before_file(block)
94
- end
95
-
96
- def Chirp.after_file &block
97
- application.after_file(block)
98
- end
99
-
100
- def Chirp.after &block
101
- application.after(block)
102
- end
103
-
104
- class Application
105
-
106
- attr_accessor :field_separator
107
- attr_reader :fields, :line, :line_no, :file
108
-
109
- def initialize
110
- @before_actions=[]
111
- @before_file_actions=[]
112
- @pattern_actions=[]
113
- @after_file_actions=[]
114
- @after_actions=[]
115
- end
116
-
117
- def files=(glob)
118
- if glob.respond_to? :[]
119
- @glob=glob
120
- else
121
- @glob = [ glob ]
122
- end
123
- end
124
-
125
- def edit_in_place=(value)
126
- @edit_in_place=value
127
- end
128
-
129
- def has_run?
130
- @has_run
131
- end
132
-
133
- def run
134
- @has_run = true
135
- @before_actions.each do |action|
136
- action.call
137
- end
138
-
139
- if not @glob
140
- process_stream
141
- elsif @edit_in_place
142
- process_edits(@glob)
143
- else
144
- process_streams(@glob)
145
- end
146
-
147
- @after_actions.each do |action|
148
- action.call
149
- end
150
-
151
- end
152
-
153
- def process_edits(glob)
154
- glob.each do |path_pattern|
155
- Dir.glob(path_pattern) do |path|
156
- puts path
157
- next if not Pathname.new(path).file?
158
- backup_path = path + ".bak"
159
- File.copy(path, backup_path)
160
- open(backup_path,"r") do |in_stream|
161
- open(path, "w") { |out_stream| process_stream(in_stream, out_stream, path) }
162
- end
163
- end
164
- end
165
- end
166
-
167
- def process_streams(glob)
168
- glob.each do |path_pattern|
169
- Dir.glob(path_pattern) do |path|
170
- open(path,"r") { |stream| process_stream(stream, STDOUT, path) }
171
- end
172
- end
173
- end
174
-
175
- def process_stream(in_stream=STDIN, out_stream=STDOUT, name='')
176
- @file=name
177
- old_stdout, $stdout = $stdout, out_stream
178
- run_before_file_actions(name)
179
- @line_no = 1
180
- while not in_stream.eof?
181
- @line = in_stream.readline.chomp
182
- @fields=line.split(@field_separator)
183
- @pattern_actions.each do |pa|
184
- if pa.matches(name, @line, @line_no)
185
- Chirp.module_eval &pa.block
186
- end
187
- end
188
- @line_no += 1
189
- end
190
- run_after_file_actions(name)
191
- $stdout = old_stdout
192
- end
193
-
194
- def run_before_file_actions(name)
195
- @before_file_actions.each { |a| a.call(name) }
196
- end
197
-
198
- def run_after_file_actions(name)
199
- @after_file_actions.each { |a| a.call(name) }
200
- end
201
-
202
- def pattern(re, block)
203
- @pattern_actions << PatternAction.new(re, block)
204
- end
205
-
206
- def line_number(n, block)
207
- @pattern_actions << LineNumberAction.new(n, block)
208
- end
209
-
210
- def before(block)
211
- @before_actions << block
212
- end
213
-
214
- def before_file(block)
215
- @before_file_actions << block
216
- end
217
-
218
- def after_file(block)
219
- @after_file_actions << block
220
- end
221
-
222
- def after(block)
223
- @after_actions << block
224
- end
225
- end
226
- end
227
-
228
- at_exit do
229
- Chirp::application.run if not Chirp::application.has_run?
12
+ module Chirp
13
+ VERSION = '0.2.0'
230
14
  end
231
-
232
-
@@ -0,0 +1,252 @@
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
+
10
+ module Chirp
11
+ def self.redirect_io_and_run(input, output=$stdout, &block)
12
+ old_stdout, $stdout = $stdout, output
13
+ old_stdin, $stdin = $stdin, input
14
+ begin
15
+ block.call
16
+ ensure
17
+ $stdin, $stdout = old_stdin, old_stdout
18
+ end
19
+ end
20
+
21
+ class Context
22
+ include FileUtils
23
+
24
+ attr_accessor :field_separator, :line, :line_no, :path, :paths
25
+ attr_writer :fields, :contents
26
+
27
+ def initialize(field_separator)
28
+ @field_separator = field_separator
29
+ end
30
+
31
+ def execute(action)
32
+ instance_eval(&action)
33
+ end
34
+
35
+ def get_binding
36
+ binding
37
+ end
38
+
39
+ def fields
40
+ @fields || @fields = line.split(@field_separator)
41
+ end
42
+
43
+ def contents
44
+ @contents || @contents = File.read(@file)
45
+ end
46
+ end
47
+
48
+ class Action
49
+ def initialize(context, &block)
50
+ @context = context
51
+ @block = block
52
+ end
53
+
54
+ def evaluate(path)
55
+ @context.path = path
56
+ @context.execute(@block)
57
+ end
58
+
59
+ def evaluate_paths(paths)
60
+ @context.paths = paths
61
+ @context.execute(@block)
62
+ end
63
+
64
+ end
65
+
66
+ class PathAction
67
+ DEFAULT_ACTION = lambda {puts line}
68
+
69
+ attr_accessor :verbose
70
+
71
+ def initialize(glob, context, &block)
72
+ @glob = glob
73
+ @context = context
74
+ @edit_in_place = false
75
+ @before_actions = []
76
+ @match_statements=[]
77
+ @after_actions = []
78
+ @field_separator = /[ \t]/
79
+ instance_eval(&block)
80
+ end
81
+
82
+ def field_separator(sep=nil)
83
+ @field_separator=sep
84
+ end
85
+
86
+ def edit_in_place(new_value=true)
87
+ @edit_in_place = new_value
88
+ end
89
+
90
+ def evaluate_paths(paths)
91
+ @context.paths = paths
92
+ paths.each do |path|
93
+ evaluate_path(path)
94
+ end
95
+ end
96
+
97
+ def line(arg1=nil, arg2=nil, &block)
98
+ action = block || DEFAULT_ACTION
99
+
100
+ if arg1.nil? and arg2.nil?
101
+ @match_statements << Statement.new( MatchPredicate.new(/.*/), action)
102
+ elsif arg2.nil?
103
+ @match_statements << Statement.new( predicate_for(arg1), action)
104
+ else
105
+ @match_statements << Statement.new(
106
+ SpanPredicate.new(predicate_for(arg1), predicate_for(arg2)), action)
107
+ end
108
+ end
109
+
110
+ def before(&block)
111
+ @before_actions << block
112
+ end
113
+
114
+ def after(&block)
115
+ @after_actions << block
116
+ end
117
+
118
+ def applies_to(path)
119
+ File.fnmatch(@glob, path)
120
+ end
121
+
122
+ def evaluate_path(path)
123
+ return nil unless applies_to(path)
124
+ unless @edit_in_place
125
+ File.open(path) do |in_stream|
126
+ Chirp::redirect_io_and_run(in_stream, $stdout) {evaluate(path)}
127
+ end
128
+ else
129
+ backup_path = path + ".bak"
130
+ File.copy(path, backup_path)
131
+ File.open(backup_path) do |in_stream|
132
+ File.open(path, 'w') do |out_stream|
133
+ Chirp::redirect_io_and_run(in_stream, out_stream) {evaluate(path)}
134
+ end
135
+ end
136
+ end
137
+ end
138
+
139
+ def evaluate(path)
140
+ @context.path = path
141
+ @context.field_separator = @field_separator
142
+ @before_actions.each {|a| @context.execute(a)}
143
+ unless @match_statements.empty?
144
+ @context.line_no = 1
145
+ while not $stdin.eof?
146
+ @context.fields=nil
147
+ @context.line = $stdin.readline.chomp
148
+ @match_statements.each do |statement|
149
+ statement.evaluate(@context)
150
+ end
151
+ @context.line_no += 1
152
+ end
153
+ end
154
+ @context.contents = nil
155
+ @after_actions.each {|a| @context.execute(a)}
156
+ end
157
+
158
+ def predicate_for(arg)
159
+ return LineNumberPredicate.new(arg) if arg.kind_of?(Numeric)
160
+ return StringPredicate.new(arg) if arg.kind_of?(String)
161
+ return MatchPredicate.new(arg) if arg.kind_of?(Regexp)
162
+ return RangePredicate.new(arg) if arg.kind_of?(Range)
163
+ return LambdaPredicate.new(arg) if arg.kind_of?(Proc)
164
+ raise "Unknown argument: #{arg}"
165
+ end
166
+ end
167
+
168
+
169
+
170
+ class Application
171
+
172
+ DEFAULT_ACTION = lambda {puts line}
173
+
174
+ attr_accessor :verbose
175
+
176
+ def initialize(program_string, verbose=false)
177
+ @glob = nil
178
+ @actions=[]
179
+ @verbose=verbose
180
+ @context = Chirp::Context.new(/[ \t]/)
181
+ self.program=program_string if program_string
182
+ end
183
+
184
+ def setup(args)
185
+ self.program=File.read(args[0])
186
+ end
187
+
188
+ def program=(chirp_program_string)
189
+ instance_eval(chirp_program_string, 'user program')
190
+ end
191
+
192
+ def run(&block)
193
+ @actions << Action.new(@context, &block)
194
+ end
195
+
196
+ def file(glob='*', &block)
197
+ @actions << PathAction.new(glob, @context, &block)
198
+ end
199
+
200
+ def files(args)
201
+ @glob = args unless args.empty?
202
+ @glob
203
+ end
204
+
205
+ def evaluate(in_stream=$stdin, out_stream=$stdout)
206
+ log('Running Chirp program')
207
+ Chirp::redirect_io_and_run(in_stream, out_stream) do
208
+ log('Running before actions')
209
+ if not @glob
210
+ process_stream(in_stream, out_stream, '-')
211
+ else
212
+ process_files(@glob)
213
+ end
214
+ log('Running after actions')
215
+ end
216
+ log('Chirp program done')
217
+ end
218
+
219
+ def process_stream(in_stream, outstream, name)
220
+ @actions.each do |path_action|
221
+ Chirp::redirect_io_and_run(in_stream, outstream) do
222
+ path_action.evaluate(name)
223
+ end
224
+ end
225
+ end
226
+
227
+ def process_files(globs)
228
+ @actions.each do |action|
229
+ paths = expand_globs(globs)
230
+ action.evaluate_paths(paths)
231
+ end
232
+ end
233
+
234
+ def log(*args)
235
+ return unless @verbose
236
+ STDERR.puts args.join(' ')
237
+ end
238
+
239
+ def expand_globs(globs)
240
+ results = []
241
+ globs.each do |path_pattern|
242
+ this_glob_results = []
243
+ Dir.glob(path_pattern) do |path|
244
+ this_glob_results << path if Pathname.new(path).file?
245
+ end
246
+ this_glob_results.sort!
247
+ results << this_glob_results
248
+ end
249
+ results.flatten!
250
+ end
251
+ end
252
+ end