aslakhellesoy-bcat 0.6.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,170 @@
1
+ class Bcat
2
+
3
+ # Converts ANSI color sequences to HTML.
4
+ #
5
+ # The ANSI module is based on code from the following libraries:
6
+ #
7
+ # ansi2html.sh:
8
+ # http://github.com/pixelb/scripts/blob/master/scripts/ansi2html.sh
9
+ #
10
+ # HTML::FromANSI:
11
+ # http://cpansearch.perl.org/src/NUFFIN/HTML-FromANSI-2.03/lib/HTML/FromANSI.pm
12
+ class ANSI
13
+ ESCAPE = "\x1b"
14
+
15
+ # Linux console palette
16
+ STYLES = {
17
+ 'ef0' => 'color:#000',
18
+ 'ef1' => 'color:#A00',
19
+ 'ef2' => 'color:#0A0',
20
+ 'ef3' => 'color:#A50',
21
+ 'ef4' => 'color:#00A',
22
+ 'ef5' => 'color:#A0A',
23
+ 'ef6' => 'color:#0AA',
24
+ 'ef7' => 'color:#AAA',
25
+ 'ef8' => 'color:#555',
26
+ 'ef9' => 'color:#F55',
27
+ 'ef10' => 'color:#5F5',
28
+ 'ef11' => 'color:#FF5',
29
+ 'ef12' => 'color:#55F',
30
+ 'ef13' => 'color:#F5F',
31
+ 'ef14' => 'color:#5FF',
32
+ 'ef15' => 'color:#FFF',
33
+ 'eb0' => 'background-color:#000',
34
+ 'eb1' => 'background-color:#A00',
35
+ 'eb2' => 'background-color:#0A0',
36
+ 'eb3' => 'background-color:#A50',
37
+ 'eb4' => 'background-color:#00A',
38
+ 'eb5' => 'background-color:#A0A',
39
+ 'eb6' => 'background-color:#0AA',
40
+ 'eb7' => 'background-color:#AAA',
41
+ 'eb8' => 'background-color:#555',
42
+ 'eb9' => 'background-color:#F55',
43
+ 'eb10' => 'background-color:#5F5',
44
+ 'eb11' => 'background-color:#FF5',
45
+ 'eb12' => 'background-color:#55F',
46
+ 'eb13' => 'background-color:#F5F',
47
+ 'eb14' => 'background-color:#5FF',
48
+ 'eb15' => 'background-color:#FFF'
49
+ }
50
+
51
+ ##
52
+ # The default xterm 256 colour palette
53
+
54
+ (0..5).each do |red|
55
+ (0..5).each do |green|
56
+ (0..5).each do |blue|
57
+ c = 16 + (red * 36) + (green * 6) + blue
58
+ r = red > 0 ? red * 40 + 55 : 0
59
+ g = green > 0 ? green * 40 + 55 : 0
60
+ b = blue > 0 ? blue * 40 + 55 : 0
61
+ STYLES["ef#{c}"] = "color:#%2.2x%2.2x%2.2x" % [r, g, b]
62
+ STYLES["eb#{c}"] = "background-color:#%2.2x%2.2x%2.2x" % [r, g, b]
63
+ end
64
+ end
65
+ end
66
+
67
+ (0..23).each do |gray|
68
+ c = gray+232
69
+ l = gray*10 + 8
70
+ STYLES["ef#{c}"] = "color:#%2.2x%2.2x%2.2x" % [l, l, l]
71
+ STYLES["eb#{c}"] = "background-color:#%2.2x%2.2x%2.2x" % [l, l, l]
72
+ end
73
+
74
+ def initialize(input)
75
+ @input =
76
+ if input.respond_to?(:to_str)
77
+ [input]
78
+ elsif !input.respond_to?(:each)
79
+ raise ArgumentError, "input must respond to each"
80
+ else
81
+ input
82
+ end
83
+ @stack = []
84
+ end
85
+
86
+ def to_html
87
+ buf = []
88
+ each { |chunk| buf << chunk }
89
+ buf.join
90
+ end
91
+
92
+ def each
93
+ buf = ''
94
+ @input.each do |chunk|
95
+ buf << chunk
96
+ tokenize(buf) do |tok, data|
97
+ case tok
98
+ when :text
99
+ yield data
100
+ when :display
101
+ case code = data
102
+ when 0 ; yield reset_styles if @stack.any?
103
+ when 1 ; yield push_tag("b") # bright
104
+ when 2 ; #dim
105
+ when 3, 4 ; yield push_tag("u")
106
+ when 5, 6 ; yield push_tag("blink")
107
+ when 7 ; #reverse
108
+ when 8 ; yield push_style("display:none")
109
+ when 9 ; yield push_tag("strike")
110
+ when 30..37 ; yield push_style("ef#{code - 30}")
111
+ when 40..47 ; yield push_style("eb#{code - 40}")
112
+ when 90..97 ; yield push_style("ef#{8 + code - 90}")
113
+ when 100..107 ; yield push_style("eb#{8 + code - 100}")
114
+ end
115
+ end
116
+ end
117
+ end
118
+ yield buf if !buf.empty?
119
+ yield reset_styles if @stack.any?
120
+ self
121
+ end
122
+
123
+ def push_tag(tag, style=nil)
124
+ style = STYLES[style] if style && !style.include?(':')
125
+ @stack.push tag
126
+ [ "<#{tag}",
127
+ (" style='#{style}'" if style),
128
+ ">"
129
+ ].join
130
+ end
131
+
132
+ def push_style(style)
133
+ push_tag "span", style
134
+ end
135
+
136
+ def reset_styles
137
+ stack, @stack = @stack, []
138
+ stack.reverse.map { |tag| "</#{tag}>" }.join
139
+ end
140
+
141
+ def tokenize(text)
142
+ tokens = [
143
+ # characters to remove completely
144
+ [/\A\x08+/, lambda { |m| '' }],
145
+
146
+ # ansi escape sequences that mess with the display
147
+ [/\A\x1b\[((?:\d{1,3};?)+|)m/, lambda { |m|
148
+ m = '0' if m.strip.empty?
149
+ m.chomp(';').split(';').
150
+ each { |code| yield :display, code.to_i };
151
+ '' }],
152
+
153
+ # malformed sequences
154
+ [/\A\x1b\[?[\d;]{0,3}/, lambda { |m| '' }],
155
+
156
+ # real text
157
+ [/\A([^\x1b\x08]+)/m, lambda { |m| yield :text, m; '' }]
158
+ ]
159
+
160
+ while (size = text.size) > 0
161
+ tokens.each do |pattern, sub|
162
+ while text.sub!(pattern) { sub.call($1) }
163
+ end
164
+ end
165
+ break if text.size == size
166
+ end
167
+ end
168
+
169
+ end
170
+ end
@@ -0,0 +1,68 @@
1
+ class Bcat
2
+ class Browser
3
+ ENVIRONMENT =
4
+ case `uname`
5
+ when /Darwin/ ; 'Darwin'
6
+ when /Linux/, /BSD/ ; 'X11'
7
+ else 'X11'
8
+ end
9
+
10
+ # browser name -> command mappings
11
+ COMMANDS = {
12
+ 'Darwin' => {
13
+ 'default' => "open",
14
+ 'safari' => "open -a Safari",
15
+ 'firefox' => "open -a Firefox",
16
+ 'chrome' => "open -a Google\\ Chrome",
17
+ 'chromium' => "open -a Chromium",
18
+ 'opera' => "open -a Opera",
19
+ 'curl' => "curl -s"
20
+ },
21
+
22
+ 'X11' => {
23
+ 'default' => "xdg-open",
24
+ 'firefox' => "firefox",
25
+ 'chrome' => "google-chrome",
26
+ 'chromium' => "chromium",
27
+ 'mozilla' => "mozilla",
28
+ 'epiphany' => "epiphany",
29
+ 'curl' => "curl -s"
30
+ }
31
+ }
32
+
33
+ # alternative names for browsers
34
+ ALIASES = {
35
+ 'google-chrome' => 'chrome',
36
+ 'google chrome' => 'chrome',
37
+ 'gnome' => 'epiphany'
38
+ }
39
+
40
+ def initialize(browser, command=ENV['BCAT_COMMAND'])
41
+ @browser = browser
42
+ @command = command
43
+ end
44
+
45
+ def open(url)
46
+ fork do
47
+ $stdin.close
48
+ exec "#{command} #{shell_quote(url)}"
49
+ exit! 128
50
+ end
51
+ end
52
+
53
+ def command
54
+ @command || browser_command
55
+ end
56
+
57
+ def browser_command(browser=@browser)
58
+ browser ||= 'default'
59
+ browser = browser.downcase
60
+ browser = ALIASES[browser] || browser
61
+ COMMANDS[ENVIRONMENT][browser]
62
+ end
63
+
64
+ def shell_quote(argument)
65
+ '"' + argument.to_s.gsub(/([\\"`$])/) { "\\" + $1 } + '"'
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,106 @@
1
+ class Bcat
2
+
3
+ # Parses HTML until the first displayable body character and provides methods
4
+ # for accessing head and body contents.
5
+ class HeadParser
6
+ attr_accessor :buf
7
+
8
+ def initialize
9
+ @buf = ''
10
+ @head = []
11
+ @body = nil
12
+ @html = nil
13
+ end
14
+
15
+ # Called to parse new data as it arrives.
16
+ def feed(data)
17
+ if complete?
18
+ @body << data
19
+ else
20
+ @buf << data
21
+ parse(@buf)
22
+ end
23
+ complete?
24
+ end
25
+
26
+ # Truthy once the first displayed character of the body has arrived.
27
+ def complete?
28
+ !@body.nil?
29
+ end
30
+
31
+ # Determine if the input is HTML. This is nil before the first non-whitespace
32
+ # character is received, true if the first non-whitespace character is a
33
+ # '<', and false if the first non-whitespace character is something other
34
+ # than '<'.
35
+ def html?
36
+ @html
37
+ end
38
+
39
+ # The head contents without any DOCTYPE, <html>, or <head> tags. This should
40
+ # consist of only <style>, <script>, <link>, <meta>, <base>, and <title> tags.
41
+ def head
42
+ @head.join.gsub(/<\/?(?:html|head|!DOCTYPE).*?>/mi, '')
43
+ end
44
+
45
+ # The current body contents. The <body> tag is guaranteed to be present. If
46
+ # a <body> was included in the input, it's preserved with original
47
+ # attributes; otherwise, a <body> tag is inserted. The inject argument can
48
+ # be used to insert a string as the immediate descendant of the <body> tag.
49
+ def body(inject=nil)
50
+ if @body =~ /\A\s*(<body.*?>)(.*)/mi
51
+ [$1, inject, $2].compact.join("\n")
52
+ else
53
+ ["<body>", inject, @body].compact.join("\n")
54
+ end
55
+ end
56
+
57
+ HEAD_TOKS = [
58
+ /\A(<!DOCTYPE.*?>)/m,
59
+ /\A(<title.*?>.*?<\/title>)/mi,
60
+ /\A(<script.*?>.*?<\/script>)/mi,
61
+ /\A(<style.*?>.*?<\/style>)/mi,
62
+ /\A(<(?:html|head|meta|link|base).*?>)/mi,
63
+ /\A(<\/(?:html|head|meta|link|base|script|style|title)>)/mi,
64
+ /\A(<!--(.*?)-->)/m
65
+ ]
66
+
67
+ BODY_TOKS = [
68
+ /\A[^<]/,
69
+ /\A<(?!html|head|meta|link|base|script|style|title).*?>/
70
+ ]
71
+
72
+ # Parses buf into head and body parts. Basic approach is to eat anything
73
+ # possibly body related until we hit text or a body element.
74
+ def parse(buf=@buf)
75
+ if @html.nil?
76
+ if buf =~ /\A\s*[<]/m
77
+ @html = true
78
+ elsif buf =~ /\A\s*[^<]/m
79
+ @html = false
80
+ end
81
+ end
82
+
83
+ while !buf.empty?
84
+ buf.sub!(/\A(\s+)/m) { @head << $1 ; '' }
85
+ matched =
86
+ HEAD_TOKS.any? do |tok|
87
+ buf.sub!(tok) do
88
+ @head << $1
89
+ ''
90
+ end
91
+ end
92
+ break unless matched
93
+ end
94
+
95
+
96
+ if buf.empty?
97
+ buf
98
+ elsif BODY_TOKS.any? { |tok| buf =~ tok }
99
+ @body = buf
100
+ nil
101
+ else
102
+ buf
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,102 @@
1
+ require 'rack/utils'
2
+
3
+ class Bcat
4
+ # ARGF style multi-file streaming interface. Input is read with IO#readpartial
5
+ # to avoid buffering.
6
+ class Reader
7
+ attr_reader :is_command
8
+ attr_reader :args
9
+ attr_reader :fds
10
+
11
+ def initialize(is_command, args=[])
12
+ @is_command = is_command
13
+ @args = args
14
+ end
15
+
16
+ def open
17
+ @fds = is_command ? open_command : open_files
18
+ @buf = []
19
+ end
20
+
21
+ def open_command
22
+ [IO.popen(args.join(' '), 'rb')]
23
+ end
24
+
25
+ def open_files
26
+ args.map do |f|
27
+ if f == '-'
28
+ $stdin
29
+ else
30
+ File.open(f, 'rb')
31
+ end
32
+ end
33
+ end
34
+
35
+ def each
36
+ yield @buf.shift while @buf.any?
37
+ while fd = fds.first
38
+ fds.shift and next if fd.closed?
39
+ fd.sync = true
40
+ begin
41
+ while buf = fd.readpartial(4096)
42
+ yield buf
43
+ end
44
+ rescue EOFError
45
+ fd.close
46
+ end
47
+ fds.shift
48
+ end
49
+ end
50
+
51
+ def sniff
52
+ @format ||=
53
+ catch :detect do
54
+ each do |chunk|
55
+ @buf << chunk
56
+ case chunk
57
+ when /\A\s*</m
58
+ throw :detect, 'html'
59
+ when /\A\s*[^<]/m
60
+ throw :detect, 'text'
61
+ end
62
+ end
63
+ throw :detect, 'text'
64
+ end
65
+ end
66
+ end
67
+
68
+ # Like Reader but writes all input to an output IO object in addition to
69
+ # yielding to the block.
70
+ class TeeFilter
71
+ def initialize(source, out=$stdout)
72
+ @source = source
73
+ @out = out
74
+ end
75
+
76
+ def each
77
+ @source.each do |chunk|
78
+ yield chunk
79
+ @out.write chunk
80
+ end
81
+ end
82
+ end
83
+
84
+ class TextFilter
85
+ include Rack::Utils
86
+
87
+ def initialize(source, force=false)
88
+ @source = source
89
+ @force = force
90
+ end
91
+
92
+ def each
93
+ yield "<pre>"
94
+ @source.each do |chunk|
95
+ chunk = escape_html(chunk)
96
+ chunk = "<span>#{chunk}</span>" if !chunk.gsub!(/\n/, "<br>")
97
+ yield chunk
98
+ end
99
+ yield "</pre>"
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,113 @@
1
+ require 'socket'
2
+ require 'stringio'
3
+ require 'rack/utils'
4
+
5
+ class Bcat
6
+ # Simple Rack handler based largely on Scott Chacon's kidgloves library:
7
+ # http://github.com/schacon/kidgloves
8
+ class Server
9
+ attr_accessor :app
10
+
11
+ def self.run(app, options={}, &block)
12
+ new(app, options).listen(&block)
13
+ end
14
+
15
+ def initialize(app, options={})
16
+ @app = app
17
+ @host = options[:Host] || '0.0.0.0'
18
+ @port = options[:Port] || 8089
19
+ end
20
+
21
+ def bind(host, port)
22
+ TCPServer.new(host, port)
23
+ rescue Errno::EADDRINUSE
24
+ port += 1
25
+ retry
26
+ end
27
+
28
+ def listen
29
+ server = TCPServer.new(@host, @port)
30
+
31
+ yield server if block_given?
32
+
33
+ loop do
34
+ socket = server.accept
35
+ socket.sync = true
36
+ log "#{socket.peeraddr[2]} (#{socket.peeraddr[3]})"
37
+ begin
38
+ req = {}
39
+
40
+ # parse the request line
41
+ request = socket.gets
42
+ method, path, version = request.split(" ", 3)
43
+ req["REQUEST_METHOD"] = method
44
+ info, query = path.split("?")
45
+ req["PATH_INFO"] = info
46
+ req["QUERY_STRING"] = query
47
+
48
+ # parse the headers
49
+ while (line = socket.gets)
50
+ line.strip!
51
+ break if line.size == 0
52
+ key, val = line.split(": ")
53
+ key = key.upcase.gsub('-', '_')
54
+ key = "HTTP_#{key}" if !%w[CONTENT_TYPE CONTENT_LENGTH].include?(key)
55
+ req[key] = val
56
+ end
57
+
58
+ # parse the body
59
+ body =
60
+ if len = req['CONTENT_LENGTH']
61
+ socket.read(len.to_i)
62
+ else
63
+ ''
64
+ end
65
+
66
+ # process the request
67
+ process_request(req, body, socket)
68
+ ensure
69
+ socket.close if not socket.closed?
70
+ end
71
+ end
72
+ end
73
+
74
+ def log(message)
75
+ # $stderr.puts message
76
+ end
77
+
78
+ def status_message(code)
79
+ Rack::Utils::HTTP_STATUS_CODES[code]
80
+ end
81
+
82
+ def process_request(request, input_body, socket)
83
+ env = {}.replace(request)
84
+ env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"]
85
+ env["QUERY_STRING"] ||= ""
86
+ env["SCRIPT_NAME"] = ""
87
+
88
+ rack_input = StringIO.new(input_body)
89
+ rack_input.set_encoding(Encoding::BINARY) if rack_input.respond_to?(:set_encoding)
90
+
91
+ env.update(
92
+ "rack.version" => [1,0],
93
+ "rack.input" => rack_input,
94
+ "rack.errors" => $stderr,
95
+ "rack.multithread" => true,
96
+ "rack.multiprocess" => true,
97
+ "rack.run_once" => false,
98
+ "rack.url_scheme" => ["yes", "on", "1"].include?(env["HTTPS"]) ? "https" : "http"
99
+ )
100
+ status, headers, body = app.call(env)
101
+ begin
102
+ socket.write("HTTP/1.1 #{status} #{status_message(status)}\r\n")
103
+ headers.each do |k, vs|
104
+ vs.split("\n").each { |v| socket.write("#{k}: #{v}\r\n")}
105
+ end
106
+ socket.write("\r\n")
107
+ body.each { |s| socket.write(s) }
108
+ ensure
109
+ body.close if body.respond_to? :close
110
+ end
111
+ end
112
+ end
113
+ end