hobix 0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/bin/hobix +90 -0
  3. data/lib/hobix/api.rb +91 -0
  4. data/lib/hobix/article.rb +22 -0
  5. data/lib/hobix/base.rb +477 -0
  6. data/lib/hobix/bixwik.rb +200 -0
  7. data/lib/hobix/commandline.rb +661 -0
  8. data/lib/hobix/comments.rb +99 -0
  9. data/lib/hobix/config.rb +39 -0
  10. data/lib/hobix/datamarsh.rb +110 -0
  11. data/lib/hobix/entry.rb +83 -0
  12. data/lib/hobix/facets/comments.rb +74 -0
  13. data/lib/hobix/facets/publisher.rb +314 -0
  14. data/lib/hobix/facets/trackbacks.rb +80 -0
  15. data/lib/hobix/linklist.rb +76 -0
  16. data/lib/hobix/out/atom.rb +92 -0
  17. data/lib/hobix/out/erb.rb +64 -0
  18. data/lib/hobix/out/okaynews.rb +55 -0
  19. data/lib/hobix/out/quick.rb +312 -0
  20. data/lib/hobix/out/rdf.rb +97 -0
  21. data/lib/hobix/out/redrum.rb +26 -0
  22. data/lib/hobix/out/rss.rb +115 -0
  23. data/lib/hobix/plugin/bloglines.rb +73 -0
  24. data/lib/hobix/plugin/calendar.rb +220 -0
  25. data/lib/hobix/plugin/flickr.rb +110 -0
  26. data/lib/hobix/plugin/recent_comments.rb +82 -0
  27. data/lib/hobix/plugin/sections.rb +91 -0
  28. data/lib/hobix/plugin/tags.rb +60 -0
  29. data/lib/hobix/publish/ping.rb +53 -0
  30. data/lib/hobix/publish/replicate.rb +283 -0
  31. data/lib/hobix/publisher.rb +18 -0
  32. data/lib/hobix/search/dictionary.rb +141 -0
  33. data/lib/hobix/search/porter_stemmer.rb +203 -0
  34. data/lib/hobix/search/simple.rb +209 -0
  35. data/lib/hobix/search/vector.rb +100 -0
  36. data/lib/hobix/storage/filesys.rb +398 -0
  37. data/lib/hobix/trackbacks.rb +94 -0
  38. data/lib/hobix/util/objedit.rb +193 -0
  39. data/lib/hobix/util/patcher.rb +155 -0
  40. data/lib/hobix/webapp/cli.rb +195 -0
  41. data/lib/hobix/webapp/htmlform.rb +107 -0
  42. data/lib/hobix/webapp/message.rb +177 -0
  43. data/lib/hobix/webapp/urigen.rb +141 -0
  44. data/lib/hobix/webapp/webrick-servlet.rb +90 -0
  45. data/lib/hobix/webapp.rb +723 -0
  46. data/lib/hobix/weblog.rb +860 -0
  47. data/lib/hobix.rb +223 -0
  48. metadata +87 -0
@@ -0,0 +1,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