notified_tail 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.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +14 -0
- data/Rakefile +9 -0
- data/lib/notified_tail.rb +82 -0
- data/notified_tail.gemspec +26 -0
- data/spec/lib/notified_tail_spec.rb +70 -0
- metadata +125 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 1a2903e3cddd8da5107080ea930b4b410f4d7183
|
4
|
+
data.tar.gz: 926996d000172e71d502019a646dc64b15171326
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 4a61d2ae2fdc622ba708f51f9137882d5091701b21a1135eef380674eb525aacd03b1e6240adbb5aac89ba7b42a015fe355a36d33744772d54b6b39368bcb59d
|
7
|
+
data.tar.gz: 05a6a3d10b602fbf805f7dee69dab70d7065d58a0116bc4d46f200352601382dfd8869939173df5f97da2cc4b5f26cd8be00d01113de7007934cd597bd3923a0
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2015 Peter Winton
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# NotifiedTail
|
2
|
+
|
3
|
+
`tail -f` for ruby using inotify/kqueue to avoid polling. Polling adds
|
4
|
+
latency and overhead. Falls back to polling on platforms where neither
|
5
|
+
inotify nor kqueue are supported.
|
6
|
+
|
7
|
+
Inspired by http://rubyforadmins.com/reading-growing-files
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
require 'notified_tail'
|
11
|
+
NotifiedTail.tail(file_path, seek_end: true) do |line|
|
12
|
+
puts line
|
13
|
+
end
|
14
|
+
```
|
data/Rakefile
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
# Tail lines in a given file
|
2
|
+
# Inspired by http://rubyforadmins.com/reading-growing-files
|
3
|
+
class NotifiedTail
|
4
|
+
|
5
|
+
# Yields complete lines, one at a time.
|
6
|
+
# Works even if file doesn't exist yet.
|
7
|
+
# @param file_path [String] The file to tail
|
8
|
+
# @option opts [Boolean] seek_end (true)
|
9
|
+
# If true, seeks to the end of the file before reporting lines.
|
10
|
+
# Otherwise, reports all lines starting at the beginning of the file.
|
11
|
+
def self.tail(file_path, opts={}, &on_line)
|
12
|
+
new.tail(file_path, opts, &on_line)
|
13
|
+
end
|
14
|
+
|
15
|
+
def tail(filename, opts, &on_line)
|
16
|
+
@stopped = false
|
17
|
+
seek_end = opts.fetch(:seek_end, true)
|
18
|
+
sleep(0.25) until File.exists?(filename)
|
19
|
+
File.open(filename) do |file|
|
20
|
+
unreported_line = ''
|
21
|
+
if seek_end
|
22
|
+
file.seek(0, IO::SEEK_END)
|
23
|
+
else
|
24
|
+
read_and_report_lines(file, unreported_line, &on_line)
|
25
|
+
end
|
26
|
+
when_modified(filename) { read_and_report_lines(file, unreported_line, &on_line) }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def stop
|
31
|
+
@queue.stop if @queue
|
32
|
+
@queue = nil
|
33
|
+
@stopped = true
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def read_and_report_lines(file, unreported_line, &on_line)
|
39
|
+
loop do
|
40
|
+
c = file.readchar
|
41
|
+
if line_ending?(c)
|
42
|
+
on_line.call(unreported_line.dup)
|
43
|
+
unreported_line.clear
|
44
|
+
while (c = file.readchar) && line_ending?(c) do; end
|
45
|
+
end
|
46
|
+
unreported_line << c
|
47
|
+
end
|
48
|
+
rescue EOFError
|
49
|
+
# done for now
|
50
|
+
end
|
51
|
+
|
52
|
+
def when_modified(file_path)
|
53
|
+
case NotifiedTail.get_ruby_platform
|
54
|
+
when /bsd/, /darwin/
|
55
|
+
require 'rb-kqueue'
|
56
|
+
@queue = KQueue::Queue.new
|
57
|
+
@queue.watch_file(ARGV.first, :extend) { yield }
|
58
|
+
@queue.run
|
59
|
+
when /linux/
|
60
|
+
require 'rb-inotify'
|
61
|
+
@queue = INotify::Notifier.new
|
62
|
+
@queue.watch(file_path, :modify) { yield }
|
63
|
+
@queue.run
|
64
|
+
else
|
65
|
+
last_mtime = File.mtime(file_path)
|
66
|
+
until @stopped do
|
67
|
+
sleep(0.5)
|
68
|
+
mtime = File.mtime(file_path)
|
69
|
+
yield if mtime != last_mtime # use != instead of > to mitigate DST complications
|
70
|
+
last_mtime = mtime
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.get_ruby_platform
|
76
|
+
RUBY_PLATFORM
|
77
|
+
end
|
78
|
+
|
79
|
+
def line_ending?(c)
|
80
|
+
c == "\n" || c == "\r"
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = 'notified_tail'
|
7
|
+
spec.version = '0.1.0'
|
8
|
+
spec.authors = ['Peter Winton']
|
9
|
+
spec.email = ['pwinton@indigobio.com']
|
10
|
+
spec.summary = %q{Low latency file tailing in ruby}
|
11
|
+
spec.description = %q{Uses inotify/kqueue on supported platforms to avoid polling latency and overhead}
|
12
|
+
spec.homepage = ''
|
13
|
+
spec.license = 'MIT'
|
14
|
+
|
15
|
+
spec.files = `git ls-files -z`.split("\x0")
|
16
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
17
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
18
|
+
spec.require_paths = ['lib']
|
19
|
+
|
20
|
+
spec.add_development_dependency 'bundler', '~> 1.7'
|
21
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
22
|
+
spec.add_development_dependency 'rspec', '~> 3.3'
|
23
|
+
|
24
|
+
spec.add_runtime_dependency 'rb-inotify'
|
25
|
+
spec.add_runtime_dependency 'rb-kqueue'
|
26
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
require 'securerandom'
|
3
|
+
require 'notified_tail'
|
4
|
+
|
5
|
+
describe NotifiedTail do
|
6
|
+
describe '#tail' do
|
7
|
+
|
8
|
+
shared_examples 'tail -f' do
|
9
|
+
let!(:lines) { [] }
|
10
|
+
let!(:fn) { SecureRandom.uuid }
|
11
|
+
after(:each) { File.delete(fn) }
|
12
|
+
|
13
|
+
it 'tails a growing file, line by line' do
|
14
|
+
|
15
|
+
append(fn, "before\ntailing\n")
|
16
|
+
|
17
|
+
watcher = Thread.start do
|
18
|
+
notifier = NotifiedTail.new
|
19
|
+
notifier.tail(fn, seek_end: seek_end) do |line|
|
20
|
+
puts "saw #{line.inspect}"
|
21
|
+
expect(line).to eql expected.first
|
22
|
+
expected.shift
|
23
|
+
notifier.stop if expected.empty?
|
24
|
+
end
|
25
|
+
end
|
26
|
+
sleep(0.25) # let the notifier start up
|
27
|
+
|
28
|
+
append(fn, "one\n")
|
29
|
+
append(fn, "two\nthree\n")
|
30
|
+
append(fn, "gr")
|
31
|
+
append(fn, "ow")
|
32
|
+
append(fn, "ing\nlonger\n")
|
33
|
+
append(fn, "line with words\n")
|
34
|
+
watcher.join
|
35
|
+
end
|
36
|
+
|
37
|
+
def append(fn, text)
|
38
|
+
File.open(fn, 'a') { |f| f.print(text) }
|
39
|
+
sleep(0.1)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
shared_examples 'tail -f -n 9999PB' do
|
44
|
+
let!(:expected) { ['before', 'tailing', 'one', 'two', 'three', 'growing', 'longer', 'line with words'] }
|
45
|
+
it_behaves_like 'tail -f'
|
46
|
+
end
|
47
|
+
|
48
|
+
shared_examples 'tail -f -n 0' do
|
49
|
+
let!(:expected) { ['one', 'two', 'three', 'growing', 'longer', 'line with words'] }
|
50
|
+
it_behaves_like 'tail -f'
|
51
|
+
end
|
52
|
+
|
53
|
+
context 'when not seeking to the end' do
|
54
|
+
let!(:seek_end) { false }
|
55
|
+
it_behaves_like 'tail -f -n 9999PB'
|
56
|
+
end
|
57
|
+
|
58
|
+
context 'when the platform does not support notifications' do
|
59
|
+
before(:each) { allow(NotifiedTail).to receive(:get_ruby_platform).and_return('wha??') }
|
60
|
+
let!(:seek_end) { false }
|
61
|
+
it_behaves_like 'tail -f -n 9999PB'
|
62
|
+
end
|
63
|
+
|
64
|
+
context 'when seeking to the end' do
|
65
|
+
let!(:seek_end) { true }
|
66
|
+
it_behaves_like 'tail -f -n 0'
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
metadata
ADDED
@@ -0,0 +1,125 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: notified_tail
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Peter Winton
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-10-27 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.7'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.7'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.3'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.3'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rb-inotify
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rb-kqueue
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
description: Uses inotify/kqueue on supported platforms to avoid polling latency and
|
84
|
+
overhead
|
85
|
+
email:
|
86
|
+
- pwinton@indigobio.com
|
87
|
+
executables: []
|
88
|
+
extensions: []
|
89
|
+
extra_rdoc_files: []
|
90
|
+
files:
|
91
|
+
- ".gitignore"
|
92
|
+
- Gemfile
|
93
|
+
- LICENSE.txt
|
94
|
+
- README.md
|
95
|
+
- Rakefile
|
96
|
+
- lib/notified_tail.rb
|
97
|
+
- notified_tail.gemspec
|
98
|
+
- spec/lib/notified_tail_spec.rb
|
99
|
+
homepage: ''
|
100
|
+
licenses:
|
101
|
+
- MIT
|
102
|
+
metadata: {}
|
103
|
+
post_install_message:
|
104
|
+
rdoc_options: []
|
105
|
+
require_paths:
|
106
|
+
- lib
|
107
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
108
|
+
requirements:
|
109
|
+
- - ">="
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: '0'
|
112
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
113
|
+
requirements:
|
114
|
+
- - ">="
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: '0'
|
117
|
+
requirements: []
|
118
|
+
rubyforge_project:
|
119
|
+
rubygems_version: 2.4.5
|
120
|
+
signing_key:
|
121
|
+
specification_version: 4
|
122
|
+
summary: Low latency file tailing in ruby
|
123
|
+
test_files:
|
124
|
+
- spec/lib/notified_tail_spec.rb
|
125
|
+
has_rdoc:
|