xail 0.0.1

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/.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