instiki 0.9.2

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 (61) hide show
  1. data/README +172 -0
  2. data/app/controllers/wiki.rb +389 -0
  3. data/app/models/author.rb +4 -0
  4. data/app/models/chunks/category.rb +31 -0
  5. data/app/models/chunks/chunk.rb +20 -0
  6. data/app/models/chunks/engines.rb +38 -0
  7. data/app/models/chunks/include.rb +29 -0
  8. data/app/models/chunks/literal.rb +19 -0
  9. data/app/models/chunks/match.rb +19 -0
  10. data/app/models/chunks/nowiki.rb +31 -0
  11. data/app/models/chunks/test.rb +18 -0
  12. data/app/models/chunks/uri.rb +97 -0
  13. data/app/models/chunks/wiki.rb +82 -0
  14. data/app/models/page.rb +86 -0
  15. data/app/models/page_lock.rb +24 -0
  16. data/app/models/page_set.rb +64 -0
  17. data/app/models/revision.rb +90 -0
  18. data/app/models/web.rb +89 -0
  19. data/app/models/wiki_content.rb +105 -0
  20. data/app/models/wiki_service.rb +83 -0
  21. data/app/models/wiki_words.rb +25 -0
  22. data/app/views/bottom.rhtml +4 -0
  23. data/app/views/markdown_help.rhtml +16 -0
  24. data/app/views/navigation.rhtml +19 -0
  25. data/app/views/rdoc_help.rhtml +16 -0
  26. data/app/views/static_style_sheet.rhtml +199 -0
  27. data/app/views/textile_help.rhtml +28 -0
  28. data/app/views/top.rhtml +49 -0
  29. data/app/views/wiki/authors.rhtml +13 -0
  30. data/app/views/wiki/edit.rhtml +31 -0
  31. data/app/views/wiki/edit_web.rhtml +138 -0
  32. data/app/views/wiki/export.rhtml +14 -0
  33. data/app/views/wiki/feeds.rhtml +10 -0
  34. data/app/views/wiki/list.rhtml +57 -0
  35. data/app/views/wiki/locked.rhtml +14 -0
  36. data/app/views/wiki/login.rhtml +11 -0
  37. data/app/views/wiki/new.rhtml +27 -0
  38. data/app/views/wiki/new_system.rhtml +78 -0
  39. data/app/views/wiki/new_web.rhtml +64 -0
  40. data/app/views/wiki/page.rhtml +81 -0
  41. data/app/views/wiki/print.rhtml +16 -0
  42. data/app/views/wiki/published.rhtml +10 -0
  43. data/app/views/wiki/recently_revised.rhtml +30 -0
  44. data/app/views/wiki/revision.rhtml +81 -0
  45. data/app/views/wiki/rollback.rhtml +31 -0
  46. data/app/views/wiki/rss_feed.rhtml +22 -0
  47. data/app/views/wiki/search.rhtml +15 -0
  48. data/app/views/wiki/tex.rhtml +23 -0
  49. data/app/views/wiki/tex_web.rhtml +35 -0
  50. data/app/views/wiki/web_list.rhtml +13 -0
  51. data/app/views/wiki_words_help.rhtml +8 -0
  52. data/instiki +67 -0
  53. data/libraries/action_controller_servlet.rb +177 -0
  54. data/libraries/diff/diff.rb +475 -0
  55. data/libraries/erb.rb +490 -0
  56. data/libraries/madeleine_service.rb +68 -0
  57. data/libraries/rdocsupport.rb +156 -0
  58. data/libraries/redcloth_for_tex.rb +869 -0
  59. data/libraries/view_helper.rb +33 -0
  60. data/libraries/web_controller_server.rb +81 -0
  61. metadata +142 -0
