instiki 0.9.2

Sign up to get free protection for your applications and to get access to all the features.
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