hobix 0.6

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