hobix 0.6
Sign up to get free protection for your applications and to get access to all the features.
- data/COPYING +18 -0
- data/README +18 -0
- data/Rakefile +96 -0
- data/bin/hobix +94 -0
- data/contrib/blosxom-to-hobix.rb +253 -0
- data/contrib/txp-to-hobix.rb +56 -0
- data/contrib/webrick-all-mine.rb +20 -0
- data/doc/CHANGELOG +285 -0
- data/doc/rdoc/classes/Hobix/API.html +382 -0
- data/doc/rdoc/classes/Hobix/Article.html +111 -0
- data/doc/rdoc/classes/Hobix/BaseContent.html +692 -0
- data/doc/rdoc/classes/Hobix/BaseEntry.html +218 -0
- data/doc/rdoc/classes/Hobix/BaseFacet.html +205 -0
- data/doc/rdoc/classes/Hobix/BaseOutput.html +122 -0
- data/doc/rdoc/classes/Hobix/BasePlugin.html +201 -0
- data/doc/rdoc/classes/Hobix/BaseProperties/ClassMethods.html +243 -0
- data/doc/rdoc/classes/Hobix/BaseProperties.html +218 -0
- data/doc/rdoc/classes/Hobix/BasePublish.html +157 -0
- data/doc/rdoc/classes/Hobix/BaseStorage.html +417 -0
- data/doc/rdoc/classes/Hobix/BixWik/Entry.html +196 -0
- data/doc/rdoc/classes/Hobix/BixWik/IndexEntry.html +170 -0
- data/doc/rdoc/classes/Hobix/BixWik/WikiRedCloth.html +111 -0
- data/doc/rdoc/classes/Hobix/BixWik.html +418 -0
- data/doc/rdoc/classes/Hobix/BixWikPlugin.html +158 -0
- data/doc/rdoc/classes/Hobix/CommandLine.html +1970 -0
- data/doc/rdoc/classes/Hobix/Comment.html +113 -0
- data/doc/rdoc/classes/Hobix/Config.html +212 -0
- data/doc/rdoc/classes/Hobix/DataMarsh.html +667 -0
- data/doc/rdoc/classes/Hobix/Entry.html +178 -0
- data/doc/rdoc/classes/Hobix/EntryEnum.html +162 -0
- data/doc/rdoc/classes/Hobix/Enumerable.html +170 -0
- data/doc/rdoc/classes/Hobix/Facets/WikiEdit.html +180 -0
- data/doc/rdoc/classes/Hobix/Facets.html +111 -0
- data/doc/rdoc/classes/Hobix/LinkList.html +182 -0
- data/doc/rdoc/classes/Hobix/Out/Quick.html +412 -0
- data/doc/rdoc/classes/Hobix/Out.html +119 -0
- data/doc/rdoc/classes/Hobix/Page.html +381 -0
- data/doc/rdoc/classes/Hobix/Trackback.html +113 -0
- data/doc/rdoc/classes/Hobix/UriStr.html +198 -0
- data/doc/rdoc/classes/Hobix/WebApp/QueryString.html +207 -0
- data/doc/rdoc/classes/Hobix/WebApp/QueryValidationFailure.html +111 -0
- data/doc/rdoc/classes/Hobix/WebApp.html +1383 -0
- data/doc/rdoc/classes/Hobix/Weblog/AuthorNotFound.html +111 -0
- data/doc/rdoc/classes/Hobix/Weblog.html +2082 -0
- data/doc/rdoc/classes/Hobix.html +399 -0
- data/doc/rdoc/classes/Kernel.html +139 -0
- data/doc/rdoc/classes/Regexp.html +154 -0
- data/doc/rdoc/classes/YAML/Omap.html +144 -0
- data/doc/rdoc/classes/YAML.html +111 -0
- data/doc/rdoc/created.rid +1 -0
- data/doc/rdoc/files/COPYING.html +129 -0
- data/doc/rdoc/files/README.html +131 -0
- data/doc/rdoc/files/doc/CHANGELOG.html +101 -0
- data/doc/rdoc/files/lib/hobix/api_rb.html +119 -0
- data/doc/rdoc/files/lib/hobix/article_rb.html +126 -0
- data/doc/rdoc/files/lib/hobix/base_rb.html +128 -0
- data/doc/rdoc/files/lib/hobix/bixwik_rb.html +126 -0
- data/doc/rdoc/files/lib/hobix/commandline_rb.html +140 -0
- data/doc/rdoc/files/lib/hobix/comments_rb.html +126 -0
- data/doc/rdoc/files/lib/hobix/config_rb.html +125 -0
- data/doc/rdoc/files/lib/hobix/datamarsh_rb.html +108 -0
- data/doc/rdoc/files/lib/hobix/entry_rb.html +118 -0
- data/doc/rdoc/files/lib/hobix/linklist_rb.html +127 -0
- data/doc/rdoc/files/lib/hobix/publisher_rb.html +126 -0
- data/doc/rdoc/files/lib/hobix/trackbacks_rb.html +128 -0
- data/doc/rdoc/files/lib/hobix/webapp_rb.html +127 -0
- data/doc/rdoc/files/lib/hobix/weblog_rb.html +135 -0
- data/doc/rdoc/files/lib/hobix_rb.html +127 -0
- data/doc/rdoc/fr_class_index.html +67 -0
- data/doc/rdoc/fr_file_index.html +44 -0
- data/doc/rdoc/fr_method_index.html +307 -0
- data/doc/rdoc/index.html +24 -0
- data/doc/rdoc/rdoc-style.css +208 -0
- data/git_hobix_update.php +13 -0
- data/lib/hobix/api.rb +91 -0
- data/lib/hobix/article.rb +22 -0
- data/lib/hobix/base.rb +480 -0
- data/lib/hobix/bixwik.rb +200 -0
- data/lib/hobix/commandline.rb +677 -0
- data/lib/hobix/comments.rb +98 -0
- data/lib/hobix/config.rb +39 -0
- data/lib/hobix/datamarsh.rb +110 -0
- data/lib/hobix/entry.rb +84 -0
- data/lib/hobix/facets/comments.rb +99 -0
- data/lib/hobix/facets/publisher.rb +314 -0
- data/lib/hobix/facets/trackbacks.rb +80 -0
- data/lib/hobix/linklist.rb +81 -0
- data/lib/hobix/out/atom.rb +101 -0
- data/lib/hobix/out/erb.rb +64 -0
- data/lib/hobix/out/okaynews.rb +55 -0
- data/lib/hobix/out/quick.rb +314 -0
- data/lib/hobix/out/rdf.rb +97 -0
- data/lib/hobix/out/redrum.rb +26 -0
- data/lib/hobix/out/rss.rb +128 -0
- data/lib/hobix/plugin/akismet.rb +196 -0
- data/lib/hobix/plugin/bloglines.rb +73 -0
- data/lib/hobix/plugin/calendar.rb +212 -0
- data/lib/hobix/plugin/flickr.rb +110 -0
- data/lib/hobix/plugin/recent_comments.rb +84 -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 +408 -0
- data/lib/hobix/trackbacks.rb +93 -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 +893 -0
- data/lib/hobix.rb +230 -0
- data/share/default-blog/hobix.yaml +16 -0
- data/share/default-blog/htdocs/site.css +174 -0
- data/share/default-blog/skel/entry.html.quick +0 -0
- data/share/default-blog/skel/index.atom.atom +0 -0
- data/share/default-blog/skel/index.html.quick-summary +0 -0
- data/share/default-blog/skel/index.xml.rss +0 -0
- data/share/default-blog/skel/index.yaml.okaynews +0 -0
- data/share/default-blog/skel/monthly.html.quick-archive +0 -0
- data/share/default-blog/skel/section.html.quick-archive +0 -0
- data/share/default-blog/skel/yearly.html.quick-archive +0 -0
- data/share/default-blog-modes.yaml +7 -0
- data/share/default-blog.apache-cgi.patch +8 -0
- data/share/default-blog.apache-ssi.patch +38 -0
- data/share/default-blog.apache2-ssi.patch +3 -0
- data/share/default-blog.cgi.patch +8 -0
- data/share/default-blog.comments.patch +5 -0
- data/share/default-blog.prototype.patch +766 -0
- data/share/default-blog.publisher.patch +5 -0
- data/share/default-blog.wiki.patch +29 -0
- data/share/publisher/css/control.css +90 -0
- data/share/publisher/css/form.css +238 -0
- data/share/publisher/css/form.import.css +72 -0
- data/share/publisher/css/main-menu.css +134 -0
- data/share/publisher/i/hobix-emblazen-1.png +0 -0
- data/share/publisher/i/hobix-emblazen-2.png +0 -0
- data/share/publisher/i/hobix-emblazen-3.png +0 -0
- data/share/publisher/i/hobix-emblazen-4.png +0 -0
- data/share/publisher/i/hobix-emblazen-5.png +0 -0
- data/share/publisher/i/hobix-emblazen-6.png +0 -0
- data/share/publisher/i/hobix-emblazen-7.png +0 -0
- data/share/publisher/index.erb +66 -0
- data/share/publisher/js/controls.js +261 -0
- data/share/publisher/js/dragdrop.js +476 -0
- data/share/publisher/js/effects.js +570 -0
- data/share/publisher/js/prototype.js +1011 -0
- 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
|