Pimki 1.0.092

Sign up to get free protection for your applications and to get access to all the features.
Files changed (85) hide show
  1. data/README +158 -0
  2. data/README-PIMKI +87 -0
  3. data/app/controllers/wiki.rb +563 -0
  4. data/app/models/author.rb +4 -0
  5. data/app/models/chunks/category.rb +31 -0
  6. data/app/models/chunks/category_test.rb +21 -0
  7. data/app/models/chunks/chunk.rb +20 -0
  8. data/app/models/chunks/engines.rb +34 -0
  9. data/app/models/chunks/include.rb +29 -0
  10. data/app/models/chunks/literal.rb +19 -0
  11. data/app/models/chunks/match.rb +19 -0
  12. data/app/models/chunks/nowiki.rb +31 -0
  13. data/app/models/chunks/nowiki_test.rb +14 -0
  14. data/app/models/chunks/test.rb +18 -0
  15. data/app/models/chunks/todo.rb +22 -0
  16. data/app/models/chunks/uri.rb +97 -0
  17. data/app/models/chunks/uri_test.rb +92 -0
  18. data/app/models/chunks/wiki.rb +82 -0
  19. data/app/models/chunks/wiki_test.rb +36 -0
  20. data/app/models/page.rb +91 -0
  21. data/app/models/page_lock.rb +24 -0
  22. data/app/models/page_set.rb +73 -0
  23. data/app/models/page_test.rb +76 -0
  24. data/app/models/revision.rb +91 -0
  25. data/app/models/revision_test.rb +252 -0
  26. data/app/models/web.rb +277 -0
  27. data/app/models/web_test.rb +53 -0
  28. data/app/models/wiki_content.rb +113 -0
  29. data/app/models/wiki_service.rb +137 -0
  30. data/app/models/wiki_service_test.rb +15 -0
  31. data/app/models/wiki_words.rb +26 -0
  32. data/app/models/wiki_words_test.rb +12 -0
  33. data/app/views/bottom.rhtml +4 -0
  34. data/app/views/markdown_help.rhtml +16 -0
  35. data/app/views/menu.rhtml +20 -0
  36. data/app/views/navigation.rhtml +26 -0
  37. data/app/views/rdoc_help.rhtml +16 -0
  38. data/app/views/static_style_sheet.rhtml +231 -0
  39. data/app/views/style.rhtml +179 -0
  40. data/app/views/textile_help.rhtml +28 -0
  41. data/app/views/top.rhtml +52 -0
  42. data/app/views/wiki/authors.rhtml +15 -0
  43. data/app/views/wiki/bliki.rhtml +101 -0
  44. data/app/views/wiki/bliki_edit.rhtml +33 -0
  45. data/app/views/wiki/bliki_new.rhtml +61 -0
  46. data/app/views/wiki/bliki_revision.rhtml +51 -0
  47. data/app/views/wiki/edit.rhtml +34 -0
  48. data/app/views/wiki/edit_menu.rhtml +27 -0
  49. data/app/views/wiki/edit_web.rhtml +139 -0
  50. data/app/views/wiki/export.rhtml +14 -0
  51. data/app/views/wiki/feeds.rhtml +10 -0
  52. data/app/views/wiki/list.rhtml +164 -0
  53. data/app/views/wiki/locked.rhtml +14 -0
  54. data/app/views/wiki/login.rhtml +11 -0
  55. data/app/views/wiki/mind.rhtml +39 -0
  56. data/app/views/wiki/new.rhtml +27 -0
  57. data/app/views/wiki/new_system.rhtml +78 -0
  58. data/app/views/wiki/new_web.rhtml +64 -0
  59. data/app/views/wiki/page.rhtml +84 -0
  60. data/app/views/wiki/print.rhtml +16 -0
  61. data/app/views/wiki/published.rhtml +10 -0
  62. data/app/views/wiki/recently_revised.rhtml +31 -0
  63. data/app/views/wiki/revision.rhtml +87 -0
  64. data/app/views/wiki/rss_feed.rhtml +22 -0
  65. data/app/views/wiki/search.rhtml +26 -0
  66. data/app/views/wiki/tex.rhtml +23 -0
  67. data/app/views/wiki/tex_web.rhtml +35 -0
  68. data/app/views/wiki/todo.rhtml +39 -0
  69. data/app/views/wiki/web_list.rhtml +13 -0
  70. data/app/views/wiki_words_help.rhtml +8 -0
  71. data/libraries/action_controller_servlet.rb +177 -0
  72. data/libraries/bluecloth.rb +1127 -0
  73. data/libraries/diff/diff.rb +475 -0
  74. data/libraries/diff/diff_test.rb +80 -0
  75. data/libraries/erb.rb +490 -0
  76. data/libraries/madeleine/automatic.rb +357 -0
  77. data/libraries/madeleine/clock.rb +94 -0
  78. data/libraries/madeleine_service.rb +69 -0
  79. data/libraries/rdocsupport.rb +156 -0
  80. data/libraries/redcloth_for_tex.rb +869 -0
  81. data/libraries/redcloth_for_tex_test.rb +41 -0
  82. data/libraries/view_helper.rb +33 -0
  83. data/libraries/web_controller_server.rb +95 -0
  84. data/pimki.rb +97 -0
  85. metadata +169 -0
