xail 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|