hobix 0.4

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