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