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,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