bcat 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CONTRIBUTING +5 -0
- data/COPYING +19 -0
- data/INSTALLING +5 -0
- data/README +28 -2
- data/Rakefile +22 -4
- data/bcat.gemspec +7 -2
- data/bin/bcat +26 -24
- data/lib/bcat.rb +55 -32
- data/lib/bcat/html.rb +106 -0
- data/lib/bcat/reader.rb +45 -6
- data/man/bcat.1.ronn +18 -8
- data/man/btee.1.ronn +18 -8
- data/test/test_bcat_head_parser.rb +56 -0
- metadata +14 -26
data/CONTRIBUTING
ADDED
data/COPYING
CHANGED
@@ -0,0 +1,19 @@
|
|
1
|
+
bcat
|
2
|
+
Copyright (c) 2010 Ryan Tomayko <http://tomayko.com/about>
|
3
|
+
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
5
|
+
of this software and associated documentation files (the "Software"), to
|
6
|
+
deal in the Software without restriction, including without limitation the
|
7
|
+
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
8
|
+
sell copies of the Software, and to permit persons to whom the Software is
|
9
|
+
furnished to do so, subject to the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be included in
|
12
|
+
all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
15
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
16
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
17
|
+
THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
18
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
19
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/INSTALLING
ADDED
data/README
CHANGED
@@ -1,7 +1,17 @@
|
|
1
|
-
bcat
|
1
|
+
bcat
|
2
|
+
http://github.com/rtomayko/bcat
|
3
|
+
git clone git://github.com/rtomayko/bcat.git
|
4
|
+
gem install bcat
|
5
|
+
|
6
|
+
bcat is a pipe to browser utility. It reads from standard input and displays
|
7
|
+
what it reads in a web browser:
|
2
8
|
|
3
9
|
$ echo "hi mom" |bcat
|
4
|
-
$ echo "hi mom" |bcat -t '
|
10
|
+
$ echo "hi mom" |bcat -t 'Important Message'
|
11
|
+
|
12
|
+
bcat assumes its input is plain text, but you can also pipe in HTML:
|
13
|
+
|
14
|
+
$ echo "<h1>hi mom</h1>" |bcat -h
|
5
15
|
$ echo "*hi mom*" |markdown |bcat -h
|
6
16
|
|
7
17
|
Browser output is displayed progressively as it's read from standard input,
|
@@ -11,3 +21,19 @@ generate output over longer periods of time:
|
|
11
21
|
$ make all |bcat
|
12
22
|
$ rake test |bcat
|
13
23
|
$ tail -f /var/log/syslog |bcat
|
24
|
+
$ (while printf .; do sleep 1; done) |bcat
|
25
|
+
|
26
|
+
See the bcat(1) manual for detailed command usage.
|
27
|
+
|
28
|
+
bcat is known to work under MacOS X, Linux, and FreeBSD (other UNIX-like
|
29
|
+
environments with freedesktop.org integration should work fine too). Progressive
|
30
|
+
output has been tested under Safari, Firefox, Chrome, and GNOME Epiphany.
|
31
|
+
|
32
|
+
See the INSTALLING, COPYING, and CONTRIBUTING files for information on those
|
33
|
+
things.
|
34
|
+
|
35
|
+
bcat was inspired by the HTML output capabilities included in TextMate
|
36
|
+
and a desire to have those capabilities from the shell and within editors
|
37
|
+
like Vim.
|
38
|
+
<http://manual.macromates.com/en/commands#html_output>
|
39
|
+
<http://blog.macromates.com/2005/html-output-for-commands/>
|
data/Rakefile
CHANGED
@@ -1,4 +1,26 @@
|
|
1
1
|
require 'date'
|
2
|
+
task :default => :test
|
3
|
+
|
4
|
+
ROOTDIR = File.expand_path('..', __FILE__).sub(/#{Dir.pwd}(?=\/)/, '.')
|
5
|
+
LIBDIR = "#{ROOTDIR}/lib"
|
6
|
+
BINDIR = "#{ROOTDIR}/bin"
|
7
|
+
|
8
|
+
task :environment do
|
9
|
+
$:.unshift ROOTDIR if !$:.include?(ROOTDIR)
|
10
|
+
$:.unshift LIBDIR if !$:.include?(LIBDIR)
|
11
|
+
ENV['RUBYLIB'] = $LOAD_PATH.join(':')
|
12
|
+
ENV['PATH'] = "#{BINDIR}:#{ENV['PATH']}"
|
13
|
+
end
|
14
|
+
|
15
|
+
desc 'Run tests'
|
16
|
+
task :test => :environment do
|
17
|
+
$LOAD_PATH.unshift "#{ROOTDIR}/test"
|
18
|
+
Dir['test/test_*.rb'].each { |f| require(f) }
|
19
|
+
end
|
20
|
+
|
21
|
+
def source_version
|
22
|
+
@source_version ||= `ruby -Ilib -rbcat -e 'puts Bcat::VERSION'`.chomp
|
23
|
+
end
|
2
24
|
|
3
25
|
require 'rubygems'
|
4
26
|
$spec = eval(File.read('bcat.gemspec'))
|
@@ -15,10 +37,6 @@ task :man do
|
|
15
37
|
sh "ronn -w -r5 man/*.ronn"
|
16
38
|
end
|
17
39
|
|
18
|
-
def source_version
|
19
|
-
@source_version ||= `ruby -Ilib -rbcat -e 'puts Bcat::VERSION'`.chomp
|
20
|
-
end
|
21
|
-
|
22
40
|
file 'bcat.gemspec' => FileList['{lib,test,bin}/**','Rakefile'] do |f|
|
23
41
|
# read spec file and split out manifest section
|
24
42
|
spec = File.read(f.name)
|
data/bcat.gemspec
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = 'bcat'
|
3
|
-
s.version = '0.
|
4
|
-
s.date = '2010-06-
|
3
|
+
s.version = '0.3.0'
|
4
|
+
s.date = '2010-06-21'
|
5
5
|
|
6
6
|
s.summary = "browser cat"
|
7
7
|
s.description =
|
@@ -13,6 +13,9 @@ Gem::Specification.new do |s|
|
|
13
13
|
|
14
14
|
# = MANIFEST =
|
15
15
|
s.files = %w[
|
16
|
+
CONTRIBUTING
|
17
|
+
COPYING
|
18
|
+
INSTALLING
|
16
19
|
README
|
17
20
|
Rakefile
|
18
21
|
bcat.gemspec
|
@@ -21,10 +24,12 @@ Gem::Specification.new do |s|
|
|
21
24
|
bin/btee
|
22
25
|
lib/bcat.rb
|
23
26
|
lib/bcat/browser.rb
|
27
|
+
lib/bcat/html.rb
|
24
28
|
lib/bcat/kidgloves.rb
|
25
29
|
lib/bcat/reader.rb
|
26
30
|
man/bcat.1.ronn
|
27
31
|
man/btee.1.ronn
|
32
|
+
test/test_bcat_head_parser.rb
|
28
33
|
]
|
29
34
|
# = MANIFEST =
|
30
35
|
|
data/bin/bcat
CHANGED
@@ -1,32 +1,41 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
-
#/ Usage: bcat [-
|
2
|
+
#/ Usage: bcat [-ht] [-m <module>] [-T <title>] [<file>]...
|
3
3
|
#/ btee <options> [<file>]...
|
4
|
-
#/ Read standard input,
|
5
|
-
#/
|
4
|
+
#/ Pipe to browser utility. Read standard input, possibly one or more <file>s,
|
5
|
+
#/ and write concatenated / formatted output to browser. When invoked as btee,
|
6
|
+
#/ also write all input back to standard output.
|
6
7
|
#/
|
7
|
-
#/
|
8
|
-
#/ -h, --html
|
9
|
-
#/ -t, --
|
10
|
-
#/
|
8
|
+
#/ Input format (auto detected by default):
|
9
|
+
#/ -h, --html input is already HTML encoded, perhaps a whole document
|
10
|
+
#/ -t, --text input is unencoded text
|
11
|
+
#/
|
12
|
+
#/ Tweak output with these options:
|
13
|
+
#/ -T, --title=<text> use <text> as the page title
|
14
|
+
#/ -m, --module=<m1>[,<m2>]...
|
15
|
+
#/ inject formatting module(s)
|
16
|
+
#/
|
17
|
+
#/ -d, --debug enable verbose debug logging on stderr
|
11
18
|
require 'optparse'
|
12
19
|
|
13
20
|
options = {
|
14
|
-
:
|
15
|
-
:title
|
16
|
-
:Host
|
17
|
-
:Port
|
18
|
-
:debug
|
21
|
+
:format => nil,
|
22
|
+
:title => Dir.pwd,
|
23
|
+
:Host => '127.0.0.1',
|
24
|
+
:Port => 8091,
|
25
|
+
:debug => false,
|
26
|
+
:tee => !!($0 =~ /tee$/)
|
19
27
|
}
|
20
28
|
|
21
29
|
(class <<self;self;end).send(:define_method, :notice) { |message|
|
22
30
|
warn "#{File.basename($0)}: #{message}" if options[:debug] }
|
23
31
|
|
24
32
|
ARGV.options do |argv|
|
25
|
-
argv.on('-h', '--html') { options[:
|
33
|
+
argv.on('-h', '--html') { options[:format] = 'html' }
|
34
|
+
argv.on('-t', '--text') { options[:format] = 'text' }
|
26
35
|
argv.on('-a', '--app=v') { |app| ENV['BCAT_APPLICATION'] = app }
|
27
|
-
argv.on('-
|
36
|
+
argv.on('-T', '--title=v') { |text| options[:title] = text }
|
28
37
|
argv.on('-d', '--debug') { options[:debug] = true }
|
29
|
-
argv.on_tail('--help')
|
38
|
+
argv.on_tail('--help') { exec "grep ^#/ <#{__FILE__} | cut -c4-" }
|
30
39
|
argv.parse!
|
31
40
|
end
|
32
41
|
ARGV.push '-' if ARGV.empty?
|
@@ -38,17 +47,10 @@ include Bcat::Browser
|
|
38
47
|
notice "env BCAT_APPLICATION=#{ENV['BCAT_APPLICATION'].inspect}"
|
39
48
|
notice "env BCAT_COMMAND=#{browser_command.inspect}"
|
40
49
|
|
41
|
-
reader =
|
42
|
-
if File.basename($0) =~ /tee$/
|
43
|
-
Bcat::TeeReader.new(ARGV, $stdout)
|
44
|
-
else
|
45
|
-
Bcat::Reader.new(ARGV)
|
46
|
-
end
|
47
|
-
|
48
50
|
notice "starting server"
|
49
51
|
pid = nil
|
50
52
|
begin
|
51
|
-
bcat = Bcat.new(
|
53
|
+
bcat = Bcat.new(ARGV, options)
|
52
54
|
bcat.serve! do |sock|
|
53
55
|
url = "http://#{bcat[:Host]}:#{bcat[:Port]}/#{File.basename(Dir.pwd)}"
|
54
56
|
pid = browser(url)
|
@@ -59,5 +61,5 @@ end
|
|
59
61
|
|
60
62
|
Process.wait(pid) if pid
|
61
63
|
status = $?
|
62
|
-
notice "
|
64
|
+
notice "browser exited with #{status}"
|
63
65
|
exit status
|
data/lib/bcat.rb
CHANGED
@@ -1,49 +1,84 @@
|
|
1
1
|
require 'rack'
|
2
2
|
require 'bcat/reader'
|
3
|
+
require 'bcat/html'
|
3
4
|
require 'bcat/kidgloves'
|
4
5
|
require 'bcat/browser'
|
5
6
|
|
6
7
|
class Bcat
|
7
|
-
VERSION = '0.
|
8
|
+
VERSION = '0.3.0'
|
8
9
|
include Rack::Utils
|
9
10
|
|
10
|
-
|
11
|
-
|
11
|
+
attr_reader :format
|
12
|
+
|
13
|
+
def initialize(files=[], config={})
|
12
14
|
@config = {:Host => '127.0.0.1', :Port => 8091}.merge(config)
|
15
|
+
@reader = Bcat::Reader.new(files)
|
16
|
+
@format = @config[:format] || @reader.sniff
|
17
|
+
|
18
|
+
@filter = @reader
|
19
|
+
@filter = TeeFilter.new(@filter) if @config[:tee]
|
20
|
+
@filter = TextFilter.new(@filter) if @format == 'text'
|
13
21
|
end
|
14
22
|
|
15
23
|
def [](key)
|
16
24
|
@config[key]
|
17
25
|
end
|
18
26
|
|
27
|
+
def to_app
|
28
|
+
app = self
|
29
|
+
Rack::Builder.new do
|
30
|
+
use Rack::Chunked
|
31
|
+
run app
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def serve!(&bk)
|
36
|
+
Rack::Handler::KidGloves.run to_app, @config, &bk
|
37
|
+
end
|
38
|
+
|
19
39
|
def call(env)
|
20
40
|
notice "#{env['REQUEST_METHOD']} #{env['PATH_INFO'].inspect}"
|
21
41
|
[200, {"Content-Type" => "text/html;charset=utf-8"}, self]
|
22
42
|
end
|
23
43
|
|
24
44
|
def each
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
45
|
+
head_parser = Bcat::HeadParser.new
|
46
|
+
|
47
|
+
@filter.each do |buf|
|
48
|
+
if head_parser.nil?
|
49
|
+
yield buf
|
50
|
+
elsif head_parser.feed(buf)
|
51
|
+
yield content_for_head(inject=head_parser.head)
|
52
|
+
yield head_parser.body
|
53
|
+
head_parser = nil
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
if head_parser
|
58
|
+
yield content_for_head(inject=head_parser.head) +
|
59
|
+
head_parser.body
|
37
60
|
end
|
38
61
|
|
39
|
-
yield "</pre>" if !self[:html]
|
40
62
|
yield foot
|
63
|
+
rescue Errno::EINVAL
|
64
|
+
# socket was closed
|
65
|
+
notice "browser client went away"
|
66
|
+
rescue => boom
|
67
|
+
notice "boom: #{boom.class}: #{boom.to_s}"
|
68
|
+
raise
|
41
69
|
end
|
42
70
|
|
43
|
-
def
|
44
|
-
[
|
45
|
-
|
46
|
-
|
71
|
+
def content_for_head(inject='')
|
72
|
+
[
|
73
|
+
"\n" * 1000,
|
74
|
+
"<!DOCTYPE html>",
|
75
|
+
"<html>",
|
76
|
+
"<head>",
|
77
|
+
"<!-- bcat was here -->",
|
78
|
+
"<title>#{self[:title] || 'bcat'}</title>",
|
79
|
+
inject.to_s,
|
80
|
+
"</head>"
|
81
|
+
].join("\n")
|
47
82
|
end
|
48
83
|
|
49
84
|
def foot
|
@@ -61,18 +96,6 @@ class Bcat
|
|
61
96
|
raise Interrupt
|
62
97
|
end
|
63
98
|
|
64
|
-
def to_app
|
65
|
-
app = self
|
66
|
-
Rack::Builder.new do
|
67
|
-
use Rack::Chunked
|
68
|
-
run app
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
def serve!(&bk)
|
73
|
-
Rack::Handler::KidGloves.run to_app, @config, &bk
|
74
|
-
end
|
75
|
-
|
76
99
|
def notice(message)
|
77
100
|
return if !@config[:debug]
|
78
101
|
warn "#{File.basename($0)}: #{message}"
|
data/lib/bcat/html.rb
ADDED
@@ -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 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>, 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).*?>)/mi,
|
63
|
+
/\A(<\/(?:html|head|meta|link|script|style|title)>)/mi,
|
64
|
+
/\A(<!--(.*?)-->)/m
|
65
|
+
]
|
66
|
+
|
67
|
+
BODY_TOKS = [
|
68
|
+
/\A[^<]/,
|
69
|
+
/\A<(?!html|head|meta|link|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
|
data/lib/bcat/reader.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'rack/utils'
|
2
|
+
|
1
3
|
class Bcat
|
2
4
|
# ARGF style multi-file streaming interface. Input is read with IO#readpartial
|
3
5
|
# to avoid buffering.
|
@@ -15,36 +17,73 @@ class Bcat
|
|
15
17
|
File.open(f, 'rb')
|
16
18
|
end
|
17
19
|
end
|
20
|
+
@buf = []
|
18
21
|
end
|
19
22
|
|
20
23
|
def each
|
21
|
-
|
24
|
+
yield @buf.shift while @buf.any?
|
25
|
+
while fd = fds.first
|
22
26
|
fd.sync = true
|
23
27
|
begin
|
24
28
|
while buf = fd.readpartial(4096)
|
25
29
|
yield buf
|
26
30
|
end
|
27
31
|
rescue EOFError
|
28
|
-
ensure
|
29
32
|
fd.close
|
30
33
|
end
|
34
|
+
fds.shift
|
31
35
|
end
|
32
36
|
end
|
37
|
+
|
38
|
+
def sniff
|
39
|
+
@format ||=
|
40
|
+
catch :detect do
|
41
|
+
each do |chunk|
|
42
|
+
@buf << chunk
|
43
|
+
case chunk
|
44
|
+
when /\A\s*</m
|
45
|
+
throw :detect, 'html'
|
46
|
+
when /\A\s*[^<]/m
|
47
|
+
throw :detect, 'text'
|
48
|
+
end
|
49
|
+
end
|
50
|
+
throw :detect, 'text'
|
51
|
+
end
|
52
|
+
end
|
33
53
|
end
|
34
54
|
|
35
55
|
# Like Reader but writes all input to an output IO object in addition to
|
36
56
|
# yielding to the block.
|
37
|
-
class
|
38
|
-
def initialize(
|
57
|
+
class TeeFilter
|
58
|
+
def initialize(source, out=$stdout)
|
59
|
+
@source = source
|
39
60
|
@out = out
|
40
|
-
super(files)
|
41
61
|
end
|
42
62
|
|
43
63
|
def each
|
44
|
-
|
64
|
+
@source.each do |chunk|
|
65
|
+
yield chunk
|
45
66
|
@out.write chunk
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
class TextFilter
|
72
|
+
include Rack::Utils
|
73
|
+
|
74
|
+
def initialize(source, force=false)
|
75
|
+
@source = source
|
76
|
+
@force = force
|
77
|
+
end
|
78
|
+
|
79
|
+
def each
|
80
|
+
yield "<pre>"
|
81
|
+
@source.each do |chunk|
|
82
|
+
chunk = escape_html(chunk)
|
83
|
+
chunk = "<span>#{chunk}</span>" if !chunk.gsub!(/\n/, "<br>")
|
46
84
|
yield chunk
|
47
85
|
end
|
86
|
+
yield "</pre>"
|
48
87
|
end
|
49
88
|
end
|
50
89
|
end
|
data/man/bcat.1.ronn
CHANGED
@@ -17,16 +17,26 @@ addition to being piped into the browser.
|
|
17
17
|
|
18
18
|
## OPTIONS
|
19
19
|
|
20
|
-
|
21
|
-
|
22
|
-
|
20
|
+
By default, `bcat` attempts to detect whether input is HTML or plain text using
|
21
|
+
a simple heuristic, but you can force input to be treated as one or the other
|
22
|
+
with these options:
|
23
|
+
|
24
|
+
* `-t`, `--text`:
|
25
|
+
The input is non-HTML encoded text. All bare `<` and `&` characters are
|
26
|
+
entity encoded, end-of-line characters are converted to `<br>`, and the
|
27
|
+
entire output is wrapped in a `<pre>`.
|
23
28
|
|
24
29
|
* `-h`, `--html`:
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
+
The input is already HTML encoded. Under this mode, bcat passes input
|
31
|
+
through to the browser mostly unmodified. The input may be a full HTML
|
32
|
+
document, or it may be an HTML fragment. `bcat` outputs `<html>`, `<head>`,
|
33
|
+
and `<body>` elements even if they are not included in the input.
|
34
|
+
|
35
|
+
Customization
|
36
|
+
|
37
|
+
* `-T`, `--title`=<text>:
|
38
|
+
Use <text> as the page `<title>`. By default, the path to the current working
|
39
|
+
directory is used as the title.
|
30
40
|
|
31
41
|
* `-a`, `--app`=`Safari`|`Google Chrome`|`Firefox`:
|
32
42
|
MacOS only. The name of the browser application. This can also be set using
|
data/man/btee.1.ronn
CHANGED
@@ -17,16 +17,26 @@ addition to being piped into the browser.
|
|
17
17
|
|
18
18
|
## OPTIONS
|
19
19
|
|
20
|
-
|
21
|
-
|
22
|
-
|
20
|
+
By default, `bcat` attempts to detect whether input is HTML or plain text using
|
21
|
+
a simple heuristic, but you can force input to be treated as one or the other
|
22
|
+
with these options:
|
23
|
+
|
24
|
+
* `-t`, `--text`:
|
25
|
+
The input is non-HTML encoded text. All bare `<` and `&` characters are
|
26
|
+
entity encoded, end-of-line characters are converted to `<br>`, and the
|
27
|
+
entire output is wrapped in a `<pre>`.
|
23
28
|
|
24
29
|
* `-h`, `--html`:
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
+
The input is already HTML encoded. Under this mode, bcat passes input
|
31
|
+
through to the browser mostly unmodified. The input may be a full HTML
|
32
|
+
document, or it may be an HTML fragment. `bcat` outputs `<html>`, `<head>`,
|
33
|
+
and `<body>` elements even if they are not included in the input.
|
34
|
+
|
35
|
+
Customization
|
36
|
+
|
37
|
+
* `-T`, `--title`=<text>:
|
38
|
+
Use <text> as the page `<title>`. By default, the path to the current working
|
39
|
+
directory is used as the title.
|
30
40
|
|
31
41
|
* `-a`, `--app`=`Safari`|`Google Chrome`|`Firefox`:
|
32
42
|
MacOS only. The name of the browser application. This can also be set using
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'contest'
|
2
|
+
require 'bcat/html'
|
3
|
+
|
4
|
+
class HeadParserTest < Test::Unit::TestCase
|
5
|
+
|
6
|
+
setup { @parser = Bcat::HeadParser.new }
|
7
|
+
|
8
|
+
test 'starts in an unknown state' do
|
9
|
+
assert @parser.html?.nil?
|
10
|
+
assert @parser.buf.empty?
|
11
|
+
end
|
12
|
+
|
13
|
+
test 'detects non-HTML input' do
|
14
|
+
@parser.feed("HOWDY <h1>")
|
15
|
+
assert_equal false, @parser.html?
|
16
|
+
assert_equal '', @parser.head
|
17
|
+
end
|
18
|
+
|
19
|
+
test 'separates head elements from body' do
|
20
|
+
@parser.feed("<style>h1{ font-size:500% }</style>")
|
21
|
+
@parser.feed("<h1>HOLLA</h1>")
|
22
|
+
assert_equal "<style>h1{ font-size:500% }</style>", @parser.head.strip
|
23
|
+
assert_equal "<body>\n<h1>HOLLA</h1>", @parser.body
|
24
|
+
end
|
25
|
+
|
26
|
+
test 'handles multiple head elements' do
|
27
|
+
stuff = [
|
28
|
+
"<style>h1{ font-size:500% }</style>",
|
29
|
+
"<link rel=alternate>",
|
30
|
+
"<script type='text/javascript'>{};</script>"
|
31
|
+
]
|
32
|
+
stuff.each { |html| @parser.feed(html) }
|
33
|
+
@parser.feed("\n \n\n\n<h1>HOLLA</h1>")
|
34
|
+
|
35
|
+
assert_equal stuff.join, @parser.head.strip
|
36
|
+
end
|
37
|
+
|
38
|
+
test 'handles full documents' do
|
39
|
+
@parser.feed("<!DOCTYPE html>\n")
|
40
|
+
@parser.feed("<html><head><title>YO</title></head>")
|
41
|
+
@parser.feed("<body id=oyy><h1>OY</h1></body></html>")
|
42
|
+
assert_equal "<title>YO</title>", @parser.head.strip
|
43
|
+
assert_equal "<body id=oyy>\n<h1>OY</h1></body></html>", @parser.body
|
44
|
+
end
|
45
|
+
|
46
|
+
test 'knows when the head is fully parsed' do
|
47
|
+
@parser.feed("<!DOCTYPE html>\n")
|
48
|
+
assert !@parser.complete?
|
49
|
+
|
50
|
+
@parser.feed("<html><head><title>YO</title></head>")
|
51
|
+
assert !@parser.complete?
|
52
|
+
|
53
|
+
@parser.feed("<body id=oyy><h1>OY</h1></body></html>")
|
54
|
+
assert @parser.complete?
|
55
|
+
end
|
56
|
+
end
|
metadata
CHANGED
@@ -1,13 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bcat
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
5
|
-
prerelease: false
|
6
|
-
segments:
|
7
|
-
- 0
|
8
|
-
- 2
|
9
|
-
- 0
|
10
|
-
version: 0.2.0
|
4
|
+
version: 0.3.0
|
11
5
|
platform: ruby
|
12
6
|
authors:
|
13
7
|
- Ryan Tomayko
|
@@ -15,23 +9,19 @@ autorequire:
|
|
15
9
|
bindir: bin
|
16
10
|
cert_chain: []
|
17
11
|
|
18
|
-
date: 2010-06-
|
12
|
+
date: 2010-06-21 00:00:00 -07:00
|
19
13
|
default_executable: bcat
|
20
14
|
dependencies:
|
21
15
|
- !ruby/object:Gem::Dependency
|
22
16
|
name: rack
|
23
|
-
|
24
|
-
|
25
|
-
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
20
|
requirements:
|
27
21
|
- - ">="
|
28
22
|
- !ruby/object:Gem::Version
|
29
|
-
hash: 3
|
30
|
-
segments:
|
31
|
-
- 0
|
32
23
|
version: "0"
|
33
|
-
|
34
|
-
version_requirements: *id001
|
24
|
+
version:
|
35
25
|
description: Concatenate input from standard input, or one or more files, and write progressive output to a browser.
|
36
26
|
email: rtomayko@gmail.com
|
37
27
|
executables:
|
@@ -41,6 +31,9 @@ extensions: []
|
|
41
31
|
extra_rdoc_files:
|
42
32
|
- COPYING
|
43
33
|
files:
|
34
|
+
- CONTRIBUTING
|
35
|
+
- COPYING
|
36
|
+
- INSTALLING
|
44
37
|
- README
|
45
38
|
- Rakefile
|
46
39
|
- bcat.gemspec
|
@@ -49,11 +42,12 @@ files:
|
|
49
42
|
- bin/btee
|
50
43
|
- lib/bcat.rb
|
51
44
|
- lib/bcat/browser.rb
|
45
|
+
- lib/bcat/html.rb
|
52
46
|
- lib/bcat/kidgloves.rb
|
53
47
|
- lib/bcat/reader.rb
|
54
48
|
- man/bcat.1.ronn
|
55
49
|
- man/btee.1.ronn
|
56
|
-
-
|
50
|
+
- test/test_bcat_head_parser.rb
|
57
51
|
has_rdoc: true
|
58
52
|
homepage: http://github.com/rtomayko/bcat/
|
59
53
|
licenses: []
|
@@ -65,27 +59,21 @@ rdoc_options:
|
|
65
59
|
require_paths:
|
66
60
|
- lib
|
67
61
|
required_ruby_version: !ruby/object:Gem::Requirement
|
68
|
-
none: false
|
69
62
|
requirements:
|
70
63
|
- - ">="
|
71
64
|
- !ruby/object:Gem::Version
|
72
|
-
hash: 3
|
73
|
-
segments:
|
74
|
-
- 0
|
75
65
|
version: "0"
|
66
|
+
version:
|
76
67
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
77
|
-
none: false
|
78
68
|
requirements:
|
79
69
|
- - ">="
|
80
70
|
- !ruby/object:Gem::Version
|
81
|
-
hash: 3
|
82
|
-
segments:
|
83
|
-
- 0
|
84
71
|
version: "0"
|
72
|
+
version:
|
85
73
|
requirements: []
|
86
74
|
|
87
75
|
rubyforge_project:
|
88
|
-
rubygems_version: 1.3.
|
76
|
+
rubygems_version: 1.3.5
|
89
77
|
signing_key:
|
90
78
|
specification_version: 3
|
91
79
|
summary: browser cat
|