@@ -0,0 +1,82 @@
1
+ require 'wiki_words'
2
+ require 'chunks/chunk'
3
+ require 'cgi'
4
+
5
+ # Contains all the methods for finding and replacing wiki related
6
+ # links.
7
+ module WikiChunk
8
+ include Chunk
9
+
10
+ # A wiki link is the top-level class for anything that refers to
11
+ # another wiki page.
12
+ class WikiLink < Chunk::Abstract
13
+ # By default, no escaped text
14
+ def escaped_text() nil end
15
+
16
+ # Delimit the link text with markers to replace later unless
17
+ # the word is escaped. In that case, just return the link text
18
+ def mask(content) escaped_text || pre_mask + link_text + post_mask end
19
+
20
+ def regexp() Regexp.new(pre_mask + '(.*)?' + post_mask) end
21
+
22
+ def revert(content) content.sub!(regexp, text) end
23
+
24
+ # Do not keep this chunk if it is escaped.
25
+ # Otherwise, pass the link procedure a page_name and link_text and
26
+ # get back a string of HTML to replace the mask with.
27
+ def unmask(content)
28
+ return nil if escaped_text
29
+ return self if content.sub!(regexp) { |match| content.page_link(page_name, $1) }
30
+ end
31
+ end
32
+
33
+ # This chunk matches a WikiWord. WikiWords can be escaped
34
+ # by prepending a '\'. When this is the case, the +escaped_text+
35
+ # method will return the WikiWord instead of the usual +nil+.
36
+ # The +page_name+ method returns the matched WikiWord.
37
+ class Word < WikiLink
38
+ def self.pattern
39
+ Regexp.new('(\\\\)?(' + WikiWords::WIKI_WORD_PATTERN + ')\b', 0, "utf-8")
40
+ end
41
+
42
+ attr_reader :page_name
43
+
44
+ def initialize(match_data)
45
+ super(match_data)
46
+ @escape = match_data[1]
47
+ @page_name = match_data[2]
48
+ end
49
+
50
+ def escaped_text() (@escape.nil? ? nil : page_name) end
51
+ def link_text() WikiWords.separate(page_name) end
52
+ end
53
+
54
+ # This chunk handles [[bracketted wiki words]] and
55
+ # [[AliasedWords|aliased wiki words]]. The first part of an
56
+ # aliased wiki word must be a WikiWord. If the WikiWord
57
+ # is aliased, the +link_text+ field will contain the
58
+ # alias, otherwise +link_text+ will contain the entire
59
+ # contents within the double brackets.
60
+ #
61
+ # NOTE: This chunk must be tested before WikiWord since
62
+ # a WikiWords can be a substring of a WikiLink.
63
+ class Link < WikiLink
64
+ def self.pattern() /\[\[([^\]]+)\]\]/ end
65
+ ALIASED_LINK_PATTERN= Regexp.new('^(.*)?\|(.*)$', 0, "utf-8")
66
+
67
+ attr_reader :page_name, :link_text
68
+
69
+ def initialize(match_data)
70
+ super(match_data)
71
+
72
+ # If the like is aliased, set the page name to the first bit
73
+ # and the link text to the second, otherwise set both to the
74
+ # contents of the double brackets.
75
+ if match_data[1] =~ ALIASED_LINK_PATTERN
76
+ @page_name, @link_text = $1, $2
77
+ else
78
+ @page_name, @link_text = match_data[1], match_data[1]
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,36 @@
1
+ require 'chunks/wiki'
2
+ require 'chunks/match'
3
+ require 'test/unit'
4
+
5
+ class WikiTest < Test::Unit::TestCase
6
+ include ChunkMatch
7
+
8
+ def test_simple
9
+ match(WikiChunk::Word, 'This is a WikiWord okay?', :page_name => 'WikiWord')
10
+ end
11
+
12
+ def test_escaped
13
+ match(WikiChunk::Word, 'Do not link to an \EscapedWord',
14
+ :page_name => 'EscapedWord', :escaped_text => 'EscapedWord'
15
+ )
16
+ end
17
+
18
+ def test_simple_brackets
19
+ match(WikiChunk::Link, 'This is a [[bracketted link]]',
20
+ :page_name => 'bracketted link', :escaped_text => nil
21
+ )
22
+ end
23
+
24
+ def test_complex_brackets
25
+ match(WikiChunk::Link, 'This is a tricky link [[Sperberg-McQueen]]',
26
+ :page_name => 'Sperberg-McQueen', :escaped_text => nil
27
+ )
28
+ end
29
+
30
+ # MDR: I'm not sure how to deal with this case just yet...
31
+ #
32
+ # def test_textile_link
33
+ # assert_no_match(WikiChunk::Word.pattern, '"Here is a special link":SpecialLink')
34
+ # end
35
+
36
+ end
@@ -0,0 +1,91 @@
1
+ require "date"
2
+ require "page_lock"
3
+ require "revision"
4
+ require "wiki_words"
5
+ require "chunks/wiki"
6
+
7
+ class Page
8
+ include PageLock
9
+
10
+ CONTINOUS_REVISION_PERIOD = 30 * 60 # 30 minutes
11
+
12
+ attr_accessor :name, :last_visited, :viewed
13
+ attr_reader :revisions, :web
14
+
15
+ def initialize(web, name, content, created_at, author)
16
+ @web, @name, @revisions = web, name, []
17
+ revise(content, created_at, author)
18
+ end
19
+
20
+ def revise(content, created_at, author)
21
+ if !@revisions.empty? && continous_revision?(created_at, author)
22
+ @revisions.last.created_at = Time.now
23
+ @revisions.last.content = content
24
+ @revisions.last.clear_display_cache
25
+ else
26
+ @revisions << Revision.new(self, @revisions.length, content, created_at, author)
27
+ end
28
+
29
+ web.refresh_pages_with_references(name) if @revisions.length == 1
30
+ end
31
+
32
+ def rollback(revision_number, created_at, author_ip = nil)
33
+ roll_back_revision = @revisions[revision_number].dup
34
+ revise(roll_back_revision.content, created_at, Author.new(roll_back_revision.author, author_ip))
35
+ end
36
+
37
+ def revisions?
38
+ revisions.length > 1
39
+ end
40
+
41
+ def revised_on
42
+ created_on
43
+ end
44
+
45
+ def pretty_revised_on
46
+ DateTime.new(revised_on.year, revised_on.mon, revised_on.day).strftime "%B %e, %Y"
47
+ end
48
+
49
+ def in_category?(cat)
50
+ cat.nil? || cat.empty? || categories.include?(cat)
51
+ end
52
+
53
+ def categories
54
+ display_content.find_chunks(Category).map { |cat| cat.list }.flatten
55
+ end
56
+
57
+ def authors
58
+ revisions.collect { |rev| rev.author }
59
+ end
60
+
61
+ def references
62
+ web.select.pages_that_reference(name)
63
+ end
64
+
65
+ # Returns the original wiki-word name as separate words, so "MyPage" becomes "My Page".
66
+ def plain_name
67
+ WikiWords.separate(name, web.brackets_only)
68
+ end
69
+
70
+ def link(options = {})
71
+ web.make_link(name, nil, options)
72
+ end
73
+
74
+ def author_link(options = {})
75
+ web.make_link(author, nil, options)
76
+ end
77
+
78
+ def last_visited() @last_visited || revisions.last.created_at end
79
+ def viewed() @viewed ||= 0 end
80
+
81
+
82
+ private
83
+ def continous_revision?(created_at, author)
84
+ @revisions.last.author == author && @revisions.last.created_at + CONTINOUS_REVISION_PERIOD > created_at
85
+ end
86
+
87
+ # Forward method calls to the current revision, so the page responds to all revision calls
88
+ def method_missing(method_symbol)
89
+ revisions.last.send(method_symbol)
90
+ end
91
+ end
@@ -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,73 @@
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 by_last_visited #{{{
27
+ self.sort_by { |page| [page.last_visited] }.reverse
28
+ end #}}}
29
+
30
+ def by_most_viewed #{{{
31
+ self.sort_by { |page| [page.viewed] }.reverse
32
+ end #}}}
33
+
34
+
35
+ def pages_that_reference(page_name)
36
+ self.select { |page| page.wiki_words.include?(page_name) }
37
+ end
38
+
39
+ def pages_authored_by(author)
40
+ self.select { |page| page.authors.include?(author) }
41
+ end
42
+
43
+ def characters
44
+ self.inject(0) { |chars,page| chars += page.content.size }
45
+ end
46
+
47
+ # Returns all the orphaned pages in this page set. That is,
48
+ # pages in this set for which there is no reference in the web.
49
+ # The HomePage and author pages are always assumed to have
50
+ # references and so cannot be orphans
51
+ def orphaned_pages
52
+ references = web.select.wiki_words + ["HomePage"] + web.select.authors
53
+ self.reject { |page| references.include?(page.name) }
54
+ end
55
+
56
+ # Returns all the wiki words in this page set for which
57
+ # there are no pages in this page set's web
58
+ def wanted_pages
59
+ wiki_words - web.select.names
60
+ end
61
+
62
+ def names
63
+ self.map { |page| page.name }
64
+ end
65
+
66
+ def wiki_words
67
+ self.inject([]) { |wiki_words, page| wiki_words << page.wiki_words }.flatten.uniq
68
+ end
69
+
70
+ def authors
71
+ self.inject([]) { |authors, page| authors << page.authors }.flatten.uniq.sort
72
+ end
73
+ end
@@ -0,0 +1,76 @@
1
+ require "test/unit"
2
+ require "web"
3
+ require "page"
4
+
5
+ class MockWeb < Web
6
+ def initialize() super('test','test') end
7
+ def [](wiki_word) %w( MyWay ThatWay SmartEngine ).include?(wiki_word) end
8
+ def refresh_pages_with_references(name) end
9
+ end
10
+
11
+ class PageTest < Test::Unit::TestCase
12
+ def setup
13
+ @page = Page.new(
14
+ MockWeb.new,
15
+ "FirstPage",
16
+ "HisWay would be MyWay in kinda ThatWay in HisWay though MyWay \\OverThere -- see SmartEngine in that SmartEngineGUI",
17
+ Time.local(2004, 4, 4, 16, 50),
18
+ "DavidHeinemeierHansson"
19
+ )
20
+ end
21
+
22
+ def test_basics
23
+ assert_equal "First Page", @page.plain_name
24
+ assert_equal "April 4, 2004", @page.pretty_revised_on
25
+ end
26
+
27
+ def test_locking
28
+ assert !@page.locked?(Time.local(2004, 4, 4, 16, 50))
29
+
30
+ @page.lock(Time.local(2004, 4, 4, 16, 30), "DavidHeinemeierHansson")
31
+
32
+ assert @page.locked?(Time.local(2004, 4, 4, 16, 50))
33
+ assert !@page.locked?(Time.local(2004, 4, 4, 17, 1))
34
+
35
+ @page.unlock
36
+
37
+ assert !@page.locked?(Time.local(2004, 4, 4, 16, 50))
38
+ end
39
+
40
+ def test_locking_duration
41
+ @page.lock(Time.local(2004, 4, 4, 16, 30), "DavidHeinemeierHansson")
42
+
43
+ assert_equal 15, @page.lock_duration(Time.local(2004, 4, 4, 16, 45))
44
+ end
45
+
46
+ def test_revision
47
+ @page.revise("HisWay would be MyWay in kinda lame", Time.local(2004, 4, 4, 16, 55), "MarianneSyhler")
48
+ assert_equal 2, @page.revisions.length, "Should have two revisions"
49
+ assert_equal "MarianneSyhler", @page.author, "Mary should be the author now"
50
+ assert_equal "DavidHeinemeierHansson", @page.revisions.first.author, "David was the first author"
51
+ end
52
+
53
+ def test_rollback
54
+ @page.revise("spot two", Time.now, "David")
55
+ @page.revise("spot three", Time.now + 2000, "David")
56
+ assert_equal 3, @page.revisions.length, "Should have three revisions"
57
+ @page.rollback(1, Time.now)
58
+ assert_equal "spot two", @page.content
59
+ end
60
+
61
+ def test_continous_revision
62
+ @page.revise("HisWay would be MyWay in kinda lame", Time.local(2004, 4, 4, 16, 55), "MarianneSyhler")
63
+ assert_equal 2, @page.revisions.length
64
+
65
+ @page.revise("HisWay would be MyWay in kinda update", Time.local(2004, 4, 4, 16, 57), "MarianneSyhler")
66
+ assert_equal 2, @page.revisions.length
67
+ assert_equal "HisWay would be MyWay in kinda update", @page.revisions.last.content
68
+
69
+ @page.revise("HisWay would be MyWay in the house", Time.local(2004, 4, 4, 16, 58), "DavidHeinemeierHansson")
70
+ assert_equal 3, @page.revisions.length
71
+ assert_equal "HisWay would be MyWay in the house", @page.revisions.last.content
72
+
73
+ @page.revise("HisWay would be MyWay in my way", Time.local(2004, 4, 4, 17, 30), "DavidHeinemeierHansson")
74
+ assert_equal 4, @page.revisions.length
75
+ end
76
+ end
@@ -0,0 +1,91 @@
1
+
2
+ require "diff/diff"
3
+
4
+ require "wiki_content"
5
+ require "chunks/wiki"
6
+
7
+ require "date"
8
+ require "author"
9
+ require "page"
10
+
11
+ class Revision
12
+ attr_accessor :page, :number, :content, :created_at, :author
13
+
14
+ def initialize(page, number, content, created_at, author)
15
+ @page, @number, @created_at, @author = page, number, created_at, author
16
+ self.content = content
17
+ end
18
+
19
+ # Ensure that the wiki content is parsed when ever it is updated.
20
+ def content=(content)
21
+ @content = content
22
+ end
23
+
24
+ def created_on
25
+ Date.new(@created_at.year, @created_at.mon, @created_at.day)
26
+ end
27
+
28
+ def pretty_created_at
29
+ # Must use DateTime because Time doesn't support %e on at least some platforms
30
+ DateTime.new(
31
+ @created_at.year, @created_at.mon, @created_at.day, @created_at.hour, @created_at.min
32
+ ).strftime "%B %e, %Y %H:%M"
33
+ end
34
+
35
+ def next_revision
36
+ page.revisions[number + 1]
37
+ end
38
+
39
+ def previous_revision
40
+ number - 1 >= 0 && page.revisions[number - 1]
41
+ end
42
+
43
+ # Returns an array of all the WikiWords present in the content of this revision.
44
+ def wiki_words
45
+ unless @wiki_words_cache
46
+ wiki_chunks = display_content.find_chunks(WikiChunk::WikiLink)
47
+ @wiki_words_cache = wiki_chunks.map { |c| ( c.escaped_text ? nil : c.page_name ) }.compact.uniq
48
+ end
49
+ @wiki_words_cache
50
+ end
51
+
52
+ # Returns an array of all the WikiWords present in the content of this revision.
53
+ # that already exists as a page in the web.
54
+ def existing_pages
55
+ wiki_words.select { |wiki_word| page.web.pages[wiki_word] }
56
+ end
57
+
58
+ # Returns an array of all the WikiWords present in the content of this revision
59
+ # that *doesn't* already exists as a page in the web.
60
+ def unexisting_pages
61
+ wiki_words - existing_pages
62
+ end
63
+
64
+ # Explicit check for new type of display cache with find_chunks method.
65
+ # Ensures new version works with older snapshots.
66
+ def display_content
67
+ unless @display_cache && @display_cache.respond_to?(:find_chunks)
68
+ #$stderr << "Rendering: #{page.name}\n"
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
+
91
+ end