hobix 0.4
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.
- 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
|