hobix 0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/bin/hobix +90 -0
  3. data/lib/hobix/api.rb +91 -0
  4. data/lib/hobix/article.rb +22 -0
  5. data/lib/hobix/base.rb +477 -0
  6. data/lib/hobix/bixwik.rb +200 -0
  7. data/lib/hobix/commandline.rb +661 -0
  8. data/lib/hobix/comments.rb +99 -0
  9. data/lib/hobix/config.rb +39 -0
  10. data/lib/hobix/datamarsh.rb +110 -0
  11. data/lib/hobix/entry.rb +83 -0
  12. data/lib/hobix/facets/comments.rb +74 -0
  13. data/lib/hobix/facets/publisher.rb +314 -0
  14. data/lib/hobix/facets/trackbacks.rb +80 -0
  15. data/lib/hobix/linklist.rb +76 -0
  16. data/lib/hobix/out/atom.rb +92 -0
  17. data/lib/hobix/out/erb.rb +64 -0
  18. data/lib/hobix/out/okaynews.rb +55 -0
  19. data/lib/hobix/out/quick.rb +312 -0
  20. data/lib/hobix/out/rdf.rb +97 -0
  21. data/lib/hobix/out/redrum.rb +26 -0
  22. data/lib/hobix/out/rss.rb +115 -0
  23. data/lib/hobix/plugin/bloglines.rb +73 -0
  24. data/lib/hobix/plugin/calendar.rb +220 -0
  25. data/lib/hobix/plugin/flickr.rb +110 -0
  26. data/lib/hobix/plugin/recent_comments.rb +82 -0
  27. data/lib/hobix/plugin/sections.rb +91 -0
  28. data/lib/hobix/plugin/tags.rb +60 -0
  29. data/lib/hobix/publish/ping.rb +53 -0
  30. data/lib/hobix/publish/replicate.rb +283 -0
  31. data/lib/hobix/publisher.rb +18 -0
  32. data/lib/hobix/search/dictionary.rb +141 -0
  33. data/lib/hobix/search/porter_stemmer.rb +203 -0
  34. data/lib/hobix/search/simple.rb +209 -0
  35. data/lib/hobix/search/vector.rb +100 -0
  36. data/lib/hobix/storage/filesys.rb +398 -0
  37. data/lib/hobix/trackbacks.rb +94 -0
  38. data/lib/hobix/util/objedit.rb +193 -0
  39. data/lib/hobix/util/patcher.rb +155 -0
  40. data/lib/hobix/webapp/cli.rb +195 -0
  41. data/lib/hobix/webapp/htmlform.rb +107 -0
  42. data/lib/hobix/webapp/message.rb +177 -0
  43. data/lib/hobix/webapp/urigen.rb +141 -0
  44. data/lib/hobix/webapp/webrick-servlet.rb +90 -0
  45. data/lib/hobix/webapp.rb +723 -0
  46. data/lib/hobix/weblog.rb +860 -0
  47. data/lib/hobix.rb +223 -0
  48. metadata +87 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: b0a7b407b2f990570b493a45f6992f4e3ea0adb8d8d57c7655da7fff8187ce8e
