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.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/bin/hobix +90 -0
  3. data/lib/hobix/api.rb +91 -0
  4. data/lib/hobix/article.rb +22 -0
  5. data/lib/hobix/base.rb +477 -0
  6. data/lib/hobix/bixwik.rb +200 -0
  7. data/lib/hobix/commandline.rb +661 -0
  8. data/lib/hobix/comments.rb +99 -0
  9. data/lib/hobix/config.rb +39 -0
  10. data/lib/hobix/datamarsh.rb +110 -0
  11. data/lib/hobix/entry.rb +83 -0
  12. data/lib/hobix/facets/comments.rb +74 -0
  13. data/lib/hobix/facets/publisher.rb +314 -0
  14. data/lib/hobix/facets/trackbacks.rb +80 -0
  15. data/lib/hobix/linklist.rb +76 -0
  16. data/lib/hobix/out/atom.rb +92 -0
  17. data/lib/hobix/out/erb.rb +64 -0
  18. data/lib/hobix/out/okaynews.rb +55 -0
  19. data/lib/hobix/out/quick.rb +312 -0
  20. data/lib/hobix/out/rdf.rb +97 -0
  21. data/lib/hobix/out/redrum.rb +26 -0
  22. data/lib/hobix/out/rss.rb +115 -0
  23. data/lib/hobix/plugin/bloglines.rb +73 -0
  24. data/lib/hobix/plugin/calendar.rb +220 -0
  25. data/lib/hobix/plugin/flickr.rb +110 -0
  26. data/lib/hobix/plugin/recent_comments.rb +82 -0
  27. data/lib/hobix/plugin/sections.rb +91 -0
  28. data/lib/hobix/plugin/tags.rb +60 -0
  29. data/lib/hobix/publish/ping.rb +53 -0
  30. data/lib/hobix/publish/replicate.rb +283 -0
  31. data/lib/hobix/publisher.rb +18 -0
  32. data/lib/hobix/search/dictionary.rb +141 -0
  33. data/lib/hobix/search/porter_stemmer.rb +203 -0
  34. data/lib/hobix/search/simple.rb +209 -0
  35. data/lib/hobix/search/vector.rb +100 -0
  36. data/lib/hobix/storage/filesys.rb +398 -0
  37. data/lib/hobix/trackbacks.rb +94 -0
  38. data/lib/hobix/util/objedit.rb +193 -0
  39. data/lib/hobix/util/patcher.rb +155 -0
  40. data/lib/hobix/webapp/cli.rb +195 -0
  41. data/lib/hobix/webapp/htmlform.rb +107 -0
  42. data/lib/hobix/webapp/message.rb +177 -0
  43. data/lib/hobix/webapp/urigen.rb +141 -0
  44. data/lib/hobix/webapp/webrick-servlet.rb +90 -0
  45. data/lib/hobix/webapp.rb +723 -0
  46. data/lib/hobix/weblog.rb +860 -0
  47. data/lib/hobix.rb +223 -0
  48. metadata +87 -0
