hobix 0.6
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.
- 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
|