4
+ data.tar.gz: 30b0d1831163f5b6623f63cc45bf09f539efa19af2d4f22e3437eb9251fd15c2
5
+ SHA512:
6
+ metadata.gz: 8f9e25975d03aa9b75ab92a74b8ff6a05e28c2b0b025ba51a1bbb0c2607a689f9fd97b702d9abf69b079a6e61e1b87ce0e2f9039950b8619a7e590ab599fd9c9
7
+ data.tar.gz: b9d521d17ce43bbc1618490fef2c0a37aa825bfa22df1922f554e832bdb7f4e56b2b0803a37bb94b827d352fae5b29d93b4907028d77bc65e4bcd9f4f241ecdb
data/bin/hobix ADDED
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/env ruby
2
+ require 'hobix/commandline'
3
+ require 'optparse'
4
+
5
+ cmdline = Class.new
6
+ cmdline.extend Hobix::CommandLine
7
+
8
+ def print_usage( cmdline )
9
+ puts "hobix #{ Hobix::VERSION } on ruby #{ ::RUBY_VERSION } (#{ ::RUBY_RELEASE_DATE }) [#{ ::RUBY_PLATFORM }]"
10
+ puts "Usage: hobix command weblog-name [command-options]"
11
+ puts "Commands are"
12
+ ['app', 'weblog', 'action'].each do |cmd_type|
13
+ cmdline.methods.collect do |m|
14
+ if m =~ /^(\w+)_(#{ cmd_type })$/
15
+ [$1, $&]
16
+ end
17
+ end.compact.sort.each do |cmd, m|
18
+ args = "#{ cmd } #{ cmdline.method( "#{ m }_args" ).call.join( ' ' ) }"
19
+ exp = cmdline.method( "#{ m }_explain" ).call.gsub( /\n/, "\n" + ( " " * 40 ) )
20
+ puts " %-38s%-40s" % [args, exp]
21
+ end
22
+ puts
23
+ end
24
+ exit
25
+ end
26
+
27
+ config = nil
28
+ opts = OptionParser.new
29
+ opts.on( "-c", "--config FILE", String ) { |val| config = val }
30
+
31
+ args = opts.parse( *ARGV )
32
+ print_usage( cmdline ) if args.length < 1 or args[0] == 'help'
33
+
34
+ # unfreeze the arguments
35
+ cmd, *opts = args.collect do |arg|
36
+ arg.dup
37
+ end
38
+ args.clear
39
+
40
+ cmdline.login( config )
41
+
42
+ mname = nil
43
+ if cmd == 'setup_blogs'
44
+ mname = cmd
45
+ elsif cmdline.respond_to? "#{ cmd }_app"
46
+ mname = "#{ cmd }_app"; opts = [YAML::load( DATA )]
47
+ elsif cmdline.respond_to? "#{ cmd }_weblog"
48
+ mname = "#{ cmd }_weblog"
49
+ elsif cmdline.respond_to? "#{ cmd }_action"
50
+ weblog = opts.shift
51
+ unless cmdline.config['weblogs'].respond_to? :has_key?
52
+ puts "*** no weblogs found in your configuration."
53
+ puts "*** use `hobix create' or `hobix add' to setup."
54
+ exit
55
+ end
56
+ unless cmdline.config['weblogs'].has_key? weblog
57
+ puts "*** no weblog `#{ weblog }' found in your configuration."
58
+ puts "*** type `hobix help' and check your spelling."
59
+ exit
60
+ end
61
+ hobix_weblog = URI::parse( cmdline.config['weblogs'][ weblog ] )
62
+ if hobix_weblog.scheme
63
+ mname = "#{ hobix_weblog.scheme }_#{ cmd }_remote"
64
+ unless cmdline.respond_to? mname
65
+ opts.unshift cmd
66
+ mname = hobix_weblog.scheme
67
+ end
68
+ else
69
+ hobix_weblog = Hobix::Weblog.load( cmdline.config['weblogs'][ weblog ] )
70
+ mname = "#{ cmd }_action"
71
+ end
72
+ opts.unshift hobix_weblog
73
+ end
74
+ unless mname
75
+ abort "*** no hobix command `#{ cmd }'. use `hobix' without arguments to get help."
76
+ end
77
+ m = cmdline.method( mname )
78
+ if m.arity == opts.length or (m.arity < 0 and opts.length >= m.arity.abs - 1)
79
+ m.call( *opts )
80
+ else
81
+ arglist = [cmd] + cmdline.method( "#{ mname }_args" ).call
82
+ need = m.arity
83
+ need = need.abs - 1 if need < 0
84
+ puts "*** wrong arguments (#{opts.length} given, #{need} needed)"
85
+ puts "*** use syntax: `hobix #{ arglist.join( ' ' ) }'"
86
+ end
87
+
88
+ __END__
89
+ # configuration
90
+ --- {}
data/lib/hobix/api.rb ADDED
@@ -0,0 +1,91 @@
1
+ #
2
+ # = hobix/api.rb
3
+ #
4
+ # Hobix API, used by any external service (DRb or REST, etc.)
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
+ module Hobix
17
+
18
+ # The API facet
19
+ class API < BaseFacet
20
+
21
+ def initialize( weblog, defaults = {} )
22
+ @weblog = weblog
23
+ end
24
+ def get app
25
+ if app.respond_to? :action_uri
26
+ return true unless protect app, @weblog
27
+ @app = app
28
+ prefix, action, *args = app.action_uri.split( '/' )
29
+ if prefix == "remote"
30
+ if respond_to? "#{ action }_action"
31
+ begin
32
+ @app.puts method( "#{ action }_action" ).call( *args ).to_yaml
33
+ return true
34
+ rescue StandardError => e
35
+ @app.puts e.to_yaml
36
+ end
37
+ return true
38
+ end
39
+ end
40
+ end
41
+ end
42
+
43
+ def upgen_action
44
+ @weblog.regenerate( :update )
45
+ "Regeneration complete"
46
+ end
47
+
48
+ def regen_action
49
+ @weblog.regenerate
50
+ "Regeneration complete"
51
+ end
52
+
53
+ def new_action
54
+ @weblog.entry_class.new
55
+ end
56
+
57
+ def list_action( *inpath )
58
+ inpath = inpath.join '/'
59
+ @weblog.storage.find( :all => true, :inpath => inpath )
60
+ end
61
+
62
+ def search_action( words, *inpath )
63
+ inpath = inpath.join '/'
64
+ @weblog.storage.find( :all => true, :inpath => inpath, :search => words.split( ',' ) )
65
+ end
66
+
67
+ def post_action( *id )
68
+ id = id.join '/'
69
+ case @app.request_method
70
+ when "GET"
71
+ @weblog.storage.load_entry id
72
+ when "POST"
73
+ entry = YAML::load( @app.request_body )
74
+ @weblog.storage.save_entry id, entry
75
+ "Entry successfully saved."
76
+ end
77
+ end
78
+
79
+ def edit_action
80
+ case @app.request_method
81
+ when "GET"
82
+ @weblog
83
+ when "POST"
84
+ config = YAML::load( @app.request_body )
85
+ config.save @weblog.hobix_yaml
86
+ "Weblog configuration saved."
87
+ end
88
+ end
89
+ end
90
+
91
+ end
@@ -0,0 +1,22 @@
1
+ #
2
+ # = hobix/entry.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 'redcloth'
17
+ require 'yaml'
18
+
19
+ module Hobix
20
+ class Article
21
+ end
22
+ end
data/lib/hobix/base.rb ADDED
@@ -0,0 +1,477 @@
1
+ #
2
+ # = hobix/weblog.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 'redcloth'
17
+ require 'yaml'
18
+
19
+ module YAML
20
+ class Omap
21
+ def keys; map { |k, v| k }; end
22
+ end
23
+ end
24
+
25
+ module Hobix
26
+ # The BasePlugin class is *bingo* the underlying class for
27
+ # all Hobix plugins. The +Class::inherited+ hook is used
28
+ # by this class to keep track of all classes that inherit
29
+ # from it.
30
+ class BasePlugin
31
+ @@plugins = {}
32
+ @@required_from = nil
33
+ # Initializes all the plugins, returning
34
+ # an Array of plugin objects. (Used by the
35
+ # +Hobix::Weblog+ class.)
36
+ def BasePlugin.start( req, opts, weblog )
37
+ @@required_from = req = req.dup
38
+ if req.tainted?
39
+ req.untaint if req =~ /^[\w\/\\]+$/
40
+ end
41
+ require( req )
42
+ @@required_from = nil
43
+
44
+ if @@plugins[req]
45
+ @@plugins[req].collect do |p|
46
+ if opts
47
+ p.new( weblog, opts )
48
+ else
49
+ p.new( weblog )
50
+ end
51
+ end
52
+ else
53
+ []
54
+ end
55
+ end
56
+ def BasePlugin.inherited( sub )
57
+ @@plugins[@@required_from] ||= []
58
+ @@plugins[@@required_from] << sub
59
+ end
60
+ end
61
+
62
+ # The BaseStorage class outlines the fundamental API for
63
+ # all storage plugins. Storage plugins are responsible
64
+ # for abstracting away entry queries and managing the loading
65
+ # of Entry objects. The goal being: cache as much as you can,
66
+ # be efficient and tidy.
67
+ #
68
+ # == Query Methods
69
+ #
70
+ # find:: Each of the query methods below uses the +find+ method
71
+ # to perform its search. This method accepts a Hash of
72
+ # parameters. Please note that calling +find+ without
73
+ # parameters will return all entries which qualify for
74
+ # placement on the front page.
75
+ #
76
+ # all:: Returns all entries. Searches find( :all => true )
77
+ # lastn:: Returns the last _n_ entries which qualify for the
78
+ # front page.
79
+ # inpath:: Returns entries within a path which qualify for the
80
+ # front page.
81
+ # after:: Returns entries created after a given date.
82
+ # before:: Returns entries created before a given date.
83
+ # within:: Returns entries created between a start and
84
+ # end date.
85
+ #
86
+ class BaseStorage < BasePlugin
87
+ def initialize( weblog )
88
+ @link = weblog.link
89
+ end
90
+ def default_entry_id; "hobix-default-entry"; end
91
+ def default_entry( author )
92
+ Hobix::Entry.new do |e|
93
+ e.id = default_entry_id
94
+ e.link = e.class.url_link e, @link, "html"
95
+ e.created = Time.now
96
+ e.modified = Time.now
97
+ e.title = "This Ghostly Message From the Slime Will Soon Vanish!"
98
+ e.tagline = "A temporary message, a tingling sensation, Hobix is up!!"
99
+ e.author = author
100
+ e.content = Hobix::Entry.text_processor.new( "Welcome to Hobix! Once you make your first blog post, this entry will disappear. However, in the meantime, you can tweak the CSS of your blog until it suits your satisfaction and you have this bit of words to act as a place holder." )
101
+ end
102
+ end
103
+ def all
104
+ find( :all => true )
105
+ end
106
+ def lastn( n )
107
+ find( :lastn => ( n || 10 ) )
108
+ end
109
+ def inpath( path, n = nil )
110
+ find( :inpath => path, :lastn => n )
111
+ end
112
+ def after( after, n = nil )
113
+ find( :after => after, :lastn => n )
114
+ end
115
+ def before( before, n = nil )
116
+ find( :before => before, :lastn => n )
117
+ end
118
+ def match( expr )
119
+ find( :match => expr )
120
+ end
121
+ def within( after, before )
122
+ find( :after => after, :before => before )
123
+ end
124
+ end
125
+
126
+ # The BaseOutput plugin is the underlying class for all output
127
+ # plugins. These plugins are associated to templates. Based on
128
+ # a template's suffix, the proper output plugin is loaded and
129
+ # used to generate page output.
130
+ class BaseOutput < BasePlugin
131
+ end
132
+
133
+ # The BasePublish plugin is the underlying class for all publishing
134
+ # plugins, which are notified of updates to pages.
135
+ #
136
+ # Publish plugins are executed after generation of the site. The plugin
137
+ # may choose to watch updates to certain types of pages. The plugin also
138
+ # receives a list of all the pages which have been updated.
139
+ #
140
+ # Generally, publish plugins fall into two categories:
141
+ #
142
+ # * Plugins which contact a service when certain updates happen.
143
+ # (Hobix includes an XML-RPC ping, which is triggered whenever
144
+ # the front page is updated.)
145
+ # * Plugins which transform Hobix output. (Hobix includes a
146
+ # replication plugin, which copies updated pages to a remote
147
+ # system via FTP or SFTP.)
148
+ #
149
+ # == Publish methods
150
+ #
151
+ # initialize( weblog, settings ):: Like all other plugins, the initialize method takes two parameters,
152
+ # a Hobix::Weblog object for the weblog being published and the
153
+ # settings data from the plugin's entry in hobix.yaml.
154
+ # watch:: (Optional) Returns an array of page types which, when published, activate the plugin.
155
+ # publish( pages ):: If pages are published and the watch criteria qualifies this plugin,
156
+ # this method is called with a hash of pages published. The key is the page type
157
+ # and the value is an array of Page objects.
158
+ class BasePublish < BasePlugin
159
+ end
160
+
161
+ # The BaseFacet plugin is the superclass for all plugins which have
162
+ # an interface (CGI, UI, etc.) These interfaces expose some functionality
163
+ # to the user through an entry form or series of views.
164
+ class BaseFacet < BasePlugin
165
+ def self.not_found app
166
+ app.send_not_found "Action `#{ app.action_uri }' not found. If this address should work, check your plugins."
167
+ end
168
+ def protect app, weblog
169
+ auth = ENV['HTTP_AUTHORIZATION'] || ENV['X-HTTP_AUTHORIZATION']
170
+ if auth
171
+ realm = 'Hobix login'
172
+ auth_type, auth = auth.split ' ', 2
173
+ authorized = false
174
+ case auth_type.downcase
175
+ when 'basic'
176
+ require 'base64'
177
+ name, pass = Base64::decode64( auth.strip ).split ':', 2
178
+ authorized = weblog.authorize name, pass
179
+ when 'digest'
180
+ require 'md5'
181
+ opts = {}
182
+ auth.gsub( /(\w+)="(.*?)"/ ) { opts[$1] = $2 }
183
+ app.puts opts.inspect
184
+ end
185
+ return true if authorized
186
+ end
187
+
188
+ app.send_unauthorized
189
+ # nonce = ["#{ Time.now.to_f }:#{ app.action_uri }"].pack("m").gsub /\s/, ''
190
+ # app.set_header 'WWW-Authenticate', %{Digest qop="auth", realm="#{ realm }", nonce="#{ nonce }", algorithm="MD5"}
191
+ app.set_header 'WWW-Authenticate', %{Basic realm="#{ realm }"}
192
+ false
193
+ end
194
+ end
195
+
196
+ # Enumerable::each_with_neighbors from Joel Vanderwerf's
197
+ # enum extenstions.
198
+ module Enumerable
199
+ def each_with_neighbors n = 1, empty = nil
200
+ nbrs = [empty] * (2 * n + 1)
201
+ offset = n
202
+
203
+ each { |x|
204
+ nbrs.shift
205
+ nbrs.push x
206
+ if offset == 0 # offset is now the offset of the first element, x0,
207
+ yield nbrs # of the sequence from the center of nbrs, or 0,
208
+ else # if x0 has already passed the center.
209
+ offset -= 1
210
+ end
211
+ }
212
+
213
+ n.times {
214
+ nbrs.shift
215
+ nbrs.push empty
216
+ if offset == 0
217
+ yield nbrs
218
+ else
219
+ offset -= 1
220
+ end
221
+ }
222
+
223
+ self
224
+ end
225
+ end
226
+
227
+ module BaseProperties
228
+ # Returns the complete list of properties for the immediate class.
229
+ # If called on an inheriting class, inherited properties are included.
230
+ module ClassMethods
231
+ def properties
232
+ if superclass.respond_to? :properties
233
+ s = superclass.properties.dup
234
+ (@__props || {}).each { |k, v| s[k] = v }
235
+ s
236
+ else
237
+ (@__props || {})
238
+ end
239
+ end
240
+ def prop_sections
241
+ if superclass.respond_to? :prop_sections
242
+ s = superclass.prop_sections.dup
243
+ (@__sects || {}).each { |k, v| s[k] = v }
244
+ s
245
+ else
246
+ (@__sects || {})
247
+ end
248
+ end
249
+ # Quick property definitions in class definitions.
250
+ def _ name, opts = nil
251
+ @__props ||= YAML::Omap[]
252
+ @__props[name] = opts
253
+ attr_accessor name unless method_defined? "#{ name }="
254
+ end
255
+ # Property sections
256
+ def _! name, opts = {}
257
+ @__sects ||= YAML::Omap[]
258
+ opts[:__sect] = @__props.last[0] rescue nil
259
+ @__sects[name] = opts
260
+ end
261
+ end
262
+ # Build a simple map of properties
263
+ def property_map
264
+ self.class.properties.map do |name, opts|
265
+ if opts
266
+ yreq = opts[:req] ? :req : :opt
267
+ ["@#{ name }", yreq] if yreq
268
+ end
269
+ end.compact
270
+ end
271
+ # Build a property map for the YAML module
272
+ def to_yaml_properties
273
+ property_map.find_all do |prop, req|
274
+ case req
275
+ when :opt
276
+ not instance_variable_get( prop ).nil?
277
+ when :req
278
+ true
279
+ end
280
+ end.
281
+ collect do |prop, req|
282
+ prop
283
+ end
284
+ end
285
+ def self.append_features klass
286
+ super
287
+ klass.extend ClassMethods
288
+ end
289
+ end
290
+
291
+ # placed here to avoid dependency cycle between base.rb and weblog.rb
292
+ class Weblog
293
+ @@entry_classes = []
294
+ def self.add_entry_class( c )
295
+ @@entry_classes << c
296
+ end
297
+ end
298
+
299
+ class BaseContent
300
+ include BaseProperties
301
+
302
+ _! 'Entry Information'
303
+ _ :id
304
+ _ :link
305
+ _ :title, :edit_as => :text, :search => :fulltext
306
+ _ :created, :edit_as => :datetime, :search => :prefix
307
+ _ :modified
308
+ _ :tags, :edit_as => :text, :search => :prefix
309
+
310
+ def initialize; yield self if block_given?; end
311
+ def day_id; created.strftime( "%Y/%m/%d" ) if created; end
312
+ def month_id; created.strftime( "%Y/%m" ) if created; end
313
+ def year_id; created.strftime( "%Y" ) if created; end
314
+ def section_id; File.dirname( id ) if id; end
315
+ def base_id; File.basename( id ) if id; end
316
+ def self.url_link( e, url = nil, ext = nil ); "#{ url }/#{ link_format e }#{ '.' + ext if ext }"; end
317
+ def self.link_format( e ); e.id; end
318
+ def force_tags; []; end
319
+
320
+ #
321
+ # If set to true, tags won't be deduced from the entry id
322
+ #
323
+ @@no_implicit_tags = false
324
+
325
+ def self.no_implicit_tags
326
+ @@no_implicit_tags = true
327
+ end
328
+
329
+ #
330
+ # When using implicit tag, the blog root (i.e) is not considered
331
+ # unless you set the value of +@@root_tag+ to what you need.
332
+ #
333
+ @@root_tag = nil
334
+ def self.root_tag=( tag )
335
+ @@root_tag = tag
336
+ end
337
+
338
+ #
339
+ # When computing so-called implicit 'implicit-tag', whether
340
+ # or not we should split the path into several tags
341
+ # (default: false)
342
+ #
343
+ @@split_implicit_tags = false
344
+
345
+ def self.split_implicit_tags
346
+ @@split_implicit_tags = true
347
+ end
348
+
349
+ #
350
+ # return an array of tags deduced from the path
351
+ # i.e. a path like ruby/hobix/foo.yml will lead
352
+ # to [ ruby, hobix ] tags
353
+ # Occurence of . (alone) will be either removed or replaced
354
+ # by the value of +root_tag+
355
+ #
356
+ def path_to_tags( path )
357
+ return [] if @@no_implicit_tags
358
+ return [] if path.nil?
359
+ if @@split_implicit_tags
360
+ tags_array = path.split("/").find_all { |e| e.size > 0 }
361
+ tags_array.pop # Last item is the entry title
362
+ else
363
+ tags_array = [ File.dirname( path )]
364
+ end
365
+ tags_array.map { |e| e == '.' ? @@root_tag : e }.compact
366
+ end
367
+
368
+ #
369
+ # return canonical tags, i.e. tags that are forced and that are deduced
370
+ # from the entry path
371
+ #
372
+ def canonical_tags( path=nil )
373
+ ( force_tags + path_to_tags( path || self.id ) ).uniq
374
+ end
375
+
376
+ def tags;( canonical_tags + Array( @tags ) ).uniq; end
377
+
378
+ def self.yaml_type( tag )
379
+ # if self.respond_to? :yaml_as
380
+ # yaml_as tag
381
+ # else
382
+ if tag =~ /^tag:([^:]+):(.+)$/
383
+ define_method( :to_yaml_type ) { "!#$1/#$2" }
384
+ YAML::add_domain_type( $1, $2 ) { |t, v| self.maker( v ) }
385
+ end
386
+ # end
387
+ end
388
+
389
+ alias to_yaml_orig to_yaml
390
+ def to_yaml( opts = {} )
391
+ opts[:UseFold] = true if opts.respond_to? :[]
392
+ self.class.text_processor_fields.each do |f|
393
+ v = instance_variable_get( '@' + f )
394
+ if v.is_a? self.class.text_processor
395
+ instance_eval %{
396
+ def @#{ f }.to_yaml( opts = {} )
397
+ s = self.to_str
398
+ def s.to_yaml_style; :fold; end
399
+ s.to_yaml( opts )
400
+ end
401
+ }
402
+ end
403
+ end
404
+ to_yaml_orig( opts )
405
+ end
406
+
407
+ # Load the weblog entry from a file.
408
+ def self.load( file )
409
+ File.open( file ) { |f| YAML::load( f ) }
410
+ end
411
+
412
+ # Accessor which returns the text processor used for untyped
413
+ # strings in Entry fields. (defaults to +RedCloth+.)
414
+ def self.text_processor; RedCloth; end
415
+ # Returns an Array of fields to which the text processor applies.
416
+ def self.text_processor_fields
417
+ self.properties.map do |name, opts|
418
+ name.to_s if opts and opts[:text_processor]
419
+ end.compact
420
+ end
421
+ # Factory method for generating Entry classes from a hash. Used
422
+ # by the YAML loader.
423
+ def self.maker( val )
424
+ self::text_processor_fields.each do |f|
425
+ if val[f].respond_to? :value
426
+ str = val[f].value
427
+ def str.to_html
428
+ self
429
+ end
430
+ val[f] = str
431
+ elsif val[f].respond_to? :to_str
432
+ val[f] = self::text_processor.new( val[f].to_str )
433
+ end
434
+ end
435
+ YAML::object_maker( self, val )
436
+ end
437
+ end
438
+
439
+ # The BaseEntry class is the underlying class for all Hobix
440
+ # entries (i.e. the content for your website/blahhg.)
441
+ class BaseEntry < BaseContent
442
+
443
+ _ :id
444
+ _ :link
445
+ _ :title, :edit_as => :text, :search => :fulltext
446
+ _ :author, :req => true, :edit_as => :text, :search => :prefix
447
+ _ :contributors, :edit_as => :array, :search => :prefix
448
+ _ :created, :edit_as => :datetime, :search => :prefix
449
+ _ :modified
450
+ _ :tags, :edit_as => :text, :search => :prefix
451
+ _ :content, :edit_as => :textarea, :search => :fulltext, :text_processor => true
452
+ _ :content_ratings, :edit_as => :array
453
+
454
+ def content_ratings; @content_ratings || [:ham]; end
455
+
456
+ def self.inherited( sub )
457
+ Weblog::add_entry_class( sub )
458
+ end
459
+
460
+ # Build the searchable text
461
+ def to_search
462
+ self.class.properties.map do |name, opts|
463
+ next unless opts
464
+ val = instance_variable_get( "@#{ name }" )
465
+ next unless val
466
+ val = val.strftime "%Y-%m-%dT%H:%M:%S" if val.respond_to? :strftime
467
+ case opts[:search]
468
+ when :prefix
469
+ "#{ name }:" + val.to_s
470
+ when :fulltext
471
+ val.to_s
472
+ end
473
+ end.compact.join "\n"
474
+ end
475
+
476
+ end
477
+ end