bcat 0.2.0 → 0.3.0
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/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
|