hobix 0.4
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.
- checksums.yaml +7 -0
- data/bin/hobix +90 -0
- data/lib/hobix/api.rb +91 -0
- data/lib/hobix/article.rb +22 -0
- data/lib/hobix/base.rb +477 -0
- data/lib/hobix/bixwik.rb +200 -0
- data/lib/hobix/commandline.rb +661 -0
- data/lib/hobix/comments.rb +99 -0
- data/lib/hobix/config.rb +39 -0
- data/lib/hobix/datamarsh.rb +110 -0
- data/lib/hobix/entry.rb +83 -0
- data/lib/hobix/facets/comments.rb +74 -0
- data/lib/hobix/facets/publisher.rb +314 -0
- data/lib/hobix/facets/trackbacks.rb +80 -0
- data/lib/hobix/linklist.rb +76 -0
- data/lib/hobix/out/atom.rb +92 -0
- data/lib/hobix/out/erb.rb +64 -0
- data/lib/hobix/out/okaynews.rb +55 -0
- data/lib/hobix/out/quick.rb +312 -0
- data/lib/hobix/out/rdf.rb +97 -0
- data/lib/hobix/out/redrum.rb +26 -0
- data/lib/hobix/out/rss.rb +115 -0
- data/lib/hobix/plugin/bloglines.rb +73 -0
- data/lib/hobix/plugin/calendar.rb +220 -0
- data/lib/hobix/plugin/flickr.rb +110 -0
- data/lib/hobix/plugin/recent_comments.rb +82 -0
- data/lib/hobix/plugin/sections.rb +91 -0
- data/lib/hobix/plugin/tags.rb +60 -0
- data/lib/hobix/publish/ping.rb +53 -0
- data/lib/hobix/publish/replicate.rb +283 -0
- data/lib/hobix/publisher.rb +18 -0
- data/lib/hobix/search/dictionary.rb +141 -0
- data/lib/hobix/search/porter_stemmer.rb +203 -0
- data/lib/hobix/search/simple.rb +209 -0
- data/lib/hobix/search/vector.rb +100 -0
- data/lib/hobix/storage/filesys.rb +398 -0
- data/lib/hobix/trackbacks.rb +94 -0
- data/lib/hobix/util/objedit.rb +193 -0
- data/lib/hobix/util/patcher.rb +155 -0
- data/lib/hobix/webapp/cli.rb +195 -0
- data/lib/hobix/webapp/htmlform.rb +107 -0
- data/lib/hobix/webapp/message.rb +177 -0
- data/lib/hobix/webapp/urigen.rb +141 -0
- data/lib/hobix/webapp/webrick-servlet.rb +90 -0
- data/lib/hobix/webapp.rb +723 -0
- data/lib/hobix/weblog.rb +860 -0
- data/lib/hobix.rb +223 -0
- metadata +87 -0
@@ -0,0 +1,314 @@
|
|
1
|
+
#
|
2
|
+
# = hobix/facets/publisher.rb
|
3
|
+
#
|
4
|
+
# Hobix command-line weblog system, web-based publishing interface.
|
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 'erb'
|
18
|
+
require 'yaml'
|
19
|
+
|
20
|
+
module Hobix
|
21
|
+
module Facets
|
22
|
+
|
23
|
+
# The Publisher plugin adds a web interface for managing Hobix blogs.
|
24
|
+
# Basically, to add the publisher to your site, ensure the plugin
|
25
|
+
# is loaded within your hobix.yaml `requires' list:
|
26
|
+
#
|
27
|
+
# requires:
|
28
|
+
# - hobix/facets/publisher
|
29
|
+
#
|
30
|
+
class Publisher < BaseFacet
|
31
|
+
class MissingRequired < Exception; end
|
32
|
+
|
33
|
+
def initialize( weblog, defaults = {} )
|
34
|
+
@weblog = weblog
|
35
|
+
end
|
36
|
+
def get app
|
37
|
+
if app.respond_to? :action_uri
|
38
|
+
ns, method_id = app.action_uri.split( '/', 2 )
|
39
|
+
return false unless ( ns.nil? or ns == "publisher" )
|
40
|
+
|
41
|
+
case method_id
|
42
|
+
when /\.js$/
|
43
|
+
app.content_type = "text/javascript"
|
44
|
+
app.puts File.read( File.join( Hobix::SHARE_PATH, "publisher", method_id ) )
|
45
|
+
return true
|
46
|
+
when /\.css$/
|
47
|
+
app.content_type = "text/css"
|
48
|
+
app.puts File.read( File.join( Hobix::SHARE_PATH, "publisher", method_id ) )
|
49
|
+
return true
|
50
|
+
when /\.png$/
|
51
|
+
app.content_type = "image/png"
|
52
|
+
app.puts File.read( File.join( Hobix::SHARE_PATH, "publisher", method_id ) )
|
53
|
+
return true
|
54
|
+
end
|
55
|
+
|
56
|
+
# dispatch the url action
|
57
|
+
method_args = (method_id || "config").split( /\// )
|
58
|
+
method_id = "config"
|
59
|
+
method_args.length.downto(1) do |i|
|
60
|
+
if respond_to? "get_#{ method_args[0,i].join( '_' ) }"
|
61
|
+
method_id = "get_#{ method_args.slice!(0,i).join( '_' ) }"
|
62
|
+
break
|
63
|
+
end
|
64
|
+
end
|
65
|
+
method_args.unshift app
|
66
|
+
return false unless respond_to? method_id
|
67
|
+
@screen = method( method_id ).call( *method_args )
|
68
|
+
return true unless @screen
|
69
|
+
|
70
|
+
# Display publisher page
|
71
|
+
erb_src = File.read( File.join( Hobix::SHARE_PATH, "publisher/index.erb" ) )
|
72
|
+
app.content_type = 'text/html'
|
73
|
+
app.puts ::ERB.new( erb_src, nil, nil, "_hobixpublish" ).result( binding )
|
74
|
+
return true
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def make_form( form )
|
79
|
+
form_erb = %q{
|
80
|
+
% current = nil
|
81
|
+
<style type="text/css">
|
82
|
+
<!--
|
83
|
+
ul.edit_as_map, ol.edit_as_omap {
|
84
|
+
list-style-image:none;
|
85
|
+
list-style-type:none;
|
86
|
+
margin-top:5px;
|
87
|
+
margin:0px;
|
88
|
+
padding:0px;
|
89
|
+
margin-left:140px;
|
90
|
+
}
|
91
|
+
ul.edit_as_map li, ol.edit_as_omap li {
|
92
|
+
padding: 3px 0px;
|
93
|
+
margin: 0px;
|
94
|
+
}
|
95
|
+
ol.edit_as_omap li .handle {
|
96
|
+
cursor: move;
|
97
|
+
}
|
98
|
+
-->
|
99
|
+
</style>
|
100
|
+
<script type="text/javascript" language="javascript">
|
101
|
+
// <![CDATA[
|
102
|
+
function sortable_to_csv(element) {
|
103
|
+
var element = $(element);
|
104
|
+
var options = {
|
105
|
+
tag: element.sortable.tag,
|
106
|
+
only: element.sortable.only,
|
107
|
+
name: element.id
|
108
|
+
}.extend(arguments[1] || {});
|
109
|
+
|
110
|
+
var items = $(element).childNodes;
|
111
|
+
var queryComponents = new Array();
|
112
|
+
|
113
|
+
for(var i=0; i<items.length; i++)
|
114
|
+
if(items[i].tagName && items[i].tagName==options.tag.toUpperCase() &&
|
115
|
+
(!options.only || (Element.Class.has(items[i], options.only))))
|
116
|
+
queryComponents.push(items[i].id.replace(element.id+'_',''));
|
117
|
+
|
118
|
+
return queryComponents;
|
119
|
+
}
|
120
|
+
// ]]>
|
121
|
+
</script>
|
122
|
+
<h2><%= form[:full_title] %></h2>
|
123
|
+
<form id="publisher_form" method="post" enctype="multipart/form-data">
|
124
|
+
<p><%= RedCloth.new( form[:intro] ).to_html %></p>
|
125
|
+
% form[:object].class.properties.each do |name, opts|
|
126
|
+
% next unless opts and opts[:edit_as]
|
127
|
+
% if sect = form[:object].class.prop_sections.detect { |k,v| v[:__sect] == current }
|
128
|
+
<fieldset>
|
129
|
+
<legend><%= sect[0] %></legend>
|
130
|
+
% end
|
131
|
+
% title = name.to_s.gsub( '_', ' ' ).capitalize
|
132
|
+
% val = form[:object].instance_variable_get( "@" + name.to_s )
|
133
|
+
% if name == :notes
|
134
|
+
<div class="notes">
|
135
|
+
<h4><%= title %></h4>
|
136
|
+
</div>
|
137
|
+
% else
|
138
|
+
<div class="<%= opts[:req] ? 'required' : 'optional' %>">
|
139
|
+
<label for="<%= name %>"><%= title %>:</label>
|
140
|
+
% case opts[:edit_as]
|
141
|
+
% when :password
|
142
|
+
<input type="password" name="<%= name %>" id="<%= name %>"
|
143
|
+
class="inputPassword" size="10" tabindex=""
|
144
|
+
maxlength="25" value="<%= val %>" />
|
145
|
+
% when :checkbox
|
146
|
+
<input type="checkbox" name="<%= name %>" id="<%= name %>"
|
147
|
+
class="inputCheckbox" tabindex="" value="1" />
|
148
|
+
% when :textarea
|
149
|
+
<textarea name="<%= name %>" id="<%= name %>" rows="<%= opts[:edit_rows] || 4 %>" cols="<%= opts[:edit_cols] || 36 %>" tabindex=""><%= val %></textarea>
|
150
|
+
% when :omap
|
151
|
+
<ol id="<%= name %>sort" class="edit_as_omap">
|
152
|
+
% val.each do |vkey, vval|
|
153
|
+
% vkey = vkey.keys.first if vkey.is_a? Hash
|
154
|
+
<li id="<%= name %>sort_<%= vkey %>" class="sorty" name="<%= vkey %>">
|
155
|
+
<span class="handle">»</span>
|
156
|
+
<%= vkey %>
|
157
|
+
<a href="<%= form[:app].absuri( :path_info => "/publisher/#{ @title }/edit/#{ name }/#{ vkey }" ) %>">edit</a>
|
158
|
+
<a href="<%= form[:app].absuri( :path_info => "/publisher/#{ @title }/del/#{ name }/#{ vkey }" ) %>">remove</a>
|
159
|
+
</li>
|
160
|
+
% end if val
|
161
|
+
<li class="new_item">
|
162
|
+
<span>»</span>
|
163
|
+
<input type="text" name="<%= name %>_new" id="<%= name %>_new" style="width:150px"
|
164
|
+
class="inputText" tabindex="" maxlength="255" value="" />
|
165
|
+
<a href="<%= form[:app].absuri( :path_info => "/publisher/#{ @title }/add/#{ name }" ) %>">add</a>
|
166
|
+
</li>
|
167
|
+
</ul>
|
168
|
+
<input type="hidden" name="<%= name %>" id="<%= name %>" value="" />
|
169
|
+
<script type="text/javascript" language="javascript">
|
170
|
+
// <![CDATA[
|
171
|
+
Sortable.create("<%= name %>sort", {handle:'handle', only: 'sorty', onUpdate:function () {
|
172
|
+
$("<%= name %>").value = sortable_to_csv(this.element).join(' ');
|
173
|
+
}});
|
174
|
+
// ]]>
|
175
|
+
</script>
|
176
|
+
% when :map
|
177
|
+
<ul id="<%= name %>_order" class="edit_as_map">
|
178
|
+
% val.each do |vkey, vval|
|
179
|
+
<li id="<%= name %>_<%= vkey %>">
|
180
|
+
<%= vkey %>
|
181
|
+
<a href="<%= form[:app].absuri( :path_info => "/publisher/#{ @title }/edit/#{ name }/#{ vkey }" ) %>">edit</a>
|
182
|
+
<a href="<%= form[:app].absuri( :path_info => "/publisher/#{ @title }/del/#{ name }/#{ vkey }" ) %>">remove</a>
|
183
|
+
</li>
|
184
|
+
% end if val
|
185
|
+
<li id="new_item">
|
186
|
+
<input type="text" name="<%= name %>_new" id="<%= name %>_new" style="width:150px"
|
187
|
+
class="inputText" tabindex="" maxlength="255" value="" />
|
188
|
+
<a href="<%= form[:app].absuri( :path_info => "/publisher/#{ @title }/add/#{ name }" ) %>">add</a>
|
189
|
+
</li>
|
190
|
+
</ul>
|
191
|
+
% else
|
192
|
+
<input type="text" name="<%= name %>" id="<%= name %>"
|
193
|
+
class="inputText" size="<%= opts[:edit_size] || 24 %>" tabindex=""
|
194
|
+
maxlength="255" value="<%= val %>" />
|
195
|
+
% end
|
196
|
+
|
197
|
+
% if opts.include? :notes
|
198
|
+
<small><%= fopts['notes'] %></small>
|
199
|
+
% end
|
200
|
+
% if form[:object].respond_to? "default_#{ name }"
|
201
|
+
<small><em>Defaults to <strong><%= form[:object].method( "default_#{ name }" ).call %></strong>.</em></small>
|
202
|
+
% end
|
203
|
+
</div>
|
204
|
+
% end
|
205
|
+
% current = name
|
206
|
+
% if form[:object].class.prop_sections.detect { |k,v| v[:__sect] == current }
|
207
|
+
</fieldset>
|
208
|
+
% end
|
209
|
+
% end
|
210
|
+
</fieldset>
|
211
|
+
<fieldset>
|
212
|
+
<div class="submit">
|
213
|
+
<div>
|
214
|
+
|
215
|
+
<input type="submit" class="inputSubmit" tabindex="" value="Submit »" />
|
216
|
+
<input type="submit" class="inputSubmit" tabindex="" value="Cancel" />
|
217
|
+
</div>
|
218
|
+
</div>
|
219
|
+
</fieldset>
|
220
|
+
</form>
|
221
|
+
}
|
222
|
+
return ::ERB.new( form_erb, 0, "%<>", "_hobixpublishFORM" ).result( binding )
|
223
|
+
end
|
224
|
+
|
225
|
+
def save_form( obj, app )
|
226
|
+
obj = obj.dup
|
227
|
+
missing = []
|
228
|
+
obj.class.properties.each do |name, opts|
|
229
|
+
next unless opts
|
230
|
+
next unless app._POST.has_key? name.to_s
|
231
|
+
val = app._POST[name.to_s]
|
232
|
+
val = nil if val and val.empty?
|
233
|
+
missing << name if val.nil? and opts[:req]
|
234
|
+
|
235
|
+
case opts[:edit_as]
|
236
|
+
when :omap
|
237
|
+
omap = obj.instance_variable_get( "@#{name}" )
|
238
|
+
sorted = val.to_s.split(/\s+/)
|
239
|
+
sorted.each { |item| omap << [item] unless omap.assoc(item) }
|
240
|
+
omap.sort_by { |item, val| sorted.index(item) || sorted.length }
|
241
|
+
when :map
|
242
|
+
map = obj.instance_variable_get( "@#{name}" )
|
243
|
+
val.to_s.split(/\s+/).each do |item|
|
244
|
+
map[item] ||= nil
|
245
|
+
end
|
246
|
+
else
|
247
|
+
obj.instance_variable_set( "@#{name}", val )
|
248
|
+
end
|
249
|
+
end
|
250
|
+
[obj, missing]
|
251
|
+
end
|
252
|
+
|
253
|
+
def red( str ); RedCloth.new( str ).to_html; end
|
254
|
+
|
255
|
+
def show_weblog_form( weblog, app )
|
256
|
+
make_form :app => app,
|
257
|
+
:full_title => 'Configure Your Weblahhg',
|
258
|
+
:intro => %q{
|
259
|
+
Generally speaking, you shouldn't have to alter many of your weblog settings.
|
260
|
+
Most of the below are available for those who really want to customize.
|
261
|
+
|
262
|
+
**Bold** fields are required.
|
263
|
+
}.gsub( /^ +/, '' ),
|
264
|
+
:object => weblog
|
265
|
+
end
|
266
|
+
|
267
|
+
def get_config( app )
|
268
|
+
@title = 'config'
|
269
|
+
case app.request_method
|
270
|
+
when "GET"
|
271
|
+
show_weblog_form( @weblog, app )
|
272
|
+
when "POST"
|
273
|
+
weblog, missing = save_form( @weblog, app )
|
274
|
+
# if missing.empty?
|
275
|
+
# weblog.save( weblog.hobix_yaml + ".edit" )
|
276
|
+
# red %{
|
277
|
+
# *Your configuraton has been saved.*
|
278
|
+
#
|
279
|
+
# Please note that this development version of Hobix isn't
|
280
|
+
# yet equipped to deal with re-sorting of the requires. I'm not that great with Prototype
|
281
|
+
# yet and I also want to write some code to sandbox the configuration, to check that the
|
282
|
+
# requires will load right before saving it.
|
283
|
+
# }
|
284
|
+
# else
|
285
|
+
# show_weblog_form( weblog, app )
|
286
|
+
# end
|
287
|
+
[weblog, missing].to_yaml
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
def get_entries( app, *entry_id )
|
292
|
+
@title = 'entries'
|
293
|
+
unless entry_id.empty?
|
294
|
+
e = @weblog.storage.load_entry( entry_id.join( '/' ) )
|
295
|
+
else
|
296
|
+
e = Hobix::Entry.new
|
297
|
+
end
|
298
|
+
case app.request_method
|
299
|
+
when "GET"
|
300
|
+
make_form :app => app,
|
301
|
+
:full_title => 'Post an Entry',
|
302
|
+
:intro => %q{
|
303
|
+
**Bold** fields are required.
|
304
|
+
}.gsub( /^ +/, '' ),
|
305
|
+
:object => e
|
306
|
+
when "POST"
|
307
|
+
e, missing = save_form( e, app )
|
308
|
+
e.inspect
|
309
|
+
end
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
end
|
314
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
#
|
2
|
+
# = hobix/facets/trackbacks.rb
|
3
|
+
#
|
4
|
+
# Hobix command-line weblog system, support 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/entry'
|
18
|
+
|
19
|
+
module Hobix
|
20
|
+
module Facets
|
21
|
+
|
22
|
+
# The Trackbacks plugin adds support for the TrackBack specification
|
23
|
+
# (http://www.sixapart.com/pronet/docs/trackback_spec).
|
24
|
+
#
|
25
|
+
# Add this require to your hobix.yaml:
|
26
|
+
#
|
27
|
+
# requires:
|
28
|
+
# - hobix/trackbacks
|
29
|
+
#
|
30
|
+
class Trackbacks < BaseFacet
|
31
|
+
def self.trackback_fields; ['url','title', 'excerpt', 'blog_name']; end
|
32
|
+
def self.trackback_class; Hobix::Trackback; end
|
33
|
+
|
34
|
+
def initialize( weblog, defaults = {} )
|
35
|
+
@weblog = weblog
|
36
|
+
end
|
37
|
+
def get app
|
38
|
+
if app.respond_to? :action_uri
|
39
|
+
action, entry_id = app.action_uri.split( '/', 2 )
|
40
|
+
case action
|
41
|
+
when "trackback"
|
42
|
+
# Validate
|
43
|
+
on_entry = @weblog.storage.load_entry( entry_id ) rescue nil
|
44
|
+
return send_trackback_response( app, false, 'No such entry' ) if on_entry.nil?
|
45
|
+
|
46
|
+
# Create a trackback comment
|
47
|
+
trackback = Trackbacks.trackback_class.new do |t|
|
48
|
+
Trackbacks.trackback_fields.each do |tf|
|
49
|
+
t.method( "#{tf}=" ).call( app._POST[tf].to_s )
|
50
|
+
end
|
51
|
+
return send_trackback_response( app, false, 'Missing URL field' ) if (t.url || '').empty?
|
52
|
+
t.created = Time.now
|
53
|
+
t.ipaddress = app.remote_addr
|
54
|
+
end
|
55
|
+
|
56
|
+
# Save the trackback, upgen
|
57
|
+
@weblog.storage.append_to_attachment( entry_id, 'trackbacks', trackback )
|
58
|
+
@weblog.regenerate :update
|
59
|
+
|
60
|
+
# Send response
|
61
|
+
send_trackback_response( app, true )
|
62
|
+
return true
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def send_trackback_response(app, ok = true, message = nil)
|
68
|
+
app.content_type = 'text/xml'
|
69
|
+
app.puts %{<?xml version="1.0" encoding="UTF-8"?>
|
70
|
+
<response>
|
71
|
+
<error>%d</error>
|
72
|
+
%s
|
73
|
+
</response>
|
74
|
+
} % [ok ? 0 : 1, message ? %{<message>#{message}</message>} : '']
|
75
|
+
true
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
#
|
2
|
+
# = hobix/linklist.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 'hobix/entry'
|
17
|
+
require 'redcloth'
|
18
|
+
require 'yaml'
|
19
|
+
|
20
|
+
# The LinkList class is an entry type for storing links. It's
|
21
|
+
# also a good example of how to subclass the Entry class so you
|
22
|
+
# can store your own kinds of entries.
|
23
|
+
#
|
24
|
+
# == Properties
|
25
|
+
#
|
26
|
+
# The LinkList responds to many of the same properties listed
|
27
|
+
# in the +Hobix::Entry+ class. The primary difference is that,
|
28
|
+
# instead of having a +content+ property, there is a +links+
|
29
|
+
# property.
|
30
|
+
#
|
31
|
+
# links:: Internally, this class stores a +YAML::Omap+, an
|
32
|
+
# Array of pairs. The links are kept in the order
|
33
|
+
# shown in the YAML file. They consist of a link
|
34
|
+
# title, paired with a URL.
|
35
|
+
#
|
36
|
+
# == Sample LinkList
|
37
|
+
#
|
38
|
+
# --- %YAML:1.0 !hobix.com,2004/linklist
|
39
|
+
# title: Hobix Links
|
40
|
+
# author: why
|
41
|
+
# created: 2004-05-30 18:53:00 -06:00
|
42
|
+
# links:
|
43
|
+
# - Hobix: http://hobix.com/
|
44
|
+
# - Learn Hobix: http://hobix.com/learn/
|
45
|
+
# - Textile Reference: http://hobix.com/textile/
|
46
|
+
#
|
47
|
+
module Hobix
|
48
|
+
class LinkList < BaseEntry
|
49
|
+
|
50
|
+
_ :links, [:req, :textarea]
|
51
|
+
|
52
|
+
# Converts the link list into a RedCloth string for display
|
53
|
+
# in templates.
|
54
|
+
def content
|
55
|
+
RedCloth.new(
|
56
|
+
@links.collect do |title, url|
|
57
|
+
"* \"#{ title }\":#{ url }"
|
58
|
+
end.join( "\n" )
|
59
|
+
)
|
60
|
+
end
|
61
|
+
|
62
|
+
# LinkLists currently output as YAML type family
|
63
|
+
# !hobix.com,2004/linklist.
|
64
|
+
yaml_type "tag:hobix.com,2004:linklist"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
YAML::add_domain_type( 'hobix.com,2004', 'linklist' ) do |type, val|
|
69
|
+
['tagline', 'summary'].each do |f|
|
70
|
+
val[f] = RedCloth.new( val[f].to_s ) if val[f]
|
71
|
+
end
|
72
|
+
if val['links'].class == ::Array
|
73
|
+
val['links'] = YAML::transfer( 'omap', val['links'] )
|
74
|
+
end
|
75
|
+
YAML::object_maker( Hobix::LinkList, val )
|
76
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
#
|
2
|
+
# = hobix/out/atom.rb
|
3
|
+
#
|
4
|
+
# Atom output for Hobix.
|
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 'hobix/base'
|
17
|
+
require 'rexml/document'
|
18
|
+
require 'uri'
|
19
|
+
require 'cgi'
|
20
|
+
|
21
|
+
module Hobix
|
22
|
+
module Out
|
23
|
+
module XmlQuick
|
24
|
+
def x( title, txt, attrs = nil )
|
25
|
+
e = REXML::Element.new title
|
26
|
+
e.text = txt if txt
|
27
|
+
attrs.each { |a,b| e.attributes[a] = b } if attrs
|
28
|
+
self << e
|
29
|
+
end
|
30
|
+
end
|
31
|
+
class Atom < Hobix::BaseOutput
|
32
|
+
def initialize( weblog )
|
33
|
+
@path = weblog.skel_path
|
34
|
+
end
|
35
|
+
def extension
|
36
|
+
"atom"
|
37
|
+
end
|
38
|
+
def load( file_name, vars )
|
39
|
+
rssdoc = REXML::Document.new( <<EOXML )
|
40
|
+
<feed version="0.3"
|
41
|
+
xmlns="http://purl.org/atom/ns#"
|
42
|
+
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
43
|
+
xml:lang="en">
|
44
|
+
<title></title>
|
45
|
+
<link rel="alternate" type="text/html" href="" />
|
46
|
+
<modified></modified>
|
47
|
+
<tagline></tagline>
|
48
|
+
<id></id>
|
49
|
+
<generator url="http://hobix.com/" version="#{ Hobix::VERSION }">Hobix</generator>
|
50
|
+
<copyright></copyright>
|
51
|
+
</feed>
|
52
|
+
EOXML
|
53
|
+
uri = vars[:weblog].link
|
54
|
+
rssdoc << REXML::XMLDecl.new
|
55
|
+
rssdoc.elements['/feed/title'].text = vars[:weblog].title
|
56
|
+
rssdoc.elements['/feed/link'].attributes['href'] = vars[:weblog].link.to_s
|
57
|
+
rssdoc.elements['/feed/tagline'].text = vars[:weblog].tagline
|
58
|
+
rssdoc.elements['/feed/modified'].text = vars[:page].updated.strftime( "%Y-%m-%dT%H:%M:%SZ" )
|
59
|
+
rssdoc.elements['/feed/id'].text = "tag:#{ uri.host },#{ Time.now.year }:blog#{ uri.path }"
|
60
|
+
rssdoc.elements['/feed/copyright'].text = vars[:weblog].copyright || "None"
|
61
|
+
( vars[:entries] || [vars[:entry]] ).each do |e|
|
62
|
+
ele = REXML::Element.new 'entry'
|
63
|
+
ele.extend XmlQuick
|
64
|
+
ele.x( 'title', e.title )
|
65
|
+
ele.x( 'link', nil, {'rel' => 'alternate', 'type' => 'text/html', 'href' => e.link } )
|
66
|
+
ele.x( 'id', "tag:#{ uri.host },#{ Time.now.year }:blog#{ CGI.escape(uri.path) }entry#{ CGI.escape( "/#{ e.id }" ) }" )
|
67
|
+
ele.x( 'issued', e.created.strftime( "%Y-%m-%dT%H:%M:%SZ" ) )
|
68
|
+
ele.x( 'modified', e.modified.strftime( "%Y-%m-%dT%H:%M:%SZ" ) )
|
69
|
+
ele.x( 'dc:subject', e.section_id )
|
70
|
+
e.tags.each do |t|
|
71
|
+
ele.x( 'dc:subject', t )
|
72
|
+
end
|
73
|
+
ele.x( 'summary',
|
74
|
+
e.summary.to_html.gsub( /img src="\//, "img src=\"#{ vars[:weblog].link }/" ),
|
75
|
+
{'type' => 'text/html', 'mode' => 'escaped'} ) if e.respond_to? :summary and e.summary
|
76
|
+
author = vars[:weblog].authors[e.author]
|
77
|
+
ele_auth = REXML::Element.new 'author'
|
78
|
+
ele_auth.extend XmlQuick
|
79
|
+
ele_auth.x( 'name', author['name'] )
|
80
|
+
ele_auth.x( 'url', author['url'] ) if author['url']
|
81
|
+
ele_auth.x( 'email', author['email'] ) if author['email']
|
82
|
+
ele << ele_auth
|
83
|
+
ele.x( 'content',
|
84
|
+
e.content.to_html.gsub( /img src="\//, "img src=\"#{ vars[:weblog].link }/" ),
|
85
|
+
{'type' => 'text/html', 'mode' => 'escaped'} )
|
86
|
+
rssdoc.elements['/feed'].add ele
|
87
|
+
end
|
88
|
+
rssdoc.to_s
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
#
|
2
|
+
# = hobix/out/erb.rb
|
3
|
+
#
|
4
|
+
# Hobix processing of ERB templates.
|
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 'hobix/base'
|
17
|
+
require 'erb'
|
18
|
+
|
19
|
+
module Hobix
|
20
|
+
module Out
|
21
|
+
class ERBError < StandardError; end
|
22
|
+
class ERB < Hobix::BaseOutput
|
23
|
+
def initialize( weblog )
|
24
|
+
@path = weblog.skel_path
|
25
|
+
end
|
26
|
+
def extension
|
27
|
+
"erb"
|
28
|
+
end
|
29
|
+
def load( file_name, vars )
|
30
|
+
@bind = binding
|
31
|
+
vars.each do |k, v|
|
32
|
+
k.untaint
|
33
|
+
k_inspect = k.inspect.untaint
|
34
|
+
eval( "#{ k } = vars[#{ k_inspect }]", @bind )
|
35
|
+
end
|
36
|
+
@relpath = File.dirname( file_name )
|
37
|
+
@load_erb = import_erb( file_name )
|
38
|
+
begin
|
39
|
+
@load_erb.result( @bind )
|
40
|
+
rescue Exception => e
|
41
|
+
raise ERBError, "Error `#{ e.message }' in erb #{ file_name }."
|
42
|
+
end
|
43
|
+
end
|
44
|
+
def expand( fname )
|
45
|
+
if fname =~ /^\//
|
46
|
+
File.join( @path, fname )
|
47
|
+
else
|
48
|
+
File.join( @relpath, fname )
|
49
|
+
end
|
50
|
+
end
|
51
|
+
def import( fname, bindto = @bind )
|
52
|
+
import_erb( expand( fname ) ).result( bindto )
|
53
|
+
end
|
54
|
+
def import_erb(fname)
|
55
|
+
File.open(fname) do |fp|
|
56
|
+
src = fp.read.gsub( /<\+\s*([\w\.\/\\\-]+)\s*\+>/ ) do
|
57
|
+
File.read( expand( $1 ) )
|
58
|
+
end
|
59
|
+
::ERB.new( src, nil, nil, "_hobixout#{ rand( 9999999 ) }" )
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
#
|
2
|
+
# = hobix/out/okaynews.rb
|
3
|
+
#
|
4
|
+
# YAML !okay/news output for Hobix.
|
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 'hobix/base'
|
17
|
+
|
18
|
+
module Hobix
|
19
|
+
class Weblog
|
20
|
+
def to_okaynews( entries )
|
21
|
+
YAML::quick_emit( self.object_id ) do |out|
|
22
|
+
out.map( "!okay/news/^feed" ) do |map|
|
23
|
+
['@title', '@tagline', '@link', '@period',
|
24
|
+
'@created', '@issued', '@modified',
|
25
|
+
'@authors', '@contributors'
|
26
|
+
].each do |m|
|
27
|
+
map.add( m[1..-1], instance_variable_get( m ) )
|
28
|
+
end
|
29
|
+
entries = entries.collect do |e|
|
30
|
+
e = e.dup
|
31
|
+
e.author = @authors[e.author]
|
32
|
+
def e.to_yaml_type
|
33
|
+
"!^entry"
|
34
|
+
end
|
35
|
+
e
|
36
|
+
end
|
37
|
+
map.add( 'entries', entries )
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
module Out
|
43
|
+
class OkayNews < Hobix::BaseOutput
|
44
|
+
def initialize( weblog )
|
45
|
+
@path = weblog.skel_path
|
46
|
+
end
|
47
|
+
def extension
|
48
|
+
"okaynews"
|
49
|
+
end
|
50
|
+
def load( file_name, vars )
|
51
|
+
vars[:weblog].to_okaynews( vars[:entries] || [vars[:entry]] )
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|