hobix 0.4
Sign up to get free protection for your applications and to get access to all the features.
- 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
|