chirp 0.1.0 → 0.2.0

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