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
data/lib/hobix/base.rb
ADDED
@@ -0,0 +1,480 @@
|
|
1
|
+
#
|
2
|
+
# = hobix/weblog.rb
|
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 'redcloth'
|
17
|
+
require 'yaml'
|
18
|
+
|
19
|
+
module YAML
|
20
|
+
class Omap
|
21
|
+
def keys; map { |k, v| k }; end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
module Hobix
|
26
|
+
# The BasePlugin class is *bingo* the underlying class for
|
27
|
+
# all Hobix plugins. The +Class::inherited+ hook is used
|
28
|
+
# by this class to keep track of all classes that inherit
|
29
|
+
# from it.
|
30
|
+
class BasePlugin
|
31
|
+
@@plugins = {}
|
32
|
+
@@required_from = nil
|
33
|
+
# Initializes all the plugins, returning
|
34
|
+
# an Array of plugin objects. (Used by the
|
35
|
+
# +Hobix::Weblog+ class.)
|
36
|
+
def BasePlugin.start( req, opts, weblog )
|
37
|
+
@@required_from = req = req.dup
|
38
|
+
if req.tainted?
|
39
|
+
req.untaint if req =~ /^[\w\/\\]+$/
|
40
|
+
end
|
41
|
+
require( req )
|
42
|
+
@@required_from = nil
|
43
|
+
|
44
|
+
if @@plugins[req]
|
45
|
+
@@plugins[req].collect do |p|
|
46
|
+
if opts
|
47
|
+
p.new( weblog, opts )
|
48
|
+
else
|
49
|
+
p.new( weblog )
|
50
|
+
end
|
51
|
+
end
|
52
|
+
else
|
53
|
+
[]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
def BasePlugin.inherited( sub )
|
57
|
+
@@plugins[@@required_from] ||= []
|
58
|
+
@@plugins[@@required_from] << sub
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# The BaseStorage class outlines the fundamental API for
|
63
|
+
# all storage plugins. Storage plugins are responsible
|
64
|
+
# for abstracting away entry queries and managing the loading
|
65
|
+
# of Entry objects. The goal being: cache as much as you can,
|
66
|
+
# be efficient and tidy.
|
67
|
+
#
|
68
|
+
# == Query Methods
|
69
|
+
#
|
70
|
+
# find:: Each of the query methods below uses the +find+ method
|
71
|
+
# to perform its search. This method accepts a Hash of
|
72
|
+
# parameters. Please note that calling +find+ without
|
73
|
+
# parameters will return all entries which qualify for
|
74
|
+
# placement on the front page.
|
75
|
+
#
|
76
|
+
# all:: Returns all entries. Searches find( :all => true )
|
77
|
+
# lastn:: Returns the last _n_ entries which qualify for the
|
78
|
+
# front page.
|
79
|
+
# inpath:: Returns entries within a path which qualify for the
|
80
|
+
# front page.
|
81
|
+
# after:: Returns entries created after a given date.
|
82
|
+
# before:: Returns entries created before a given date.
|
83
|
+
# within:: Returns entries created between a start and
|
84
|
+
# end date.
|
85
|
+
#
|
86
|
+
class BaseStorage < BasePlugin
|
87
|
+
def initialize( weblog )
|
88
|
+
@link = weblog.link
|
89
|
+
end
|
90
|
+
def default_entry_id; "hobix-default-entry"; end
|
91
|
+
def default_entry( author )
|
92
|
+
Hobix::Entry.new do |e|
|
93
|
+
e.id = default_entry_id
|
94
|
+
e.link = e.class.url_link e, @link, "html"
|
95
|
+
e.created = Time.now
|
96
|
+
e.modified = Time.now
|
97
|
+
e.updated = Time.now
|
98
|
+
e.title = "This Ghostly Message From the Slime Will Soon Vanish!"
|
99
|
+
e.tagline = "A temporary message, a tingling sensation, Hobix is up!!"
|
100
|
+
e.author = author
|
101
|
+
e.content = Hobix::Entry.text_processor.new( "Welcome to Hobix! Once you make your first blog post, this entry will disappear. However, in the meantime, you can tweak the CSS of your blog until it suits your satisfaction and you have this bit of words to act as a place holder." )
|
102
|
+
end
|
103
|
+
end
|
104
|
+
def all
|
105
|
+
find( :all => true )
|
106
|
+
end
|
107
|
+
def lastn( n )
|
108
|
+
find( :lastn => ( n || 10 ) )
|
109
|
+
end
|
110
|
+
def inpath( path, n = nil )
|
111
|
+
find( :inpath => path, :lastn => n )
|
112
|
+
end
|
113
|
+
def after( after, n = nil )
|
114
|
+
find( :after => after, :lastn => n )
|
115
|
+
end
|
116
|
+
def before( before, n = nil )
|
117
|
+
find( :before => before, :lastn => n )
|
118
|
+
end
|
119
|
+
def match( expr )
|
120
|
+
find( :match => expr )
|
121
|
+
end
|
122
|
+
def within( after, before )
|
123
|
+
find( :after => after, :before => before )
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
# The BaseOutput plugin is the underlying class for all output
|
128
|
+
# plugins. These plugins are associated to templates. Based on
|
129
|
+
# a template's suffix, the proper output plugin is loaded and
|
130
|
+
# used to generate page output.
|
131
|
+
class BaseOutput < BasePlugin
|
132
|
+
end
|
133
|
+
|
134
|
+
# The BasePublish plugin is the underlying class for all publishing
|
135
|
+
# plugins, which are notified of updates to pages.
|
136
|
+
#
|
137
|
+
# Publish plugins are executed after generation of the site. The plugin
|
138
|
+
# may choose to watch updates to certain types of pages. The plugin also
|
139
|
+
# receives a list of all the pages which have been updated.
|
140
|
+
#
|
141
|
+
# Generally, publish plugins fall into two categories:
|
142
|
+
#
|
143
|
+
# * Plugins which contact a service when certain updates happen.
|
144
|
+
# (Hobix includes an XML-RPC ping, which is triggered whenever
|
145
|
+
# the front page is updated.)
|
146
|
+
# * Plugins which transform Hobix output. (Hobix includes a
|
147
|
+
# replication plugin, which copies updated pages to a remote
|
148
|
+
# system via FTP or SFTP.)
|
149
|
+
#
|
150
|
+
# == Publish methods
|
151
|
+
#
|
152
|
+
# initialize( weblog, settings ):: Like all other plugins, the initialize method takes two parameters,
|
153
|
+
# a Hobix::Weblog object for the weblog being published and the
|
154
|
+
# settings data from the plugin's entry in hobix.yaml.
|
155
|
+
# watch:: (Optional) Returns an array of page types which, when published, activate the plugin.
|
156
|
+
# publish( pages ):: If pages are published and the watch criteria qualifies this plugin,
|
157
|
+
# this method is called with a hash of pages published. The key is the page type
|
158
|
+
# and the value is an array of Page objects.
|
159
|
+
class BasePublish < BasePlugin
|
160
|
+
end
|
161
|
+
|
162
|
+
# The BaseFacet plugin is the superclass for all plugins which have
|
163
|
+
# an interface (CGI, UI, etc.) These interfaces expose some functionality
|
164
|
+
# to the user through an entry form or series of views.
|
165
|
+
class BaseFacet < BasePlugin
|
166
|
+
def self.not_found app
|
167
|
+
app.send_not_found "Action `#{ app.action_uri }' not found. If this address should work, check your plugins."
|
168
|
+
end
|
169
|
+
def protect app, weblog
|
170
|
+
auth = ENV['HTTP_AUTHORIZATION'] || ENV['X-HTTP_AUTHORIZATION']
|
171
|
+
if auth
|
172
|
+
realm = 'Hobix login'
|
173
|
+
auth_type, auth = auth.split ' ', 2
|
174
|
+
authorized = false
|
175
|
+
case auth_type.downcase
|
176
|
+
when 'basic'
|
177
|
+
require 'base64'
|
178
|
+
name, pass = Base64::decode64( auth.strip ).split ':', 2
|
179
|
+
authorized = weblog.authorize name, pass
|
180
|
+
when 'digest'
|
181
|
+
require 'md5'
|
182
|
+
opts = {}
|
183
|
+
auth.gsub( /(\w+)="(.*?)"/ ) { opts[$1] = $2 }
|
184
|
+
app.puts opts.inspect
|
185
|
+
end
|
186
|
+
return true if authorized
|
187
|
+
end
|
188
|
+
|
189
|
+
app.send_unauthorized
|
190
|
+
# nonce = ["#{ Time.now.to_f }:#{ app.action_uri }"].pack("m").gsub /\s/, ''
|
191
|
+
# app.set_header 'WWW-Authenticate', %{Digest qop="auth", realm="#{ realm }", nonce="#{ nonce }", algorithm="MD5"}
|
192
|
+
app.set_header 'WWW-Authenticate', %{Basic realm="#{ realm }"}
|
193
|
+
false
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
# Enumerable::each_with_neighbors from Joel Vanderwerf's
|
198
|
+
# enum extenstions.
|
199
|
+
module Enumerable
|
200
|
+
def each_with_neighbors n = 1, empty = nil
|
201
|
+
nbrs = [empty] * (2 * n + 1)
|
202
|
+
offset = n
|
203
|
+
|
204
|
+
each { |x|
|
205
|
+
nbrs.shift
|
206
|
+
nbrs.push x
|
207
|
+
if offset == 0 # offset is now the offset of the first element, x0,
|
208
|
+
yield nbrs # of the sequence from the center of nbrs, or 0,
|
209
|
+
else # if x0 has already passed the center.
|
210
|
+
offset -= 1
|
211
|
+
end
|
212
|
+
}
|
213
|
+
|
214
|
+
n.times {
|
215
|
+
nbrs.shift
|
216
|
+
nbrs.push empty
|
217
|
+
if offset == 0
|
218
|
+
yield nbrs
|
219
|
+
else
|
220
|
+
offset -= 1
|
221
|
+
end
|
222
|
+
}
|
223
|
+
|
224
|
+
self
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
module BaseProperties
|
229
|
+
# Returns the complete list of properties for the immediate class.
|
230
|
+
# If called on an inheriting class, inherited properties are included.
|
231
|
+
module ClassMethods
|
232
|
+
def properties
|
233
|
+
if superclass.respond_to? :properties
|
234
|
+
s = superclass.properties.dup
|
235
|
+
(@__props || {}).each { |k, v| s[k] = v }
|
236
|
+
s
|
237
|
+
else
|
238
|
+
(@__props || {})
|
239
|
+
end
|
240
|
+
end
|
241
|
+
def prop_sections
|
242
|
+
if superclass.respond_to? :prop_sections
|
243
|
+
s = superclass.prop_sections.dup
|
244
|
+
(@__sects || {}).each { |k, v| s[k] = v }
|
245
|
+
s
|
246
|
+
else
|
247
|
+
(@__sects || {})
|
248
|
+
end
|
249
|
+
end
|
250
|
+
# Quick property definitions in class definitions.
|
251
|
+
def _ name, opts = nil
|
252
|
+
@__props ||= YAML::Omap[]
|
253
|
+
@__props[name] = opts
|
254
|
+
attr_accessor name unless method_defined? "#{ name }="
|
255
|
+
end
|
256
|
+
# Property sections
|
257
|
+
def _! name, opts = {}
|
258
|
+
@__sects ||= YAML::Omap[]
|
259
|
+
opts[:__sect] = @__props.last[0] rescue nil
|
260
|
+
@__sects[name] = opts
|
261
|
+
end
|
262
|
+
end
|
263
|
+
# Build a simple map of properties
|
264
|
+
def property_map
|
265
|
+
self.class.properties.map do |name, opts|
|
266
|
+
if opts
|
267
|
+
yreq = opts[:req] ? :req : :opt
|
268
|
+
["@#{ name }", yreq] if yreq
|
269
|
+
end
|
270
|
+
end.compact
|
271
|
+
end
|
272
|
+
# Build a property map for the YAML module
|
273
|
+
def to_yaml_properties
|
274
|
+
property_map.find_all do |prop, req|
|
275
|
+
case req
|
276
|
+
when :opt
|
277
|
+
not instance_variable_get( prop ).nil?
|
278
|
+
when :req
|
279
|
+
true
|
280
|
+
end
|
281
|
+
end.
|
282
|
+
collect do |prop, req|
|
283
|
+
prop
|
284
|
+
end
|
285
|
+
end
|
286
|
+
def self.append_features klass
|
287
|
+
super
|
288
|
+
klass.extend ClassMethods
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
# placed here to avoid dependency cycle between base.rb and weblog.rb
|
293
|
+
class Weblog
|
294
|
+
@@entry_classes = []
|
295
|
+
def self.add_entry_class( c )
|
296
|
+
@@entry_classes << c
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
class BaseContent
|
301
|
+
include BaseProperties
|
302
|
+
|
303
|
+
_! 'Entry Information'
|
304
|
+
_ :id
|
305
|
+
_ :link
|
306
|
+
_ :title, :edit_as => :text, :search => :fulltext
|
307
|
+
_ :created, :edit_as => :datetime, :search => :prefix
|
308
|
+
_ :modified, :show_as => :datetime
|
309
|
+
_ :updated, :show_as => :datetime
|
310
|
+
_ :tags, :edit_as => :text, :search => :prefix
|
311
|
+
|
312
|
+
def initialize; yield self if block_given?; end
|
313
|
+
def day_id; created.strftime( "%Y/%m/%d" ) if created; end
|
314
|
+
def month_id; created.strftime( "%Y/%m" ) if created; end
|
315
|
+
def year_id; created.strftime( "%Y" ) if created; end
|
316
|
+
def section_id; File.dirname( id ) if id; end
|
317
|
+
def base_id; File.basename( id ) if id; end
|
318
|
+
def self.url_link( e, url = nil, ext = nil ); "#{ url }/#{ link_format e }#{ '.' + ext if ext }"; end
|
319
|
+
def self.link_format( e ); e.id; end
|
320
|
+
def force_tags; []; end
|
321
|
+
|
322
|
+
#
|
323
|
+
# If set to true, tags won't be deduced from the entry id
|
324
|
+
#
|
325
|
+
@@no_implicit_tags = false
|
326
|
+
|
327
|
+
def self.no_implicit_tags
|
328
|
+
@@no_implicit_tags = true
|
329
|
+
end
|
330
|
+
|
331
|
+
#
|
332
|
+
# When using implicit tag, the blog root (i.e) is not considered
|
333
|
+
# unless you set the value of +@@root_tag+ to what you need.
|
334
|
+
#
|
335
|
+
@@root_tag = nil
|
336
|
+
def self.root_tag=( tag )
|
337
|
+
@@root_tag = tag
|
338
|
+
end
|
339
|
+
|
340
|
+
#
|
341
|
+
# When computing so-called implicit 'implicit-tag', whether
|
342
|
+
# or not we should split the path into several tags
|
343
|
+
# (default: false)
|
344
|
+
#
|
345
|
+
@@split_implicit_tags = false
|
346
|
+
|
347
|
+
def self.split_implicit_tags
|
348
|
+
@@split_implicit_tags = true
|
349
|
+
end
|
350
|
+
|
351
|
+
#
|
352
|
+
# return an array of tags deduced from the path
|
353
|
+
# i.e. a path like ruby/hobix/foo.yml will lead
|
354
|
+
# to [ ruby, hobix ] tags
|
355
|
+
# Occurence of . (alone) will be either removed or replaced
|
356
|
+
# by the value of +root_tag+
|
357
|
+
#
|
358
|
+
def path_to_tags( path )
|
359
|
+
return [] if @@no_implicit_tags
|
360
|
+
return [] if path.nil?
|
361
|
+
if @@split_implicit_tags
|
362
|
+
tags_array = path.split("/").find_all { |e| e.size > 0 }
|
363
|
+
tags_array.pop # Last item is the entry title
|
364
|
+
else
|
365
|
+
tags_array = [ File.dirname( path )]
|
366
|
+
end
|
367
|
+
tags_array.map { |e| e == '.' ? @@root_tag : e }.compact
|
368
|
+
end
|
369
|
+
|
370
|
+
#
|
371
|
+
# return canonical tags, i.e. tags that are forced and that are deduced
|
372
|
+
# from the entry path
|
373
|
+
#
|
374
|
+
def canonical_tags( path=nil )
|
375
|
+
( force_tags + path_to_tags( path || self.id ) ).uniq
|
376
|
+
end
|
377
|
+
|
378
|
+
def tags;( canonical_tags + Array( @tags ) ).uniq; end
|
379
|
+
|
380
|
+
def self.yaml_type( tag )
|
381
|
+
# if self.respond_to? :yaml_as
|
382
|
+
# yaml_as tag
|
383
|
+
# else
|
384
|
+
if tag =~ /^tag:([^:]+):(.+)$/
|
385
|
+
define_method( :to_yaml_type ) { "!#$1/#$2" }
|
386
|
+
YAML::add_domain_type( $1, $2 ) { |t, v| self.maker( v ) }
|
387
|
+
end
|
388
|
+
# end
|
389
|
+
end
|
390
|
+
|
391
|
+
alias to_yaml_orig to_yaml
|
392
|
+
def to_yaml( opts = {} )
|
393
|
+
opts[:UseFold] = true if opts.respond_to? :[]
|
394
|
+
self.class.text_processor_fields.each do |f|
|
395
|
+
v = instance_variable_get( '@' + f )
|
396
|
+
if v.is_a? self.class.text_processor
|
397
|
+
instance_eval %{
|
398
|
+
def @#{ f }.to_yaml( opts = {} )
|
399
|
+
s = self.to_str
|
400
|
+
def s.to_yaml_style; :literal; end
|
401
|
+
s.to_yaml( opts )
|
402
|
+
end
|
403
|
+
}
|
404
|
+
end
|
405
|
+
end
|
406
|
+
to_yaml_orig( opts )
|
407
|
+
end
|
408
|
+
|
409
|
+
# Load the weblog entry from a file.
|
410
|
+
def self.load( file )
|
411
|
+
File.open( file ) { |f| YAML::load( f ) }
|
412
|
+
end
|
413
|
+
|
414
|
+
# Accessor which returns the text processor used for untyped
|
415
|
+
# strings in Entry fields. (defaults to +RedCloth+.)
|
416
|
+
def self.text_processor; RedCloth; end
|
417
|
+
# Returns an Array of fields to which the text processor applies.
|
418
|
+
def self.text_processor_fields
|
419
|
+
self.properties.map do |name, opts|
|
420
|
+
name.to_s if opts and opts[:text_processor]
|
421
|
+
end.compact
|
422
|
+
end
|
423
|
+
# Factory method for generating Entry classes from a hash. Used
|
424
|
+
# by the YAML loader.
|
425
|
+
def self.maker( val )
|
426
|
+
self::text_processor_fields.each do |f|
|
427
|
+
if val[f].respond_to? :value
|
428
|
+
str = val[f].value
|
429
|
+
def str.to_html
|
430
|
+
self
|
431
|
+
end
|
432
|
+
val[f] = str
|
433
|
+
elsif val[f].respond_to? :to_str
|
434
|
+
val[f] = self::text_processor.new( val[f].to_str )
|
435
|
+
end
|
436
|
+
end
|
437
|
+
YAML::object_maker( self, val )
|
438
|
+
end
|
439
|
+
end
|
440
|
+
|
441
|
+
# The BaseEntry class is the underlying class for all Hobix
|
442
|
+
# entries (i.e. the content for your website/blahhg.)
|
443
|
+
class BaseEntry < BaseContent
|
444
|
+
|
445
|
+
_ :id
|
446
|
+
_ :link
|
447
|
+
_ :title, :edit_as => :text, :search => :fulltext
|
448
|
+
_ :author, :req => true, :edit_as => :text, :search => :prefix
|
449
|
+
_ :contributors, :edit_as => :array, :search => :prefix
|
450
|
+
_ :created, :edit_as => :datetime, :search => :prefix
|
451
|
+
_ :modified, :show_as => :datetime
|
452
|
+
_ :updated, :show_as => :datetime
|
453
|
+
_ :tags, :edit_as => :text, :search => :prefix
|
454
|
+
_ :content, :edit_as => :textarea, :search => :fulltext, :text_processor => true
|
455
|
+
_ :content_ratings, :edit_as => :array
|
456
|
+
|
457
|
+
def content_ratings; @content_ratings || [:ham]; end
|
458
|
+
|
459
|
+
def self.inherited( sub )
|
460
|
+
Weblog::add_entry_class( sub )
|
461
|
+
end
|
462
|
+
|
463
|
+
# Build the searchable text
|
464
|
+
def to_search
|
465
|
+
self.class.properties.map do |name, opts|
|
466
|
+
next unless opts
|
467
|
+
val = instance_variable_get( "@#{ name }" )
|
468
|
+
next unless val
|
469
|
+
val = val.strftime "%Y-%m-%dT%H:%M:%S" if val.respond_to? :strftime
|
470
|
+
case opts[:search]
|
471
|
+
when :prefix
|
472
|
+
"#{ name }:" + val.to_s
|
473
|
+
when :fulltext
|
474
|
+
val.to_s
|
475
|
+
end
|
476
|
+
end.compact.join "\n"
|
477
|
+
end
|
478
|
+
|
479
|
+
end
|
480
|
+
end
|
data/lib/hobix/bixwik.rb
ADDED
@@ -0,0 +1,200 @@
|
|
1
|
+
#
|
2
|
+
# = hobix/bixwik.rb
|
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 'hobix/weblog'
|
17
|
+
|
18
|
+
module Hobix
|
19
|
+
# The BixWik class is an extended Weblog, which acts like a Wiki.
|
20
|
+
# (See http://instiki.org/ for inspiration.)
|
21
|
+
class BixWikPlugin < Hobix::BasePlugin
|
22
|
+
def initialize( weblog )
|
23
|
+
class << weblog
|
24
|
+
include Hobix::BixWik
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
module BixWik
|
30
|
+
|
31
|
+
QUICK_MENU = YAML::load <<-END
|
32
|
+
--- %YAML:1.0 !omap
|
33
|
+
- HomePage: [Home Page, H, Start Over]
|
34
|
+
- list/index: [All Pages, A, Alphabetically sorted list of pages]
|
35
|
+
- recent/index: [Recently Revised, U, Pages sorted by when they were last changed]
|
36
|
+
- authors/index: [Authors, ~, Who wrote what]
|
37
|
+
- FeedList: [Feed List, ~, Subscribe to changes by RSS]
|
38
|
+
END
|
39
|
+
|
40
|
+
def default_entry_class; "Hobix::BixWik::Entry"; end
|
41
|
+
def default_index_class; "Hobix::BixWik::IndexEntry"; end
|
42
|
+
|
43
|
+
# Handler for templates with `index' prefix. These pages simply
|
44
|
+
# mirror the `HomePage' entry.
|
45
|
+
def skel_index( path_storage, section_path )
|
46
|
+
homePage = path_storage.match( /^HomePage$/ ).first
|
47
|
+
page = Page.new( 'index' )
|
48
|
+
unless homePage
|
49
|
+
homePage = Hobix::Storage::IndexEntry.new( path_storage.default_entry( authors.keys.first ) )
|
50
|
+
end
|
51
|
+
page.timestamp = homePage.created
|
52
|
+
page.updated = homePage.created
|
53
|
+
yield :page => page, :entry => homePage
|
54
|
+
end
|
55
|
+
|
56
|
+
# Handler for templates with `list/index' prefix. These templates will
|
57
|
+
# receive IndexEntry objects for every entry in the system. Only one
|
58
|
+
# index page is requested by this handler.
|
59
|
+
def skel_recent_index( path_storage, section_path )
|
60
|
+
index_entries = storage.find( :all => true )
|
61
|
+
page = Page.new( 'list/index' )
|
62
|
+
page.timestamp = index_entries.first.created
|
63
|
+
page.updated = storage.last_updated( index_entries )
|
64
|
+
yield :page => page, :entries => index_entries
|
65
|
+
end
|
66
|
+
|
67
|
+
# Handler for templates with `recent/index' prefix. These templates will
|
68
|
+
# receive entries loaded by +Hobix::BaseStorage#lastn+. Only one
|
69
|
+
# index page is requested by this handler.
|
70
|
+
def skel_recent_index( path_storage, section_path )
|
71
|
+
index_entries = storage.lastn( @lastn || 120 )
|
72
|
+
page = Page.new( 'recent/index' )
|
73
|
+
page.timestamp = index_entries.first.created
|
74
|
+
page.updated = storage.last_updated( index_entries )
|
75
|
+
yield :page => page, :entries => index_entries
|
76
|
+
end
|
77
|
+
|
78
|
+
# Handler for templates with `list/index' prefix. These templates will
|
79
|
+
# receive a list of all pages in the Wiki.
|
80
|
+
def skel_list_index( path_storage, section_path )
|
81
|
+
all_pages = storage.all
|
82
|
+
page = Page.new( 'list/index' )
|
83
|
+
page.timestamp = all_pages.first.created
|
84
|
+
page.updated = storage.last_updated( all_pages )
|
85
|
+
yield :page => page, :entries => all_pages, :no_load => true
|
86
|
+
end
|
87
|
+
|
88
|
+
def abs_link( word )
|
89
|
+
output_entry_map[word] && output_entry_map[word][:page].link
|
90
|
+
end
|
91
|
+
|
92
|
+
def wiki_page( src )
|
93
|
+
src.gsub( /\b([A-Z][a-z]+[A-Z][\w\/]+)\b/ ) { wiki_link( $1 ) }
|
94
|
+
end
|
95
|
+
|
96
|
+
def wiki_link( word )
|
97
|
+
abs_link = output_entry_map[word]
|
98
|
+
if abs_link
|
99
|
+
"<a class=\"existingWikiWord\" href=\"#{ expand_path( abs_link[:page].link ) }\">#{ Hobix::BixWik::wiki_word word }</a>"
|
100
|
+
else
|
101
|
+
"<span class=\"newWikiWord\">#{ Hobix::BixWik::wiki_word word }<a href=\"#{ expand_path( "control/edit/#{ word }" ) }\">?</a></span>"
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def self.wiki_word( id )
|
106
|
+
Hobix::BixWik::QUICK_MENU[ id ].to_a.first || id.gsub( /^\w|_\w|[A-Z]/ ) { |up| " #{up[-1, 1].upcase}" }.strip
|
107
|
+
end
|
108
|
+
|
109
|
+
require 'redcloth'
|
110
|
+
class WikiRedCloth < RedCloth
|
111
|
+
end
|
112
|
+
|
113
|
+
class IndexEntry < Hobix::IndexEntry
|
114
|
+
_ :author
|
115
|
+
def title
|
116
|
+
Hobix::BixWik::wiki_word( self.id )
|
117
|
+
end
|
118
|
+
def to_yaml_type
|
119
|
+
"!hobix.com,2004/bixwik/indexEntry"
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
class Entry < Hobix::Entry
|
124
|
+
def title
|
125
|
+
Hobix::BixWik::wiki_word( self.id )
|
126
|
+
end
|
127
|
+
def to_yaml_type
|
128
|
+
"!hobix.com,2004/bixwik/entry"
|
129
|
+
end
|
130
|
+
def self.text_processor; WikiRedCloth; end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
YAML::add_domain_type( 'hobix.com,2004', 'bixwik/entry' ) do |type, val|
|
136
|
+
Hobix::BixWik::Entry::maker( val )
|
137
|
+
end
|
138
|
+
|
139
|
+
YAML::add_domain_type( 'hobix.com,2004', 'bixwik/indexEntry' ) do |type, val|
|
140
|
+
YAML::object_maker( Hobix::BixWik::IndexEntry, val )
|
141
|
+
end
|
142
|
+
|
143
|
+
module Hobix
|
144
|
+
module Facets
|
145
|
+
class WikiEdit < BaseFacet
|
146
|
+
def initialize( weblog, defaults = {} )
|
147
|
+
@weblog = weblog
|
148
|
+
end
|
149
|
+
def get app
|
150
|
+
if app.respond_to? :action_uri
|
151
|
+
ns, method_id = app.action_uri.split( '/', 2 )
|
152
|
+
return false unless ns == "edit"
|
153
|
+
|
154
|
+
# Display publisher page
|
155
|
+
app.content_type = 'text/html'
|
156
|
+
app.puts ::ERB.new( erb_src, nil, nil, "_bixwik" ).result( binding )
|
157
|
+
return true
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
module Out
|
164
|
+
class Quick
|
165
|
+
def banner_erb; %{
|
166
|
+
<% page_id = page.id %>
|
167
|
+
<% page_id = 'HomePage' if page.id == 'index' %>
|
168
|
+
<% page_name = Hobix::BixWik::wiki_word( page_id ) %>
|
169
|
+
<div id="banner">
|
170
|
+
<% if page_id == "HomePage" %>
|
171
|
+
<h1 id="title"><%= weblog.title %></h1>
|
172
|
+
<% if weblog.tagline %><div id="tagline"><%= weblog.tagline %></div><% end %>
|
173
|
+
<% else %>
|
174
|
+
<div id="title"><%= weblog.title %></div>
|
175
|
+
<h1 id="pageName"><%= page_name %></h1>
|
176
|
+
<% end %>
|
177
|
+
<form id="navigationForm" class="navigation" action="<%= weblog.expand_path( 'search' ) %>" action="get" style="font-size: 10px">
|
178
|
+
<% Hobix::BixWik::QUICK_MENU.each do |menu_link, attr| %>
|
179
|
+
<% if page_id == menu_link %>
|
180
|
+
<%= attr[0] %>
|
181
|
+
<% else %>
|
182
|
+
<a href="<%= weblog.abs_link( menu_link ) %>" title="<% if attr[1] %>[<%= attr[1] %>] <% end %><%= attr[2] %>"
|
183
|
+
accesskey="<%= attr[1] %>"><%= attr[0] %></a>
|
184
|
+
<% end %> |
|
185
|
+
<% end %>
|
186
|
+
<input type="text" id="searchField" name="query" style="font-size: 10px" value="Search" onClick="this.value == 'Search' ? this.value = '' : true">
|
187
|
+
</form>
|
188
|
+
</div> }
|
189
|
+
end
|
190
|
+
def entry_title_erb; end
|
191
|
+
def entry_content_erb
|
192
|
+
%{ <div class="entryContent"><%= weblog.wiki_page( entry.content.to_html ) %></div> }
|
193
|
+
end
|
194
|
+
def sidebar_erb; nil; end
|
195
|
+
def entry_footer_erb; %{
|
196
|
+
Revision from <%= ( entry.modified || entry.created ).strftime( "%d %B %Y at %H:%M" ) %> by <%= weblog.wiki_link( "authors/" + entry.author ) %> }
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|