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,408 @@
1
+ #
2
+ # = hobix/storage/filesys.rb
3
+ #
4
+ # Hobix command-line weblog system.
5
+ #
6
+ # Copyright (c) 2003-2004 why the lucky stiff
7
+ #
8
+ # Written & maintained by why the lucky stiff <why@ruby-lang.org>
9
+ #
10
+ # This program is free software, released under a BSD license.
11
+ # See COPYING for details.
12
+ #
13
+ #--
14
+ # $Id$
15
+ #++
16
+ require 'find'
17
+ require 'yaml'
18
+ require 'fileutils'
19
+ # require 'hobix/search/simple'
20
+
21
+ module Hobix
22
+
23
+ #
24
+ # The IndexEntry class
25
+ #
26
+ class IndexEntry < BaseContent
27
+ def initialize( entry, fields = self.class.properties.keys )
28
+ fields.each do |field|
29
+ val = if entry.respond_to? field
30
+ entry.send( field )
31
+ elsif respond_to? "make_#{field}"
32
+ send( "make_#{field}", entry )
33
+ else
34
+ :unset
35
+ end
36
+ send( "#{field}=", val )
37
+ end
38
+ yield self if block_given?
39
+ end
40
+
41
+ yaml_type "!hobix.com,2004/storage/indexEntry"
42
+ end
43
+
44
+ module Storage
45
+
46
+ #
47
+ # The FileSys class is a storage plugin, it manages the loading and dumping of
48
+ # Hobix entries and attachments. The FileSys class also keeps an index of entry
49
+ # information, to keep the system from loading unneeded entries.
50
+ class FileSys < Hobix::BaseStorage
51
+ # Start the storage plugin for the +weblog+ passed in.
52
+ def initialize( weblog )
53
+ super( weblog )
54
+ @updated = {}
55
+ @basepath = weblog.entry_path
56
+ @default_author = weblog.authors.keys.first
57
+ @weblog = weblog
58
+ end
59
+
60
+ def now; Time.at( Time.now.to_i ); end
61
+
62
+ # The default extension for entries. Defaults to: yaml.
63
+ def extension
64
+ 'yaml'
65
+ end
66
+
67
+ # Determine if +id+ is a valid entry identifier, untaint if so.
68
+ def check_id( id )
69
+ id.untaint if id.tainted? and id =~ /^[\w\/\\]+$/
70
+ end
71
+
72
+ # Build an entry's complete path based on its +id+. Optionally, extension +ext+ can
73
+ # be used to find the path of attachments.
74
+ def entry_path( id, ext = extension )
75
+ File.join( @basepath, id.split( '/' ) ) + "." + ext
76
+ end
77
+
78
+ # Brings an entry's updated time current.
79
+ def touch_entry( id )
80
+ check_id( id )
81
+ @updated[id] = Time.now
82
+ FileUtils.touch entry_path( id )
83
+ end
84
+
85
+ # Save the entry object +e+ and identify it as +id+. The +create_category+ flag
86
+ # will forcefully make the needed directories.
87
+ def save_entry( id, e, create_category=false )
88
+ load_index
89
+ check_id( id )
90
+ e.created ||= (@index.has_key?( id ) ? @index[id].created : now)
91
+ path = entry_path( id )
92
+
93
+ unless create_category and File.exists? @basepath
94
+ FileUtils.makedirs File.dirname( path )
95
+ end
96
+
97
+ File.open( path, 'w' ) { |f| YAML::dump( e, f ) }
98
+
99
+ @entry_cache ||= {}
100
+ e.id = id
101
+ e.link = e.class.url_link e, @link, @weblog.central_ext
102
+ e.updated = e.modified = now
103
+ @entry_cache[id] = e
104
+
105
+ @index[id] = @weblog.index_class.new( e ) do |i|
106
+ i.updated = e.updated
107
+ end
108
+ @updated[id] = e.updated
109
+ # catalog_search_entry( e )
110
+ sort_index( true )
111
+ e
112
+ end
113
+
114
+ # Loads the entry object identified by +id+. Entries are cached for future loading.
115
+ def load_entry( id )
116
+ return default_entry( @default_author ) if id == default_entry_id
117
+ load_index
118
+ check_id( id )
119
+ @entry_cache ||= {}
120
+ unless @entry_cache.has_key? id
121
+ entry_file = entry_path( id )
122
+ e = Hobix::Entry::load( entry_file )
123
+ e.id = id
124
+ e.link = e.class.url_link e, @link, @weblog.central_ext
125
+ e.updated = updated( id )
126
+ unless e.created
127
+ e.created = @index[id].created
128
+ e.modified = @index[id].modified
129
+ File.open( entry_file, 'w' ) { |f| YAML::dump( e, f ) }
130
+ end
131
+ @entry_cache[id] = e
132
+ else
133
+ @entry_cache[id]
134
+ end
135
+ end
136
+
137
+ # Loads the search engine database. The database will be cleansed and re-scanned if +wash+ is true.
138
+ # def load_search_index( wash )
139
+ # @search_index = Hobix::Search::Simple::Searcher.load( File.join( @basepath, 'index.search' ), wash )
140
+ # end
141
+
142
+ # Catalogs an entry object +e+ in the search engine.
143
+ # def catalog_search_entry( e )
144
+ # @search_index.catalog( Hobix::Search::Simple::Content.new( e.to_search, e.id, e.modified, e.content_ratings ) )
145
+ # end
146
+
147
+ # Determines if the search engine has already scanned an entry represented by IndexEntry +ie+.
148
+ # def search_needs_update? ie
149
+ # not @search_index.has_entry? ie.id, ie.modified
150
+ # end
151
+
152
+ # Load the internal index (saved at @entry_path/index.hobix) and refresh any timestamps
153
+ # which may be stale.
154
+ def load_index
155
+ return false if @index
156
+ index_path = File.join( @basepath, 'index.hobix' )
157
+ index = if File.exists? index_path
158
+ YAML::load( File.open( index_path ) )
159
+ else
160
+ YAML::Omap::new
161
+ end
162
+ @index = YAML::Omap::new
163
+ # load_search_index( index.length == 0 )
164
+
165
+ modified = false
166
+ index_fields = @weblog.index_class.properties.keys
167
+ Find::find( @basepath ) do |path|
168
+ path.untaint
169
+ if FileTest.directory? path
170
+ Find.prune if File.basename(path)[0] == ?.
171
+ else
172
+ entry_path = path.gsub( /^#{ Regexp::quote( @basepath ) }\/?/, '' )
173
+ next if entry_path !~ /\.#{ Regexp::quote( extension ) }$/
174
+ entry_paths = File.split( $` )
175
+ entry_paths.shift if entry_paths.first == '.'
176
+ entry_id = entry_paths.join( '/' )
177
+ @updated[entry_id] = File.mtime( path )
178
+
179
+ index_entry = nil
180
+ if ( index.has_key? entry_id ) and !( index[entry_id].is_a? ::Time ) # pre-0.4 index format
181
+ index_entry = index[entry_id]
182
+ end
183
+ ## we will (re)load the entry if:
184
+ if not index_entry.respond_to?( :updated ) or # it's new
185
+ ( index_entry.updated != @updated[entry_id] ) # it's changed
186
+ # or index_fields.detect { |f| index_entry.send( f ).nil? } # index fields have been added
187
+ # or search_needs_update? index_entry # entry is old or not available in search db
188
+
189
+ puts "++ Reloaded #{ entry_id }"
190
+ efile = entry_path( entry_id )
191
+ e = Hobix::Entry::load( efile )
192
+ e.id = entry_id
193
+ index_entry = @weblog.index_class.new( e, index_fields ) do |i|
194
+ i.updated = @updated[entry_id]
195
+ end
196
+ # catalog_search_entry( e )
197
+ modified = true
198
+ end
199
+ index_entry.id = entry_id
200
+ @index[entry_id] = index_entry
201
+ end
202
+ end
203
+ sort_index( modified )
204
+ true
205
+ end
206
+
207
+ # Sorts the internal entry index (used by load_index.)
208
+ def sort_index( modified )
209
+ return unless @index
210
+ index_path = File.join( @basepath, 'index.hobix' )
211
+ @index.sort! { |x,y| y[1].created <=> x[1].created }
212
+ if modified
213
+ File.open( index_path, 'w' ) do |f|
214
+ YAML::dump( @index, f )
215
+ end
216
+ # @search_index.dump
217
+ end
218
+ end
219
+
220
+ # Returns a Hobix::Storage::FileSys object with its scope limited
221
+ # to entries inside a certain path +p+.
222
+ def path_storage( p )
223
+ return self if ['', '.'].include? p
224
+ load_index
225
+ path_storage = self.dup
226
+ path_storage.instance_eval do
227
+ @index = @index.dup.delete_if do |id, entry|
228
+ if id.index( p ) != 0
229
+ @updated.delete( p )
230
+ true
231
+ end
232
+ end
233
+ end
234
+ path_storage
235
+ end
236
+
237
+ # Returns an Array all `sections', or directories which contain entries.
238
+ # If you have three entries: `news/article1', `about/me', and `news/misc/article2',
239
+ # then you have three sections: `news', `about', `news/misc'.
240
+ def sections( opts = nil )
241
+ load_index
242
+ hsh = {}
243
+ @index.collect { |id, e| e.section_id }.uniq.sort
244
+ end
245
+
246
+ # Find entries based on criteria from the +search+ hash.
247
+ # Possible criteria include:
248
+ #
249
+ # :after:: Select entries created after a given Time.
250
+ # :before:: Select entries created before a given Time.
251
+ # :inpath:: Select entries contained within a path.
252
+ # :match:: Select entries with an +id+ which match a Regexp.
253
+ # :search:: Fulltext search of entries for search words.
254
+ # :lastn:: Limit the search to include only a given number of entries.
255
+ #
256
+ # This method returns an Array of +IndexEntry+ objects for use in
257
+ # skel_* methods.
258
+ def find( search = {} )
259
+ load_index
260
+ _index = @index
261
+ if _index.empty?
262
+ e = default_entry( @default_author )
263
+ @updated[e.id] = e.updated
264
+ _index = {e.id => @weblog.index_class.new(e)}
265
+ end
266
+ # if search[:search]
267
+ # sr = @search_index.find_words( search[:search] )
268
+ # end
269
+ unless search[:all]
270
+ ignore_test = nil
271
+ ignored = @weblog.sections_ignored
272
+ unless ignored.empty?
273
+ ignore_test = /^(#{ ignored.collect { |i| Regexp.quote( i ) }.join( '|' ) })/
274
+ end
275
+ end
276
+ entries = _index.collect do |id, entry|
277
+ skip = false
278
+ if ignore_test and not search[:all]
279
+ skip = entry.id =~ ignore_test
280
+ end
281
+ search.each do |skey, sval|
282
+ break if skip
283
+ skip = case skey
284
+ when :after
285
+ entry.created < sval
286
+ when :before
287
+ entry.created > sval
288
+ when :inpath
289
+ entry.id.index( sval ) != 0
290
+ when :match
291
+ not entry.id.match sval
292
+ # when :search
293
+ # not sr.results[entry.id]
294
+ else
295
+ false
296
+ end
297
+ end
298
+ if skip then nil else entry end
299
+ end.compact
300
+ entries.slice!( search[:lastn]..-1 ) if search[:lastn] and entries.length > search[:lastn]
301
+ entries
302
+ end
303
+
304
+ # Returns a Time object for the latest updated time for a group of
305
+ # +entries+ (pass in an Array of IndexEntry objects).
306
+ def last_updated( entries )
307
+ entries.collect do |entry|
308
+ updated( entry.id )
309
+ end.max
310
+ end
311
+
312
+ # Returns a Time object for the latest modified time for a group of
313
+ # +entries+ (pass in an Array of IndexEntry objects).
314
+ def last_modified( entries )
315
+ entries.collect do |entry|
316
+ entry.modified
317
+ end.max
318
+ end
319
+
320
+ # Returns a Time object for the latest creation time for a group of
321
+ # +entries+ (pass in an Array of IndexEntry objects).
322
+ def last_created( entries )
323
+ entries.collect do |entry|
324
+ entry.created
325
+ end.max
326
+ end
327
+
328
+ # Returns a Time object representing the +updated+ time for the
329
+ # entry identified by +entry_id+. Takes into account attachments
330
+ # which have been updated.
331
+ def updated( entry_id )
332
+ find_attached( entry_id ).inject( @updated[entry_id] ) do |max, ext|
333
+ mtime = File.mtime( entry_path( entry_id, ext ) )
334
+ mtime > max ? mtime : max
335
+ end
336
+ end
337
+
338
+ # Returns an Array of Arrays representing the months which contain
339
+ # +entries+ (pass in an Array of IndexEntry objects).
340
+ #
341
+ # See Hobix::Weblog.skel_month for an example of this method's usage.
342
+ def get_months( entries )
343
+ return [] if entries.empty?
344
+ first_time = entries.collect { |e| e.created }.min
345
+ last_time = entries.collect { |e| e.created }.max
346
+ start = Time.mktime( first_time.year, first_time.month, 1 )
347
+ stop = Time.mktime( last_time.year, last_time.month, last_time.day )
348
+ months = []
349
+ until start > stop
350
+ next_year, next_month = start.year, start.month + 1
351
+ if next_month > 12
352
+ next_year += next_month / 12
353
+ next_month %= 12
354
+ end
355
+ month_end = Time.mktime( next_year, next_month, 1 ) - 1
356
+ months << [ start, month_end, start.strftime( "/%Y/%m/" ) ] unless find( :after => start, :before => month_end).empty?
357
+ start = month_end + 1
358
+ end
359
+ months
360
+ end
361
+
362
+ # Discovers attachments to an entry identified by +id+.
363
+ def find_attached( id )
364
+ check_id( id )
365
+ Dir[ entry_path( id, '*' ) ].collect do |att|
366
+ atp = att.match( /#{ Regexp::quote( id ) }\.(?!#{ extension }$)/ )
367
+ atp.post_match if atp
368
+ end.compact
369
+ end
370
+
371
+ # Loads an attachment to an entry identified by +id+. Entries
372
+ # can have any kind of YAML attachment, each which a specific extension.
373
+ def load_attached( id, ext )
374
+ check_id( id )
375
+ @attach_cache ||= {}
376
+ file_id = "#{ id }.#{ ext }"
377
+ unless @attach_cache.has_key? file_id
378
+ @attach_cache[id] = File.open( entry_path( id, ext ) ) do |f|
379
+ YAML::load( f )
380
+ end
381
+ else
382
+ @attach_cache[id]
383
+ end
384
+ end
385
+
386
+ # Saves an attachment to an entry identified by +id+. The attachment
387
+ # +e+ is saved with an extension +ext+.
388
+ def save_attached( id, ext, e )
389
+ check_id( id )
390
+ File.open( entry_path( id, ext ), 'w' ) do |f|
391
+ YAML::dump( e, f )
392
+ end
393
+
394
+ @attach_cache ||= {}
395
+ @attach_cache[id] = e
396
+ end
397
+
398
+ # Appends the given items to an entry attachment with the given type, and
399
+ # then saves the modified attachment. If an attachment of the given type
400
+ # does not exist, it will be created.
401
+ def append_to_attachment( entry_id, attachment_type, *items )
402
+ attachment = load_attached( entry_id, attachment_type ) rescue []
403
+ attachment += items
404
+ save_attached( entry_id, attachment_type, attachment )
405
+ end
406
+ end
407
+ end
408
+ end
@@ -0,0 +1,93 @@
1
+ #
2
+ # = hobix/trackbacks.rb
3
+ #
4
+ # Hobix command-line weblog system, API for trackbacks.
5
+ #
6
+ # Copyright (c) 2003-2004 why the lucky stiff
7
+ #
8
+ # Written & maintained by why the lucky stiff <why@ruby-lang.org>
9
+ #
10
+ # This program is free software, released under a BSD license.
11
+ # See COPYING for details.
12
+ #
13
+ #--
14
+ # $Id$
15
+ #++
16
+
17
+ require 'hobix/facets/trackbacks'
18
+ require 'time'
19
+ require 'rexml/document'
20
+
21
+ module Hobix
22
+ module Out
23
+ class Quick
24
+ prepend_def :entry_title_erb, %{
25
+ <+ entry_trackback_rdf +>
26
+ }
27
+
28
+ def entry_trackback_rdf_erb; %{
29
+ <!--
30
+ <%= trackback_rdf_for( weblog, entry ) %>
31
+ -->
32
+ } end
33
+
34
+ append_def :entry_erb, %{
35
+ <% if entry and not defined? entries %><+ entry_trackback +><% end %>
36
+ }
37
+
38
+ def entry_trackback_erb; %{
39
+ <div id="trackbacks">
40
+ <% entry_id = entry.id %>
41
+ <% trackbacks = weblog.storage.load_attached( entry_id, "trackbacks") rescue [] %>
42
+ <% trackbacks.each do |trackback| %>
43
+ <div class="entry">
44
+ <div class="entryAttrib">
45
+ <div class="entryAuthor"><h3><%= trackback.blog_name %></h3></div>
46
+ <div class="entryTime">tracked back on <%= trackback.created.strftime("<nobr>%d %b %Y</nobr> at <nobr>%H:%M</nobr>" ) %></div>
47
+ </div>
48
+ <div class="entryContentOuter"><div class="entryContent">
49
+ <h3><a href="<%= trackback.url %>"><%= trackback.title %></a></h3>
50
+ <%= trackback.excerpt %>
51
+ </div></div>
52
+ </div>
53
+ <% end %>
54
+ </div>
55
+ } end
56
+
57
+ private
58
+ def trackback_rdf_for( weblog, entry )
59
+ trackback_link = '%s/control/trackback/%s' % [weblog.link, entry.id]
60
+ doc = REXML::Document.new
61
+ rdf = doc.add_element( "rdf:RDF" )
62
+ rdf.add_namespace( "rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#" )
63
+ rdf.add_namespace( "trackback", "http://madskills.com/public/xml/rss/module/trackback/" )
64
+ rdf.add_namespace( "dc", "http://purl.org/dc/elements/1.1/" )
65
+ desc = rdf.add_element( "rdf:Description" )
66
+ desc.add_attribute( "rdf:about", "")
67
+ desc.add_attribute( "trackback:ping", trackback_link )
68
+ desc.add_attribute( "dc:title", entry.title )
69
+ desc.add_attribute( "dc:identifier", entry.link )
70
+ ## i've dropped the following fields because i don't think they're used, and
71
+ ## dc:description in particular will potentially double the size of the
72
+ ## html pages. if they're actually useful to anyone, please re-add.
73
+ ##
74
+ ## desc.add_attribute( "dc:description", ( entry.summary || entry.content ).to_html )
75
+ ## desc.add_attribute( "dc:creator", entry.author )
76
+ ## desc.add_attribute( "dc:date", entry.created.xmlschema )
77
+ doc.to_s
78
+ end
79
+ end
80
+ end
81
+
82
+ class Trackback < BaseContent
83
+ _! "Trackback Information"
84
+ _ :blog_name, :edit_as => :text, :req => true
85
+ _ :url, :edit_as => :text, :req => true
86
+ _ :title, :edit_as => :text, :req => true
87
+ _ :excerpt , :edit_as => :text, :req => true
88
+ _ :created, :edit_as => :datetime
89
+ _ :ipaddress, :edit_as => :text
90
+
91
+ yaml_type "tag:hobix.com,2005:trackback"
92
+ end
93
+ end
@@ -0,0 +1,193 @@
1
+ #
2
+ # = hobix/util/objedit
3
+ #
4
+ # Hobix command-line weblog system.
5
+ #
6
+ # Copyright (c) 2003-2004 why the lucky stiff
7
+ #
8
+ # Written & maintained by why the lucky stiff <why@ruby-lang.org>
9
+ #
10
+ # This program is free software, released under a BSD license.
11
+ # See COPYING for details.
12
+ #
13
+ #--
14
+ # $Id$
15
+ #++
16
+ require 'ncurses'
17
+ require 'yaml'
18
+
19
+ module Hobix
20
+ module Util
21
+ # The ObjEdit class provides an ncurses-based editor for
22
+ # modifying Ruby objects. The ncurses library must be installed,
23
+ # which is available at http://ncurses-ruby.berlios.de/.
24
+ def self.ObjEdit( obj )
25
+ include Ncurses
26
+ include Ncurses::Form
27
+ # Initialize ncurses
28
+ scr = Ncurses.initscr
29
+ out_obj = nil
30
+ Ncurses.start_color
31
+ Ncurses.cbreak
32
+ Ncurses.keypad scr, true
33
+
34
+ # Initialize few color pairs
35
+ Ncurses.init_pair 1, COLOR_RED, COLOR_BLACK
36
+ Ncurses.init_pair 2, COLOR_WHITE, COLOR_BLACK
37
+ Ncurses.init_pair 3, COLOR_YELLOW, COLOR_BLACK
38
+ Ncurses.init_pair 4, COLOR_RED, COLOR_BLACK
39
+ scr.bkgd Ncurses.COLOR_PAIR(2)
40
+
41
+ # Initialize the fields
42
+ y = 0
43
+ labels = []
44
+ label_end = 12
45
+ ivars = []
46
+ fields =
47
+ obj.property_map.collect do |ivar, flag, edit_as|
48
+ ht, wt = 1, 60
49
+ case edit_as
50
+ when :text
51
+ field = FIELD.new ht, wt, y, 1, 0, 0
52
+ when :textarea
53
+ ht, wt = 5, 60
54
+ field = FIELD.new ht, wt, y, 1, 60, 0
55
+ end
56
+ if y + ht + 8 >= Ncurses.LINES
57
+ field.set_new_page TRUE
58
+ y = 0
59
+ end
60
+ labels << [y + 2, ivar, ht, wt]
61
+ ivars << ivar[1..-1]
62
+ label_end = ivar.length + 3 if label_end < ivar.length + 3
63
+ y += ht + 1
64
+
65
+ field.field_opts_off O_AUTOSKIP
66
+ field.set_field_back A_REVERSE
67
+ field.set_field_fore A_BOLD
68
+ field_write( field, obj.instance_variable_get( ivar ) )
69
+ field
70
+ end
71
+
72
+ # Create the form
73
+ my_form = FORM.new fields
74
+ my_form.user_object = "Editing #{ obj.class }"
75
+ rows, cols = [], []
76
+ my_form.scale_form rows, cols
77
+
78
+ # Create the window
79
+ my_win = WINDOW.new rows[0] + 3, cols[0] + 20, 0, 0
80
+ my_win.bkgd Ncurses.COLOR_PAIR( 3 )
81
+ my_win.keypad TRUE
82
+
83
+ # Attach
84
+ my_form.set_form_win my_win
85
+ my_form.set_form_sub my_win.derwin( rows[0], cols[0], 2, label_end )
86
+ my_form.form_opts_off O_NL_OVERLOAD
87
+ my_form.post_form
88
+ labels.each do |y, ivar, ht, wt|
89
+ my_win.mvaddstr y, 2, ivar
90
+ end
91
+ scr.mvprintw Ncurses.LINES - 2, 28, "Use TAB to switch between fields"
92
+ scr.mvprintw Ncurses.LINES - 1, 28, "F2 to save | F3 to cancel"
93
+ scr.refresh
94
+ my_win.wrefresh
95
+
96
+ # Loop through to get user requests
97
+ pressed = []
98
+ while((ch = my_win.getch()) != KEY_F2)
99
+ pressed << ch
100
+ case ch
101
+ when 16 # Ctrl + P
102
+ my_form.form_driver REQ_PREV_PAGE
103
+ my_form.form_driver REQ_FIRST_FIELD
104
+
105
+ when 14 # Ctrl + N
106
+ my_form.form_driver REQ_NEXT_PAGE
107
+ my_form.form_driver REQ_LAST_FIELD
108
+
109
+ when KEY_C3, ?\t
110
+ # Go to next field
111
+ my_form.form_driver REQ_NEXT_FIELD
112
+ # Go to the end of the present buffer
113
+ # Leaves nicely at the last character
114
+ my_form.form_driver REQ_END_LINE
115
+
116
+ when KEY_C1
117
+ # Go to previous field
118
+ my_form.form_driver REQ_PREV_FIELD
119
+ my_form.form_driver REQ_END_LINE
120
+
121
+ when KEY_UP
122
+ my_form.form_driver REQ_PREV_LINE
123
+
124
+ when KEY_DOWN
125
+ my_form.form_driver REQ_NEXT_LINE
126
+
127
+ when KEY_LEFT
128
+ # Go to previous character
129
+ my_form.form_driver REQ_PREV_CHAR
130
+
131
+ when KEY_RIGHT
132
+ # Go to previous field
133
+ my_form.form_driver REQ_NEXT_CHAR
134
+
135
+ when KEY_BACKSPACE, 010
136
+ my_form.form_driver REQ_DEL_PREV
137
+
138
+ when KEY_ENTER, ?\n, ?\r
139
+ my_form.form_driver REQ_NEW_LINE
140
+
141
+ when KEY_F3
142
+ return nil
143
+
144
+ else
145
+ # If this is a normal character, it gets Printed
146
+ my_form.form_driver ch
147
+ end
148
+ end
149
+ # Un post form and free the memory
150
+ my_form.form_driver REQ_NEXT_FIELD
151
+ my_form.unpost_form
152
+ my_form.free_form
153
+ obj_props = {}
154
+ fields.each do |f|
155
+ b = field_read(f)
156
+ f.free_field()
157
+ if String === b and b.empty?
158
+ b = nil
159
+ end
160
+ obj_props[ivars.shift] = b
161
+ end
162
+ out_obj = YAML::transfer( obj.to_yaml_type[1..-1], obj_props )
163
+ ensure
164
+ Ncurses.endwin
165
+ # p pressed
166
+ # p out_obj
167
+ end
168
+ def self.field_write( f, obj )
169
+ rows, cols, frow, fcol, nrow, nbuf = [], [], [], [], [], []
170
+ f.field_info( rows, cols, frow, fcol, nrow, nbuf )
171
+ if String === obj
172
+ obj = "#{ obj }"
173
+ end
174
+ str = obj.to_yaml( :BestWidth => cols[0] - 4 ).
175
+ sub( /^\-\-\-\s*(\>[0-9\-\+]*\n)?/, '' ).
176
+ gsub( /^([^\n]*)\n/ ) { |line| "%-#{cols}s" % [$1] }
177
+ f.set_field_buffer 0, str
178
+ end
179
+ def self.field_read( f )
180
+ rows, cols, frow, fcol, nrow, nbuf = [], [], [], [], [], []
181
+ f.field_info( rows, cols, frow, fcol, nrow, nbuf )
182
+ val = f.field_buffer(0).scan( /.{#{ cols[0] }}/ )
183
+ YAML::load(
184
+ if val.length > 1
185
+ "--- >\n " +
186
+ val.collect { |line| line.rstrip }.join( "\n " ).rstrip
187
+ else
188
+ "--- #{ val[0] }"
189
+ end
190
+ )
191
+ end
192
+ end
193
+ end