hobix 0.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/bin/hobix +90 -0
- data/lib/hobix/api.rb +91 -0
- data/lib/hobix/article.rb +22 -0
- data/lib/hobix/base.rb +477 -0
- data/lib/hobix/bixwik.rb +200 -0
- data/lib/hobix/commandline.rb +661 -0
- data/lib/hobix/comments.rb +99 -0
- data/lib/hobix/config.rb +39 -0
- data/lib/hobix/datamarsh.rb +110 -0
- data/lib/hobix/entry.rb +83 -0
- data/lib/hobix/facets/comments.rb +74 -0
- data/lib/hobix/facets/publisher.rb +314 -0
- data/lib/hobix/facets/trackbacks.rb +80 -0
- data/lib/hobix/linklist.rb +76 -0
- data/lib/hobix/out/atom.rb +92 -0
- data/lib/hobix/out/erb.rb +64 -0
- data/lib/hobix/out/okaynews.rb +55 -0
- data/lib/hobix/out/quick.rb +312 -0
- data/lib/hobix/out/rdf.rb +97 -0
- data/lib/hobix/out/redrum.rb +26 -0
- data/lib/hobix/out/rss.rb +115 -0
- data/lib/hobix/plugin/bloglines.rb +73 -0
- data/lib/hobix/plugin/calendar.rb +220 -0
- data/lib/hobix/plugin/flickr.rb +110 -0
- data/lib/hobix/plugin/recent_comments.rb +82 -0
- data/lib/hobix/plugin/sections.rb +91 -0
- data/lib/hobix/plugin/tags.rb +60 -0
- data/lib/hobix/publish/ping.rb +53 -0
- data/lib/hobix/publish/replicate.rb +283 -0
- data/lib/hobix/publisher.rb +18 -0
- data/lib/hobix/search/dictionary.rb +141 -0
- data/lib/hobix/search/porter_stemmer.rb +203 -0
- data/lib/hobix/search/simple.rb +209 -0
- data/lib/hobix/search/vector.rb +100 -0
- data/lib/hobix/storage/filesys.rb +398 -0
- data/lib/hobix/trackbacks.rb +94 -0
- data/lib/hobix/util/objedit.rb +193 -0
- data/lib/hobix/util/patcher.rb +155 -0
- data/lib/hobix/webapp/cli.rb +195 -0
- data/lib/hobix/webapp/htmlform.rb +107 -0
- data/lib/hobix/webapp/message.rb +177 -0
- data/lib/hobix/webapp/urigen.rb +141 -0
- data/lib/hobix/webapp/webrick-servlet.rb +90 -0
- data/lib/hobix/webapp.rb +723 -0
- data/lib/hobix/weblog.rb +860 -0
- data/lib/hobix.rb +223 -0
- metadata +87 -0
@@ -0,0 +1,94 @@
|
|
1
|
+
#
|
2
|
+
# = hobix/trackbacks.rb
|
3
|
+
#
|
4
|
+
# Hobix command-line weblog system, API for trackbacks.
|
5
|
+
#
|
6
|
+
# Copyright (c) 2003-2004 why the lucky stiff
|
7
|
+
#
|
8
|
+
# Written & maintained by why the lucky stiff <why@ruby-lang.org>
|
9
|
+
#
|
10
|
+
# This program is free software, released under a BSD license.
|
11
|
+
# See COPYING for details.
|
12
|
+
#
|
13
|
+
#--
|
14
|
+
# $Id$
|
15
|
+
#++
|
16
|
+
|
17
|
+
require 'hobix/facets/trackbacks'
|
18
|
+
require 'time'
|
19
|
+
require 'rexml/document'
|
20
|
+
|
21
|
+
module Hobix
|
22
|
+
module Out
|
23
|
+
class Quick
|
24
|
+
prepend_def :entry_title_erb, %{
|
25
|
+
<+ entry_trackback_rdf +>
|
26
|
+
}
|
27
|
+
|
28
|
+
def entry_trackback_rdf_erb; %{
|
29
|
+
<!--
|
30
|
+
<%= trackback_rdf_for( weblog, entry ) %>
|
31
|
+
-->
|
32
|
+
} end
|
33
|
+
|
34
|
+
append_def :entry_erb, %{
|
35
|
+
<% if entry and not defined? entries %><+ entry_trackback +><% end %>
|
36
|
+
}
|
37
|
+
|
38
|
+
def entry_trackback_erb; %{
|
39
|
+
<a name="trackbacks"></a>
|
40
|
+
<div id="trackbacks">
|
41
|
+
<% entry_id = entry.id %>
|
42
|
+
<% trackbacks = weblog.storage.load_attached( entry_id, "trackbacks") rescue [] %>
|
43
|
+
<% trackbacks.each do |trackback| %>
|
44
|
+
<div class="entry">
|
45
|
+
<div class="entryAttrib">
|
46
|
+
<div class="entryAuthor"><h3><%= trackback.blog_name %></h3></div>
|
47
|
+
<div class="entryTime">tracked back on <%= trackback.created.strftime("<nobr>%d %b %Y</nobr> at <nobr>%I:%M %p</nobr>" ) %></div>
|
48
|
+
</div>
|
49
|
+
<div class="entryContentOuter"><div class="entryContent">
|
50
|
+
<h3><a href="<%= trackback.url %>"><%= trackback.title %></a></h3>
|
51
|
+
<%= trackback.excerpt %>
|
52
|
+
</div></div>
|
53
|
+
</div>
|
54
|
+
<% end %>
|
55
|
+
</div>
|
56
|
+
} end
|
57
|
+
|
58
|
+
private
|
59
|
+
def trackback_rdf_for( weblog, entry )
|
60
|
+
trackback_link = '%s/control/trackback/%s' % [weblog.link, entry.id]
|
61
|
+
doc = REXML::Document.new
|
62
|
+
rdf = doc.add_element( "rdf:RDF" )
|
63
|
+
rdf.add_namespace( "rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#" )
|
64
|
+
rdf.add_namespace( "trackback", "http://madskills.com/public/xml/rss/module/trackback/" )
|
65
|
+
rdf.add_namespace( "dc", "http://purl.org/dc/elements/1.1/" )
|
66
|
+
desc = rdf.add_element( "rdf:Description" )
|
67
|
+
desc.add_attribute( "rdf:about", "")
|
68
|
+
desc.add_attribute( "trackback:ping", trackback_link )
|
69
|
+
desc.add_attribute( "dc:title", entry.title )
|
70
|
+
desc.add_attribute( "dc:identifier", entry.link )
|
71
|
+
## i've dropped the following fields because i don't think they're used, and
|
72
|
+
## dc:description in particular will potentially double the size of the
|
73
|
+
## html pages. if they're actually useful to anyone, please re-add.
|
74
|
+
##
|
75
|
+
## desc.add_attribute( "dc:description", ( entry.summary || entry.content ).to_html )
|
76
|
+
## desc.add_attribute( "dc:creator", entry.author )
|
77
|
+
## desc.add_attribute( "dc:date", entry.created.xmlschema )
|
78
|
+
doc.to_s
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
class Trackback < BaseContent
|
84
|
+
_! "Trackback Information"
|
85
|
+
_ :blog_name, :edit_as => :text, :req => true
|
86
|
+
_ :url, :edit_as => :text, :req => true
|
87
|
+
_ :title, :edit_as => :text, :req => true
|
88
|
+
_ :excerpt , :edit_as => :text, :req => true
|
89
|
+
_ :created, :edit_as => :datetime
|
90
|
+
_ :ipaddress, :edit_as => :text
|
91
|
+
|
92
|
+
yaml_type "tag:hobix.com,2005:trackback"
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,193 @@
|
|
1
|
+
#
|
2
|
+
# = hobix/util/objedit
|
3
|
+
#
|
4
|
+
# Hobix command-line weblog system.
|
5
|
+
#
|
6
|
+
# Copyright (c) 2003-2004 why the lucky stiff
|
7
|
+
#
|
8
|
+
# Written & maintained by why the lucky stiff <why@ruby-lang.org>
|
9
|
+
#
|
10
|
+
# This program is free software, released under a BSD license.
|
11
|
+
# See COPYING for details.
|
12
|
+
#
|
13
|
+
#--
|
14
|
+
# $Id$
|
15
|
+
#++
|
16
|
+
require 'ncurses'
|
17
|
+
require 'yaml'
|
18
|
+
|
19
|
+
module Hobix
|
20
|
+
module Util
|
21
|
+
# The ObjEdit class provides an ncurses-based editor for
|
22
|
+
# modifying Ruby objects. The ncurses library must be installed,
|
23
|
+
# which is available at http://ncurses-ruby.berlios.de/.
|
24
|
+
def self.ObjEdit( obj )
|
25
|
+
include Ncurses
|
26
|
+
include Ncurses::Form
|
27
|
+
# Initialize ncurses
|
28
|
+
scr = Ncurses.initscr
|
29
|
+
out_obj = nil
|
30
|
+
Ncurses.start_color
|
31
|
+
Ncurses.cbreak
|
32
|
+
Ncurses.keypad scr, true
|
33
|
+
|
34
|
+
# Initialize few color pairs
|
35
|
+
Ncurses.init_pair 1, COLOR_RED, COLOR_BLACK
|
36
|
+
Ncurses.init_pair 2, COLOR_WHITE, COLOR_BLACK
|
37
|
+
Ncurses.init_pair 3, COLOR_YELLOW, COLOR_BLACK
|
38
|
+
Ncurses.init_pair 4, COLOR_RED, COLOR_BLACK
|
39
|
+
scr.bkgd Ncurses.COLOR_PAIR(2)
|
40
|
+
|
41
|
+
# Initialize the fields
|
42
|
+
y = 0
|
43
|
+
labels = []
|
44
|
+
label_end = 12
|
45
|
+
ivars = []
|
46
|
+
fields =
|
47
|
+
obj.property_map.collect do |ivar, flag, edit_as|
|
48
|
+
ht, wt = 1, 60
|
49
|
+
case edit_as
|
50
|
+
when :text
|
51
|
+
field = FIELD.new ht, wt, y, 1, 0, 0
|
52
|
+
when :textarea
|
53
|
+
ht, wt = 5, 60
|
54
|
+
field = FIELD.new ht, wt, y, 1, 60, 0
|
55
|
+
end
|
56
|
+
if y + ht + 8 >= Ncurses.LINES
|
57
|
+
field.set_new_page TRUE
|
58
|
+
y = 0
|
59
|
+
end
|
60
|
+
labels << [y + 2, ivar, ht, wt]
|
61
|
+
ivars << ivar[1..-1]
|
62
|
+
label_end = ivar.length + 3 if label_end < ivar.length + 3
|
63
|
+
y += ht + 1
|
64
|
+
|
65
|
+
field.field_opts_off O_AUTOSKIP
|
66
|
+
field.set_field_back A_REVERSE
|
67
|
+
field.set_field_fore A_BOLD
|
68
|
+
field_write( field, obj.instance_variable_get( ivar ) )
|
69
|
+
field
|
70
|
+
end
|
71
|
+
|
72
|
+
# Create the form
|
73
|
+
my_form = FORM.new fields
|
74
|
+
my_form.user_object = "Editing #{ obj.class }"
|
75
|
+
rows, cols = [], []
|
76
|
+
my_form.scale_form rows, cols
|
77
|
+
|
78
|
+
# Create the window
|
79
|
+
my_win = WINDOW.new rows[0] + 3, cols[0] + 20, 0, 0
|
80
|
+
my_win.bkgd Ncurses.COLOR_PAIR( 3 )
|
81
|
+
my_win.keypad TRUE
|
82
|
+
|
83
|
+
# Attach
|
84
|
+
my_form.set_form_win my_win
|
85
|
+
my_form.set_form_sub my_win.derwin( rows[0], cols[0], 2, label_end )
|
86
|
+
my_form.form_opts_off O_NL_OVERLOAD
|
87
|
+
my_form.post_form
|
88
|
+
labels.each do |y, ivar, ht, wt|
|
89
|
+
my_win.mvaddstr y, 2, ivar
|
90
|
+
end
|
91
|
+
scr.mvprintw Ncurses.LINES - 2, 28, "Use TAB to switch between fields"
|
92
|
+
scr.mvprintw Ncurses.LINES - 1, 28, "F2 to save | F3 to cancel"
|
93
|
+
scr.refresh
|
94
|
+
my_win.wrefresh
|
95
|
+
|
96
|
+
# Loop through to get user requests
|
97
|
+
pressed = []
|
98
|
+
while((ch = my_win.getch()) != KEY_F2)
|
99
|
+
pressed << ch
|
100
|
+
case ch
|
101
|
+
when 16 # Ctrl + P
|
102
|
+
my_form.form_driver REQ_PREV_PAGE
|
103
|
+
my_form.form_driver REQ_FIRST_FIELD
|
104
|
+
|
105
|
+
when 14 # Ctrl + N
|
106
|
+
my_form.form_driver REQ_NEXT_PAGE
|
107
|
+
my_form.form_driver REQ_LAST_FIELD
|
108
|
+
|
109
|
+
when KEY_C3, ?\t
|
110
|
+
# Go to next field
|
111
|
+
my_form.form_driver REQ_NEXT_FIELD
|
112
|
+
# Go to the end of the present buffer
|
113
|
+
# Leaves nicely at the last character
|
114
|
+
my_form.form_driver REQ_END_LINE
|
115
|
+
|
116
|
+
when KEY_C1
|
117
|
+
# Go to previous field
|
118
|
+
my_form.form_driver REQ_PREV_FIELD
|
119
|
+
my_form.form_driver REQ_END_LINE
|
120
|
+
|
121
|
+
when KEY_UP
|
122
|
+
my_form.form_driver REQ_PREV_LINE
|
123
|
+
|
124
|
+
when KEY_DOWN
|
125
|
+
my_form.form_driver REQ_NEXT_LINE
|
126
|
+
|
127
|
+
when KEY_LEFT
|
128
|
+
# Go to previous character
|
129
|
+
my_form.form_driver REQ_PREV_CHAR
|
130
|
+
|
131
|
+
when KEY_RIGHT
|
132
|
+
# Go to previous field
|
133
|
+
my_form.form_driver REQ_NEXT_CHAR
|
134
|
+
|
135
|
+
when KEY_BACKSPACE, 010
|
136
|
+
my_form.form_driver REQ_DEL_PREV
|
137
|
+
|
138
|
+
when KEY_ENTER, ?\n, ?\r
|
139
|
+
my_form.form_driver REQ_NEW_LINE
|
140
|
+
|
141
|
+
when KEY_F3
|
142
|
+
return nil
|
143
|
+
|
144
|
+
else
|
145
|
+
# If this is a normal character, it gets Printed
|
146
|
+
my_form.form_driver ch
|
147
|
+
end
|
148
|
+
end
|
149
|
+
# Un post form and free the memory
|
150
|
+
my_form.form_driver REQ_NEXT_FIELD
|
151
|
+
my_form.unpost_form
|
152
|
+
my_form.free_form
|
153
|
+
obj_props = {}
|
154
|
+
fields.each do |f|
|
155
|
+
b = field_read(f)
|
156
|
+
f.free_field()
|
157
|
+
if String === b and b.empty?
|
158
|
+
b = nil
|
159
|
+
end
|
160
|
+
obj_props[ivars.shift] = b
|
161
|
+
end
|
162
|
+
out_obj = YAML::transfer( obj.to_yaml_type[1..-1], obj_props )
|
163
|
+
ensure
|
164
|
+
Ncurses.endwin
|
165
|
+
# p pressed
|
166
|
+
# p out_obj
|
167
|
+
end
|
168
|
+
def self.field_write( f, obj )
|
169
|
+
rows, cols, frow, fcol, nrow, nbuf = [], [], [], [], [], []
|
170
|
+
f.field_info( rows, cols, frow, fcol, nrow, nbuf )
|
171
|
+
if String === obj
|
172
|
+
obj = "#{ obj }"
|
173
|
+
end
|
174
|
+
str = obj.to_yaml( :BestWidth => cols[0] - 4 ).
|
175
|
+
sub( /^\-\-\-\s*(\>[0-9\-\+]*\n)?/, '' ).
|
176
|
+
gsub( /^([^\n]*)\n/ ) { |line| "%-#{cols}s" % [$1] }
|
177
|
+
f.set_field_buffer 0, str
|
178
|
+
end
|
179
|
+
def self.field_read( f )
|
180
|
+
rows, cols, frow, fcol, nrow, nbuf = [], [], [], [], [], []
|
181
|
+
f.field_info( rows, cols, frow, fcol, nrow, nbuf )
|
182
|
+
val = f.field_buffer(0).scan( /.{#{ cols[0] }}/ )
|
183
|
+
YAML::load(
|
184
|
+
if val.length > 1
|
185
|
+
"--- >\n " +
|
186
|
+
val.collect { |line| line.rstrip }.join( "\n " ).rstrip
|
187
|
+
else
|
188
|
+
"--- #{ val[0] }"
|
189
|
+
end
|
190
|
+
)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
@@ -0,0 +1,155 @@
|
|
1
|
+
#
|
2
|
+
# = hobix/util/patcher
|
3
|
+
#
|
4
|
+
# Hobix command-line weblog system.
|
5
|
+
#
|
6
|
+
# Copyright (c) 2003-2004 why the lucky stiff
|
7
|
+
#
|
8
|
+
# Written & maintained by why the lucky stiff <why@ruby-lang.org>
|
9
|
+
#
|
10
|
+
# This program is free software, released under a BSD license.
|
11
|
+
# See COPYING for details.
|
12
|
+
#
|
13
|
+
#--
|
14
|
+
# $Id$
|
15
|
+
#++
|
16
|
+
require 'fileutils'
|
17
|
+
|
18
|
+
module Hobix
|
19
|
+
module Util
|
20
|
+
# The Patcher class applies Hobix's own YAML patch format to a directory.
|
21
|
+
# These patches can create or append to existing plain-text files, as well
|
22
|
+
# as modifying YAML files using YPath.
|
23
|
+
#
|
24
|
+
# To apply your patch:
|
25
|
+
#
|
26
|
+
# patch_set = Hobix::Util::Patcher['1.patch', '2.patch']
|
27
|
+
# patch_set.apply('/dir/to/unaltered/code')
|
28
|
+
#
|
29
|
+
class PatchError < Exception; end
|
30
|
+
class Patcher
|
31
|
+
# Initialize the Patcher with a list of +paths+ to patches which
|
32
|
+
# must be applied in order.
|
33
|
+
#
|
34
|
+
# patch_set = Hobix::Util::Patcher.new('1.patch', '2.patch')
|
35
|
+
# patch_set.apply('/dir/to/unaltered/code')
|
36
|
+
#
|
37
|
+
def initialize( *paths )
|
38
|
+
@patches = {}
|
39
|
+
paths.each do |path|
|
40
|
+
YAML::load_file( path ).each do |k, v|
|
41
|
+
( @patches[k] ||= [] ) << v
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Alias for Patcher.new.
|
47
|
+
#
|
48
|
+
# patch_set = Hobix::Util::Patcher['1.patch', '2.patch']
|
49
|
+
# patch_set.apply('/dir/to/unaltered/code')
|
50
|
+
#
|
51
|
+
def Patcher.[]( *paths )
|
52
|
+
Patcher.new( *paths )
|
53
|
+
end
|
54
|
+
|
55
|
+
# Apply the patches loaded into this class against a +path+ containing
|
56
|
+
# unaltered files.
|
57
|
+
#
|
58
|
+
# patch_set = Hobix::Util::Patcher['1.patch', '2.patch']
|
59
|
+
# patch_set.apply('/dir/to/unaltered/code')
|
60
|
+
#
|
61
|
+
def apply( path )
|
62
|
+
@patches.map do |fname, patchset|
|
63
|
+
fname = File.join( path, fname ) # .gsub( /^.*?[\/\\]/, '' ) )
|
64
|
+
ftarg = File.read( fname ) rescue ''
|
65
|
+
ftarg = YAML::load( ftarg ) if fname =~ /\.yaml$/
|
66
|
+
|
67
|
+
patchset.each_with_index do |(ptype, patch), patchno|
|
68
|
+
# apply the changes
|
69
|
+
puts "*** Applying patch ##{ patchno + 1 } for #{ fname } (#{ ptype })."
|
70
|
+
ftarg = method( ptype.gsub( /\W/, '_' ) ).call( ftarg, patch )
|
71
|
+
end
|
72
|
+
|
73
|
+
[fname, ftarg]
|
74
|
+
end.
|
75
|
+
each do |fname, ftext|
|
76
|
+
# save the files
|
77
|
+
if ftext == :remove
|
78
|
+
FileUtils.rm_rf fname
|
79
|
+
else
|
80
|
+
FileUtils.makedirs( File.dirname( fname ) )
|
81
|
+
ftext = ftext.to_yaml if fname =~ /\.yaml$/
|
82
|
+
File.open( fname, 'w+' ) { |f| f << ftext }
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def file_remove( target, text )
|
88
|
+
:remove
|
89
|
+
end
|
90
|
+
|
91
|
+
def file_create( target, text )
|
92
|
+
text.to_s
|
93
|
+
end
|
94
|
+
|
95
|
+
def file_ensure( target, text )
|
96
|
+
target << text unless target.include? text
|
97
|
+
target
|
98
|
+
end
|
99
|
+
|
100
|
+
def yaml_merge( obj, merge )
|
101
|
+
obj = obj.value if obj.respond_to? :value
|
102
|
+
if obj.class != merge.class and merge.class != Hash
|
103
|
+
raise PatchError, "*** Patch failure since #{ obj.class } != #{ merge.class }."
|
104
|
+
end
|
105
|
+
|
106
|
+
case obj
|
107
|
+
when Hash
|
108
|
+
merge.each do |k, v|
|
109
|
+
if obj.has_key? k
|
110
|
+
yaml_merge obj[k], v
|
111
|
+
else
|
112
|
+
obj[k] = v
|
113
|
+
end
|
114
|
+
end
|
115
|
+
when Array
|
116
|
+
at = nil
|
117
|
+
merge.each do |v|
|
118
|
+
vat = obj.index( v )
|
119
|
+
if vat
|
120
|
+
at = vat if vat > at.to_i
|
121
|
+
else
|
122
|
+
if at
|
123
|
+
obj[at+=1,0] = v
|
124
|
+
else
|
125
|
+
obj << v
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
when String
|
130
|
+
obj.replace merge
|
131
|
+
else
|
132
|
+
merge.each do |k, v|
|
133
|
+
ivar = obj.instance_variable_get "@#{k}"
|
134
|
+
if ivar
|
135
|
+
yaml_merge ivar, v
|
136
|
+
else
|
137
|
+
obj.instance_variable_set "@#{k}", v
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
obj
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
YAML::add_domain_type( 'hobix.com,2004', 'patches/list' ) do |type, val|
|
149
|
+
val
|
150
|
+
end
|
151
|
+
['yaml-merge', 'file-create', 'file-ensure', 'file-remove'].each do |ptype|
|
152
|
+
YAML::add_domain_type( 'hobix.com,2004', 'patches/' + ptype ) do |type, val|
|
153
|
+
[ptype, val]
|
154
|
+
end
|
155
|
+
end
|
@@ -0,0 +1,195 @@
|
|
1
|
+
# = webapp command line interface
|
2
|
+
#
|
3
|
+
# A web application using webapp has CLI (command line interface).
|
4
|
+
# You can invoke a webapp script from command line.
|
5
|
+
#
|
6
|
+
# xxx.cgi [options] [/path_info] [?query_string]
|
7
|
+
# -h, --help show this message
|
8
|
+
# -o, --output=FILE set output file
|
9
|
+
# --cern-meta output header as CERN httpd metafile
|
10
|
+
# --server-name=STRING set server name
|
11
|
+
# --server-port=INTEGER set server port number
|
12
|
+
# --script-name=STRING set script name
|
13
|
+
# --remote-addr=STRING set remote IP address
|
14
|
+
# --header=NAME:BODY set additional request header
|
15
|
+
#
|
16
|
+
# For example, hello.cgi, as follows, can be invoked from command line.
|
17
|
+
#
|
18
|
+
# % cat hello.cgi
|
19
|
+
# #!/usr/bin/env ruby
|
20
|
+
# require 'webapp'
|
21
|
+
# WebApp {|w| w.puts "Hello" }
|
22
|
+
# % ./hello.cgi
|
23
|
+
# Status: 200 OK
|
24
|
+
# Content-Type: text/plain
|
25
|
+
# Content-Length: 6
|
26
|
+
#
|
27
|
+
# Hello
|
28
|
+
#
|
29
|
+
# webapp.rb can be used in command line directly as follows.
|
30
|
+
# This document use the form to make examples short.
|
31
|
+
#
|
32
|
+
# % ruby -rwebapp -e 'WebApp {|w| w.puts "Hello" }'
|
33
|
+
# Status: 200 OK
|
34
|
+
# Content-Type: text/plain
|
35
|
+
# Content-Length: 6
|
36
|
+
#
|
37
|
+
# Hello
|
38
|
+
#
|
39
|
+
# The web application takes two optional argument: path info and query string.
|
40
|
+
# The optional first argument which begins with '/' is path info.
|
41
|
+
# The optional second argument which begins with '?' is query string.
|
42
|
+
# Since '?' is a shell meta character, it should be quoted.
|
43
|
+
#
|
44
|
+
# % ruby -rwebapp -e '
|
45
|
+
# WebApp {|w|
|
46
|
+
# w.puts w.path_info
|
47
|
+
# w.puts w.query_string
|
48
|
+
# }' /a '?q'
|
49
|
+
# Status: 200 OK
|
50
|
+
# Content-Type: text/plain
|
51
|
+
# Content-Length: 30
|
52
|
+
#
|
53
|
+
# /a
|
54
|
+
# #<WebApp::QueryString: ?q>
|
55
|
+
#
|
56
|
+
# If the option -o is specified, a response is generated on the specified file.
|
57
|
+
# Note that the format is suitable for Apache mod_asis.
|
58
|
+
#
|
59
|
+
# % ruby -rwebapp -e 'WebApp {|w| w.puts "Hello" }' -- -o ~/public_html/hello.asis
|
60
|
+
# % cat ~/public_html/hello.asis
|
61
|
+
# Status: 200 OK
|
62
|
+
# Content-Type: text/plain
|
63
|
+
# Content-Length: 6
|
64
|
+
#
|
65
|
+
# Hello
|
66
|
+
#
|
67
|
+
# If the option --cern-meta is specified addition to -o,
|
68
|
+
# The header in the response is stored in separated file.
|
69
|
+
# Note that the format is suitable for Apache mod_cern_meta.
|
70
|
+
#
|
71
|
+
# % ruby -rwebapp -e 'WebApp {|w| w.puts "Hello" }' -- --cern-meta -o ~/public_html/hello2.txt
|
72
|
+
# % cat ~/public_html/.web/hello2.txt.meta
|
73
|
+
# Content-Type: text/plain
|
74
|
+
# Content-Length: 6
|
75
|
+
# % cat ~/public_html/hello2.txt
|
76
|
+
# Hello
|
77
|
+
#
|
78
|
+
# The options --server-name, --server-port, --script-name and --remote-addr specifies
|
79
|
+
# information visible from web application.
|
80
|
+
# For example, WebApp#server_name returns a server name specified by --server-name.
|
81
|
+
#
|
82
|
+
# % ruby -rwebapp -e 'WebApp {|w| w.puts w.server_name }'
|
83
|
+
# Status: 200 OK
|
84
|
+
# Content-Type: text/plain
|
85
|
+
# Content-Length: 10
|
86
|
+
#
|
87
|
+
# localhost
|
88
|
+
# % ruby -rwebapp -e 'WebApp {|w| w.puts w.server_name }' -- --server-name=www.example.org
|
89
|
+
# Status: 200 OK
|
90
|
+
# Content-Type: text/plain
|
91
|
+
# Content-Length: 16
|
92
|
+
#
|
93
|
+
# www.example.org
|
94
|
+
#
|
95
|
+
# The option --header specifies an additional request header.
|
96
|
+
# For example, specifying "Accept-Encoding: gzip" makes output gzipped.
|
97
|
+
#
|
98
|
+
# % ruby -rwebapp -e 'WebApp {|w| w.puts "Hello"*100 }' -- --header='Accept-Encoding: gzip'|cat -v
|
99
|
+
# Status: 200 OK
|
100
|
+
# Content-Type: text/plain
|
101
|
+
# Content-Encoding: gzip
|
102
|
+
# Content-Length: 31
|
103
|
+
#
|
104
|
+
# ^_M-^K^H^@^O^VM-TA^@^CM-sHM-MM-IM-IM-w^X%F^RM-A^E^@ZTsDM-u^A^@^@
|
105
|
+
|
106
|
+
require 'optparse'
|
107
|
+
|
108
|
+
module Hobix
|
109
|
+
class WebApp
|
110
|
+
class Manager
|
111
|
+
# CLI (command line interface)
|
112
|
+
def run_cli
|
113
|
+
opt_output = '-'
|
114
|
+
opt_cern_meta = false
|
115
|
+
opt_server_name = 'localhost'
|
116
|
+
opt_server_port = 80
|
117
|
+
opt_script_name = "/#{File.basename($0)}"
|
118
|
+
opt_remote_addr = '127.0.0.1'
|
119
|
+
opt_headers = []
|
120
|
+
ARGV.options {|q|
|
121
|
+
q.banner = "#{File.basename $0} [options] [/path_info] [?query_string]"
|
122
|
+
q.def_option('-h', '--help', 'show this message') { puts q; exit(0) }
|
123
|
+
q.def_option('-o FILE', '--output=FILE', 'set output file') {|arg| opt_output = arg.untaint }
|
124
|
+
q.def_option('--cern-meta', 'output header as CERN httpd metafile') { opt_cern_meta = true }
|
125
|
+
q.def_option('--server-name=STRING', 'set server name') {|arg| opt_server_name = arg }
|
126
|
+
q.def_option('--server-port=INTEGER', 'set server port number') {|arg| opt_server_port = arg.to_i }
|
127
|
+
q.def_option('--script-name=STRING', 'set script name') {|arg| opt_script_name = arg }
|
128
|
+
q.def_option('--remote-addr=STRING', 'set remote IP address') {|arg| opt_remote_addr = arg }
|
129
|
+
q.def_option('--header=NAME:BODY', 'set additional request header') {|arg| opt_headers << arg.split(/:/, 2) }
|
130
|
+
q.parse!
|
131
|
+
}
|
132
|
+
if path_info = ARGV.shift
|
133
|
+
if %r{\A/} !~ path_info
|
134
|
+
ARGV.unshift path_info
|
135
|
+
path_info = nil
|
136
|
+
end
|
137
|
+
end
|
138
|
+
if query_string = ARGV.shift
|
139
|
+
if %r{\A\?} !~ query_string
|
140
|
+
ARGV.unshift query_string
|
141
|
+
query_string = nil
|
142
|
+
end
|
143
|
+
end
|
144
|
+
if !ARGV.empty?
|
145
|
+
raise "extra arguments: #{ARGV.inspect[1..-2]}"
|
146
|
+
end
|
147
|
+
path_info ||= ''
|
148
|
+
query_string ||= ''
|
149
|
+
setup_request = lambda {|req|
|
150
|
+
req.make_request_header_from_cgi_env({
|
151
|
+
'REQUEST_METHOD' => 'GET',
|
152
|
+
'SERVER_NAME' => opt_server_name,
|
153
|
+
'SERVER_PORT' => opt_server_port,
|
154
|
+
'SCRIPT_NAME' => opt_script_name,
|
155
|
+
'PATH_INFO' => path_info,
|
156
|
+
'QUERY_STRING' => query_string,
|
157
|
+
'SERVER_PROTOCOL' => 'HTTP/1.0',
|
158
|
+
'REMOTE_ADDR' => opt_remote_addr,
|
159
|
+
'CONTENT_TYPE' => ''
|
160
|
+
})
|
161
|
+
opt_headers.each {|name, body|
|
162
|
+
req.header_object.add name, body
|
163
|
+
}
|
164
|
+
}
|
165
|
+
output_response = lambda {|res|
|
166
|
+
if opt_output == '-'
|
167
|
+
res.output_cgi_status_field($stdout)
|
168
|
+
res.output_message($stdout)
|
169
|
+
else
|
170
|
+
if opt_cern_meta
|
171
|
+
dir = "#{File.dirname(opt_output)}/.web"
|
172
|
+
begin
|
173
|
+
Dir.mkdir dir
|
174
|
+
rescue Errno::EEXIST
|
175
|
+
end
|
176
|
+
open("#{dir}/#{File.basename(opt_output)}.meta", 'w') {|f|
|
177
|
+
#res.output_cgi_status_field(f)
|
178
|
+
res.output_header(f)
|
179
|
+
}
|
180
|
+
open(opt_output, 'w') {|f|
|
181
|
+
res.output_body(f)
|
182
|
+
}
|
183
|
+
else
|
184
|
+
open(opt_output, 'w') {|f|
|
185
|
+
res.output_cgi_status_field(f)
|
186
|
+
res.output_message(f)
|
187
|
+
}
|
188
|
+
end
|
189
|
+
end
|
190
|
+
}
|
191
|
+
primitive_run(setup_request, output_response)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|