@@ -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
@@ -0,0 +1,141 @@
1
+ require 'uri'
2
+
3
+ module Hobix
4
+ class WebApp
5
+ # :stopdoc:
6
+ class URIGen
7
+ def initialize(scheme, server_name, server_port, script_name, path_info)
8
+ @scheme = scheme
9
+ @server_name = server_name
10
+ @server_port = server_port
11
+ @script_name = script_name
12
+ @path_info = path_info
13
+ uri = "#{scheme}://#{server_name}:#{server_port}"
14
+ uri << script_name.gsub(%r{[^/]+}) {|segment| pchar_escape(segment) }
15
+ uri << path_info.gsub(%r{[^/]+}) {|segment| pchar_escape(segment) }
16
+ @base_uri = URI.parse(uri)
17
+ end
18
+ attr_reader :base_uri
19
+
20
+ def make_relative_uri(hash)
21
+ script = nil
22
+ path_info = nil
23
+ query = nil
24
+ fragment = nil
25
+ hash.each_pair {|k,v|
26
+ case k
27
+ when :script then script = v
28
+ when :path_info then path_info = v
29
+ when :query then query = v
30
+ when :fragment then fragment = v
31
+ else
32
+ raise ArgumentError, "unexpected: #{k} => #{v}"
33
+ end
34
+ }
35
+
36
+ if !script
37
+ script = @script_name
38
+ elsif %r{\A/} !~ script
39
+ script = @script_name.sub(%r{[^/]*\z}) { script }
40
+ while script.sub!(%r{/[^/]*/\.\.(?=/|\z)}, '')
41
+ end
42
+ script.sub!(%r{\A/\.\.(?=/|\z)}, '')
43
+ end
44
+
45
+ path_info = '/' + path_info if %r{\A[^/]} =~ path_info
46
+
47
+ dst = "#{script}#{path_info}"
48
+ dst.sub!(%r{\A/}, '')
49
+ dst.sub!(%r{[^/]*\z}, '')
50
+ dst_basename = $&
51
+
52
+ src = "#{@script_name}#{@path_info}"
53
+ src.sub!(%r{\A/}, '')
54
+ src.sub!(%r{[^/]*\z}, '')
55
+
56
+ while src[%r{\A[^/]*/}] == dst[%r{\A[^/]*/}]
57
+ if $~
58
+ src.sub!(%r{\A[^/]*/}, '')
59
+ dst.sub!(%r{\A[^/]*/}, '')
60
+ else
61
+ break
62
+ end
63
+ end
64
+
65
+ rel_path = '../' * src.count('/')
66
+ rel_path << dst << dst_basename
67
+ rel_path = './' if rel_path.empty?
68
+
69
+ rel_path.gsub!(%r{[^/]+}) {|segment| pchar_escape(segment) }
70
+ if /\A[A-Za-z][A-Za-z0-9+\-.]*:/ =~ rel_path # It seems absolute URI.
71
+ rel_path.sub!(/:/, '%3A')
72
+ end
73
+
74
+ if query
75
+ case query
76
+ when QueryString
77
+ query = query.instance_eval { @escaped_query_string }
78
+ when Hash
79
+ query = query.map {|k, v|
80
+ case v
81
+ when String
82
+ "#{form_escape(k)}=#{form_escape(v)}"
83
+ when Array
84
+ v.map {|e|
85
+ unless String === e
86
+ raise ArgumentError, "unexpected query value: #{e.inspect}"
87
+ end
88
+ "#{form_escape(k)}=#{form_escape(e)}"
89
+ }
90
+ else
91
+ raise ArgumentError, "unexpected query value: #{v.inspect}"
92
+ end
93
+ }.join(';')
94
+ else
95
+ raise ArgumentError, "unexpected query: #{query.inspect}"
96
+ end
97
+ unless query.empty?
98
+ query = '?' + uric_escape(query)
99
+ end
100
+ else
101
+ query = ''
102
+ end
103
+
104
+ if fragment
105
+ fragment = "#" + uric_escape(fragment)
106
+ else
107
+ fragment = ''
108
+ end
109
+
110
+ URI.parse(rel_path + query + fragment)
111
+ end
112
+
113
+ def make_absolute_uri(hash)
114
+ @base_uri + make_relative_uri(hash)
115
+ end
116
+
117
+ Alpha = 'a-zA-Z'
118
+ Digit = '0-9'
119
+ AlphaNum = Alpha + Digit
120
+ Mark = '\-_.!~*\'()'
121
+ Unreserved = AlphaNum + Mark
122
+ PChar = Unreserved + ':@&=+$,'
123
+ def pchar_escape(s)
124
+ s.gsub(/[^#{PChar}]/on) {|c| sprintf("%%%02X", c[0]) }
125
+ end
126
+
127
+ Reserved = ';/?:@&=+$,'
128
+ Uric = Reserved + Unreserved
129
+ def uric_escape(s)
130
+ s.gsub(/[^#{Uric}]/on) {|c| sprintf("%%%02X", c[0]) }
131
+ end
132
+
133
+ def form_escape(s)
134
+ s.gsub(/[#{Reserved}\x00-\x1f\x7f-\xff]/on) {|c|
135
+ sprintf("%%%02X", c[0])
136
+ }.gsub(/ /on) { '+' }
137
+ end
138
+ end
139
+ # :startdoc:
140
+ end
141
+ end
@@ -0,0 +1,90 @@
1
+ # webapp/webrick-servlet.rb provides an adaptor for a webapp and WEBrick.
2
+ #
3
+ # webapp/webrick-servlet.rb registers a handler for .webrick extension
4
+ # using WEBrick::HTTPServlet::FileHandler.add_handler.
5
+ # So WEBrick based HTTP server which require webapp/webrick-servlet.rb
6
+ # can run a web application using webapp which filename has .webrick
7
+ # extension.
8
+ #
9
+ # Such HTTP server can be defined as follows.
10
+ #
11
+ # require 'webapp/webrick-servlet'
12
+ # httpd = WEBrick::HTTPServer.new(:Port => 10080, :DocumentRoot => Dir.getwd)
13
+ # trap(:INT){ httpd.shutdown }
14
+ # httpd.start
15
+ #
16
+ # Apart from that, webapp/webrick-servlet.rb provides
17
+ # WebApp::WEBrickServletHandler.load_servlet which load WEBrick servlet
18
+ # using webapp.
19
+ #
20
+ # WebApp::WEBrickServletHandler.load_servlet can be used for constructing
21
+ # a web site with a single web application as follows.
22
+ #
23
+ # require 'webapp/webrick-servlet'
24
+ # servlet = ARGV.shift
25
+ # httpd = WEBrick::HTTPServer.new(:Port => 10080)
26
+ # trap(:INT){ httpd.shutdown }
27
+ # httpd.mount("/", WebApp::WEBrickServletHandler.load_servlet(servlet))
28
+ # httpd.start
29
+ #
30
+ # When above server accept a request to http://host:10080/foo/bar,
31
+ # the servlet takes /foo/bar as a path_info.
32
+ #
33
+ # Note that the servlet loading mechanism may be used without webapp
34
+ # because the mechanism itself is not webapp dependent.
35
+
36
+ require 'webrick'
37
+
38
+ module Hobix
39
+ class WebApp
40
+ class WEBrickServletHandler
41
+ LoadedServlets = {}
42
+ def WEBrickServletHandler.get_instance(config, name)
43
+ unless LoadedServlets[name]
44
+ LoadedServlets[name] = load_servlet(name)
45
+ end
46
+ LoadedServlets[name]
47
+ end
48
+
49
+ # load a WEBrick servlet written using webapp.
50
+ # WEBrickServletHandler.load_servlet returns a WEBrick servlet
51
+ # generated by WEBrick::HTTPServlet::ProcHandler.
52
+ def WEBrickServletHandler.load_servlet(path)
53
+ begin
54
+ Thread.current[:webrick_load_servlet] = true
55
+ load path, true
56
+ unless Thread.current[:webrick_load_servlet].respond_to? :call
57
+ raise "WEBrick servlet is not registered: #{path}"
58
+ end
59
+ procedure = Thread.current[:webrick_load_servlet]
60
+ return WEBrick::HTTPServlet::ProcHandler.new(procedure)
61
+ ensure
62
+ Thread.current[:webrick_load_servlet] = nil
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
68
+ WEBrick::HTTPServlet::FileHandler.add_handler('webrick',
69
+ Hobix::WebApp::WEBrickServletHandler)
70
+
71
+ if $0 == __FILE__
72
+ # usage: [-p port] [docroot|servlet]
73
+ require 'optparse'
74
+ port = 10080
75
+ ARGV.options {|q|
76
+ q.def_option('--help', 'show this message') {puts q; exit(0)}
77
+ q.def_option('--port=portnum', 'specify server port (default: 10080)') {|num| port = num.to_i }
78
+ q.parse!
79
+ }
80
+ docroot = ARGV.shift || Dir.getwd
81
+ httpd = WEBrick::HTTPServer.new(:Port => port)
82
+ trap(:INT){ httpd.shutdown }
83
+ if File.directory? docroot
84
+ httpd.mount("/", WEBrick::HTTPServlet::FileHandler, docroot, WEBrick::Config::HTTP[:DocumentRootOptions])
85
+ httpd.start
86
+ else
87
+ httpd.mount("/", Hobix::WebApp::WEBrickServletHandler.load_servlet(docroot))
88
+ httpd.start
89
+ end
90
+ end