wtails 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/README.md ADDED
@@ -0,0 +1,52 @@
1
+ wtails
2
+ ======
3
+
4
+ Wtails is a web server acts like 'tails -f', inspired by webtail( https://github.com/r7kamura/webtail ).
5
+
6
+ You can watch multiple files with slider UI.
7
+
8
+ Wtails can serve files without launching browser(--serve), this option is usable for watching remote files.
9
+
10
+ usage
11
+ =====
12
+
13
+ % wtails foo.log bar.log baz.log - --serve
14
+
15
+ then web server started, and you can now browse them at 'http://localhost:9999'.
16
+
17
+ screenshot
18
+ ==========
19
+
20
+ single view
21
+ -----------
22
+ ![single view](https://raw.github.com/jonigata/wtails/master/doc/img/single_view.png)
23
+
24
+ dual (upper/lower) view
25
+ -----------------------
26
+ ![dual view](https://raw.github.com/jonigata/wtails/master/doc/img/dual_view.png)
27
+
28
+ .wtailsrc
29
+ =========
30
+
31
+ Semantics of .wtailsrc is different from webtail. You should modify 'line' local variable.
32
+
33
+ example:
34
+
35
+ ```
36
+ $ cat ~/.webtailrc
37
+ var text = line.text();
38
+
39
+ if (text == '\n') {
40
+ line.css({
41
+ margin: '3em 0',
42
+ height: 1,
43
+ background: 'lime'
44
+ });
45
+ }
46
+
47
+ if (text.match(/require|opt/)) {
48
+ line.css({
49
+ color: '#E1017B'
50
+ });
51
+ }
52
+ ```
data/bin/wtails ADDED
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'trollop'
5
+
6
+ opts = Trollop::options do
7
+ version "wtails 0.1 (c) 2012 Naoyuki Hirayama"
8
+ banner <<-EOS
9
+ Wtails is a web server acts like 'tail -f'.
10
+
11
+ Usage:
12
+ wtails [options] <filename>+
13
+
14
+ EOS
15
+
16
+ opt :port, "Port number for http server", :default => 9999
17
+ opt :rc, 'Callback file location', :default => "~/.wtailsrc"
18
+ opt :serve, "Just serve, don't open browser", :default => false
19
+ end
20
+
21
+ Trollop::die "need at least one filename" if ARGV.empty?
22
+
23
+ $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
24
+ require "wtails"
25
+ begin
26
+ Wtails.run(opts.to_hash, ARGV)
27
+ rescue Errno::ENOENT => e
28
+ puts e
29
+ end
Binary file
Binary file
data/lib/wtails.rb ADDED
@@ -0,0 +1,54 @@
1
+ require 'uri'
2
+
3
+ require 'rubygems'
4
+ require "eventmachine-tail"
5
+ require 'websocket-eventmachine-server'
6
+
7
+ require "sinatra/base"
8
+ require "launchy"
9
+
10
+ require "wtails/version"
11
+ require "wtails/web_server"
12
+ require "wtails/web_socket"
13
+ require "wtails/tail"
14
+ require "wtails/stdin"
15
+ #require "wtails/browser"
16
+
17
+ module Wtails
18
+ extend self
19
+
20
+ def run(opts, files)
21
+ configure(opts)
22
+
23
+ Thread.abort_on_exception = true
24
+ EM.run do
25
+ EM.defer { WebSocket.run(opts, files) }
26
+ EM.defer { WebServer.run(opts, files) }
27
+ #EM.defer { Browser.run }
28
+ Tail.run(opts, files)
29
+ end
30
+ end
31
+
32
+ def channel(path)
33
+ @channel ||= {}
34
+ @channel[path] ||= EM::Channel.new
35
+ end
36
+
37
+ def config
38
+ @config
39
+ end
40
+
41
+ def http_server
42
+ @http_server
43
+ end
44
+
45
+ def http_server=(http_server)
46
+ @http_server = http_server
47
+ end
48
+
49
+ private
50
+
51
+ def configure(args)
52
+ @config = args
53
+ end
54
+ end
@@ -0,0 +1,21 @@
1
+ module Wtails
2
+ module Browser
3
+ extend self
4
+
5
+ def run
6
+ if Wtails.config[:serve]
7
+ puts "serve on http://localhost:#{Wtails.config[:port]}"
8
+ else
9
+ # ugly, but there is nothing for it but to poll
10
+ # because thin has no start callback
11
+ while true
12
+ if Wtails.http_server && Wtails.http_server.running?
13
+ break
14
+ end
15
+ sleep(0.1)
16
+ end
17
+ ::Launchy.open("http://localhost:#{Wtails.config[:port]}") rescue nil
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,28 @@
1
+ module Wtails
2
+ module Stdin
3
+ extend self
4
+
5
+ ENTITY_MAP = {
6
+ "&lt;" => "<",
7
+ "&gt;" => ">",
8
+ }
9
+
10
+ def run
11
+ STDIN.each do |line|
12
+ line = unescape_entity(line)
13
+ line = strip_ansi_sequence(line)
14
+ Wtails.channel('-') << line
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ def strip_ansi_sequence(str)
21
+ str.gsub(/\e\[.*?m/, "")
22
+ end
23
+
24
+ def unescape_entity(str)
25
+ str.gsub(/#{Regexp.union(ENTITY_MAP.keys)}/o) {|key| ENTITY_MAP[key] }
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,33 @@
1
+ module Wtails
2
+ module Tail
3
+ extend self
4
+
5
+ ENTITY_MAP = {
6
+ "&lt;" => "<",
7
+ "&gt;" => ">",
8
+ }
9
+
10
+ def run(opts, files)
11
+ files.each do |path|
12
+ if path == '-'
13
+ EM.defer { Wtails::Stdin.run }
14
+ else
15
+ EventMachine::file_tail(path, Reader)
16
+ end
17
+ end
18
+ end
19
+
20
+ class Reader < EventMachine::FileTail
21
+ def initialize(path, startpos=-1)
22
+ super(path, startpos)
23
+ @buffer = BufferedTokenizer.new
24
+ end
25
+
26
+ def receive_data(data)
27
+ @buffer.extract(data).each do |line|
28
+ Wtails.channel(path) << line
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,3 @@
1
+ module Wtails
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,48 @@
1
+ module Wtails
2
+ module WebServer
3
+ extend self
4
+
5
+ def run(opts, files)
6
+ ::Rack::Handler::Thin.run(
7
+ Server.new,
8
+ :Port => opts[:port]
9
+ ) do |http_server|
10
+ Wtails.http_server = http_server
11
+ end
12
+ end
13
+
14
+ class Server < ::Sinatra::Base
15
+ set :wtailsrc do
16
+ path = File.expand_path(Wtails.config[:rc])
17
+ File.exist?(path) && File.read(path)
18
+ end
19
+ set :views, File.expand_path("../../../views/", __FILE__)
20
+ set :public, File.expand_path("../../../public/", __FILE__)
21
+
22
+ get "/" do
23
+ redirect "/single"
24
+ end
25
+
26
+ get "/single" do
27
+ erb :single, :locals => make_local_variables
28
+ end
29
+
30
+ get "/dual" do
31
+ erb :dual, :locals => make_local_variables
32
+ end
33
+
34
+ get "/test" do
35
+ erb :test
36
+ end
37
+
38
+ def make_local_variables
39
+ {
40
+ :files => WebSocket.servers.map { |s| [s.file, s.port] },
41
+ :wtailsrc => settings.wtailsrc,
42
+ :host => URI.parse(request.url).host,
43
+ }
44
+ end
45
+
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,74 @@
1
+ module Wtails
2
+ module WebSocket
3
+ extend self
4
+
5
+ LOG_SIZE = 100
6
+
7
+ def run(opts, files)
8
+ port = opts[:port]
9
+ @servers = files.map do |file|
10
+ port += 1
11
+ Server.new(file, port)
12
+ end
13
+ end
14
+
15
+ def servers
16
+ @servers ||= []
17
+ end
18
+
19
+ class Server
20
+ def initialize(file, port)
21
+ @file = file
22
+ @port = port
23
+ @logs = []
24
+ @channel = Wtails.channel(file)
25
+ @channel.subscribe do |msg|
26
+ @logs << msg
27
+ @logs.shift if @logs.size > LOG_SIZE
28
+ end
29
+
30
+ opts = {:host => "127.0.0.1", :port => port}
31
+ s = ::WebSocket::EventMachine::Server.start(opts) do |socket|
32
+ socket.onopen(&onopen(socket))
33
+ socket.onmessage(&onmessage)
34
+ socket.onerror(&onerror)
35
+ end
36
+
37
+ def onopen(socket)
38
+ proc do
39
+ send_message = proc do |message|
40
+ next unless message
41
+ str = message.respond_to?(:force_encoding) ?
42
+ message.force_encoding("UTF-8") :
43
+ message
44
+
45
+ socket.send(str)
46
+ end
47
+
48
+ @logs.each(&send_message)
49
+ id = @channel.subscribe(&send_message)
50
+
51
+ socket.onclose do
52
+ @channel.unsubscribe(id)
53
+ end
54
+ end
55
+ end
56
+
57
+ def onmessage
58
+ proc do |message|
59
+ @channel << message
60
+ end
61
+ end
62
+
63
+ def onerror
64
+ proc do |error|
65
+ puts error
66
+ end
67
+ end
68
+ end
69
+
70
+ attr_reader :port
71
+ attr_reader :file
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,166 @@
1
+ html {
2
+ width: 100%;
3
+ height: 100%;
4
+ }
5
+
6
+ body {
7
+ background: #444;
8
+ margin: 0;
9
+ padding: 0 0 20px 0;
10
+ width: 100%;
11
+ height: 100%;
12
+
13
+ box-sizing: border-box;
14
+ -moz-box-sizing: border-box;
15
+ -webkit-box-sizing: border-box;
16
+ -o-box-sizing: border-box;
17
+ -ms-box-sizing: border-box;
18
+ }
19
+
20
+ header {
21
+ overflow: visible;
22
+ height: 0;
23
+ }
24
+
25
+ header div {
26
+ background: #888;
27
+ padding: 2px 8px 4px 8px;
28
+ }
29
+
30
+ header div a {
31
+ color: #eee;
32
+ }
33
+
34
+ .main-view {
35
+ height: 100%;
36
+ padding-top: 32px;
37
+
38
+ box-sizing: border-box;
39
+ -moz-box-sizing: border-box;
40
+ -webkit-box-sizing: border-box;
41
+ -o-box-sizing: border-box;
42
+ -ms-box-sizing: border-box;
43
+ }
44
+
45
+ .pane-full {
46
+ width: 100%;
47
+ height: 100%;
48
+ position: relative;
49
+ }
50
+
51
+ .pane-half {
52
+ width: 100%;
53
+ height: 50%;
54
+ position: relative;
55
+ }
56
+
57
+ .slider {
58
+ overflow: hidden;
59
+ position: relative;
60
+ width: 100%;
61
+ height: 100%;
62
+ }
63
+ .slider ul {
64
+ margin: 0 auto;
65
+ padding: 0;
66
+ height: 100%;
67
+ }
68
+
69
+ .slider li {
70
+ float: left;
71
+ list-style:none;
72
+ margin:0 16px 0 0;
73
+ height: 100%;
74
+ }
75
+
76
+ .terminal-title {
77
+ color: #ddd;
78
+ height: 0px;
79
+ overflow: visible;
80
+ text-align: center;
81
+ }
82
+
83
+ .terminal-place {
84
+ height: 100%;
85
+ padding-top: 18px;
86
+
87
+ box-sizing: border-box;
88
+ -moz-box-sizing: border-box;
89
+ -webkit-box-sizing: border-box;
90
+ -o-box-sizing: border-box;
91
+ -ms-box-sizing: border-box;
92
+ }
93
+
94
+ .terminal-frame {
95
+ overflow: hidden;
96
+ width: 100%;
97
+ height: 100%;
98
+ border: 1px solid #EEE;
99
+ background: #111;
100
+ padding: 12px;
101
+
102
+ box-sizing: border-box;
103
+ -moz-box-sizing: border-box;
104
+ -webkit-box-sizing: border-box;
105
+ -o-box-sizing: border-box;
106
+ -ms-box-sizing: border-box;
107
+ }
108
+
109
+ .terminal {
110
+ color: #ddd;
111
+ }
112
+
113
+ .controls {
114
+ position: absolute;
115
+ width: 100%;
116
+ height: 100%;
117
+ top: 0;
118
+ left: 0;
119
+ }
120
+
121
+ .floating-button {
122
+ position: absolute;
123
+ border-radius: 5px;
124
+ -webkit-border-radius: 5px;
125
+ -moz-border-radius: 5px;
126
+ background: rgba(255, 255, 255, 0.2);
127
+ color: #fff;
128
+ text-decoration: none;
129
+ width: 48px;
130
+ height: 48px;
131
+ }
132
+
133
+ .floating-button > span {
134
+ width: 48px;
135
+ height: 48px;
136
+ display: table-cell;
137
+ vertical-align: middle;
138
+ text-align: center;
139
+ }
140
+
141
+ .prev-slide {
142
+ top: 45%;
143
+ left: -8px;
144
+ }
145
+
146
+ .next-slide {
147
+ top: 45%;
148
+ right: -8px;
149
+ }
150
+
151
+ .left-button {
152
+ width: 16px;
153
+ height: 24px;
154
+ }
155
+
156
+ .right-button {
157
+ width: 16px;
158
+ height: 24px;
159
+ }
160
+
161
+ pre {
162
+ margin: 0;
163
+ padding: 0;
164
+ font-family: "Monaco", "Consolas", monospace;
165
+ }
166
+