hobix 0.6
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 +18 -0
- data/README +18 -0
- data/Rakefile +96 -0
- data/bin/hobix +94 -0
- data/contrib/blosxom-to-hobix.rb +253 -0
- data/contrib/txp-to-hobix.rb +56 -0
- data/contrib/webrick-all-mine.rb +20 -0
- data/doc/CHANGELOG +285 -0
- data/doc/rdoc/classes/Hobix/API.html +382 -0
- data/doc/rdoc/classes/Hobix/Article.html +111 -0
- data/doc/rdoc/classes/Hobix/BaseContent.html +692 -0
- data/doc/rdoc/classes/Hobix/BaseEntry.html +218 -0
- data/doc/rdoc/classes/Hobix/BaseFacet.html +205 -0
- data/doc/rdoc/classes/Hobix/BaseOutput.html +122 -0
- data/doc/rdoc/classes/Hobix/BasePlugin.html +201 -0
- data/doc/rdoc/classes/Hobix/BaseProperties/ClassMethods.html +243 -0
- data/doc/rdoc/classes/Hobix/BaseProperties.html +218 -0
- data/doc/rdoc/classes/Hobix/BasePublish.html +157 -0
- data/doc/rdoc/classes/Hobix/BaseStorage.html +417 -0
- data/doc/rdoc/classes/Hobix/BixWik/Entry.html +196 -0
- data/doc/rdoc/classes/Hobix/BixWik/IndexEntry.html +170 -0
- data/doc/rdoc/classes/Hobix/BixWik/WikiRedCloth.html +111 -0
- data/doc/rdoc/classes/Hobix/BixWik.html +418 -0
- data/doc/rdoc/classes/Hobix/BixWikPlugin.html +158 -0
- data/doc/rdoc/classes/Hobix/CommandLine.html +1970 -0
- data/doc/rdoc/classes/Hobix/Comment.html +113 -0
- data/doc/rdoc/classes/Hobix/Config.html +212 -0
- data/doc/rdoc/classes/Hobix/DataMarsh.html +667 -0
- data/doc/rdoc/classes/Hobix/Entry.html +178 -0
- data/doc/rdoc/classes/Hobix/EntryEnum.html +162 -0
- data/doc/rdoc/classes/Hobix/Enumerable.html +170 -0
- data/doc/rdoc/classes/Hobix/Facets/WikiEdit.html +180 -0
- data/doc/rdoc/classes/Hobix/Facets.html +111 -0
- data/doc/rdoc/classes/Hobix/LinkList.html +182 -0
- data/doc/rdoc/classes/Hobix/Out/Quick.html +412 -0
- data/doc/rdoc/classes/Hobix/Out.html +119 -0
- data/doc/rdoc/classes/Hobix/Page.html +381 -0
- data/doc/rdoc/classes/Hobix/Trackback.html +113 -0
- data/doc/rdoc/classes/Hobix/UriStr.html +198 -0
- data/doc/rdoc/classes/Hobix/WebApp/QueryString.html +207 -0
- data/doc/rdoc/classes/Hobix/WebApp/QueryValidationFailure.html +111 -0
- data/doc/rdoc/classes/Hobix/WebApp.html +1383 -0
- data/doc/rdoc/classes/Hobix/Weblog/AuthorNotFound.html +111 -0
- data/doc/rdoc/classes/Hobix/Weblog.html +2082 -0
- data/doc/rdoc/classes/Hobix.html +399 -0
- data/doc/rdoc/classes/Kernel.html +139 -0
- data/doc/rdoc/classes/Regexp.html +154 -0
- data/doc/rdoc/classes/YAML/Omap.html +144 -0
- data/doc/rdoc/classes/YAML.html +111 -0
- data/doc/rdoc/created.rid +1 -0
- data/doc/rdoc/files/COPYING.html +129 -0
- data/doc/rdoc/files/README.html +131 -0
- data/doc/rdoc/files/doc/CHANGELOG.html +101 -0
- data/doc/rdoc/files/lib/hobix/api_rb.html +119 -0
- data/doc/rdoc/files/lib/hobix/article_rb.html +126 -0
- data/doc/rdoc/files/lib/hobix/base_rb.html +128 -0
- data/doc/rdoc/files/lib/hobix/bixwik_rb.html +126 -0
- data/doc/rdoc/files/lib/hobix/commandline_rb.html +140 -0
- data/doc/rdoc/files/lib/hobix/comments_rb.html +126 -0
- data/doc/rdoc/files/lib/hobix/config_rb.html +125 -0
- data/doc/rdoc/files/lib/hobix/datamarsh_rb.html +108 -0
- data/doc/rdoc/files/lib/hobix/entry_rb.html +118 -0
- data/doc/rdoc/files/lib/hobix/linklist_rb.html +127 -0
- data/doc/rdoc/files/lib/hobix/publisher_rb.html +126 -0
- data/doc/rdoc/files/lib/hobix/trackbacks_rb.html +128 -0
- data/doc/rdoc/files/lib/hobix/webapp_rb.html +127 -0
- data/doc/rdoc/files/lib/hobix/weblog_rb.html +135 -0
- data/doc/rdoc/files/lib/hobix_rb.html +127 -0
- data/doc/rdoc/fr_class_index.html +67 -0
- data/doc/rdoc/fr_file_index.html +44 -0
- data/doc/rdoc/fr_method_index.html +307 -0
- data/doc/rdoc/index.html +24 -0
- data/doc/rdoc/rdoc-style.css +208 -0
- data/git_hobix_update.php +13 -0
- data/lib/hobix/api.rb +91 -0
- data/lib/hobix/article.rb +22 -0
- data/lib/hobix/base.rb +480 -0
- data/lib/hobix/bixwik.rb +200 -0
- data/lib/hobix/commandline.rb +677 -0
- data/lib/hobix/comments.rb +98 -0
- data/lib/hobix/config.rb +39 -0
- data/lib/hobix/datamarsh.rb +110 -0
- data/lib/hobix/entry.rb +84 -0
- data/lib/hobix/facets/comments.rb +99 -0
- data/lib/hobix/facets/publisher.rb +314 -0
- data/lib/hobix/facets/trackbacks.rb +80 -0
- data/lib/hobix/linklist.rb +81 -0
- data/lib/hobix/out/atom.rb +101 -0
- data/lib/hobix/out/erb.rb +64 -0
- data/lib/hobix/out/okaynews.rb +55 -0
- data/lib/hobix/out/quick.rb +314 -0
- data/lib/hobix/out/rdf.rb +97 -0
- data/lib/hobix/out/redrum.rb +26 -0
- data/lib/hobix/out/rss.rb +128 -0
- data/lib/hobix/plugin/akismet.rb +196 -0
- data/lib/hobix/plugin/bloglines.rb +73 -0
- data/lib/hobix/plugin/calendar.rb +212 -0
- data/lib/hobix/plugin/flickr.rb +110 -0
- data/lib/hobix/plugin/recent_comments.rb +84 -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 +408 -0
- data/lib/hobix/trackbacks.rb +93 -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 +893 -0
- data/lib/hobix.rb +230 -0
- data/share/default-blog/hobix.yaml +16 -0
- data/share/default-blog/htdocs/site.css +174 -0
- data/share/default-blog/skel/entry.html.quick +0 -0
- data/share/default-blog/skel/index.atom.atom +0 -0
- data/share/default-blog/skel/index.html.quick-summary +0 -0
- data/share/default-blog/skel/index.xml.rss +0 -0
- data/share/default-blog/skel/index.yaml.okaynews +0 -0
- data/share/default-blog/skel/monthly.html.quick-archive +0 -0
- data/share/default-blog/skel/section.html.quick-archive +0 -0
- data/share/default-blog/skel/yearly.html.quick-archive +0 -0
- data/share/default-blog-modes.yaml +7 -0
- data/share/default-blog.apache-cgi.patch +8 -0
- data/share/default-blog.apache-ssi.patch +38 -0
- data/share/default-blog.apache2-ssi.patch +3 -0
- data/share/default-blog.cgi.patch +8 -0
- data/share/default-blog.comments.patch +5 -0
- data/share/default-blog.prototype.patch +766 -0
- data/share/default-blog.publisher.patch +5 -0
- data/share/default-blog.wiki.patch +29 -0
- data/share/publisher/css/control.css +90 -0
- data/share/publisher/css/form.css +238 -0
- data/share/publisher/css/form.import.css +72 -0
- data/share/publisher/css/main-menu.css +134 -0
- data/share/publisher/i/hobix-emblazen-1.png +0 -0
- data/share/publisher/i/hobix-emblazen-2.png +0 -0
- data/share/publisher/i/hobix-emblazen-3.png +0 -0
- data/share/publisher/i/hobix-emblazen-4.png +0 -0
- data/share/publisher/i/hobix-emblazen-5.png +0 -0
- data/share/publisher/i/hobix-emblazen-6.png +0 -0
- data/share/publisher/i/hobix-emblazen-7.png +0 -0
- data/share/publisher/index.erb +66 -0
- data/share/publisher/js/controls.js +261 -0
- data/share/publisher/js/dragdrop.js +476 -0
- data/share/publisher/js/effects.js +570 -0
- data/share/publisher/js/prototype.js +1011 -0
- metadata +230 -0
|
@@ -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
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
module Hobix
|
|
2
|
+
class WebApp
|
|
3
|
+
class QueryString
|
|
4
|
+
# decode self as application/x-www-form-urlencoded and returns
|
|
5
|
+
# HTMLFormQuery object.
|
|
6
|
+
def decode_as_application_x_www_form_urlencoded
|
|
7
|
+
# xxx: warning if invalid?
|
|
8
|
+
pairs = []
|
|
9
|
+
@escaped_query_string.scan(/([^&;=]*)=([^&;]*)/) {|key, val|
|
|
10
|
+
key.gsub!(/\+/, ' ')
|
|
11
|
+
key.gsub!(/%([0-9A-F][0-9A-F])/i) { [$1].pack("H*") }
|
|
12
|
+
val.gsub!(/\+/, ' ')
|
|
13
|
+
val.gsub!(/%([0-9A-F][0-9A-F])/i) { [$1].pack("H*") }
|
|
14
|
+
pairs << [key.freeze, val.freeze]
|
|
15
|
+
}
|
|
16
|
+
HTMLFormQuery.new(pairs)
|
|
17
|
+
end
|
|
18
|
+
# decode self as multipart/form-data and returns
|
|
19
|
+
# HTMLFormQuery object.
|
|
20
|
+
def decode_as_multipart_form_data( boundary )
|
|
21
|
+
# xxx: warning if invalid?
|
|
22
|
+
require 'tempfile'
|
|
23
|
+
pairs = []
|
|
24
|
+
boundary = "--" + boundary
|
|
25
|
+
eol = "\015\012"
|
|
26
|
+
str = @escaped_query_string.gsub( /(?:\r?\n|\A)#{ Regexp::quote( boundary ) }--#{ eol }.*/m, '' )
|
|
27
|
+
str.split( /(?:\r?\n|\A)#{ Regexp::quote( boundary ) }#{ eol }/m ).each do |part|
|
|
28
|
+
headers = {}
|
|
29
|
+
header, value = part.split( "#{eol}#{eol}", 2 )
|
|
30
|
+
next unless header and value
|
|
31
|
+
field_name, field_data = nil, {}
|
|
32
|
+
if header =~ /Content-Disposition: form-data;.*(?:\sname="([^"]+)")/m
|
|
33
|
+
field_name = $1
|
|
34
|
+
end
|
|
35
|
+
if header =~ /Content-Disposition: form-data;.*(?:\sfilename="([^"]+)")/m
|
|
36
|
+
body = Tempfile.new( "WebApp" )
|
|
37
|
+
body.binmode if defined? body.binmode
|
|
38
|
+
body.print value
|
|
39
|
+
body.rewind
|
|
40
|
+
field_data = {'filename' => $1, 'tempfile' => body}
|
|
41
|
+
field_data['type'] = $1 if header =~ /Content-Type: (.+?)(?:#{ eol }|\Z)/m
|
|
42
|
+
else
|
|
43
|
+
field_data = value.gsub( /#{ eol }\Z/, '' )
|
|
44
|
+
end
|
|
45
|
+
pairs << [field_name, field_data]
|
|
46
|
+
end
|
|
47
|
+
HTMLFormQuery.new(pairs)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# HTMLFormQuery represents a query submitted by HTML form.
|
|
52
|
+
class HTMLFormQuery
|
|
53
|
+
|
|
54
|
+
def HTMLFormQuery.each_string_key_pair(arg, &block) # :nodoc:
|
|
55
|
+
if arg.respond_to? :to_ary
|
|
56
|
+
arg = arg.to_ary
|
|
57
|
+
if arg.length == 2 && arg.first.respond_to?(:to_str)
|
|
58
|
+
yield WebApp.make_frozen_string(arg.first), arg.last
|
|
59
|
+
else
|
|
60
|
+
arg.each {|elt|
|
|
61
|
+
HTMLFormQuery.each_string_key_pair(elt, &block)
|
|
62
|
+
}
|
|
63
|
+
end
|
|
64
|
+
elsif arg.respond_to? :to_pair
|
|
65
|
+
arg.each_pair {|key, val|
|
|
66
|
+
yield WebApp.make_frozen_string(key), val
|
|
67
|
+
}
|
|
68
|
+
else
|
|
69
|
+
raise ArgumentError, "non-pairs argument: #{arg.inspect}"
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def initialize(*args)
|
|
74
|
+
@param = []
|
|
75
|
+
HTMLFormQuery.each_string_key_pair(args) {|key, val|
|
|
76
|
+
@param << [key, val]
|
|
77
|
+
}
|
|
78
|
+
@param.freeze
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def each
|
|
82
|
+
@param.each {|key, val|
|
|
83
|
+
yield key.dup, val.dup
|
|
84
|
+
}
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def [](key)
|
|
88
|
+
if pair = @param.assoc(key)
|
|
89
|
+
return pair.last.dup
|
|
90
|
+
end
|
|
91
|
+
return nil
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def lookup_all(key)
|
|
95
|
+
result = []
|
|
96
|
+
@param.each {|k, val|
|
|
97
|
+
result << val if k == key
|
|
98
|
+
}
|
|
99
|
+
return result
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def keys
|
|
103
|
+
@param.map {|key, val| key }.uniq
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
module Hobix
|
|
2
|
+
class WebApp
|
|
3
|
+
# :stopdoc:
|
|
4
|
+
class Header
|
|
5
|
+
def Header.capitalize_field_name(field_name)
|
|
6
|
+
field_name.gsub(/[A-Za-z]+/) {|s| s.capitalize }
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def initialize
|
|
10
|
+
@fields = []
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def freeze
|
|
14
|
+
@fields.freeze
|
|
15
|
+
super
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def dup
|
|
19
|
+
result = Header.new
|
|
20
|
+
@fields.each {|_, k, v|
|
|
21
|
+
result.add(k, v)
|
|
22
|
+
}
|
|
23
|
+
result
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def clear
|
|
27
|
+
@fields.clear
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def remove(field_name)
|
|
31
|
+
k1 = field_name.downcase
|
|
32
|
+
@fields.reject! {|k2, _, _| k1 == k2 }
|
|
33
|
+
nil
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def add(field_name, field_body)
|
|
37
|
+
field_name = WebApp.make_frozen_string(field_name)
|
|
38
|
+
field_body = WebApp.make_frozen_string(field_body)
|
|
39
|
+
@fields << [field_name.downcase.freeze, field_name, field_body]
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def set(field_name, field_body)
|
|
43
|
+
field_name = WebApp.make_frozen_string(field_name)
|
|
44
|
+
remove(field_name)
|
|
45
|
+
add(field_name, field_body)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def has?(field_name)
|
|
49
|
+
@fields.assoc(field_name.downcase) != nil
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def [](field_name)
|
|
53
|
+
k1 = field_name.downcase
|
|
54
|
+
@fields.each {|k2, field_name, field_body|
|
|
55
|
+
return field_body.dup if k1 == k2
|
|
56
|
+
}
|
|
57
|
+
nil
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def lookup_all(field_name)
|
|
61
|
+
k1 = field_name.downcase
|
|
62
|
+
result = []
|
|
63
|
+
@fields.each {|k2, field_name, field_body|
|
|
64
|
+
result << field_body.dup if k1 == k2
|
|
65
|
+
}
|
|
66
|
+
result
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def each
|
|
70
|
+
@fields.each {|_, field_name, field_body|
|
|
71
|
+
field_name = field_name.dup
|
|
72
|
+
field_body = field_body.dup
|
|
73
|
+
yield field_name, field_body
|
|
74
|
+
}
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
class Message
|
|
79
|
+
def initialize(header={}, body='')
|
|
80
|
+
@header_object = Header.new
|
|
81
|
+
case header
|
|
82
|
+
when Hash
|
|
83
|
+
header.each_pair {|k, v|
|
|
84
|
+
raise ArgumentError, "unexpected header field name: #{k.inspect}" unless k.respond_to? :to_str
|
|
85
|
+
raise ArgumentError, "unexpected header field body: #{v.inspect}" unless v.respond_to? :to_str
|
|
86
|
+
@header_object.add k.to_str, v.to_str
|
|
87
|
+
}
|
|
88
|
+
when Array
|
|
89
|
+
header.each {|k, v|
|
|
90
|
+
raise ArgumentError, "unexpected header field name: #{k.inspect}" unless k.respond_to? :to_str
|
|
91
|
+
raise ArgumentError, "unexpected header field body: #{v.inspect}" unless v.respond_to? :to_str
|
|
92
|
+
@header_object.add k.to_str, v.to_str
|
|
93
|
+
}
|
|
94
|
+
else
|
|
95
|
+
raise ArgumentError, "unexpected header argument: #{header.inspect}"
|
|
96
|
+
end
|
|
97
|
+
raise ArgumentError, "unexpected body: #{body.inspect}" unless body.respond_to? :to_str
|
|
98
|
+
@body_object = StringIO.new(body.to_str)
|
|
99
|
+
end
|
|
100
|
+
attr_reader :header_object, :body_object
|
|
101
|
+
|
|
102
|
+
def freeze
|
|
103
|
+
@header_object.freeze
|
|
104
|
+
@body_object.string.freeze
|
|
105
|
+
super
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def output_header(out)
|
|
109
|
+
@header_object.each {|k, v|
|
|
110
|
+
out << "#{k}: #{v}\n"
|
|
111
|
+
}
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def output_body(out)
|
|
115
|
+
out << @body_object.string
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def output_message(out)
|
|
119
|
+
output_header(out)
|
|
120
|
+
out << "\n"
|
|
121
|
+
output_body(out)
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
class Request < Message
|
|
126
|
+
def initialize(request_line=nil, header={}, body='')
|
|
127
|
+
@request_line = request_line
|
|
128
|
+
super header, body
|
|
129
|
+
end
|
|
130
|
+
attr_reader :request_method,
|
|
131
|
+
:server_name, :server_port,
|
|
132
|
+
:script_name, :path_info,
|
|
133
|
+
:query_string,
|
|
134
|
+
:server_protocol,
|
|
135
|
+
:remote_addr, :content_type, :request_uri, :action_uri
|
|
136
|
+
|
|
137
|
+
def make_request_header_from_cgi_env(env)
|
|
138
|
+
env.each {|k, v|
|
|
139
|
+
next if /\AHTTP_/ !~ k
|
|
140
|
+
k = Header.capitalize_field_name($')
|
|
141
|
+
k.gsub!(/_/, '-')
|
|
142
|
+
@header_object.add k, v
|
|
143
|
+
}
|
|
144
|
+
@request_method = env['REQUEST_METHOD']
|
|
145
|
+
@server_name = ( env['SERVER_NAME'] || '' ).gsub( /\:\d+$/, '' ) # lighttpd affixes port!!
|
|
146
|
+
@server_port = env['SERVER_PORT'].to_i
|
|
147
|
+
@script_name = env['SCRIPT_NAME'] || ''
|
|
148
|
+
@path_info = env['PATH_INFO'] || ''
|
|
149
|
+
@query_string = QueryString.primitive_new_for_raw_query_string(env['QUERY_STRING'] || '')
|
|
150
|
+
@server_protocol = env['SERVER_PROTOCOL'] || ''
|
|
151
|
+
@remote_addr = env['REMOTE_ADDR'] || ''
|
|
152
|
+
@content_type = env['CONTENT_TYPE'] || ''
|
|
153
|
+
|
|
154
|
+
# non-standard:
|
|
155
|
+
@request_uri = env['REQUEST_URI'] # Apache
|
|
156
|
+
|
|
157
|
+
# hobix action uri
|
|
158
|
+
@action_uri = ( env['PATH_INFO'] || env['REQUEST_URI'] ).
|
|
159
|
+
gsub( /^(#{ Regexp::quote( File::dirname( @script_name ) ) })?\/*/, '' ).
|
|
160
|
+
gsub( /\?.+$/, '' )
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
class Response < Message
|
|
165
|
+
def initialize(status_line='200 OK', header={}, body='')
|
|
166
|
+
@status_line = status_line
|
|
167
|
+
super header, body
|
|
168
|
+
end
|
|
169
|
+
attr_accessor :status_line
|
|
170
|
+
|
|
171
|
+
def output_cgi_status_field(out)
|
|
172
|
+
out << "Status: #{self.status_line}\n"
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
# :startdoc:
|
|
176
|
+
end
|
|
177
|
+
end
|