Refactoring of chunks and rendering [Denis Mertz]
This commit is contained in:
parent
a87ef98aef
commit
78bad46419
19 changed files with 365 additions and 180 deletions
|
@ -13,22 +13,19 @@ class Category < Chunk::Abstract
|
||||||
|
|
||||||
attr_reader :hidden, :list
|
attr_reader :hidden, :list
|
||||||
|
|
||||||
def initialize(match_data)
|
def initialize(match_data, content)
|
||||||
super(match_data)
|
super(match_data, content)
|
||||||
@hidden = match_data[1]
|
@hidden = match_data[1]
|
||||||
@list = match_data[2].split(',').map { |c| c.strip }
|
@list = match_data[2].split(',').map { |c| c.strip }
|
||||||
|
@unmask_text = ''
|
||||||
|
if @hidden
|
||||||
|
@unmask_text = ''
|
||||||
|
else
|
||||||
|
category_urls = @list.map { |category| url(category) }.join(', ')
|
||||||
|
@unmask_text = '<div class="property"> category: ' + category_urls + '</div>'
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# If the chunk is hidden, erase the mask and return this chunk
|
|
||||||
# otherwise, surround it with a 'div' block.
|
|
||||||
def unmask(content)
|
|
||||||
return '' if hidden
|
|
||||||
|
|
||||||
category_urls = @list.map{|category| url(category) }.join(', ')
|
|
||||||
replacement = '<div class="property"> category: ' + category_urls + '</div>'
|
|
||||||
self if content.sub!(mask(content), replacement)
|
|
||||||
end
|
|
||||||
|
|
||||||
# TODO move presentation of page metadata to controller/view
|
# TODO move presentation of page metadata to controller/view
|
||||||
def url(category)
|
def url(category)
|
||||||
%{<a class="category_link" href="../list/?category=#{category}">#{category}</a>}
|
%{<a class="category_link" href="../list/?category=#{category}">#{category}</a>}
|
||||||
|
|
|
@ -6,35 +6,74 @@ require 'uri/common'
|
||||||
# +pattern+ that states what sort of text it matches.
|
# +pattern+ that states what sort of text it matches.
|
||||||
# Chunks are initalized by passing in the result of a
|
# Chunks are initalized by passing in the result of a
|
||||||
# match by its pattern.
|
# match by its pattern.
|
||||||
|
|
||||||
module Chunk
|
module Chunk
|
||||||
class Abstract
|
class Abstract
|
||||||
attr_reader :text
|
|
||||||
|
|
||||||
def initialize(match_data) @text = match_data[0] end
|
# automatically construct the array of derivatives of Chunk::Abstract
|
||||||
|
@derivatives = []
|
||||||
|
|
||||||
|
class << self
|
||||||
|
attr_reader :derivatives
|
||||||
|
end
|
||||||
|
|
||||||
|
def self::inherited( klass )
|
||||||
|
Abstract::derivatives << klass
|
||||||
|
end
|
||||||
|
|
||||||
|
# the class name part of the mask strings
|
||||||
|
def self.mask_string
|
||||||
|
self.to_s.delete(':').downcase
|
||||||
|
end
|
||||||
|
|
||||||
|
# a regexp that matches all chunk_types masks
|
||||||
|
def Abstract::mask_re(chunk_types)
|
||||||
|
tmp = chunk_types.map{|klass| klass.mask_string}.join("|")
|
||||||
|
Regexp.new("chunk(\\d+)(#{tmp})chunk")
|
||||||
|
end
|
||||||
|
|
||||||
|
attr_reader :text, :unmask_text, :unmask_mode
|
||||||
|
|
||||||
|
def initialize(match_data, content)
|
||||||
|
@text = match_data[0]
|
||||||
|
@content = content
|
||||||
|
@unmask_mode = :normal
|
||||||
|
end
|
||||||
|
|
||||||
# Find all the chunks of the given type in content
|
# Find all the chunks of the given type in content
|
||||||
# Each time the pattern is matched, create a new
|
# Each time the pattern is matched, create a new
|
||||||
# chunk for it, and replace the occurance of the chunk
|
# chunk for it, and replace the occurance of the chunk
|
||||||
# in this content with its mask.
|
# in this content with its mask.
|
||||||
def self.apply_to(content)
|
def self.apply_to(content)
|
||||||
content.gsub!( self.pattern ) do |match|
|
content.gsub!( self.pattern ) do |match|
|
||||||
new_chunk = self.new($~)
|
new_chunk = self.new($~, content)
|
||||||
content.chunks << new_chunk
|
content.add_chunk(new_chunk)
|
||||||
new_chunk.mask(content)
|
new_chunk.mask
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def mask(content)
|
def mask
|
||||||
"chunk#{self.object_id}#{self.class.to_s.delete(':').downcase}chunk"
|
"chunk#{self.object_id}#{self.class.mask_string}chunk"
|
||||||
end
|
end
|
||||||
|
|
||||||
def revert(content)
|
def unmask
|
||||||
content.sub!( Regexp.new(mask(content)), text )
|
@content.sub!(mask, @unmask_text)
|
||||||
end
|
end
|
||||||
|
|
||||||
def unmask(content)
|
def rendered?
|
||||||
self if revert(content)
|
@unmask_mode == :normal
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def escaped?
|
||||||
|
@unmask_mode == :escape
|
||||||
|
end
|
||||||
|
|
||||||
|
def revert
|
||||||
|
@content.sub!(mask, @text)
|
||||||
|
# unregister
|
||||||
|
@content.delete_chunk(self)
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -13,38 +13,33 @@ module Engines
|
||||||
# Create a new chunk for the whole content and replace it with its mask.
|
# Create a new chunk for the whole content and replace it with its mask.
|
||||||
def self.apply_to(content)
|
def self.apply_to(content)
|
||||||
new_chunk = self.new(content)
|
new_chunk = self.new(content)
|
||||||
content.chunks << new_chunk
|
content.replace(new_chunk.mask)
|
||||||
content.replace(new_chunk.mask(content))
|
|
||||||
end
|
|
||||||
|
|
||||||
def unmask(content)
|
|
||||||
self
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
# Never create engines by constructor - use apply_to instead
|
# Never create engines by constructor - use apply_to instead
|
||||||
def initialize(text)
|
def initialize(content)
|
||||||
@text = text
|
@content = content
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
class Textile < AbstractEngine
|
class Textile < AbstractEngine
|
||||||
def mask(content)
|
def mask
|
||||||
RedCloth.new(text,content.options[:engine_opts]).to_html
|
RedCloth.new(@content, @content.options[:engine_opts]).to_html
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class Markdown < AbstractEngine
|
class Markdown < AbstractEngine
|
||||||
def mask(content)
|
def mask
|
||||||
RedCloth.new(text,content.options[:engine_opts]).to_html
|
RedCloth.new(@content, @content.options[:engine_opts]).to_html
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class RDoc < AbstractEngine
|
class RDoc < AbstractEngine
|
||||||
def mask(content)
|
def mask
|
||||||
RDocSupport::RDocFormatter.new(text).to_html
|
RDocSupport::RDocFormatter.new(@content).to_html
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -2,29 +2,40 @@ require 'chunks/wiki'
|
||||||
|
|
||||||
# Includes the contents of another page for rendering.
|
# Includes the contents of another page for rendering.
|
||||||
# The include command looks like this: "[[!include PageName]]".
|
# The include command looks like this: "[[!include PageName]]".
|
||||||
# It is a WikiLink since it refers to another page (PageName)
|
# It is a WikiReference since it refers to another page (PageName)
|
||||||
# and the wiki content using this command must be notified
|
# and the wiki content using this command must be notified
|
||||||
# of changes to that page.
|
# of changes to that page.
|
||||||
# If the included page could not be found, a warning is displayed.
|
# If the included page could not be found, a warning is displayed.
|
||||||
class Include < WikiChunk::WikiLink
|
|
||||||
INCLUDE_PATTERN = /^\[\[!include(.*)\]\]\s*$/i
|
class Include < WikiChunk::WikiReference
|
||||||
|
|
||||||
|
INCLUDE_PATTERN = /\[\[!include(.*)\]\]\s*/i
|
||||||
def self.pattern() INCLUDE_PATTERN end
|
def self.pattern() INCLUDE_PATTERN end
|
||||||
|
|
||||||
attr_reader :page_name
|
|
||||||
|
|
||||||
def initialize(match_data)
|
def initialize(match_data, content)
|
||||||
super(match_data)
|
super
|
||||||
@page_name = match_data[1].strip
|
@page_name = match_data[1].strip
|
||||||
|
@unmask_text = get_unmask_text_avoiding_recursion_loops
|
||||||
end
|
end
|
||||||
|
|
||||||
# This replaces the [[!include PageName]] text with
|
private
|
||||||
# the contents of PageName if it exists. Otherwise
|
|
||||||
# a warning is displayed.
|
def get_unmask_text_avoiding_recursion_loops
|
||||||
def mask(content)
|
if refpage then
|
||||||
page = content.web.pages[page_name]
|
if refpage.wiki_includes.include?(@content.page_name)
|
||||||
(page ? page.content : "<em>Could not include #{page_name}</em>")
|
# this will break the recursion
|
||||||
|
@content.delete_chunk(self)
|
||||||
|
refpage.clear_display_cache
|
||||||
|
return "<em>Recursive include detected; #{@page_name} --> #{@content.page_name} " +
|
||||||
|
"--> #{@page_name}</em>\n"
|
||||||
|
else
|
||||||
|
@content.merge_chunks(refpage.display_content)
|
||||||
|
return refpage.display_content.pre_rendered
|
||||||
|
end
|
||||||
|
else
|
||||||
|
return "<em>Could not include #{@page_name}</em>\n"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Keep this chunk regardless of what happens.
|
|
||||||
def unmask(content) self end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,15 +5,25 @@ require 'chunks/chunk'
|
||||||
# occuring within literal areas such as <code> and <pre> blocks
|
# occuring within literal areas such as <code> and <pre> blocks
|
||||||
# and within HTML tags.
|
# and within HTML tags.
|
||||||
module Literal
|
module Literal
|
||||||
|
|
||||||
|
class AbstractLiteral < Chunk::Abstract
|
||||||
|
|
||||||
|
def initialize(match_data, content)
|
||||||
|
super
|
||||||
|
@unmask_text = @text
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
# A literal chunk that protects 'code' and 'pre' tags from wiki rendering.
|
# A literal chunk that protects 'code' and 'pre' tags from wiki rendering.
|
||||||
class Pre < Chunk::Abstract
|
class Pre < AbstractLiteral
|
||||||
PRE_BLOCKS = "a|pre|code"
|
PRE_BLOCKS = "a|pre|code"
|
||||||
PRE_PATTERN = Regexp.new('<('+PRE_BLOCKS+')\b[^>]*?>.*?</\1>', Regexp::MULTILINE)
|
PRE_PATTERN = Regexp.new('<('+PRE_BLOCKS+')\b[^>]*?>.*?</\1>', Regexp::MULTILINE)
|
||||||
def self.pattern() PRE_PATTERN end
|
def self.pattern() PRE_PATTERN end
|
||||||
end
|
end
|
||||||
|
|
||||||
# A literal chunk that protects HTML tags from wiki rendering.
|
# A literal chunk that protects HTML tags from wiki rendering.
|
||||||
class Tags < Chunk::Abstract
|
class Tags < AbstractLiteral
|
||||||
TAGS = "a|img|em|strong|div|span|table|td|th|ul|ol|li|dl|dt|dd"
|
TAGS = "a|img|em|strong|div|span|table|td|th|ul|ol|li|dl|dt|dd"
|
||||||
TAGS_PATTERN = Regexp.new('<(?:'+TAGS+')[^>]*?>', Regexp::MULTILINE)
|
TAGS_PATTERN = Regexp.new('<(?:'+TAGS+')[^>]*?>', Regexp::MULTILINE)
|
||||||
def self.pattern() TAGS_PATTERN end
|
def self.pattern() TAGS_PATTERN end
|
||||||
|
|
|
@ -14,18 +14,15 @@ require 'chunks/chunk'
|
||||||
# Author: Mark Reid <mark at threewordslong dot com>
|
# Author: Mark Reid <mark at threewordslong dot com>
|
||||||
# Created: 8th June 2004
|
# Created: 8th June 2004
|
||||||
class NoWiki < Chunk::Abstract
|
class NoWiki < Chunk::Abstract
|
||||||
|
|
||||||
NOWIKI_PATTERN = Regexp.new('<nowiki>(.*?)</nowiki>')
|
NOWIKI_PATTERN = Regexp.new('<nowiki>(.*?)</nowiki>')
|
||||||
def self.pattern() NOWIKI_PATTERN end
|
def self.pattern() NOWIKI_PATTERN end
|
||||||
|
|
||||||
attr_reader :plain_text
|
attr_reader :plain_text
|
||||||
|
|
||||||
def initialize(match_data)
|
def initialize(match_data, content)
|
||||||
super(match_data)
|
super
|
||||||
@plain_text = match_data[1]
|
@plain_text = @unmask_text = match_data[1]
|
||||||
end
|
end
|
||||||
|
|
||||||
# The nowiki content is not unmasked. This means the chunk will be reverted
|
|
||||||
# using the plain text.
|
|
||||||
def unmask(content) nil end
|
|
||||||
def revert(content) content.sub!(mask(content), plain_text) end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -86,23 +86,24 @@ class URIChunk < Chunk::Abstract
|
||||||
|
|
||||||
def self.apply_to(content)
|
def self.apply_to(content)
|
||||||
content.gsub!( self.pattern ) do |matched_text|
|
content.gsub!( self.pattern ) do |matched_text|
|
||||||
chunk = self.new($~)
|
chunk = self.new($~, content)
|
||||||
if chunk.avoid_autolinking?
|
if chunk.avoid_autolinking?
|
||||||
# do not substitute
|
# do not substitute nor register the chunk
|
||||||
matched_text
|
matched_text
|
||||||
else
|
else
|
||||||
content.chunks << chunk
|
content.add_chunk(chunk)
|
||||||
chunk.mask(content)
|
chunk.mask
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def initialize(match_data)
|
def initialize(match_data, content)
|
||||||
super(match_data)
|
super
|
||||||
@link_text = match_data[0]
|
@link_text = match_data[0]
|
||||||
@suspicious_preceding_character = match_data[1]
|
@suspicious_preceding_character = match_data[1]
|
||||||
@original_scheme, @user, @host, @port, @path, @query, @fragment = match_data[2..-1]
|
@original_scheme, @user, @host, @port, @path, @query, @fragment = match_data[2..-1]
|
||||||
treat_trailing_character
|
treat_trailing_character
|
||||||
|
@unmask_text = "<a href=\"#{uri}\">#{link_text}</a>"
|
||||||
end
|
end
|
||||||
|
|
||||||
def avoid_autolinking?
|
def avoid_autolinking?
|
||||||
|
@ -123,18 +124,6 @@ class URIChunk < Chunk::Abstract
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# If the text should be escaped then don't keep this chunk.
|
|
||||||
# Otherwise only keep this chunk if it was substituted back into the
|
|
||||||
# content.
|
|
||||||
def unmask(content)
|
|
||||||
return nil if escaped_text
|
|
||||||
return self if content.sub!(mask(content), "<a href=\"#{uri}\">#{link_text}</a>")
|
|
||||||
end
|
|
||||||
|
|
||||||
# If there is no hostname in the URI, do not render it
|
|
||||||
# It's probably only contains the scheme, eg 'something:'
|
|
||||||
def escaped_text() ( host.nil? ? @uri : nil ) end
|
|
||||||
|
|
||||||
def scheme
|
def scheme
|
||||||
@original_scheme or (@user ? 'mailto' : 'http')
|
@original_scheme or (@user ? 'mailto' : 'http')
|
||||||
end
|
end
|
||||||
|
|
|
@ -7,62 +7,53 @@ require 'cgi'
|
||||||
module WikiChunk
|
module WikiChunk
|
||||||
include Chunk
|
include Chunk
|
||||||
|
|
||||||
# A wiki link is the top-level class for anything that refers to
|
# A wiki reference is the top-level class for anything that refers to
|
||||||
# another wiki page.
|
# another wiki page.
|
||||||
class WikiLink < Chunk::Abstract
|
class WikiReference < Chunk::Abstract
|
||||||
|
|
||||||
attr_reader :page_name, :link_text, :link_type
|
# Name of the referenced page
|
||||||
|
attr_reader :page_name
|
||||||
|
|
||||||
|
# the referenced page
|
||||||
|
def refpage
|
||||||
|
@content.web.pages[@page_name]
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
def initialize(*args)
|
# A wiki link is the top-level class for links that refers to
|
||||||
|
# another wiki page.
|
||||||
|
class WikiLink < WikiReference
|
||||||
|
|
||||||
|
attr_reader :link_text, :link_type
|
||||||
|
|
||||||
|
def initialize(match_data, content)
|
||||||
super
|
super
|
||||||
@link_type = :show
|
@link_type = :show
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.apply_to(content)
|
def self.apply_to(content)
|
||||||
content.gsub!( self.pattern ) do |matched_text|
|
content.gsub!( self.pattern ) do |matched_text|
|
||||||
chunk = self.new($~)
|
chunk = self.new($~, content)
|
||||||
if chunk.textile_url?
|
if chunk.textile_url?
|
||||||
# do not substitute
|
# do not substitute
|
||||||
matched_text
|
matched_text
|
||||||
else
|
else
|
||||||
content.chunks << chunk
|
content.add_chunk(chunk)
|
||||||
chunk.mask(content)
|
chunk.mask
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# the referenced page
|
||||||
|
def refpage
|
||||||
|
@content.web.pages[@page_name]
|
||||||
|
end
|
||||||
|
|
||||||
def textile_url?
|
def textile_url?
|
||||||
not @textile_link_suffix.nil?
|
not @textile_link_suffix.nil?
|
||||||
end
|
end
|
||||||
|
|
||||||
# By default, no escaped text
|
|
||||||
def escaped_text() nil end
|
|
||||||
|
|
||||||
# Replace link with a mask, but if the word is escaped, then don't replace it
|
|
||||||
def mask(content)
|
|
||||||
escaped_text || super(content)
|
|
||||||
end
|
|
||||||
|
|
||||||
def revert(content) content.sub!(mask(content), text) end
|
|
||||||
|
|
||||||
# Do not keep this chunk if it is escaped.
|
|
||||||
# Otherwise, pass the link procedure a page_name and link_text and
|
|
||||||
# get back a string of HTML to replace the mask with.
|
|
||||||
def unmask(content)
|
|
||||||
if escaped_text
|
|
||||||
return self
|
|
||||||
else
|
|
||||||
chunk_found = content.sub!(mask(content)) do |match|
|
|
||||||
content.page_link(page_name, link_text, link_type)
|
|
||||||
end
|
|
||||||
if chunk_found
|
|
||||||
return self
|
|
||||||
else
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# This chunk matches a WikiWord. WikiWords can be escaped
|
# This chunk matches a WikiWord. WikiWords can be escaped
|
||||||
|
@ -70,6 +61,9 @@ module WikiChunk
|
||||||
# method will return the WikiWord instead of the usual +nil+.
|
# method will return the WikiWord instead of the usual +nil+.
|
||||||
# The +page_name+ method returns the matched WikiWord.
|
# The +page_name+ method returns the matched WikiWord.
|
||||||
class Word < WikiLink
|
class Word < WikiLink
|
||||||
|
|
||||||
|
attr_reader :escaped_text
|
||||||
|
|
||||||
unless defined? WIKI_WORD
|
unless defined? WIKI_WORD
|
||||||
WIKI_WORD = Regexp.new('(":)?(\\\\)?(' + WikiWords::WIKI_WORD_PATTERN + ')\b', 0, "utf-8")
|
WIKI_WORD = Regexp.new('(":)?(\\\\)?(' + WikiWords::WIKI_WORD_PATTERN + ')\b', 0, "utf-8")
|
||||||
end
|
end
|
||||||
|
@ -78,15 +72,19 @@ module WikiChunk
|
||||||
WIKI_WORD
|
WIKI_WORD
|
||||||
end
|
end
|
||||||
|
|
||||||
def initialize(match_data)
|
def initialize(match_data, content)
|
||||||
super(match_data)
|
super
|
||||||
@textile_link_suffix, @escape, @page_name = match_data[1..3]
|
@textile_link_suffix, @escape, @page_name = match_data[1..3]
|
||||||
|
if @escape
|
||||||
|
@unmask_mode = :escape
|
||||||
|
@escaped_text = @page_name
|
||||||
|
else
|
||||||
|
@escaped_text = nil
|
||||||
|
end
|
||||||
|
@link_text = WikiWords.separate(@page_name)
|
||||||
|
@unmask_text = (@escaped_text || @content.page_link(@page_name, @link_text, @link_type))
|
||||||
end
|
end
|
||||||
|
|
||||||
def escaped_text
|
|
||||||
page_name unless @escape.nil?
|
|
||||||
end
|
|
||||||
def link_text() WikiWords.separate(page_name) end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# This chunk handles [[bracketted wiki words]] and
|
# This chunk handles [[bracketted wiki words]] and
|
||||||
|
@ -108,12 +106,13 @@ module WikiChunk
|
||||||
|
|
||||||
def self.pattern() WIKI_LINK end
|
def self.pattern() WIKI_LINK end
|
||||||
|
|
||||||
def initialize(match_data)
|
def initialize(match_data, content)
|
||||||
super(match_data)
|
super
|
||||||
@textile_link_suffix, @page_name = match_data[1..2]
|
@textile_link_suffix, @page_name = match_data[1..2]
|
||||||
@link_text = @page_name
|
@link_text = @page_name
|
||||||
separate_link_type
|
separate_link_type
|
||||||
separate_alias
|
separate_alias
|
||||||
|
@unmask_text = @content.page_link(@page_name, @link_text, @link_type)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
|
@ -72,6 +72,14 @@ class Page
|
||||||
@web.select.pages_that_reference(name)
|
@web.select.pages_that_reference(name)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def linked_from
|
||||||
|
@web.select.pages_that_link_to(name)
|
||||||
|
end
|
||||||
|
|
||||||
|
def included_from
|
||||||
|
@web.select.pages_that_include(name)
|
||||||
|
end
|
||||||
|
|
||||||
# Returns the original wiki-word name as separate words, so "MyPage" becomes "My Page".
|
# Returns the original wiki-word name as separate words, so "MyPage" becomes "My Page".
|
||||||
def plain_name
|
def plain_name
|
||||||
@web.brackets_only ? name : WikiWords.separate(name)
|
@web.brackets_only ? name : WikiWords.separate(name)
|
||||||
|
|
|
@ -32,9 +32,17 @@ class PageSet < Array
|
||||||
end
|
end
|
||||||
|
|
||||||
def pages_that_reference(page_name)
|
def pages_that_reference(page_name)
|
||||||
|
self.select { |page| page.wiki_references.include?(page_name) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def pages_that_link_to(page_name)
|
||||||
self.select { |page| page.wiki_words.include?(page_name) }
|
self.select { |page| page.wiki_words.include?(page_name) }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def pages_that_include(page_name)
|
||||||
|
self.select { |page| page.wiki_includes.include?(page_name) }
|
||||||
|
end
|
||||||
|
|
||||||
def pages_authored_by(author)
|
def pages_authored_by(author)
|
||||||
self.select { |page| page.authors.include?(author) }
|
self.select { |page| page.authors.include?(author) }
|
||||||
end
|
end
|
||||||
|
@ -70,4 +78,4 @@ class PageSet < Array
|
||||||
self.inject([]) { |authors, page| authors << page.authors }.flatten.uniq.sort
|
self.inject([]) { |authors, page| authors << page.authors }.flatten.uniq.sort
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -34,11 +34,29 @@ class Revision
|
||||||
number > 0 ? page.revisions[number - 1] : nil
|
number > 0 ? page.revisions[number - 1] : nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Returns an array of all the WikiIncludes present in the content of this revision.
|
||||||
|
def wiki_includes
|
||||||
|
unless @wiki_includes_cache
|
||||||
|
chunks = display_content.find_chunks(Include)
|
||||||
|
@wiki_includes_cache = chunks.map { |c| ( c.escaped? ? nil : c.page_name ) }.compact.uniq
|
||||||
|
end
|
||||||
|
@wiki_includes_cache
|
||||||
|
end
|
||||||
|
|
||||||
|
# Returns an array of all the WikiReferences present in the content of this revision.
|
||||||
|
def wiki_references
|
||||||
|
unless @wiki_references_cache
|
||||||
|
chunks = display_content.find_chunks(WikiChunk::WikiReference)
|
||||||
|
@wiki_references_cache = chunks.map { |c| ( c.escaped? ? nil : c.page_name ) }.compact.uniq
|
||||||
|
end
|
||||||
|
@wiki_references_cache
|
||||||
|
end
|
||||||
|
|
||||||
# Returns an array of all the WikiWords present in the content of this revision.
|
# Returns an array of all the WikiWords present in the content of this revision.
|
||||||
def wiki_words
|
def wiki_words
|
||||||
unless @wiki_words_cache
|
unless @wiki_words_cache
|
||||||
wiki_chunks = display_content.find_chunks(WikiChunk::WikiLink)
|
wiki_chunks = display_content.find_chunks(WikiChunk::WikiLink)
|
||||||
@wiki_words_cache = wiki_chunks.map { |c| ( c.escaped_text ? nil : c.page_name ) }.compact.uniq
|
@wiki_words_cache = wiki_chunks.map { |c| ( c.escaped? ? nil : c.page_name ) }.compact.uniq
|
||||||
end
|
end
|
||||||
@wiki_words_cache
|
@wiki_words_cache
|
||||||
end
|
end
|
||||||
|
@ -55,11 +73,12 @@ class Revision
|
||||||
wiki_words - existing_pages
|
wiki_words - existing_pages
|
||||||
end
|
end
|
||||||
|
|
||||||
# Explicit check for new type of display cache with find_chunks method.
|
# Explicit check for new type of display cache with chunks_by_type method.
|
||||||
# Ensures new version works with older snapshots.
|
# Ensures new version works with older snapshots.
|
||||||
def display_content
|
def display_content
|
||||||
unless @display_cache && @display_cache.respond_to?(:find_chunks)
|
unless @display_cache && @display_cache.respond_to?(:chunks_by_type)
|
||||||
@display_cache = WikiContent.new(self)
|
@display_cache = WikiContent.new(self)
|
||||||
|
@display_cache.render!
|
||||||
end
|
end
|
||||||
@display_cache
|
@display_cache
|
||||||
end
|
end
|
||||||
|
@ -69,7 +88,7 @@ class Revision
|
||||||
end
|
end
|
||||||
|
|
||||||
def clear_display_cache
|
def clear_display_cache
|
||||||
@display_cache = @published_cache = @wiki_words_cache = nil
|
@wiki_references_cache = @wiki_includes = @display_cache = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
def display_published
|
def display_published
|
||||||
|
@ -78,12 +97,12 @@ class Revision
|
||||||
end
|
end
|
||||||
|
|
||||||
def display_content_for_export
|
def display_content_for_export
|
||||||
WikiContent.new(self, {:mode => :export} )
|
WikiContent.new(self, {:mode => :export} ).render!
|
||||||
end
|
end
|
||||||
|
|
||||||
def force_rendering
|
def force_rendering
|
||||||
begin
|
begin
|
||||||
display_content
|
display_content.render!
|
||||||
rescue Exception => e
|
rescue Exception => e
|
||||||
ApplicationController.logger.error "Failed rendering page #{@name}"
|
ApplicationController.logger.error "Failed rendering page #{@name}"
|
||||||
ApplicationController.logger.error e
|
ApplicationController.logger.error e
|
||||||
|
|
|
@ -156,6 +156,11 @@ class Web
|
||||||
PageSet.new(self, @pages.values, condition)
|
PageSet.new(self, @pages.values, condition)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# This ensures compatibility with 0.9 storages
|
||||||
|
def wiki
|
||||||
|
@wiki ||= WikiService.instance
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
# Returns an array of all the wiki words in any current revision
|
# Returns an array of all the wiki words in any current revision
|
||||||
def wiki_words
|
def wiki_words
|
||||||
|
@ -167,9 +172,4 @@ class Web
|
||||||
pages.keys
|
pages.keys
|
||||||
end
|
end
|
||||||
|
|
||||||
# This ensures compatibility with 0.9 storages
|
end
|
||||||
def wiki
|
|
||||||
@wiki ||= WikiService.instance
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
|
@ -37,22 +37,95 @@ require 'chunks/nowiki'
|
||||||
# AUTHOR: Mark Reid <mark @ threewordslong . com>
|
# AUTHOR: Mark Reid <mark @ threewordslong . com>
|
||||||
# CREATED: 15th May 2004
|
# CREATED: 15th May 2004
|
||||||
# UPDATED: 22nd May 2004
|
# UPDATED: 22nd May 2004
|
||||||
|
|
||||||
|
module ChunkManager
|
||||||
|
attr_reader :chunks_by_type, :chunks_by_id
|
||||||
|
|
||||||
|
# regexp that match all chunk type masks
|
||||||
|
CHUNK_MASK_RE = Chunk::Abstract.mask_re(Chunk::Abstract::derivatives)
|
||||||
|
|
||||||
|
def init_chunk_manager
|
||||||
|
@chunks_by_type = Hash.new
|
||||||
|
Chunk::Abstract::derivatives.each{|chunk_type|
|
||||||
|
@chunks_by_type[chunk_type] = Array.new
|
||||||
|
}
|
||||||
|
@chunks_by_id = Hash.new
|
||||||
|
end
|
||||||
|
|
||||||
|
def add_chunk(c)
|
||||||
|
@chunks_by_type[c.class] << c
|
||||||
|
@chunks_by_id[c.object_id] = c
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete_chunk(c)
|
||||||
|
@chunks_by_type[c.class].delete(c)
|
||||||
|
@chunks_by_id.delete(c.object_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
def chunks
|
||||||
|
@chunks_by_id.values
|
||||||
|
end
|
||||||
|
|
||||||
|
def merge_chunks(other)
|
||||||
|
other.chunks_by_id.each_value{|c| add_chunk(c)}
|
||||||
|
end
|
||||||
|
|
||||||
|
def scan_chunkid(text)
|
||||||
|
text.scan(CHUNK_MASK_RE){|a| yield a[0].to_i }
|
||||||
|
end
|
||||||
|
|
||||||
|
def find_chunks(chunk_type)
|
||||||
|
@chunks_by_id.values.select { |chunk| chunk.kind_of?(chunk_type) and chunk.rendered? }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# A simplified version of WikiContent. Useful to avoid recursion problems in
|
||||||
|
# WikiContent.new
|
||||||
|
class WikiContentStub < String
|
||||||
|
attr_reader :options
|
||||||
|
include ChunkManager
|
||||||
|
def initialize(content, options)
|
||||||
|
super(content)
|
||||||
|
@options = options
|
||||||
|
init_chunk_manager
|
||||||
|
end
|
||||||
|
|
||||||
|
# Detects the mask strings contained in the text of chunks of type chunk_types
|
||||||
|
# and yields the corresponding chunk ids
|
||||||
|
# example: content = "chunk123categorychunk <pre>chunk456categorychunk</pre>"
|
||||||
|
# inside_chunks(Literal::Pre) ==> yield 456
|
||||||
|
def inside_chunks(chunk_types)
|
||||||
|
chunk_types.each{|chunk_type| chunk_type.apply_to(self) }
|
||||||
|
|
||||||
|
chunk_types.each{|chunk_type| @chunks_by_type[chunk_type].each{|hide_chunk|
|
||||||
|
scan_chunkid(hide_chunk.text){|id| yield id }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
class WikiContent < String
|
class WikiContent < String
|
||||||
|
|
||||||
PRE_ENGINE_ACTIONS = [ NoWiki, Category, Include, WikiChunk::Link, URIChunk,
|
ACTIVE_CHUNKS = [ NoWiki, Category, WikiChunk::Link, URIChunk, LocalURIChunk,
|
||||||
LocalURIChunk, WikiChunk::Word ].freeze
|
WikiChunk::Word ]
|
||||||
POST_ENGINE_ACTIONS = [ Literal::Pre, Literal::Tags ].freeze
|
HIDE_CHUNKS = [ Literal::Pre, Literal::Tags ]
|
||||||
|
|
||||||
|
MASK_RE = {
|
||||||
|
ACTIVE_CHUNKS => Chunk::Abstract.mask_re(ACTIVE_CHUNKS),
|
||||||
|
HIDE_CHUNKS => Chunk::Abstract.mask_re(HIDE_CHUNKS)
|
||||||
|
}
|
||||||
|
|
||||||
DEFAULT_OPTS = {
|
DEFAULT_OPTS = {
|
||||||
:pre_engine_actions => PRE_ENGINE_ACTIONS,
|
:active_chunks => ACTIVE_CHUNKS,
|
||||||
:post_engine_actions => POST_ENGINE_ACTIONS,
|
|
||||||
:engine => Engines::Textile,
|
:engine => Engines::Textile,
|
||||||
:engine_opts => [],
|
:engine_opts => [],
|
||||||
:mode => :show
|
:mode => :show
|
||||||
}.freeze
|
}.freeze
|
||||||
|
|
||||||
attr_reader :web, :options, :rendered, :chunks
|
attr_reader :web, :options, :revision, :not_rendered, :pre_rendered
|
||||||
|
|
||||||
|
include ChunkManager
|
||||||
|
|
||||||
# Create a new wiki content string from the given one.
|
# Create a new wiki content string from the given one.
|
||||||
# The options are explained at the top of this file.
|
# The options are explained at the top of this file.
|
||||||
def initialize(revision, options = {})
|
def initialize(revision, options = {})
|
||||||
|
@ -60,13 +133,14 @@ class WikiContent < String
|
||||||
@web = @revision.page.web
|
@web = @revision.page.web
|
||||||
|
|
||||||
@options = DEFAULT_OPTS.dup.merge(options)
|
@options = DEFAULT_OPTS.dup.merge(options)
|
||||||
@options[:engine] = Engines::MAP[@web.markup]
|
@options[:engine] = Engines::MAP[@web.markup]
|
||||||
@options[:engine_opts] = [:filter_html, :filter_styles] if @web.safe_mode
|
@options[:engine_opts] = [:filter_html, :filter_styles] if @web.safe_mode
|
||||||
@options[:pre_engine_actions] = (PRE_ENGINE_ACTIONS - [WikiChunk::Word]) if @web.brackets_only
|
@options[:active_chunks] = (ACTIVE_CHUNKS - [WikiChunk::Word] ) if @web.brackets_only
|
||||||
|
|
||||||
super(@revision.content)
|
super(@revision.content)
|
||||||
|
init_chunk_manager
|
||||||
render!(@options[:pre_engine_actions] + [@options[:engine]] + @options[:post_engine_actions])
|
build_chunks
|
||||||
|
@not_rendered = String.new(self)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Call @web.page_link using current options.
|
# Call @web.page_link using current options.
|
||||||
|
@ -75,17 +149,50 @@ class WikiContent < String
|
||||||
@web.make_link(name, text, @options)
|
@web.make_link(name, text, @options)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Find all the chunks of the given types
|
def build_chunks
|
||||||
def find_chunks(chunk_type)
|
# create and mask Includes and "active_chunks" chunks
|
||||||
rendered.select { |chunk| chunk.kind_of?(chunk_type) }
|
Include.apply_to(self)
|
||||||
|
@options[:active_chunks].each{|chunk_type| chunk_type.apply_to(self)}
|
||||||
|
|
||||||
|
# Handle hiding contexts like "pre" and "code" etc..
|
||||||
|
# The markup (textile, rdoc etc) can produce such contexts with its own syntax.
|
||||||
|
# To reveal them, we work on a copy of the content.
|
||||||
|
# The copy is rendered and used to detect the chunks that are inside protecting context
|
||||||
|
# These chunks are reverted on the original content string.
|
||||||
|
|
||||||
|
copy = WikiContentStub.new(self, @options)
|
||||||
|
@options[:engine].apply_to(copy)
|
||||||
|
|
||||||
|
copy.inside_chunks(HIDE_CHUNKS) do |id|
|
||||||
|
@chunks_by_id[id].revert
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Render this content using the specified actions.
|
def pre_render!
|
||||||
def render!(chunk_types)
|
unless @pre_rendered
|
||||||
@chunks = []
|
@chunks_by_type[Include].each{|chunk| chunk.unmask }
|
||||||
chunk_types.each { |chunk_type| chunk_type.apply_to(self) }
|
@pre_rendered = String.new(self)
|
||||||
@rendered = @chunks.map { |chunk| chunk.unmask(self) }.compact
|
end
|
||||||
(@chunks - @rendered).each { |chunk| chunk.revert(self) }
|
@pre_rendered
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
def render!
|
||||||
|
pre_render!
|
||||||
|
@options[:engine].apply_to(self)
|
||||||
|
# unmask in one go. $~[1].to_i is the chunk id
|
||||||
|
gsub!(MASK_RE[ACTIVE_CHUNKS]){
|
||||||
|
if chunk = @chunks_by_id[$~[1].to_i]
|
||||||
|
chunk.unmask_text
|
||||||
|
# if we match a chunkmask that existed in the original content string
|
||||||
|
# just keep it as it is
|
||||||
|
else
|
||||||
|
$~[0]
|
||||||
|
end}
|
||||||
|
self
|
||||||
|
end
|
||||||
|
|
||||||
|
def page_name
|
||||||
|
@revision.page.name
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
|
@ -75,15 +75,24 @@
|
||||||
<% end %>
|
<% end %>
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
<% if @page.references.length > 0 %>
|
<% unless @page.linked_from.empty? %>
|
||||||
<small>
|
<small>
|
||||||
| Linked from:
|
| Linked from:
|
||||||
<%= @page.references.collect { |referring_page|
|
<%= @page.linked_from.collect { |referring_page|
|
||||||
link_to_existing_page referring_page
|
link_to_existing_page referring_page
|
||||||
}.join(", ")
|
}.join(", ")
|
||||||
%>
|
%>
|
||||||
</small>
|
</small>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
|
<% if @page.included_from.length > 0 %>
|
||||||
|
<small>
|
||||||
|
| Included from: <%= @page.included_from.collect { |referring_page|
|
||||||
|
link_to_existing_page referring_page
|
||||||
|
}.join(", ")
|
||||||
|
%>
|
||||||
|
</small>
|
||||||
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script language="Javascript">
|
<script language="Javascript">
|
||||||
|
|
|
@ -75,11 +75,12 @@ end
|
||||||
# It provides a easy way to test whether a chunk matches a particular string
|
# It provides a easy way to test whether a chunk matches a particular string
|
||||||
# and any the values of any fields that should be set after a match.
|
# and any the values of any fields that should be set after a match.
|
||||||
class ContentStub < String
|
class ContentStub < String
|
||||||
attr_reader :chunks, :content
|
include ChunkManager
|
||||||
def initialize(str)
|
def initialize(str)
|
||||||
super
|
super
|
||||||
@chunks = []
|
init_chunk_manager
|
||||||
end
|
end
|
||||||
|
def page_link(*); end
|
||||||
end
|
end
|
||||||
|
|
||||||
module ChunkMatch
|
module ChunkMatch
|
||||||
|
@ -97,7 +98,7 @@ module ChunkMatch
|
||||||
expected_chunk_state.each_pair do |a_method, expected_value|
|
expected_chunk_state.each_pair do |a_method, expected_value|
|
||||||
assert content.chunks.last.kind_of?(chunk_type)
|
assert content.chunks.last.kind_of?(chunk_type)
|
||||||
assert_respond_to(content.chunks.last, a_method)
|
assert_respond_to(content.chunks.last, a_method)
|
||||||
assert_equal(expected_value, content.chunks.last.send(a_method.to_sym),
|
assert_equal(expected_value, content.chunks.last.send(a_method.to_sym),
|
||||||
"Wrong #{a_method} value")
|
"Wrong #{a_method} value")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,12 +5,6 @@ require 'chunks/wiki'
|
||||||
|
|
||||||
class WikiTest < Test::Unit::TestCase
|
class WikiTest < Test::Unit::TestCase
|
||||||
|
|
||||||
class ContentStub < String
|
|
||||||
def chunks
|
|
||||||
@chunks ||= []
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
include ChunkMatch
|
include ChunkMatch
|
||||||
|
|
||||||
def test_simple
|
def test_simple
|
||||||
|
@ -18,21 +12,19 @@ class WikiTest < Test::Unit::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_escaped
|
def test_escaped
|
||||||
|
# escape is only implemented in WikiChunk::Word
|
||||||
match(WikiChunk::Word, 'Do not link to an \EscapedWord',
|
match(WikiChunk::Word, 'Do not link to an \EscapedWord',
|
||||||
:page_name => 'EscapedWord', :escaped_text => 'EscapedWord'
|
:page_name => 'EscapedWord', :escaped_text => 'EscapedWord'
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_simple_brackets
|
def test_simple_brackets
|
||||||
match(WikiChunk::Link, 'This is a [[bracketted link]]',
|
match(WikiChunk::Link, 'This is a [[bracketted link]]', :page_name => 'bracketted link')
|
||||||
:page_name => 'bracketted link', :escaped_text => nil
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_complex_brackets
|
def test_complex_brackets
|
||||||
match(WikiChunk::Link, 'This is a tricky link [[Sperberg-McQueen]]',
|
match(WikiChunk::Link, 'This is a tricky link [[Sperberg-McQueen]]',
|
||||||
:page_name => 'Sperberg-McQueen', :escaped_text => nil
|
:page_name => 'Sperberg-McQueen')
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_textile_link
|
def test_textile_link
|
||||||
|
|
|
@ -15,6 +15,7 @@ class RevisionTest < Test::Unit::TestCase
|
||||||
['MyWay', 'SmartEngine', 'ThatWay'].each do |page|
|
['MyWay', 'SmartEngine', 'ThatWay'].each do |page|
|
||||||
@wiki.write_page('wiki1', page, page, Time.now, 'Me')
|
@wiki.write_page('wiki1', page, page, Time.now, 'Me')
|
||||||
end
|
end
|
||||||
|
@wiki.write_page('wiki1','NoWikiWord', 'hey you', Time.now, 'Me')
|
||||||
|
|
||||||
@revision = Revision.new(@page, 1,
|
@revision = Revision.new(@page, 1,
|
||||||
'HisWay would be MyWay in kinda ThatWay in HisWay though MyWay \OverThere -- ' +
|
'HisWay would be MyWay in kinda ThatWay in HisWay though MyWay \OverThere -- ' +
|
||||||
|
@ -24,6 +25,9 @@ class RevisionTest < Test::Unit::TestCase
|
||||||
|
|
||||||
def test_wiki_words
|
def test_wiki_words
|
||||||
assert_equal %w( HisWay MyWay SmartEngine SmartEngineGUI ThatWay ), @revision.wiki_words.sort
|
assert_equal %w( HisWay MyWay SmartEngine SmartEngineGUI ThatWay ), @revision.wiki_words.sort
|
||||||
|
|
||||||
|
@wiki.write_page('wiki1', 'NoWikiWord', 'hey you', Time.now, 'Me')
|
||||||
|
assert_equal [], @wiki.read_page('wiki1', 'NoWikiWord').wiki_words
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_existing_pages
|
def test_existing_pages
|
||||||
|
|
|
@ -209,7 +209,7 @@ class URITest < Test::Unit::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
def assert_conversion_does_not_apply(chunk_type, str)
|
def assert_conversion_does_not_apply(chunk_type, str)
|
||||||
processed_str = str.dup
|
processed_str = ContentStub.new(str.dup)
|
||||||
chunk_type.apply_to(processed_str)
|
chunk_type.apply_to(processed_str)
|
||||||
assert_equal(str, processed_str)
|
assert_equal(str, processed_str)
|
||||||
end
|
end
|
||||||
|
|
|
@ -134,4 +134,4 @@ class WebTest < Test::Unit::TestCase
|
||||||
@web.add_page(Page.new(@web, 'EverBeenHated', 'I am me EverBeenHated',
|
@web.add_page(Page.new(@web, 'EverBeenHated', 'I am me EverBeenHated',
|
||||||
Time.local(2004, 4, 4, 16, 51), 'DavidHeinemeierHansson'))
|
Time.local(2004, 4, 4, 16, 51), 'DavidHeinemeierHansson'))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue