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.
Files changed (156) hide show
  1. data/COPYING +18 -0
  2. data/README +18 -0
  3. data/Rakefile +96 -0
  4. data/bin/hobix +94 -0
  5. data/contrib/blosxom-to-hobix.rb +253 -0
  6. data/contrib/txp-to-hobix.rb +56 -0
  7. data/contrib/webrick-all-mine.rb +20 -0
  8. data/doc/CHANGELOG +285 -0
  9. data/doc/rdoc/classes/Hobix/API.html +382 -0
  10. data/doc/rdoc/classes/Hobix/Article.html +111 -0
  11. data/doc/rdoc/classes/Hobix/BaseContent.html +692 -0
  12. data/doc/rdoc/classes/Hobix/BaseEntry.html +218 -0
  13. data/doc/rdoc/classes/Hobix/BaseFacet.html +205 -0
  14. data/doc/rdoc/classes/Hobix/BaseOutput.html +122 -0
  15. data/doc/rdoc/classes/Hobix/BasePlugin.html +201 -0
  16. data/doc/rdoc/classes/Hobix/BaseProperties/ClassMethods.html +243 -0
  17. data/doc/rdoc/classes/Hobix/BaseProperties.html +218 -0
  18. data/doc/rdoc/classes/Hobix/BasePublish.html +157 -0
  19. data/doc/rdoc/classes/Hobix/BaseStorage.html +417 -0
  20. data/doc/rdoc/classes/Hobix/BixWik/Entry.html +196 -0
  21. data/doc/rdoc/classes/Hobix/BixWik/IndexEntry.html +170 -0
  22. data/doc/rdoc/classes/Hobix/BixWik/WikiRedCloth.html +111 -0
  23. data/doc/rdoc/classes/Hobix/BixWik.html +418 -0
  24. data/doc/rdoc/classes/Hobix/BixWikPlugin.html +158 -0
  25. data/doc/rdoc/classes/Hobix/CommandLine.html +1970 -0
  26. data/doc/rdoc/classes/Hobix/Comment.html +113 -0
  27. data/doc/rdoc/classes/Hobix/Config.html +212 -0
  28. data/doc/rdoc/classes/Hobix/DataMarsh.html +667 -0
  29. data/doc/rdoc/classes/Hobix/Entry.html +178 -0
  30. data/doc/rdoc/classes/Hobix/EntryEnum.html +162 -0
  31. data/doc/rdoc/classes/Hobix/Enumerable.html +170 -0
  32. data/doc/rdoc/classes/Hobix/Facets/WikiEdit.html +180 -0
  33. data/doc/rdoc/classes/Hobix/Facets.html +111 -0
  34. data/doc/rdoc/classes/Hobix/LinkList.html +182 -0
  35. data/doc/rdoc/classes/Hobix/Out/Quick.html +412 -0
  36. data/doc/rdoc/classes/Hobix/Out.html +119 -0
  37. data/doc/rdoc/classes/Hobix/Page.html +381 -0
  38. data/doc/rdoc/classes/Hobix/Trackback.html +113 -0
  39. data/doc/rdoc/classes/Hobix/UriStr.html +198 -0
  40. data/doc/rdoc/classes/Hobix/WebApp/QueryString.html +207 -0
  41. data/doc/rdoc/classes/Hobix/WebApp/QueryValidationFailure.html +111 -0
  42. data/doc/rdoc/classes/Hobix/WebApp.html +1383 -0
  43. data/doc/rdoc/classes/Hobix/Weblog/AuthorNotFound.html +111 -0
  44. data/doc/rdoc/classes/Hobix/Weblog.html +2082 -0
  45. data/doc/rdoc/classes/Hobix.html +399 -0
  46. data/doc/rdoc/classes/Kernel.html +139 -0
  47. data/doc/rdoc/classes/Regexp.html +154 -0
  48. data/doc/rdoc/classes/YAML/Omap.html +144 -0
  49. data/doc/rdoc/classes/YAML.html +111 -0
  50. data/doc/rdoc/created.rid +1 -0
  51. data/doc/rdoc/files/COPYING.html +129 -0
  52. data/doc/rdoc/files/README.html +131 -0
  53. data/doc/rdoc/files/doc/CHANGELOG.html +101 -0
  54. data/doc/rdoc/files/lib/hobix/api_rb.html +119 -0
  55. data/doc/rdoc/files/lib/hobix/article_rb.html +126 -0
  56. data/doc/rdoc/files/lib/hobix/base_rb.html +128 -0
  57. data/doc/rdoc/files/lib/hobix/bixwik_rb.html +126 -0
  58. data/doc/rdoc/files/lib/hobix/commandline_rb.html +140 -0
  59. data/doc/rdoc/files/lib/hobix/comments_rb.html +126 -0
  60. data/doc/rdoc/files/lib/hobix/config_rb.html +125 -0
  61. data/doc/rdoc/files/lib/hobix/datamarsh_rb.html +108 -0
  62. data/doc/rdoc/files/lib/hobix/entry_rb.html +118 -0
  63. data/doc/rdoc/files/lib/hobix/linklist_rb.html +127 -0
  64. data/doc/rdoc/files/lib/hobix/publisher_rb.html +126 -0
  65. data/doc/rdoc/files/lib/hobix/trackbacks_rb.html +128 -0
  66. data/doc/rdoc/files/lib/hobix/webapp_rb.html +127 -0
  67. data/doc/rdoc/files/lib/hobix/weblog_rb.html +135 -0
  68. data/doc/rdoc/files/lib/hobix_rb.html +127 -0
  69. data/doc/rdoc/fr_class_index.html +67 -0
  70. data/doc/rdoc/fr_file_index.html +44 -0
  71. data/doc/rdoc/fr_method_index.html +307 -0
  72. data/doc/rdoc/index.html +24 -0
  73. data/doc/rdoc/rdoc-style.css +208 -0
  74. data/git_hobix_update.php +13 -0
  75. data/lib/hobix/api.rb +91 -0
  76. data/lib/hobix/article.rb +22 -0
  77. data/lib/hobix/base.rb +480 -0
  78. data/lib/hobix/bixwik.rb +200 -0
  79. data/lib/hobix/commandline.rb +677 -0
  80. data/lib/hobix/comments.rb +98 -0
  81. data/lib/hobix/config.rb +39 -0
  82. data/lib/hobix/datamarsh.rb +110 -0
  83. data/lib/hobix/entry.rb +84 -0
  84. data/lib/hobix/facets/comments.rb +99 -0
  85. data/lib/hobix/facets/publisher.rb +314 -0
  86. data/lib/hobix/facets/trackbacks.rb +80 -0
  87. data/lib/hobix/linklist.rb +81 -0
  88. data/lib/hobix/out/atom.rb +101 -0
  89. data/lib/hobix/out/erb.rb +64 -0
  90. data/lib/hobix/out/okaynews.rb +55 -0
  91. data/lib/hobix/out/quick.rb +314 -0
  92. data/lib/hobix/out/rdf.rb +97 -0
  93. data/lib/hobix/out/redrum.rb +26 -0
  94. data/lib/hobix/out/rss.rb +128 -0
  95. data/lib/hobix/plugin/akismet.rb +196 -0
  96. data/lib/hobix/plugin/bloglines.rb +73 -0
  97. data/lib/hobix/plugin/calendar.rb +212 -0
  98. data/lib/hobix/plugin/flickr.rb +110 -0
  99. data/lib/hobix/plugin/recent_comments.rb +84 -0
  100. data/lib/hobix/plugin/sections.rb +91 -0
  101. data/lib/hobix/plugin/tags.rb +60 -0
  102. data/lib/hobix/publish/ping.rb +53 -0
  103. data/lib/hobix/publish/replicate.rb +283 -0
  104. data/lib/hobix/publisher.rb +18 -0
  105. data/lib/hobix/search/dictionary.rb +141 -0
  106. data/lib/hobix/search/porter_stemmer.rb +203 -0
  107. data/lib/hobix/search/simple.rb +209 -0
  108. data/lib/hobix/search/vector.rb +100 -0
  109. data/lib/hobix/storage/filesys.rb +408 -0
  110. data/lib/hobix/trackbacks.rb +93 -0
  111. data/lib/hobix/util/objedit.rb +193 -0
  112. data/lib/hobix/util/patcher.rb +155 -0
  113. data/lib/hobix/webapp/cli.rb +195 -0
  114. data/lib/hobix/webapp/htmlform.rb +107 -0
  115. data/lib/hobix/webapp/message.rb +177 -0
  116. data/lib/hobix/webapp/urigen.rb +141 -0
  117. data/lib/hobix/webapp/webrick-servlet.rb +90 -0
  118. data/lib/hobix/webapp.rb +723 -0
  119. data/lib/hobix/weblog.rb +893 -0
  120. data/lib/hobix.rb +230 -0
  121. data/share/default-blog/hobix.yaml +16 -0
  122. data/share/default-blog/htdocs/site.css +174 -0
  123. data/share/default-blog/skel/entry.html.quick +0 -0
  124. data/share/default-blog/skel/index.atom.atom +0 -0
  125. data/share/default-blog/skel/index.html.quick-summary +0 -0
  126. data/share/default-blog/skel/index.xml.rss +0 -0
  127. data/share/default-blog/skel/index.yaml.okaynews +0 -0
  128. data/share/default-blog/skel/monthly.html.quick-archive +0 -0
  129. data/share/default-blog/skel/section.html.quick-archive +0 -0
  130. data/share/default-blog/skel/yearly.html.quick-archive +0 -0
  131. data/share/default-blog-modes.yaml +7 -0
  132. data/share/default-blog.apache-cgi.patch +8 -0
  133. data/share/default-blog.apache-ssi.patch +38 -0
  134. data/share/default-blog.apache2-ssi.patch +3 -0
  135. data/share/default-blog.cgi.patch +8 -0
  136. data/share/default-blog.comments.patch +5 -0
  137. data/share/default-blog.prototype.patch +766 -0
  138. data/share/default-blog.publisher.patch +5 -0
  139. data/share/default-blog.wiki.patch +29 -0
  140. data/share/publisher/css/control.css +90 -0
  141. data/share/publisher/css/form.css +238 -0
  142. data/share/publisher/css/form.import.css +72 -0
  143. data/share/publisher/css/main-menu.css +134 -0
  144. data/share/publisher/i/hobix-emblazen-1.png +0 -0
  145. data/share/publisher/i/hobix-emblazen-2.png +0 -0
  146. data/share/publisher/i/hobix-emblazen-3.png +0 -0
  147. data/share/publisher/i/hobix-emblazen-4.png +0 -0
  148. data/share/publisher/i/hobix-emblazen-5.png +0 -0
  149. data/share/publisher/i/hobix-emblazen-6.png +0 -0
  150. data/share/publisher/i/hobix-emblazen-7.png +0 -0
  151. data/share/publisher/index.erb +66 -0
  152. data/share/publisher/js/controls.js +261 -0
  153. data/share/publisher/js/dragdrop.js +476 -0
  154. data/share/publisher/js/effects.js +570 -0
  155. data/share/publisher/js/prototype.js +1011 -0
  156. 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