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.
Files changed (120) hide show
  1. data/LICENSE.txt +66 -0
  2. data/README.txt +64 -0
  3. data/bin/soks-create-wiki.rb +193 -0
  4. data/contrib/diff/lcs.rb +1105 -0
  5. data/contrib/diff/lcs/array.rb +21 -0
  6. data/contrib/diff/lcs/block.rb +51 -0
  7. data/contrib/diff/lcs/callbacks.rb +322 -0
  8. data/contrib/diff/lcs/change.rb +169 -0
  9. data/contrib/diff/lcs/hunk.rb +257 -0
  10. data/contrib/diff/lcs/ldiff.rb +226 -0
  11. data/contrib/diff/lcs/string.rb +19 -0
  12. data/contrib/diff_licence.txt +76 -0
  13. data/contrib/easyprompt.rb +58 -0
  14. data/contrib/easyprompt_licence.txt +504 -0
  15. data/contrib/redcloth-3.0.3.rb +1113 -0
  16. data/contrib/redcloth_license.txt +27 -0
  17. data/lib/authenticators.rb +121 -0
  18. data/lib/helpers/counter-helpers.rb +132 -0
  19. data/lib/helpers/default-helpers.rb +416 -0
  20. data/lib/helpers/mail2wiki-helper.rb +105 -0
  21. data/lib/helpers/maintenance-helpers.rb +149 -0
  22. data/lib/helpers/rss2wiki-helper.rb +47 -0
  23. data/lib/helpers/wiki2html.rb +60 -0
  24. data/lib/soks-model.rb +271 -0
  25. data/lib/soks-servlet.rb +177 -0
  26. data/lib/soks-storage.rb +187 -0
  27. data/lib/soks-upgrade-0.0.2.rb +70 -0
  28. data/lib/soks-utils.rb +327 -0
  29. data/lib/soks-view.rb +399 -0
  30. data/lib/soks.rb +27 -0
  31. data/rakefile +109 -0
  32. data/templates/default/attachment/favicon.ico +0 -0
  33. data/templates/default/attachment/logo.jpg +0 -0
  34. data/templates/default/attachment/logo.png +0 -0
  35. data/templates/default/attachment/logo.tiff +0 -0
  36. data/templates/default/attachment/newpage.js +41 -0
  37. data/templates/default/attachment/print_stylesheet.css +2 -0
  38. data/templates/default/attachment/robots.txt +6 -0
  39. data/templates/default/attachment/rss.png +0 -0
  40. data/templates/default/attachment/stylesheet.css +219 -0
  41. data/templates/default/banned_titles.txt +67 -0
  42. data/templates/default/caches/readme.txt +1 -0
  43. data/templates/default/content/Api%20for%20classes%20to%20modify%20the%20wiki.textile +30 -0
  44. data/templates/default/content/Author.textile +16 -0
  45. data/templates/default/content/Automatic%20Summaries.textile +40 -0
  46. data/templates/default/content/Automatic%20counters.textile +22 -0
  47. data/templates/default/content/Automatic%20exporters.textile +23 -0
  48. data/templates/default/content/Automatic%20importers.textile +59 -0
  49. data/templates/default/content/Automatic%20linking.textile +7 -0
  50. data/templates/default/content/Automatic%20maintenance%20helpers.textile +39 -0
  51. data/templates/default/content/Bug%3A%20Competing%20edits.textile +22 -0
  52. data/templates/default/content/Bug%3A%20Does%20not%20make%20use%20of%20if%2Dmodified%2Dsince%20r.textile +3 -0
  53. data/templates/default/content/Bug%3A%20Email%20adresses%20in%20page%20titles%20cause%20incorrec.textile +3 -0
  54. data/templates/default/content/Bug%3A%20GEM%20limits%20title%20lengths.textile +3 -0
  55. data/templates/default/content/Bug%3A%20Memory%20leak.textile +13 -0
  56. data/templates/default/content/Bug%3A%20Page%2Einserted%5Finto%20is%20never%20purged.textile +17 -0
  57. data/templates/default/content/Bug%3A%20Pages%20that%20link%20here%20may%20not%20appear%20on%20r.textile +13 -0
  58. data/templates/default/content/Bug%3A%20Textile%20mishandles%20paragraphs.textile +37 -0
  59. data/templates/default/content/Bug%3A%20Unanticipated%20Rollbacks.textile +23 -0
  60. data/templates/default/content/Bug%3A%20notextile%20does%20not%20prevent%20page%20inserts.textile +3 -0
  61. data/templates/default/content/Home%20Page.textile +22 -0
  62. data/templates/default/content/How%20to%20administrate%20this%20wiki.textile +57 -0
  63. data/templates/default/content/How%20to%20change%20the%20way%20this%20wiki%20looks.textile +32 -0
  64. data/templates/default/content/How%20to%20export%20a%20site%20from%20this%20wiki.textile +82 -0
  65. data/templates/default/content/How%20to%20get%20the%20latest%20Soks%20from%20cvs.textile +45 -0
  66. data/templates/default/content/How%20to%20hack%20soks.textile +66 -0
  67. data/templates/default/content/How%20to%20import%20a%20site%20from%20instiki.textile +15 -0
  68. data/templates/default/content/How%20to%20import%20data.textile +41 -0
  69. data/templates/default/content/How%20to%20install%20Soks.textile +33 -0
  70. data/templates/default/content/How%20to%20password%20protect%20your%20wiki.textile +53 -0
  71. data/templates/default/content/How%20to%20re%2Dbuild%20the%20page%20cache.textile +71 -0
  72. data/templates/default/content/How%20to%20report%20a%20bug.textile +9 -0
  73. data/templates/default/content/How%20to%20upgrade%20soks.textile +32 -0
  74. data/templates/default/content/How%20to%20use%20the%20Automatic%20Helper%20classes.textile +12 -0
  75. data/templates/default/content/How%20to%20use%20this%20wiki.textile +30 -0
  76. data/templates/default/content/List%20of%20changes.textile +10 -0
  77. data/templates/default/content/News%3A%20Version%201%2D0%2D0%20released.textile +19 -0
  78. data/templates/default/content/News%3A%20Version%201%2D0%2D1%20released.textile +12 -0
  79. data/templates/default/content/Pages%20to%20include%20in%20the%20distribution.textile +55 -0
  80. data/templates/default/content/Per%20Wiki%20Templates.textile +37 -0
  81. data/templates/default/content/Picture%20of%20a%20pair%20of%20soks.textile +1 -0
  82. data/templates/default/content/Planned%20Features.textile +74 -0
  83. data/templates/default/content/README.textile +64 -0
  84. data/templates/default/content/RSS%20feed.textile +9 -0
  85. data/templates/default/content/Recent%20changes%20to%20this%20site.textile +352 -0
  86. data/templates/default/content/SOKS%20features.textile +19 -0
  87. data/templates/default/content/Sidebar%20Page.textile +6 -0
  88. data/templates/default/content/Site%20Index.textile +241 -0
  89. data/templates/default/content/Soks%27s%20Licence.textile +66 -0
  90. data/templates/default/content/Tag%3A%20Include%20this%20page%20in%20the%20distribution.textile +6 -0
  91. data/templates/default/start.rb +90 -0
  92. data/templates/default/version.txt +1 -0
  93. data/templates/default/views/Page_content.rhtml +1 -0
  94. data/templates/default/views/Page_edit.rhtml +79 -0
  95. data/templates/default/views/Page_find.rhtml +35 -0
  96. data/templates/default/views/Page_linksfromrss.rhtml +24 -0
  97. data/templates/default/views/Page_listrss.rhtml +46 -0
  98. data/templates/default/views/Page_meta.rhtml +44 -0
  99. data/templates/default/views/Page_print.rhtml +6 -0
  100. data/templates/default/views/Page_revision.rhtml +39 -0
  101. data/templates/default/views/Page_revisions.rhtml +36 -0
  102. data/templates/default/views/Page_rss.rhtml +57 -0
  103. data/templates/default/views/Page_view.rhtml +8 -0
  104. data/templates/default/views/UploadPage_edit.rhtml +63 -0
  105. data/templates/default/views/frame.rhtml +63 -0
  106. data/templates/default/views/messages.yaml +7 -0
  107. data/test/html/2006Mar.html +66 -0
  108. data/test/html/poignant.html +36 -0
  109. data/test/html/poignant.textile +36 -0
  110. data/test/mock-objects.rb +69 -0
  111. data/test/test_counter-helper.rb +162 -0
  112. data/test/test_soks-helper-maintenance.rb +106 -0
  113. data/test/test_soks-helpers.rb +145 -0
  114. data/test/test_soks-model.rb +144 -0
  115. data/test/test_soks-servlet.rb +240 -0
  116. data/test/test_soks-storage.rb +108 -0
  117. data/test/test_soks-utils.rb +226 -0
  118. data/test/test_soks-view.rb +193 -0
  119. data/test/test_soks.rb +9 -0
  120. 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 ) ) : "&nbsp; |\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 << "| &nbsp; | [[ #{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
+