rawk 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/bin/rawk ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ PROJECT_DIR=File.expand_path(File.join(File.dirname(__FILE__), '..'))
4
+ $: << File.join(PROJECT_DIR, 'lib')
5
+
6
+ require 'rawk/rawk'
7
+
8
+ DATA = STDIN
9
+ DSL_CODE = ARGV[0]
10
+
11
+ Rawk::Program.new(DATA).run(DSL_CODE)
data/lib/rawk/rawk.rb ADDED
@@ -0,0 +1,94 @@
1
+ require 'set'
2
+ require 'observer'
3
+
4
+ module Rawk
5
+ class Program
6
+ attr_reader :fs
7
+
8
+ def initialize(io)
9
+ @start, @every, @finish = Set.new, Set.new, Set.new
10
+ @input_stream = InputStream.new(io)
11
+ @input_stream.add_observer(self)
12
+ initialize_builtins!
13
+ end
14
+
15
+ private
16
+ def initialize_builtins!
17
+ @fs = " "
18
+ @nr = 0
19
+ end
20
+
21
+ public
22
+ def on_new_line
23
+ @nr += 1
24
+ end
25
+ alias :update :on_new_line
26
+
27
+ def run(code = "", &block)
28
+ load!(code, &block)
29
+ execute_code!
30
+ end
31
+
32
+ # DSL
33
+ def start(&block)
34
+ @start << block
35
+ end
36
+
37
+ def every(&block)
38
+ @every << block
39
+ end
40
+
41
+ def finish(&block)
42
+ @finish << block
43
+ end
44
+
45
+ private
46
+ def load!(code, &block)
47
+ if code.empty?
48
+ instance_eval(&block)
49
+ else
50
+ instance_eval(code)
51
+ end
52
+ end
53
+
54
+ def execute_code!
55
+ @start.each {|b| b.call}
56
+ @input_stream.each_line do |row|
57
+ @every.each {|b| b.call(Line.new(row, @fs))}
58
+ end
59
+ @finish.each {|b| b.call}
60
+ end
61
+ end
62
+
63
+ class InputStream
64
+ include Observable
65
+
66
+ def initialize(io)
67
+ @io = io
68
+ end
69
+
70
+ def each_line
71
+ @io.each_line do |line|
72
+ changed
73
+ notify_observers
74
+ yield line
75
+ end
76
+ end
77
+ end
78
+
79
+ class Line < String
80
+ def initialize(str, fs)
81
+ self.replace(str.chomp)
82
+ @fs = fs
83
+ end
84
+
85
+ def cols
86
+ split(@fs)
87
+ end
88
+ alias :c :cols
89
+
90
+ def nf
91
+ cols.length
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+
3
+ module Rawk
4
+ describe Line do
5
+ before do
6
+ @fs = " "
7
+ @data = "a b c\n"
8
+ @line = Line.new(@data, @fs)
9
+ end
10
+ it "is a string" do
11
+ @line.is_a?(String).should be_true
12
+ end
13
+ it "chomps itself on creation" do
14
+ @line.should == "a b c"
15
+ end
16
+ it "finds space-delimited columns by" do
17
+ @line.cols.should == ["a","b","c"]
18
+ @line.c.should == @line.cols
19
+ end
20
+ it "calculates the number of fields" do
21
+ @line.nf.should == 3
22
+ end
23
+
24
+ context "with a field separator ','" do
25
+ it "splits the line into columns using a comma" do
26
+ line = Line.new("a,b,c d", ",")
27
+ line.c.should == ["a","b","c d"]
28
+ end
29
+ end
30
+ context "with a regular expression field separator" do
31
+ it "splits the line into columns using the regular expression" do
32
+ line = Line.new("a,b|c", /[,|]/)
33
+ line.c.should == ["a","b","c"]
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,146 @@
1
+ require 'spec_helper'
2
+ require 'stringio'
3
+
4
+ module Rawk
5
+ describe Program do
6
+ include TestHelpers
7
+
8
+ before do
9
+ @data = StringIO.new("a b\nc d\ne f\n")
10
+ @program = Program.new(@data)
11
+ end
12
+
13
+ context "when passed code in a string" do
14
+ it "runs the string as ruby code" do
15
+ code = "every {puts 'foo'}"
16
+ out = capture_stdout { @program.run code }.string
17
+ out.should == "foo\nfoo\nfoo\n"
18
+ end
19
+ end
20
+
21
+ context "when passed code in ruby blocks" do
22
+ it "executes the blocks in order" do
23
+ start_block, every_block, finish_block = lambda {}, lambda {}, lambda {}
24
+ start_block.should_receive(:call).once
25
+ every_block.should_receive(:call).exactly(3).times
26
+ finish_block.should_receive(:call).once
27
+
28
+ @program.run do
29
+ start &start_block
30
+ every &every_block
31
+ finish &finish_block
32
+ end
33
+ end
34
+
35
+ it "includes the scope of the calling program" do
36
+ result = []
37
+ @program.run do
38
+ every {|l| result << l}
39
+ end
40
+ result.should == ["a b","c d","e f"]
41
+ end
42
+ end
43
+
44
+ describe "condition execution semantics" do
45
+ describe "start condition" do
46
+ it "can have many start conditions" do
47
+ block1, block2 = lambda {}, lambda {}
48
+ block1.should_receive(:call).once
49
+ block2.should_receive(:call).once
50
+ @program.run do
51
+ start &block1
52
+ start &block2
53
+ end
54
+ end
55
+ it "only calls a duplicate start block once" do
56
+ block = lambda {}
57
+ block.should_receive(:call).once
58
+ @program.run do
59
+ start &block
60
+ start &block
61
+ end
62
+ end
63
+ end
64
+
65
+ describe "every condition" do
66
+ it "runs once for each line of input data" do
67
+ block = lambda {}
68
+ block.should_receive(:call).once.with("a b").ordered
69
+ block.should_receive(:call).once.with("c d").ordered
70
+ block.should_receive(:call).once.with("e f").ordered
71
+ @program.run {every &block}
72
+ end
73
+
74
+ it "runs multiple every conditions" do
75
+ block1, block2 = lambda {}, lambda {}
76
+ block1.should_receive(:call).exactly(3).times
77
+ block2.should_receive(:call).exactly(3).times
78
+ @program.run do
79
+ every &block1
80
+ every &block2
81
+ end
82
+ end
83
+
84
+ it "only runs once when specified many times" do
85
+ block = lambda {}
86
+ block.should_receive(:call).exactly(3).times
87
+ @program.run do
88
+ every &block
89
+ every &block
90
+ end
91
+ end
92
+ end
93
+
94
+ describe "finish condition" do
95
+ it "can have many finish conditions" do
96
+ block1, block2 = lambda {}, lambda {}
97
+ block1.should_receive(:call).once
98
+ block2.should_receive(:call).once
99
+ @program.run do
100
+ finish &block1
101
+ finish &block2
102
+ end
103
+ end
104
+ it "only runs once when specified many times" do
105
+ block = lambda {}
106
+ block.should_receive(:call).once
107
+ @program.run do
108
+ finish &block
109
+ finish &block
110
+ end
111
+ end
112
+ end
113
+ end
114
+
115
+ describe "support for standard awk built-in variables" do
116
+ it "calculates the current record num as nr" do
117
+ record_nums = []
118
+ @program.run do
119
+ start {record_nums << @nr}
120
+ every {record_nums << @nr}
121
+ finish {record_nums << @nr}
122
+ end
123
+ record_nums.should == [0,1,2,3,3]
124
+ end
125
+
126
+ describe "fs" do
127
+ it "holds the current field separator expression" do
128
+ @program.fs.should == " "
129
+ end
130
+ it "is applied to each line of data" do
131
+ data = "line"
132
+ Line.should_receive(:new).with(data, " ")
133
+ Program.new(data).run {every {|l| nil}}
134
+ end
135
+ it "can be changed by the user's program" do
136
+ data = "line"
137
+ Line.should_receive(:new).with(data, ",").and_return("dummy")
138
+ Program.new(data).run do
139
+ start {@fs = ','}
140
+ every {|l| nil}
141
+ end
142
+ end
143
+ end
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,15 @@
1
+ PROJECT_DIR=File.expand_path(File.dirname(__FILE__), '..')
2
+ $: << File.join(PROJECT_DIR, 'lib')
3
+
4
+ require 'rawk/rawk'
5
+
6
+ module Rawk::TestHelpers
7
+ def capture_stdout
8
+ out = StringIO.new
9
+ $stdout = out
10
+ yield
11
+ return out
12
+ ensure
13
+ $stdout = STDOUT
14
+ end
15
+ end
metadata ADDED
@@ -0,0 +1,49 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rawk
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Adrian Mowat
9
+ autorequire: rawk/rawk
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-08-14 00:00:00.000000000Z
13
+ dependencies: []
14
+ description:
15
+ email:
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - bin/rawk
21
+ - lib/rawk/rawk.rb
22
+ - spec/rawk/line_spec.rb
23
+ - spec/rawk/rawk_spec.rb
24
+ - spec/spec_helper.rb
25
+ homepage:
26
+ licenses: []
27
+ post_install_message:
28
+ rdoc_options: []
29
+ require_paths:
30
+ - - lib
31
+ required_ruby_version: !ruby/object:Gem::Requirement
32
+ none: false
33
+ requirements:
34
+ - - ! '>='
35
+ - !ruby/object:Gem::Version
36
+ version: '0'
37
+ required_rubygems_version: !ruby/object:Gem::Requirement
38
+ none: false
39
+ requirements:
40
+ - - ! '>='
41
+ - !ruby/object:Gem::Version
42
+ version: '0'
43
+ requirements: []
44
+ rubyforge_project:
45
+ rubygems_version: 1.8.6
46
+ signing_key:
47
+ specification_version: 3
48
+ summary: An awk-inspired ruby DSL
49
+ test_files: []