Massive change of SVN properties to deal with EOL style problem

This commit is contained in:
Alexey Verkhovsky 2005-01-24 18:52:04 +00:00
parent b747b611b3
commit 3b6566577c
108 changed files with 12417 additions and 12417 deletions

6
app/models/author.rb Executable file → Normal file
View file

@ -1,4 +1,4 @@
class Author < String
attr_accessor :ip
def initialize(name, ip) @ip = ip; super(name) end
class Author < String
attr_accessor :ip
def initialize(name, ip) @ip = ip; super(name) end
end

70
app/models/chunks/category.rb Executable file → Normal file
View file

@ -1,35 +1,35 @@
require 'chunks/chunk'
# The category chunk looks for "category: news" on a line by
# itself and parses the terms after the ':' as categories.
# Other classes can search for Category chunks within
# rendered content to find out what categories this page
# should be in.
#
# Category lines can be hidden using ':category: news', for example
class Category < Chunk::Abstract
def self.pattern() return /^(:)?category\s*:(.*)$/i end
attr_reader :hidden, :list
def initialize(match_data)
super(match_data)
@hidden = match_data[1]
@list = match_data[2].split(',').map { |c| c.strip }
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
def url(category)
%{<a class="category_link" href="../list/?category=#{category}">#{category}</a>}
end
end
require 'chunks/chunk'
# The category chunk looks for "category: news" on a line by
# itself and parses the terms after the ':' as categories.
# Other classes can search for Category chunks within
# rendered content to find out what categories this page
# should be in.
#
# Category lines can be hidden using ':category: news', for example
class Category < Chunk::Abstract
def self.pattern() return /^(:)?category\s*:(.*)$/i end
attr_reader :hidden, :list
def initialize(match_data)
super(match_data)
@hidden = match_data[1]
@list = match_data[2].split(',').map { |c| c.strip }
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
def url(category)
%{<a class="category_link" href="../list/?category=#{category}">#{category}</a>}
end
end

80
app/models/chunks/chunk.rb Executable file → Normal file
View file

@ -1,40 +1,40 @@
require 'digest/md5'
require 'uri/common'
# A chunk is a pattern of text that can be protected
# and interrogated by a renderer. Each Chunk class has a
# +pattern+ that states what sort of text it matches.
# Chunks are initalized by passing in the result of a
# match by its pattern.
module Chunk
class Abstract
attr_reader :text
def initialize(match_data) @text = match_data[0] end
# Find all the chunks of the given type in content
# Each time the pattern is matched, create a new
# chunk for it, and replace the occurance of the chunk
# in this content with its mask.
def self.apply_to(content)
content.gsub!( self.pattern ) do |match|
new_chunk = self.new($~)
content.chunks << new_chunk
new_chunk.mask(content)
end
end
def mask(content)
"chunk#{self.object_id}#{self.class.to_s.delete(':').downcase}chunk"
end
def revert(content)
content.sub!( Regexp.new(mask(content)), text )
end
def unmask(content)
self if revert(content)
end
end
end
require 'digest/md5'
require 'uri/common'
# A chunk is a pattern of text that can be protected
# and interrogated by a renderer. Each Chunk class has a
# +pattern+ that states what sort of text it matches.
# Chunks are initalized by passing in the result of a
# match by its pattern.
module Chunk
class Abstract
attr_reader :text
def initialize(match_data) @text = match_data[0] end
# Find all the chunks of the given type in content
# Each time the pattern is matched, create a new
# chunk for it, and replace the occurance of the chunk
# in this content with its mask.
def self.apply_to(content)
content.gsub!( self.pattern ) do |match|
new_chunk = self.new($~)
content.chunks << new_chunk
new_chunk.mask(content)
end
end
def mask(content)
"chunk#{self.object_id}#{self.class.to_s.delete(':').downcase}chunk"
end
def revert(content)
content.sub!( Regexp.new(mask(content)), text )
end
def unmask(content)
self if revert(content)
end
end
end

76
app/models/chunks/engines.rb Executable file → Normal file
View file

@ -1,38 +1,38 @@
$: << File.dirname(__FILE__) + "../../libraries"
require 'redcloth'
require 'bluecloth'
require 'rdocsupport'
require 'chunks/chunk'
# The markup engines are Chunks that call the one of RedCloth, BlueCloth
# or RDoc to convert text. This markup occurs when the chunk is required
# to mask itself.
module Engines
class Textile < Chunk::Abstract
def self.pattern() /^(.*)$/m end
def mask(content)
RedCloth.new(text,content.options[:engine_opts]).to_html
end
def unmask(content) self end
end
class Markdown < Chunk::Abstract
def self.pattern() /^(.*)$/m end
def mask(content)
BlueCloth.new(text,content.options[:engine_opts]).to_html
end
def unmask(content) self end
end
class RDoc < Chunk::Abstract
def self.pattern() /^(.*)$/m end
def mask(content)
RDocSupport::RDocFormatter.new(text).to_html
end
def unmask(content) self end
end
MAP = { :textile => Textile, :markdown => Markdown, :rdoc => RDoc }
end
$: << File.dirname(__FILE__) + "../../libraries"
require 'redcloth'
require 'bluecloth'
require 'rdocsupport'
require 'chunks/chunk'
# The markup engines are Chunks that call the one of RedCloth, BlueCloth
# or RDoc to convert text. This markup occurs when the chunk is required
# to mask itself.
module Engines
class Textile < Chunk::Abstract
def self.pattern() /^(.*)$/m end
def mask(content)
RedCloth.new(text,content.options[:engine_opts]).to_html
end
def unmask(content) self end
end
class Markdown < Chunk::Abstract
def self.pattern() /^(.*)$/m end
def mask(content)
BlueCloth.new(text,content.options[:engine_opts]).to_html
end
def unmask(content) self end
end
class RDoc < Chunk::Abstract
def self.pattern() /^(.*)$/m end
def mask(content)
RDocSupport::RDocFormatter.new(text).to_html
end
def unmask(content) self end
end
MAP = { :textile => Textile, :markdown => Markdown, :rdoc => RDoc }
end

58
app/models/chunks/include.rb Executable file → Normal file
View file

@ -1,29 +1,29 @@
require 'chunks/wiki'
# Includes the contents of another page for rendering.
# The include command looks like this: "[[!include PageName]]".
# It is a WikiLink since it refers to another page (PageName)
# and the wiki content using this command must be notified
# of changes to that page.
# If the included page could not be found, a warning is displayed.
class Include < WikiChunk::WikiLink
def self.pattern() /^\[\[!include(.*)\]\]\s*$/i end
attr_reader :page_name
def initialize(match_data)
super(match_data)
@page_name = match_data[1].strip
end
# This replaces the [[!include PageName]] text with
# the contents of PageName if it exists. Otherwise
# a warning is displayed.
def mask(content)
page = content.web.pages[page_name]
(page ? page.content : "<em>Could not include #{page_name}</em>")
end
# Keep this chunk regardless of what happens.
def unmask(content) self end
end
require 'chunks/wiki'
# Includes the contents of another page for rendering.
# The include command looks like this: "[[!include PageName]]".
# It is a WikiLink since it refers to another page (PageName)
# and the wiki content using this command must be notified
# of changes to that page.
# If the included page could not be found, a warning is displayed.
class Include < WikiChunk::WikiLink
def self.pattern() /^\[\[!include(.*)\]\]\s*$/i end
attr_reader :page_name
def initialize(match_data)
super(match_data)
@page_name = match_data[1].strip
end
# This replaces the [[!include PageName]] text with
# the contents of PageName if it exists. Otherwise
# a warning is displayed.
def mask(content)
page = content.web.pages[page_name]
(page ? page.content : "<em>Could not include #{page_name}</em>")
end
# Keep this chunk regardless of what happens.
def unmask(content) self end
end

38
app/models/chunks/literal.rb Executable file → Normal file
View file

@ -1,19 +1,19 @@
require 'chunks/chunk'
# These are basic chunks that have a pattern and can be protected.
# They are used by rendering process to prevent wiki rendering
# occuring within literal areas such as <code> and <pre> blocks
# and within HTML tags.
module Literal
# A literal chunk that protects 'code' and 'pre' tags from wiki rendering.
class Pre < Chunk::Abstract
PRE_BLOCKS = "a|pre|code"
def self.pattern() Regexp.new('<('+PRE_BLOCKS+')\b[^>]*?>.*?</\1>', Regexp::MULTILINE) end
end
# A literal chunk that protects HTML tags from wiki rendering.
class Tags < Chunk::Abstract
TAGS = "a|img|em|strong|div|span|table|td|th|ul|ol|li|dl|dt|dd"
def self.pattern() Regexp.new('<(?:'+TAGS+')[^>]*?>', Regexp::MULTILINE) end
end
end
require 'chunks/chunk'
# These are basic chunks that have a pattern and can be protected.
# They are used by rendering process to prevent wiki rendering
# occuring within literal areas such as <code> and <pre> blocks
# and within HTML tags.
module Literal
# A literal chunk that protects 'code' and 'pre' tags from wiki rendering.
class Pre < Chunk::Abstract
PRE_BLOCKS = "a|pre|code"
def self.pattern() Regexp.new('<('+PRE_BLOCKS+')\b[^>]*?>.*?</\1>', Regexp::MULTILINE) end
end
# A literal chunk that protects HTML tags from wiki rendering.
class Tags < Chunk::Abstract
TAGS = "a|img|em|strong|div|span|table|td|th|ul|ol|li|dl|dt|dd"
def self.pattern() Regexp.new('<(?:'+TAGS+')[^>]*?>', Regexp::MULTILINE) end
end
end

62
app/models/chunks/nowiki.rb Executable file → Normal file
View file

@ -1,31 +1,31 @@
require 'chunks/chunk'
# This chunks allows certain parts of a wiki page to be hidden from the
# rest of the rendering pipeline. It should be run at the beginning
# of the pipeline in `wiki_content.rb`.
#
# An example use of this chunk is to markup double brackets or
# auto URI links:
# <nowiki>Here are [[double brackets]] and a URI: www.uri.org</nowiki>
#
# The contents of the chunks will not be processed by any other chunk
# so the `www.uri.org` and the double brackets will appear verbatim.
#
# Author: Mark Reid <mark at threewordslong dot com>
# Created: 8th June 2004
class NoWiki < Chunk::Abstract
def self.pattern() Regexp.new('<nowiki>(.*?)</nowiki>') end
attr_reader :plain_text
def initialize(match_data)
super(match_data)
@plain_text = match_data[1]
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
require 'chunks/chunk'
# This chunks allows certain parts of a wiki page to be hidden from the
# rest of the rendering pipeline. It should be run at the beginning
# of the pipeline in `wiki_content.rb`.
#
# An example use of this chunk is to markup double brackets or
# auto URI links:
# <nowiki>Here are [[double brackets]] and a URI: www.uri.org</nowiki>
#
# The contents of the chunks will not be processed by any other chunk
# so the `www.uri.org` and the double brackets will appear verbatim.
#
# Author: Mark Reid <mark at threewordslong dot com>
# Created: 8th June 2004
class NoWiki < Chunk::Abstract
def self.pattern() Regexp.new('<nowiki>(.*?)</nowiki>') end
attr_reader :plain_text
def initialize(match_data)
super(match_data)
@plain_text = match_data[1]
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

36
app/models/chunks/test.rb Executable file → Normal file
View file

@ -1,18 +1,18 @@
require 'test/unit'
class ChunkTest < Test::Unit::TestCase
# Asserts a number of tests for the given type and text.
def match(type, test_text, expected)
pattern = type.pattern
assert_match(pattern, test_text)
pattern =~ test_text # Previous assertion guarantees match
chunk = type.new($~)
# Test if requested parts are correct.
for method_sym, value in expected do
assert_respond_to(chunk, method_sym)
assert_equal(value, chunk.method(method_sym).call, "Checking value of '#{method_sym}'")
end
end
end
require 'test/unit'
class ChunkTest < Test::Unit::TestCase
# Asserts a number of tests for the given type and text.
def match(type, test_text, expected)
pattern = type.pattern
assert_match(pattern, test_text)
pattern =~ test_text # Previous assertion guarantees match
chunk = type.new($~)
# Test if requested parts are correct.
for method_sym, value in expected do
assert_respond_to(chunk, method_sym)
assert_equal(value, chunk.method(method_sym).call, "Checking value of '#{method_sym}'")
end
end
end

358
app/models/chunks/uri.rb Executable file → Normal file
View file

@ -1,179 +1,179 @@
require 'chunks/chunk'
# This wiki chunk matches arbitrary URIs, using patterns from the Ruby URI modules.
# It parses out a variety of fields that could be used by renderers to format
# the links in various ways (shortening domain names, hiding email addresses)
# It matches email addresses and host.com.au domains without schemes (http://)
# but adds these on as required.
#
# The heuristic used to match a URI is designed to err on the side of caution.
# That is, it is more likely to not autolink a URI than it is to accidently
# autolink something that is not a URI. The reason behind this is it is easier
# to force a URI link by prefixing 'http://' to it than it is to escape and
# incorrectly marked up non-URI.
#
# I'm using a part of the [ISO 3166-1 Standard][iso3166] for country name suffixes.
# The generic names are from www.bnoack.com/data/countrycode2.html)
# [iso3166]: http://geotags.com/iso3166/
class URIChunk < Chunk::Abstract
include URI::REGEXP::PATTERN
# this condition is to get rid of pesky warnings in tests
unless defined? URIChunk::INTERNET_URI_REGEXP
GENERIC = '(?:aero|biz|com|coop|edu|gov|info|int|mil|museum|name|net|org)'
COUNTRY = '(?:au|at|be|ca|ch|de|dk|fr|hk|in|ir|it|jp|nl|no|pt|ru|se|sw|tv|tw|uk|us)'
# These are needed otherwise HOST will match almost anything
TLDS = "(?:#{GENERIC}|#{COUNTRY})"
# Redefine USERINFO so that it must have non-zero length
USERINFO = "(?:[#{UNRESERVED};:&=+$,]|#{ESCAPED})+"
# unreserved_no_ending = alphanum | mark, but URI_ENDING [)!] excluded
UNRESERVED_NO_ENDING = "-_.~*'(#{ALNUM}"
# this ensures that query or fragment do not end with URI_ENDING
# and enable us to use a much simpler self.pattern Regexp
# uric_no_ending = reserved | unreserved_no_ending | escaped
URIC_NO_ENDING = "(?:[#{UNRESERVED_NO_ENDING}#{RESERVED}]|#{ESCAPED})"
# query = *uric
QUERY = "#{URIC_NO_ENDING}*"
# fragment = *uric
FRAGMENT = "#{URIC_NO_ENDING}*"
# DOMLABEL is defined in the ruby uri library, TLDS is defined above
INTERNET_HOSTNAME = "(?:#{DOMLABEL}\\.)+#{TLDS}"
# Correct a typo bug in ruby 1.8.x lib/uri/common.rb
PORT = '\\d*'
INTERNET_URI =
"(?:(#{SCHEME}):/{0,2})?" + # Optional scheme: (\1)
"(?:(#{USERINFO})@)?" + # Optional userinfo@ (\2)
"(#{INTERNET_HOSTNAME})" + # Mandatory hostname (\3)
"(?::(#{PORT}))?" + # Optional :port (\4)
"(#{ABS_PATH})?" + # Optional absolute path (\5)
"(?:\\?(#{QUERY}))?" + # Optional ?query (\6)
"(?:\\#(#{FRAGMENT}))?" # Optional #fragment (\7)
TEXTILE_SYNTAX_PREFIX = '(!)?'
INTERNET_URI_REGEXP = Regexp.new(TEXTILE_SYNTAX_PREFIX + INTERNET_URI, Regexp::EXTENDED, 'N')
end
def URIChunk.pattern
INTERNET_URI_REGEXP
end
attr_reader :user, :host, :port, :path, :query, :fragment, :link_text
def self.apply_to(content)
content.gsub!( self.pattern ) do |matched_text|
chunk = self.new($~)
if chunk.textile_url? or chunk.textile_image?
# do not substitute
matched_text
else
content.chunks << chunk
chunk.mask(content)
end
end
end
def initialize(match_data)
super(match_data)
@link_text = match_data[0]
@textile_prefix, @original_scheme, @user, @host, @port, @path, @query, @fragment =
match_data[1..-1]
treat_trailing_character
end
def textile_url?
@textile_prefix == '":'
end
def textile_image?
@textile_prefix == '!' and @trailing_punctuation == '!'
end
def treat_trailing_character
# If the last character matched by URI pattern is in ! or ), this may be part of the markup,
# not a URL. We should handle it as such. It is possible to do it by a regexp, but
# much easier to do programmatically
last_char = @link_text[-1..-1]
if last_char == ')' or last_char == '!'
@trailing_punctuation = last_char
@link_text.chop!
[@original_scheme, @user, @host, @port, @path, @query, @fragment].compact.last.chop!
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
@original_scheme or (@user ? 'mailto' : 'http')
end
def scheme_delimiter
scheme == 'mailto' ? ':' : '://'
end
def user_delimiter
'@' unless @user.nil?
end
def port_delimiter
':' unless @port.nil?
end
def query_delimiter
'?' unless @query.nil?
end
def uri
[scheme, scheme_delimiter, user, user_delimiter, host, port_delimiter, port, path,
query_delimiter, query].compact.join
end
end
# uri with mandatory scheme but less restrictive hostname, like
# http://localhost:2500/blah.html
class LocalURIChunk < URIChunk
unless defined? LocalURIChunk::LOCAL_URI_REGEXP
# hostname can be just a simple word like 'localhost'
ANY_HOSTNAME = "(?:#{DOMLABEL}\\.)*#{TOPLABEL}\\.?"
# The basic URI expression as a string
# Scheme and hostname are mandatory
LOCAL_URI =
"(?:(#{SCHEME})://)+" + # Mandatory scheme:// (\1)
"(?:(#{USERINFO})@)?" + # Optional userinfo@ (\2)
"(#{ANY_HOSTNAME})" + # Mandatory hostname (\3)
"(?::(#{PORT}))?" + # Optional :port (\4)
"(#{ABS_PATH})?" + # Optional absolute path (\5)
"(?:\\?(#{QUERY}))?" + # Optional ?query (\6)
"(?:\\#(#{FRAGMENT}))?" # Optional #fragment (\7)
LOCAL_URI_REGEXP = Regexp.new(TEXTILE_SYNTAX_PREFIX + LOCAL_URI, Regexp::EXTENDED, 'N')
end
def LocalURIChunk.pattern
LOCAL_URI_REGEXP
end
end
require 'chunks/chunk'
# This wiki chunk matches arbitrary URIs, using patterns from the Ruby URI modules.
# It parses out a variety of fields that could be used by renderers to format
# the links in various ways (shortening domain names, hiding email addresses)
# It matches email addresses and host.com.au domains without schemes (http://)
# but adds these on as required.
#
# The heuristic used to match a URI is designed to err on the side of caution.
# That is, it is more likely to not autolink a URI than it is to accidently
# autolink something that is not a URI. The reason behind this is it is easier
# to force a URI link by prefixing 'http://' to it than it is to escape and
# incorrectly marked up non-URI.
#
# I'm using a part of the [ISO 3166-1 Standard][iso3166] for country name suffixes.
# The generic names are from www.bnoack.com/data/countrycode2.html)
# [iso3166]: http://geotags.com/iso3166/
class URIChunk < Chunk::Abstract
include URI::REGEXP::PATTERN
# this condition is to get rid of pesky warnings in tests
unless defined? URIChunk::INTERNET_URI_REGEXP
GENERIC = '(?:aero|biz|com|coop|edu|gov|info|int|mil|museum|name|net|org)'
COUNTRY = '(?:au|at|be|ca|ch|de|dk|fr|hk|in|ir|it|jp|nl|no|pt|ru|se|sw|tv|tw|uk|us)'
# These are needed otherwise HOST will match almost anything
TLDS = "(?:#{GENERIC}|#{COUNTRY})"
# Redefine USERINFO so that it must have non-zero length
USERINFO = "(?:[#{UNRESERVED};:&=+$,]|#{ESCAPED})+"
# unreserved_no_ending = alphanum | mark, but URI_ENDING [)!] excluded
UNRESERVED_NO_ENDING = "-_.~*'(#{ALNUM}"
# this ensures that query or fragment do not end with URI_ENDING
# and enable us to use a much simpler self.pattern Regexp
# uric_no_ending = reserved | unreserved_no_ending | escaped
URIC_NO_ENDING = "(?:[#{UNRESERVED_NO_ENDING}#{RESERVED}]|#{ESCAPED})"
# query = *uric
QUERY = "#{URIC_NO_ENDING}*"
# fragment = *uric
FRAGMENT = "#{URIC_NO_ENDING}*"
# DOMLABEL is defined in the ruby uri library, TLDS is defined above
INTERNET_HOSTNAME = "(?:#{DOMLABEL}\\.)+#{TLDS}"
# Correct a typo bug in ruby 1.8.x lib/uri/common.rb
PORT = '\\d*'
INTERNET_URI =
"(?:(#{SCHEME}):/{0,2})?" + # Optional scheme: (\1)
"(?:(#{USERINFO})@)?" + # Optional userinfo@ (\2)
"(#{INTERNET_HOSTNAME})" + # Mandatory hostname (\3)
"(?::(#{PORT}))?" + # Optional :port (\4)
"(#{ABS_PATH})?" + # Optional absolute path (\5)
"(?:\\?(#{QUERY}))?" + # Optional ?query (\6)
"(?:\\#(#{FRAGMENT}))?" # Optional #fragment (\7)
TEXTILE_SYNTAX_PREFIX = '(!)?'
INTERNET_URI_REGEXP = Regexp.new(TEXTILE_SYNTAX_PREFIX + INTERNET_URI, Regexp::EXTENDED, 'N')
end
def URIChunk.pattern
INTERNET_URI_REGEXP
end
attr_reader :user, :host, :port, :path, :query, :fragment, :link_text
def self.apply_to(content)
content.gsub!( self.pattern ) do |matched_text|
chunk = self.new($~)
if chunk.textile_url? or chunk.textile_image?
# do not substitute
matched_text
else
content.chunks << chunk
chunk.mask(content)
end
end
end
def initialize(match_data)
super(match_data)
@link_text = match_data[0]
@textile_prefix, @original_scheme, @user, @host, @port, @path, @query, @fragment =
match_data[1..-1]
treat_trailing_character
end
def textile_url?
@textile_prefix == '":'
end
def textile_image?
@textile_prefix == '!' and @trailing_punctuation == '!'
end
def treat_trailing_character
# If the last character matched by URI pattern is in ! or ), this may be part of the markup,
# not a URL. We should handle it as such. It is possible to do it by a regexp, but
# much easier to do programmatically
last_char = @link_text[-1..-1]
if last_char == ')' or last_char == '!'
@trailing_punctuation = last_char
@link_text.chop!
[@original_scheme, @user, @host, @port, @path, @query, @fragment].compact.last.chop!
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
@original_scheme or (@user ? 'mailto' : 'http')
end
def scheme_delimiter
scheme == 'mailto' ? ':' : '://'
end
def user_delimiter
'@' unless @user.nil?
end
def port_delimiter
':' unless @port.nil?
end
def query_delimiter
'?' unless @query.nil?
end
def uri
[scheme, scheme_delimiter, user, user_delimiter, host, port_delimiter, port, path,
query_delimiter, query].compact.join
end
end
# uri with mandatory scheme but less restrictive hostname, like
# http://localhost:2500/blah.html
class LocalURIChunk < URIChunk
unless defined? LocalURIChunk::LOCAL_URI_REGEXP
# hostname can be just a simple word like 'localhost'
ANY_HOSTNAME = "(?:#{DOMLABEL}\\.)*#{TOPLABEL}\\.?"
# The basic URI expression as a string
# Scheme and hostname are mandatory
LOCAL_URI =
"(?:(#{SCHEME})://)+" + # Mandatory scheme:// (\1)
"(?:(#{USERINFO})@)?" + # Optional userinfo@ (\2)
"(#{ANY_HOSTNAME})" + # Mandatory hostname (\3)
"(?::(#{PORT}))?" + # Optional :port (\4)
"(#{ABS_PATH})?" + # Optional absolute path (\5)
"(?:\\?(#{QUERY}))?" + # Optional ?query (\6)
"(?:\\#(#{FRAGMENT}))?" # Optional #fragment (\7)
LOCAL_URI_REGEXP = Regexp.new(TEXTILE_SYNTAX_PREFIX + LOCAL_URI, Regexp::EXTENDED, 'N')
end
def LocalURIChunk.pattern
LOCAL_URI_REGEXP
end
end

284
app/models/chunks/wiki.rb Executable file → Normal file
View file

@ -1,142 +1,142 @@
require 'wiki_words'
require 'chunks/chunk'
require 'chunks/wiki'
require 'cgi'
# Contains all the methods for finding and replacing wiki related links.
module WikiChunk
include Chunk
# A wiki link is the top-level class for anything that refers to
# another wiki page.
class WikiLink < Chunk::Abstract
attr_reader :page_name, :link_text, :link_type
def initialize(*args)
super
@link_type = 'show'
end
def self.apply_to(content)
content.gsub!( self.pattern ) do |matched_text|
chunk = self.new($~)
if chunk.textile_url?
# do not substitute
matched_text
else
content.chunks << chunk
chunk.mask(content)
end
end
end
def textile_url?
not @textile_link_suffix.nil?
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
# This chunk matches a WikiWord. WikiWords can be escaped
# by prepending a '\'. When this is the case, the +escaped_text+
# method will return the WikiWord instead of the usual +nil+.
# The +page_name+ method returns the matched WikiWord.
class Word < WikiLink
unless defined? WIKI_LINK
WIKI_WORD = Regexp.new('(":)?(\\\\)?(' + WikiWords::WIKI_WORD_PATTERN + ')\b', 0, "utf-8")
end
def self.pattern
WIKI_WORD
end
def initialize(match_data)
super(match_data)
@textile_link_suffix, @escape, @page_name = match_data[1..3]
end
def escaped_text
page_name unless @escape.nil?
end
def link_text() WikiWords.separate(page_name) end
end
# This chunk handles [[bracketted wiki words]] and
# [[AliasedWords|aliased wiki words]]. The first part of an
# aliased wiki word must be a WikiWord. If the WikiWord
# is aliased, the +link_text+ field will contain the
# alias, otherwise +link_text+ will contain the entire
# contents within the double brackets.
#
# NOTE: This chunk must be tested before WikiWord since
# a WikiWords can be a substring of a WikiLink.
class Link < WikiLink
unless defined? WIKI_LINK
WIKI_LINK = /(":)?\[\[([^\]]+)\]\]/
LINK_TYPE_SEPARATION = Regexp.new('^(.+):((file)|(pic))$', 0, 'utf-8')
ALIAS_SEPARATION = Regexp.new('^(.+)\|(.+)$', 0, 'utf-8')
end
def self.pattern() WIKI_LINK end
def initialize(match_data)
super(match_data)
@textile_link_suffix, @page_name = match_data[1..2]
@link_text = @page_name
separate_link_type
separate_alias
end
private
# if link wihin the brackets has a form of [[filename:file]] or [[filename:pic]],
# this means a link to a picture or a file
def separate_link_type
link_type_match = LINK_TYPE_SEPARATION.match(@page_name)
if link_type_match
@link_text = @page_name = link_type_match[1]
@link_type = link_type_match[2..3].compact[0]
end
end
# link text may be different from page name. this will look like [[actual page|link text]]
def separate_alias
alias_match = ALIAS_SEPARATION.match(@page_name)
if alias_match
@page_name, @link_text = alias_match[1..2]
end
# note that [[filename|link text:file]] is also supported
end
end
end
require 'wiki_words'
require 'chunks/chunk'
require 'chunks/wiki'
require 'cgi'
# Contains all the methods for finding and replacing wiki related links.
module WikiChunk
include Chunk
# A wiki link is the top-level class for anything that refers to
# another wiki page.
class WikiLink < Chunk::Abstract
attr_reader :page_name, :link_text, :link_type
def initialize(*args)
super
@link_type = 'show'
end
def self.apply_to(content)
content.gsub!( self.pattern ) do |matched_text|
chunk = self.new($~)
if chunk.textile_url?
# do not substitute
matched_text
else
content.chunks << chunk
chunk.mask(content)
end
end
end
def textile_url?
not @textile_link_suffix.nil?
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
# This chunk matches a WikiWord. WikiWords can be escaped
# by prepending a '\'. When this is the case, the +escaped_text+
# method will return the WikiWord instead of the usual +nil+.
# The +page_name+ method returns the matched WikiWord.
class Word < WikiLink
unless defined? WIKI_LINK
WIKI_WORD = Regexp.new('(":)?(\\\\)?(' + WikiWords::WIKI_WORD_PATTERN + ')\b', 0, "utf-8")
end
def self.pattern
WIKI_WORD
end
def initialize(match_data)
super(match_data)
@textile_link_suffix, @escape, @page_name = match_data[1..3]
end
def escaped_text
page_name unless @escape.nil?
end
def link_text() WikiWords.separate(page_name) end
end
# This chunk handles [[bracketted wiki words]] and
# [[AliasedWords|aliased wiki words]]. The first part of an
# aliased wiki word must be a WikiWord. If the WikiWord
# is aliased, the +link_text+ field will contain the
# alias, otherwise +link_text+ will contain the entire
# contents within the double brackets.
#
# NOTE: This chunk must be tested before WikiWord since
# a WikiWords can be a substring of a WikiLink.
class Link < WikiLink
unless defined? WIKI_LINK
WIKI_LINK = /(":)?\[\[([^\]]+)\]\]/
LINK_TYPE_SEPARATION = Regexp.new('^(.+):((file)|(pic))$', 0, 'utf-8')
ALIAS_SEPARATION = Regexp.new('^(.+)\|(.+)$', 0, 'utf-8')
end
def self.pattern() WIKI_LINK end
def initialize(match_data)
super(match_data)
@textile_link_suffix, @page_name = match_data[1..2]
@link_text = @page_name
separate_link_type
separate_alias
end
private
# if link wihin the brackets has a form of [[filename:file]] or [[filename:pic]],
# this means a link to a picture or a file
def separate_link_type
link_type_match = LINK_TYPE_SEPARATION.match(@page_name)
if link_type_match
@link_text = @page_name = link_type_match[1]
@link_type = link_type_match[2..3].compact[0]
end
end
# link text may be different from page name. this will look like [[actual page|link text]]
def separate_alias
alias_match = ALIAS_SEPARATION.match(@page_name)
if alias_match
@page_name, @link_text = alias_match[1..2]
end
# note that [[filename|link text:file]] is also supported
end
end
end

View file

@ -1,45 +1,45 @@
require 'instiki_errors'
class FileYard
attr_reader :files_path
def initialize(files_path)
@files_path = files_path
@files = Dir["#{files_path}/*"].collect{|path| File.basename(path) if File.file?(path) }.compact
end
def upload_file(name, io)
sanitize_file_name(name)
if io.kind_of?(Tempfile)
io.close
FileUtils.mv(io.path, file_path(name))
else
File.open(file_path(name), 'wb') { |f| f.write(io.read) }
end
# just in case, restrict read access and prohibit write access to the uploaded file
FileUtils.chmod(0440, file_path(name))
end
def files
Dir["#{files_path}/*"].collect{|path| File.basename(path) if File.file?(path)}.compact
end
def has_file?(name)
files.include?(name)
end
def file_path(name)
"#{files_path}/#{name}"
end
SANE_FILE_NAME = /[-_\.A-Za-z0-9]{1,255}/
def sanitize_file_name(name)
unless name =~ SANE_FILE_NAME
raise Instiki::ValidationError.new("Invalid file name: '#{name}'.\n" +
"Only latin characters, digits, dots, underscores and dashes are accepted.")
end
end
end
require 'instiki_errors'
class FileYard
attr_reader :files_path
def initialize(files_path)
@files_path = files_path
@files = Dir["#{files_path}/*"].collect{|path| File.basename(path) if File.file?(path) }.compact
end
def upload_file(name, io)
sanitize_file_name(name)
if io.kind_of?(Tempfile)
io.close
FileUtils.mv(io.path, file_path(name))
else
File.open(file_path(name), 'wb') { |f| f.write(io.read) }
end
# just in case, restrict read access and prohibit write access to the uploaded file
FileUtils.chmod(0440, file_path(name))
end
def files
Dir["#{files_path}/*"].collect{|path| File.basename(path) if File.file?(path)}.compact
end
def has_file?(name)
files.include?(name)
end
def file_path(name)
"#{files_path}/#{name}"
end
SANE_FILE_NAME = /[-_\.A-Za-z0-9]{1,255}/
def sanitize_file_name(name)
unless name =~ SANE_FILE_NAME
raise Instiki::ValidationError.new("Invalid file name: '#{name}'.\n" +
"Only latin characters, digits, dots, underscores and dashes are accepted.")
end
end
end

184
app/models/page.rb Executable file → Normal file
View file

@ -1,92 +1,92 @@
require 'date'
require 'page_lock'
require 'revision'
require 'wiki_words'
require 'chunks/wiki'
class Page
include PageLock
attr_reader :name, :web
attr_accessor :revisions
def initialize(web, name, content, created_at, author)
@web, @name, @revisions = web, name, []
revise(content, created_at, author)
end
def revise(content, created_at, author)
if not @revisions.empty? and content == @revisions.last.content
raise Instiki::ValidationError.new(
"You have tried to save page '#{name}' without changing its content")
end
# A user may change a page, look at it and make some more changes - several times.
# Not to record every such iteration as a new revision, if the previous revision was done
# by the same author, not more than 30 minutes ago, then update the last revision instead of
# creating a new one
if !@revisions.empty? && continous_revision?(created_at, author)
@revisions.last.created_at = created_at
@revisions.last.content = content
@revisions.last.clear_display_cache
else
@revisions << Revision.new(self, @revisions.length, content, created_at, author)
end
web.refresh_pages_with_references(name) if @revisions.length == 1
end
def rollback(revision_number, created_at, author_ip = nil)
roll_back_revision = @revisions[revision_number].dup
revise(roll_back_revision.content, created_at, Author.new(roll_back_revision.author, author_ip))
end
def revisions?
revisions.length > 1
end
def revised_on
created_on
end
def in_category?(cat)
cat.nil? || cat.empty? || categories.include?(cat)
end
def categories
display_content.find_chunks(Category).map { |cat| cat.list }.flatten
end
def authors
revisions.collect { |rev| rev.author }
end
def references
web.select.pages_that_reference(name)
end
# Returns the original wiki-word name as separate words, so "MyPage" becomes "My Page".
def plain_name
web.brackets_only ? name : WikiWords.separate(name)
end
def link(options = {})
web.make_link(name, nil, options)
end
def author_link(options = {})
web.make_link(author, nil, options)
end
private
def continous_revision?(created_at, author)
@revisions.last.author == author && @revisions.last.created_at + 30.minutes > created_at
end
# Forward method calls to the current revision, so the page responds to all revision calls
def method_missing(method_symbol)
revisions.last.send(method_symbol)
end
end
require 'date'
require 'page_lock'
require 'revision'
require 'wiki_words'
require 'chunks/wiki'
class Page
include PageLock
attr_reader :name, :web
attr_accessor :revisions
def initialize(web, name, content, created_at, author)
@web, @name, @revisions = web, name, []
revise(content, created_at, author)
end
def revise(content, created_at, author)
if not @revisions.empty? and content == @revisions.last.content
raise Instiki::ValidationError.new(
"You have tried to save page '#{name}' without changing its content")
end
# A user may change a page, look at it and make some more changes - several times.
# Not to record every such iteration as a new revision, if the previous revision was done
# by the same author, not more than 30 minutes ago, then update the last revision instead of
# creating a new one
if !@revisions.empty? && continous_revision?(created_at, author)
@revisions.last.created_at = created_at
@revisions.last.content = content
@revisions.last.clear_display_cache
else
@revisions << Revision.new(self, @revisions.length, content, created_at, author)
end
web.refresh_pages_with_references(name) if @revisions.length == 1
end
def rollback(revision_number, created_at, author_ip = nil)
roll_back_revision = @revisions[revision_number].dup
revise(roll_back_revision.content, created_at, Author.new(roll_back_revision.author, author_ip))
end
def revisions?
revisions.length > 1
end
def revised_on
created_on
end
def in_category?(cat)
cat.nil? || cat.empty? || categories.include?(cat)
end
def categories
display_content.find_chunks(Category).map { |cat| cat.list }.flatten
end
def authors
revisions.collect { |rev| rev.author }
end
def references
web.select.pages_that_reference(name)
end
# Returns the original wiki-word name as separate words, so "MyPage" becomes "My Page".
def plain_name
web.brackets_only ? name : WikiWords.separate(name)
end
def link(options = {})
web.make_link(name, nil, options)
end
def author_link(options = {})
web.make_link(author, nil, options)
end
private
def continous_revision?(created_at, author)
@revisions.last.author == author && @revisions.last.created_at + 30.minutes > created_at
end
# Forward method calls to the current revision, so the page responds to all revision calls
def method_missing(method_symbol)
revisions.last.send(method_symbol)
end
end

46
app/models/page_lock.rb Executable file → Normal file
View file

@ -1,24 +1,24 @@
# Contains all the lock methods to be mixed in with the page
module PageLock
LOCKING_PERIOD = 30 * 60 # 30 minutes
def lock(time, locked_by)
@locked_at, @locked_by = time, locked_by
end
def lock_duration(time)
((time - @locked_at) / 60).to_i unless @locked_at.nil?
end
def unlock
@locked_at = nil
end
def locked?(comparison_time)
@locked_at + LOCKING_PERIOD > comparison_time unless @locked_at.nil?
end
def locked_by_link
web.make_link(@locked_by)
end
# Contains all the lock methods to be mixed in with the page
module PageLock
LOCKING_PERIOD = 30 * 60 # 30 minutes
def lock(time, locked_by)
@locked_at, @locked_by = time, locked_by
end
def lock_duration(time)
((time - @locked_at) / 60).to_i unless @locked_at.nil?
end
def unlock
@locked_at = nil
end
def locked?(comparison_time)
@locked_at + LOCKING_PERIOD > comparison_time unless @locked_at.nil?
end
def locked_by_link
web.make_link(@locked_by)
end
end

144
app/models/page_set.rb Executable file → Normal file
View file

@ -1,73 +1,73 @@
# Container for a set of pages with methods for manipulation.
class PageSet < Array
attr_reader :web
def initialize(web, pages = nil, condition = nil)
@web = web
# if pages is not specified, make a list of all pages in the web
if pages.nil?
super(web.pages.values)
# otherwise use specified pages and condition to produce a set of pages
elsif condition.nil?
super(pages)
else
super(pages.select { |page| condition[page] })
end
end
def most_recent_revision
self.map { |page| page.created_at }.max || Time.at(0)
end
def by_name
PageSet.new(@web, sort_by { |page| page.name })
end
alias :sort :by_name
def by_revision
PageSet.new(@web, sort_by { |page| page.created_at }).reverse
end
def pages_that_reference(page_name)
self.select { |page| page.wiki_words.include?(page_name) }
end
def pages_authored_by(author)
self.select { |page| page.authors.include?(author) }
end
def characters
self.inject(0) { |chars,page| chars += page.content.size }
end
# Returns all the orphaned pages in this page set. That is,
# pages in this set for which there is no reference in the web.
# The HomePage and author pages are always assumed to have
# references and so cannot be orphans
def orphaned_pages
references = web.select.wiki_words + ["HomePage"] + web.select.authors
self.reject { |page| references.include?(page.name) }
end
# Returns all the wiki words in this page set for which
# there are no pages in this page set's web
def wanted_pages
wiki_words - web.select.names
end
def names
self.map { |page| page.name }
end
def wiki_words
self.inject([]) { |wiki_words, page| wiki_words << page.wiki_words }.flatten.uniq
end
def authors
self.inject([]) { |authors, page| authors << page.authors }.flatten.uniq.sort
end
# Container for a set of pages with methods for manipulation.
class PageSet < Array
attr_reader :web
def initialize(web, pages = nil, condition = nil)
@web = web
# if pages is not specified, make a list of all pages in the web
if pages.nil?
super(web.pages.values)
# otherwise use specified pages and condition to produce a set of pages
elsif condition.nil?
super(pages)
else
super(pages.select { |page| condition[page] })
end
end
def most_recent_revision
self.map { |page| page.created_at }.max || Time.at(0)
end
def by_name
PageSet.new(@web, sort_by { |page| page.name })
end
alias :sort :by_name
def by_revision
PageSet.new(@web, sort_by { |page| page.created_at }).reverse
end
def pages_that_reference(page_name)
self.select { |page| page.wiki_words.include?(page_name) }
end
def pages_authored_by(author)
self.select { |page| page.authors.include?(author) }
end
def characters
self.inject(0) { |chars,page| chars += page.content.size }
end
# Returns all the orphaned pages in this page set. That is,
# pages in this set for which there is no reference in the web.
# The HomePage and author pages are always assumed to have
# references and so cannot be orphans
def orphaned_pages
references = web.select.wiki_words + ["HomePage"] + web.select.authors
self.reject { |page| references.include?(page.name) }
end
# Returns all the wiki words in this page set for which
# there are no pages in this page set's web
def wanted_pages
wiki_words - web.select.names
end
def names
self.map { |page| page.name }
end
def wiki_words
self.inject([]) { |wiki_words, page| wiki_words << page.wiki_words }.flatten.uniq
end
def authors
self.inject([]) { |authors, page| authors << page.authors }.flatten.uniq.sort
end
end

166
app/models/revision.rb Executable file → Normal file
View file

@ -1,83 +1,83 @@
require 'diff'
require 'wiki_content'
require 'chunks/wiki'
require 'date'
require 'author'
require 'page'
class Revision
attr_accessor :page, :number, :content, :created_at, :author
def initialize(page, number, content, created_at, author)
@page, @number, @created_at, @author = page, number, created_at, author
self.content = content
end
def created_on
Date.new(@created_at.year, @created_at.mon, @created_at.day)
end
def pretty_created_at
# Must use DateTime because Time doesn't support %e on at least some platforms
DateTime.new(
@created_at.year, @created_at.mon, @created_at.day, @created_at.hour, @created_at.min
).strftime "%B %e, %Y %H:%M"
end
def next_revision
page.revisions[number + 1]
end
def previous_revision
number > 0 ? page.revisions[number - 1] : nil
end
# Returns an array of all the WikiWords present in the content of this revision.
def wiki_words
unless @wiki_words_cache
wiki_chunks = display_content.find_chunks(WikiChunk::WikiLink)
@wiki_words_cache = wiki_chunks.map { |c| ( c.escaped_text ? nil : c.page_name ) }.compact.uniq
end
@wiki_words_cache
end
# Returns an array of all the WikiWords present in the content of this revision.
# that already exists as a page in the web.
def existing_pages
wiki_words.select { |wiki_word| page.web.pages[wiki_word] }
end
# Returns an array of all the WikiWords present in the content of this revision
# that *doesn't* already exists as a page in the web.
def unexisting_pages
wiki_words - existing_pages
end
# Explicit check for new type of display cache with find_chunks method.
# Ensures new version works with older snapshots.
def display_content
unless @display_cache && @display_cache.respond_to?(:find_chunks)
@display_cache = WikiContent.new(self)
end
@display_cache
end
def display_diff
previous_revision ? HTMLDiff.diff(previous_revision.display_content, display_content) : display_content
end
def clear_display_cache
@display_cache = @published_cache = @wiki_words_cache = nil
end
def display_published
@published_cache = WikiContent.new(self, {:mode => :publish}) if @published_cache.nil?
@published_cache
end
def display_content_for_export
WikiContent.new(self, {:mode => :export} )
end
end
require 'diff'
require 'wiki_content'
require 'chunks/wiki'
require 'date'
require 'author'
require 'page'
class Revision
attr_accessor :page, :number, :content, :created_at, :author
def initialize(page, number, content, created_at, author)
@page, @number, @created_at, @author = page, number, created_at, author
self.content = content
end
def created_on
Date.new(@created_at.year, @created_at.mon, @created_at.day)
end
def pretty_created_at
# Must use DateTime because Time doesn't support %e on at least some platforms
DateTime.new(
@created_at.year, @created_at.mon, @created_at.day, @created_at.hour, @created_at.min
).strftime "%B %e, %Y %H:%M"
end
def next_revision
page.revisions[number + 1]
end
def previous_revision
number > 0 ? page.revisions[number - 1] : nil
end
# Returns an array of all the WikiWords present in the content of this revision.
def wiki_words
unless @wiki_words_cache
wiki_chunks = display_content.find_chunks(WikiChunk::WikiLink)
@wiki_words_cache = wiki_chunks.map { |c| ( c.escaped_text ? nil : c.page_name ) }.compact.uniq
end
@wiki_words_cache
end
# Returns an array of all the WikiWords present in the content of this revision.
# that already exists as a page in the web.
def existing_pages
wiki_words.select { |wiki_word| page.web.pages[wiki_word] }
end
# Returns an array of all the WikiWords present in the content of this revision
# that *doesn't* already exists as a page in the web.
def unexisting_pages
wiki_words - existing_pages
end
# Explicit check for new type of display cache with find_chunks method.
# Ensures new version works with older snapshots.
def display_content
unless @display_cache && @display_cache.respond_to?(:find_chunks)
@display_cache = WikiContent.new(self)
end
@display_cache
end
def display_diff
previous_revision ? HTMLDiff.diff(previous_revision.display_content, display_content) : display_content
end
def clear_display_cache
@display_cache = @published_cache = @wiki_words_cache = nil
end
def display_published
@published_cache = WikiContent.new(self, {:mode => :publish}) if @published_cache.nil?
@published_cache
end
def display_content_for_export
WikiContent.new(self, {:mode => :export} )
end
end

308
app/models/web.rb Executable file → Normal file
View file

@ -1,155 +1,155 @@
require "cgi"
require "page"
require "page_set"
require "wiki_words"
require "zip/zip"
class Web
attr_accessor :name, :address, :password, :markup, :color, :safe_mode, :pages
attr_accessor :additional_style, :published, :brackets_only, :count_pages, :allow_uploads
def initialize(parent_wiki, name, address, password = nil)
@wiki, @name, @address, @password = parent_wiki, name, address, password
# default values
@markup = :textile
@color = '008B26'
@safe_mode = false
@pages = {}
@allow_uploads = true
@additional_style = nil
@published = false
@brackets_only = false
@count_pages = false
@allow_uploads = true
end
def add_page(page)
@pages[page.name] = page
end
def remove_pages(pages_to_be_removed)
pages.delete_if { |page_name, page| pages_to_be_removed.include?(page) }
end
def select(&condition)
PageSet.new(self, @pages.values, condition)
end
def revised_on
select.most_recent_revision
end
def authors
select.authors
end
def categories
select.map { |page| page.categories }.flatten.uniq.sort
end
# Create a link for the given page name and link text based
# on the render mode in options and whether the page exists
# in the this web.
def make_link(name, text = nil, options = {})
text = CGI.escapeHTML(text || WikiWords.separate(name))
mode = options[:mode]
link_type = options[:link_type] || 'show'
case link_type
when 'show'
make_page_link(mode, name, text)
when 'file'
make_file_link(mode, name, text)
when 'pic'
make_pic_link(mode, name, text)
else
raise "Unknown link type: #{link_type}"
end
end
def make_page_link(mode, name, text)
link = CGI.escape(name)
case mode
when :export
if has_page?(name) then "<a class=\"existingWikiWord\" href=\"#{link}.html\">#{text}</a>"
else "<span class=\"newWikiWord\">#{text}</span>" end
when :publish
if has_page?(name) then "<a class=\"existingWikiWord\" href=\"../published/#{link}\">#{text}</a>"
else "<span class=\"newWikiWord\">#{text}</span>" end
else
if has_page?(name)
"<a class=\"existingWikiWord\" href=\"../show/#{link}\">#{text}</a>"
else
"<span class=\"newWikiWord\">#{text}<a href=\"../show/#{link}\">?</a></span>"
end
end
end
def make_file_link(mode, name, text)
link = CGI.escape(name)
case mode
when :export
if has_file?(name) then "<a class=\"existingWikiWord\" href=\"#{link}.html\">#{text}</a>"
else "<span class=\"newWikiWord\">#{text}</span>" end
when :publish
if has_file?(name) then "<a class=\"existingWikiWord\" href=\"../published/#{link}\">#{text}</a>"
else "<span class=\"newWikiWord\">#{text}</span>" end
else
if has_file?(name)
"<a class=\"existingWikiWord\" href=\"../file/#{link}\">#{text}</a>"
else
"<span class=\"newWikiWord\">#{text}<a href=\"../file/#{link}\">?</a></span>"
end
end
end
def make_pic_link(mode, name, text)
link = CGI.escape(name)
case mode
when :export
if has_file?(name) then "<img alt=\"#{text}\" src=\"#{link}\" />"
else "<img alt=\"#{text}\" src=\"no image\" />" end
when :publish
if has_file?(name) then "<img alt=\"#{text}\" src=\"#{link}\" />"
else "<span class=\"newWikiWord\">#{text}</span>" end
else
if has_file?(name) then "<img alt=\"#{text}\" src=\"../pic/#{link}\" />"
else "<span class=\"newWikiWord\">#{text}<a href=\"../pic/#{link}\">?</a></span>" end
end
end
def has_page?(name)
pages[name]
end
def has_file?(name)
wiki.file_yard(self).has_file?(name)
end
# Clears the display cache for all the pages with references to
def refresh_pages_with_references(page_name)
select.pages_that_reference(page_name).each { |page|
page.revisions.each { |revision| revision.clear_display_cache }
}
end
def refresh_revisions
select.each { |page| page.revisions.each { |revision| revision.clear_display_cache } }
end
private
# Returns an array of all the wiki words in any current revision
def wiki_words
pages.values.inject([]) { |wiki_words, page| wiki_words << page.wiki_words }.flatten.uniq
end
# Returns an array of all the page names on this web
def page_names
pages.keys
end
# This ensures compatibility with 0.9 storages
def wiki
@wiki ||= WikiService.instance
end
require "cgi"
require "page"
require "page_set"
require "wiki_words"
require "zip/zip"
class Web
attr_accessor :name, :address, :password, :markup, :color, :safe_mode, :pages
attr_accessor :additional_style, :published, :brackets_only, :count_pages, :allow_uploads
def initialize(parent_wiki, name, address, password = nil)
@wiki, @name, @address, @password = parent_wiki, name, address, password
# default values
@markup = :textile
@color = '008B26'
@safe_mode = false
@pages = {}
@allow_uploads = true
@additional_style = nil
@published = false
@brackets_only = false
@count_pages = false
@allow_uploads = true
end
def add_page(page)
@pages[page.name] = page
end
def remove_pages(pages_to_be_removed)
pages.delete_if { |page_name, page| pages_to_be_removed.include?(page) }
end
def select(&condition)
PageSet.new(self, @pages.values, condition)
end
def revised_on
select.most_recent_revision
end
def authors
select.authors
end
def categories
select.map { |page| page.categories }.flatten.uniq.sort
end
# Create a link for the given page name and link text based
# on the render mode in options and whether the page exists
# in the this web.
def make_link(name, text = nil, options = {})
text = CGI.escapeHTML(text || WikiWords.separate(name))
mode = options[:mode]
link_type = options[:link_type] || 'show'
case link_type
when 'show'
make_page_link(mode, name, text)
when 'file'
make_file_link(mode, name, text)
when 'pic'
make_pic_link(mode, name, text)
else
raise "Unknown link type: #{link_type}"
end
end
def make_page_link(mode, name, text)
link = CGI.escape(name)
case mode
when :export
if has_page?(name) then "<a class=\"existingWikiWord\" href=\"#{link}.html\">#{text}</a>"
else "<span class=\"newWikiWord\">#{text}</span>" end
when :publish
if has_page?(name) then "<a class=\"existingWikiWord\" href=\"../published/#{link}\">#{text}</a>"
else "<span class=\"newWikiWord\">#{text}</span>" end
else
if has_page?(name)
"<a class=\"existingWikiWord\" href=\"../show/#{link}\">#{text}</a>"
else
"<span class=\"newWikiWord\">#{text}<a href=\"../show/#{link}\">?</a></span>"
end
end
end
def make_file_link(mode, name, text)
link = CGI.escape(name)
case mode
when :export
if has_file?(name) then "<a class=\"existingWikiWord\" href=\"#{link}.html\">#{text}</a>"
else "<span class=\"newWikiWord\">#{text}</span>" end
when :publish
if has_file?(name) then "<a class=\"existingWikiWord\" href=\"../published/#{link}\">#{text}</a>"
else "<span class=\"newWikiWord\">#{text}</span>" end
else
if has_file?(name)
"<a class=\"existingWikiWord\" href=\"../file/#{link}\">#{text}</a>"
else
"<span class=\"newWikiWord\">#{text}<a href=\"../file/#{link}\">?</a></span>"
end
end
end
def make_pic_link(mode, name, text)
link = CGI.escape(name)
case mode
when :export
if has_file?(name) then "<img alt=\"#{text}\" src=\"#{link}\" />"
else "<img alt=\"#{text}\" src=\"no image\" />" end
when :publish
if has_file?(name) then "<img alt=\"#{text}\" src=\"#{link}\" />"
else "<span class=\"newWikiWord\">#{text}</span>" end
else
if has_file?(name) then "<img alt=\"#{text}\" src=\"../pic/#{link}\" />"
else "<span class=\"newWikiWord\">#{text}<a href=\"../pic/#{link}\">?</a></span>" end
end
end
def has_page?(name)
pages[name]
end
def has_file?(name)
wiki.file_yard(self).has_file?(name)
end
# Clears the display cache for all the pages with references to
def refresh_pages_with_references(page_name)
select.pages_that_reference(page_name).each { |page|
page.revisions.each { |revision| revision.clear_display_cache }
}
end
def refresh_revisions
select.each { |page| page.revisions.each { |revision| revision.clear_display_cache } }
end
private
# Returns an array of all the wiki words in any current revision
def wiki_words
pages.values.inject([]) { |wiki_words, page| wiki_words << page.wiki_words }.flatten.uniq
end
# Returns an array of all the page names on this web
def page_names
pages.keys
end
# This ensures compatibility with 0.9 storages
def wiki
@wiki ||= WikiService.instance
end
end

192
app/models/wiki_content.rb Executable file → Normal file
View file

@ -1,97 +1,97 @@
require 'cgi'
require 'chunks/engines'
require 'chunks/category'
require 'chunks/include'
require 'chunks/wiki'
require 'chunks/literal'
require 'chunks/uri'
require 'chunks/nowiki'
# Wiki content is just a string that can process itself with a chain of
# actions. The actions can modify wiki content so that certain parts of
# it are protected from being rendered by later actions.
#
# When wiki content is rendered, it can be interrogated to find out
# which chunks were rendered. This means things like categories, wiki
# links, can be determined.
#
# Exactly how wiki content is rendered is determined by a number of
# settings that are optionally passed in to a constructor. The current
# options are:
# * :engine
# => The structural markup engine to use (Textile, Markdown, RDoc)
# * :engine_opts
# => A list of options to pass to the markup engines (safe modes, etc)
# * :pre_engine_actions
# => A list of render actions or chunks to be processed before the
# markup engine is applied. By default this is:
# Category, Include, URIChunk, WikiChunk::Link, WikiChunk::Word
# * :post_engine_actions
# => A list of render actions or chunks to apply after the markup
# engine. By default these are:
# Literal::Pre, Literal::Tags
# * :mode
# => How should the content be rendered? For normal display (:display),
# publishing (:publish) or export (:export)?
#
# AUTHOR: Mark Reid <mark @ threewordslong . com>
# CREATED: 15th May 2004
# UPDATED: 22nd May 2004
class WikiContent < String
PRE_ENGINE_ACTIONS = [ NoWiki, Category, Include, WikiChunk::Link, URIChunk, LocalURIChunk,
WikiChunk::Word ]
POST_ENGINE_ACTIONS = [ Literal::Pre, Literal::Tags ]
DEFAULT_OPTS = {
:pre_engine_actions => PRE_ENGINE_ACTIONS,
:post_engine_actions => POST_ENGINE_ACTIONS,
:engine => Engines::Textile,
:engine_opts => [],
:mode => [:display]
}
attr_reader :web, :options, :rendered, :chunks
# Create a new wiki content string from the given one.
# The options are explained at the top of this file.
def initialize(revision, options = {})
@revision = revision
@web = @revision.page.web
# Deep copy of DEFAULT_OPTS to ensure that changes to PRE/POST_ENGINE_ACTIONS stay local
@options = Marshal.load(Marshal.dump(DEFAULT_OPTS)).update(options)
@options[:engine] = Engines::MAP[@web.markup] || Engines::Textile
@options[:engine_opts] = (@web.safe_mode ? [:filter_html, :filter_styles] : [])
@options[:pre_engine_actions].delete(WikiChunk::Word) if @web.brackets_only
super(@revision.content)
begin
render!(@options[:pre_engine_actions] + [@options[:engine]] + @options[:post_engine_actions])
# FIXME this is where all the parsing problems were shoved under the carpet
# rescue => e
# @rendered = e.message
end
end
# Call @web.page_link using current options.
def page_link(name, text, link_type)
@options[:link_type] = link_type || :show
@web.make_link(name, text, @options)
end
# Find all the chunks of the given types
def find_chunks(chunk_type)
rendered.select { |chunk| chunk.kind_of?(chunk_type) }
end
# Render this content using the specified actions.
def render!(chunk_types)
@chunks = []
chunk_types.each { |chunk_type| chunk_type.apply_to(self) }
@rendered = @chunks.map { |chunk| chunk.unmask(self) }.compact
(@chunks - @rendered).each { |chunk| chunk.revert(self) }
end
require 'cgi'
require 'chunks/engines'
require 'chunks/category'
require 'chunks/include'
require 'chunks/wiki'
require 'chunks/literal'
require 'chunks/uri'
require 'chunks/nowiki'
# Wiki content is just a string that can process itself with a chain of
# actions. The actions can modify wiki content so that certain parts of
# it are protected from being rendered by later actions.
#
# When wiki content is rendered, it can be interrogated to find out
# which chunks were rendered. This means things like categories, wiki
# links, can be determined.
#
# Exactly how wiki content is rendered is determined by a number of
# settings that are optionally passed in to a constructor. The current
# options are:
# * :engine
# => The structural markup engine to use (Textile, Markdown, RDoc)
# * :engine_opts
# => A list of options to pass to the markup engines (safe modes, etc)
# * :pre_engine_actions
# => A list of render actions or chunks to be processed before the
# markup engine is applied. By default this is:
# Category, Include, URIChunk, WikiChunk::Link, WikiChunk::Word
# * :post_engine_actions
# => A list of render actions or chunks to apply after the markup
# engine. By default these are:
# Literal::Pre, Literal::Tags
# * :mode
# => How should the content be rendered? For normal display (:display),
# publishing (:publish) or export (:export)?
#
# AUTHOR: Mark Reid <mark @ threewordslong . com>
# CREATED: 15th May 2004
# UPDATED: 22nd May 2004
class WikiContent < String
PRE_ENGINE_ACTIONS = [ NoWiki, Category, Include, WikiChunk::Link, URIChunk, LocalURIChunk,
WikiChunk::Word ]
POST_ENGINE_ACTIONS = [ Literal::Pre, Literal::Tags ]
DEFAULT_OPTS = {
:pre_engine_actions => PRE_ENGINE_ACTIONS,
:post_engine_actions => POST_ENGINE_ACTIONS,
:engine => Engines::Textile,
:engine_opts => [],
:mode => [:display]
}
attr_reader :web, :options, :rendered, :chunks
# Create a new wiki content string from the given one.
# The options are explained at the top of this file.
def initialize(revision, options = {})
@revision = revision
@web = @revision.page.web
# Deep copy of DEFAULT_OPTS to ensure that changes to PRE/POST_ENGINE_ACTIONS stay local
@options = Marshal.load(Marshal.dump(DEFAULT_OPTS)).update(options)
@options[:engine] = Engines::MAP[@web.markup] || Engines::Textile
@options[:engine_opts] = (@web.safe_mode ? [:filter_html, :filter_styles] : [])
@options[:pre_engine_actions].delete(WikiChunk::Word) if @web.brackets_only
super(@revision.content)
begin
render!(@options[:pre_engine_actions] + [@options[:engine]] + @options[:post_engine_actions])
# FIXME this is where all the parsing problems were shoved under the carpet
# rescue => e
# @rendered = e.message
end
end
# Call @web.page_link using current options.
def page_link(name, text, link_type)
@options[:link_type] = link_type || :show
@web.make_link(name, text, @options)
end
# Find all the chunks of the given types
def find_chunks(chunk_type)
rendered.select { |chunk| chunk.kind_of?(chunk_type) }
end
# Render this content using the specified actions.
def render!(chunk_types)
@chunks = []
chunk_types.each { |chunk_type| chunk_type.apply_to(self) }
@rendered = @chunks.map { |chunk| chunk.unmask(self) }.compact
(@chunks - @rendered).each { |chunk| chunk.revert(self) }
end
end

444
app/models/wiki_service.rb Executable file → Normal file
View file

@ -1,222 +1,222 @@
require 'open-uri'
require 'yaml'
require 'madeleine'
require 'madeleine/automatic'
require 'madeleine/zmarshal'
require 'web'
require 'page'
require 'author'
require 'file_yard'
module AbstractWikiService
attr_reader :webs, :system
def authenticate(password)
password == (@system[:password] || 'instiki')
end
def create_web(name, address, password = nil)
@webs[address] = Web.new(self, name, address, password) unless @webs[address]
end
def delete_web(address)
@webs[address] = nil
end
def file_yard(web)
raise "Web #{@web.name} does not belong to this wiki service" unless @webs.values.include?(web)
# TODO cache FileYards
FileYard.new("#{self.storage_path}/#{web.address}")
end
def init_wiki_service
@webs = {}
@system = {}
end
def read_page(web_address, page_name)
ApplicationController.logger.debug "Reading page '#{page_name}' from web '#{web_address}'"
web = @webs[web_address]
if web.nil?
ApplicationController.logger.debug "Web '#{web_address}' not found"
return nil
else
page = web.pages[page_name]
ApplicationController.logger.debug "Page '#{page_name}' #{page.nil? ? 'not' : ''} found"
return page
end
end
def remove_orphaned_pages(web_address)
@webs[web_address].remove_pages(@webs[web_address].select.orphaned_pages)
end
def revise_page(web_address, page_name, content, revised_on, author)
page = read_page(web_address, page_name)
page.revise(content, revised_on, author)
page
end
def rollback_page(web_address, page_name, revision_number, created_at, author_id = nil)
page = read_page(web_address, page_name)
page.rollback(revision_number, created_at, author_id)
page
end
def setup(password, web_name, web_address)
@system[:password] = password
create_web(web_name, web_address)
end
def setup?
not (@webs.empty?)
end
def update_web(old_address, new_address, name, markup, color, additional_style, safe_mode = false,
password = nil, published = false, brackets_only = false, count_pages = false,
allow_uploads = true)
if old_address != new_address
@webs[new_address] = @webs[old_address]
@webs.delete(old_address)
@webs[new_address].address = new_address
end
web = @webs[new_address]
web.refresh_revisions if settings_changed?(web, markup, safe_mode, brackets_only)
web.name, web.markup, web.color, web.additional_style, web.safe_mode =
name, markup, color, additional_style, safe_mode
web.password, web.published, web.brackets_only, web.count_pages, web.allow_uploads =
password, published, brackets_only, count_pages, allow_uploads
end
def write_page(web_address, page_name, content, written_on, author)
page = Page.new(@webs[web_address], page_name, content, written_on, author)
@webs[web_address].add_page(page)
page
end
def storage_path
self.class.storage_path
end
private
def settings_changed?(web, markup, safe_mode, brackets_only)
web.markup != markup ||
web.safe_mode != safe_mode ||
web.brackets_only != brackets_only
end
end
class WikiService
include AbstractWikiService
include Madeleine::Automatic::Interceptor
# These methods do not change the state of persistent objects, and
# should not be ogged by Madeleine
automatic_read_only :authenticate, :read_page, :setup?, :webs, :storage_path, :file_yard
@@storage_path = './storage/'
class << self
def storage_path=(storage_path)
@@storage_path = storage_path
end
def storage_path
@@storage_path
end
def clean_storage
MadeleineServer.clean_storage(self)
end
def instance
@madeleine ||= MadeleineServer.new(self)
@system = @madeleine.system
return @system
end
def snapshot
@madeleine.snapshot
end
end
def initialize
init_wiki_service
end
end
class MadeleineServer
attr_reader :storage_path
# Clears all the command_log and snapshot files located in the storage directory, so the
# database is essentially dropped and recreated as blank
def self.clean_storage(service)
begin
Dir.foreach(service.storage_path) do |file|
if file =~ /(command_log|snapshot)$/
File.delete(File.join(service.storage_path, file))
end
end
rescue
Dir.mkdir(service.storage_path)
end
end
def initialize(service)
@storage_path = service.storage_path
@server = Madeleine::Automatic::AutomaticSnapshotMadeleine.new(service.storage_path,
Madeleine::ZMarshal.new) {
service.new
}
start_snapshot_thread
end
def command_log_present?
not Dir[storage_path + '/*.command_log'].empty?
end
def snapshot
@server.take_snapshot
end
def start_snapshot_thread
Thread.new(@server) {
hours_since_last_snapshot = 0
while true
begin
hours_since_last_snapshot += 1
# Take a snapshot if there is a command log, or 24 hours
# have passed since the last snapshot
if command_log_present? or hours_since_last_snapshot >= 24
ActionController::Base.logger.info "[#{Time.now.strftime('%Y-%m-%d %H:%M:%S')}] " +
'Taking a Madeleine snapshot'
snapshot
hours_since_last_snapshot = 0
end
sleep(1.hour)
rescue => e
ActionController::Base.logger.error(e)
# wait for a minute (not to spoof the log with the same error)
# and go back into the loop, to keep trying
sleep(1.minute)
ActionController::Base.logger.info("Retrying to save a snapshot")
end
end
}
end
def system
@server.system
end
end
require 'open-uri'
require 'yaml'
require 'madeleine'
require 'madeleine/automatic'
require 'madeleine/zmarshal'
require 'web'
require 'page'
require 'author'
require 'file_yard'
module AbstractWikiService
attr_reader :webs, :system
def authenticate(password)
password == (@system[:password] || 'instiki')
end
def create_web(name, address, password = nil)
@webs[address] = Web.new(self, name, address, password) unless @webs[address]
end
def delete_web(address)
@webs[address] = nil
end
def file_yard(web)
raise "Web #{@web.name} does not belong to this wiki service" unless @webs.values.include?(web)
# TODO cache FileYards
FileYard.new("#{self.storage_path}/#{web.address}")
end
def init_wiki_service
@webs = {}
@system = {}
end
def read_page(web_address, page_name)
ApplicationController.logger.debug "Reading page '#{page_name}' from web '#{web_address}'"
web = @webs[web_address]
if web.nil?
ApplicationController.logger.debug "Web '#{web_address}' not found"
return nil
else
page = web.pages[page_name]
ApplicationController.logger.debug "Page '#{page_name}' #{page.nil? ? 'not' : ''} found"
return page
end
end
def remove_orphaned_pages(web_address)
@webs[web_address].remove_pages(@webs[web_address].select.orphaned_pages)
end
def revise_page(web_address, page_name, content, revised_on, author)
page = read_page(web_address, page_name)
page.revise(content, revised_on, author)
page
end
def rollback_page(web_address, page_name, revision_number, created_at, author_id = nil)
page = read_page(web_address, page_name)
page.rollback(revision_number, created_at, author_id)
page
end
def setup(password, web_name, web_address)
@system[:password] = password
create_web(web_name, web_address)
end
def setup?
not (@webs.empty?)
end
def update_web(old_address, new_address, name, markup, color, additional_style, safe_mode = false,
password = nil, published = false, brackets_only = false, count_pages = false,
allow_uploads = true)
if old_address != new_address
@webs[new_address] = @webs[old_address]
@webs.delete(old_address)
@webs[new_address].address = new_address
end
web = @webs[new_address]
web.refresh_revisions if settings_changed?(web, markup, safe_mode, brackets_only)
web.name, web.markup, web.color, web.additional_style, web.safe_mode =
name, markup, color, additional_style, safe_mode
web.password, web.published, web.brackets_only, web.count_pages, web.allow_uploads =
password, published, brackets_only, count_pages, allow_uploads
end
def write_page(web_address, page_name, content, written_on, author)
page = Page.new(@webs[web_address], page_name, content, written_on, author)
@webs[web_address].add_page(page)
page
end
def storage_path
self.class.storage_path
end
private
def settings_changed?(web, markup, safe_mode, brackets_only)
web.markup != markup ||
web.safe_mode != safe_mode ||
web.brackets_only != brackets_only
end
end
class WikiService
include AbstractWikiService
include Madeleine::Automatic::Interceptor
# These methods do not change the state of persistent objects, and
# should not be ogged by Madeleine
automatic_read_only :authenticate, :read_page, :setup?, :webs, :storage_path, :file_yard
@@storage_path = './storage/'
class << self
def storage_path=(storage_path)
@@storage_path = storage_path
end
def storage_path
@@storage_path
end
def clean_storage
MadeleineServer.clean_storage(self)
end
def instance
@madeleine ||= MadeleineServer.new(self)
@system = @madeleine.system
return @system
end
def snapshot
@madeleine.snapshot
end
end
def initialize
init_wiki_service
end
end
class MadeleineServer
attr_reader :storage_path
# Clears all the command_log and snapshot files located in the storage directory, so the
# database is essentially dropped and recreated as blank
def self.clean_storage(service)
begin
Dir.foreach(service.storage_path) do |file|
if file =~ /(command_log|snapshot)$/
File.delete(File.join(service.storage_path, file))
end
end
rescue
Dir.mkdir(service.storage_path)
end
end
def initialize(service)
@storage_path = service.storage_path
@server = Madeleine::Automatic::AutomaticSnapshotMadeleine.new(service.storage_path,
Madeleine::ZMarshal.new) {
service.new
}
start_snapshot_thread
end
def command_log_present?
not Dir[storage_path + '/*.command_log'].empty?
end
def snapshot
@server.take_snapshot
end
def start_snapshot_thread
Thread.new(@server) {
hours_since_last_snapshot = 0
while true
begin
hours_since_last_snapshot += 1
# Take a snapshot if there is a command log, or 24 hours
# have passed since the last snapshot
if command_log_present? or hours_since_last_snapshot >= 24
ActionController::Base.logger.info "[#{Time.now.strftime('%Y-%m-%d %H:%M:%S')}] " +
'Taking a Madeleine snapshot'
snapshot
hours_since_last_snapshot = 0
end
sleep(1.hour)
rescue => e
ActionController::Base.logger.error(e)
# wait for a minute (not to spoof the log with the same error)
# and go back into the loop, to keep trying
sleep(1.minute)
ActionController::Base.logger.info("Retrying to save a snapshot")
end
end
}
end
def system
@server.system
end
end

46
app/models/wiki_words.rb Executable file → Normal file
View file

@ -1,23 +1,23 @@
# Contains all the methods for finding and replacing wiki words
module WikiWords
# In order of appearance: Latin, greek, cyrillian, armenian
I18N_HIGHER_CASE_LETTERS =
"À<EFBFBD>?ÂÃÄÅĀĄĂÆÇĆČĈĊĎ<C48A>?ÈÉÊËĒĘĚĔĖĜĞĠĢĤĦÌ<C4A6><>?ĪĨĬĮİIJĴĶ<C4B4>?ĽĹĻĿÑŃŇŅŊÒÓÔÕÖØŌ<C398>?ŎŒŔŘŖŚŠŞŜȘŤŢŦȚÙÚÛÜŪŮŰŬŨŲŴ<C5B2>?ŶŸŹŽŻ" +
"ΑΒΓΔΕΖΗΘΙΚΛΜ<EFBFBD>?ΞΟΠΡΣΤΥΦΧΨΩ" +
"ΆΈΉΊΌΎ<EFBFBD>?ѠѢѤѦѨѪѬѮѰѲѴѶѸѺѼѾҀҊҌҎ<D28C>?ҒҔҖҘҚҜҞҠҢҤҦҨҪҬҮҰҲҴҶҸҺҼҾ<D2BC>?ӃӅӇӉӋ<D389>?<3F>?ӒӔӖӘӚӜӞӠӢӤӦӨӪӬӮӰӲӴӸЖ" +
"ԱԲԳԴԵԶԷԸԹԺԻԼԽԾԿՀ<EFBFBD>?ՂՃՄՅՆՇՈՉՊՋՌ<D58B>?<3F>?<3F>?ՑՒՓՔՕՖ"
I18N_LOWER_CASE_LETTERS =
"àáâãäå<EFBFBD>?ąăæçć<C3A7>?ĉċ<C489>?đèéêëēęěĕėƒ<C497>?ğġģĥħìíîïīĩĭįıijĵķĸłľĺļŀñńňņʼnŋòóôõöø<C3B6><>?œŕřŗśšş<C5A1>?șťţŧțùúûüūůűŭũųŵýÿŷžżźÞþßſ<C39F>" +
"άέήίΰαβγδεζηθικλμνξοπ<EFBFBD>στυφχψωϊϋό<CF8B><>?" +
"абвгдежзийклмнопр<EFBFBD>уфхцчшщъыь<D18B><>?<3F>?ёђѓєѕіїјљћќ<D19B>?ўџѡѣѥѧѩѫѭѯѱѳѵѷѹѻѽѿ<D1BD><>?<3F>?ґғҕҗҙқ<D299>?ҟҡңҥҧҩҫҭүұҳҵҷҹһҽҿӀӂӄӆӈӊӌӎӑӓӕӗәӛ<D399>?ӟӡӣӥӧөӫӭӯӱӳӵӹ" +
"աբգդեզէըթժիլխծկհձղճմյնշոչպջռսվտր<EFBFBD>?ւփքօֆև"
WIKI_WORD_PATTERN = '[A-Z' + I18N_HIGHER_CASE_LETTERS + '][a-z' + I18N_LOWER_CASE_LETTERS + ']+[A-Z' + I18N_HIGHER_CASE_LETTERS + ']\w+'
CAMEL_CASED_WORD_BORDER = /([a-z#{I18N_LOWER_CASE_LETTERS}])([A-Z#{I18N_HIGHER_CASE_LETTERS}])/u
def self.separate(wiki_word)
wiki_word.gsub(CAMEL_CASED_WORD_BORDER, '\1 \2')
end
end
# Contains all the methods for finding and replacing wiki words
module WikiWords
# In order of appearance: Latin, greek, cyrillian, armenian
I18N_HIGHER_CASE_LETTERS =
"À<EFBFBD>?ÂÃÄÅĀĄĂÆÇĆČĈĊĎ<C48A>?ÈÉÊËĒĘĚĔĖĜĞĠĢĤĦÌ<C4A6><>?ĪĨĬĮİIJĴĶ<C4B4>?ĽĹĻĿÑŃŇŅŊÒÓÔÕÖØŌ<C398>?ŎŒŔŘŖŚŠŞŜȘŤŢŦȚÙÚÛÜŪŮŰŬŨŲŴ<C5B2>?ŶŸŹŽŻ" +
"ΑΒΓΔΕΖΗΘΙΚΛΜ<EFBFBD>?ΞΟΠΡΣΤΥΦΧΨΩ" +
"ΆΈΉΊΌΎ<EFBFBD>?ѠѢѤѦѨѪѬѮѰѲѴѶѸѺѼѾҀҊҌҎ<D28C>?ҒҔҖҘҚҜҞҠҢҤҦҨҪҬҮҰҲҴҶҸҺҼҾ<D2BC>?ӃӅӇӉӋ<D389>?<3F>?ӒӔӖӘӚӜӞӠӢӤӦӨӪӬӮӰӲӴӸЖ" +
"ԱԲԳԴԵԶԷԸԹԺԻԼԽԾԿՀ<EFBFBD>?ՂՃՄՅՆՇՈՉՊՋՌ<D58B>?<3F>?<3F>?ՑՒՓՔՕՖ"
I18N_LOWER_CASE_LETTERS =
"àáâãäå<EFBFBD>?ąăæçć<C3A7>?ĉċ<C489>?đèéêëēęěĕėƒ<C497>?ğġģĥħìíîïīĩĭįıijĵķĸłľĺļŀñńňņʼnŋòóôõöø<C3B6><>?œŕřŗśšş<C5A1>?șťţŧțùúûüūůűŭũųŵýÿŷžżźÞþßſ<C39F>" +
"άέήίΰαβγδεζηθικλμνξοπ<EFBFBD>στυφχψωϊϋό<CF8B><>?" +
"абвгдежзийклмнопр<EFBFBD>уфхцчшщъыь<D18B><>?<3F>?ёђѓєѕіїјљћќ<D19B>?ўџѡѣѥѧѩѫѭѯѱѳѵѷѹѻѽѿ<D1BD><>?<3F>?ґғҕҗҙқ<D299>?ҟҡңҥҧҩҫҭүұҳҵҷҹһҽҿӀӂӄӆӈӊӌӎӑӓӕӗәӛ<D399>?ӟӡӣӥӧөӫӭӯӱӳӵӹ" +
"աբգդեզէըթժիլխծկհձղճմյնշոչպջռսվտր<EFBFBD>?ւփքօֆև"
WIKI_WORD_PATTERN = '[A-Z' + I18N_HIGHER_CASE_LETTERS + '][a-z' + I18N_LOWER_CASE_LETTERS + ']+[A-Z' + I18N_HIGHER_CASE_LETTERS + ']\w+'
CAMEL_CASED_WORD_BORDER = /([a-z#{I18N_LOWER_CASE_LETTERS}])([A-Z#{I18N_HIGHER_CASE_LETTERS}])/u
def self.separate(wiki_word)
wiki_word.gsub(CAMEL_CASED_WORD_BORDER, '\1 \2')
end
end