hobix 0.6

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