@@ -0,0 +1,24 @@
1
+ # Contains all the lock methods to be mixed in with the page
2
+ module PageLock
3
+ LOCKING_PERIOD = 30 * 60 # 30 minutes
4
+
5
+ def lock(time, locked_by)
6
+ @locked_at, @locked_by = time, locked_by
7
+ end
8
+
9
+ def lock_duration(time)
10
+ ((time - @locked_at) / 60).to_i unless @locked_at.nil?
11
+ end
12
+
13
+ def unlock
14
+ @locked_at = nil
15
+ end
16
+
17
+ def locked?(comparison_time)
18
+ @locked_at + LOCKING_PERIOD > comparison_time unless @locked_at.nil?
19
+ end
20
+
21
+ def locked_by_link
22
+ web.make_link(@locked_by)
23
+ end
24
+ end
@@ -0,0 +1,64 @@
1
+ # Container for a set of pages with methods for manipulation.
2
+ class PageSet < Array
3
+ attr_reader :web
4
+
5
+ def initialize(web, pages, accept_proc)
6
+ @web = web
7
+ if accept_proc.nil? then
8
+ super(pages)
9
+ else
10
+ super(pages.select { |page| accept_proc[page] })
11
+ end
12
+ end
13
+
14
+ def most_recent_revision
15
+ self.sort_by { |page| [page.created_at] }.reverse.first.created_at
16
+ end
17
+
18
+ def by_name
19
+ self.sort_by { |page| [page.name] }
20
+ end
21
+
22
+ def by_revision
23
+ self.sort_by { |page| [page.created_at] }.reverse
24
+ end
25
+
26
+ def pages_that_reference(page_name)
27
+ self.select { |page| page.wiki_words.include?(page_name) }
28
+ end
29
+
30
+ def pages_authored_by(author)
31
+ self.select { |page| page.authors.include?(author) }
32
+ end
33
+
34
+ def characters
35
+ self.inject(0) { |chars,page| chars += page.content.size }
36
+ end
37
+
38
+ # Returns all the orphaned pages in this page set. That is,
39
+ # pages in this set for which there is no reference in the web.
40
+ # The HomePage and author pages are always assumed to have
41
+ # references and so cannot be orphans
42
+ def orphaned_pages
43
+ references = web.select.wiki_words + ["HomePage"] + web.select.authors
44
+ self.reject { |page| references.include?(page.name) }
45
+ end
46
+
47
+ # Returns all the wiki words in this page set for which
48
+ # there are no pages in this page set's web
49
+ def wanted_pages
50
+ wiki_words - web.select.names
51
+ end
52
+
53
+ def names
54
+ self.map { |page| page.name }
55
+ end
56
+
57
+ def wiki_words
58
+ self.inject([]) { |wiki_words, page| wiki_words << page.wiki_words }.flatten.uniq
59
+ end
60
+
61
+ def authors
62
+ self.inject([]) { |authors, page| authors << page.authors }.flatten.uniq.sort
63
+ end
64
+ end
@@ -0,0 +1,90 @@
1
+ $: << File.dirname(__FILE__) + "../../libraries"
2
+
3
+ require "diff/diff"
4
+
5
+ require "wiki_content"
6
+ require "chunks/wiki"
7
+
8
+ require "date"
9
+ require "author"
10
+ require "page"
11
+
12
+ class Revision
13
+ attr_accessor :page, :number, :content, :created_at, :author
14
+
15
+ def initialize(page, number, content, created_at, author)
16
+ @page, @number, @created_at, @author = page, number, created_at, author
17
+ self.content = content
18
+ end
19
+
20
+ # Ensure that the wiki content is parsed when ever it is updated.
21
+ def content=(content)
22
+ @content = content
23
+ end
24
+
25
+ def created_on
26
+ Date.new(@created_at.year, @created_at.mon, @created_at.day)
27
+ end
28
+
29
+ def pretty_created_at
30
+ # Must use DateTime because Time doesn't support %e on at least some platforms
31
+ DateTime.new(
32
+ @created_at.year, @created_at.mon, @created_at.day, @created_at.hour, @created_at.min
33
+ ).strftime "%B %e, %Y %H:%M"
34
+ end
35
+
36
+ def next_revision
37
+ page.revisions[number + 1]
38
+ end
39
+
40
+ def previous_revision
41
+ number - 1 >= 0 && page.revisions[number - 1]
42
+ end
43
+
44
+ # Returns an array of all the WikiWords present in the content of this revision.
45
+ def wiki_words
46
+ unless @wiki_words_cache
47
+ wiki_chunks = display_content.find_chunks(WikiChunk::WikiLink)
48
+ @wiki_words_cache = wiki_chunks.map { |c| ( c.escaped_text ? nil : c.page_name ) }.compact.uniq
49
+ end
50
+ @wiki_words_cache
51
+ end
52
+
53
+ # Returns an array of all the WikiWords present in the content of this revision.
54
+ # that already exists as a page in the web.
55
+ def existing_pages
56
+ wiki_words.select { |wiki_word| page.web.pages[wiki_word] }
57
+ end
58
+
59
+ # Returns an array of all the WikiWords present in the content of this revision
60
+ # that *doesn't* already exists as a page in the web.
61
+ def unexisting_pages
62
+ wiki_words - existing_pages
63
+ end
64
+
65
+ # Explicit check for new type of display cache with find_chunks method.
66
+ # Ensures new version works with older snapshots.
67
+ def display_content
68
+ unless @display_cache && @display_cache.respond_to?(:find_chunks)
69
+ @display_cache = WikiContent.new(self)
70
+ end
71
+ @display_cache
72
+ end
73
+
74
+ def display_diff
75
+ previous_revision ? HTMLDiff.diff(previous_revision.display_content, display_content) : display_content
76
+ end
77
+
78
+ def clear_display_cache
79
+ @display_cache = @published_cache = @wiki_words_cache = nil
80
+ end
81
+
82
+ def display_published
83
+ @published_cache = WikiContent.new(self, {:mode => :publish}) if @published_cache.nil?
84
+ @published_cache
85
+ end
86
+
87
+ def display_content_for_export
88
+ WikiContent.new(self, {:mode => :export} )
89
+ end
90
+ end
@@ -0,0 +1,89 @@
1
+ require "cgi"
2
+ require "page"
3
+ require "page_set"
4
+ require "wiki_words"
5
+ require "zip/zip"
6
+
7
+ class Web
8
+ attr_accessor :pages, :name, :address, :password
9
+ attr_accessor :markup, :color, :safe_mode, :additional_style, :published, :brackets_only, :count_pages
10
+
11
+ def initialize(name, address, password = nil)
12
+ @name, @address, @password, @safe_mode = name, address, password, false
13
+ @pages = {}
14
+ end
15
+
16
+ def add_page(page)
17
+ @pages[page.name] = page
18
+ end
19
+
20
+ def remove_pages(pages_to_be_removed)
21
+ pages.delete_if { |page_name, page| pages_to_be_removed.include?(page) }
22
+ end
23
+
24
+ def select(&accept)
25
+ PageSet.new(self, @pages.values, accept)
26
+ end
27
+
28
+ def revised_on
29
+ select.most_recent_revision
30
+ end
31
+
32
+ def authors
33
+ select.authors
34
+ end
35
+
36
+ def categories
37
+ select.map { |page| page.categories }.flatten.uniq.sort
38
+ end
39
+
40
+ # Create a link for the given page name and link text based
41
+ # on the render mode in options and whether the page exists
42
+ # in the this web.
43
+ def make_link(name, text = nil, options = {})
44
+ page = pages[name]
45
+ text = text || WikiWords.separate(name)
46
+ link = CGI.escape(name)
47
+
48
+ case options[:mode]
49
+ when :export
50
+ if page then "<a class=\"existingWikiWord\" href=\"#{link}.html\">#{text}</a>"
51
+ else "<span class=\"newWikiWord\">#{text}</span>" end
52
+ when :publish
53
+ if page then "<a class=\"existingWikiWord\" href=\"../show/#{link}\">#{text}</a>"
54
+ else "<span class=\"newWikiWord\">#{text}</span>" end
55
+ else
56
+ if page then "<a class=\"existingWikiWord\" href=\"../show/#{link}\">#{text}</a>"
57
+ else "<span class=\"newWikiWord\">#{text}<a href=\"../show/#{link}\">?</a></span>" end
58
+ end
59
+ end
60
+
61
+
62
+ # Clears the display cache for all the pages with references to
63
+ def refresh_pages_with_references(page_name)
64
+ select.pages_that_reference(page_name).each { |page|
65
+ page.revisions.each { |revision| revision.clear_display_cache }
66
+ }
67
+ end
68
+
69
+ def refresh_revisions
70
+ select.each { |page| page.revisions.each { |revision| revision.clear_display_cache } }
71
+ end
72
+
73
+ # Default values
74
+ def markup() @markup || :textile end
75
+ def color() @color || "008B26" end
76
+ def brackets_only() @brackets_only || false end
77
+ def count_pages() @count_pages || false end
78
+
79
+ private
80
+ # Returns an array of all the wiki words in any current revision
81
+ def wiki_words
82
+ pages.values.inject([]) { |wiki_words, page| wiki_words << page.wiki_words }.flatten.uniq
83
+ end
84
+
85
+ # Returns an array of all the page names on this web
86
+ def page_names
87
+ pages.keys
88
+ end
89
+ end
@@ -0,0 +1,105 @@
1
+ require 'cgi'
2
+ require 'chunks/engines'
3
+ require 'chunks/category'
4
+ require 'chunks/include'
5
+ require 'chunks/wiki'
6
+ require 'chunks/literal'
7
+ require 'chunks/uri'
8
+ require 'chunks/nowiki'
9
+
10
+ # Wiki content is just a string that can process itself with a chain of
11
+ # actions. The actions can modify wiki content so that certain parts of
12
+ # it are protected from being rendered by later actions.
13
+ #
14
+ # When wiki content is rendered, it can be interrogated to find out
15
+ # which chunks were rendered. This means things like categories, wiki
16
+ # links, can be determined.
17
+ #
18
+ # Exactly how wiki content is rendered is determined by a number of
19
+ # settings that are optionally passed in to a constructor. The current
20
+ # options are:
21
+ # * :engine
22
+ # => The structural markup engine to use (Textile, Markdown, RDoc)
23
+ # * :engine_opts
24
+ # => A list of options to pass to the markup engines (safe modes, etc)
25
+ # * :pre_engine_actions
26
+ # => A list of render actions or chunks to be processed before the
27
+ # markup engine is applied. By default this is:
28
+ # Category, Include, URIChunk, WikiChunk::Link, WikiChunk::Word
29
+ # * :post_engine_actions
30
+ # => A list of render actions or chunks to apply after the markup
31
+ # engine. By default these are:
32
+ # Literal::Pre, Literal::Tags
33
+ # * :mode
34
+ # => How should the content be rendered? For normal display (:display),
35
+ # publishing (:publish) or export (:export)?
36
+ #
37
+ # AUTHOR: Mark Reid <mark @ threewordslong . com>
38
+ # CREATED: 15th May 2004
39
+ # UPDATED: 22nd May 2004
40
+ class WikiContent < String
41
+
42
+ PRE_ENGINE_ACTIONS = [ NoWiki, Category, Include, URIChunk, WikiChunk::Link, WikiChunk::Word ]
43
+ POST_ENGINE_ACTIONS = [ Literal::Pre, Literal::Tags ]
44
+ DEFAULT_OPTS = {
45
+ :pre_engine_actions => PRE_ENGINE_ACTIONS,
46
+ :post_engine_actions => POST_ENGINE_ACTIONS,
47
+ :engine => Engines::Textile,
48
+ :engine_opts => [],
49
+ :mode => [:display]
50
+ }
51
+
52
+ attr_reader :web, :options, :rendered
53
+
54
+ # Create a new wiki content string from the given one.
55
+ # The options are explained at the top of this file.
56
+ def initialize(revision, options = {})
57
+ @revision = revision
58
+ @web = @revision.page.web
59
+
60
+ # Deep copy of DEFAULT_OPTS to ensure that changes to PRE/POST_ENGINE_ACTIONS stay local
61
+ @options = Marshal.load(Marshal.dump(DEFAULT_OPTS)).update(options)
62
+ @options[:engine] = Engines::MAP[@web.markup] || Engines::Textile
63
+ @options[:engine_opts] = (@web.safe_mode ? [:filter_html, :filter_styles] : [])
64
+
65
+ @options[:pre_engine_actions].delete(WikiChunk::Word) if @web.brackets_only
66
+
67
+ super(@revision.content)
68
+
69
+ begin
70
+ render!(@options[:pre_engine_actions] + [@options[:engine]] + @options[:post_engine_actions])
71
+ rescue => e
72
+ @rendered = e.message
73
+ end
74
+ end
75
+
76
+ # Call @web.page_link using current options.
77
+ def page_link(name, text)
78
+ @web.make_link(name, text, @options)
79
+ end
80
+
81
+ # Find all the chunks of the given types
82
+ def find_chunks(chunk_type)
83
+ rendered.select { |chunk| chunk.kind_of?(chunk_type) }
84
+ end
85
+
86
+ # Render this content using the specified actions.
87
+ def render!(chunk_types)
88
+ @chunks = []
89
+ chunk_types.each { |chunk_type| self.apply_type!(chunk_type) }
90
+
91
+ @rendered = @chunks.map { |chunk| chunk.unmask(self) }.compact
92
+ (@chunks - @rendered).each { |chunk| chunk.revert(self) }
93
+ end
94
+
95
+ # Find all the chunks of the given type in this content
96
+ # Each time the type's pattern is matched, create a new
97
+ # chunk for it, and replace the occurance of the chunk
98
+ # in this content with its mask.
99
+ def apply_type!(chunk_type)
100
+ self.gsub!( chunk_type.pattern ) do |match|
101
+ @chunks << chunk_type.new($~)
102
+ @chunks.last.mask(self)
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,83 @@
1
+ $:.unshift(File.dirname(__FILE__) + "/../../libraries/")
2
+
3
+ require "madeleine_service"
4
+ require "web"
5
+ require "page"
6
+ require "author"
7
+
8
+ class WikiService < MadeleineService
9
+ attr_reader :webs, :system
10
+
11
+ def initialize
12
+ @webs, @system = {}, {}
13
+ end
14
+
15
+ def setup?
16
+ !@system.empty?
17
+ end
18
+
19
+ def authenticate(password)
20
+ password == (@system["password"] || "instiki")
21
+ end
22
+
23
+ def setup(password, web_name, web_address)
24
+ @system["password"] = password
25
+ create_web(web_name, web_address)
26
+ end
27
+
28
+ def create_web(name, address, password = nil)
29
+ @webs[address] = Web.new(name, address, password) unless @webs[address]
30
+ end
31
+
32
+ def update_web(old_address, new_address, name, markup, color, additional_style, safe_mode = false,
33
+ password = nil, published = false, brackets_only = false, count_pages = false)
34
+ if old_address != new_address
35
+ @webs[new_address] = @webs[old_address]
36
+ @webs.delete(old_address)
37
+ @webs[new_address].address = new_address
38
+ end
39
+
40
+ web = @webs[new_address]
41
+ web.refresh_revisions if settings_changed?(web, markup, safe_mode, brackets_only)
42
+
43
+ web.name, web.markup, web.color, web.additional_style, web.safe_mode =
44
+ name, markup, color, additional_style, safe_mode
45
+
46
+ web.password, web.published, web.brackets_only, web.count_pages =
47
+ password, published, brackets_only, count_pages
48
+ end
49
+
50
+ def read_page(web_address, page_name)
51
+ web = @webs[web_address]
52
+ web ? web.pages[page_name] : nil
53
+ end
54
+
55
+ def write_page(web_address, page_name, content, written_on, author)
56
+ page = Page.new(@webs[web_address], page_name, content, written_on, author)
57
+ @webs[web_address].add_page(page)
58
+ page
59
+ end
60
+
61
+ def revise_page(web_address, page_name, content, revised_on, author)
62
+ page = read_page(web_address, page_name)
63
+ page.revise(content, revised_on, author)
64
+ page
65
+ end
66
+
67
+ def rollback_page(web_address, page_name, revision_number, created_at, author_id = nil)
68
+ page = read_page(web_address, page_name)
69
+ page.rollback(revision_number, created_at, author_id)
70
+ page
71
+ end
72
+
73
+ def remove_orphaned_pages(web_address)
74
+ @webs[web_address].remove_pages(@webs[web_address].select.orphaned_pages)
75
+ end
76
+
77
+ private
78
+ def settings_changed?(web, markup, safe_mode, brackets_only)
79
+ web.markup != markup ||
80
+ web.safe_mode != safe_mode ||
81
+ web.brackets_only != brackets_only
82
+ end
83
+ end