peekbot 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/dispatch.fcgi +15 -0
- data/m.bat +3 -0
- data/make_all_images.rb +51 -0
- data/peekbot.rb +599 -0
- data/peekbot_helper.rb +153 -0
- data/static/changes.txt +56 -0
- data/static/peekbot.png +0 -0
- data/static/style.css +67 -0
- metadata +92 -0
data/dispatch.fcgi
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/local/bin/ruby
|
2
|
+
require 'rubygems'
|
3
|
+
require 'camping'
|
4
|
+
require 'camping/fastcgi'
|
5
|
+
require 'peekbot'
|
6
|
+
|
7
|
+
Camping::FastCGI.serve("peekbot.rb")
|
8
|
+
|
9
|
+
FCGI.each do |req|
|
10
|
+
# req.out << "Content-Type: text/html\r\n\r\nHello, World!<br>\n"
|
11
|
+
# req.env.keys.sort.each {|k| req.out << "#{k}=#{req.env[k]}<br>\n"}
|
12
|
+
camp_do(req)
|
13
|
+
# req.finish
|
14
|
+
end
|
15
|
+
|
data/m.bat
ADDED
data/make_all_images.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
#!/usr/local/bin/ruby
|
2
|
+
lib_path=File.expand_path(File.dirname(__FILE__)+"//..//ripxplore//lib")
|
3
|
+
$:.unshift(lib_path) unless $:.include?(lib_path)
|
4
|
+
|
5
|
+
lib_path=File.expand_path(File.dirname(__FILE__)+"//..//..//ripxplore//lib")
|
6
|
+
$:.unshift(lib_path) unless $:.include?(lib_path)
|
7
|
+
|
8
|
+
require 'RipXplore'
|
9
|
+
require 'FileCache'
|
10
|
+
PEEKBOT_URL_CACHE_DIR=File.dirname(__FILE__)+'/.image_cache'
|
11
|
+
NATIVE_FILE_CACHE_PATH='/static/native_files'
|
12
|
+
PEEKBOT_NATIVE_FILE_CACHE_DIR=File.dirname(__FILE__)+NATIVE_FILE_CACHE_PATH
|
13
|
+
|
14
|
+
LAST_PEEKBOT_UPDATE=File.stat(__FILE__).mtime
|
15
|
+
|
16
|
+
filecache=FileCache.new(PEEKBOT_URL_CACHE_DIR,PEEKBOT_NATIVE_FILE_CACHE_DIR)
|
17
|
+
filecache.make_file_system_image_list
|
18
|
+
all_image_urls=filecache.file_system_image_urls_in_cache
|
19
|
+
all_image_urls.reverse.each do |url|
|
20
|
+
puts "dumping #{url} at #{Time.now.to_s}"
|
21
|
+
begin
|
22
|
+
thread1=Thread.new do
|
23
|
+
image=filecache.get_file_system_image(url)
|
24
|
+
puts "got image"
|
25
|
+
image.pictures.each do |picture|
|
26
|
+
local_filename=filecache.force_native_file_to_cache(picture,:to_picture,picture.picture_format.to_s)
|
27
|
+
puts "made picture #{local_filename}"
|
28
|
+
end
|
29
|
+
image.texts.each do |text|
|
30
|
+
if text.respond_to?(:to_screendump) then
|
31
|
+
local_filename=filecache.force_native_file_to_cache(text,:to_screendump,"png")
|
32
|
+
puts "made screendump #{local_filename}"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
60.times do
|
38
|
+
break unless thread1.alive?
|
39
|
+
sleep 1
|
40
|
+
end
|
41
|
+
if thread1.alive? then
|
42
|
+
puts "killing thread"
|
43
|
+
thread1.kill
|
44
|
+
end
|
45
|
+
rescue
|
46
|
+
puts $!
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
|
data/peekbot.rb
ADDED
@@ -0,0 +1,599 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__)) unless
|
2
|
+
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
require 'camping'
|
6
|
+
|
7
|
+
Markaby::Builder.set(:indent, 2)
|
8
|
+
|
9
|
+
lib_path=File.expand_path(File.dirname(__FILE__)+"//..//ripxplore//lib")
|
10
|
+
$:.unshift(lib_path) unless $:.include?(lib_path)
|
11
|
+
|
12
|
+
lib_path=File.expand_path(File.dirname(__FILE__)+"//..//..//ripxplore//lib")
|
13
|
+
$:.unshift(lib_path) unless $:.include?(lib_path)
|
14
|
+
|
15
|
+
require 'peekbot_helper'
|
16
|
+
|
17
|
+
require 'RipXplore'
|
18
|
+
|
19
|
+
PEEKBOT_URL_CACHE_DIR=File.dirname(__FILE__)+'/.image_cache'
|
20
|
+
NATIVE_FILE_CACHE_PATH='/static/native_files'
|
21
|
+
PEEKBOT_NATIVE_FILE_CACHE_DIR=File.dirname(__FILE__)+NATIVE_FILE_CACHE_PATH
|
22
|
+
|
23
|
+
LAST_PEEKBOT_UPDATE=File.stat(__FILE__).mtime
|
24
|
+
|
25
|
+
require 'FileCache'
|
26
|
+
@@filecache=FileCache.new(PEEKBOT_URL_CACHE_DIR,PEEKBOT_NATIVE_FILE_CACHE_DIR)
|
27
|
+
|
28
|
+
|
29
|
+
Camping.goes :PeekBot
|
30
|
+
|
31
|
+
@@images={}
|
32
|
+
def get_image(url)
|
33
|
+
if @@images[url].nil? then
|
34
|
+
|
35
|
+
@@images[url]=@@filecache.get_file_system_image(url)
|
36
|
+
end
|
37
|
+
@@images[url]
|
38
|
+
end
|
39
|
+
|
40
|
+
def local_mode?
|
41
|
+
remote_host=@env["REMOTE_ADDR"]
|
42
|
+
is_localhost=(remote_host=~/127\.0\.0\.1/)
|
43
|
+
is_localhost
|
44
|
+
end
|
45
|
+
def path_for_cached_native_file(native_file,method_to_cache,extension)
|
46
|
+
local_filename=@@filecache.force_native_file_to_cache(native_file,method_to_cache,extension)
|
47
|
+
local_filename.sub(PEEKBOT_NATIVE_FILE_CACHE_DIR,NATIVE_FILE_CACHE_PATH)
|
48
|
+
end
|
49
|
+
|
50
|
+
module PeekBot::Controllers
|
51
|
+
|
52
|
+
|
53
|
+
# The root slash shows the `index' view.
|
54
|
+
class Index < R '/'
|
55
|
+
def get
|
56
|
+
if local_mode? then
|
57
|
+
redirect R(NavigateArchive,"file://#{Dir.getwd}")
|
58
|
+
else
|
59
|
+
render :index
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
class HelpWanted < R '/help_wanted/'
|
65
|
+
def get
|
66
|
+
render :help_wanted
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
class NavigateArchive < R '/navigate_archive/(.+)'
|
71
|
+
def get(archive_url)
|
72
|
+
@working_url=WorkingUrl.new(archive_url)
|
73
|
+
@images,@directories,@other_files=PeekBotHelper.get_archive_contents(@working_url.archive_url)
|
74
|
+
render :navigate_archive
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
class Static < R '/static/(.+)'
|
79
|
+
MIME_TYPES = {'.css' => 'text/css', '.js' => 'text/javascript', '.jpg' => 'image/jpeg,','.png' => 'image/png','.gif' => 'image/gif'}
|
80
|
+
PATH = File.expand_path(File.dirname(__FILE__))
|
81
|
+
def get(path)
|
82
|
+
@headers['Content-Type'] = MIME_TYPES[path[/\.\w+$/, 0]] || "text/plain"
|
83
|
+
unless path.include? ".." # prevent directory traversal attacks
|
84
|
+
@headers['X-Sendfile'] = "#{PATH}/static/#{path}"
|
85
|
+
else
|
86
|
+
@status = "403"
|
87
|
+
"403 - Invalid path"
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
class CatalogImage < R '/catalog_image/(.+)'
|
94
|
+
def get(image_url)
|
95
|
+
@working_url=WorkingUrl.split_image_url_into_parts(image_url)
|
96
|
+
@image=get_image(@working_url.image_url)
|
97
|
+
render :catalog_image
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
class AllFilesOfType < R '/all_([^/]+)s/(.+)'
|
102
|
+
def get(filetype,image_url)
|
103
|
+
@working_url=WorkingUrl.split_image_url_into_parts(image_url)
|
104
|
+
@image=get_image(@working_url.image_url)
|
105
|
+
render "all_#{filetype}s".to_sym
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
class NativeFile < R '/native_file/(.+)/(\d+)'
|
110
|
+
def get(image_url,native_file_index)
|
111
|
+
@display_mode=input.display_mode
|
112
|
+
@working_url=WorkingUrl.split_image_url_into_parts(image_url)
|
113
|
+
@native_file_index=native_file_index
|
114
|
+
@image=get_image(@working_url.image_url)
|
115
|
+
@native_file=@image.files[native_file_index.to_i]
|
116
|
+
render :native_file
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
class SectorViewer < R '/sector_viewer/(.+)/(\d+)/(\d+)'
|
121
|
+
def get(image_url,track,sector)
|
122
|
+
@display_mode=input.display_mode
|
123
|
+
@working_url=WorkingUrl.split_image_url_into_parts(image_url)
|
124
|
+
@track=track.to_i
|
125
|
+
@sector=sector.to_i
|
126
|
+
@image=get_image(@working_url.image_url)
|
127
|
+
render :sector_viewer
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
class RandomFile < R '/random_([^/]+)/'
|
132
|
+
def get(filetype)
|
133
|
+
@random_filetype=filetype
|
134
|
+
random_filetype_collection_name="#{@random_filetype}s".to_sym
|
135
|
+
@image=@@filecache.random_file_system_image(random_filetype_collection_name)
|
136
|
+
@working_url=WorkingUrl.split_image_url_into_parts(@image.filename)
|
137
|
+
random_filetype_collection=@image.send(random_filetype_collection_name)
|
138
|
+
random_index=rand(random_filetype_collection.length)
|
139
|
+
@native_file=random_filetype_collection[random_index]
|
140
|
+
render :random_file
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
# Any other page name gets sent to the view
|
145
|
+
# of the same name.
|
146
|
+
#
|
147
|
+
# /index -> Views#index
|
148
|
+
# /sample -> Views#sample
|
149
|
+
#
|
150
|
+
|
151
|
+
class Page < R '/(\w+)'
|
152
|
+
def get(page_name)
|
153
|
+
render page_name
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
end
|
158
|
+
|
159
|
+
|
160
|
+
module PeekBot::Views
|
161
|
+
|
162
|
+
# If you have a `layout' method like this, it
|
163
|
+
# will wrap the HTML in the other methods. The
|
164
|
+
# `self << yield' is where the HTML is inserted.
|
165
|
+
def layout
|
166
|
+
html do
|
167
|
+
head do
|
168
|
+
title 'peekbot'
|
169
|
+
link :href=>R(Static,'style.css'), :rel=>'stylesheet', :type=>'text/css'
|
170
|
+
end
|
171
|
+
body do
|
172
|
+
self << header
|
173
|
+
self << yield
|
174
|
+
self << footer
|
175
|
+
end
|
176
|
+
# footer { 'hello'}
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
# The `index' view. Inside your views, you express
|
181
|
+
# the HTML in Ruby. See http://code.whytheluckystiff.net/markaby/.
|
182
|
+
def index
|
183
|
+
|
184
|
+
text "peekbot is an attempt to make vintage computing artifacts visible to the modern intarweb."
|
185
|
+
text "It is a web proxy that allows navigation through online collections of disk images. "
|
186
|
+
text "For disk images in formats which peekbot understands, the file system contained on that disk image can be explored."
|
187
|
+
br
|
188
|
+
br
|
189
|
+
text "Many of the common file types found in the file systems can be converted to a format that can be viewed in a browser."
|
190
|
+
text "For example, tokenised BASIC files can be listed as ASCII, or (some) old graphic formats converted to PNG."
|
191
|
+
text "There is also a track/sector viewer, and any file found on a disk image can be viewed as a hex dump."
|
192
|
+
br
|
193
|
+
br
|
194
|
+
text "peekbot is based on"
|
195
|
+
a 'ripxplore',:href=>'http://ripxplore.rubyforge.org/'
|
196
|
+
text ", a ruby library for identifying and extracting data from disk images."
|
197
|
+
br
|
198
|
+
br
|
199
|
+
text "To get started, you could "
|
200
|
+
a 'view a random picture',:href=>R(RandomFile,:picture)
|
201
|
+
text" or else navigate with peekbot through some online disk image archives by clicking on one of the archive descriptions below."
|
202
|
+
br
|
203
|
+
br
|
204
|
+
ul do
|
205
|
+
[
|
206
|
+
['http://cocomag.dyndns.org/swarc/',"Tim Lindner's Tandy Color Computer archive"],
|
207
|
+
['http://mirrors.apple2.org.za/ftp.apple.asimov.net/',"Asimov Apple 2 image collections mirrored at apple2.org.za"],
|
208
|
+
['http://mirrors.apple2.org.za/landover.no-ip.com/',"Landover Apple 2 image collections mirrored at apple2.org.za"],
|
209
|
+
# ['http://mirrors.apple2.org.za/',"Apple 2 image collections mirrored at apple2.org.za"],
|
210
|
+
['http://www.atariarchives.org/oldhackers/',"Old Hackers Atari User Group Disk Archive (at AtariArchives.org)"],
|
211
|
+
# ['http://www.atariarchives.org/swlibrary/',"Atari Software Library (at AtariArchives.org)"],
|
212
|
+
# ['http://www.cs.vu.nl/pub/ipoorten/atari.8bit/megazine/atr/','Atari Megazine archive'],
|
213
|
+
['ftp://ftp.whtech.com/emulators/pc99/',"TI 99/4A archive at ftp.whtech.com"],
|
214
|
+
['http://disk-images.jamtronix.com/ti99/',"Jamtronix mirror of TOSEC TI 99/4A collection"],
|
215
|
+
['http://disk-images.jamtronix.com/coco/',"Jamtronix mirror of Tandy Color Computer dsks"],
|
216
|
+
['http://www.classic-computers.org.nz/system-80/software_archive.htm',"Terry Stewart's collection of Dick Smith System 80 software"],
|
217
|
+
# ['http://disk-images.jamtronix.com/trs80/',"Jamtronix mirror of the Dutch TRS-80 usergroup collection (http://www.trs80.nl/)"],
|
218
|
+
# ['http://www.zimmers.net/anonftp/pub/cbm/c64/',"Bob Zimmerman's mirror of the FUNET C64 archive"],
|
219
|
+
# ['http://ftp.strassenbahn.tk:81/inntram-public/off-topic/c64/',"Strassenbahn.Tk C64 Archive"],
|
220
|
+
#
|
221
|
+
['http://www.zx81.nl/files.html','ZX81 archive at www.zx81.nl'],
|
222
|
+
].each do |archive_url|
|
223
|
+
li do
|
224
|
+
a archive_url[1], :href=> R(NavigateArchive,archive_url[0])
|
225
|
+
#br
|
226
|
+
#text "[#{archive_url[0]}]"
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
br
|
232
|
+
text "NB - peekbot does not archive any images itself - they are all downloaded into peekbot from the referenced archive on demand."
|
233
|
+
end
|
234
|
+
|
235
|
+
def nav_bar
|
236
|
+
|
237
|
+
return if @working_url.nil?
|
238
|
+
raise "security violation - only localhost can access file:// URLs" if @working_url.archive_url=~/^file:/ && ! (local_mode?)
|
239
|
+
raise 'invalid @working_url' unless @working_url.kind_of?(WorkingUrl)
|
240
|
+
|
241
|
+
if ! @working_url.archive_url.nil? then
|
242
|
+
text ' now peeking at : '
|
243
|
+
if @working_url.image_basename.nil? then
|
244
|
+
text URI.unescape(@working_url.archive_url)
|
245
|
+
a "[go direct]", :href=>@working_url.archive_url unless local_mode?
|
246
|
+
else
|
247
|
+
a @working_url.archive_url, :href=> R(NavigateArchive,@working_url.archive_url)
|
248
|
+
if @native_file.nil? then
|
249
|
+
a @working_url.image_basename, :href=> R(CatalogImage,@working_url.image_url)
|
250
|
+
else
|
251
|
+
a @working_url.image_basename, :href=> R(CatalogImage,@working_url.image_url)
|
252
|
+
text ' / '
|
253
|
+
text @native_file.filename
|
254
|
+
end
|
255
|
+
br
|
256
|
+
a "[download #{File.basename(@working_url.image_url)}]", :href=>@working_url.image_url unless local_mode?
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|
260
|
+
def navigate_archive
|
261
|
+
|
262
|
+
table {
|
263
|
+
tr.header { td { text "Navigation Options"} }
|
264
|
+
@directories.each do |dirname|
|
265
|
+
tr.directory {
|
266
|
+
td {a URI.unescape(dirname), :href=> R(NavigateArchive,PeekBotHelper.combine_url(@working_url.archive_url,dirname).to_s)}
|
267
|
+
td {}
|
268
|
+
}
|
269
|
+
end
|
270
|
+
|
271
|
+
tr.header { td { text "Peekable Images"} }
|
272
|
+
@images.each do |filename|
|
273
|
+
tr.image {
|
274
|
+
td {
|
275
|
+
a 'explore', :href=> R(CatalogImage,PeekBotHelper.combine_url(@working_url.archive_url,filename))
|
276
|
+
if (! local_mode?) then
|
277
|
+
text ' | '
|
278
|
+
begin
|
279
|
+
a 'download', :href=> PeekBotHelper.combine_url(@working_url.archive_url,filename).to_s
|
280
|
+
rescue #nasty kludge but for the moment, just skip over filenames that URI doesn't like
|
281
|
+
end
|
282
|
+
end
|
283
|
+
}
|
284
|
+
td {text URI.unescape(filename) }
|
285
|
+
}
|
286
|
+
end
|
287
|
+
|
288
|
+
if @other_files.length>0 then
|
289
|
+
tr.header { td { text "Other Files"} }
|
290
|
+
@other_files.each do |filename|
|
291
|
+
tr.other_file {
|
292
|
+
td {
|
293
|
+
begin
|
294
|
+
a 'download', :href=> PeekBotHelper.combine_url(@working_url.archive_url,filename).to_s
|
295
|
+
rescue #nasty kludge but for the moment, just skip over filenames that URI doesn't like
|
296
|
+
end
|
297
|
+
}
|
298
|
+
td {text URI.unescape(filename)}
|
299
|
+
}
|
300
|
+
end
|
301
|
+
end
|
302
|
+
}
|
303
|
+
end
|
304
|
+
|
305
|
+
def native_file
|
306
|
+
h3 @native_file.filename
|
307
|
+
case @display_mode
|
308
|
+
when 'hex' then
|
309
|
+
pre.hex_dump @native_file.to_hex_dump
|
310
|
+
when 'to_picture', 'to_png' then
|
311
|
+
scale =600/@native_file.picture_width
|
312
|
+
scale=1 if scale<1
|
313
|
+
img :src=>path_for_cached_native_file(@native_file,:to_picture,@native_file.picture_format.to_s),:width=>@native_file.picture_width*scale,:height=>@native_file.picture_height*scale
|
314
|
+
when 'to_screendump' then
|
315
|
+
#~ scale =800/@native_file.screendump_width
|
316
|
+
#~ scale=1 if scale<1
|
317
|
+
#~ img :src=>path_for_cached_native_file(@native_file,:to_screendump,"png"),:width=>@native_file.screendump_width*scale,:height=>@native_file.screendump_height*scale
|
318
|
+
center {
|
319
|
+
img :src=>path_for_cached_native_file(@native_file,:to_screendump,"png"),:width=>'80%'
|
320
|
+
}
|
321
|
+
else
|
322
|
+
pre.text @native_file.send(@display_mode)
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
def all_pictures
|
327
|
+
h2 "VIEWING ALL PICTURES"
|
328
|
+
@image.pictures.each do |picture_file|
|
329
|
+
h3 picture_file.filename
|
330
|
+
scale =600/picture_file.picture_width
|
331
|
+
scale=1 if scale<1
|
332
|
+
img :src=>path_for_cached_native_file(picture_file,:to_picture,picture_file.picture_format.to_s),:width=>picture_file.picture_width*scale,:height=>picture_file.picture_height*scale, :alt=>picture_file.filename
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
def all_texts
|
337
|
+
h2 "VIEWING ALL TEXT FILES"
|
338
|
+
@image.texts.each do |text_file|
|
339
|
+
h3 text_file.filename
|
340
|
+
pre text_file.to_text
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
344
|
+
def random_file
|
345
|
+
h2 "RANDOM #{@random_filetype.to_s.upcase}"
|
346
|
+
native_file=@native_file
|
347
|
+
text "Peekbot chose this #{@random_filetype.to_s} at random for you. It was found on a disk image that appears to have been originally for the #{@image.host_system.full_name}. You may like to look at:"
|
348
|
+
ul {
|
349
|
+
if (native_file.respond_to?(:to_screendump)) then
|
350
|
+
i=@image.files.index(native_file)
|
351
|
+
li {a "how this file would have looked on the #{@image.host_system.full_name} ", :href=> R(NativeFile,@working_url.image_url,i)+"?display_mode=to_screendump"}
|
352
|
+
end
|
353
|
+
li {a "all #{@image.files.length} files on this disk image", :href=> R(CatalogImage,@working_url.image_url)}
|
354
|
+
if @image.pictures.length>1 then
|
355
|
+
li {a "the #{@image.pictures.length} pictures on this disk image", :href=> R(AllFilesOfType,:picture,@working_url.image_url)}
|
356
|
+
end
|
357
|
+
if @image.texts.length>1 then
|
358
|
+
li {a "the #{@image.texts.length} texts on this disk image", :href=> R(AllFilesOfType,:text,@working_url.image_url)}
|
359
|
+
end
|
360
|
+
|
361
|
+
[:picture,:text,:listing].each do |random_filetype|
|
362
|
+
li {a "#{random_filetype.to_s== @random_filetype.to_s ? "another" : "a"} random #{random_filetype.to_s}", :href=> R(RandomFile,random_filetype)}
|
363
|
+
end
|
364
|
+
}
|
365
|
+
|
366
|
+
h3 native_file.filename
|
367
|
+
if random_filetype.to_s==:picture.to_s then
|
368
|
+
scale =600/native_file.picture_width
|
369
|
+
scale=1 if scale<1
|
370
|
+
img :src=>path_for_cached_native_file(native_file,:to_picture,native_file.picture_format.to_s),:width=>native_file.picture_width*scale,:height=>native_file.picture_height*scale, :alt=>native_file.filename
|
371
|
+
else
|
372
|
+
pre native_file.send("to_#{random_filetype}".to_sym)
|
373
|
+
end
|
374
|
+
end
|
375
|
+
|
376
|
+
|
377
|
+
def image_header
|
378
|
+
div.image_attributes do
|
379
|
+
h2 'image summary'
|
380
|
+
table.image_info do
|
381
|
+
[:host_system,:image_format,:file_system,:track_count,:volume_name].each do |att|
|
382
|
+
if (@image.respond_to?(att)) then
|
383
|
+
att_value=@image.send(att)
|
384
|
+
tr {
|
385
|
+
td att.to_s
|
386
|
+
td att_value
|
387
|
+
} unless (att_value.nil? || att_value==0)
|
388
|
+
end
|
389
|
+
end
|
390
|
+
if @image.pictures.length>0 then
|
391
|
+
tr {
|
392
|
+
td "pictures"
|
393
|
+
td {
|
394
|
+
text @image.pictures.length
|
395
|
+
a "VIEW ALL", :href=> R(AllFilesOfType,:picture,@working_url.image_url)
|
396
|
+
}
|
397
|
+
}
|
398
|
+
end
|
399
|
+
if @image.texts.length>0 then
|
400
|
+
tr {
|
401
|
+
td "text files"
|
402
|
+
td {
|
403
|
+
text @image.texts.length
|
404
|
+
a "VIEW ALL", :href=> R(AllFilesOfType,:text,@working_url.image_url)
|
405
|
+
}
|
406
|
+
}
|
407
|
+
|
408
|
+
end
|
409
|
+
|
410
|
+
|
411
|
+
end
|
412
|
+
end
|
413
|
+
end
|
414
|
+
|
415
|
+
def track_list
|
416
|
+
div.track_list do
|
417
|
+
h3 'tracks'
|
418
|
+
@image.track_list.each do |track|
|
419
|
+
a "[%02X]" % track,:href=>R(SectorViewer,@working_url.image_url,track,@image.sectors_in_track(track)[0])+"\#sector_viewer",:rel=>"nofollow"
|
420
|
+
text " "
|
421
|
+
end
|
422
|
+
end unless @image.track_list.length==0
|
423
|
+
end
|
424
|
+
|
425
|
+
def sector_viewer
|
426
|
+
image_header
|
427
|
+
div.sector_viewer do
|
428
|
+
h2 "sector viewer"
|
429
|
+
h3 {a "sectors in track $#{"%02x" % track}",:name=>"sector_viewer"}
|
430
|
+
@image.sectors_in_track(track).each do |sector|
|
431
|
+
a "[%02X]" % sector,:href=>R(SectorViewer,@working_url.image_url,track,sector)+"\#sector_viewer",:rel=>"nofollow"
|
432
|
+
text " "
|
433
|
+
end
|
434
|
+
pre @image.dump_sector(track,sector)
|
435
|
+
|
436
|
+
end
|
437
|
+
|
438
|
+
track_list
|
439
|
+
end
|
440
|
+
def catalog_image
|
441
|
+
|
442
|
+
image_header
|
443
|
+
|
444
|
+
div.image_catalog do
|
445
|
+
h2 'all files'
|
446
|
+
|
447
|
+
table.image_catalog {
|
448
|
+
tr {
|
449
|
+
th 'View As'
|
450
|
+
th 'Filename'
|
451
|
+
th 'Type'
|
452
|
+
th 'Length (bytes)'
|
453
|
+
th 'Load Address'
|
454
|
+
}
|
455
|
+
|
456
|
+
@image.files.length.times do |i|
|
457
|
+
file=@image.files[i]
|
458
|
+
tr {
|
459
|
+
|
460
|
+
td {
|
461
|
+
a "HEX",:href=>R(NativeFile,@working_url.image_url,i)+"?display_mode=hex",:rel=>"nofollow"
|
462
|
+
[:to_listing,:to_text,:to_picture,:to_screendump].each do |display_mode|
|
463
|
+
if file.respond_to?(display_mode) then
|
464
|
+
text " | "
|
465
|
+
description=display_mode.to_s.sub("to_","").upcase
|
466
|
+
a description,:href=>R(NativeFile,@working_url.image_url,i)+"?display_mode=#{display_mode.to_s}"
|
467
|
+
end
|
468
|
+
end
|
469
|
+
}
|
470
|
+
td file.filename
|
471
|
+
td file.type_description
|
472
|
+
td "$%04X" % file.contents.length
|
473
|
+
td "$%04X" % file.load_address
|
474
|
+
}
|
475
|
+
end
|
476
|
+
}
|
477
|
+
end unless @image.files.length==0
|
478
|
+
track_list
|
479
|
+
end
|
480
|
+
|
481
|
+
def header
|
482
|
+
h1.header do
|
483
|
+
a :href=>'/' do
|
484
|
+
img :src=>'/static/peekbot.png', :height=>130, :width=>229
|
485
|
+
end
|
486
|
+
end
|
487
|
+
nav_bar
|
488
|
+
end
|
489
|
+
|
490
|
+
def footer
|
491
|
+
p.footer do
|
492
|
+
text 'peekbot '
|
493
|
+
a "version #{RipXplore::VERSION}" , :href=>'/version_info'
|
494
|
+
text ' powered by '
|
495
|
+
a 'ripxplore',:href=>'http://ripxplore.rubyforge.org/'
|
496
|
+
text ', '
|
497
|
+
a 'camping',:href=>'http://code.whytheluckystiff.net/camping/'
|
498
|
+
text ' & '
|
499
|
+
a 'ruby',:href=>'http://www.ruby-lang.org/'
|
500
|
+
i {
|
501
|
+
text "(last updated:#{LAST_PEEKBOT_UPDATE} : "
|
502
|
+
a 'change log',:href=>'/static/changes.txt'
|
503
|
+
text ")"
|
504
|
+
}
|
505
|
+
br
|
506
|
+
text 'whipped up by '
|
507
|
+
a 'jonno',:href=>'http://blog.jamtronix.com/'
|
508
|
+
text ' at '
|
509
|
+
a 'jamtronix dot com',:href=>'http://www.jamtronix.com/'
|
510
|
+
br
|
511
|
+
br
|
512
|
+
text "want to"
|
513
|
+
a "help build peekbot?" , :href=>'/help_wanted/'
|
514
|
+
|
515
|
+
end
|
516
|
+
end
|
517
|
+
|
518
|
+
|
519
|
+
def version_info
|
520
|
+
table {
|
521
|
+
tr {
|
522
|
+
td 'RipXplore Version'
|
523
|
+
td RipXplore::VERSION
|
524
|
+
}
|
525
|
+
tr {
|
526
|
+
td 'Image Format File Extensions'
|
527
|
+
td ImageFormat.all_possible_extensions.join(", ")
|
528
|
+
}
|
529
|
+
|
530
|
+
tr {
|
531
|
+
td 'File Systems'
|
532
|
+
td FileSystem.all_file_systems.join(", ")
|
533
|
+
}
|
534
|
+
|
535
|
+
tr {
|
536
|
+
td 'Native File Types'
|
537
|
+
td NativeFileType.all_native_file_types.join(", ")
|
538
|
+
}
|
539
|
+
}
|
540
|
+
end
|
541
|
+
|
542
|
+
def help_wanted
|
543
|
+
h1 "help wanted!"
|
544
|
+
text "peekbot is very much a work in progress. I am putting this version out
|
545
|
+
now as a 'proof of concept', and to see if anyone finds it intersting
|
546
|
+
enough to help work on it. "
|
547
|
+
br
|
548
|
+
br
|
549
|
+
text "Areas where assistance would be most valuable:"
|
550
|
+
br
|
551
|
+
br
|
552
|
+
text" 1) web design. peekbot uses camping, with markaby + css for layout, so
|
553
|
+
hopefully someone with css chops could make it look much slicker
|
554
|
+
without too much effort. I'm sure the UI and navigation could be much
|
555
|
+
improved as well - I am a humble backend hack so would be very happy
|
556
|
+
to work with anyone who knew how to make a web app easy to use. "
|
557
|
+
br
|
558
|
+
br
|
559
|
+
text"2) plug-ins for image formats (especially .sdk), file systems, and
|
560
|
+
native file types. My goal for ripxplore is to have everything in 100%
|
561
|
+
native ruby for maximum portability, and the framework is convoluted
|
562
|
+
and not yet well documented, so it's probably a bit early for anyone
|
563
|
+
but a masochist ruby freak to try writing a plugin unaided, but I
|
564
|
+
would be happy to work with anyone interested, or else if someone has
|
565
|
+
some images they'd particularly like to see peekbot interpret, and can
|
566
|
+
give specs or source code i can convert, that would be great too.
|
567
|
+
There is a heap of opporunity for the C64 particularly, including
|
568
|
+
converters to detect various image formats and render as PNG."
|
569
|
+
br
|
570
|
+
br
|
571
|
+
text"3) documentation - how to use the website, how to use ripxplore for
|
572
|
+
specialised conversion and extraction tasks & how to write a plugin."
|
573
|
+
br
|
574
|
+
br
|
575
|
+
text"If you are interested in any of the above, contact me vie email at jonno at jamtronix dot com"
|
576
|
+
end
|
577
|
+
end
|
578
|
+
|
579
|
+
if __FILE__ == $0 then
|
580
|
+
#run from command line
|
581
|
+
port=6502
|
582
|
+
require 'mongrel'
|
583
|
+
require 'mongrel/camping'
|
584
|
+
begin
|
585
|
+
config = Mongrel::Configurator.new do
|
586
|
+
listener :port => port do
|
587
|
+
uri "/", :handler => Mongrel::Camping::CampingHandler.new(PeekBot)
|
588
|
+
trap("INT") { stop }
|
589
|
+
run
|
590
|
+
end
|
591
|
+
end
|
592
|
+
puts "** peekbot is running at http://localhost:#{port}/"
|
593
|
+
config.join
|
594
|
+
rescue Errno::EADDRINUSE
|
595
|
+
puts "** ERROR : port #{port} in use - is peekbot already running?"
|
596
|
+
end
|
597
|
+
|
598
|
+
end
|
599
|
+
|
data/peekbot_helper.rb
ADDED
@@ -0,0 +1,153 @@
|
|
1
|
+
|
2
|
+
lib_path=File.expand_path(File.dirname(__FILE__)+"//..//ripxplore//lib")
|
3
|
+
$:.unshift(lib_path) unless $:.include?(lib_path)
|
4
|
+
|
5
|
+
require 'RipXplore'
|
6
|
+
require 'hpricot'
|
7
|
+
require 'open-uri'
|
8
|
+
|
9
|
+
WEB_PAGE_EXTENSIONS=['.html','.htm','.php','.asp','.aspx']
|
10
|
+
class PeekBotHelper
|
11
|
+
def PeekBotHelper.get_archive_contents(archive_url)
|
12
|
+
|
13
|
+
images=[]
|
14
|
+
directories=[]
|
15
|
+
other_files=[]
|
16
|
+
if (url_is_website?(archive_url)) then
|
17
|
+
path=URI.split(archive_url)[5]
|
18
|
+
directories<<'../' unless (path=="" || path=='/')
|
19
|
+
html=get_url(archive_url)
|
20
|
+
doc=Hpricot(html)
|
21
|
+
doc.search("a[@href]").each do |a|
|
22
|
+
href=a.attributes["href"]
|
23
|
+
href.sub!("/index.html","/")
|
24
|
+
next if href=~/:/ #skip anything that has a colon - we only want relative URLs
|
25
|
+
next if href=~/#/ #skip anything that has a hash - we don't want anchored links
|
26
|
+
next if href=~/\?/ #skip anything thats a query
|
27
|
+
next if href=~/^\.+$/ #skip . and ..
|
28
|
+
if (href=~/\w\/$/) || WEB_PAGE_EXTENSIONS.include?(File.extname(href))
|
29
|
+
# if !(href=~/^\//) then #directories end with a /, but skip absolute paths
|
30
|
+
|
31
|
+
directories<<(href)
|
32
|
+
# end
|
33
|
+
else
|
34
|
+
if FileSystemImage.is_file_system_image_filename?(href) then
|
35
|
+
images<<href
|
36
|
+
else
|
37
|
+
other_files<<href unless href=~/\/$/
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
elsif (url_is_ftpsite?(archive_url)) then
|
42
|
+
|
43
|
+
require 'net/ftp'
|
44
|
+
require 'uri'
|
45
|
+
ftp_uri=URI.parse(archive_url)
|
46
|
+
partial_path="/"
|
47
|
+
ftp_uri.path.split("/").each do |p|
|
48
|
+
next if p.length<1
|
49
|
+
directories<<partial_path
|
50
|
+
partial_path+=(p+"/")
|
51
|
+
end
|
52
|
+
ftp=Net::FTP.new(ftp_uri.host)
|
53
|
+
ftp.login(ftp_uri.user.nil? ? 'anonymous': ftp_uri.user,ftp_uri.password)
|
54
|
+
ftp.nlst(URI.unescape(ftp_uri.path)).each do |f|
|
55
|
+
#yes this is a horrible hack - assume that filenames with a . are files, if no dot then assume it's a directory
|
56
|
+
if f=~/\./ then
|
57
|
+
if FileSystemImage.is_file_system_image_filename?(f) then
|
58
|
+
images<<f
|
59
|
+
else
|
60
|
+
other_files<<f
|
61
|
+
end
|
62
|
+
else
|
63
|
+
f="/"+f unless f=~/\// #if we are at the root directory, make sure all paths start with a /
|
64
|
+
directories<<f
|
65
|
+
end
|
66
|
+
end
|
67
|
+
ftp.quit
|
68
|
+
else #assume it's a local file
|
69
|
+
dirname=PeekBotHelper.file_url_to_pathname(archive_url)
|
70
|
+
dirname+=File::Separator unless dirname=~/#{File::Separator}$/
|
71
|
+
# "stripped #{archive_url} to #{dirname}"
|
72
|
+
Dir.foreach(dirname) do |filename|
|
73
|
+
next if filename=="."
|
74
|
+
images<<filename if FileSystemImage.is_file_system_image_filename?(filename)
|
75
|
+
full_dir_path=file_url_to_pathname(combine_url("#{dirname}",filename))
|
76
|
+
directories<<filename if FileTest.directory?(full_dir_path)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
[images.sort,directories.sort,other_files.sort]
|
80
|
+
end
|
81
|
+
|
82
|
+
@@url_cache={}
|
83
|
+
def PeekBotHelper.get_url(url)
|
84
|
+
if @@url_cache[url].nil? then
|
85
|
+
@@url_cache[url]=open(url).read
|
86
|
+
end
|
87
|
+
@@url_cache[url]
|
88
|
+
end
|
89
|
+
|
90
|
+
def PeekBotHelper.url_is_website?(url)
|
91
|
+
url=~/^http[s]?:/ ?true : false
|
92
|
+
end
|
93
|
+
|
94
|
+
def PeekBotHelper.url_is_ftpsite?(url)
|
95
|
+
url=~/^ftp?:/ ?true : false
|
96
|
+
end
|
97
|
+
|
98
|
+
def PeekBotHelper.url_is_local_file?(url)
|
99
|
+
return true if (url=~/^file:/ ) || (url=~/^[^\/]*\\/ ) || (url[1].chr==':') || (url[0].chr==File::Separator)
|
100
|
+
false
|
101
|
+
end
|
102
|
+
|
103
|
+
def PeekBotHelper.file_url_to_pathname(url)
|
104
|
+
pathname=URI.unescape(url.sub(/file:\/+/,""))
|
105
|
+
# puts pathname
|
106
|
+
# puts pathname[1].chr
|
107
|
+
pathname="#{File::Separator}#{pathname}" unless ((pathname[1].chr==':') || (pathname[0].chr==File::Separator))
|
108
|
+
pathname
|
109
|
+
end
|
110
|
+
def PeekBotHelper.combine_url(first_url,second_url)
|
111
|
+
|
112
|
+
# URI::FTP.join seems broken (complains about missing typcode) so here's a horrible hacky replacement
|
113
|
+
# puts "joining #{first_url},#{second_url}"
|
114
|
+
if url_is_ftpsite?(first_url) then
|
115
|
+
ftp_uri=URI.parse(first_url)
|
116
|
+
result=URI::FTP.new('ftp',
|
117
|
+
"#{ftp_uri.user}:#{ftp_uri.password}",
|
118
|
+
ftp_uri.host, ftp_uri.port, nil,
|
119
|
+
second_url,
|
120
|
+
nil, nil,nil).to_s.sub("//:@","//")
|
121
|
+
elsif url_is_local_file?(first_url) then
|
122
|
+
dir_path=PeekBotHelper.file_url_to_pathname(first_url)
|
123
|
+
result="file://#{File.expand_path(second_url,dir_path)}"
|
124
|
+
else
|
125
|
+
result=URI.join(first_url,second_url)
|
126
|
+
end
|
127
|
+
# puts "RESULT: #{result}"
|
128
|
+
result
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
class WorkingUrl
|
133
|
+
attr_accessor :archive_url,:image_url
|
134
|
+
def initialize(archive_url=nil,image_url=nil)
|
135
|
+
#when running under Apache + mod_rewrite, the second / in http:// is getting stripped, so add it back in if necessary
|
136
|
+
@archive_url=archive_url.sub(/:\/([^\/])/,'://\1').gsub(' ','%20').gsub('#','%23').gsub('[','%5B').gsub(']','%5D') unless archive_url.nil?
|
137
|
+
@image_url=image_url.sub(/:\/([^\/])/,'://\1').gsub(' ','%20').gsub('#','%23').gsub('[','%5B').gsub(']','%5D') unless image_url.nil?
|
138
|
+
end
|
139
|
+
|
140
|
+
def image_basename
|
141
|
+
if image_url.nil? then
|
142
|
+
nil
|
143
|
+
else
|
144
|
+
URI.unescape(File.basename(image_url))
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
|
149
|
+
def WorkingUrl.split_image_url_into_parts(url)
|
150
|
+
return WorkingUrl.new(File.dirname(url)+"/",url)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
data/static/changes.txt
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
PEEKBOT CHANGE LOG
|
2
|
+
|
3
|
+
2008-12-14
|
4
|
+
* initial support for ZX81
|
5
|
+
|
6
|
+
2008-12-13
|
7
|
+
* low-res Coco pictures
|
8
|
+
|
9
|
+
2008-12-07
|
10
|
+
* detokenise TI 99/4a BASIC files
|
11
|
+
|
12
|
+
2008-12-05
|
13
|
+
* eliminate timeout (500 camping error) the first time a random picture/text/listing was viewed when app restarted
|
14
|
+
|
15
|
+
2008-11-30
|
16
|
+
* added 'screendump' feature
|
17
|
+
|
18
|
+
2008-11-29
|
19
|
+
* now caches PNG & GIF files (rather than re-extracting from image on each view)
|
20
|
+
* added random text and listing feature
|
21
|
+
|
22
|
+
2008-11-28
|
23
|
+
* added RLE support
|
24
|
+
|
25
|
+
2008-11-25
|
26
|
+
* added GIF support
|
27
|
+
|
28
|
+
2008-11-25
|
29
|
+
* 'All Pictures' & 'All Texts' views
|
30
|
+
* Random Picture page
|
31
|
+
|
32
|
+
2008-11-24
|
33
|
+
* Apple 2 hires picture improvements
|
34
|
+
|
35
|
+
2008-11-21
|
36
|
+
* added support for Tandy Color Computer files (Tim Lindner)
|
37
|
+
|
38
|
+
2008-11-15
|
39
|
+
* fixed some TI 99/4a file system bugs
|
40
|
+
* added support for TI Artist images
|
41
|
+
|
42
|
+
|
43
|
+
2008-11-14
|
44
|
+
* added 'help wanted' page
|
45
|
+
* added support for FTP archives
|
46
|
+
* added link to TI 99/4a archive at ftp.whtech.com
|
47
|
+
* partial support for TI 99/4a formats
|
48
|
+
|
49
|
+
2008-11-13
|
50
|
+
* removed listing (disassembly) of binary files
|
51
|
+
* removed links to Apple 2 archives that aren't mainly DSK files
|
52
|
+
* made peekbot logo in page header clickable
|
53
|
+
* links to hex dumps now marked 'rel=nofollow' (no point getting a hex dump into google)
|
54
|
+
|
55
|
+
2008-11-09
|
56
|
+
* first public version
|
data/static/peekbot.png
ADDED
Binary file
|
data/static/style.css
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
body { background-color: #cde; color: #333; }
|
2
|
+
|
3
|
+
body, p, ol, ul, td, th {
|
4
|
+
font-family: verdana, arial, helvetica, sans-serif;
|
5
|
+
font-size: 22px;
|
6
|
+
line-height: 28px;
|
7
|
+
}
|
8
|
+
|
9
|
+
pre {
|
10
|
+
background-color: #eee;
|
11
|
+
padding: 10px;
|
12
|
+
font-size: 16px;
|
13
|
+
white-space: pre-wrap; /* css-3 */
|
14
|
+
white-space: -moz-pre-wrap !important; /* Mozilla, since 1999 */
|
15
|
+
white-space: -pre-wrap; /* Opera 4-6 */
|
16
|
+
white-space: -o-pre-wrap; /* Opera 7 */
|
17
|
+
word-wrap: break-word; /* Internet Explorer 5.5+ */
|
18
|
+
|
19
|
+
}
|
20
|
+
|
21
|
+
|
22
|
+
td,th {
|
23
|
+
padding-left: 10px;
|
24
|
+
padding-right: 10px;
|
25
|
+
}
|
26
|
+
|
27
|
+
tr.header {
|
28
|
+
background-color: #aaf;
|
29
|
+
}
|
30
|
+
pre.hex_dump {
|
31
|
+
background-color: #aaf;
|
32
|
+
}
|
33
|
+
|
34
|
+
p.footer {
|
35
|
+
font-size: 12px;
|
36
|
+
line-height: 16px;
|
37
|
+
}
|
38
|
+
|
39
|
+
h2 {
|
40
|
+
background-color: #bcd;
|
41
|
+
}
|
42
|
+
|
43
|
+
h3 {
|
44
|
+
background-color: #bcd;
|
45
|
+
}
|
46
|
+
|
47
|
+
img {
|
48
|
+
border: 0px;
|
49
|
+
}
|
50
|
+
|
51
|
+
li.directory {
|
52
|
+
background-color: #89a;
|
53
|
+
}
|
54
|
+
|
55
|
+
li.image {
|
56
|
+
background-color: #9ab;
|
57
|
+
}
|
58
|
+
|
59
|
+
li.other_file {
|
60
|
+
background-color: #abc;
|
61
|
+
}
|
62
|
+
|
63
|
+
a { color: #000; }
|
64
|
+
a:visited { color: #666; }
|
65
|
+
a:hover { color: #fff; background-color:#000; }
|
66
|
+
|
67
|
+
|
metadata
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
rubygems_version: 0.9.4
|
3
|
+
specification_version: 1
|
4
|
+
name: peekbot
|
5
|
+
version: !ruby/object:Gem::Version
|
6
|
+
version: 0.0.1
|
7
|
+
date: 2008-12-20 00:00:00 +11:00
|
8
|
+
summary: a web app that allows browsing of disk images as used by vintage computer emulators
|
9
|
+
require_paths:
|
10
|
+
- lib
|
11
|
+
email: jonno@jamtronix.com
|
12
|
+
homepage: http://peekbot.rubyforge.org
|
13
|
+
rubyforge_project:
|
14
|
+
description:
|
15
|
+
autorequire:
|
16
|
+
default_executable:
|
17
|
+
bindir: .
|
18
|
+
has_rdoc: false
|
19
|
+
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">"
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.0.0
|
24
|
+
version:
|
25
|
+
platform: ruby
|
26
|
+
signing_key:
|
27
|
+
cert_chain:
|
28
|
+
post_install_message:
|
29
|
+
authors:
|
30
|
+
- Jonno Downes
|
31
|
+
files:
|
32
|
+
- ./dispatch.fcgi
|
33
|
+
- ./m.bat
|
34
|
+
- ./make_all_images.rb
|
35
|
+
- ./peekbot.rb
|
36
|
+
- ./peekbot_helper.rb
|
37
|
+
- ./static
|
38
|
+
- ./static/changes.txt
|
39
|
+
- ./static/peekbot.png
|
40
|
+
- ./static/style.css
|
41
|
+
- static/changes.txt
|
42
|
+
- static/peekbot.png
|
43
|
+
- static/style.css
|
44
|
+
test_files: []
|
45
|
+
|
46
|
+
rdoc_options: []
|
47
|
+
|
48
|
+
extra_rdoc_files: []
|
49
|
+
|
50
|
+
executables:
|
51
|
+
- peekbot.rb
|
52
|
+
extensions: []
|
53
|
+
|
54
|
+
requirements: []
|
55
|
+
|
56
|
+
dependencies:
|
57
|
+
- !ruby/object:Gem::Dependency
|
58
|
+
name: hpricot
|
59
|
+
version_requirement:
|
60
|
+
version_requirements: !ruby/object:Gem::Version::Requirement
|
61
|
+
requirements:
|
62
|
+
- - ">"
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: 0.0.0
|
65
|
+
version:
|
66
|
+
- !ruby/object:Gem::Dependency
|
67
|
+
name: ripxplore
|
68
|
+
version_requirement:
|
69
|
+
version_requirements: !ruby/object:Gem::Version::Requirement
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: 0.0.3
|
74
|
+
version:
|
75
|
+
- !ruby/object:Gem::Dependency
|
76
|
+
name: camping
|
77
|
+
version_requirement:
|
78
|
+
version_requirements: !ruby/object:Gem::Version::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 0.0.0
|
83
|
+
version:
|
84
|
+
- !ruby/object:Gem::Dependency
|
85
|
+
name: mongrel
|
86
|
+
version_requirement:
|
87
|
+
version_requirements: !ruby/object:Gem::Version::Requirement
|
88
|
+
requirements:
|
89
|
+
- - ">="
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: 0.3.10
|
92
|
+
version:
|