gf-Soks 1.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.
- data/LICENSE.txt +66 -0
- data/README.txt +64 -0
- data/bin/soks-create-wiki.rb +193 -0
- data/contrib/diff/lcs.rb +1105 -0
- data/contrib/diff/lcs/array.rb +21 -0
- data/contrib/diff/lcs/block.rb +51 -0
- data/contrib/diff/lcs/callbacks.rb +322 -0
- data/contrib/diff/lcs/change.rb +169 -0
- data/contrib/diff/lcs/hunk.rb +257 -0
- data/contrib/diff/lcs/ldiff.rb +226 -0
- data/contrib/diff/lcs/string.rb +19 -0
- data/contrib/diff_licence.txt +76 -0
- data/contrib/easyprompt.rb +58 -0
- data/contrib/easyprompt_licence.txt +504 -0
- data/contrib/redcloth-3.0.3.rb +1113 -0
- data/contrib/redcloth_license.txt +27 -0
- data/lib/authenticators.rb +121 -0
- data/lib/helpers/counter-helpers.rb +132 -0
- data/lib/helpers/default-helpers.rb +416 -0
- data/lib/helpers/mail2wiki-helper.rb +105 -0
- data/lib/helpers/maintenance-helpers.rb +149 -0
- data/lib/helpers/rss2wiki-helper.rb +47 -0
- data/lib/helpers/wiki2html.rb +60 -0
- data/lib/soks-model.rb +271 -0
- data/lib/soks-servlet.rb +177 -0
- data/lib/soks-storage.rb +187 -0
- data/lib/soks-upgrade-0.0.2.rb +70 -0
- data/lib/soks-utils.rb +327 -0
- data/lib/soks-view.rb +399 -0
- data/lib/soks.rb +27 -0
- data/rakefile +109 -0
- data/templates/default/attachment/favicon.ico +0 -0
- data/templates/default/attachment/logo.jpg +0 -0
- data/templates/default/attachment/logo.png +0 -0
- data/templates/default/attachment/logo.tiff +0 -0
- data/templates/default/attachment/newpage.js +41 -0
- data/templates/default/attachment/print_stylesheet.css +2 -0
- data/templates/default/attachment/robots.txt +6 -0
- data/templates/default/attachment/rss.png +0 -0
- data/templates/default/attachment/stylesheet.css +219 -0
- data/templates/default/banned_titles.txt +67 -0
- data/templates/default/caches/readme.txt +1 -0
- data/templates/default/content/Api%20for%20classes%20to%20modify%20the%20wiki.textile +30 -0
- data/templates/default/content/Author.textile +16 -0
- data/templates/default/content/Automatic%20Summaries.textile +40 -0
- data/templates/default/content/Automatic%20counters.textile +22 -0
- data/templates/default/content/Automatic%20exporters.textile +23 -0
- data/templates/default/content/Automatic%20importers.textile +59 -0
- data/templates/default/content/Automatic%20linking.textile +7 -0
- data/templates/default/content/Automatic%20maintenance%20helpers.textile +39 -0
- data/templates/default/content/Bug%3A%20Competing%20edits.textile +22 -0
- data/templates/default/content/Bug%3A%20Does%20not%20make%20use%20of%20if%2Dmodified%2Dsince%20r.textile +3 -0
- data/templates/default/content/Bug%3A%20Email%20adresses%20in%20page%20titles%20cause%20incorrec.textile +3 -0
- data/templates/default/content/Bug%3A%20GEM%20limits%20title%20lengths.textile +3 -0
- data/templates/default/content/Bug%3A%20Memory%20leak.textile +13 -0
- data/templates/default/content/Bug%3A%20Page%2Einserted%5Finto%20is%20never%20purged.textile +17 -0
- data/templates/default/content/Bug%3A%20Pages%20that%20link%20here%20may%20not%20appear%20on%20r.textile +13 -0
- data/templates/default/content/Bug%3A%20Textile%20mishandles%20paragraphs.textile +37 -0
- data/templates/default/content/Bug%3A%20Unanticipated%20Rollbacks.textile +23 -0
- data/templates/default/content/Bug%3A%20notextile%20does%20not%20prevent%20page%20inserts.textile +3 -0
- data/templates/default/content/Home%20Page.textile +22 -0
- data/templates/default/content/How%20to%20administrate%20this%20wiki.textile +57 -0
- data/templates/default/content/How%20to%20change%20the%20way%20this%20wiki%20looks.textile +32 -0
- data/templates/default/content/How%20to%20export%20a%20site%20from%20this%20wiki.textile +82 -0
- data/templates/default/content/How%20to%20get%20the%20latest%20Soks%20from%20cvs.textile +45 -0
- data/templates/default/content/How%20to%20hack%20soks.textile +66 -0
- data/templates/default/content/How%20to%20import%20a%20site%20from%20instiki.textile +15 -0
- data/templates/default/content/How%20to%20import%20data.textile +41 -0
- data/templates/default/content/How%20to%20install%20Soks.textile +33 -0
- data/templates/default/content/How%20to%20password%20protect%20your%20wiki.textile +53 -0
- data/templates/default/content/How%20to%20re%2Dbuild%20the%20page%20cache.textile +71 -0
- data/templates/default/content/How%20to%20report%20a%20bug.textile +9 -0
- data/templates/default/content/How%20to%20upgrade%20soks.textile +32 -0
- data/templates/default/content/How%20to%20use%20the%20Automatic%20Helper%20classes.textile +12 -0
- data/templates/default/content/How%20to%20use%20this%20wiki.textile +30 -0
- data/templates/default/content/List%20of%20changes.textile +10 -0
- data/templates/default/content/News%3A%20Version%201%2D0%2D0%20released.textile +19 -0
- data/templates/default/content/News%3A%20Version%201%2D0%2D1%20released.textile +12 -0
- data/templates/default/content/Pages%20to%20include%20in%20the%20distribution.textile +55 -0
- data/templates/default/content/Per%20Wiki%20Templates.textile +37 -0
- data/templates/default/content/Picture%20of%20a%20pair%20of%20soks.textile +1 -0
- data/templates/default/content/Planned%20Features.textile +74 -0
- data/templates/default/content/README.textile +64 -0
- data/templates/default/content/RSS%20feed.textile +9 -0
- data/templates/default/content/Recent%20changes%20to%20this%20site.textile +352 -0
- data/templates/default/content/SOKS%20features.textile +19 -0
- data/templates/default/content/Sidebar%20Page.textile +6 -0
- data/templates/default/content/Site%20Index.textile +241 -0
- data/templates/default/content/Soks%27s%20Licence.textile +66 -0
- data/templates/default/content/Tag%3A%20Include%20this%20page%20in%20the%20distribution.textile +6 -0
- data/templates/default/start.rb +90 -0
- data/templates/default/version.txt +1 -0
- data/templates/default/views/Page_content.rhtml +1 -0
- data/templates/default/views/Page_edit.rhtml +79 -0
- data/templates/default/views/Page_find.rhtml +35 -0
- data/templates/default/views/Page_linksfromrss.rhtml +24 -0
- data/templates/default/views/Page_listrss.rhtml +46 -0
- data/templates/default/views/Page_meta.rhtml +44 -0
- data/templates/default/views/Page_print.rhtml +6 -0
- data/templates/default/views/Page_revision.rhtml +39 -0
- data/templates/default/views/Page_revisions.rhtml +36 -0
- data/templates/default/views/Page_rss.rhtml +57 -0
- data/templates/default/views/Page_view.rhtml +8 -0
- data/templates/default/views/UploadPage_edit.rhtml +63 -0
- data/templates/default/views/frame.rhtml +63 -0
- data/templates/default/views/messages.yaml +7 -0
- data/test/html/2006Mar.html +66 -0
- data/test/html/poignant.html +36 -0
- data/test/html/poignant.textile +36 -0
- data/test/mock-objects.rb +69 -0
- data/test/test_counter-helper.rb +162 -0
- data/test/test_soks-helper-maintenance.rb +106 -0
- data/test/test_soks-helpers.rb +145 -0
- data/test/test_soks-model.rb +144 -0
- data/test/test_soks-servlet.rb +240 -0
- data/test/test_soks-storage.rb +108 -0
- data/test/test_soks-utils.rb +226 -0
- data/test/test_soks-view.rb +193 -0
- data/test/test_soks.rb +9 -0
- metadata +182 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
Redcloth License
|
|
2
|
+
|
|
3
|
+
Redistribution and use in source and binary forms, with or without
|
|
4
|
+
modification, are permitted provided that the following conditions are met:
|
|
5
|
+
|
|
6
|
+
* Redistributions of source code must retain the above copyright notice,
|
|
7
|
+
this list of conditions and the following disclaimer.
|
|
8
|
+
|
|
9
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
|
10
|
+
this list of conditions and the following disclaimer in the documentation
|
|
11
|
+
and/or other materials provided with the distribution.
|
|
12
|
+
|
|
13
|
+
* Neither the name Textile nor the names of its contributors may be used to
|
|
14
|
+
endorse or promote products derived from this software without specific
|
|
15
|
+
prior written permission.
|
|
16
|
+
|
|
17
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
18
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
19
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
20
|
+
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
|
21
|
+
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
22
|
+
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
23
|
+
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
24
|
+
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
25
|
+
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
26
|
+
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
27
|
+
POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
require 'webrick/config'
|
|
2
|
+
require 'webrick/httpstatus'
|
|
3
|
+
require 'webrick/httpauth/authenticator'
|
|
4
|
+
require 'base64'
|
|
5
|
+
|
|
6
|
+
module WEBrick
|
|
7
|
+
module HTTPAuth
|
|
8
|
+
|
|
9
|
+
module SoksUserCookie
|
|
10
|
+
|
|
11
|
+
def username_from_cookie(request)
|
|
12
|
+
cookie = request.cookies.find { |cookie| cookie.name == 'username' }
|
|
13
|
+
return cookie.value if cookie
|
|
14
|
+
return nil
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def add_cookie(request,response)
|
|
18
|
+
cookie = WEBrick::Cookie.new( 'username', request.user )
|
|
19
|
+
cookie.path = '/'
|
|
20
|
+
cookie.expires = Time.now + ( 60 * 60 * 24 * 180 ) # Expires in 180 days
|
|
21
|
+
response.cookies << cookie
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
class NoAuthenticationRequired
|
|
27
|
+
include SoksUserCookie
|
|
28
|
+
|
|
29
|
+
def authenticate(req, res)
|
|
30
|
+
req.user = username_from_cookie(req) || req.meta_vars["HTTP_X_FORWARDED_FOR"] || req.meta_vars["REMOTE_ADDR"]
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
class NotPermitted
|
|
37
|
+
|
|
38
|
+
def authenticate(req, res)
|
|
39
|
+
raise WEBrick::HTTPStatus::Unauthorized
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
class AskForUserName
|
|
45
|
+
include WEBrick::HTTPAuth::Authenticator
|
|
46
|
+
include SoksUserCookie
|
|
47
|
+
|
|
48
|
+
AuthScheme = "Basic"
|
|
49
|
+
|
|
50
|
+
def initialize( realm = "editing" )
|
|
51
|
+
config = { :UserDB => "nodb" , :Realm => realm }
|
|
52
|
+
check_init(config)
|
|
53
|
+
@config = Config::BasicAuth.dup.update(config)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def authenticate(req, res)
|
|
57
|
+
unless basic_credentials = check_scheme(req)
|
|
58
|
+
challenge(req, res)
|
|
59
|
+
end
|
|
60
|
+
userid, password = Base64.decode64(basic_credentials).split(":", 2)
|
|
61
|
+
if userid.empty?
|
|
62
|
+
error("user id was not given.")
|
|
63
|
+
challenge(req, res)
|
|
64
|
+
end
|
|
65
|
+
info("%s: authentication succeeded.", userid)
|
|
66
|
+
req.user = userid
|
|
67
|
+
add_cookie(req,res)
|
|
68
|
+
userid
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def challenge(req, res)
|
|
72
|
+
res[@response_field] = "#{@auth_scheme} realm=\"#{@realm}\""
|
|
73
|
+
raise @auth_exception
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
class SiteWidePassword
|
|
79
|
+
include Authenticator
|
|
80
|
+
include SoksUserCookie
|
|
81
|
+
|
|
82
|
+
AuthScheme = "Basic"
|
|
83
|
+
|
|
84
|
+
attr_reader :realm, :userdb, :logger
|
|
85
|
+
|
|
86
|
+
def initialize( password = "", realm = "editing" )
|
|
87
|
+
config = { :UserDB => "nodb" , :Realm => realm }
|
|
88
|
+
check_init(config)
|
|
89
|
+
@config = Config::BasicAuth.dup.update(config)
|
|
90
|
+
@password = password
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def authenticate(req, res)
|
|
94
|
+
unless basic_credentials = check_scheme(req)
|
|
95
|
+
challenge(req, res)
|
|
96
|
+
end
|
|
97
|
+
userid, password = Base64.decode64(basic_credentials).split(":", 2)
|
|
98
|
+
password ||= ""
|
|
99
|
+
if userid.empty?
|
|
100
|
+
error("user id was not given.")
|
|
101
|
+
challenge(req, res)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
if password != @password
|
|
105
|
+
error("%s: password unmatch.", userid)
|
|
106
|
+
challenge(req, res)
|
|
107
|
+
end
|
|
108
|
+
info("%s: authentication succeeded.", userid)
|
|
109
|
+
req.user = userid
|
|
110
|
+
add_cookie(req,res)
|
|
111
|
+
userid
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def challenge(req, res)
|
|
115
|
+
res[@response_field] = "#{@auth_scheme} realm=\"#{@realm}\""
|
|
116
|
+
raise @auth_exception
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
end
|
|
121
|
+
end
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
class CounterObject
|
|
2
|
+
|
|
3
|
+
attr_reader :total,:start_time
|
|
4
|
+
|
|
5
|
+
def initialize( show_top = 20 )
|
|
6
|
+
@show_top = show_top
|
|
7
|
+
@counts = {}
|
|
8
|
+
@total, @start_time = 0, Time.now
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def count( thing_to_count )
|
|
12
|
+
@counts[thing_to_count] = ( @counts[thing_to_count] || 0 ) + 1
|
|
13
|
+
@total += 1
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def each
|
|
17
|
+
running_total, displayed = 0, 0
|
|
18
|
+
@counts.sort_by { |thing,count| count }.reverse_each do |thing,count|
|
|
19
|
+
break if @show_top && ( displayed >= @show_top )
|
|
20
|
+
displayed += 1
|
|
21
|
+
running_total += count
|
|
22
|
+
yield thing, count
|
|
23
|
+
end
|
|
24
|
+
if @show_top && (@show_top < @counts.size)
|
|
25
|
+
yield "#{@counts.size - @show_top} others", @total-running_total
|
|
26
|
+
end
|
|
27
|
+
yield "*Total*", "*#{@total}*"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def [](thing)
|
|
31
|
+
@counts[thing]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def empty?
|
|
35
|
+
@counts.empty?
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
class ViewCountHelper
|
|
41
|
+
|
|
42
|
+
attr_reader :counts
|
|
43
|
+
|
|
44
|
+
def initialize( wiki, views_to_count = [ 'view' ], page_name = 'Popular Pages', update_page_every = :hour, show_top = 20, cache_name = 'viewcount' )
|
|
45
|
+
@wiki = wiki
|
|
46
|
+
@counts = wiki.load_cache(cache_name) || CounterObject.new(show_top)
|
|
47
|
+
@views_to_count, @page_name = views_to_count, page_name
|
|
48
|
+
@wiki.watch_for(:page_viewed) { |event,page,view,author| count page, view, author }
|
|
49
|
+
@wiki.watch_for(update_page_every) { render_count_page }
|
|
50
|
+
@wiki.watch_for(:shutdown) { wiki.save_cache(cache_name,@counts)}
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def count( page, view, author )
|
|
54
|
+
return unless should_count?( page, view, author )
|
|
55
|
+
@counts.count(count_key( page, view, author ))
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def should_count?( page, view, author )
|
|
59
|
+
@views_to_count.include?( view.downcase )
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def count_key( page, view, author )
|
|
63
|
+
page.name
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def render_count_page
|
|
67
|
+
content = "h1. #{@page_name}\n\n"
|
|
68
|
+
content << "Count since #{@counts.start_time}. Updated on #{Time.now}\n\n"
|
|
69
|
+
@counts.each do |page,count|
|
|
70
|
+
content << "| #{page} | #{count} |\n"
|
|
71
|
+
end
|
|
72
|
+
@wiki.revise( @page_name, content, 'AutomaticCounter' )
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
class ViewerCountHelper < ViewCountHelper
|
|
78
|
+
|
|
79
|
+
def initialize( wiki, views_to_count = [ 'view' ], page_name = 'Prolific Viewers', update_page_every = :hour, show_top = 20, cache_name = 'viewercount' )
|
|
80
|
+
super( wiki, views_to_count, page_name, update_page_every, show_top, cache_name )
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def count_key( page, view, author )
|
|
84
|
+
author
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
class AuthorCountHelper
|
|
89
|
+
|
|
90
|
+
attr_reader :counts
|
|
91
|
+
|
|
92
|
+
def initialize( wiki, page_name = 'Principal Authors', update_page_every = :hour, show_top = 20, cache_name = 'authorcount' )
|
|
93
|
+
@wiki = wiki
|
|
94
|
+
@counts = wiki.load_cache(cache_name) || CounterObject.new(show_top)
|
|
95
|
+
count_from_scratch if @counts.empty?
|
|
96
|
+
@page_name = page_name
|
|
97
|
+
@wiki.watch_for(:page_revised) { |event,page,revision| count page, revision }
|
|
98
|
+
@wiki.watch_for(update_page_every) { render_count_page }
|
|
99
|
+
@wiki.watch_for(:shutdown) { wiki.save_cache(cache_name,@counts)}
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def count_from_scratch
|
|
103
|
+
@wiki.each do |pagename,page|
|
|
104
|
+
page.revisions.each do |revision|
|
|
105
|
+
count( page, revision )
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def count( page, revision )
|
|
111
|
+
return unless should_count?( page, revision )
|
|
112
|
+
@counts.count(count_key( page, revision ))
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def should_count?( page, revision )
|
|
116
|
+
revision.author !~ /^Automatic/
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def count_key( page, revision )
|
|
120
|
+
revision.author
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def render_count_page
|
|
124
|
+
content = "h1. #{@page_name}\n\n"
|
|
125
|
+
content << "Count since #{@counts.start_time}. Updated on #{Time.now}\n\n"
|
|
126
|
+
@counts.each do |page,count|
|
|
127
|
+
content << "| #{page} | #{count} |\n"
|
|
128
|
+
end
|
|
129
|
+
@wiki.revise( @page_name, content, 'AutomaticCounter' )
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
end
|
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
require 'soks'
|
|
2
|
+
require 'net/smtp'
|
|
3
|
+
|
|
4
|
+
class AutomaticUpdateCrossLinks
|
|
5
|
+
|
|
6
|
+
def initialize( wiki, view, banned_titles = [] )
|
|
7
|
+
@wiki, @view, @banned_titles = wiki, view, banned_titles
|
|
8
|
+
@wiki.watch_for( :page_created ) { |event, page| new_page( page ) }
|
|
9
|
+
@wiki.watch_for( :page_deleted ) { |event, page| delete_page( page ) }
|
|
10
|
+
@wiki.watch_for( :page_revised ) { |event, page| page_revised( page ) }
|
|
11
|
+
update_all_pages
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def new_page( page )
|
|
15
|
+
return if title_banned? page.name
|
|
16
|
+
@view.rollingmatch[ page.name ] = page
|
|
17
|
+
titleregex = Regexp.new( Regexp.escape(page.name), Regexp::IGNORECASE )
|
|
18
|
+
# Refresh any page that might mention the title of the new page
|
|
19
|
+
@wiki.each do |name, linkedpage |
|
|
20
|
+
next if linkedpage.is_a? UploadPage
|
|
21
|
+
next unless linkedpage.textile =~ titleregex
|
|
22
|
+
@view.refresh_redcloth( linkedpage )
|
|
23
|
+
# Pages can be inserted into other pages, so need to refresh those as well
|
|
24
|
+
linkedpage.inserted_into.each { |insert| @view.refresh_redcloth( insert ) }
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def delete_page( page )
|
|
29
|
+
@view.rollingmatch.delete( page.name )
|
|
30
|
+
page.links_to.each { |linkedpage| @view.refresh_redcloth( linkedpage ) }
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def page_revised( page )
|
|
34
|
+
page.inserted_into.each { |including_page| @view.refresh_redcloth( including_page ) }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def update_all_pages
|
|
38
|
+
@wiki.each do |pagename, page|
|
|
39
|
+
@view.rollingmatch[ page.name ] = page unless title_banned?( page.name )
|
|
40
|
+
@view.links.links[ page ] = page.links_from
|
|
41
|
+
end
|
|
42
|
+
# Not needed now, because notife d
|
|
43
|
+
#@wiki.each do |pagename, page|
|
|
44
|
+
# @view.redcloth( page )
|
|
45
|
+
#end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def title_banned?( title )
|
|
49
|
+
@banned_titles.include? title
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
class AutomaticSummary
|
|
54
|
+
|
|
55
|
+
DEFAULT_SETTINGS = {
|
|
56
|
+
:max_pages_to_show => nil,
|
|
57
|
+
:description => 'This summary was created automatically',
|
|
58
|
+
:author => 'AutomaticSummary',
|
|
59
|
+
:lines_to_include => 10,
|
|
60
|
+
:sort_pages_by => :created_on, # Could be :revised_on or :score or :name or :name_for_index, or :author
|
|
61
|
+
:reverse_sort => false,
|
|
62
|
+
:remove_deleted_pages => true, # If false will keep references to deleted pages
|
|
63
|
+
:summarise_revisions => false, # If true will list revisions rather than pages
|
|
64
|
+
:merge_revisions_within => false, # If set to a number, repeats with the same author within that many seconds will be merged
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
attr_reader :name, :settings, :summary, :wiki, :decision
|
|
68
|
+
|
|
69
|
+
def initialize( wiki, name, settings = {}, &decision )
|
|
70
|
+
@wiki, @name, @decision = wiki, name, decision
|
|
71
|
+
@settings = DEFAULT_SETTINGS.merge( settings )
|
|
72
|
+
@summary = FiniteUniqueList.new( @settings[:max_pages_to_show], @settings[:reverse_sort], @settings[:sort_pages_by] )
|
|
73
|
+
add_existing_pages
|
|
74
|
+
start_watching wiki
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def start_watching( wiki )
|
|
78
|
+
wiki.watch_for( :page_revised ) do |event,page,revision|
|
|
79
|
+
thing = settings[:summarise_revisions ] ? revision : page
|
|
80
|
+
summary.include?( thing ) ? confirm_old(thing) : check_new(thing)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def confirm_old(thing)
|
|
85
|
+
summary.remove(thing) unless summarise?( thing )
|
|
86
|
+
render_summary_page
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def check_new(thing)
|
|
90
|
+
return unless summarise? thing
|
|
91
|
+
remove_previous_revisions thing
|
|
92
|
+
summary.add thing
|
|
93
|
+
render_summary_page
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def summarise?( thing )
|
|
97
|
+
return false if thing.name == name
|
|
98
|
+
return false if settings[:remove_deleted_pages] && thing.deleted?
|
|
99
|
+
decision.call( thing )
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def add_existing_pages
|
|
103
|
+
if settings[:summarise_revisions]
|
|
104
|
+
scan_revisions_allready_in_wiki
|
|
105
|
+
else
|
|
106
|
+
scan_pages_allready_in_wiki
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def scan_pages_allready_in_wiki
|
|
111
|
+
wiki.each( settings[:remove_deleted_pages] ) do |name,page|
|
|
112
|
+
next unless summarise?(page)
|
|
113
|
+
summary.add(page)
|
|
114
|
+
end
|
|
115
|
+
render_summary_page
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def scan_revisions_allready_in_wiki
|
|
119
|
+
wiki.each( settings[:remove_deleted_pages] ) do |name,page|
|
|
120
|
+
page.revisions.each do |revision|
|
|
121
|
+
next unless summarise? revision
|
|
122
|
+
remove_previous_revisions( revision )
|
|
123
|
+
summary.add revision
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
render_summary_page
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def remove_previous_revisions( revision )
|
|
130
|
+
return unless settings[:summarise_revisions]
|
|
131
|
+
return unless settings[:merge_revisions_within]
|
|
132
|
+
revision.number.downto(0) do |previous_number|
|
|
133
|
+
previous_revision = revision.revisions.at( previous_number )
|
|
134
|
+
break unless previous_revision.author == revision.author
|
|
135
|
+
break unless (revision.revised_on - previous_revision.revised_on) < settings[:merge_revisions_within]
|
|
136
|
+
summary.remove( previous_revision )
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# These methods relate to how the summary is shown.
|
|
141
|
+
|
|
142
|
+
def render_summary_page
|
|
143
|
+
wiki.page( name ).content =~ /(.*?<automaticsummary.*?>).*?(<\/automaticsummary>.*)/mi
|
|
144
|
+
wiki.revise( name, ($1 || new_top) + "\n\n" + render_summary + "\n\n" + ($2 || new_tail), @settings[:author] )
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def render_summary
|
|
148
|
+
return "No pages found to summarise" if @summary.empty?
|
|
149
|
+
summary.map { |page| render_summary_of_page(page) }.to_s
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def render_summary_of_page( page )
|
|
153
|
+
page.is_inserted_into(wiki.page( name ))
|
|
154
|
+
content = "<div class='subpage'>"
|
|
155
|
+
content << "[[ #{page.name} ]]<br />\n\n"
|
|
156
|
+
if page.is_a? UploadPage
|
|
157
|
+
content << "[[ insert #{page.name} ]]"
|
|
158
|
+
else
|
|
159
|
+
content << page.content.first_lines( settings[:lines_to_include] ).close_unmatched_html
|
|
160
|
+
end
|
|
161
|
+
content << "\n\np(more). [[(more) => #{page.name}]]\n\n</div>\n"
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def new_top
|
|
165
|
+
(wiki.page(name).empty? ? "" : "#{wiki.page(name).content}\n\n" ) +
|
|
166
|
+
"h2. #{name}
|
|
167
|
+
|
|
168
|
+
p{font-size: x-small;}. #{@settings[:description]}
|
|
169
|
+
|
|
170
|
+
<automaticsummary warning='DO NOT EDIT between these automatic summary tags, anything you write may be overwritten without warning'>
|
|
171
|
+
"
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def new_tail
|
|
175
|
+
"</automaticsummary>"
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
class AutomaticList < AutomaticSummary
|
|
182
|
+
|
|
183
|
+
def render_summary
|
|
184
|
+
summary.map { |page| render_list_item(page) }.to_s
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def render_list_item( page )
|
|
188
|
+
"* [[ #{page.name} ]]\n"
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
class AutomaticDetailedList < AutomaticList
|
|
194
|
+
|
|
195
|
+
def render_list_item( page )
|
|
196
|
+
"* [[ #{page.name} ]] revised on #{page.revised_on.strftime('%Y %b %d %H:%M')} by #{page.author}\n"
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
class AutomaticRecentChanges < AutomaticSummary
|
|
202
|
+
|
|
203
|
+
def initialize( wiki, changes = 200, pagename = "Recent changes to this site", author = "AutomaticRecentChanges", exclude_automatic_helpers = true, merge_revisions_within = 60*60*12 )
|
|
204
|
+
super( wiki, pagename, :summarise_revisions => true,
|
|
205
|
+
:max_pages_to_show => changes,
|
|
206
|
+
:author => author,
|
|
207
|
+
:sort_pages_by => :revised_on,
|
|
208
|
+
:event => :page_revised,
|
|
209
|
+
:reverse_sort => true,
|
|
210
|
+
:remove_deleted_pages => false,
|
|
211
|
+
:merge_revisions_within => merge_revisions_within ) do |revision|
|
|
212
|
+
exclude_automatic_helpers ? revision.author !~ /^Automatic/i : true
|
|
213
|
+
end
|
|
214
|
+
wiki.watch_for( :day ) { render_summary_page }
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def render_summary
|
|
218
|
+
content = "<div class='recentchanges'>\n\nh2. Today\n\n"
|
|
219
|
+
previous_time = Time.now
|
|
220
|
+
@summary.each do |revision|
|
|
221
|
+
unless revision.revised_on.same_day?( previous_time )
|
|
222
|
+
content << "\nh2. #{revision.revised_on.relative_day}\n\n"
|
|
223
|
+
previous_time = revision.revised_on
|
|
224
|
+
end
|
|
225
|
+
content << "* #{revision.revised_on.strftime('%H:%M')} - [[#{revision.name}]] revised by #{revision.author} ([[changes => /revision/#{revision.name}?time=#{revision.created_on.to_i}]])\n"
|
|
226
|
+
end
|
|
227
|
+
content << "\n\n</div>"
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
class AutomaticOnePageIndex
|
|
233
|
+
|
|
234
|
+
def initialize( wiki, pagename = "Site Index", author = "AutomaticIndex" )
|
|
235
|
+
AutomaticList.new( wiki, pagename, :author => author, :sort_pages_by => :name_for_index ) do |page|
|
|
236
|
+
page.name !~ /#{Regexp.escape(pagename)}/io
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
class AutomaticMultiPageIndex
|
|
243
|
+
|
|
244
|
+
def initialize( wiki, pageroot = "Site Index", author = "AutomaticIndex" )
|
|
245
|
+
('A'..'Z').each do |letter|
|
|
246
|
+
AutomaticList.new( wiki, "#{pageroot} #{letter}.", :author => author, :sort_pages_by => :name_for_index ) do |page|
|
|
247
|
+
if page.name =~ /^#{Regexp.escape(pageroot)} ([a-z]|Other)\./i
|
|
248
|
+
false
|
|
249
|
+
else
|
|
250
|
+
page.name =~ /^#{letter}.*/i
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
AutomaticList.new( wiki, "#{pageroot} Other.", :author => author, :sort_pages_by => :name_for_index ) do |page|
|
|
256
|
+
if page.name =~ /^#{Regexp.escape(pageroot)} ([a-z]|Other)\./i
|
|
257
|
+
false
|
|
258
|
+
else
|
|
259
|
+
page.name =~ /^[^A-Za-z].*/i
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
AutomaticList.new( wiki, pageroot, :author => author, :sort_pages_by => :name_for_index ) do |page|
|
|
264
|
+
page.name =~ /^#{Regexp.escape(pageroot)} ([a-z]|Other)\./i
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
class AutomaticCalendar
|
|
270
|
+
|
|
271
|
+
attr_reader :month_pagename, :day_pagename
|
|
272
|
+
|
|
273
|
+
def initialize( wiki, month_pagename = '%Y %b', day_pagename = '%Y %b %d', author = "AutomaticCalendar" )
|
|
274
|
+
@wiki, @month_pagename, @day_pagename, @author = wiki, month_pagename, day_pagename, author
|
|
275
|
+
render_coming_year
|
|
276
|
+
@wiki.watch_for(:month) { render_coming_year }
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
def render_coming_year
|
|
280
|
+
Time.now.month.upto( Time.now.month+12 ) { |m| render_month( m ) }
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
def render_month( month )
|
|
284
|
+
@wiki.revise( month_pagename( month ), calendar_for( month ) , @author ) unless @wiki.exists?( month_pagename( month ) )
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
def calendar_for( month )
|
|
288
|
+
content = "<div class='calendar'>\n\n"
|
|
289
|
+
content << "|_. Su |_. Mo |_. Tu |_. We |_. Th |_. Fr |_. Sa |\n"
|
|
290
|
+
1.upto( time_for( month, 1 ).wday ) { content << "| . " }
|
|
291
|
+
day = nil
|
|
292
|
+
1.upto( 31 ) do |day_no|
|
|
293
|
+
day = time_for( month, day_no )
|
|
294
|
+
break if day.month > month
|
|
295
|
+
content << "| [[ #{day_no} => #{day_pagename( day )} ]] "
|
|
296
|
+
content << "|\n" if day.wday == 6
|
|
297
|
+
end
|
|
298
|
+
day.wday.upto( 5 ) { content << "| . " }
|
|
299
|
+
content << "|\n"
|
|
300
|
+
content << "\n\n#{month_pagename( month-1 )} #{month_pagename( month+1 )} \n"
|
|
301
|
+
content << "\n</div>"
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
def time_for( month, day = 1 )
|
|
305
|
+
year = Time.now.year
|
|
306
|
+
if month > 12
|
|
307
|
+
year +=1
|
|
308
|
+
month -= 12
|
|
309
|
+
elsif month < 1
|
|
310
|
+
year -= 1
|
|
311
|
+
month = month + 12
|
|
312
|
+
end
|
|
313
|
+
if day > 31
|
|
314
|
+
month += 1
|
|
315
|
+
day -= 31
|
|
316
|
+
end
|
|
317
|
+
Time.local( year, month, day, 8, 0 )
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
def month_pagename( month = Time.now.month ) time_for( month ).strftime(@month_pagename) end
|
|
321
|
+
|
|
322
|
+
def day_pagename( date = Time.now ) date.strftime(@day_pagename) end
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
class AutomaticUpcomingEvents
|
|
326
|
+
|
|
327
|
+
def initialize( wiki, calendar, days_passed = 0, days_future = 7, pagename = 'Upcoming Events', author = "AutomaticUpcomingEvents" )
|
|
328
|
+
@wiki, @calendar, @days_passed, @days__future, @pagename, @author = wiki, calendar, days_passed, days_future, pagename, author
|
|
329
|
+
@wiki.watch_for( :page_revised ) { |event, page| page_revised( page ) }
|
|
330
|
+
@wiki.watch_for( :day ) { render_upcoming_events }
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
def page_revised( page )
|
|
334
|
+
render_upcoming_events if page.name =~ /^\d\d\d\d ... \d\d/
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
def render_upcoming_events
|
|
338
|
+
content = "<div class='upcomingevents'>\n\n"
|
|
339
|
+
Time.now.day.upto( Time.now.day+7 ) do |day|
|
|
340
|
+
time = @calendar.time_for( Time.now.month, day )
|
|
341
|
+
content << "| [[ #{time.relative_day} => #{@calendar.day_pagename( time )} ]] |"
|
|
342
|
+
content << (@wiki.exists?( @calendar.day_pagename(time) ) ? render_event( @calendar.day_pagename( time ) ) : " |\n")
|
|
343
|
+
end
|
|
344
|
+
content << "\n\np(more). [[(more) => #{@calendar.month_pagename}]]\n\n"
|
|
345
|
+
content << "\n</div>\n"
|
|
346
|
+
@wiki.revise( "Upcoming Events", content , "AutomaticCalendar" )
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
def render_event( name )
|
|
350
|
+
page = @wiki.page( name )
|
|
351
|
+
headings = page.textile.select { |line| line =~ /^h\d\./ }
|
|
352
|
+
headings = headings.map { |heading| heading.to_s[4..-1].strip }
|
|
353
|
+
headings = [ page.textile.first_lines(1) ] if headings.empty?
|
|
354
|
+
content = " [[ #{headings.shift} => #{page.name} ]] |\n"
|
|
355
|
+
headings.each { |heading| content << "| | [[ #{heading} => #{page.name} ]] |\n" }
|
|
356
|
+
content
|
|
357
|
+
end
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
class AutomaticAuthorIndex
|
|
361
|
+
|
|
362
|
+
def initialize( wiki, create_author_pages = true, author_include_regexp = /.*/, author_exclude_regexp = /^Automatic.*/, pagename = "authors", author = "AutomaticAuthorIndexer", author_page_tail = "s pages" )
|
|
363
|
+
@wiki, @create_author_pages, @author_include_regexp, @author_exclude_regexp, @pagename, @author, @author_page_tail = wiki, create_author_pages, author_include_regexp, author_exclude_regexp, pagename, author, author_page_tail
|
|
364
|
+
@authors = []
|
|
365
|
+
find_all_authors( wiki )
|
|
366
|
+
@wiki.watch_for( :page_revised ) { |event, page, revision|
|
|
367
|
+
$stderr.puts "Got a page revised message in AutomaticAuthorIndex"
|
|
368
|
+
add_author( revision ) }
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
def add_author( revision )
|
|
372
|
+
return if @authors.include? revision.author
|
|
373
|
+
return unless revision.author =~ @author_include_regexp
|
|
374
|
+
return unless revision.author !~ @author_exclude_regexp
|
|
375
|
+
@authors << revision.author
|
|
376
|
+
@authors.sort!
|
|
377
|
+
create_author_page_for(revision.author) if @create_author_pages
|
|
378
|
+
render_author_index
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
def find_all_authors( wiki )
|
|
382
|
+
wiki.each do |name, page|
|
|
383
|
+
page.revisions.each do |revision|
|
|
384
|
+
add_author( revision )
|
|
385
|
+
end
|
|
386
|
+
end
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
def render_author_index
|
|
390
|
+
content = "h1. Index of Authors\n\n"
|
|
391
|
+
@authors.each do |author|
|
|
392
|
+
content << "* #{author}"
|
|
393
|
+
if @create_author_pages
|
|
394
|
+
content << " #{author}#{@author_page_tail}"
|
|
395
|
+
end
|
|
396
|
+
content << "\n"
|
|
397
|
+
end
|
|
398
|
+
@wiki.revise( @pagename, content, @author )
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
def create_author_page_for( author )
|
|
403
|
+
AutomaticSummary.new( @wiki, { :pagename => "#{author}#{@author_page_tail}" ,
|
|
404
|
+
:regexp_for_author => /#{author}/,
|
|
405
|
+
:author => @author,
|
|
406
|
+
:only_new_pages => false,
|
|
407
|
+
:sort_pages_by => :revised_on,
|
|
408
|
+
:include_metadata => true,
|
|
409
|
+
:summarise_revisions => true,
|
|
410
|
+
:reverse_sort => true,
|
|
411
|
+
:remove_deleted_pages => false,
|
|
412
|
+
} )
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
end
|
|
416
|
+
|