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 +7 -0
- data/Gemfile +4 -0
- data/README.md +71 -0
- data/Rakefile +1 -0
- data/bin/xail +5 -0
- data/examples/logview.xail.rb +34 -0
- data/examples/logviewtest.txt +11 -0
- data/lib/xail/config.rb +62 -0
- data/lib/xail/filter.rb +273 -0
- data/lib/xail/ui.rb +12 -0
- data/lib/xail/version.rb +3 -0
- data/lib/xail.rb +43 -0
- data/spec/filter_spec.rb +83 -0
- data/xail.gemspec +26 -0
- metadata +94 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
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,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
|
+
}
|
data/lib/xail/config.rb
ADDED
@@ -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
|
data/lib/xail/filter.rb
ADDED
@@ -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
data/lib/xail/version.rb
ADDED
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
|
data/spec/filter_spec.rb
ADDED
@@ -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
|