bcat 0.0.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/COPYING +0 -0
- data/Rakefile +42 -0
- data/bcat.gemspec +37 -0
- data/bin/bcat +34 -0
- data/lib/bcat.rb +69 -0
- data/lib/bcat/kidgloves.rb +149 -0
- data/man/bcat.1.ronn +27 -0
- metadata +87 -0
data/COPYING
ADDED
File without changes
|
data/Rakefile
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'date'
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
$spec = eval(File.read('bcat.gemspec'))
|
5
|
+
|
6
|
+
desc "Build gem package"
|
7
|
+
task :package => 'bcat.gemspec' do
|
8
|
+
sh "gem build bcat.gemspec"
|
9
|
+
end
|
10
|
+
|
11
|
+
desc 'Build the manual'
|
12
|
+
task :man do
|
13
|
+
ENV['RONN_MANUAL'] = "Bcat #{source_version}"
|
14
|
+
ENV['RONN_ORGANIZATION'] = "Ryan Tomayko"
|
15
|
+
sh "ronn -w -r5 man/*.ronn"
|
16
|
+
end
|
17
|
+
|
18
|
+
def source_version
|
19
|
+
@source_version ||= `ruby -Ilib -rbcat -e 'puts Bcat::VERSION'`.chomp
|
20
|
+
end
|
21
|
+
|
22
|
+
file 'bcat.gemspec' => FileList['{lib,test,bin}/**','Rakefile'] do |f|
|
23
|
+
# read spec file and split out manifest section
|
24
|
+
spec = File.read(f.name)
|
25
|
+
head, manifest, tail = spec.split(" # = MANIFEST =\n")
|
26
|
+
# replace version and date
|
27
|
+
head.sub!(/\.version = '.*'/, ".version = '#{source_version}'")
|
28
|
+
head.sub!(/\.date = '.*'/, ".date = '#{Date.today.to_s}'")
|
29
|
+
# determine file list from git ls-files
|
30
|
+
files = `git ls-files`.
|
31
|
+
split("\n").
|
32
|
+
sort.
|
33
|
+
reject{ |file| file =~ /^\./ }.
|
34
|
+
reject { |file| file =~ /^doc/ }.
|
35
|
+
map{ |file| " #{file}" }.
|
36
|
+
join("\n")
|
37
|
+
# piece file back together and write...
|
38
|
+
manifest = " s.files = %w[\n#{files}\n ]\n"
|
39
|
+
spec = [head,manifest,tail].join(" # = MANIFEST =\n")
|
40
|
+
File.open(f.name, 'w') { |io| io.write(spec) }
|
41
|
+
puts "updated #{f.name}"
|
42
|
+
end
|
data/bcat.gemspec
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'bcat'
|
3
|
+
s.version = '0.0.0'
|
4
|
+
s.date = '2010-06-19'
|
5
|
+
|
6
|
+
s.summary = "browser cat"
|
7
|
+
s.description =
|
8
|
+
"Concatenate input from standard input, or one or more files, " +
|
9
|
+
"and write progressive output to a browser."
|
10
|
+
|
11
|
+
s.authors = ["Ryan Tomayko"]
|
12
|
+
s.email = "rtomayko@gmail.com"
|
13
|
+
|
14
|
+
# = MANIFEST =
|
15
|
+
s.files = %w[
|
16
|
+
Rakefile
|
17
|
+
bcat.gemspec
|
18
|
+
bin/bcat
|
19
|
+
lib/bcat.rb
|
20
|
+
lib/bcat/kidgloves.rb
|
21
|
+
man/bcat.1.ronn
|
22
|
+
]
|
23
|
+
# = MANIFEST =
|
24
|
+
|
25
|
+
s.default_executable = 'bcat'
|
26
|
+
s.executables = ['bcat']
|
27
|
+
|
28
|
+
s.test_files = s.files.select {|path| path =~ /^test\/.*_test.rb/}
|
29
|
+
s.add_dependency 'rack'
|
30
|
+
|
31
|
+
s.extra_rdoc_files = %w[COPYING]
|
32
|
+
|
33
|
+
s.has_rdoc = true
|
34
|
+
s.homepage = "http://github.com/rtomayko/bcat/"
|
35
|
+
s.rdoc_options = ["--line-numbers", "--inline-source"]
|
36
|
+
s.require_paths = %w[lib]
|
37
|
+
end
|
data/bin/bcat
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# Usage: bcat [<file>]...
|
3
|
+
# Read standard input, or one or more <file>s, and write to browser.
|
4
|
+
require 'bcat'
|
5
|
+
|
6
|
+
fds =
|
7
|
+
ARGV.map do |file|
|
8
|
+
if file == '-'
|
9
|
+
$stdin
|
10
|
+
else
|
11
|
+
File.open(file, 'rb')
|
12
|
+
end
|
13
|
+
end
|
14
|
+
fds = [$stdin] if fds.empty?
|
15
|
+
|
16
|
+
command = ENV['BCAT_COMMAND'] || 'open $BCAT_ARGS "$BCAT_URL"'
|
17
|
+
|
18
|
+
pid = nil
|
19
|
+
begin
|
20
|
+
bcat = Bcat.new(fds)
|
21
|
+
bcat.serve! do |sock|
|
22
|
+
pid =
|
23
|
+
fork do
|
24
|
+
(fds + [sock, $stdin, $stdout]).uniq.each { |fd| fd.close }
|
25
|
+
ENV['BCAT_URL'] = "http://#{bcat[:Host]}:#{bcat[:Port]}/#{File.basename(Dir.pwd)}"
|
26
|
+
ENV['BCAT_ARGS'] = "-a '#{ENV['BCAT_APPLICATION']}'" if !ENV['BCAT_APPLICATION'].to_s.empty?
|
27
|
+
exec "/bin/sh -c \"#{command}\""
|
28
|
+
end
|
29
|
+
end
|
30
|
+
rescue Interrupt
|
31
|
+
end
|
32
|
+
|
33
|
+
Process.wait(pid) if pid
|
34
|
+
exit $?
|
data/lib/bcat.rb
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'rack'
|
2
|
+
require 'bcat/kidgloves'
|
3
|
+
|
4
|
+
class Bcat
|
5
|
+
VERSION = '0.0.0'
|
6
|
+
include Rack::Utils
|
7
|
+
|
8
|
+
def initialize(fds=[$stdin], config={})
|
9
|
+
@fds = fds
|
10
|
+
@config = {:Host => '127.0.0.1', :Port => 8091}.merge(config)
|
11
|
+
end
|
12
|
+
|
13
|
+
def [](key)
|
14
|
+
@config[key]
|
15
|
+
end
|
16
|
+
|
17
|
+
def call(env)
|
18
|
+
[200, {"Content-Type" => "text/html;charset=utf-8"}, self]
|
19
|
+
end
|
20
|
+
|
21
|
+
def head
|
22
|
+
["<html>",
|
23
|
+
"<head><title>bcat</title></head>",
|
24
|
+
"<body><pre>"].join
|
25
|
+
end
|
26
|
+
|
27
|
+
def foot
|
28
|
+
"</pre></body></html>"
|
29
|
+
end
|
30
|
+
|
31
|
+
def each
|
32
|
+
yield "\n" * 1000
|
33
|
+
yield "<!DOCTYPE html>\n"
|
34
|
+
yield head
|
35
|
+
|
36
|
+
begin
|
37
|
+
@fds.each do |fd|
|
38
|
+
begin
|
39
|
+
while buf = fd.readpartial(4096)
|
40
|
+
output = escape_html(buf)
|
41
|
+
output = output.gsub(/\n/, "<br>")
|
42
|
+
yield "<script>document.write('#{output}');</script>"
|
43
|
+
end
|
44
|
+
rescue EOFError
|
45
|
+
ensure
|
46
|
+
fd.close rescue nil
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
yield foot
|
52
|
+
end
|
53
|
+
|
54
|
+
def close
|
55
|
+
raise Interrupt
|
56
|
+
end
|
57
|
+
|
58
|
+
def to_app
|
59
|
+
app = self
|
60
|
+
Rack::Builder.new do
|
61
|
+
use Rack::Chunked
|
62
|
+
run app
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def serve!(&bk)
|
67
|
+
Rack::Handler::KidGloves.run to_app, @config, &bk
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,149 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'stringio'
|
3
|
+
|
4
|
+
module Rack
|
5
|
+
module Handler
|
6
|
+
class KidGloves
|
7
|
+
attr_accessor :app
|
8
|
+
|
9
|
+
StatusMessage = {
|
10
|
+
100 => 'Continue',
|
11
|
+
101 => 'Switching Protocols',
|
12
|
+
200 => 'OK',
|
13
|
+
201 => 'Created',
|
14
|
+
202 => 'Accepted',
|
15
|
+
203 => 'Non-Authoritative Information',
|
16
|
+
204 => 'No Content',
|
17
|
+
205 => 'Reset Content',
|
18
|
+
206 => 'Partial Content',
|
19
|
+
300 => 'Multiple Choices',
|
20
|
+
301 => 'Moved Permanently',
|
21
|
+
302 => 'Found',
|
22
|
+
303 => 'See Other',
|
23
|
+
304 => 'Not Modified',
|
24
|
+
305 => 'Use Proxy',
|
25
|
+
307 => 'Temporary Redirect',
|
26
|
+
400 => 'Bad Request',
|
27
|
+
401 => 'Unauthorized',
|
28
|
+
402 => 'Payment Required',
|
29
|
+
403 => 'Forbidden',
|
30
|
+
404 => 'Not Found',
|
31
|
+
405 => 'Method Not Allowed',
|
32
|
+
406 => 'Not Acceptable',
|
33
|
+
407 => 'Proxy Authentication Required',
|
34
|
+
408 => 'Request Timeout',
|
35
|
+
409 => 'Conflict',
|
36
|
+
410 => 'Gone',
|
37
|
+
411 => 'Length Required',
|
38
|
+
412 => 'Precondition Failed',
|
39
|
+
413 => 'Request Entity Too Large',
|
40
|
+
414 => 'Request-URI Too Large',
|
41
|
+
415 => 'Unsupported Media Type',
|
42
|
+
416 => 'Request Range Not Satisfiable',
|
43
|
+
417 => 'Expectation Failed',
|
44
|
+
500 => 'Internal Server Error',
|
45
|
+
501 => 'Not Implemented',
|
46
|
+
502 => 'Bad Gateway',
|
47
|
+
503 => 'Service Unavailable',
|
48
|
+
504 => 'Gateway Timeout',
|
49
|
+
505 => 'HTTP Version Not Supported'
|
50
|
+
}
|
51
|
+
|
52
|
+
def self.run(app, options={}, &block)
|
53
|
+
new(app, options).listen(&block)
|
54
|
+
end
|
55
|
+
|
56
|
+
def initialize(app, options={})
|
57
|
+
@app = app
|
58
|
+
@host = options[:Host] || '0.0.0.0'
|
59
|
+
@port = options[:Port] || 8089
|
60
|
+
end
|
61
|
+
|
62
|
+
def listen
|
63
|
+
log "Starting server on #{@host}:#{@port}"
|
64
|
+
server = TCPServer.new(@host, @port)
|
65
|
+
|
66
|
+
yield server if block_given?
|
67
|
+
|
68
|
+
loop do
|
69
|
+
socket = server.accept
|
70
|
+
socket.sync = true
|
71
|
+
log "#{socket.peeraddr[2]} (#{socket.peeraddr[3]})"
|
72
|
+
begin
|
73
|
+
|
74
|
+
req = {}
|
75
|
+
|
76
|
+
# parse the request line
|
77
|
+
request = socket.gets
|
78
|
+
method, path, version = request.split(" ")
|
79
|
+
req["REQUEST_METHOD"] = method
|
80
|
+
info, query = path.split("?")
|
81
|
+
req["PATH_INFO"] = info
|
82
|
+
req["QUERY_STRING"] = query
|
83
|
+
|
84
|
+
# parse the headers
|
85
|
+
while (line = socket.gets)
|
86
|
+
line.strip!
|
87
|
+
break if line.size == 0
|
88
|
+
key, val = line.split(": ")
|
89
|
+
key = key.upcase.gsub('-', '_')
|
90
|
+
key = "HTTP_#{key}" if !%w[CONTENT_TYPE CONTENT_LENGTH].include?(key)
|
91
|
+
req[key] = val
|
92
|
+
end
|
93
|
+
|
94
|
+
# parse the body
|
95
|
+
body = ''
|
96
|
+
if (len = req['CONTENT_LENGTH']) && ["POST", "PUT"].member?(method)
|
97
|
+
body = socket.read(len.to_i)
|
98
|
+
end
|
99
|
+
|
100
|
+
# process the request
|
101
|
+
process_request(req, body, socket)
|
102
|
+
|
103
|
+
ensure
|
104
|
+
socket.close if not socket.closed?
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def log(message)
|
110
|
+
# $stderr.puts message
|
111
|
+
end
|
112
|
+
|
113
|
+
def status_message(code)
|
114
|
+
StatusMessage[code]
|
115
|
+
end
|
116
|
+
|
117
|
+
def process_request(request, input_body, socket)
|
118
|
+
env = {}.replace(request)
|
119
|
+
env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"]
|
120
|
+
env["QUERY_STRING"] ||= ""
|
121
|
+
env["SCRIPT_NAME"] = ""
|
122
|
+
|
123
|
+
rack_input = StringIO.new(input_body)
|
124
|
+
rack_input.set_encoding(Encoding::BINARY) if rack_input.respond_to?(:set_encoding)
|
125
|
+
|
126
|
+
env.update({"rack.version" => [1,0],
|
127
|
+
"rack.input" => rack_input,
|
128
|
+
"rack.errors" => $stderr,
|
129
|
+
"rack.multithread" => true,
|
130
|
+
"rack.multiprocess" => true,
|
131
|
+
"rack.run_once" => false,
|
132
|
+
|
133
|
+
"rack.url_scheme" => ["yes", "on", "1"].include?(env["HTTPS"]) ? "https" : "http"
|
134
|
+
})
|
135
|
+
status, headers, body = app.call(env)
|
136
|
+
begin
|
137
|
+
socket.write("HTTP/1.1 #{status} #{status_message(status)}\r\n")
|
138
|
+
headers.each do |k, vs|
|
139
|
+
vs.split("\n").each { |v| socket.write("#{k}: #{v}\r\n")}
|
140
|
+
end
|
141
|
+
socket.write("\r\n")
|
142
|
+
body.each { |s| socket.write(s) }
|
143
|
+
ensure
|
144
|
+
body.close if body.respond_to? :close
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
data/man/bcat.1.ronn
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
bcat(1) -- browser cat
|
2
|
+
======================
|
3
|
+
|
4
|
+
## SYNOPSIS
|
5
|
+
|
6
|
+
`bcat`<br>
|
7
|
+
`bcat` <file>... [-]<br>
|
8
|
+
|
9
|
+
## DESCRIPTION
|
10
|
+
|
11
|
+
The `bcat` utility reads from standard input, or one or more <file>s, and writes
|
12
|
+
output to a web browser progressively. When <file> is '-'
|
13
|
+
|
14
|
+
## ENVIRONMENT
|
15
|
+
|
16
|
+
* `BCAT_COMMAND`=`open -a $BCAT_APPLICATION $url`:
|
17
|
+
* `BCAT_COMMAND`=`xdg-open "$url"`:
|
18
|
+
The command used to launch to the browser application.
|
19
|
+
|
20
|
+
* `BCAT_APPLICATION`=[`Safari | Google Chrome | Firefox`]:
|
21
|
+
MacOS X only. The name of the Application . This is given as the value of
|
22
|
+
the `-a` argument to open(1). When `BCAT_APPLICATION` is undefined, the
|
23
|
+
system default browser is used.
|
24
|
+
|
25
|
+
## SEE ALSO
|
26
|
+
|
27
|
+
cat(1), open(1)
|
metadata
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: bcat
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 31
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 0
|
10
|
+
version: 0.0.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Ryan Tomayko
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-06-19 00:00:00 -07:00
|
19
|
+
default_executable: bcat
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: rack
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 3
|
30
|
+
segments:
|
31
|
+
- 0
|
32
|
+
version: "0"
|
33
|
+
type: :runtime
|
34
|
+
version_requirements: *id001
|
35
|
+
description: Concatenate input from standard input, or one or more files, and write progressive output to a browser.
|
36
|
+
email: rtomayko@gmail.com
|
37
|
+
executables:
|
38
|
+
- bcat
|
39
|
+
extensions: []
|
40
|
+
|
41
|
+
extra_rdoc_files:
|
42
|
+
- COPYING
|
43
|
+
files:
|
44
|
+
- Rakefile
|
45
|
+
- bcat.gemspec
|
46
|
+
- bin/bcat
|
47
|
+
- lib/bcat.rb
|
48
|
+
- lib/bcat/kidgloves.rb
|
49
|
+
- man/bcat.1.ronn
|
50
|
+
- COPYING
|
51
|
+
has_rdoc: true
|
52
|
+
homepage: http://github.com/rtomayko/bcat/
|
53
|
+
licenses: []
|
54
|
+
|
55
|
+
post_install_message:
|
56
|
+
rdoc_options:
|
57
|
+
- --line-numbers
|
58
|
+
- --inline-source
|
59
|
+
require_paths:
|
60
|
+
- lib
|
61
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
62
|
+
none: false
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
hash: 3
|
67
|
+
segments:
|
68
|
+
- 0
|
69
|
+
version: "0"
|
70
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
71
|
+
none: false
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
hash: 3
|
76
|
+
segments:
|
77
|
+
- 0
|
78
|
+
version: "0"
|
79
|
+
requirements: []
|
80
|
+
|
81
|
+
rubyforge_project:
|
82
|
+
rubygems_version: 1.3.7
|
83
|
+
signing_key:
|
84
|
+
specification_version: 3
|
85
|
+
summary: browser cat
|
86
|
+
test_files: []
|
87
|
+
|