hobix 0.6
Sign up to get free protection for your applications and to get access to all the features.
- 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
|