rawk 0.1.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.
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: []