xail 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,7 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ .idea
6
+ *.iml
7
+ *.swp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in xail.gemspec
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,71 @@
1
+ # xail - tail for winners
2
+
3
+ xail is a super lightweight Ruby DSL for building simple stream/log analysis
4
+ tools.
5
+
6
+ A simple log viewer: `logview.xail.rb`:
7
+
8
+
9
+ #!/usr/bin/env xail
10
+ group('fatal') {
11
+ contains 'fatal'
12
+ bell
13
+ }
14
+
15
+ group('error') {
16
+ contains 'error', 'exception'
17
+ red
18
+ bold
19
+ }
20
+
21
+ group('warning') {
22
+ contains 'warn'
23
+ yellow
24
+ }
25
+
26
+ You can then run it directly:
27
+
28
+ $ ./logview.xail.rb
29
+
30
+ And it will accept input on stdin or a filename on the command line.
31
+
32
+ You can directly specify:
33
+
34
+ ## Filters
35
+
36
+ There are filters and compound filters. A filter takes a line from a stream
37
+ and performs some action, potentially altering the stream, or terminating the flow.
38
+ Compound filters take a stream and also a set of subfilters. These generally implement
39
+ flow control.
40
+
41
+ Stdout is the ultimate consumer of the strings, unless they've been filtered.
42
+
43
+ ### Compound Filters
44
+ * `cascade` -- stream the result of first subfilter that streams
45
+ * `composition` -- applies each subfilter on the stream of the preceding subfilter, streaming the final result
46
+
47
+ * `and` -- streams the original if all subfilters stream
48
+ * `or` -- streams the original if any subfilter streams
49
+ * `not` -- streams the original if no subfilters stream
50
+
51
+ ### Matching Filters
52
+ * `contains` -- streams the original if any of the parameters are included
53
+ * `replace` -- mutates the stream
54
+
55
+ ### Alerting Filters
56
+ * `execute` executes a command, replacing `{}` with the line content
57
+ * `bell` rings a terminal bell (if possible)
58
+
59
+ ### Styling Filters
60
+ * `black`, `red`, `green`, `yellow`, `blue`, `magenta`, `cyan`, `white` -- adjust foreground color
61
+ * `onblack`, `onred`, `ongreen`, `onyellow`, `onblue`, `onmagenta`, `oncyan`, `onwhite` -- adjust background color
62
+ * `bold`, `blink`, `underscore`, `negative`, `dark` -- apply effects to the text
63
+
64
+ ### Special Filters
65
+ * `sample` -- samples the stream, only printing at the rate requested
66
+ * `stop` -- stops processing of this stream and continues with the next
67
+ * `count` -- [todo] computes the rate of the stream for display (need UI aspect)
68
+
69
+ ### Custom Filters
70
+
71
+ You can easily develop your own filters. Either as an anonymous block:
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/bin/xail ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+ $LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib')
3
+
4
+ require 'xail'
5
+
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env xail
2
+ #
3
+ # a simple log error and warning highlighter
4
+ #
5
+
6
+ class ExecuteFilter < AbstractFilter
7
+ def initialize(command)
8
+ @command = command
9
+ end
10
+
11
+ def streamLine(line)
12
+ puts "EXECUTING #{@command} #{line}"
13
+ return line
14
+ end
15
+ end
16
+
17
+
18
+ group('fatal') {
19
+ contains 'fatal'
20
+ red
21
+ onblue
22
+ bell
23
+ }
24
+
25
+ group('error') {
26
+ contains 'error', 'exception'
27
+ red
28
+ bold
29
+ }
30
+
31
+ group('warning') {
32
+ contains 'warn'
33
+ yellow
34
+ }
@@ -0,0 +1,11 @@
1
+ [trace] hi
2
+ [trace] there
3
+ [warn] vagrant
4
+ [trace] how
5
+ [error] are you doing?
6
+ [fatal] all that?
7
+ [error] stuff?
8
+ [warn ] test
9
+ [trace] finit.
10
+
11
+
@@ -0,0 +1,62 @@
1
+
2
+ require 'xail/filter'
3
+
4
+ module Xail
5
+ module DSL
6
+ def get_binding
7
+ binding
8
+ end
9
+
10
+ def filter_scope(compound)
11
+ filter_in_scope << compound
12
+ @filter_stack << compound
13
+
14
+ yield
15
+
16
+ @filter_stack.pop
17
+ end
18
+
19
+ def stream(name, source = null)
20
+ source ||= name
21
+ end
22
+
23
+ def group(name, &filters)
24
+ # TODO intergrate with UX
25
+ filter_scope(FilterComposition.new) {
26
+ filters.yield
27
+ }
28
+ end
29
+
30
+ def has_final
31
+ @has_final
32
+ end
33
+
34
+ def rest(&filters)
35
+ if @has_final
36
+ raise "rest may only be specified once"
37
+ end
38
+
39
+ @has_final = true
40
+ filter_scope(FilterComposition.new) {
41
+ filters.yield
42
+ }
43
+ end
44
+
45
+ def filter_in_scope
46
+ @filter_stack ||= [FilterCascade.new]
47
+ @filter_stack.last
48
+ end
49
+
50
+ def method_missing(name, *args, &block)
51
+ filterClass = FilterRegistry::get_filter(name.downcase)
52
+ filter = filterClass.new(*args)
53
+ filter_in_scope << filter
54
+
55
+ rescue UnknownFilter => error
56
+ abort error.to_s
57
+
58
+ rescue => error
59
+ abort "#{filter_in_scope} will not accept #{name} as subfilter: #{error}"
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,273 @@
1
+
2
+ require 'term/ansicolor'
3
+
4
+ module Xail
5
+
6
+ class UnknownFilter < Exception
7
+ def initialize(filter)
8
+ super("Unknown filter ``#{filter}''. Known: #{FilterRegistry.filters.keys.sort}")
9
+ end
10
+ end
11
+
12
+ class FilterRegistry
13
+ def self.filters
14
+ @@filters ||= FilterRegistry.find_filters
15
+ end
16
+
17
+
18
+ # naively assume all filters are defined in this file, or at least loaded
19
+ # before this executes
20
+ def self.find_filters
21
+ filters = Hash.new
22
+ ObjectSpace.each_object(Class).select { |classObject|
23
+ classObject < AbstractFilter and
24
+ not classObject.name.downcase.include? "abstract"
25
+ }.map { |filterclass|
26
+ filtername = filterclass.name.split('::').last.gsub(/filter/i, '')
27
+ filters[filtername.downcase] = filterclass
28
+ }
29
+
30
+ filters
31
+ end
32
+
33
+ def self.get_filter(key)
34
+ name = key.to_s
35
+
36
+ @@filters ||= FilterRegistry.filters
37
+
38
+ if @@filters.has_key? name
39
+ return @@filters[name]
40
+ end
41
+
42
+ # if we couldn't find the filter, rebuild the list and try
43
+ # again
44
+ @@filters = FilterRegistry.find_filters
45
+
46
+ if @@filters.has_key? name
47
+ return @@filters[name]
48
+ else
49
+ raise UnknownFilter.new(name)
50
+ end
51
+ end
52
+ end
53
+
54
+
55
+
56
+
57
+ class AbstractFilter
58
+ def filterName
59
+ self.class.name.split('::').last.gsub(/filter/i, '')
60
+ end
61
+
62
+ def streamLine(line)
63
+ end
64
+ end
65
+
66
+
67
+
68
+ #
69
+ # Filter Composer
70
+ #
71
+
72
+ class AbstractCompoundFilter < AbstractFilter
73
+ def initialize
74
+ @filters = []
75
+ end
76
+
77
+ def <<(filter)
78
+ @filters << filter
79
+ end
80
+ end
81
+
82
+
83
+ # a cascade streams the next filter on rejection
84
+ class FilterCascade < AbstractCompoundFilter
85
+ def streamLine(input)
86
+ @filters.each do |filter|
87
+ line = filter.streamLine(input)
88
+ if line != nil
89
+ return line
90
+ end
91
+ end
92
+
93
+ nil
94
+ end
95
+ end
96
+
97
+
98
+ # a composition streams the next filter on success
99
+ class FilterComposition < AbstractCompoundFilter
100
+ def streamLine(input)
101
+ @filters.inject(input) do |line,filter|
102
+ if line != nil
103
+ filter.streamLine(line)
104
+ else
105
+ nil
106
+ end
107
+ end
108
+ end
109
+ end
110
+
111
+
112
+ # the and filter streams the original if all component filters stream
113
+ class AndFilter < AbstractCompoundFilter
114
+ def streamLine(line)
115
+ @filters.each do |filter|
116
+ if !filter.streamLine(line)
117
+ return nil
118
+ end
119
+ end
120
+
121
+ line
122
+ end
123
+ end
124
+
125
+
126
+ # the or filter streams the original if any component filter streams
127
+ class OrFilter < AbstractCompoundFilter
128
+ def streamLine(line)
129
+ @filters.each do |filter|
130
+ if filter.streamLine line
131
+ return line
132
+ end
133
+ end
134
+
135
+ nil
136
+ end
137
+ end
138
+
139
+
140
+ # the not filter streams the original if none of the component filters stream
141
+ class NotFilter < AndFilter
142
+ def streamLine(line)
143
+ result = super.streamLine(line)
144
+
145
+ if result != nil
146
+ nil
147
+ else
148
+ line
149
+ end
150
+ end
151
+ end
152
+
153
+ class ContainsFilter < AbstractFilter
154
+ def initialize(*keys)
155
+ @keys = keys
156
+ end
157
+
158
+ def streamLine(line)
159
+ @keys.each do |key|
160
+ if line.include?(key)
161
+ return line
162
+ end
163
+ end
164
+
165
+ nil
166
+ end
167
+ end
168
+
169
+ class ReplaceFilter < AbstractFilter
170
+ def initialize(regexp)
171
+ @regexp = regexp
172
+ end
173
+
174
+ def streamLine(line)
175
+ # why fold when you can iterate
176
+ final = line
177
+ @regexp.each_pair do |patt,val|
178
+ final.gsub!(patt,val)
179
+ end
180
+ final
181
+ end
182
+ end
183
+
184
+
185
+ # the stop filter never streams
186
+ class StopFilter < AbstractFilter
187
+ def streamLine(line)
188
+ nil
189
+ end
190
+ end
191
+
192
+ # the pass through filter always streams
193
+ class PassThroughFilter < AbstractFilter
194
+ def streamLine(line)
195
+ line
196
+ end
197
+ end
198
+
199
+
200
+
201
+
202
+ #
203
+ # Stream Mutators
204
+ #
205
+
206
+ class SampleFilter < AbstractFilter
207
+ def initialize(params)
208
+ @rate = params.to_i
209
+ @count = 0
210
+ end
211
+
212
+ def streamLine(line)
213
+ res = if @count % @rate == 0
214
+ line
215
+ else
216
+ ""
217
+ end
218
+
219
+ @count += 1
220
+ res
221
+ end
222
+ end
223
+
224
+
225
+
226
+ #
227
+ # Stream Decorators
228
+ #
229
+
230
+ class BellFilter < AbstractFilter
231
+ def streamLine(line)
232
+ return "\a"+line
233
+ end
234
+ end
235
+
236
+ class AbstractColorFilter < AbstractFilter
237
+ def initialize
238
+ @colors = Term::ANSIColor
239
+ end
240
+
241
+ def streamLine(line)
242
+ return @colors.send(filterName.gsub(/On/,"on_").downcase, line)
243
+ end
244
+ end
245
+
246
+
247
+ # foregrounds
248
+ class Black < AbstractColorFilter; end
249
+ class Red < AbstractColorFilter; end
250
+ class Green < AbstractColorFilter; end
251
+ class Yellow < AbstractColorFilter; end
252
+ class Blue < AbstractColorFilter; end
253
+ class Magenta < AbstractColorFilter; end
254
+ class Cyan < AbstractColorFilter; end
255
+ class White < AbstractColorFilter; end
256
+
257
+ # backgrounds
258
+ class OnBlack < AbstractColorFilter; end
259
+ class OnRed < AbstractColorFilter; end
260
+ class OnGreen < AbstractColorFilter; end
261
+ class OnYellow < AbstractColorFilter; end
262
+ class OnBlue < AbstractColorFilter; end
263
+ class OnMagenta < AbstractColorFilter; end
264
+ class OnCyan < AbstractColorFilter; end
265
+ class OnWhite < AbstractColorFilter; end
266
+
267
+ # effects
268
+ class Bold < AbstractColorFilter; end
269
+ class Blink < AbstractColorFilter; end
270
+ class Underscore < AbstractColorFilter; end
271
+ class Negative < AbstractColorFilter; end
272
+ class Dark < AbstractColorFilter; end
273
+ end
data/lib/xail/ui.rb ADDED
@@ -0,0 +1,12 @@
1
+ module Xail
2
+ class UI
3
+ def write_line()
4
+
5
+ end
6
+
7
+ def write_status_line()
8
+
9
+ end
10
+ end
11
+ end
12
+
@@ -0,0 +1,3 @@
1
+ module Xail
2
+ VERSION = "0.0.1"
3
+ end
data/lib/xail.rb ADDED
@@ -0,0 +1,43 @@
1
+ require 'yaml'
2
+ require 'trollop'
3
+ require 'pp'
4
+
5
+ require 'xail/version'
6
+ require 'xail/filter'
7
+ require 'xail/config'
8
+
9
+ module Xail
10
+ @opts = Trollop::options do
11
+ version "xail - #{VERSION} (c) 2012 w.k.macura - Released under BSD license"
12
+ banner <<-EOS
13
+ A Ruby utility for performing basic stream processing, directly focused on increasing error visibility in logs
14
+ EOS
15
+ end
16
+
17
+
18
+ def Xail.run(configuration)
19
+
20
+ begin
21
+ extend Xail::DSL
22
+
23
+ eval(configuration)
24
+ filter = filter_in_scope
25
+
26
+ if !has_final
27
+ filter << PassThroughFilter.new
28
+ end
29
+ end
30
+
31
+
32
+ stream = $stdin
33
+ stream.each() do |line|
34
+ streamed = filter.streamLine(line)
35
+ if streamed and streamed.size > 0
36
+ printf streamed
37
+ end
38
+ end
39
+ end
40
+
41
+ config = IO.read(ARGV[0])
42
+ Xail.run(config)
43
+ end
@@ -0,0 +1,83 @@
1
+
2
+ require 'xail/filter'
3
+
4
+ include Xail
5
+
6
+ describe BellFilter do
7
+ it "prepends a bell to the stream" do
8
+ f = BellFilter.new
9
+ f.streamLine("hi").should eq("\ahi")
10
+ end
11
+ end
12
+
13
+ describe Yellow do
14
+ it "should apply escape codes to the filter" do
15
+ f = Yellow.new
16
+ f.streamLine("hi").should eq("\e[33mhi\e[0m")
17
+ end
18
+ end
19
+
20
+ describe PassThroughFilter do
21
+ it "should passthrough streams" do
22
+ f = PassThroughFilter.new
23
+ f.streamLine("hi").should eq("hi")
24
+ end
25
+ end
26
+
27
+ describe StopFilter do
28
+ it "should stop all streams" do
29
+ f = StopFilter.new
30
+ f.streamLine("hi").should eq(nil)
31
+ end
32
+ end
33
+
34
+ describe ContainsFilter do
35
+ it "should stream if it matches" do
36
+ f = ContainsFilter.new("hi")
37
+ f.streamLine("bye").should eq(nil)
38
+ f.streamLine("hi there").should eq("hi there")
39
+ end
40
+ end
41
+
42
+ describe ReplaceFilter do
43
+ it "should replace on streams" do
44
+ f = ReplaceFilter.new(/hi/ => 'bye', /there/ => 'potato')
45
+ f.streamLine("hi there").should eq("bye potato")
46
+ end
47
+ end
48
+
49
+ describe SampleFilter do
50
+ it "should sample at the given rate" do
51
+ f = SampleFilter.new(5)
52
+ f.streamLine("a").should eq "a"
53
+ f.streamLine("b").should eq ""
54
+ f.streamLine("c").should eq ""
55
+ f.streamLine("d").should eq ""
56
+ f.streamLine("e").should eq ""
57
+ f.streamLine("f").should eq "f"
58
+ end
59
+ end
60
+
61
+ describe FilterCascade do
62
+ it "should cascade through" do
63
+ f = FilterCascade.new
64
+ f << StopFilter.new
65
+ f << StopFilter.new
66
+ f << ContainsFilter.new("hi")
67
+ f << StopFilter.new
68
+
69
+ f.streamLine("bye").should eq(nil)
70
+ f.streamLine("hi").should eq("hi")
71
+ end
72
+ end
73
+
74
+ describe FilterComposition do
75
+ it "should compose through" do
76
+ f = FilterComposition.new
77
+ f << ReplaceFilter.new('a'=>'b')
78
+ f << ContainsFilter.new('hi')
79
+
80
+ f.streamLine('bye').should eq(nil)
81
+ f.streamLine('hi there alice').should eq('hi there blice')
82
+ end
83
+ end
data/xail.gemspec ADDED
@@ -0,0 +1,26 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "xail/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "xail"
7
+ s.version = Xail::VERSION
8
+ s.authors = ["Wiktor Macura"]
9
+ s.email = ["wiktor@tumblr.com"]
10
+ s.homepage = ""
11
+ s.summary = %q{tail for winners}
12
+ s.description = %q{A lightweight Ruby DSL for building text-stream filters}
13
+
14
+ s.rubyforge_project = "xail"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- spec/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ # specify any dependencies here; for example:
22
+ s.add_development_dependency "rspec"
23
+
24
+ s.add_runtime_dependency "trollop"
25
+ s.add_runtime_dependency "term-ansicolor"
26
+ end
metadata ADDED
@@ -0,0 +1,94 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: xail
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Wiktor Macura
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-02-24 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: &70198967869060 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: *70198967869060
25
+ - !ruby/object:Gem::Dependency
26
+ name: trollop
27
+ requirement: &70198967868420 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *70198967868420
36
+ - !ruby/object:Gem::Dependency
37
+ name: term-ansicolor
38
+ requirement: &70198967867800 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :runtime
45
+ prerelease: false
46
+ version_requirements: *70198967867800
47
+ description: A lightweight Ruby DSL for building text-stream filters
48
+ email:
49
+ - wiktor@tumblr.com
50
+ executables:
51
+ - xail
52
+ extensions: []
53
+ extra_rdoc_files: []
54
+ files:
55
+ - .gitignore
56
+ - Gemfile
57
+ - README.md
58
+ - Rakefile
59
+ - bin/xail
60
+ - examples/logview.xail.rb
61
+ - examples/logviewtest.txt
62
+ - lib/xail.rb
63
+ - lib/xail/config.rb
64
+ - lib/xail/filter.rb
65
+ - lib/xail/ui.rb
66
+ - lib/xail/version.rb
67
+ - spec/filter_spec.rb
68
+ - xail.gemspec
69
+ homepage: ''
70
+ licenses: []
71
+ post_install_message:
72
+ rdoc_options: []
73
+ require_paths:
74
+ - lib
75
+ required_ruby_version: !ruby/object:Gem::Requirement
76
+ none: false
77
+ requirements:
78
+ - - ! '>='
79
+ - !ruby/object:Gem::Version
80
+ version: '0'
81
+ required_rubygems_version: !ruby/object:Gem::Requirement
82
+ none: false
83
+ requirements:
84
+ - - ! '>='
85
+ - !ruby/object:Gem::Version
86
+ version: '0'
87
+ requirements: []
88
+ rubyforge_project: xail
89
+ rubygems_version: 1.8.10
90
+ signing_key:
91
+ specification_version: 3
92
+ summary: tail for winners
93
+ test_files:
94
+ - spec/filter_spec.rb