wtails 0.0.1

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