aslakhellesoy-bcat 0.6.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.
@@ -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