notified_tail 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,15 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
15
+ .idea
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in notified_tail.gemspec
4
+ gemspec
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,9 @@
1
+ begin
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
7
+ rescue LoadError
8
+ # no rspec available
9
+ end
@@ -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: