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.
- checksums.yaml +7 -0
- data/bin/hobix +90 -0
- data/lib/hobix/api.rb +91 -0
- data/lib/hobix/article.rb +22 -0
- data/lib/hobix/base.rb +477 -0
- data/lib/hobix/bixwik.rb +200 -0
- data/lib/hobix/commandline.rb +661 -0
- data/lib/hobix/comments.rb +99 -0
- data/lib/hobix/config.rb +39 -0
- data/lib/hobix/datamarsh.rb +110 -0
- data/lib/hobix/entry.rb +83 -0
- data/lib/hobix/facets/comments.rb +74 -0
- data/lib/hobix/facets/publisher.rb +314 -0
- data/lib/hobix/facets/trackbacks.rb +80 -0
- data/lib/hobix/linklist.rb +76 -0
- data/lib/hobix/out/atom.rb +92 -0
- data/lib/hobix/out/erb.rb +64 -0
- data/lib/hobix/out/okaynews.rb +55 -0
- data/lib/hobix/out/quick.rb +312 -0
- data/lib/hobix/out/rdf.rb +97 -0
- data/lib/hobix/out/redrum.rb +26 -0
- data/lib/hobix/out/rss.rb +115 -0
- data/lib/hobix/plugin/bloglines.rb +73 -0
- data/lib/hobix/plugin/calendar.rb +220 -0
- data/lib/hobix/plugin/flickr.rb +110 -0
- data/lib/hobix/plugin/recent_comments.rb +82 -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 +398 -0
- data/lib/hobix/trackbacks.rb +94 -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 +860 -0
- data/lib/hobix.rb +223 -0
- metadata +87 -0
data/lib/hobix/weblog.rb
ADDED
@@ -0,0 +1,860 @@
|
|
1
|
+
#
|
2
|
+
# = hobix/weblog.rb
|
3
|
+
#
|
4
|
+
# Hobix command-line weblog system.
|
5
|
+
#
|
6
|
+
# Copyright (c) 2003-2004 why the lucky stiff
|
7
|
+
# Copyright (c) 2005 MenTaLguY
|
8
|
+
#
|
9
|
+
# Written & maintained by why the lucky stiff <why@ruby-lang.org>
|
10
|
+
# Additional bits by MenTaLguY <mental@rydia.net>
|
11
|
+
#
|
12
|
+
# This program is free software, released under a BSD license.
|
13
|
+
# See COPYING for details.
|
14
|
+
#
|
15
|
+
#--
|
16
|
+
# $Id$
|
17
|
+
#++
|
18
|
+
require 'hobix/base'
|
19
|
+
require 'hobix/entry'
|
20
|
+
require 'hobix/linklist'
|
21
|
+
require 'find'
|
22
|
+
require 'ftools'
|
23
|
+
require 'uri'
|
24
|
+
require 'yaml'
|
25
|
+
|
26
|
+
module Hobix
|
27
|
+
# The UriStr mixin ensures that URIs are supplied a to_str
|
28
|
+
# method and a to_yaml method which allows the URI to act more
|
29
|
+
# like a string. In most cases, Hobix users will be using URIs
|
30
|
+
# as strings.
|
31
|
+
module UriStr
|
32
|
+
def to_str; to_s; end
|
33
|
+
def to_yaml( opts = {} )
|
34
|
+
self.to_s.to_yaml( opts )
|
35
|
+
end
|
36
|
+
def rooturi
|
37
|
+
rooturi = dup
|
38
|
+
rooturi.path = ''
|
39
|
+
rooturi
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
#
|
44
|
+
# The Page class is very simple class which contains information
|
45
|
+
# specific to a template.
|
46
|
+
#
|
47
|
+
# == Introduction
|
48
|
+
#
|
49
|
+
# The +id+, +next+ and +prev+ accessors
|
50
|
+
# provide ids for the current page and its neighbors
|
51
|
+
# (for example, in the case of monthly archives, which may have
|
52
|
+
# surrounding months.)
|
53
|
+
#
|
54
|
+
# To get complete URLs for each of the above, use: +link+,
|
55
|
+
# +next_link+, and +prev_link+.
|
56
|
+
#
|
57
|
+
# The +timestamp+ accessor contains the earliest date pertinent to
|
58
|
+
# the page. For example, in the case of a monthly archive, it
|
59
|
+
# will contain a +Time+ object for the first day of the month.
|
60
|
+
# In the case of the `index' page, you'll get a Time object for
|
61
|
+
# the earliest entry on the page.
|
62
|
+
#
|
63
|
+
# The +updated+ accessor contains the latest date pertinent to
|
64
|
+
# the page. Usually this would be the most recent modification
|
65
|
+
# time among entries on the page. This accessor is used by the
|
66
|
+
# regeneration system to determine if a page needs regeneration.
|
67
|
+
# See +Hobix::Weblog#regenerate+ for more.
|
68
|
+
#
|
69
|
+
# == Context in Hobix
|
70
|
+
#
|
71
|
+
# There are only two places you'll encounter this class in Hobix.
|
72
|
+
#
|
73
|
+
# If you are writing an output plugin, a Page class is passed in
|
74
|
+
# the _vars_ hash to the +BaseOutput#load+ method. You'll find
|
75
|
+
# the class in vars[:page].
|
76
|
+
#
|
77
|
+
# If you are writing ERB or RedRum templates, these vars are passed
|
78
|
+
# into the templates. The Page class is accessible as a variable
|
79
|
+
# called `page'.
|
80
|
+
#
|
81
|
+
# = Examples
|
82
|
+
#
|
83
|
+
# == Example 1: Pagination in a Template
|
84
|
+
#
|
85
|
+
# Let's say we want every entry in our site to contain links to
|
86
|
+
# the entries which are chronologically nearby.
|
87
|
+
#
|
88
|
+
# If we're using RedRum templates, we could do the following
|
89
|
+
# in entry.html.redrum:
|
90
|
+
#
|
91
|
+
# <% if page.prev %>"last":<%= page.prev_link %><% end %>
|
92
|
+
# <% if page.next %>"next":<%= page.next_link %><% end %>
|
93
|
+
#
|
94
|
+
class Page
|
95
|
+
attr_accessor :link, :next, :prev, :timestamp, :updated
|
96
|
+
def initialize( id )
|
97
|
+
@id = id
|
98
|
+
end
|
99
|
+
def id; dirj( @dir, @id ).gsub( /^\/+/, '' ); end
|
100
|
+
def link; dirj( @dir, @id ) + @ext; end
|
101
|
+
def next_link; dirj( @dir, @next ) + @ext if @next; end
|
102
|
+
def prev_link; dirj( @dir, @prev ) + @ext if @prev; end
|
103
|
+
def dirj( dir, link ) #:nodoc:
|
104
|
+
if link[0] != ?/ and link != '.'
|
105
|
+
link = File.join( dir == '.' ? "/" : dir, link )
|
106
|
+
end
|
107
|
+
link
|
108
|
+
end
|
109
|
+
def add_path( dir, ext ) #:nodoc:
|
110
|
+
@dir, @ext = dir, ext
|
111
|
+
end
|
112
|
+
def reference_fields; [:next, :prev]; end
|
113
|
+
def references; reference_fields.map { |f| self.send f }.compact; end
|
114
|
+
end
|
115
|
+
#
|
116
|
+
# The Weblog class is the core of Hobix scripting. Although often
|
117
|
+
# you use it's +storage+ accessor to get to entries, the Weblog
|
118
|
+
# class itself contains weblog configuration information and
|
119
|
+
# methods for managing the weblog.
|
120
|
+
#
|
121
|
+
# == Properties
|
122
|
+
#
|
123
|
+
# The following accessors are available for retrieving configuration
|
124
|
+
# data, all of which is stored in hobix.yaml.
|
125
|
+
#
|
126
|
+
# title:: The title of the weblog.
|
127
|
+
# link:: The absolute url to the weblog. (When accessed through
|
128
|
+
# the class -- weblog.link -- this is returned as a URI.)
|
129
|
+
# authors:: A hash, in which keys are author's abbreviated names,
|
130
|
+
# paired with hashes of `name', `url' and `email'
|
131
|
+
# information.
|
132
|
+
# contributors:: Same structure as the authors hash. For storing
|
133
|
+
# information on third-party contributors.
|
134
|
+
# tagline:: The short catchphrase associated with the weblog.
|
135
|
+
# copyright:: Brief copyright information.
|
136
|
+
# period:: How often is the weblog updated? Frequency in seconds.
|
137
|
+
# path:: Complete system path to the directory containing
|
138
|
+
# hobix.yaml.
|
139
|
+
# sections:: Specially tagged directories which act as independent
|
140
|
+
# subsites or hidden categories.
|
141
|
+
# requires:: A list of required libraries, paired with possible
|
142
|
+
# configuration data for a library.
|
143
|
+
# entry_path:: Path to entry storage.
|
144
|
+
# skel_path:: Path to template's directory.
|
145
|
+
# output_path:: Path to output directory.
|
146
|
+
# lib_path:: Path to extension library directory.
|
147
|
+
#
|
148
|
+
# == Regeneration
|
149
|
+
#
|
150
|
+
# One of the primary uses of the Weblog class is to coordinate
|
151
|
+
# regenerations of the site. More about regeneration can be found
|
152
|
+
# in the documentation for the +regenerate+ method.
|
153
|
+
#
|
154
|
+
# == Skel Methods
|
155
|
+
#
|
156
|
+
# The Weblog class also contains handlers for template prefixes.
|
157
|
+
# (Templates are usually contained in `skel').
|
158
|
+
#
|
159
|
+
# Each `prefix' has its accompanying skel_prefix method. So, for
|
160
|
+
# `index' templates (such as index.html.erb), the skel_index method
|
161
|
+
# is executed and passed a block which is supplied a hash by the skel
|
162
|
+
# method.
|
163
|
+
#
|
164
|
+
# Usually this hash only needs to contain :page and :entries (or :entry)
|
165
|
+
# items. Any other items will simply be added to the vars hash.
|
166
|
+
#
|
167
|
+
# To give you a general idea, skel_index looks something like this:
|
168
|
+
#
|
169
|
+
# def skel_index( path_storage )
|
170
|
+
# index_entries = path_storage.lastn( @lastn )
|
171
|
+
# page = Page.new( 'index' )
|
172
|
+
# page.prev = index_entries.last.created.strftime( "%Y/%m/index" )
|
173
|
+
# page.timestamp = index_entries.first.created
|
174
|
+
# page.updated = path_storage.last_modified( index_entries )
|
175
|
+
# yield :page => page, :entries => index_entries
|
176
|
+
# end
|
177
|
+
#
|
178
|
+
# The page object is instantiated, describing where output will go.
|
179
|
+
# The entries list, describing which entries qualify for this prefix,
|
180
|
+
# is queried from storage. We then yield back to the regeneration
|
181
|
+
# system with our hash.
|
182
|
+
#
|
183
|
+
# Creating your own template prefixes is simply a matter of adding
|
184
|
+
# a new skel method for that prefix to the Weblog class.
|
185
|
+
#
|
186
|
+
# = Examples
|
187
|
+
#
|
188
|
+
# == Example 1: Viewing Configuration
|
189
|
+
#
|
190
|
+
# Since configuration is stored in YAML, you can generate the hobix.yaml
|
191
|
+
# configuration file by simply running +to_yaml+ on a weblog.
|
192
|
+
#
|
193
|
+
# require 'hobix/weblog'
|
194
|
+
# weblog = Hobix::Weblog.load( '/my/blahhg/hobix.yaml' )
|
195
|
+
# puts weblog.to_yaml
|
196
|
+
# #=> --- # prints YAML configuration
|
197
|
+
#
|
198
|
+
# == Example 2: Adding a Template Prefix
|
199
|
+
#
|
200
|
+
# On Hobix.com, only news entries are shown on the front page. The
|
201
|
+
# site also has `about' and `learn' entry paths for storing the faqs
|
202
|
+
# and tutorials. Although I didn't want to display the complete
|
203
|
+
# text of these items, I did want a sidebar to contain links to them.
|
204
|
+
#
|
205
|
+
# So I added a `sidebar' prefix, which loads from these entry paths.
|
206
|
+
# I have a sidebar.html.erb, which is included using Apache SSIs.
|
207
|
+
# The advantage to this approach is that when an update occurs in
|
208
|
+
# either of these paths, the sidebar will be updated in the next
|
209
|
+
# regeneration. Rather than having to regenerate every page in the
|
210
|
+
# site to see the change reflected.
|
211
|
+
#
|
212
|
+
# I added a `lib/hobix.com.rb' to the site's `lib' directory. And
|
213
|
+
# in hobix.yaml, I included a line requiring this file. The file
|
214
|
+
# simply contains the new skel method.
|
215
|
+
#
|
216
|
+
# module Hobix
|
217
|
+
# class Weblog
|
218
|
+
# def skel_sidebar( path_storage )
|
219
|
+
# ## Load `about' and `learn' entries
|
220
|
+
# abouts = path_storage.find( :all => true, :inpath => 'about' ).reverse
|
221
|
+
# learns = path_storage.find( :all => true, :inpath => 'learn' ).reverse
|
222
|
+
#
|
223
|
+
# ## Create page data
|
224
|
+
# page = Page.new( 'sidebar' )
|
225
|
+
# page.updated = path_storage.last_modified( abouts + learns )
|
226
|
+
# yield :page => page,
|
227
|
+
# :about_entries => abouts, :learn_entries => learns
|
228
|
+
# end
|
229
|
+
# end
|
230
|
+
# end
|
231
|
+
#
|
232
|
+
# There is a lot going on here. I'll try to explain the most vital parts and
|
233
|
+
# leave the rest up to you.
|
234
|
+
#
|
235
|
+
# First, storage queries don't return full Entry objects. You can read more
|
236
|
+
# about this in the +Hobix::BaseStorage+ class docs. The storage query returns
|
237
|
+
# Arrays which contain each entry's id (a String) and the entry's modification time
|
238
|
+
# (a Time object).
|
239
|
+
#
|
240
|
+
# See, the regeneration system will do the business of loading the full entries.
|
241
|
+
# The skel method's job is just to report which entries *qualify* for a
|
242
|
+
# template. The regeneration system will only load those entries
|
243
|
+
# if an update is needed.
|
244
|
+
#
|
245
|
+
# We create a Page object, which dictates that the output will be saved to
|
246
|
+
# /sidebar.ext. A modification time is discovered by passing a combined list
|
247
|
+
# to +Hobix::BaseStorage#last_modified+. The +updated+ property is being
|
248
|
+
# set to the latest timestamp among the about and learn entries.
|
249
|
+
#
|
250
|
+
# PLEASE NOTE: The +updated+ property is very important. The regeneration
|
251
|
+
# system will use this timestamp to determine what pages need updating.
|
252
|
+
# See +Hobix::Weblog#regenerate+ for more.
|
253
|
+
#
|
254
|
+
# We then yield to the regeneration system. Note that any hash key which
|
255
|
+
# ends with `entries' will have its contents loaded as full Entry objects, should
|
256
|
+
# the prefix qualify for regeneration.
|
257
|
+
#
|
258
|
+
# == The page_storage variable
|
259
|
+
#
|
260
|
+
# The +page_storage+ variable passed into the method is a trimmed copy of the
|
261
|
+
# +Weblog#storage+ variable. Whereas +Weblog#storage+ gives you access to all
|
262
|
+
# stored entries, +page_storage+ only gives you access to entries within
|
263
|
+
# a certain path.
|
264
|
+
#
|
265
|
+
# So, if you have a template skel/index.html.quick, then this template will
|
266
|
+
# be passed a +path_storage+ variable which encompasses all entries. However,
|
267
|
+
# for template skel/friends/eric/index.html.quick will be given a
|
268
|
+
# +path_storage+ which includes only entries in the `friends/eric' path.
|
269
|
+
#
|
270
|
+
# The simple rule is: if you want to have access to load from the entire
|
271
|
+
# weblog storage, use +storage+. If you want your template to honor its
|
272
|
+
# path, use +path_storage+. Both are +Hobix::BaseStorage+ objects and
|
273
|
+
# respond to the same methods.
|
274
|
+
class Weblog
|
275
|
+
include BaseProperties
|
276
|
+
|
277
|
+
_! 'Basic Information'
|
278
|
+
_ :title, :req => true, :edit_as => :text
|
279
|
+
_ :link, :req => true, :edit_as => :text
|
280
|
+
_ :tagline, :req => true, :edit_as => :text
|
281
|
+
_ :copyright, :edit_as => :text
|
282
|
+
_ :period, :edit_as => :text
|
283
|
+
_ :lastn, :edit_as => :text
|
284
|
+
|
285
|
+
_! 'Entry Customization'
|
286
|
+
_ :entry_class, :edit_as => :text
|
287
|
+
_ :index_class, :edit_as => :text
|
288
|
+
_ :central_prefix, :edit_as => :text
|
289
|
+
_ :central_ext, :edit_as => :text
|
290
|
+
|
291
|
+
_! 'Paths'
|
292
|
+
_ :entry_path, :edit_as => :text
|
293
|
+
_ :lib_path, :edit_as => :text
|
294
|
+
_ :skel_path, :edit_as => :text
|
295
|
+
_ :output_path, :edit_as => :text
|
296
|
+
|
297
|
+
_! 'Participants'
|
298
|
+
_ :authors, :req => true, :edit_as => :map
|
299
|
+
_ :contributors, :edit_as => :map
|
300
|
+
|
301
|
+
_! 'Links'
|
302
|
+
_ :linklist, :edit_as => :omap
|
303
|
+
|
304
|
+
_! 'Sections'
|
305
|
+
_ :sections, :edit_as => :map
|
306
|
+
|
307
|
+
_! 'Libraries and Plugins'
|
308
|
+
_ :requires, :req => true, :edit_as => :omap
|
309
|
+
|
310
|
+
attr_accessor :path
|
311
|
+
attr_reader :hobix_yaml
|
312
|
+
|
313
|
+
# After the weblog is initialize, the +start+ method is called
|
314
|
+
# with the full system path to the directory containing the configuration.
|
315
|
+
#
|
316
|
+
# This method sets up all the paths and loads the plugins.
|
317
|
+
def start( hobix_yaml )
|
318
|
+
@hobix_yaml = hobix_yaml
|
319
|
+
@path = File.dirname( hobix_yaml )
|
320
|
+
@sections ||= {}
|
321
|
+
if File.exists?( lib_path )
|
322
|
+
$LOAD_PATH << lib_path
|
323
|
+
end
|
324
|
+
@plugins = []
|
325
|
+
@requires.each do |req|
|
326
|
+
opts = nil
|
327
|
+
unless req.respond_to? :to_str
|
328
|
+
req, opts = req.to_a.first
|
329
|
+
end
|
330
|
+
plugin_conf = File.join( @path, req.gsub( /\W+/, '.' ) )
|
331
|
+
if File.exists? plugin_conf
|
332
|
+
puts "*** Loading #{ plugin_conf }"
|
333
|
+
plugin_conf = YAML::load_file plugin_conf
|
334
|
+
if opts
|
335
|
+
opts.merge! plugin_conf
|
336
|
+
else
|
337
|
+
opts = plugin_conf
|
338
|
+
end
|
339
|
+
end
|
340
|
+
@plugins += Hobix::BasePlugin::start( req, opts, self )
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
344
|
+
def default_entry_path; "entries"; end
|
345
|
+
def default_skel_path; "skel"; end
|
346
|
+
def default_output_path; "htdocs"; end
|
347
|
+
def default_lib_path; "lib"; end
|
348
|
+
def default_central_prefix; "entry"; end
|
349
|
+
def default_central_ext; "html"; end
|
350
|
+
def default_entry_class; "Hobix::Entry"; end
|
351
|
+
def default_index_class; "Hobix::IndexEntry"; end
|
352
|
+
|
353
|
+
def entry_path; File.expand_path( @entry_path || default_entry_path, @path ).untaint; end
|
354
|
+
def skel_path; File.expand_path( @skel_path || default_skel_path, @path ).untaint; end
|
355
|
+
def output_path; File.expand_path( @output_path || default_output_path, @path ).untaint; end
|
356
|
+
def lib_path; File.expand_path( @lib_path || default_lib_path, @path ).untaint; end
|
357
|
+
def central_prefix; @central_prefix =~ /^[\w\.]+$/ ? @central_prefix.untaint : default_central_prefix; end
|
358
|
+
def central_ext; @central_ext =~ /^\w*$/ ? @central_ext.untaint : default_central_ext; end
|
359
|
+
def entry_class( tag = nil )
|
360
|
+
tag = @entry_class =~ /^[\w:]+$/ ? @entry_class.untaint : default_entry_class unless tag
|
361
|
+
|
362
|
+
found_class = nil
|
363
|
+
if @@entry_classes
|
364
|
+
found_class = @@entry_classes.find do |c|
|
365
|
+
tag == c.name.split( '::' ).last.downcase
|
366
|
+
end
|
367
|
+
end
|
368
|
+
|
369
|
+
begin
|
370
|
+
found_class || Hobix.const_find( tag )
|
371
|
+
rescue NameError => e
|
372
|
+
raise NameError, "No such entry class #{ tag }"
|
373
|
+
end
|
374
|
+
end
|
375
|
+
def index_class( tag = nil )
|
376
|
+
tag = @index_class =~ /^[\w:]+$/ ? @index_class.untaint : default_index_class unless tag
|
377
|
+
begin
|
378
|
+
Hobix.const_find( tag )
|
379
|
+
rescue NameError => e
|
380
|
+
raise NameError, "No such index class #{ tag }"
|
381
|
+
end
|
382
|
+
end
|
383
|
+
|
384
|
+
def link
|
385
|
+
URI::parse( @link.gsub( /\/$/, '' ) ).extend Hobix::UriStr
|
386
|
+
end
|
387
|
+
|
388
|
+
def linklist
|
389
|
+
if @linklist.class == ::Array
|
390
|
+
YAML::transfer( 'hobix.com,2004/linklist', {'links' => @linklist} )
|
391
|
+
else
|
392
|
+
@linklist
|
393
|
+
end
|
394
|
+
end
|
395
|
+
|
396
|
+
# Translate paths relative to the weblahhg's URL. This is especially important
|
397
|
+
# if a weblog isn't at the root directory for a domain.
|
398
|
+
def expand_path( path )
|
399
|
+
File.expand_path( path.gsub( /^\/+/, '' ), self.link.path.gsub( /\/*$/, '/' ) )
|
400
|
+
end
|
401
|
+
|
402
|
+
# Load the weblog information from a YAML file and +start+ the Weblog.
|
403
|
+
def Weblog::load( hobix_yaml )
|
404
|
+
hobix_yaml = File.expand_path( hobix_yaml )
|
405
|
+
weblog = YAML::load( File::open( hobix_yaml ) )
|
406
|
+
weblog.start( hobix_yaml )
|
407
|
+
weblog
|
408
|
+
end
|
409
|
+
|
410
|
+
# Save the weblog configuration to its hobix.yaml (or optionally
|
411
|
+
# provide a path where you would like to save.)
|
412
|
+
def save( file = @hobix_yaml )
|
413
|
+
unless file
|
414
|
+
raise ArgumentError, "Missing argument: path to save configuration (0 of 1)"
|
415
|
+
end
|
416
|
+
File::open( file, 'w' ) do |f|
|
417
|
+
YAML::dump( self, f )
|
418
|
+
end
|
419
|
+
self
|
420
|
+
end
|
421
|
+
|
422
|
+
# Used by +regenerate+ to construct the vars hash by calling
|
423
|
+
# the appropriate skel method for each page.
|
424
|
+
def build_pages( page_name )
|
425
|
+
vars = {}
|
426
|
+
paths = page_name.split( '/' )
|
427
|
+
loop do
|
428
|
+
try_page = paths.join( '_' ).gsub('-','_')
|
429
|
+
if respond_to? "skel_#{ try_page }"
|
430
|
+
path_storage = storage.path_storage( File.dirname( page_name ) )
|
431
|
+
method( "skel_#{ try_page }" ).call( path_storage ) do |vars|
|
432
|
+
vars[:weblog] = self
|
433
|
+
raise TypeError, "No `page' variable returned from skel_#{ try_page }." unless vars[:page]
|
434
|
+
yield vars
|
435
|
+
end
|
436
|
+
return
|
437
|
+
end
|
438
|
+
break unless paths.slice!( -2 ) ## go up a directory
|
439
|
+
end
|
440
|
+
vars[:weblog] = self
|
441
|
+
vars[:page] = Page.new( page_name )
|
442
|
+
vars[:page].timestamp = Time.now
|
443
|
+
yield vars
|
444
|
+
end
|
445
|
+
|
446
|
+
# Sets up a weblog. Should only be run once (which Hobix
|
447
|
+
# performs automatically upon blog creation).
|
448
|
+
def setup
|
449
|
+
@plugins.each do |p|
|
450
|
+
if p.respond_to? :setup
|
451
|
+
p.setup
|
452
|
+
end
|
453
|
+
end
|
454
|
+
end
|
455
|
+
|
456
|
+
# Returns the storage plugin currently in use. (There
|
457
|
+
# can be only one per weblog.)
|
458
|
+
def storage
|
459
|
+
@plugins.detect { |p| p.is_a? BaseStorage }
|
460
|
+
end
|
461
|
+
|
462
|
+
# Returns an Array of all output plugins in use. (There can
|
463
|
+
# be many.)
|
464
|
+
def outputs
|
465
|
+
@plugins.find_all { |p| p.is_a? BaseOutput }
|
466
|
+
end
|
467
|
+
|
468
|
+
# Returns an Array of all publisher plugins in use. (There can
|
469
|
+
# be many.)
|
470
|
+
def publishers
|
471
|
+
@plugins.find_all { |p| p.is_a? BasePublish }
|
472
|
+
end
|
473
|
+
|
474
|
+
# Returns an Array of all facet plugins in use. (There can
|
475
|
+
# be many.)
|
476
|
+
def facets
|
477
|
+
@plugins.find_all { |p| p.is_a? BaseFacet }
|
478
|
+
end
|
479
|
+
|
480
|
+
def facet_for( app )
|
481
|
+
facets.each { |p| return if p.get app }
|
482
|
+
Hobix::BaseFacet.not_found app
|
483
|
+
end
|
484
|
+
|
485
|
+
# Clears the hash used to cache the results of +output_map+.
|
486
|
+
def reset_output_map; @output_map = nil; end
|
487
|
+
|
488
|
+
# Reads +skel_path+ for templates and builds a hash of all the various output
|
489
|
+
# files which will be generated. This method will cache the output_map once.
|
490
|
+
# Subsequent calls to +output_map+ will quickly return the cached hash. To reset
|
491
|
+
# the cache, use +reset_output_map+.
|
492
|
+
def output_map
|
493
|
+
@output_map ||= nil
|
494
|
+
return @output_map if @output_map
|
495
|
+
path_watch = {}
|
496
|
+
@output_entry_map = {}
|
497
|
+
Find::find( skel_path ) do |path|
|
498
|
+
path.untaint
|
499
|
+
if File.basename(path)[0] == ?.
|
500
|
+
Find.prune
|
501
|
+
elsif not FileTest.directory? path
|
502
|
+
tpl_path = path.gsub( /^#{ Regexp::quote( skel_path ) }\/?/, '' )
|
503
|
+
output = outputs.detect { |p| if tpl_path =~ /\.#{ p.extension }$/; tpl_path = $`; end }
|
504
|
+
if output
|
505
|
+
## Figure out template extension and output filename
|
506
|
+
page_name, tpl_ext = tpl_path.dup, ''
|
507
|
+
while page_name =~ /\.\w+$/; page_name = $`; tpl_ext = $& + tpl_ext; end
|
508
|
+
next if tpl_ext.empty?
|
509
|
+
## Build the output pages
|
510
|
+
build_pages( page_name ) do |vars|
|
511
|
+
## Extension and Path
|
512
|
+
vars[:page].add_path( File.dirname( tpl_path ), tpl_ext )
|
513
|
+
vars[:template] = path
|
514
|
+
vars[:output] = output
|
515
|
+
eid = ( vars[:entry] && vars[:entry].id ) || page_name
|
516
|
+
if not @output_entry_map[ eid ]
|
517
|
+
@output_entry_map[ eid ] = vars
|
518
|
+
elsif tpl_ext.split( '.' )[1] == central_ext
|
519
|
+
@output_entry_map[ eid ] = vars
|
520
|
+
end
|
521
|
+
|
522
|
+
## If output by a deeper page, skip
|
523
|
+
pub_name, = path_watch[vars[:page].link]
|
524
|
+
next if pub_name and !( vars[:page].link.index( page_name ) == 0 and
|
525
|
+
page_name.length > pub_name.length )
|
526
|
+
|
527
|
+
path_watch[vars[:page].link] = [page_name, vars]
|
528
|
+
end
|
529
|
+
end
|
530
|
+
end
|
531
|
+
end
|
532
|
+
@output_map = {}
|
533
|
+
path_watch.each_value do |page_name, vars|
|
534
|
+
@output_map[page_name] ||= []
|
535
|
+
@output_map[page_name] << vars
|
536
|
+
end
|
537
|
+
@output_map
|
538
|
+
end
|
539
|
+
|
540
|
+
# Built from the map of output destinations described by +output_map+, this map pairs
|
541
|
+
# entry IDs against their canonical destinations. The @central_prefix and @central_ext
|
542
|
+
# variables determine what output is canonical.
|
543
|
+
def output_entry_map
|
544
|
+
output_map
|
545
|
+
@output_entry_map
|
546
|
+
end
|
547
|
+
|
548
|
+
# Regenerates the weblog, processing templates in +skel_path+
|
549
|
+
# with the data found in +entry_path+, storing output in
|
550
|
+
# +output_path+.
|
551
|
+
#
|
552
|
+
# The _how_ parameter dictates how this is done,
|
553
|
+
# Currently, if _how_ is nil the weblog is completely regen'd.
|
554
|
+
# If it is :update, the weblog is only upgen'd.
|
555
|
+
#
|
556
|
+
# == How Updates Work
|
557
|
+
#
|
558
|
+
# It's very important to know how updates work, especially if
|
559
|
+
# you are writing custom skel methods or devious new kinds of
|
560
|
+
# templates. When performing an update, this method will skip
|
561
|
+
# pages if the following conditions are met:
|
562
|
+
#
|
563
|
+
# 1. The Page object for a given output page must have its
|
564
|
+
# +updated+ timestamp set.
|
565
|
+
# 2. The output file pointed to by the Page object must
|
566
|
+
# already exist.
|
567
|
+
# 3. The +updated+ timestamp must be older than than the
|
568
|
+
# modification time of the output file.
|
569
|
+
# 4. The modification time of the input template must be older
|
570
|
+
# than than the modification time of the output file.
|
571
|
+
#
|
572
|
+
# To ensure that your custom methods and templates are qualifying
|
573
|
+
# to be skipped on an upgen, be sure to set the +updated+ timestamp
|
574
|
+
# of the Page object to the latest date of the content's modification.
|
575
|
+
#
|
576
|
+
def regenerate( how = nil )
|
577
|
+
retouch nil, how
|
578
|
+
end
|
579
|
+
def retouch( only_path = nil, how = nil )
|
580
|
+
published = {}
|
581
|
+
published_types = []
|
582
|
+
output_map.each do |page_name, outputs|
|
583
|
+
puts "[Building #{ page_name } pages]"
|
584
|
+
outputs.each do |vars|
|
585
|
+
full_out_path = File.join( output_path, vars[:page].link.split( '/' ) )
|
586
|
+
## If retouching, skip pages outside of path
|
587
|
+
next if only_path and vars[:page].link.index( "/" + only_path ) != 0
|
588
|
+
|
589
|
+
## If updating, skip any that are unchanged
|
590
|
+
next if how == :update and
|
591
|
+
File.exists?( full_out_path ) and
|
592
|
+
File.mtime( vars[:template] ) < File.mtime( full_out_path ) and
|
593
|
+
( vars[:page].updated.nil? or
|
594
|
+
vars[:page].updated < File.mtime( full_out_path ) )
|
595
|
+
|
596
|
+
p_publish vars[:page]
|
597
|
+
vars.keys.each do |var_name|
|
598
|
+
case var_name.to_s
|
599
|
+
when /entry$/
|
600
|
+
unless vars[:no_load]
|
601
|
+
vars[var_name] = load_and_validate_entry( vars[var_name].id )
|
602
|
+
end
|
603
|
+
when /entries$/
|
604
|
+
unless vars[:no_load]
|
605
|
+
vars[var_name].collect! do |e|
|
606
|
+
load_and_validate_entry( e.id )
|
607
|
+
end
|
608
|
+
end
|
609
|
+
vars[var_name].extend Hobix::EntryEnum
|
610
|
+
end
|
611
|
+
end
|
612
|
+
|
613
|
+
## Publish the page
|
614
|
+
vars = vars.dup
|
615
|
+
output = vars.delete( :output )
|
616
|
+
template = vars.delete( :template )
|
617
|
+
txt = output.load( template, vars )
|
618
|
+
## A plugin perhaps needs to change the output page name
|
619
|
+
full_out_path = File.join( output_path, vars[:page].link.split( '/' ) )
|
620
|
+
File.makedirs( File.dirname( full_out_path ) )
|
621
|
+
File.open( full_out_path, 'w' ) do |f|
|
622
|
+
f << txt
|
623
|
+
f.chmod 0664 rescue nil
|
624
|
+
end
|
625
|
+
published[vars[:page].link] = vars[:page]
|
626
|
+
published_types << page_name
|
627
|
+
end
|
628
|
+
end
|
629
|
+
published_types.uniq!
|
630
|
+
publishers.each do |p|
|
631
|
+
if p.respond_to? :watch
|
632
|
+
if p.watch & published_types != []
|
633
|
+
p.publish( published )
|
634
|
+
end
|
635
|
+
else
|
636
|
+
p.publish( published )
|
637
|
+
end
|
638
|
+
end
|
639
|
+
reset_output_map
|
640
|
+
end
|
641
|
+
|
642
|
+
# Handler for templates with `index' prefix. These templates will
|
643
|
+
# receive entries loaded by +Hobix::BaseStorage#lastn+. Only one
|
644
|
+
# index page is requested by this handler.
|
645
|
+
def skel_index( path_storage )
|
646
|
+
index_entries = path_storage.lastn( @lastn )
|
647
|
+
page = Page.new( 'index' )
|
648
|
+
page.prev = index_entries.last.created.strftime( "%Y/%m/index" )
|
649
|
+
page.timestamp = index_entries.first.created
|
650
|
+
page.updated = path_storage.last_modified( index_entries )
|
651
|
+
yield :page => page, :entries => index_entries
|
652
|
+
end
|
653
|
+
|
654
|
+
# Handler for templates with `daily' prefix. These templates will
|
655
|
+
# receive a list of entries for each day that has at least one entry
|
656
|
+
# created in its time period. This handler requests daily pages
|
657
|
+
# to be output as `/%Y/%m/%d.ext'.
|
658
|
+
def skel_daily( path_storage )
|
659
|
+
entry_range = path_storage.find
|
660
|
+
first_time, last_time = entry_range.last.created, entry_range.first.created
|
661
|
+
start = Time.mktime( first_time.year, first_time.month, first_time.day, 0, 0, 0 ) + 1
|
662
|
+
stop = Time.mktime( last_time.year, last_time.month, last_time.day, 23, 59, 59 )
|
663
|
+
days = []
|
664
|
+
one_day = 24 * 60 * 60
|
665
|
+
until start > stop
|
666
|
+
day_entries = path_storage.within( start, start + one_day - 1 )
|
667
|
+
days << [day_entries.last.created, day_entries] unless day_entries.empty?
|
668
|
+
start += one_day
|
669
|
+
end
|
670
|
+
days.extend Hobix::Enumerable
|
671
|
+
days.each_with_neighbors do |prev, curr, nextd|
|
672
|
+
page = Page.new( curr[0].strftime( "%Y/%m/%d" ) )
|
673
|
+
page.prev = prev[0].strftime( "%Y/%m/%d" ) if prev
|
674
|
+
page.next = nextd[0].strftime( "%Y/%m/%d" ) if nextd
|
675
|
+
page.timestamp = curr[0]
|
676
|
+
page.updated = path_storage.last_modified( curr[1] )
|
677
|
+
yield :page => page, :entries => curr[1]
|
678
|
+
end
|
679
|
+
end
|
680
|
+
|
681
|
+
# Handler for templates with `monthly' prefix. These templates will
|
682
|
+
# receive a list of entries for each month that has at least one entry
|
683
|
+
# created in its time period. This handler requests monthly pages
|
684
|
+
# to be output as `/%Y/%m/index.ext'.
|
685
|
+
def skel_monthly( path_storage )
|
686
|
+
months = path_storage.get_months( path_storage.find )
|
687
|
+
months.extend Hobix::Enumerable
|
688
|
+
months.each_with_neighbors do |prev, curr, nextm|
|
689
|
+
entries = path_storage.within( curr[0], curr[1] )
|
690
|
+
page = Page.new( curr[0].strftime( "%Y/%m/index" ) )
|
691
|
+
page.prev = prev[0].strftime( "%Y/%m/index" ) if prev
|
692
|
+
page.next = nextm[0].strftime( "%Y/%m/index" ) if nextm
|
693
|
+
page.timestamp = curr[1]
|
694
|
+
page.updated = path_storage.last_modified( entries )
|
695
|
+
yield :page => page, :entries => entries
|
696
|
+
end
|
697
|
+
end
|
698
|
+
|
699
|
+
# Handler for templates with `yearly' prefix. These templates will
|
700
|
+
# receive a list of entries for each month that has at least one entry
|
701
|
+
# created in its time period. This handler requests yearly pages
|
702
|
+
# to be output as `/%Y/index.ext'.
|
703
|
+
def skel_yearly( path_storage )
|
704
|
+
entry_range = path_storage.find
|
705
|
+
first_time, last_time = entry_range.last.created, entry_range.first.created
|
706
|
+
years = (first_time.year..last_time.year).collect do |y|
|
707
|
+
[ Time.mktime( y, 1, 1 ), Time.mktime( y + 1, 1, 1 ) - 1 ]
|
708
|
+
end
|
709
|
+
years.extend Hobix::Enumerable
|
710
|
+
years.each_with_neighbors do |prev, curr, nextm|
|
711
|
+
entries = path_storage.within( curr[0], curr[1] )
|
712
|
+
page = Page.new( curr[0].strftime( "%Y/index" ) )
|
713
|
+
page.prev = prev[0].strftime( "%Y/index" ) if prev
|
714
|
+
page.next = nextm[0].strftime( "%Y/index" ) if nextm
|
715
|
+
page.timestamp = curr[1]
|
716
|
+
page.updated = path_storage.last_modified( entries )
|
717
|
+
yield :page => page, :entries => entries
|
718
|
+
end
|
719
|
+
end
|
720
|
+
|
721
|
+
# Handler for templates with `entry' prefix. These templates will
|
722
|
+
# receive one entry for each entry in the weblog. The handler requests
|
723
|
+
# entry pages to be output as `/shortName.ext'.
|
724
|
+
def skel_entry( path_storage )
|
725
|
+
all_entries = [path_storage.find]
|
726
|
+
all_entries += sections_ignored.collect { |ign| path_storage.find( :all => true, :inpath => ign ) }
|
727
|
+
all_entries.each do |entry_set|
|
728
|
+
entry_set.extend Hobix::Enumerable
|
729
|
+
entry_set.each_with_neighbors do |nexte, entry, prev|
|
730
|
+
page = Page.new( entry.class.url_link( entry ) )
|
731
|
+
page.prev = prev.id if prev
|
732
|
+
page.next = nexte.id if nexte
|
733
|
+
page.timestamp = entry.created
|
734
|
+
page.updated = path_storage.modified( entry.id )
|
735
|
+
yield :page => page, :entry => entry
|
736
|
+
end
|
737
|
+
end
|
738
|
+
end
|
739
|
+
|
740
|
+
# Handler for templates with `section' prefix. These templates
|
741
|
+
# will receive all entries below a given directory. The handler
|
742
|
+
# requests will be output as `/section/index.ext'.
|
743
|
+
def skel_section( path_storage )
|
744
|
+
section_map = {}
|
745
|
+
path_storage.all.each do |entry|
|
746
|
+
dirs = entry.id.split( '/' )
|
747
|
+
while ( dirs.pop; dirs.first )
|
748
|
+
section = dirs.join( '/' )
|
749
|
+
section_map[ section ] ||= []
|
750
|
+
section_map[ section ] << entry
|
751
|
+
end
|
752
|
+
end
|
753
|
+
section_map.each do |section, entries|
|
754
|
+
page = Page.new( "/#{ section }/index" )
|
755
|
+
page.updated = path_storage.last_modified( entries )
|
756
|
+
yield :page => page, :entries => entries
|
757
|
+
end
|
758
|
+
end
|
759
|
+
|
760
|
+
# Receive a Hash pairing all section ids with the options for that section.
|
761
|
+
def sections( opts = nil )
|
762
|
+
sections = Marshal::load( Marshal::dump( @sections ) )
|
763
|
+
observes = !sections.values.detect { |s| s['observe'] }
|
764
|
+
storage.sections.each do |s|
|
765
|
+
sections[s] ||= {}
|
766
|
+
sections[s]['observe'] ||= sections[s].has_key?( 'ignore' ) ? !sections[s]['ignore'] : observes
|
767
|
+
sections[s]['ignore'] ||= !sections[s]['observe']
|
768
|
+
end
|
769
|
+
sections
|
770
|
+
end
|
771
|
+
|
772
|
+
# Returns a hash of special sorting cases. Key is the entry path,
|
773
|
+
# value is the sorting method. Storage plugins must honor these
|
774
|
+
# default sorts.
|
775
|
+
def sections_sorts
|
776
|
+
@sections.inject( {} ) do |sorts, set|
|
777
|
+
k, v = set
|
778
|
+
sorts[k] = v['sort_by'] if v['sort_by']
|
779
|
+
sorts
|
780
|
+
end
|
781
|
+
end
|
782
|
+
|
783
|
+
# Returns an Array of entry paths ignored by general querying.
|
784
|
+
# Storage plugins must withhold these entries from queries, unless
|
785
|
+
# the :all => true setting is passed to the query.
|
786
|
+
def sections_ignored
|
787
|
+
sections.collect do |k, v|
|
788
|
+
k if v['ignore']
|
789
|
+
end.compact
|
790
|
+
end
|
791
|
+
|
792
|
+
# Handler for templates with `tags' prefix. These templates
|
793
|
+
# will receive a tag with all entries tagged with it. The handler
|
794
|
+
# requests will be output as `/tags/<tag>/index.ext'.
|
795
|
+
def skel_tags( path_storage )
|
796
|
+
# Get a list of all known tags
|
797
|
+
tags = path_storage.find( :all => true ).map { |e| e.tags }.flatten.uniq
|
798
|
+
|
799
|
+
tags.each do |tag|
|
800
|
+
entries = path_storage.find.find_all { |e| e.tags.member? tag }
|
801
|
+
page = Page.new( File::join('tags',tag,'index' ) )
|
802
|
+
page.updated = path_storage.last_modified( entries )
|
803
|
+
yield :page => page, :entries => entries
|
804
|
+
end
|
805
|
+
end
|
806
|
+
|
807
|
+
|
808
|
+
class AuthorNotFound < Exception; end
|
809
|
+
|
810
|
+
# Loads an entry from +storage+, first validating that the author
|
811
|
+
# is listed in the weblog config.
|
812
|
+
def load_and_validate_entry( entry_id )
|
813
|
+
entry = storage.load_entry( entry_id )
|
814
|
+
unless authors.has_key?( entry.author )
|
815
|
+
raise AuthorNotFound, "Invalid author '#{ entry.author }' found in entry #{ entry_id }"
|
816
|
+
end
|
817
|
+
entry
|
818
|
+
end
|
819
|
+
|
820
|
+
def authorize( user, pass )
|
821
|
+
require 'digest/sha1'
|
822
|
+
authors[user]['password'] == Digest::SHA1.new( pass )
|
823
|
+
end
|
824
|
+
|
825
|
+
# For convenience, storage queries can be made through the Weblog
|
826
|
+
# class. Queries will return the full Entry data, though, so it's
|
827
|
+
# best to use this only when you're scripting and need data quick.
|
828
|
+
def method_missing( methId, *args )
|
829
|
+
if storage.respond_to? methId
|
830
|
+
storage.method( methId ).call( *args ).collect do |e|
|
831
|
+
load_and_validate_entry( e.id )
|
832
|
+
end
|
833
|
+
end
|
834
|
+
end
|
835
|
+
|
836
|
+
# Prints publication information the screen. Override this if
|
837
|
+
# you want to suppress output or change the display.
|
838
|
+
def p_publish( obj )
|
839
|
+
puts "## Page: #{ obj.link }, updated #{ obj.updated }"
|
840
|
+
end
|
841
|
+
|
842
|
+
## YAML Display
|
843
|
+
|
844
|
+
# Returns the YAML type information, which expands to
|
845
|
+
# tag:hobix.com,2004:weblog.
|
846
|
+
def to_yaml_type
|
847
|
+
"!hobix.com,2004/weblog"
|
848
|
+
end
|
849
|
+
|
850
|
+
end
|
851
|
+
end
|
852
|
+
|
853
|
+
YAML::add_domain_type( 'hobix.com,2004', 'weblog' ) do |type, val|
|
854
|
+
YAML::object_maker( Hobix::Weblog, val )
|
855
|
+
end
|
856
|
+
|
857
|
+
YAML::add_domain_type( 'hobix.com,2004', 'bixwik' ) do |type, val|
|
858
|
+
require 'hobix/bixwik'
|
859
|
+
YAML::object_maker( Hobix::BixWik, val )
|
860
|
+
end
|