Initial import of the sources from SVN
This commit is contained in:
parent
e1a14bc27b
commit
223b2bec6c
137
CHANGELOG
Executable file
137
CHANGELOG
Executable file
|
@ -0,0 +1,137 @@
|
|||
HEAD:
|
||||
Wiki extracts (to HTML and plain text) will leave only the last extract file in ./storage
|
||||
Local hyperlinks in published pages point to published pages [Michael DeHaan]
|
||||
Various usability enhancements
|
||||
|
||||
* 0.9.2:
|
||||
Rollback takes the user to an edit form. The form has to be submitted for the change to take place.
|
||||
Changed to use inline style on published pages
|
||||
Fixed "forward in time" on the last revision before current page
|
||||
Instiki won't log bogus error messages when creating a new Wiki.
|
||||
Fixed deprecation warning for Object.id (introduced in Ruby 1.8.2)
|
||||
Madeleine upgraded to 0.7.1
|
||||
Madeleine snapshots are compressed
|
||||
Packaged as a gem
|
||||
|
||||
* 0.9.1:
|
||||
Added performance improvements for updating existing pages
|
||||
Fixed IP logging and RSS feeds behind proxies [With help from Guan Yang]
|
||||
Fixed default storage directory (borked running on Windows) [Spotted by Curt Hibbs]
|
||||
|
||||
* 0.9.0:
|
||||
Added aliased links such as [[HomePage|that nice home page]] [Mark Reid]
|
||||
Added include other page content with [[!include TableOfContents]] [Mark Reid]
|
||||
Added delete orphan pages from the Edit Web screen [by inspiration from Simon Arnaud]
|
||||
Added logging of IP address for authors (who's behind the rollback wars)
|
||||
Added Categories pages through backlinks (use "categories: news, instiki" on start of line) [Mark Reid]
|
||||
Added option to use bracket-style wiki links only (and hence ban WikiWords)
|
||||
Added command-line option to specify different storage path
|
||||
Added print view without navigation
|
||||
Added character and page (2275 characters including spaces) counter (important for student papers)
|
||||
Off by default, activate it on the Edit Web screen
|
||||
Added LaTeX/PDF integration on Textile installations with pdflatex installed on system (EXPERIMENTAL)
|
||||
Use the home page as a table of contents with a unordered list to control sections
|
||||
Added limit of 15 to the number of pages included in RSS feed
|
||||
Moved static parts of stylesheet to separate file [Lau TŒrnskov]
|
||||
Fixed better semantics for revision movement [Ryan Singer]
|
||||
Fixed color diffs to work much better [Xen/Mertz/Atkins]
|
||||
Fixed performance problems for All Pages list [Dennis Mertz]
|
||||
Fixed lots of rendering bugs [Mark Reid]
|
||||
Upgraded to RedCloth 2.0.11 [integrating the fine work of Dennis Mertz]
|
||||
|
||||
* 0.8.9:
|
||||
Added color diffs to see changes between revisions [Bill Atkins]
|
||||
They're aren't quite perfect yet as new paragraphs split the <ins> tags (hence 0.8.9, not 0.9.0)
|
||||
Added redirect to edit if content of page generates an error
|
||||
(so the page doesn't become unusable on bugs in the markup engines)
|
||||
Fixed update Web with different address bug [Denis Metz]
|
||||
Fixed a bunch of wiki word rendering issues by doing wiki word detection and replacment at once
|
||||
Upgraded to BlueCloth 0.0.3b (should fix loads of problems on Markdown wikis)
|
||||
|
||||
* 0.8.5:
|
||||
Instiki can now serve as a CMS by running a password-protected web with a published front
|
||||
Added version check at startup (Instiki needs Ruby 1.8.1)
|
||||
|
||||
* 0.8.1:
|
||||
Actually included RedCloth 2.0.7 in the release
|
||||
|
||||
* 0.8.0:
|
||||
NOTE: Single-web wikis created in versions prior to 0.8.0 have "instiki" as their system password
|
||||
Accepts wiki words in bracket style. Ex: [[wiki word]], [[c]], [[We could'nt have done it!]]
|
||||
Accepts camel-case wiki words in all latin, greek, cyrillian, and armenian unicode characters
|
||||
Many thanks to Guan Yang for building the higher- and lower-case lookup tables
|
||||
And thanks to Simon Arnaud for the initial patch that got the work started
|
||||
Changed charset to UTF-8
|
||||
Cut down on command-line options and replaced them with an per-web config screen
|
||||
Added option to extend the stylesheet on a per-web basis to tweak the look in details
|
||||
Added simple color options for variety
|
||||
Added option to add/remove password protection on each web
|
||||
Added the wiki name of the author locking a given page (instead of just "someone")
|
||||
Removed single/multi-web distinction -- all Instikis are now multi-web
|
||||
Load libraries from an unshifted load path, so that old installed libraries doesn't clash [Emiel van de Laar]
|
||||
Keeps the author cookie forever, so you don't have to enter your name again and again
|
||||
Fixed XHTML so it validates [Bruce D'Arcus]
|
||||
Authors are no longer listed under orphan pages
|
||||
Added export to markup (great for backups, potentially for switching wiki engine)
|
||||
Don't link wiki words that proceeds from either /, = or ?
|
||||
(http://c2.com/cgi/wiki?WikiWikiClones, /show/HomePage, cgi.pl?show=WikiWord without escaping)
|
||||
Accessing an unexisting page redirects to a different url (/new/PageName)
|
||||
Increased snapshot time to just once a day (cuts down on disk storage requirements)
|
||||
Made RDoc support work better with 1.8.1 [Mauricio Fern‡ndez]
|
||||
Added convinient redirect from /wiki/ to /wiki/show/HomePage
|
||||
Fixed BlueCloth bug with backticks at start of line
|
||||
Updated to RedCloth 2.0.7 (and linked to the new Textile reference)
|
||||
|
||||
* 0.7.0:
|
||||
Added Markdown (BlueCloth) and RDoc [Mauricio Fern‡ndez] as command-line markup choices
|
||||
Added wanted and orphan page lists to All pages (only show up if there's actually orphan or wanted pages)
|
||||
Added ISO-8859-1 as XML encoding in RSS feeds (makes FeedReader among others happy for special entities)
|
||||
Added proper links in the RSS feed (but the body links are still relative, which NNW and others doesn't grok)
|
||||
Added access keys: E => Edit, H => HomePage, A => All Pages, U => Recently Revised, X => Export
|
||||
Added password-login through URL (so you can subscribe to feed on a protected web)
|
||||
Added web passwords to the feed links for protected webs, so they work without manual login
|
||||
Added the web name in small letters above all pages within a web
|
||||
Polished authors and recently revised
|
||||
Updated to RedCloth 2.0.6
|
||||
Changed content type for RSS feeds to text/xml (makes Mozilla Aggreg8 happy)
|
||||
Changed searching to be case insensitive
|
||||
Changed HomePage to display the name of the web instead
|
||||
Changed exported HTML pages to be valid XHTML (which can be preprocessed by XSLT)
|
||||
Fixed broken recently revised
|
||||
|
||||
* 0.6.0:
|
||||
Fixed Windows compatibility [Florian]
|
||||
Fixed bug that would prevent Madeleine from taking snapshots in Daemon mode
|
||||
Added export entire web as HTML in a zip file
|
||||
Added RSS feeds
|
||||
Added proper getops support for the growing number of options [Florian]
|
||||
Added safe mode that forbids style options in RedCloth [Florian]
|
||||
Updated RedCloth to 2.0.5
|
||||
|
||||
* 0.5.0:
|
||||
NOTE: 0.5.0 is NOT compatible with databases from earlier versions
|
||||
Added revisions
|
||||
Added multiple webs
|
||||
Added password protection for webs on multi-web setups
|
||||
Added the notion of authors (that are saved in a cookie)
|
||||
Added command-line option for not running as a Daemon on Unix
|
||||
|
||||
* 0.3.1:
|
||||
Added option to escape wiki words with \
|
||||
|
||||
* 0.3.0:
|
||||
Brought all files into common style (including Textile help on the edit page)
|
||||
Added page locking (if someone already is editing a page there's a warning)
|
||||
Added daemon abilities on Unix (keep Instiki running after you close the terminal)
|
||||
Made port 2500 the default port, so Instiki can be launched by dobbelt-click
|
||||
Added Textile cache to speed-up rendering of large pages
|
||||
Made WikiWords look like "Wiki Words"
|
||||
Updated RedCloth to 2.0.4
|
||||
|
||||
* 0.2.5:
|
||||
Upgraded to RedCloth 2.0.2 and Madeleine 0.6.1, which means the
|
||||
Windows problems are gone. Also fixed a problem with wikiwords
|
||||
that used part of other wikiwords.
|
||||
|
||||
* 0.2.0:
|
||||
First public release
|
36
CONTROLLER_TESTS
Executable file
36
CONTROLLER_TESTS
Executable file
|
@ -0,0 +1,36 @@
|
|||
DONE
|
||||
|
||||
edit
|
||||
create_system
|
||||
index
|
||||
locked
|
||||
new
|
||||
new_system
|
||||
show
|
||||
recently_revised
|
||||
save
|
||||
revision
|
||||
rollback
|
||||
search
|
||||
list
|
||||
web_list
|
||||
authenticate
|
||||
login
|
||||
create_web
|
||||
new_web
|
||||
update_web
|
||||
authors
|
||||
remove_orphaned_pages
|
||||
cancel_edit
|
||||
print
|
||||
published
|
||||
rss_with_content
|
||||
rss_with_headlines
|
||||
export_html
|
||||
export_markup
|
||||
|
||||
TODO
|
||||
|
||||
pdf
|
||||
export_pdf
|
||||
export_tex
|
68
README
Executable file
68
README
Executable file
|
@ -0,0 +1,68 @@
|
|||
===What is Instiki?
|
||||
|
||||
Admitted, it's YetAnotherWikiClone[http://c2.com/cgi/wiki?WikiWikiClones], but with a strong focus
|
||||
on simplicity of installation and running:
|
||||
|
||||
Step 1. Download
|
||||
|
||||
Step 2. Run "instiki"
|
||||
|
||||
Step 3. Chuckle... "There's no step three!" (TM)
|
||||
|
||||
You're now running a perfectly suitable wiki on port 2500
|
||||
that'll present you with one-step setup, followed by a textarea for the home page
|
||||
on http://localhost:2500.
|
||||
|
||||
Instiki lowers the barriers of interest for when you might consider
|
||||
using a wiki. It's so simple to get running that you'll find yourself
|
||||
using it for anything -- taking notes, brainstorming, organizing a
|
||||
gathering.
|
||||
|
||||
===Features:
|
||||
* Regular expression search: Find deep stuff really fast
|
||||
* Revisions: Follow the changes on every page from birth. Rollback to an earlier rev
|
||||
* Export to HTML or markup in a zip: Take the entire wiki with you home or for reference
|
||||
* RSS feeds to track recently revised pages
|
||||
* Multiple webs: Create separate wikis with their own namespace
|
||||
* Password-protected webs: Keep it private
|
||||
* Authors: Each revision is associated with an author, so you can see who changed what
|
||||
* Reference tracker: Which other pages are pointing to the current?
|
||||
* Speed: Using Madelein[http://madeleine.sourceforge.net] for persistence (all pages are in memory)
|
||||
* Three markup choices: Textile[http://www.textism.com/tools/textile]
|
||||
(default / RedCloth[http://www.whytheluckystiff.net/ruby/redcloth]),
|
||||
Markdown (BlueCloth[http://bluecloth.rubyforge.org]), and RDoc[http://rdoc.sourceforge.net/doc]
|
||||
* Embedded webserver: Through WEBrick[http://www.webrick.org]
|
||||
* Internationalization: Wiki words in any latin, greek, cyrillian, or armenian characters
|
||||
* Color diffs: Track changes through revisions
|
||||
|
||||
===Missing:
|
||||
* File attachments
|
||||
|
||||
===Install from gem:
|
||||
* Install rubygems
|
||||
* Run "gem install instiki"
|
||||
* Change to a directory where you want Instiki to keep its data files (for example, ~/instiki/)
|
||||
* Run "instiki" - this will create a "storage" directory (for example, ~/instiki/storage), and start a new Wiki service
|
||||
|
||||
Make sure that you always launch Instiki from the same working directory, or specify the storage directory in runtime parameters, such as:
|
||||
instiki --storage ~/instiki/storage
|
||||
|
||||
===Command-line options:
|
||||
* Run "instiki --help"
|
||||
|
||||
===History:
|
||||
* See CHANGELOG
|
||||
|
||||
===Download latest from:
|
||||
* http://rubyforge.org/project/showfiles.php?group_id=186
|
||||
|
||||
===Visit the official Instiki wiki:
|
||||
* http://www.instiki.org
|
||||
|
||||
===License:
|
||||
* same as Ruby's
|
||||
|
||||
---
|
||||
Author:: David Heinemeier Hansson
|
||||
Email:: david@loudthinking.com
|
||||
Weblog:: http://www.loudthinking.com
|
27
app/controllers/application.rb
Normal file
27
app/controllers/application.rb
Normal file
|
@ -0,0 +1,27 @@
|
|||
require 'url_rewriting_hack'
|
||||
|
||||
# The filters added to this controller will be run for all controllers in the application.
|
||||
# Likewise will all the methods added be available for all controllers.
|
||||
class ApplicationController < ActionController::Base
|
||||
|
||||
# implements Instiki's legacy URLs
|
||||
require 'url_rewriting_hack'
|
||||
|
||||
# For injecting a different wiki model implementation. Intended for use in tests
|
||||
def self.wiki=(the_wiki)
|
||||
# a global variable is used here because Rails reloads controller and model classes in the
|
||||
# development environment; therefore, storing it as a class variable does not work
|
||||
# class variable is, anyway, not much different from a global variable
|
||||
$instiki_wiki_service = the_wiki
|
||||
logger.debug("Wiki service: #{the_wiki.to_s}")
|
||||
end
|
||||
|
||||
def self.wiki
|
||||
$instiki_wiki_service
|
||||
end
|
||||
|
||||
def wiki
|
||||
$instiki_wiki_service
|
||||
end
|
||||
|
||||
end
|
411
app/controllers/wiki_controller.rb
Executable file
411
app/controllers/wiki_controller.rb
Executable file
|
@ -0,0 +1,411 @@
|
|||
require 'application'
|
||||
require 'fileutils'
|
||||
require 'redcloth_for_tex'
|
||||
|
||||
class WikiController < ApplicationController
|
||||
|
||||
before_filter :pre_process
|
||||
|
||||
EXPORT_DIRECTORY = File.dirname(__FILE__) + "/../../storage/" unless const_defined?("EXPORT_DIRECTORY")
|
||||
|
||||
def index
|
||||
if @web_name
|
||||
redirect_show 'HomePage'
|
||||
elsif not wiki.setup?
|
||||
redirect_to :action => 'new_system'
|
||||
elsif wiki.webs.length == 1
|
||||
redirect_show 'HomePage', wiki.webs.values.first.address
|
||||
else
|
||||
redirect_to :action => 'web_list'
|
||||
end
|
||||
end
|
||||
|
||||
# Administrating the Instiki setup --------------------------------------------
|
||||
|
||||
def create_system
|
||||
wiki.setup(@params['password'], @params['web_name'], @params['web_address']) unless wiki.setup?
|
||||
redirect_to :action => 'index'
|
||||
end
|
||||
|
||||
def create_web
|
||||
if wiki.authenticate(@params['system_password'])
|
||||
wiki.create_web(@params['name'], @params['address'])
|
||||
redirect_show('HomePage', @params['address'])
|
||||
else
|
||||
redirect_to :action => 'index'
|
||||
end
|
||||
end
|
||||
|
||||
def new_system
|
||||
redirect_to(:action => 'index') if wiki.setup?
|
||||
# otherwise, to template
|
||||
end
|
||||
|
||||
def new_web
|
||||
redirect_to :action => 'index' if wiki.system['password'].nil?
|
||||
# otherwise, to template
|
||||
end
|
||||
|
||||
|
||||
# Outside a single web --------------------------------------------------------
|
||||
|
||||
def authenticate
|
||||
if password_check(@params['password'])
|
||||
redirect_show('HomePage')
|
||||
else
|
||||
redirect_to :action => 'login'
|
||||
end
|
||||
end
|
||||
|
||||
def login
|
||||
# go straight to template
|
||||
end
|
||||
|
||||
def web_list
|
||||
@webs = wiki.webs.values.sort_by { |web| web.name }
|
||||
end
|
||||
|
||||
|
||||
# Within a single web ---------------------------------------------------------
|
||||
|
||||
def authors
|
||||
@authors = @web.select.authors
|
||||
end
|
||||
|
||||
def export_html
|
||||
export_pages_as_zip('html') { |page| @page = page; render_to_string 'wiki/print' }
|
||||
end
|
||||
|
||||
def export_markup
|
||||
export_pages_as_zip(@web.markup) { |page| page.content }
|
||||
end
|
||||
|
||||
def export_pdf
|
||||
file_name = "#{web.address}-tex-#{web.revised_on.strftime("%Y-%m-%d-%H-%M")}"
|
||||
file_path = EXPORT_DIRECTORY + file_name
|
||||
|
||||
export_web_to_tex(file_path + ".tex") unless FileTest.exists?(file_path + ".tex")
|
||||
convert_tex_to_pdf(file_path + ".tex")
|
||||
send_export(file_name + ".pdf", file_path + ".pdf")
|
||||
end
|
||||
|
||||
def export_tex
|
||||
file_name = "#{web.address}-tex-#{web.revised_on.strftime("%Y-%m-%d-%H-%M")}.tex"
|
||||
file_path = EXPORT_DIRECTORY + file_name
|
||||
|
||||
export_web_to_tex(file_path) unless FileTest.exists?(file_path)
|
||||
send_export(file_name, file_path)
|
||||
end
|
||||
|
||||
def feeds
|
||||
# to template
|
||||
end
|
||||
|
||||
def list
|
||||
parse_category
|
||||
@pages_by_name = @pages_in_category.by_name
|
||||
@page_names_that_are_wanted = @pages_in_category.wanted_pages
|
||||
@pages_that_are_orphaned = @pages_in_category.orphaned_pages
|
||||
end
|
||||
|
||||
def recently_revised
|
||||
parse_category
|
||||
@pages_by_revision = @pages_in_category.by_revision
|
||||
end
|
||||
|
||||
def remove_orphaned_pages
|
||||
if wiki.authenticate(@params['system_password'])
|
||||
wiki.remove_orphaned_pages(@web_name)
|
||||
redirect_to :action => 'list'
|
||||
else
|
||||
redirect_show 'HomePage'
|
||||
end
|
||||
end
|
||||
|
||||
def rss_with_content
|
||||
render_rss
|
||||
end
|
||||
|
||||
def rss_with_headlines
|
||||
render_rss(hide_description = true)
|
||||
end
|
||||
|
||||
def search
|
||||
@query = @params['query']
|
||||
@results = @web.select { |page| page.content =~ /#{@query}/i }.sort
|
||||
redirect_show(@results.first.name) if @results.length == 1
|
||||
end
|
||||
|
||||
def update_web
|
||||
if wiki.authenticate(@params['system_password'])
|
||||
wiki.update_web(
|
||||
@web.address, @params['address'], @params['name'],
|
||||
@params['markup'].intern,
|
||||
@params['color'], @params['additional_style'],
|
||||
@params['safe_mode'] ? true : false,
|
||||
@params['password'].empty? ? nil : @params['password'],
|
||||
@params['published'] ? true : false,
|
||||
@params['brackets_only'] ? true : false,
|
||||
@params['count_pages'] ? true : false
|
||||
)
|
||||
redirect_show('HomePage', @params['address'])
|
||||
else
|
||||
redirect_show('HomePage')
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Within a single page --------------------------------------------------------
|
||||
|
||||
def cancel_edit
|
||||
@page.unlock
|
||||
redirect_show
|
||||
end
|
||||
|
||||
def edit
|
||||
if @page.nil?
|
||||
redirect_to :action => 'index'
|
||||
elsif @page.locked?(Time.now) and not @params['break_lock']
|
||||
redirect_to :web => @web_name, :action => 'locked', :id => @page_name
|
||||
else
|
||||
@page.lock(Time.now, @author)
|
||||
end
|
||||
end
|
||||
|
||||
def locked
|
||||
# to template
|
||||
end
|
||||
|
||||
def new
|
||||
# go straight to template, all necessary variables are already set in the filter
|
||||
end
|
||||
|
||||
def pdf
|
||||
page = wiki.read_page(@web_name, @page_name)
|
||||
safe_page_name = page.name.gsub(/\W/, '')
|
||||
file_name = "#{safe_page_name}-#{web.address}-#{page.created_at.strftime("%Y-%m-%d-%H-%M")}"
|
||||
file_path = EXPORT_DIRECTORY + file_name
|
||||
|
||||
export_page_to_tex(file_path + '.tex') unless FileTest.exists?(file_path + '.tex')
|
||||
convert_tex_to_pdf(file_path + '.tex')
|
||||
send_export(file_name + '.pdf', file_path + '.pdf')
|
||||
end
|
||||
|
||||
def print
|
||||
# to template
|
||||
end
|
||||
|
||||
def published
|
||||
if @web.published
|
||||
@page = wiki.read_page(@web_name, @page_name || 'HomePage')
|
||||
else
|
||||
redirect_show('HomePage')
|
||||
end
|
||||
end
|
||||
|
||||
def revision
|
||||
get_page_and_revision
|
||||
end
|
||||
|
||||
def rollback
|
||||
get_page_and_revision
|
||||
end
|
||||
|
||||
def save
|
||||
redirect_to :action => 'index' if @page_name.nil?
|
||||
|
||||
if @web.pages[@page_name]
|
||||
page = wiki.revise_page(
|
||||
@web_name, @page_name, @params['content'], Time.now,
|
||||
Author.new(@params['author'], remote_ip)
|
||||
)
|
||||
page.unlock
|
||||
else
|
||||
page = wiki.write_page(
|
||||
@web_name, @page_name, @params['content'], Time.now,
|
||||
Author.new(@params['author'], remote_ip)
|
||||
)
|
||||
end
|
||||
cookies['author'] = @params['author']
|
||||
redirect_show(@page_name)
|
||||
end
|
||||
|
||||
def show
|
||||
if @page
|
||||
begin
|
||||
render_action 'page'
|
||||
# TODO this rescue should differentiate between errors due to rendering and errors in
|
||||
# the application itself (for application errors, it's better not to rescue the error at all)
|
||||
rescue => e
|
||||
logger.error e
|
||||
if in_a_web?
|
||||
redirect_to :web => @web_name, :action => 'edit',
|
||||
:action_suffix => "#{CGI.escape(@page_name)}?msg=#{CGI.escape(e.message)}"
|
||||
else
|
||||
raise e
|
||||
end
|
||||
end
|
||||
else
|
||||
redirect_to :web => @web_name, :action => 'new', :id => CGI.escape(@page_name)
|
||||
end
|
||||
end
|
||||
|
||||
def tex
|
||||
@tex_content = RedClothForTex.new(@page.content).to_tex
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
def authorized?
|
||||
@web.nil? ||
|
||||
@web.password.nil? ||
|
||||
cookies['web_address'] == @web.password ||
|
||||
password_check(@params['password'])
|
||||
end
|
||||
|
||||
def check_authorization(action_name)
|
||||
if in_a_web? and
|
||||
not authorized? and
|
||||
not %w( login authenticate published ).include?(action_name)
|
||||
redirect_to :action => 'login'
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
def convert_tex_to_pdf(tex_path)
|
||||
`cd #{File.dirname(tex_path)}; pdflatex --interaction=scrollmode '#{File.basename(tex_path)}'`
|
||||
end
|
||||
|
||||
def export_page_to_tex(file_path)
|
||||
tex
|
||||
File.open(file_path, 'w') { |f| f.write(template_engine("tex").result(binding)) }
|
||||
end
|
||||
|
||||
def export_pages_as_zip(file_type, &block)
|
||||
|
||||
file_prefix = "#{@web.address}-#{file_type}-"
|
||||
timestamp = @web.revised_on.strftime('%Y-%m-%d-%H-%M-%S')
|
||||
file_path = EXPORT_DIRECTORY + file_prefix + timestamp + '.zip'
|
||||
tmp_path = "#{file_path}.tmp"
|
||||
|
||||
Zip::ZipOutputStream.open(tmp_path) do |zip_out|
|
||||
@web.select.by_name.each do |page|
|
||||
zip_out.put_next_entry("#{page.name}.#{file_type}")
|
||||
zip_out.puts(block.call(page))
|
||||
end
|
||||
# add an index file, if exporting to HTML
|
||||
if file_type.to_s.downcase == 'html'
|
||||
zip_out.put_next_entry 'index.html'
|
||||
zip_out.puts <<-EOL
|
||||
<html>
|
||||
<head>
|
||||
<META HTTP-EQUIV="Refresh" CONTENT="0;URL=HomePage.#{file_type}">
|
||||
</head>
|
||||
</html>
|
||||
EOL
|
||||
end
|
||||
end
|
||||
FileUtils.rm_rf(Dir[EXPORT_DIRECTORY + file_prefix + '*.zip'])
|
||||
FileUtils.mv(tmp_path, file_path)
|
||||
send_file(file_path, :type => 'application/zip')
|
||||
end
|
||||
|
||||
def export_web_to_tex(file_path)
|
||||
@tex_content = table_of_contents(web.pages['HomePage'].content.dup, render_tex_web)
|
||||
File.open(file_path, 'w') { |f| f.write(template_engine('tex_web').result(binding)) }
|
||||
end
|
||||
|
||||
def get_page_and_revision
|
||||
@revision = @page.revisions[@params['rev'].to_i]
|
||||
end
|
||||
|
||||
def in_a_web?
|
||||
not @web_name.nil?
|
||||
end
|
||||
|
||||
def parse_category
|
||||
@categories = @web.categories
|
||||
@category = @params['category']
|
||||
if @categories.include?(@category)
|
||||
@pages_in_category = @web.select { |page| page.in_category?(@category) }
|
||||
@set_name = "category '#{@category}'"
|
||||
else
|
||||
@pages_in_category = PageSet.new(@web).by_name
|
||||
@set_name = 'the web'
|
||||
end
|
||||
@category_links = @categories.map { |c|
|
||||
if @category == c
|
||||
%{<span class="selected">#{c}</span>}
|
||||
else
|
||||
%{<a href="?category=#{c}">#{c}</a>}
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
def password_check(password)
|
||||
if password == @web.password
|
||||
cookies['web_address'] = password
|
||||
true
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def pre_process
|
||||
@action_name = @params['action'] || 'index'
|
||||
@web_name = @params['web']
|
||||
@wiki = wiki
|
||||
@web = @wiki.webs[@web_name] unless @web_name.nil?
|
||||
@page_name = @params['id']
|
||||
@page = @wiki.read_page(@web_name, @page_name) unless @page_name.nil?
|
||||
@author = cookies['author'] || 'AnonymousCoward'
|
||||
check_authorization(@action_name)
|
||||
end
|
||||
|
||||
def redirect_show(page_name = @page_name, web = @web_name)
|
||||
redirect_to :web => web, :action => 'show', :id => CGI.escape(page_name)
|
||||
end
|
||||
|
||||
def remote_ip
|
||||
ip = @request.remote_ip
|
||||
logger.info(ip)
|
||||
ip
|
||||
end
|
||||
|
||||
def render_rss(hide_description = false)
|
||||
@pages_by_revision = @web.select.by_revision.first(15)
|
||||
@hide_description = hide_description
|
||||
@response.headers['Content-Type'] = 'text/xml'
|
||||
render 'wiki/rss_feed'
|
||||
end
|
||||
|
||||
def render_tex_web
|
||||
@web.select.by_name.inject({}) do |tex_web, page|
|
||||
tex_web[page.name] = RedClothForTex.new(page.content).to_tex
|
||||
tex_web
|
||||
end
|
||||
end
|
||||
|
||||
def render_to_string(template_name)
|
||||
add_variables_to_assigns
|
||||
render template_name
|
||||
@template.render_file(template_name)
|
||||
end
|
||||
|
||||
# Returns an array with each of the parts in the request as an element. So /something/cool/dude
|
||||
# returns ["something", "cool", "dude"]
|
||||
def request_path
|
||||
request_path_parts = @request.path.to_s.split(/\//)
|
||||
request_path_parts.length > 1 ? request_path_parts[1..-1] : []
|
||||
end
|
||||
|
||||
def template_engine(template_name)
|
||||
ERB.new(IO.readlines(RAILS_ROOT + '/app/views/wiki/' + template_name + '.rhtml').join)
|
||||
end
|
||||
|
||||
def truncate(text, length = 30, truncate_string = '...')
|
||||
if text.length > length then text[0..(length - 3)] + truncate_string else text end
|
||||
end
|
||||
|
||||
end
|
36
app/helpers/application_helper.rb
Normal file
36
app/helpers/application_helper.rb
Normal file
|
@ -0,0 +1,36 @@
|
|||
# The methods added to this helper will be available to all templates in the application.
|
||||
module ApplicationHelper
|
||||
|
||||
# Accepts a container (hash, array, enumerable, your type) and returns a string of option tags. Given a container
|
||||
# where the elements respond to first and last (such as a two-element array), the "lasts" serve as option values and
|
||||
# the "firsts" as option text. Hashes are turned into this form automatically, so the keys become "firsts" and values
|
||||
# become lasts. If +selected+ is specified, the matching "last" or element will get the selected option-tag.
|
||||
#
|
||||
# Examples (call, result):
|
||||
# html_options([["Dollar", "$"], ["Kroner", "DKK"]])
|
||||
# <option value="$">Dollar</option>\n<option value="DKK">Kroner</option>
|
||||
#
|
||||
# html_options([ "VISA", "Mastercard" ], "Mastercard")
|
||||
# <option>VISA</option>\n<option selected>Mastercard</option>
|
||||
#
|
||||
# html_options({ "Basic" => "$20", "Plus" => "$40" }, "$40")
|
||||
# <option value="$20">Basic</option>\n<option value="$40" selected>Plus</option>
|
||||
def html_options(container, selected = nil)
|
||||
container = container.to_a if Hash === container
|
||||
|
||||
html_options = container.inject([]) do |options, element|
|
||||
if element.respond_to?(:first) && element.respond_to?(:last)
|
||||
if element.last != selected
|
||||
options << "<option value=\"#{element.last}\">#{element.first}</option>"
|
||||
else
|
||||
options << "<option value=\"#{element.last}\" selected>#{element.first}</option>"
|
||||
end
|
||||
else
|
||||
options << ((element != selected) ? "<option>#{element}</option>" : "<option selected>#{element}</option>")
|
||||
end
|
||||
end
|
||||
|
||||
html_options.join("\n")
|
||||
end
|
||||
|
||||
end
|
4
app/models/author.rb
Executable file
4
app/models/author.rb
Executable file
|
@ -0,0 +1,4 @@
|
|||
class Author < String
|
||||
attr_accessor :ip
|
||||
def initialize(name, ip) @ip = ip; super(name) end
|
||||
end
|
31
app/models/chunks/category.rb
Executable file
31
app/models/chunks/category.rb
Executable file
|
@ -0,0 +1,31 @@
|
|||
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
|
||||
|
||||
# Mark this chunk's start and end points but allow the terms
|
||||
# after the ':' to be marked up.
|
||||
def mask(content) pre_mask + list.join(', ') + post_mask end
|
||||
|
||||
# If the chunk is hidden, erase the mask and return this chunk
|
||||
# otherwise, surround it with a 'div' block.
|
||||
def unmask(content)
|
||||
replacement = ( hidden ? '' : '<div class="property">category:\1</div>' )
|
||||
self if content.sub!( Regexp.new( pre_mask+'(.*)?'+post_mask ), replacement )
|
||||
end
|
||||
end
|
20
app/models/chunks/chunk.rb
Executable file
20
app/models/chunks/chunk.rb
Executable file
|
@ -0,0 +1,20 @@
|
|||
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
|
||||
def pre_mask() "chunk#{self.object_id}start " end
|
||||
def post_mask() " chunk#{self.object_id}end" end
|
||||
def mask(content) "chunk#{self.object_id}chunk" end
|
||||
def revert(content) content.sub!( Regexp.new(mask(content)), text ) end
|
||||
def unmask(content) self if revert(content) end
|
||||
end
|
||||
end
|
38
app/models/chunks/engines.rb
Executable file
38
app/models/chunks/engines.rb
Executable file
|
@ -0,0 +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
|
||||
|
29
app/models/chunks/include.rb
Executable file
29
app/models/chunks/include.rb
Executable file
|
@ -0,0 +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
|
19
app/models/chunks/literal.rb
Executable file
19
app/models/chunks/literal.rb
Executable file
|
@ -0,0 +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
|
19
app/models/chunks/match.rb
Executable file
19
app/models/chunks/match.rb
Executable file
|
@ -0,0 +1,19 @@
|
|||
# This module is to be included in unit tests that involve matching chunks.
|
||||
# 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.
|
||||
module ChunkMatch
|
||||
|
||||
# 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
|
31
app/models/chunks/nowiki.rb
Executable file
31
app/models/chunks/nowiki.rb
Executable file
|
@ -0,0 +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!( Regexp.new(mask(content)), plain_text ) end
|
||||
end
|
18
app/models/chunks/test.rb
Executable file
18
app/models/chunks/test.rb
Executable file
|
@ -0,0 +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
|
103
app/models/chunks/uri.rb
Executable file
103
app/models/chunks/uri.rb
Executable file
|
@ -0,0 +1,103 @@
|
|||
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? URI_CHUNK_CONSTANTS_DEFINED
|
||||
URI_CHUNK_CONSTANTS_DEFINED = true
|
||||
|
||||
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})+"
|
||||
|
||||
# Pattern of legal URI endings to stop interference with some Textile
|
||||
# markup. (Images: !URI!) and other punctuation eg, (http://wiki.com/)
|
||||
URI_ENDING = '[)!]'
|
||||
|
||||
# The basic URI expression as a string
|
||||
URI_PATTERN =
|
||||
"(?:(#{SCHEME})://)?" + # Optional scheme:// (\1|\8)
|
||||
"(?:(#{USERINFO})@)?" + # Optional userinfo@ (\2|\9)
|
||||
"(#{HOSTNAME}#{TLDS})" + # Mandatory host eg, HOST.com.au (\3|\10)
|
||||
"(?::(#{PORT}))?" + # Optional :port (\4|\11)
|
||||
"(#{ABS_PATH})?" + # Optional absolute path (\5|\12)
|
||||
"(?:\\?(#{QUERY}))?" + # Optional ?query (\6|\13)
|
||||
"(?:\\#(#{FRAGMENT}))?" # Optional #fragment (\7|\14)
|
||||
|
||||
end
|
||||
|
||||
def self.pattern()
|
||||
# This pattern first tries to match the URI_PATTERN that ends with
|
||||
# punctuation that is a valid URI character (eg, ')', '!'). If
|
||||
# such a match occurs, there should be no backtracking (hence the ?> ).
|
||||
# If the string cannot match a URI ending with URI_ENDING, then a second
|
||||
# attempt is tried.
|
||||
Regexp.new("(?>#{URI_PATTERN}(?=#{URI_ENDING}))|#{URI_PATTERN}", Regexp::EXTENDED, 'N')
|
||||
end
|
||||
|
||||
attr_reader :uri, :scheme, :user, :host, :port, :path, :query, :fragment, :link_text
|
||||
|
||||
def initialize(match_data)
|
||||
super(match_data)
|
||||
# Since the URI_PATTERN is tried twice, there are two sets of
|
||||
# groups, one from \1 to \7 and the second from \8 to \14.
|
||||
# The fields are set by which ever group matches.
|
||||
@scheme = match_data[1] || match_data[8]
|
||||
@user = match_data[2] || match_data[9]
|
||||
@host = match_data[3] || match_data[10]
|
||||
@port = match_data[4] || match_data[11]
|
||||
@path = match_data[5] || match_data[12]
|
||||
@query = match_data[6] || match_data[13]
|
||||
@fragment = match_data[7] || match_data[14]
|
||||
|
||||
# If there is no scheme, add an appropriate one, otherwise
|
||||
# set the URI to the matched text.
|
||||
@text_scheme = scheme
|
||||
@uri = (scheme ? match_data[0] : nil )
|
||||
@scheme = scheme || ( user ? 'mailto' : 'http' )
|
||||
@delimiter = ( scheme == 'mailto' ? ':' : '://' )
|
||||
@uri ||= scheme + @delimiter + match_data[0]
|
||||
|
||||
# Build up the link text. Schemes are omitted unless explicitly given.
|
||||
@link_text = ''
|
||||
@link_text << "#{@scheme}#{@delimiter}" if @text_scheme
|
||||
@link_text << "#{@user}@" if @user
|
||||
@link_text << "#{@host}" if @host
|
||||
@link_text << ":#{@port}" if @port
|
||||
@link_text << "#{@path}" if @path
|
||||
@link_text << "?#{@query}" if @query
|
||||
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!( Regexp.new(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
|
||||
end
|
83
app/models/chunks/wiki.rb
Executable file
83
app/models/chunks/wiki.rb
Executable file
|
@ -0,0 +1,83 @@
|
|||
require 'wiki_words'
|
||||
require 'chunks/chunk'
|
||||
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
|
||||
# By default, no escaped text
|
||||
def escaped_text() nil end
|
||||
|
||||
# Delimit the link text with markers to replace later unless
|
||||
# the word is escaped. In that case, just return the link text
|
||||
def mask(content) escaped_text || pre_mask + link_text + post_mask end
|
||||
|
||||
def regexp() Regexp.new(pre_mask + '(.*)?' + post_mask) end
|
||||
|
||||
def revert(content) content.sub!(regexp, 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)
|
||||
return nil if escaped_text
|
||||
return self if content.sub!(regexp) { |match| content.page_link(page_name, $1) }
|
||||
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
|
||||
def self.pattern
|
||||
Regexp.new('(\\\\)?(' + WikiWords::WIKI_WORD_PATTERN + ')\b', 0, "utf-8")
|
||||
end
|
||||
|
||||
attr_reader :page_name
|
||||
|
||||
def initialize(match_data)
|
||||
super(match_data)
|
||||
@escape = match_data[1]
|
||||
@page_name = match_data[2]
|
||||
end
|
||||
|
||||
def escaped_text() (@escape.nil? ? nil : page_name) 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
|
||||
def self.pattern() /\[\[([^\]]+)\]\]/ end
|
||||
|
||||
ALIASED_LINK_PATTERN ||= Regexp.new('^(.*)?\|(.*)$', 0, "utf-8")
|
||||
|
||||
attr_reader :page_name, :link_text
|
||||
|
||||
def initialize(match_data)
|
||||
super(match_data)
|
||||
|
||||
# If the like is aliased, set the page name to the first bit
|
||||
# and the link text to the second, otherwise set both to the
|
||||
# contents of the double brackets.
|
||||
if match_data[1] =~ ALIASED_LINK_PATTERN
|
||||
@page_name, @link_text = $1, $2
|
||||
else
|
||||
@page_name, @link_text = match_data[1], match_data[1]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
86
app/models/page.rb
Executable file
86
app/models/page.rb
Executable file
|
@ -0,0 +1,86 @@
|
|||
require "date"
|
||||
require "page_lock"
|
||||
require "revision"
|
||||
require "wiki_words"
|
||||
require "chunks/wiki"
|
||||
|
||||
class Page
|
||||
include PageLock
|
||||
|
||||
CONTINOUS_REVISION_PERIOD = 30 * 60 # 30 minutes
|
||||
|
||||
attr_reader :name, :revisions, :web
|
||||
|
||||
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 !@revisions.empty? && continous_revision?(created_at, author)
|
||||
@revisions.last.created_at = Time.now
|
||||
@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 pretty_revised_on
|
||||
DateTime.new(revised_on.year, revised_on.mon, revised_on.day).strftime "%B %e, %Y"
|
||||
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
|
||||
WikiWords.separate(name, web.brackets_only)
|
||||
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 + CONTINOUS_REVISION_PERIOD > 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
|
24
app/models/page_lock.rb
Executable file
24
app/models/page_lock.rb
Executable file
|
@ -0,0 +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
|
||||
end
|
73
app/models/page_set.rb
Executable file
73
app/models/page_set.rb
Executable file
|
@ -0,0 +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
|
||||
|
||||
end
|
90
app/models/revision.rb
Executable file
90
app/models/revision.rb
Executable file
|
@ -0,0 +1,90 @@
|
|||
$: << File.dirname(__FILE__) + "../../libraries"
|
||||
|
||||
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
|
||||
|
||||
# Ensure that the wiki content is parsed when ever it is updated.
|
||||
def content=(content)
|
||||
@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 - 1 >= 0 && page.revisions[number - 1]
|
||||
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
|
89
app/models/web.rb
Executable file
89
app/models/web.rb
Executable file
|
@ -0,0 +1,89 @@
|
|||
require "cgi"
|
||||
require "page"
|
||||
require "page_set"
|
||||
require "wiki_words"
|
||||
require "zip/zip"
|
||||
|
||||
class Web
|
||||
attr_accessor :pages, :name, :address, :password
|
||||
attr_accessor :markup, :color, :safe_mode, :additional_style, :published, :brackets_only, :count_pages
|
||||
|
||||
def initialize(name, address, password = nil)
|
||||
@name, @address, @password, @safe_mode = name, address, password, false
|
||||
@pages = {}
|
||||
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 = {})
|
||||
page = pages[name]
|
||||
text = text || WikiWords.separate(name)
|
||||
link = CGI.escape(name)
|
||||
|
||||
case options[:mode]
|
||||
when :export
|
||||
if page then "<a class=\"existingWikiWord\" href=\"#{link}.html\">#{text}</a>"
|
||||
else "<span class=\"newWikiWord\">#{text}</span>" end
|
||||
when :publish
|
||||
if page then "<a class=\"existingWikiWord\" href=\"../published/#{link}\">#{text}</a>"
|
||||
else "<span class=\"newWikiWord\">#{text}</span>" end
|
||||
else
|
||||
if page then "<a class=\"existingWikiWord\" href=\"../show/#{link}\">#{text}</a>"
|
||||
else "<span class=\"newWikiWord\">#{text}<a href=\"../show/#{link}\">?</a></span>" end
|
||||
end
|
||||
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
|
||||
|
||||
# Default values
|
||||
def markup() @markup || :textile end
|
||||
def color() @color || "008B26" end
|
||||
def brackets_only() @brackets_only || false end
|
||||
def count_pages() @count_pages || false 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
|
||||
end
|
105
app/models/wiki_content.rb
Executable file
105
app/models/wiki_content.rb
Executable file
|
@ -0,0 +1,105 @@
|
|||
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, URIChunk, WikiChunk::Link, 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
|
||||
|
||||
# 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])
|
||||
rescue => e
|
||||
@rendered = e.message
|
||||
end
|
||||
end
|
||||
|
||||
# Call @web.page_link using current options.
|
||||
def page_link(name, text)
|
||||
@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| self.apply_type!(chunk_type) }
|
||||
|
||||
@rendered = @chunks.map { |chunk| chunk.unmask(self) }.compact
|
||||
(@chunks - @rendered).each { |chunk| chunk.revert(self) }
|
||||
end
|
||||
|
||||
# Find all the chunks of the given type in this content
|
||||
# Each time the type's pattern is matched, create a new
|
||||
# chunk for it, and replace the occurance of the chunk
|
||||
# in this content with its mask.
|
||||
def apply_type!(chunk_type)
|
||||
self.gsub!( chunk_type.pattern ) do |match|
|
||||
@chunks << chunk_type.new($~)
|
||||
@chunks.last.mask(self)
|
||||
end
|
||||
end
|
||||
end
|
168
app/models/wiki_service.rb
Executable file
168
app/models/wiki_service.rb
Executable file
|
@ -0,0 +1,168 @@
|
|||
require 'open-uri'
|
||||
require 'yaml'
|
||||
require 'madeleine'
|
||||
require 'madeleine/automatic'
|
||||
require 'madeleine/zmarshal'
|
||||
|
||||
require 'web'
|
||||
require 'page'
|
||||
require 'author'
|
||||
|
||||
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(name, address, password) unless @webs[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)
|
||||
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 =
|
||||
password, published, brackets_only, count_pages
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
@@storage_path = self.name.downcase + '_storage'
|
||||
|
||||
class << self
|
||||
def storage_path
|
||||
@@storage_path
|
||||
end
|
||||
|
||||
def storage_path=(storage_path)
|
||||
@@storage_path = storage_path
|
||||
end
|
||||
|
||||
def clean_storage
|
||||
MadeleineServer.clean_storage(self)
|
||||
end
|
||||
|
||||
def instance
|
||||
@system ||= MadeleineServer.new(self).system
|
||||
end
|
||||
end
|
||||
|
||||
def initialize
|
||||
init_wiki_service
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class MadeleineServer
|
||||
SNAPSHOT_INTERVAL = 60 * 60 * 24 # Each day
|
||||
AUTOMATIC_SNAPSHOTS = true
|
||||
|
||||
# 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)
|
||||
@server = Madeleine::Automatic::AutomaticSnapshotMadeleine.new(service.storage_path,
|
||||
Madeleine::ZMarshal.new) {
|
||||
service.new
|
||||
}
|
||||
start_snapshot_thread if AUTOMATIC_SNAPSHOTS
|
||||
end
|
||||
|
||||
def system
|
||||
@server.system
|
||||
end
|
||||
|
||||
def start_snapshot_thread
|
||||
Thread.new(@server) {
|
||||
while true
|
||||
sleep(SNAPSHOT_INTERVAL)
|
||||
@server.take_snapshot
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
end
|
25
app/models/wiki_words.rb
Executable file
25
app/models/wiki_words.rb
Executable file
|
@ -0,0 +1,25 @@
|
|||
# Contains all the methods for finding and replacing wiki words
|
||||
module WikiWords
|
||||
# In order of appearance: Latin, greek, cyrillian, armenian
|
||||
I18N_HIGHER_CASE_LETTERS =
|
||||
"ÀÁÂÃÄÅĀĄĂÆÇĆČĈĊĎĐÈÉÊËĒĘĚĔĖĜĞĠĢĤĦÌÍÎÏĪĨĬĮİIJĴĶŁĽĹĻĿÑŃŇŅŊÒÓÔÕÖØŌŐŎŒŔŘŖŚŠŞŜȘŤŢŦȚÙÚÛÜŪŮŰŬŨŲŴÝŶŸŹŽŻ" +
|
||||
"ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩ" +
|
||||
"ΆΈΉΊΌΎΏѠѢѤѦѨѪѬѮѰѲѴѶѸѺѼѾҀҊҌҎҐҒҔҖҘҚҜҞҠҢҤҦҨҪҬҮҰҲҴҶҸҺҼҾӁӃӅӇӉӋӍӐӒӔӖӘӚӜӞӠӢӤӦӨӪӬӮӰӲӴӸЖ" +
|
||||
"ԱԲԳԴԵԶԷԸԹԺԻԼԽԾԿՀՁՂՃՄՅՆՇՈՉՊՋՌՍՏՐՑՒՓՔՕՖ"
|
||||
|
||||
I18N_LOWER_CASE_LETTERS =
|
||||
"àáâãäåāąăæçćčĉċďđèéêëēęěĕėƒĝğġģĥħìíîïīĩĭįıijĵķĸłľĺļŀñńňņʼnŋòóôõöøōőŏœŕřŗśšşŝșťţŧțùúûüūůűŭũųŵýÿŷžżźÞþßſÐð" +
|
||||
"άέήίΰαβγδεζηθικλμνξοπρςστυφχψωϊϋόύώΐ" +
|
||||
"абвгдежзийклмнопрстуфхцчшщъыьэюяѐёђѓєѕіїјљћќѝўџѡѣѥѧѩѫѭѯѱѳѵѷѹѻѽѿҁҋҍҏґғҕҗҙқҝҟҡңҥҧҩҫҭүұҳҵҷҹһҽҿӀӂӄӆӈӊӌӎӑӓӕӗәӛӝӟӡӣӥӧөӫӭӯӱӳӵӹ" +
|
||||
"աբգդեզէըթժիլխծկհձղճմյնշոչպջռսվտրցւփքօֆև"
|
||||
|
||||
WIKI_WORD_PATTERN = '[A-Z' + I18N_HIGHER_CASE_LETTERS + '][a-z' + I18N_LOWER_CASE_LETTERS + ']+[A-Z' + I18N_HIGHER_CASE_LETTERS + ']\w+'
|
||||
|
||||
def self.separate(wiki_word, ignore_separation = false)
|
||||
if ignore_separation
|
||||
wiki_word
|
||||
else
|
||||
wiki_word.gsub(/([a-z#{I18N_LOWER_CASE_LETTERS}])([A-Z#{I18N_HIGHER_CASE_LETTERS}])/u, '\1 \2')
|
||||
end
|
||||
end
|
||||
end
|
4
app/views/bottom.rhtml
Executable file
4
app/views/bottom.rhtml
Executable file
|
@ -0,0 +1,4 @@
|
|||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
16
app/views/markdown_help.rhtml
Executable file
16
app/views/markdown_help.rhtml
Executable file
|
@ -0,0 +1,16 @@
|
|||
<div id="TextileHelp" style="float: right; width: 250px; margin-top: 5px">
|
||||
<h3>Markdown formatting tips (<a target="_new" href="http://daringfireball.net/projects/markdown/syntax">advanced</a>)</h3>
|
||||
<table cellspacing="0" cellpadding="0">
|
||||
<tr><td>_your text_</td><td class="arrow">→</td><td><em>your text</em></td></tr>
|
||||
<tr><td>**your text**</td><td class="arrow">→</td><td><strong>your text</strong></td></tr>
|
||||
<tr><td>`my code`</td><td class="arrow">→</td><td><code>my code</code></td></tr>
|
||||
<tr><td>* Bulleted list<br />* Second item</td><td class="arrow">→</td><td>• Bulleted list<br />• Second item</td></tr>
|
||||
<tr><td>1. Numbered list<br />1. Second item</td><td class="arrow">→</td><td>1. Numbered list<br />2. Second item</td></tr>
|
||||
<tr><td>[link name](URL)</td><td class="arrow">→</td><td><a href="URL">link name</a></td></tr>
|
||||
<tr><td>***</td><td class="arrow">→</td><td>Horizontal ruler</td></tr>
|
||||
<tr><td><http://url><br /><email@add.com></td><td class="arrow">→</td><td>Auto-linked</td></tr>
|
||||
<tr><td>![Alt text](URL)</td><td class="arrow">→</td><td>Image</td></tr>
|
||||
</table>
|
||||
|
||||
<%= render 'wiki_words_help' %>
|
||||
</div>
|
25
app/views/navigation.rhtml
Executable file
25
app/views/navigation.rhtml
Executable file
|
@ -0,0 +1,25 @@
|
|||
<%
|
||||
def list_item(title, url, description, accesskey = nil)
|
||||
if @title == title
|
||||
"<b class=\"navOn\" title=\"#{description}\" accesskey=\"#{accesskey}\">#{title}</b>"
|
||||
else
|
||||
"<a href=\"#{url}\" title=\"#{description}\" accesskey=\"#{accesskey}\">#{title}</a>"
|
||||
end
|
||||
end
|
||||
%>
|
||||
|
||||
<form id="navigationForm" class="navigation" action="../search/" method="get" style="font-size: 10px">
|
||||
|
||||
<% if @action_name != "published" then %>
|
||||
<%= list_item "Home Page", "../show/HomePage", "Home, Sweet Home", "H" %> |
|
||||
<%= list_item "All Pages", "../list/", "Alphabetically sorted list of pages", "A" %> |
|
||||
<%= list_item "Recently Revised", "../recently_revised/", "Pages sorted by when they were last changed", "U" %> |
|
||||
<%= list_item "Authors", "../authors/", "Who wrote what" %> |
|
||||
<%= list_item "Feeds", "../feeds/", "Subscribe to changes by RSS" %> |
|
||||
<%= list_item "Export", "../export/", "Download a zip with all the pages in this wiki", "X" %> |
|
||||
<input type="text" id="searchField" name="query" style="font-size: 10px" value="Search" onClick="this.value == 'Search' ? this.value = '' : true">
|
||||
<% else %>
|
||||
<%= list_item "Home Page", "../published/HomePage", "Home, Sweet Home", "H" %> |
|
||||
<% end%>
|
||||
|
||||
</form>
|
16
app/views/rdoc_help.rhtml
Executable file
16
app/views/rdoc_help.rhtml
Executable file
|
@ -0,0 +1,16 @@
|
|||
<div id="TextileHelp" style="float: right; width: 250px; margin-top: 5px">
|
||||
<h3>RDoc formatting tips (<a target="_new" href="http://rdoc.sourceforge.net/doc/files/markup/simple_markup_rb.html">advanced</a>)</h3>
|
||||
<table cellspacing="0" cellpadding="0">
|
||||
<tr><td>_your text_</td><td class="arrow">→</td><td><em>your text</em></td></tr>
|
||||
<tr><td>*your text*</td><td class="arrow">→</td><td><strong>your text</strong></td></tr>
|
||||
<tr><td>* Bulleted list<br />* Second item</td><td class="arrow">→</td><td>• Bulleted list<br />• Second item</td></tr>
|
||||
<tr><td>1. Numbered list<br />2. Second item</td><td class="arrow">→</td><td>1. Numbered list<br />2. Second item</td></tr>
|
||||
<tr><td>+my_code+</td><td class="arrow">→</td><td><code>my_code</code></td></tr>
|
||||
<tr><td>---</td><td class="arrow">→</td><td>Horizontal ruler</td></tr>
|
||||
<tr><td>[[URL linkname]]</td><td class="arrow">→</td><td><a href="URL">linkname</a></td></tr>
|
||||
<tr><td>http://url<br />mailto:e@add.com</td><td class="arrow">→</td><td>Auto-linked</td></tr>
|
||||
<tr><td>imageURL</td><td class="arrow">→</td><td>Image</td></tr>
|
||||
</table>
|
||||
|
||||
<%= render 'wiki_words_help' %>
|
||||
</div>
|
28
app/views/textile_help.rhtml
Executable file
28
app/views/textile_help.rhtml
Executable file
|
@ -0,0 +1,28 @@
|
|||
<div id="TextileHelp" style="float: right; width: 250px; margin-top: 5px">
|
||||
<h3>Textile formatting tips (<a href="#" onClick="quickRedReference(); return false;">advanced</a>)</h3>
|
||||
<table cellspacing="0" cellpadding="0">
|
||||
<tr><td>_your text_</td><td class="arrow">→</td><td><em>your text</em></td></tr>
|
||||
<tr><td>*your text*</td><td class="arrow">→</td><td><strong>your text</strong></td></tr>
|
||||
<tr><td>%{color:red}hello%</td><td class="arrow">→</td><td><span style="color: red;">hello</span></td></tr>
|
||||
<tr><td>* Bulleted list<br />* Second item</td><td class="arrow">→</td><td>• Bulleted list<br />• Second item</td></tr>
|
||||
<tr><td># Numbered list<br /># Second item</td><td class="arrow">→</td><td>1. Numbered list<br />2. Second item</td></tr>
|
||||
<tr><td>"linkname":URL</td><td class="arrow">→</td><td><a href="URL">linkname</a></td></tr>
|
||||
<tr><td>|a|table|row|<br />|b|table|row|</td><td class="arrow">→</td><td>Table</td></tr>
|
||||
<tr><td>http://url<br />email@address.com</td><td class="arrow">→</td><td>Auto-linked</td></tr>
|
||||
<tr><td>!imageURL!</td><td class="arrow">→</td><td>Image</td></tr>
|
||||
</table>
|
||||
|
||||
<%= render 'wiki_words_help' %>
|
||||
</div>
|
||||
|
||||
<script language="JavaScript">
|
||||
function quickRedReference() {
|
||||
window.open(
|
||||
"http://hobix.com/textile/quick.html",
|
||||
"redRef",
|
||||
"height=600,width=550,channelmode=0,dependent=0," +
|
||||
"directories=0,fullscreen=0,location=0,menubar=0," +
|
||||
"resizable=0,scrollbars=1,status=1,toolbar=0"
|
||||
);
|
||||
}
|
||||
</script>
|
49
app/views/top.rhtml
Executable file
49
app/views/top.rhtml
Executable file
|
@ -0,0 +1,49 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<title>
|
||||
<% if @page and (@page.name == 'HomePage') and (%w( show published print ).include?(@action_name)) %>
|
||||
<%= @web.name %>
|
||||
<% elsif @web %>
|
||||
<%= @title %> in <%= @web.name %>
|
||||
<% else %>
|
||||
<%= @title %>
|
||||
<% end %>
|
||||
</title>
|
||||
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
|
||||
<style type="text/css">
|
||||
h1#pageName, .newWikiWord a, a.existingWikiWord, .newWikiWord a:hover, #TextileHelp h3 {
|
||||
color: #<%= @web ? @web.color : "393" %>;
|
||||
}
|
||||
|
||||
#Container, #Content {
|
||||
width: <%= @content_width || "600" %>px;
|
||||
}
|
||||
<%= File.read(RAILS_ROOT + '/public/stylesheets/instiki.css') if @inline_style %>
|
||||
</style>
|
||||
|
||||
<link rel="Stylesheet" href="/stylesheets/instiki.css" type="text/css" media="screen" />
|
||||
|
||||
<style type="text/css">
|
||||
<%= @style_additions %>
|
||||
<%= @web ? @web.additional_style : "" %>
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="Container">
|
||||
<div id="Content">
|
||||
|
||||
<h1 id="pageName">
|
||||
<% if @page and (@page.name == 'HomePage') and %w( show published print ).include?(@action_name) %>
|
||||
<%= @web.name %>
|
||||
<% elsif @web %>
|
||||
<small><%= @web.name %></small><br />
|
||||
<%= @title %>
|
||||
<% else %>
|
||||
<%= @title %>
|
||||
<% end %>
|
||||
</h1>
|
||||
|
||||
<%= render 'navigation' unless @web.nil? || @hide_navigation %>
|
13
app/views/wiki/authors.rhtml
Executable file
13
app/views/wiki/authors.rhtml
Executable file
|
@ -0,0 +1,13 @@
|
|||
<% @title = 'Authors' %><%= render 'top' %>
|
||||
|
||||
<ul id="authorList">
|
||||
<% for author in @authors %>
|
||||
<li>
|
||||
<%= @web.make_link(author) %>
|
||||
co- or authored:
|
||||
<%= @web.select.pages_authored_by(author).collect { |page| page.link }.join ', ' %>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
|
||||
<%= render 'bottom' %>
|
31
app/views/wiki/edit.rhtml
Executable file
31
app/views/wiki/edit.rhtml
Executable file
|
@ -0,0 +1,31 @@
|
|||
<%
|
||||
@title = "Editing #{@page.name}"
|
||||
@content_width = 720
|
||||
@hide_navigation = true
|
||||
%><%= render 'top' %>
|
||||
|
||||
<%= "<p style='color:red'>Please correct the error that caused this error in rendering:<br/><small>#{@params["msg"]}</small></p>" if @params["msg"] %>
|
||||
|
||||
<%= render("#{@web.markup}_help") if @web %>
|
||||
|
||||
<form id="editForm" action="../save/<%= @page.name %>" method="post" onSubmit="cleanAuthorName();">
|
||||
<p>
|
||||
<textarea name="content" style="width: 450px; height: 500px"><%= @page.content %></textarea>
|
||||
</p>
|
||||
<p>
|
||||
<input type="submit" value="Update"> as
|
||||
<input type="text" name="author" id="authorName" value="<%= @author %>"
|
||||
onClick="this.value == 'AnonymousCoward' ? this.value = '' : true">
|
||||
| <a href="../cancel_edit/<%= @page.name %>">Cancel</a> <small>(unlocks page)</small>
|
||||
</p>
|
||||
</form>
|
||||
|
||||
<script language="JavaScript1.2">
|
||||
function cleanAuthorName() {
|
||||
if (document.getElementById('authorName').value == "") {
|
||||
document.getElementById('authorName').value = 'AnonymousCoward';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<%= render 'bottom' %>
|
138
app/views/wiki/edit_web.rhtml
Executable file
138
app/views/wiki/edit_web.rhtml
Executable file
|
@ -0,0 +1,138 @@
|
|||
<% @title = "Edit Web" %><%= render 'top' %>
|
||||
|
||||
<form action="../update_web" id="setup" method="post" onSubmit="cleanAddress(); return validateSetup()">
|
||||
<h2 style="margin-bottom: 3px">Name and address</h2>
|
||||
<div class="help">
|
||||
The name of the web is included in the title on all pages. The address is the base path that all pages within the web live beneath.
|
||||
Ex: the address "rails" gives URLs like <i>/rails/show/HomePage</i>.
|
||||
</div>
|
||||
|
||||
<div class="inputBox">
|
||||
Name: <input type="text" id="name" name="name" value="<%= @web.name %>" onChange="proposeAddress();">
|
||||
Address: <input type="text" id="address" name="address" value="<%= @web.address %>" onChange="cleanAddress();">
|
||||
<i>(Letters & digits only)</i>
|
||||
</div>
|
||||
|
||||
<h2 style="margin-bottom: 3px">Specialize</h2>
|
||||
<div class="help">
|
||||
Turning safe mode on will strip HTML tags and stylesheet options from the content of all pages.
|
||||
Turning on "brackets only" will require all wiki words to be as [[wiki word]] and WikiWord won't work.
|
||||
Additions to the stylesheet take precedence over the existing styles. <i>Hint:</i> View source on a page you want to
|
||||
style to find ID names on individual tags. <a href="#" onClick="document.getElementById('additionalStyle').style.display='block';return false;">See styles >></a>
|
||||
</div>
|
||||
<div class="inputBox">
|
||||
Markup:
|
||||
<select name="markup">
|
||||
<%= html_options({ "Textile" => :textile, "Markdown" => :markdown, "RDoc" => :rdoc }, @web.markup) %>
|
||||
</select>
|
||||
|
||||
|
||||
|
||||
Color:
|
||||
<select name="color">
|
||||
<%= html_options({ "Green" => "008B26", "Purple" => "504685", "Red" => "DA0006", "Orange" => "FA6F00", "Grey" => "8BA2B0" },
|
||||
@web.color) %>
|
||||
</select>
|
||||
|
||||
|
||||
|
||||
<small>
|
||||
|
||||
<input type="checkbox" name="safe_mode"<%= " CHECKED" if @web.safe_mode %>> Safe mode
|
||||
|
||||
|
||||
|
||||
<input type="checkbox" name="brackets_only"<%= " CHECKED" if @web.brackets_only %>> Brackets only
|
||||
|
||||
|
||||
|
||||
<input type="checkbox" name="count_pages"<%= " CHECKED" if @web.count_pages %>> Count pages
|
||||
|
||||
</small>
|
||||
|
||||
<textarea id="additionalStyle" style="display: none; margin-top: 10px; margin-bottom: 5px; width: 560px; height: 200px" name="additional_style"><%= @web.additional_style %></textarea>
|
||||
</div>
|
||||
|
||||
<h2 style="margin-bottom: 3px">Password protection for this web (<%= @web.name %>)</h2>
|
||||
<div class="help">
|
||||
This is the password that visitors need to view and edit this web. Setting the password to nothing will remove the password protection.
|
||||
</div>
|
||||
<div class="inputBox">
|
||||
Password: <input type="password" id="password" name="password" value="<%= @web.password %>">
|
||||
Verify: <input type="password" id="password_check" value="<%= @web.password %>" name="password_check">
|
||||
</div>
|
||||
|
||||
<h2 style="margin-bottom: 3px">Publish read-only version of this web (<%= @web.name %>)</h2>
|
||||
<div class="help">
|
||||
You can turn on a read-only version of this web that's accessible even when the regular web is password protected.
|
||||
The published version is accessible through URLs like /wiki/published/HomePage.
|
||||
</div>
|
||||
<div class="inputBox">
|
||||
<input type="checkbox" name="published"<%= " CHECKED" if @web.published %>> Publish this web
|
||||
</div>
|
||||
|
||||
<p align="right">
|
||||
<small>
|
||||
Enter system password
|
||||
<input type="password" id="system_password" name="system_password">
|
||||
and
|
||||
<input type="submit" value="Update Web">
|
||||
<br/><br/>
|
||||
...or forget changes and <a href="/new_web/">create a new web</a>
|
||||
</small>
|
||||
</p>
|
||||
|
||||
</form>
|
||||
|
||||
<br/>
|
||||
<h1>Other administrative tasks</h1>
|
||||
|
||||
<form action="../remove_orphaned_pages" id="remove_orphaned_pages" method="post">
|
||||
|
||||
<p align="right">
|
||||
<small>
|
||||
Clean up by entering system password
|
||||
<input type="password" id="system_password" name="system_password">
|
||||
and
|
||||
<input type="submit" value="Delete Orphan Pages">
|
||||
</small>
|
||||
</p>
|
||||
|
||||
<script>
|
||||
function proposeAddress() {
|
||||
document.getElementById('address').value =
|
||||
document.getElementById('name').value.replace(/[^a-zA-Z0-9]/g, "").toLowerCase();
|
||||
}
|
||||
|
||||
function cleanAddress() {
|
||||
document.getElementById('address').value =
|
||||
document.getElementById('address').value.replace(/[^a-zA-Z0-9]/g, "").toLowerCase();
|
||||
}
|
||||
|
||||
function validateSetup() {
|
||||
if (document.getElementById('system_password').value == "") {
|
||||
alert("You must enter the system password");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (document.getElementById('name').value == "") {
|
||||
alert("You must pick a name for the web");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (document.getElementById('address').value == "") {
|
||||
alert("You must pick an address for the web");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (document.getElementById('password').value != "" &&
|
||||
document.getElementById('password').value != document.getElementById('password_check').value) {
|
||||
alert("The password and its verification doesn't match");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<%= render 'bottom' %>
|
14
app/views/wiki/export.rhtml
Executable file
14
app/views/wiki/export.rhtml
Executable file
|
@ -0,0 +1,14 @@
|
|||
<% @title = "Export" %><%= render 'top' %>
|
||||
|
||||
<p>You can export all the pages in this web as a zip file in either HTML (with working links and all) or the pure markup (to import in another wiki).</p>
|
||||
|
||||
<ul id="feedsList">
|
||||
<li><a href="../export_html">HTML</a>
|
||||
<li><a href="../export_markup">Markup (<%= @web.markup %>)</a>
|
||||
<% if OPTIONS[:pdflatex] && @web.markup == :textile %>
|
||||
<li><a href="../export_tex">TeX</a>
|
||||
<li><a href="../export_pdf">PDF</a>
|
||||
<% end %>
|
||||
</ul>
|
||||
|
||||
<%= render 'bottom' %>
|
10
app/views/wiki/feeds.rhtml
Executable file
10
app/views/wiki/feeds.rhtml
Executable file
|
@ -0,0 +1,10 @@
|
|||
<% @title = "Feeds" %><%= render 'top' %>
|
||||
|
||||
<p>You can subscribe to this wiki by RSS and get either just the headlines of the pages that change or the entire page.</p>
|
||||
|
||||
<ul id="feedsList">
|
||||
<li><a href="../rss_with_content<%= "?password=#{web.password}" if @web.password %>">Full content (RSS 2.0)</a>
|
||||
<li><a href="../rss_with_headlines<%= "?password=#{web.password}" if @web.password %>">Headlines (RSS 2.0)</a>
|
||||
</ul>
|
||||
|
||||
<%= render 'bottom' %>
|
59
app/views/wiki/list.rhtml
Executable file
59
app/views/wiki/list.rhtml
Executable file
|
@ -0,0 +1,59 @@
|
|||
<% @title = "All Pages" %><%= render 'top' %>
|
||||
|
||||
<% unless @categories.empty? %>
|
||||
<div id="categories">
|
||||
<strong>Categories</strong>:
|
||||
[<a href=".">Any</a>]
|
||||
<%= @category_links.join(', ') %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div id="allPages" style="float: left; width: 280px; margin-right: 30px">
|
||||
<% unless @pages_that_are_orphaned.empty? && @page_names_that_are_wanted.empty? %>
|
||||
<h2>
|
||||
All Pages
|
||||
<br/><small style="font-size: 12px"><i>All pages in <%= @set_name %> listed alphabetically</i></small>
|
||||
</h2>
|
||||
<% end %>
|
||||
|
||||
<ul><% for page in @pages_by_name %>
|
||||
<li><a href="../show/<%= page.name %>"><%= truncate(page.plain_name, 35) %></a></li>
|
||||
<% end %></ul>
|
||||
|
||||
<% if @web.count_pages %>
|
||||
<% total_chars = @pages_in_category.characters %>
|
||||
<p><small>All content: <%= total_chars %> chars / <%= sprintf("%-.1f", (total_chars / 2275 )) %> pages</small></p>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div style="float: left; width: 280px">
|
||||
<% unless @page_names_that_are_wanted.empty? %>
|
||||
<h2>
|
||||
Wanted Pages
|
||||
<br/><small style="font-size: 12px"><i>Unexisting pages that other pages in <%= @set_name %> reference</i></small>
|
||||
</h2>
|
||||
|
||||
<ul style="margin-bottom: 10px">
|
||||
<% for page_name in @page_names_that_are_wanted %>
|
||||
<li>
|
||||
<a href="../show/<%= page_name %>"><%= truncate(WikiWords.separate(page_name), 35) %></a>
|
||||
wanted by
|
||||
<%= web.select.pages_that_reference(page_name).collect { |page| page.link }.join(", ") %>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
<% end %>
|
||||
|
||||
<% unless @pages_that_are_orphaned.empty? %>
|
||||
<h2>
|
||||
Orphaned Pages
|
||||
<br/><small style="font-size: 12px"><i>Pages in <%= @set_name %> that no other page reference</i></small>
|
||||
</h2>
|
||||
|
||||
<ul style="margin-bottom: 35px">
|
||||
<% for page in @pages_that_are_orphaned %><li><a href="../show/<%= page.name %>"><%= truncate(page.plain_name, 35) %></a></li><% end %>
|
||||
</ul>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<%= render 'bottom' %>
|
14
app/views/wiki/locked.rhtml
Executable file
14
app/views/wiki/locked.rhtml
Executable file
|
@ -0,0 +1,14 @@
|
|||
<% @title = "#{@page.plain_name} is locked" %><%= render 'top' %>
|
||||
|
||||
<% if @page.lock_duration(Time.now) == 0 %>
|
||||
<p><%= @page.locked_by_link %> just started editing this page.</p>
|
||||
<% else %>
|
||||
<p><%= @page.locked_by_link %> has been editing this page for <%= @page.lock_duration(Time.now) %> minutes.</p>
|
||||
<% end %>
|
||||
|
||||
<p>
|
||||
<a href="<%= @page.name %>?break_lock=1" accesskey="E">Edit the page anyway</a> |
|
||||
<a href="../show/<%= @page.name %>">Cancel</a>
|
||||
</p>
|
||||
|
||||
<%= render 'bottom' %>
|
11
app/views/wiki/login.rhtml
Executable file
11
app/views/wiki/login.rhtml
Executable file
|
@ -0,0 +1,11 @@
|
|||
<% @title = "#{@web_name} Login" %><% @hide_navigation = true %><%= render 'top' %>
|
||||
|
||||
<form action="authenticate" method="post">
|
||||
<p>
|
||||
<b>Password</b><br />
|
||||
<input type="password" name="password" />
|
||||
</p>
|
||||
</form>
|
||||
|
||||
|
||||
<%= render 'bottom' %>
|
27
app/views/wiki/new.rhtml
Executable file
27
app/views/wiki/new.rhtml
Executable file
|
@ -0,0 +1,27 @@
|
|||
<%
|
||||
@title = "Creating #{WikiWords.separate(CGI.unescape(@page_name))}"
|
||||
@content_width = 720
|
||||
@hide_navigation = true
|
||||
%><%= render 'top' %>
|
||||
|
||||
<%= render("#{@web.markup}_help") if @web %>
|
||||
|
||||
<form action="../save/<%= @page_name %>" method="post" onSubmit="cleanAuthorName();">
|
||||
<p>
|
||||
<textarea name="content" style="width: 450px; height: 500px"></textarea>
|
||||
</p>
|
||||
<p>
|
||||
<input type="submit" value="Create"> as
|
||||
<input type="text" name="author" id="authorName" value="<%= @author %>" onClick="this.value == 'AnonymousCoward' ? this.value = '' : true">
|
||||
</p>
|
||||
</form>
|
||||
|
||||
<script language="JavaScript1.2">
|
||||
function cleanAuthorName() {
|
||||
if (document.getElementById('authorName').value == "") {
|
||||
document.getElementById('authorName').value = 'AnonymousCoward';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<%= render 'bottom' %>
|
78
app/views/wiki/new_system.rhtml
Executable file
78
app/views/wiki/new_system.rhtml
Executable file
|
@ -0,0 +1,78 @@
|
|||
<% @title = "Instiki Setup"; @content_width = 500 %><%= render 'top' %>
|
||||
|
||||
<p>
|
||||
Congratulations on succesfully installing and starting Instiki.
|
||||
Since this is the first time Instiki has been run on this port, you'll need to do a brief one-time setup.
|
||||
</p>
|
||||
|
||||
<form action="../create_system" id="setup" method="post" onSubmit="return validateSetup()">
|
||||
<ol class="setup">
|
||||
<li>
|
||||
|
||||
<h2 style="margin-bottom: 3px">Name and address for your first web</h2>
|
||||
<div class="help">
|
||||
The name of the web is included in the title on all pages. The address is the base path that all pages within the web live beneath. Ex: the address "rails" gives URLs like <i>/rails/show/HomePage</i>. The address can only consist of letters & digits.
|
||||
</div>
|
||||
<div class="inputBox">
|
||||
Name: <input type="text" id="web_name" name="web_name" value="Wiki" onChange="proposeAddress();"
|
||||
onClick="this.value == 'Wiki' ? this.value = '' : true">
|
||||
Address: <input type="text" id="web_address" name="web_address" onChange="cleanAddress();" value="wiki">
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<h2 style="margin-bottom: 3px">Password for creating and changing webs</h2>
|
||||
<div class="help">
|
||||
Administrative access allows you to make new webs and change existing ones.<br/>
|
||||
Everyone with this password will be able to do this, so pick it carefully.
|
||||
</div>
|
||||
<div class="inputBox">
|
||||
Password: <input type="password" id="password" name="password">
|
||||
Verify: <input type="password" id="password_check" name="password_check">
|
||||
</div>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<p align="right">
|
||||
<input type="submit" value="Setup" style="margin-left: 40px">
|
||||
</p>
|
||||
</form>
|
||||
|
||||
<script>
|
||||
function proposeAddress() {
|
||||
document.getElementById('web_address').value =
|
||||
document.getElementById('web_name').value.replace(/[^a-zA-Z0-9]/g, "").toLowerCase();
|
||||
}
|
||||
|
||||
function cleanAddress() {
|
||||
document.getElementById('web_address').value =
|
||||
document.getElementById('web_address').value.replace(/[^a-zA-Z0-9]/g, "").toLowerCase();
|
||||
}
|
||||
|
||||
function validateSetup() {
|
||||
if (document.getElementById('web_name').value == "") {
|
||||
alert("You must pick a name for the first web");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (document.getElementById('web_address').value == "") {
|
||||
alert("You must pick an address for the first web");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (document.getElementById('password').value == "") {
|
||||
alert("You must pick a system password");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (document.getElementById('password_check').value == "" ||
|
||||
document.getElementById('password').value != document.getElementById('password_check').value) {
|
||||
alert("The password and its verification doesn't match");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<%= render 'bottom' %>
|
64
app/views/wiki/new_web.rhtml
Executable file
64
app/views/wiki/new_web.rhtml
Executable file
|
@ -0,0 +1,64 @@
|
|||
<% @title = "New Wiki Web"; @content_width = 500 %><%= render 'top' %>
|
||||
|
||||
<p>
|
||||
Each web serves as an isolated name space for wiki pages, so different subjects or projects can write about different <i>MuppetShows</i>.
|
||||
</p>
|
||||
|
||||
<form action="../create_web" id="setup" method="post" onSubmit="cleanAddress(); return validateSetup()">
|
||||
<ol class="setup">
|
||||
<li>
|
||||
<h2 style="margin-bottom: 3px">Name and address for your new web</h2>
|
||||
<div class="help">
|
||||
The name of the web is included in the title on all pages. The address is the base path that all pages within the web live beneath. Ex: the address "rails" gives URLs like <i>/rails/show/HomePage</i>. The address can only consist of letters & digits.
|
||||
</div>
|
||||
<div class="inputBox">
|
||||
Name: <input type="text" id="web_name" name="name" onChange="proposeAddress();" />
|
||||
Address: <input type="text" id="web_address" name="address" onChange="cleanAddress();" />
|
||||
</div>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
|
||||
<p align="right">
|
||||
<small>
|
||||
Enter system password
|
||||
<input type="password" id="system_password" name="system_password">
|
||||
and
|
||||
<input type="submit" value="Create Web">
|
||||
</small>
|
||||
</p>
|
||||
|
||||
</form>
|
||||
|
||||
<script>
|
||||
function proposeAddress() {
|
||||
document.getElementById('web_address').value =
|
||||
document.getElementById('web_name').value.replace(/[^a-zA-Z0-9]/g, "").toLowerCase();
|
||||
}
|
||||
|
||||
function cleanAddress() {
|
||||
document.getElementById('web_address').value =
|
||||
document.getElementById('web_address').value.replace(/[^a-zA-Z0-9]/g, "").toLowerCase();
|
||||
}
|
||||
|
||||
function validateSetup() {
|
||||
if (document.getElementById('web_name').value == "") {
|
||||
alert("You must pick a name for the new web");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (document.getElementById('web_address').value == "") {
|
||||
alert("You must pick an address for the new web");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (document.getElementById('system_password').value == "") {
|
||||
alert("You must enter the system password");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<%= render 'bottom' %>
|
81
app/views/wiki/page.rhtml
Executable file
81
app/views/wiki/page.rhtml
Executable file
|
@ -0,0 +1,81 @@
|
|||
<% @title = @page.plain_name %>
|
||||
<%= render 'top' %>
|
||||
|
||||
<div id="revision">
|
||||
<%= @page.display_content %>
|
||||
</div>
|
||||
|
||||
<div id="changes" style="display: none">
|
||||
<p style="background: #eee; padding: 3px; border: 1px solid silver">
|
||||
<small>
|
||||
Showing changes from revision #<%= @page.number - 1 %> to #<%= @page.number %>:
|
||||
<ins class="diffins">Added</ins> | <del class="diffdel">Removed</del>
|
||||
</small>
|
||||
</p>
|
||||
|
||||
<%= @page.display_diff %>
|
||||
</div>
|
||||
|
||||
<div class="byline">
|
||||
<%= @page.revisions? ? "Revised" : "Created" %> on <%= @page.pretty_created_at %>
|
||||
by <%= @page.author_link %>
|
||||
<%= "(#{@page.author.ip})" if @page.author.respond_to?(:ip) %>
|
||||
<% if @web.count_pages %>
|
||||
<% total_chars = @page.content.length %>
|
||||
(<%= total_chars %> characters / <%= sprintf("%-.1f", (total_chars / 2275 rescue 0)) %> pages)
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="navigation">
|
||||
<% if @page.name == "HomePage" %>
|
||||
<a href="../edit/<%= @page.name %>" class="navlink" accesskey="E">Edit Page</a>
|
||||
| <a href="../edit_web/" class="navlink">Edit Web</a>
|
||||
<% else %>
|
||||
<a href="../edit/<%= @page.name %>" class="navlink" accesskey="E">Edit</a>
|
||||
<% end %>
|
||||
|
||||
<% if @page.revisions.length > 1 %>
|
||||
| <a href="../revision/<%= @page.name %>?rev=<%= @page.revisions.length - 2 %>" class="navlink" accesskey="R">Back in time</a>
|
||||
<small>(<%= @page.revisions.length - 1 %> revisions)</small>
|
||||
<% end %>
|
||||
|
||||
<% if @page.revisions.length > 1 %>
|
||||
<span id="show_changes">
|
||||
| <a href="#" onClick="toggleChanges(); return false;">See changes</a>
|
||||
</span>
|
||||
<span id="hide_changes" style="display: none">
|
||||
| <a href="#" onClick="toggleChanges(); return false;">Hide changes</a>
|
||||
</span>
|
||||
<% end %>
|
||||
|
||||
<small>
|
||||
| Views: <a href="../print/<%= @page.name %>">Print</a>
|
||||
<% if defined? RedClothForTex and RedClothForTex.available? and @web.markup == :textile %>
|
||||
| <a href="../tex/<%= @page.name %>">TeX</a> | <a href="../pdf/<%= @page.name %>">PDF</a>
|
||||
<% end %>
|
||||
</small>
|
||||
|
||||
<% if @page.references.length > 0 %>
|
||||
<small>
|
||||
| Linked from: <%= @page.references.collect { |ref| ref.link }.join(", ") %>
|
||||
</small>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<script language="Javascript">
|
||||
function toggleChanges() {
|
||||
if (document.getElementById("changes").style.display == "none") {
|
||||
document.getElementById("changes").style.display = "block";
|
||||
document.getElementById("revision").style.display = "none";
|
||||
document.getElementById("show_changes").style.display = "none";
|
||||
document.getElementById("hide_changes").style.display = "inline";
|
||||
} else {
|
||||
document.getElementById("changes").style.display = "none";
|
||||
document.getElementById("revision").style.display = "block";
|
||||
document.getElementById("show_changes").style.display = "inline";
|
||||
document.getElementById("hide_changes").style.display = "none";
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<%= render 'bottom' %>
|
16
app/views/wiki/print.rhtml
Executable file
16
app/views/wiki/print.rhtml
Executable file
|
@ -0,0 +1,16 @@
|
|||
<%
|
||||
@title = @page.plain_name
|
||||
@hide_navigation = true
|
||||
@style_additions = ".newWikiWord { background-color: white; font-style: italic; }"
|
||||
@inline_style = true
|
||||
%><%= render 'top' %>
|
||||
|
||||
<%= @page.display_content_for_export %>
|
||||
|
||||
<div class="byline">
|
||||
<%= @page.revisions? ? "Revised" : "Created" %> on <%= @page.pretty_created_at %>
|
||||
by
|
||||
<%= @page.author_link({ :mode => :export }) %>
|
||||
</div>
|
||||
|
||||
<%= render 'bottom' %>
|
10
app/views/wiki/published.rhtml
Executable file
10
app/views/wiki/published.rhtml
Executable file
|
@ -0,0 +1,10 @@
|
|||
<%
|
||||
@title = @page.plain_name
|
||||
@hide_navigation = false
|
||||
@style_additions = ".newWikiWord { background-color: white; font-style: italic; }"
|
||||
@inline_style = true
|
||||
%><%= render 'top' %>
|
||||
|
||||
<%= @page.display_published %>
|
||||
|
||||
<%= render 'bottom' %>
|
30
app/views/wiki/recently_revised.rhtml
Executable file
30
app/views/wiki/recently_revised.rhtml
Executable file
|
@ -0,0 +1,30 @@
|
|||
<% @title = "Recently Revised" %><%= render 'top' %>
|
||||
|
||||
<% unless @categories.empty? %>
|
||||
<div id="categories">
|
||||
<strong>Categories</strong>:
|
||||
[<a href=".">Any</a>]
|
||||
<%= @category_links.join(', ') %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<% revision_date = Date.new(2100) %>
|
||||
<ul>
|
||||
<% for page in @pages_by_revision %>
|
||||
<% if page.revised_on < revision_date %>
|
||||
</ul><b><%= page.pretty_revised_on %></b><ul>
|
||||
<% end %>
|
||||
|
||||
<li>
|
||||
<a href="../show/<%= page.name %>"><%= page.plain_name %></a>
|
||||
<div class="byline" style="margin-bottom: 0px">
|
||||
by <%= page.author_link %>
|
||||
at <%= page.created_at.strftime "%H:%M" %>
|
||||
<%= "from #{page.author.ip}" if page.author.respond_to?(:ip) %>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<% revision_date = page.revised_on %>
|
||||
<% end %>
|
||||
|
||||
<%= render 'bottom' %>
|
81
app/views/wiki/revision.rhtml
Executable file
81
app/views/wiki/revision.rhtml
Executable file
|
@ -0,0 +1,81 @@
|
|||
<% @title = "#{@page.plain_name} (Rev ##{@revision.number})" %><%= render 'top' %>
|
||||
|
||||
<div id="revision">
|
||||
<%= @revision.display_content %>
|
||||
</div>
|
||||
|
||||
<div id="changes" style="display: none">
|
||||
<p style="background: #eee; padding: 3px; border: 1px solid silver">
|
||||
<small>
|
||||
Showing changes from revision #<%= @revision.number - 1 %> to #<%= @revision.number %>:
|
||||
<ins class="diffins">Added</ins> | <del class="diffdel">Removed</del>
|
||||
</small>
|
||||
</p>
|
||||
|
||||
<%= @revision.display_diff %>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="byline">
|
||||
<%= "Revision from #{@revision.pretty_created_at} by" %>
|
||||
<%= @page.web.make_link(@revision.author) %>
|
||||
</div>
|
||||
|
||||
<div class="navigation">
|
||||
|
||||
<% if @revision.next_revision %>
|
||||
<% if @revision.next_revision.number < (@page.revisions.length - 1) %>
|
||||
<a href="../revision/<%= @page.name %>?rev=<%= @revision.next_revision.number %>" class="navlink">
|
||||
<% else %>
|
||||
<a href="../show/<%= @page.name %>" class="navlink">
|
||||
<% end %>
|
||||
Forward in time</a>
|
||||
(<%= @revision.page.revisions.length - @revision.next_revision.number %> more)
|
||||
<% end %>
|
||||
|
||||
<% if @revision.next_revision && @revision.previous_revision %>
|
||||
|
|
||||
<% end %>
|
||||
|
||||
<% if @revision.previous_revision %>
|
||||
<a href="../revision/<%= @page.name %>?rev=<%= @revision.previous_revision.number %>" class="navlink">Back in time</a>
|
||||
(<%= @revision.previous_revision.number + 1 %> more)
|
||||
<% end %>
|
||||
|
||||
| <a href="../show/<%= @page.name %>" class="navlink">See current</a>
|
||||
|
||||
<% if @revision.previous_revision %>
|
||||
<span id="show_changes">
|
||||
| <a href="#" onClick="toggleChanges(); return false;">See changes</a>
|
||||
</span>
|
||||
<span id="hide_changes" style="display: none">
|
||||
| <a href="#" onClick="toggleChanges(); return false;">Hide changes</a>
|
||||
</span>
|
||||
<% end %>
|
||||
|
||||
| <a href="../rollback/<%= @page.name %>?rev=<%= @revision.number %>" class="navlink">Rollback</a>
|
||||
|
||||
<% if @page.references.length > 0 %>
|
||||
<small>
|
||||
| Linked from: <%= @page.references.collect { |ref| "<a href='#{ref.name}'>#{ref.name}</a>" }.join(", ") %>
|
||||
</small>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<script language="Javascript">
|
||||
function toggleChanges() {
|
||||
if (document.getElementById("changes").style.display == "none") {
|
||||
document.getElementById("changes").style.display = "block";
|
||||
document.getElementById("revision").style.display = "none";
|
||||
document.getElementById("show_changes").style.display = "none";
|
||||
document.getElementById("hide_changes").style.display = "inline";
|
||||
} else {
|
||||
document.getElementById("changes").style.display = "none";
|
||||
document.getElementById("revision").style.display = "block";
|
||||
document.getElementById("show_changes").style.display = "inline";
|
||||
document.getElementById("hide_changes").style.display = "none";
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<%= render 'bottom' %>
|
31
app/views/wiki/rollback.rhtml
Executable file
31
app/views/wiki/rollback.rhtml
Executable file
|
@ -0,0 +1,31 @@
|
|||
<%
|
||||
@title = "Rollback to #{@page.plain_name} Rev ##{@revision.number}"
|
||||
@content_width = 720
|
||||
@hide_navigation = true
|
||||
%><%= render 'top' %>
|
||||
|
||||
<%= "<p style='color:red'>Please correct the error that caused this error in rendering:<br/><small>#{@params["msg"]}</small></p>" if @params["msg"] %>
|
||||
|
||||
<form id="editForm" action="../save/<%= @page.name %>" method="post" onSubmit="cleanAuthorName();">
|
||||
<p>
|
||||
<textarea name="content" style="font-size: 12px; width: 450px; height: 500px"><%= @revision.content %></textarea>
|
||||
</p>
|
||||
<p>
|
||||
<input type="submit" value="Update"> as
|
||||
<input type="text" name="author" id="authorName" value="<%= @author %>"
|
||||
onClick="this.value == 'AnonymousCoward' ? this.value = '' : true">
|
||||
| <a href="../cancel_edit/<%= @page.name %>">Cancel</a> <small>(unlocks page)</small>
|
||||
</p>
|
||||
</form>
|
||||
|
||||
<%= render("#{@web.markup}_help") if @web %>
|
||||
|
||||
<script language="JavaScript1.2">
|
||||
function cleanAuthorName() {
|
||||
if (document.getElementById('authorName').value == "") {
|
||||
document.getElementById('authorName').value = 'AnonymousCoward';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<%= render 'bottom' %>
|
22
app/views/wiki/rss_feed.rhtml
Executable file
22
app/views/wiki/rss_feed.rhtml
Executable file
|
@ -0,0 +1,22 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<channel>
|
||||
<title><%= @web.name %></title>
|
||||
<link><%= url_for :only_path => false, :web => @web_name, :action => 'show', :id => 'HomePage' %></link>
|
||||
<description>An Instiki wiki</description>
|
||||
<language>en-us</language>
|
||||
<ttl>40</ttl>
|
||||
<% for page in @pages_by_revision %>
|
||||
<item>
|
||||
<title><%= page.plain_name %></title>
|
||||
<% unless @hide_description %>
|
||||
<description><%= CGI.escapeHTML(page.display_content) %></description>
|
||||
<% end %>
|
||||
<pubDate><%= page.created_at.strftime "%a, %e %b %Y %H:%M:%S %Z" %></pubDate>
|
||||
<guid><%= url_for :only_path => false, :web => @web_name, :action => 'show', :id => page.name %></guid>
|
||||
<link><%= url_for :only_path => false, :web => @web_name, :action => 'show', :id => page.name %></link>
|
||||
<dc:creator><%= WikiWords.separate(page.author) %></dc:creator>
|
||||
</item>
|
||||
<% end %>
|
||||
</channel>
|
||||
</rss>
|
15
app/views/wiki/search.rhtml
Executable file
15
app/views/wiki/search.rhtml
Executable file
|
@ -0,0 +1,15 @@
|
|||
<% @title = @results.length > 0 ? "#{@results.length} pages contains \"#{@params["query"]}\"" : "No pages contains \"#{@query}\"" %><%= render 'top' %>
|
||||
|
||||
<% if @results.length > 0 %>
|
||||
<ul>
|
||||
<% for page in @results %>
|
||||
<li><a href="../show/<%= page.name %>"><%= page.plain_name %></a></li>
|
||||
<% end %>
|
||||
</ul>
|
||||
<% else %>
|
||||
<p>Perhaps you should try expanding your query. Remember that Instiki searches for entire phrases, so if you search for "all that jazz" it will not match pages that contain these words in separation—only as a sentence fragment.</p>
|
||||
|
||||
<p>If you're a high-tech computer wizard, you might even want try constructing a regular expression. That's actually what Instiki uses, so go right ahead and flex your "[a-z]*Leet?RegExpSkill(s|z)"</p>
|
||||
<% end %>
|
||||
|
||||
<%= render 'bottom' %>
|
23
app/views/wiki/tex.rhtml
Executable file
23
app/views/wiki/tex.rhtml
Executable file
|
@ -0,0 +1,23 @@
|
|||
\documentclass[12pt,titlepage]{article}
|
||||
|
||||
\usepackage[danish]{babel} %danske tekster
|
||||
\usepackage[OT1]{fontenc} %rigtige danske bogstaver...
|
||||
\usepackage{a4}
|
||||
\usepackage{graphicx}
|
||||
\usepackage{ucs}
|
||||
\usepackage[utf8]{inputenc}
|
||||
\input epsf
|
||||
|
||||
%-------------------------------------------------------------------
|
||||
|
||||
\begin{document}
|
||||
|
||||
\sloppy
|
||||
|
||||
%-------------------------------------------------------------------
|
||||
|
||||
\section*{<%= @page.name %>}
|
||||
|
||||
<%= @tex_content %>
|
||||
|
||||
\end{document}
|
35
app/views/wiki/tex_web.rhtml
Executable file
35
app/views/wiki/tex_web.rhtml
Executable file
|
@ -0,0 +1,35 @@
|
|||
\documentclass[12pt,titlepage]{article}
|
||||
|
||||
\usepackage{fancyhdr}
|
||||
\pagestyle{fancy}
|
||||
|
||||
\fancyhead[LE,RO]{}
|
||||
\fancyhead[LO,RE]{\nouppercase{\bfseries \leftmark}}
|
||||
\fancyfoot[C]{\thepage}
|
||||
|
||||
\usepackage[danish]{babel} %danske tekster
|
||||
\usepackage{a4}
|
||||
\usepackage{graphicx}
|
||||
\usepackage{ucs}
|
||||
\usepackage[utf8]{inputenc}
|
||||
\input epsf
|
||||
|
||||
|
||||
%-------------------------------------------------------------------
|
||||
|
||||
\title{<%= @web_name %>}
|
||||
|
||||
\begin{document}
|
||||
|
||||
\maketitle
|
||||
|
||||
\tableofcontents
|
||||
\pagebreak
|
||||
|
||||
\sloppy
|
||||
|
||||
%-------------------------------------------------------------------
|
||||
|
||||
<%= @tex_content %>
|
||||
|
||||
\end{document}
|
20
app/views/wiki/web_list.rhtml
Executable file
20
app/views/wiki/web_list.rhtml
Executable file
|
@ -0,0 +1,20 @@
|
|||
<% @title = "Wiki webs" %><%= render 'top' %>
|
||||
|
||||
<ul>
|
||||
<% for web in @webs %>
|
||||
<li>
|
||||
<% if web.published %>
|
||||
<%= web.make_link 'HomePage', web.name, :mode => :publish %> (read-only) /
|
||||
<%= web.make_link 'HomePage', 'editable version', :mode => :edit %> (requires login)
|
||||
<% else %>
|
||||
<%= web.make_link 'HomePage', nil, :mode => :edit %>
|
||||
<% end %>
|
||||
<div class="byline" style="margin-bottom: 0px">
|
||||
<%= web.pages.length %> pages by <%= web.authors.length %> authors
|
||||
</div>
|
||||
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
|
||||
<%= render 'bottom' %>
|
9
app/views/wiki_words_help.rhtml
Executable file
9
app/views/wiki_words_help.rhtml
Executable file
|
@ -0,0 +1,9 @@
|
|||
<h3>Wiki words</h3>
|
||||
<p style="border-top: 1px dotted #ccc; margin-top: 0px">
|
||||
Two or more uppercase words stuck together (camel case) or any phrase surrounded by double
|
||||
brackets is a wiki word. A camel-case wiki word can be escaped by putting \ in front of it.
|
||||
</p>
|
||||
<p>
|
||||
Wiki words: <i>HomePage, ThreeWordsTogether, [[C++]], [[Let's play again!]]</i><br/>
|
||||
Not wiki words: <i>IBM, School</i>
|
||||
</p>
|
59
config/environment.rb
Normal file
59
config/environment.rb
Normal file
|
@ -0,0 +1,59 @@
|
|||
if RUBY_VERSION < "1.8.1"
|
||||
puts "Instiki requires Ruby 1.8.1+"
|
||||
exit
|
||||
end
|
||||
|
||||
RAILS_ROOT = File.dirname(__FILE__) + "/../" unless defined? RAILS_ROOT
|
||||
RAILS_ENV = ENV['RAILS_ENV'] || 'production' unless defined? RAILS_ENV
|
||||
|
||||
unless defined? ADDITIONAL_LOAD_PATHS
|
||||
# Mocks first.
|
||||
ADDITIONAL_LOAD_PATHS = ["#{RAILS_ROOT}/test/mocks/#{RAILS_ENV}"]
|
||||
|
||||
# Then model subdirectories.
|
||||
ADDITIONAL_LOAD_PATHS.concat(Dir["#{RAILS_ROOT}/app/models/[_a-z]*"])
|
||||
|
||||
# Followed by the standard includes.
|
||||
ADDITIONAL_LOAD_PATHS.concat %w(
|
||||
app
|
||||
app/models
|
||||
app/controllers
|
||||
app/helpers
|
||||
config
|
||||
libraries
|
||||
).map { |dir| "#{File.expand_path(File.join(RAILS_ROOT, dir))}" }
|
||||
|
||||
# Third party vendors
|
||||
ADDITIONAL_LOAD_PATHS.concat %w(
|
||||
vendor/bluecloth-1.0.0/lib
|
||||
vendor/madeleine-0.7.1/lib
|
||||
vendor/redcloth-2.0.11/lib
|
||||
vendor/rubyzip-0.5.6
|
||||
vendor/actionpack/lib
|
||||
vendor/activesupport/lib
|
||||
vendor/railties/lib
|
||||
).map { |dir|
|
||||
"#{File.expand_path(File.join(RAILS_ROOT, dir))}"
|
||||
}.delete_if { |dir| not File.exist?(dir) }
|
||||
|
||||
# Prepend to $LOAD_PATH
|
||||
ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) }
|
||||
end
|
||||
|
||||
require 'action_controller'
|
||||
require 'active_record_stub'
|
||||
require 'dependencies'
|
||||
|
||||
unless defined? RAILS_DEFAULT_LOGGER
|
||||
RAILS_DEFAULT_LOGGER = Logger.new(STDERR)
|
||||
RAILS_DEFAULT_LOGGER.level = Logger::INFO
|
||||
ActionController::Base.logger ||= RAILS_DEFAULT_LOGGER
|
||||
end
|
||||
|
||||
# Environment-specific configuration.
|
||||
require "environments/#{RAILS_ENV}"
|
||||
require 'wiki_service'
|
||||
|
||||
Socket.do_not_reverse_lookup = true
|
||||
|
||||
ActionController::Base.template_root ||= "#{RAILS_ROOT}/app/views/"
|
4
config/environments/development.rb
Executable file
4
config/environments/development.rb
Executable file
|
@ -0,0 +1,4 @@
|
|||
Dependencies.mechanism = :require
|
||||
ActionController::Base.consider_all_requests_local = true
|
||||
BREAKPOINT_SERVER_PORT = 42531
|
||||
ActionController::Base.logger.level = Logger::DEBUG
|
2
config/environments/production.rb
Normal file
2
config/environments/production.rb
Normal file
|
@ -0,0 +1,2 @@
|
|||
Dependencies.mechanism = :require
|
||||
ActionController::Base.consider_all_requests_local = false
|
14
config/environments/test.rb
Normal file
14
config/environments/test.rb
Normal file
|
@ -0,0 +1,14 @@
|
|||
Dependencies.mechanism = :require
|
||||
ActionController::Base.consider_all_requests_local = true
|
||||
|
||||
require 'fileutils'
|
||||
FileUtils.mkdir_p(RAILS_ROOT + "/log")
|
||||
|
||||
unless defined? TEST_LOGGER
|
||||
timestamp = Time.now.strftime('%Y%m%d%H%M%S')
|
||||
log_name = RAILS_ROOT + "/log/instiki_test.#{timestamp}.log"
|
||||
$stderr.puts "To see the Rails log:\n less #{log_name}"
|
||||
|
||||
TEST_LOGGER = ActionController::Base.logger = Logger.new(log_name)
|
||||
ActionController::Base.logger.level = Logger::DEBUG
|
||||
end
|
2
instiki
Executable file
2
instiki
Executable file
|
@ -0,0 +1,2 @@
|
|||
#!/usr/bin/ruby
|
||||
load File.dirname(__FILE__) + "/script/server"
|
44
instiki.gemspec
Executable file
44
instiki.gemspec
Executable file
|
@ -0,0 +1,44 @@
|
|||
$__instiki_source_patterns = ['[A-Z]*', 'instiki', 'app/**/*', 'libraries/**/*', 'vendor/**/*']
|
||||
|
||||
spec = Gem::Specification.new do |s|
|
||||
s.platform = Gem::Platform::RUBY
|
||||
s.name = 'instiki'
|
||||
s.version = "0.9.2"
|
||||
s.summary = 'Easy to install WikiClone running on WEBrick and Madeleine'
|
||||
s.description = <<-EOF
|
||||
Instiki is a Wiki Clone written in Ruby that ships with an embedded
|
||||
webserver. You can setup up an Instiki in just a few steps.
|
||||
Possibly the simplest wiki setup ever.
|
||||
EOF
|
||||
s.author = 'David Heinemeier Hansson'
|
||||
s.email = 'david@loudthinking.com'
|
||||
s.rubyforge_project = 'instiki'
|
||||
s.homepage = 'http://www.instiki.org'
|
||||
|
||||
s.bindir = '.'
|
||||
s.executables = ['instiki']
|
||||
s.default_executable = 'instiki'
|
||||
|
||||
s.has_rdoc = true
|
||||
s.rdoc_options << '--title' << 'Instiki -- The Wiki' <<
|
||||
'--line-numbers' << '--inline-source'
|
||||
# TODO: specify README as main RDoc file
|
||||
|
||||
s.add_dependency('madeleine', '= 0.7.1')
|
||||
s.add_dependency('BlueCloth', '= 1.0.0')
|
||||
s.add_dependency('RedCloth', '= 2.0.11')
|
||||
s.add_dependency('rubyzip', '= 0.5.5')
|
||||
s.requirements << 'none'
|
||||
s.require_path = 'libraries'
|
||||
|
||||
s.files = $__instiki_source_patterns.inject([]) { |list, glob|
|
||||
list << Dir[glob].delete_if { |path|
|
||||
File.directory?(path) or
|
||||
path.include?('CVS/') or
|
||||
path.include?('vendor/') or
|
||||
path.include?('test/') or
|
||||
path.include?('_test.rb')
|
||||
}
|
||||
}.flatten
|
||||
|
||||
end
|
23
libraries/active_record_stub.rb
Executable file
23
libraries/active_record_stub.rb
Executable file
|
@ -0,0 +1,23 @@
|
|||
# This project uses Railties, which has an external dependency on ActiveRecord
|
||||
# Since ActiveRecord may not be present in Instiki runtime environment, this
|
||||
# file provides a stub replacement for it
|
||||
|
||||
unless defined? ActiveRecord::Base
|
||||
|
||||
module ActiveRecord
|
||||
class Base
|
||||
|
||||
# dependency in railties/lib/dispatcher.rb
|
||||
def self.reset_column_information_and_inheritable_attributes_for_all_subclasses
|
||||
# noop
|
||||
end
|
||||
|
||||
# dependency in actionpack/lib/action_controller/benchmarking.rb
|
||||
def self.connected?
|
||||
false
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
end
|
475
libraries/diff.rb
Executable file
475
libraries/diff.rb
Executable file
|
@ -0,0 +1,475 @@
|
|||
# heavily based off difflib.py - see that file for documentation
|
||||
# ported from Python by Bill Atkins
|
||||
|
||||
# This does not support all features offered by difflib; it
|
||||
# implements only the subset of features necessary
|
||||
# to support a Ruby version of HTML Differ. You're welcome to finish this off.
|
||||
|
||||
# By default, String#each iterates by line. This isn't really appropriate
|
||||
# for diff, so often a string will be split by // to get an array of one-
|
||||
# character strings.
|
||||
|
||||
# Some methods in Diff are untested and are not guaranteed to work. The
|
||||
# methods in HTMLDiff and any methods it calls should work quite well.
|
||||
|
||||
# changes by DenisMertz
|
||||
# * main change:
|
||||
# ** get the tag soup away
|
||||
# the tag soup problem was first reported with <p> tags, but it appeared also with
|
||||
# <li>, <ul> etc... tags
|
||||
# this version should mostly fix these problems
|
||||
# ** added a Builder class to manage the creation of the final htmldiff
|
||||
# * minor changes:
|
||||
# ** use symbols instead of string to represent opcodes
|
||||
# ** small fix to html2list
|
||||
#
|
||||
|
||||
module Enumerable
|
||||
def reduce(init)
|
||||
result = init
|
||||
each { |item| result = yield(result, item) }
|
||||
result
|
||||
end
|
||||
end
|
||||
|
||||
module Diff
|
||||
|
||||
class SequenceMatcher
|
||||
def initialize(a=[''], b=[''], isjunk=nil, byline=false)
|
||||
a = (!byline and a.kind_of? String) ? a.split(//) : a
|
||||
b = (!byline and b.kind_of? String) ? b.split(//) : b
|
||||
@isjunk = isjunk || proc {}
|
||||
set_seqs a, b
|
||||
end
|
||||
|
||||
def set_seqs(a, b)
|
||||
set_seq_a a
|
||||
set_seq_b b
|
||||
end
|
||||
|
||||
def set_seq_a(a)
|
||||
@a = a
|
||||
@matching_blocks = @opcodes = nil
|
||||
end
|
||||
|
||||
def set_seq_b(b)
|
||||
@b = b
|
||||
@matching_blocks = @opcodes = nil
|
||||
chain_b
|
||||
end
|
||||
|
||||
def chain_b
|
||||
@fullbcount = nil
|
||||
@b2j = {}
|
||||
pophash = {}
|
||||
junkdict = {}
|
||||
|
||||
@b.each_with_index do |elt, i|
|
||||
if @b2j.has_key? elt
|
||||
indices = @b2j[elt]
|
||||
if @b.length >= 200 and indices.length * 100 > @b.length
|
||||
pophash[elt] = 1
|
||||
indices.clear
|
||||
else
|
||||
indices.push i
|
||||
end
|
||||
else
|
||||
@b2j[elt] = [i]
|
||||
end
|
||||
end
|
||||
|
||||
pophash.each_key { |elt| @b2j.delete elt }
|
||||
|
||||
junkdict = {}
|
||||
|
||||
unless @isjunk.nil?
|
||||
[pophash, @b2j].each do |d|
|
||||
d.each_key do |elt|
|
||||
if @isjunk.call(elt)
|
||||
junkdict[elt] = 1
|
||||
d.delete elt
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@isbjunk = junkdict.method(:has_key?)
|
||||
@isbpopular = junkdict.method(:has_key?)
|
||||
end
|
||||
|
||||
def find_longest_match(alo, ahi, blo, bhi)
|
||||
besti, bestj, bestsize = alo, blo, 0
|
||||
|
||||
j2len = {}
|
||||
|
||||
(alo..ahi).step do |i|
|
||||
newj2len = {}
|
||||
(@b2j[@a[i]] || []).each do |j|
|
||||
if j < blo
|
||||
next
|
||||
end
|
||||
if j >= bhi
|
||||
break
|
||||
end
|
||||
|
||||
k = newj2len[j] = (j2len[j - 1] || 0) + 1
|
||||
if k > bestsize
|
||||
besti, bestj, bestsize = i - k + 1, j - k + 1, k
|
||||
end
|
||||
end
|
||||
j2len = newj2len
|
||||
end
|
||||
|
||||
while besti > alo and bestj > blo and
|
||||
not @isbjunk.call(@b[bestj-1]) and
|
||||
@a[besti-1] == @b[bestj-1]
|
||||
besti, bestj, bestsize = besti-1, bestj-1, bestsize+1
|
||||
end
|
||||
|
||||
while besti+bestsize < ahi and bestj+bestsize < bhi and
|
||||
not @isbjunk.call(@b[bestj+bestsize]) and
|
||||
@a[besti+bestsize] == @b[bestj+bestsize]
|
||||
bestsize += 1
|
||||
end
|
||||
|
||||
while besti > alo and bestj > blo and
|
||||
@isbjunk.call(@b[bestj-1]) and
|
||||
@a[besti-1] == @b[bestj-1]
|
||||
besti, bestj, bestsize = besti-1, bestj-1, bestsize+1
|
||||
end
|
||||
|
||||
while besti+bestsize < ahi and bestj+bestsize < bhi and
|
||||
@isbjunk.call(@b[bestj+bestsize]) and
|
||||
@a[besti+bestsize] == @b[bestj+bestsize]
|
||||
bestsize += 1
|
||||
end
|
||||
|
||||
[besti, bestj, bestsize]
|
||||
end
|
||||
|
||||
def get_matching_blocks
|
||||
return @matching_blocks unless @matching_blocks.nil? or
|
||||
@matching_blocks.empty?
|
||||
|
||||
@matching_blocks = []
|
||||
la, lb = @a.length, @b.length
|
||||
match_block_helper(0, la, 0, lb, @matching_blocks)
|
||||
@matching_blocks.push [la, lb, 0]
|
||||
end
|
||||
|
||||
def match_block_helper(alo, ahi, blo, bhi, answer)
|
||||
i, j, k = x = find_longest_match(alo, ahi, blo, bhi)
|
||||
if not k.zero?
|
||||
if alo < i and blo < j
|
||||
match_block_helper(alo, i, blo, j, answer)
|
||||
end
|
||||
answer.push x
|
||||
if i + k < ahi and j + k < bhi
|
||||
match_block_helper(i + k, ahi, j + k, bhi, answer)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def get_opcodes
|
||||
unless @opcodes.nil? or @opcodes.empty?
|
||||
return @opcodes
|
||||
end
|
||||
|
||||
i = j = 0
|
||||
@opcodes = answer = []
|
||||
get_matching_blocks.each do |ai, bj, size|
|
||||
tag = if i < ai and j < bj
|
||||
:replace
|
||||
elsif i < ai
|
||||
:delete
|
||||
elsif j < bj
|
||||
:insert
|
||||
end
|
||||
|
||||
answer.push [tag, i, ai, j, bj] if tag
|
||||
|
||||
i, j = ai + size, bj + size
|
||||
|
||||
answer.push [:equal, ai, i, bj, j] unless size.zero?
|
||||
|
||||
end
|
||||
return answer
|
||||
end
|
||||
|
||||
# XXX: untested
|
||||
def get_grouped_opcodes(n=3)
|
||||
codes = get_opcodes
|
||||
if codes[0][0] == :equal
|
||||
tag, i1, i2, j1, j2 = codes[0]
|
||||
codes[0] = tag, [i1, i2 - n].max, i2, [j1, j2-n].max, j2
|
||||
end
|
||||
|
||||
if codes[-1][0] == :equal
|
||||
tag, i1, i2, j1, j2 = codes[-1]
|
||||
codes[-1] = tag, i1, min(i2, i1+n), j1, min(j2, j1+n)
|
||||
end
|
||||
nn = n + n
|
||||
group = []
|
||||
codes.each do |tag, i1, i2, j1, j2|
|
||||
if tag == :equal and i2-i1 > nn
|
||||
group.push [tag, i1, [i2, i1 + n].min, j1, [j2, j1 + n].min]
|
||||
yield group
|
||||
group = []
|
||||
i1, j1 = [i1, i2-n].max, [j1, j2-n].max
|
||||
group.push [tag, i1, i2, j1 ,j2]
|
||||
end
|
||||
end
|
||||
if group and group.length != 1 and group[0][0] == :equal
|
||||
yield group
|
||||
end
|
||||
end
|
||||
|
||||
def ratio
|
||||
matches = get_matching_blocks.reduce(0) do |sum, triple|
|
||||
sum + triple[-1]
|
||||
end
|
||||
Diff.calculate_ratio(matches, @a.length + @b.length)
|
||||
end
|
||||
|
||||
def quick_ratio
|
||||
if @fullbcount.nil? or @fullbcount.empty?
|
||||
@fullbcount = {}
|
||||
@b.each do |elt|
|
||||
@fullbcount[elt] = (@fullbcount[elt] || 0) + 1
|
||||
end
|
||||
end
|
||||
|
||||
avail = {}
|
||||
matches = 0
|
||||
@a.each do |elt|
|
||||
if avail.has_key? elt
|
||||
numb = avail[elt]
|
||||
else
|
||||
numb = @fullbcount[elt] || 0
|
||||
end
|
||||
avail[elt] = numb - 1
|
||||
if numb > 0
|
||||
matches += 1
|
||||
end
|
||||
end
|
||||
Diff.calculate_ratio matches, @a.length + @b.length
|
||||
end
|
||||
|
||||
def real_quick_ratio
|
||||
la, lb = @a.length, @b.length
|
||||
Diff.calculate_ratio([la, lb].min, la + lb)
|
||||
end
|
||||
|
||||
protected :chain_b, :match_block_helper
|
||||
end # end class SequenceMatcher
|
||||
|
||||
def self.calculate_ratio(matches, length)
|
||||
return 1.0 if length.zero?
|
||||
2.0 * matches / length
|
||||
end
|
||||
|
||||
# XXX: untested
|
||||
def self.get_close_matches(word, possibilities, n=3, cutoff=0.6)
|
||||
unless n > 0
|
||||
raise "n must be > 0: #{n}"
|
||||
end
|
||||
unless 0.0 <= cutoff and cutoff <= 1.0
|
||||
raise "cutoff must be in (0.0..1.0): #{cutoff}"
|
||||
end
|
||||
|
||||
result = []
|
||||
s = SequenceMatcher.new
|
||||
s.set_seq_b word
|
||||
possibilities.each do |x|
|
||||
s.set_seq_a x
|
||||
if s.real_quick_ratio >= cutoff and
|
||||
s.quick_ratio >= cutoff and
|
||||
s.ratio >= cutoff
|
||||
result.push [s.ratio, x]
|
||||
end
|
||||
end
|
||||
|
||||
unless result.nil? or result.empty?
|
||||
result.sort
|
||||
result.reverse!
|
||||
result = result[-n..-1]
|
||||
end
|
||||
result.collect { |score, x| x }
|
||||
end
|
||||
|
||||
def self.count_leading(line, ch)
|
||||
i, n = 0, line.length
|
||||
while i < n and line[i].chr == ch
|
||||
i += 1
|
||||
end
|
||||
i
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
module HTMLDiff
|
||||
include Diff
|
||||
class Builder
|
||||
VALID_METHODS = [:replace, :insert, :delete, :equal]
|
||||
def initialize(a, b)
|
||||
@a = a
|
||||
@b = b
|
||||
@content = []
|
||||
end
|
||||
|
||||
def do_op(opcode)
|
||||
@opcode = opcode
|
||||
op = @opcode[0]
|
||||
VALID_METHODS.include?(op) or raise(NameError, "Invalid opcode #{op}")
|
||||
self.method(op).call
|
||||
end
|
||||
|
||||
def result
|
||||
@content.join('')
|
||||
end
|
||||
|
||||
#this methods have to be called via do_op(opcode) so that @opcode is set properly
|
||||
private
|
||||
|
||||
def replace
|
||||
delete("diffmod")
|
||||
insert("diffmod")
|
||||
end
|
||||
|
||||
def insert(tagclass="diffins")
|
||||
op_helper("ins", tagclass, @b[@opcode[3]...@opcode[4]])
|
||||
end
|
||||
|
||||
def delete(tagclass="diffdel")
|
||||
op_helper("del", tagclass, @a[@opcode[1]...@opcode[2]])
|
||||
end
|
||||
|
||||
def equal
|
||||
@content += @b[@opcode[3]...@opcode[4]]
|
||||
end
|
||||
|
||||
# using this as op_helper would be equivalent to the first version of diff.rb by Bill Atkins
|
||||
def op_helper_simple(tagname, tagclass, to_add)
|
||||
@content << "<#{tagname} class=\"#{tagclass}\">"
|
||||
@content += to_add
|
||||
@content << "</#{tagname}>"
|
||||
end
|
||||
|
||||
# this tries to put <p> tags or newline chars before the opening diff tags (<ins> or <del>)
|
||||
# or after the ending diff tags
|
||||
# as a result the diff tags should be the "more inside" possible.
|
||||
# this seems to work nice with html containing only paragraphs
|
||||
# but not sure it works if there are other tags (div, span ... ? ) around
|
||||
def op_helper(tagname, tagclass, to_add)
|
||||
@content << to_add.shift while ( HTMLDiff.is_newline(to_add.first) or
|
||||
HTMLDiff.is_p_close_tag(to_add.first) or
|
||||
HTMLDiff.is_p_open_tag(to_add.first) )
|
||||
@content << "<#{tagname} class=\"#{tagclass}\">"
|
||||
@content += to_add
|
||||
last_tags = []
|
||||
last_tags.unshift(@content.pop) while ( HTMLDiff.is_newline(@content.last) or
|
||||
HTMLDiff.is_p_close_tag(@content.last) or
|
||||
HTMLDiff.is_p_open_tag(@content.last) )
|
||||
last_tags.unshift "</#{tagname}>"
|
||||
@content += last_tags
|
||||
remove_empty_diff(tagname, tagclass)
|
||||
end
|
||||
|
||||
def remove_empty_diff(tagname, tagclass)
|
||||
if @content[-2] == "<#{tagname} class=\"#{tagclass}\">" and @content[-1] == "</#{tagname}>" then
|
||||
@content.pop
|
||||
@content.pop
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def self.is_newline(x)
|
||||
(x == "\n") or (x == "\r") or (x == "\t")
|
||||
end
|
||||
|
||||
def self.is_p_open_tag(x)
|
||||
x =~ /\A<(p|li|ul|ol|dir|dt|dl)/
|
||||
end
|
||||
|
||||
def self.is_p_close_tag(x)
|
||||
x =~ %r!\A</(p|li|ul|ol|dir|dt|dl)!
|
||||
end
|
||||
|
||||
def self.diff(a, b)
|
||||
a, b = a.split(//), b.split(//) if a.kind_of? String and b.kind_of? String
|
||||
a, b = html2list(a), html2list(b)
|
||||
|
||||
out = Builder.new(a, b)
|
||||
s = SequenceMatcher.new(a, b)
|
||||
|
||||
s.get_opcodes.each do |opcode|
|
||||
out.do_op(opcode)
|
||||
end
|
||||
|
||||
out.result
|
||||
end
|
||||
|
||||
def self.html2list(x, b=false)
|
||||
mode = 'char'
|
||||
cur = ''
|
||||
out = []
|
||||
|
||||
x = x.split(//) if x.kind_of? String
|
||||
|
||||
x.each do |c|
|
||||
if mode == 'tag'
|
||||
if c == '>'
|
||||
if b
|
||||
cur += ']'
|
||||
else
|
||||
cur += c
|
||||
end
|
||||
out.push(cur)
|
||||
cur = ''
|
||||
mode = 'char'
|
||||
else
|
||||
cur += c
|
||||
end
|
||||
elsif mode == 'char'
|
||||
if c == '<'
|
||||
out.push cur
|
||||
if b
|
||||
cur = '['
|
||||
else
|
||||
cur = c
|
||||
end
|
||||
mode = 'tag'
|
||||
elsif /\s/.match c
|
||||
out.push cur + c
|
||||
cur = ''
|
||||
else
|
||||
cur += c
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
out.push cur
|
||||
# TODO: make something better here
|
||||
out.each{|x| x.chomp! unless is_newline(x)}
|
||||
out.find_all { |x| x != '' }
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
if __FILE__ == $0
|
||||
|
||||
require 'pp'
|
||||
# a = "<p>this is the original string</p>" # \n<p>but around the world</p>"
|
||||
# b = "<p>this is the original </p><p>other parag</p><p>string</p>"
|
||||
a = "<ul>\n\t<li>one</li>\n\t<li>two</li>\n</ul>"
|
||||
b = "<ul>\n\t<li>one</li>\n\t<li>two\n\t<ul><li>abc</li></ul></li>\n</ul>"
|
||||
puts a
|
||||
pp HTMLDiff.html2list(a)
|
||||
puts
|
||||
puts b
|
||||
pp HTMLDiff.html2list(b)
|
||||
puts
|
||||
puts HTMLDiff.diff(a, b)
|
||||
end
|
152
libraries/rdocsupport.rb
Executable file
152
libraries/rdocsupport.rb
Executable file
|
@ -0,0 +1,152 @@
|
|||
begin
|
||||
require "rdoc/markup/simple_markup"
|
||||
require 'rdoc/markup/simple_markup/to_html'
|
||||
rescue LoadError
|
||||
# use old version if available
|
||||
require 'markup/simple_markup'
|
||||
require 'markup/simple_markup/to_html'
|
||||
end
|
||||
|
||||
module RDocSupport
|
||||
|
||||
# A simple +rdoc+ markup class which recognizes some additional
|
||||
# formatting commands suitable for Wiki use.
|
||||
class RDocMarkup < SM::SimpleMarkup
|
||||
def initialize
|
||||
super()
|
||||
|
||||
pre = '(?:\\s|^|\\\\)'
|
||||
|
||||
# links of the form
|
||||
# [[<url> description with spaces]]
|
||||
add_special(/((\\)?\[\[\S+?\s+.+?\]\])/,:TIDYLINK)
|
||||
|
||||
# and external references
|
||||
add_special(/((\\)?(link:|anchor:|http:|mailto:|ftp:|img:|www\.)\S+\w\/?)/,
|
||||
:HYPERLINK)
|
||||
|
||||
# <br/>
|
||||
add_special(%r{(#{pre}<br/>)}, :BR)
|
||||
|
||||
# and <center> ... </center>
|
||||
add_html("center", :CENTER)
|
||||
end
|
||||
|
||||
def convert(text, handler)
|
||||
super.sub(/^<p>\n/, '').sub(/<\/p>$/, '')
|
||||
end
|
||||
end
|
||||
|
||||
# Handle special hyperlinking requirments for RDoc formatted
|
||||
# entries. Requires RDoc
|
||||
|
||||
class HyperLinkHtml < SM::ToHtml
|
||||
|
||||
# Initialize the HyperLinkHtml object.
|
||||
# [path] location of the node
|
||||
# [site] object representing the whole site (typically of class
|
||||
# +Site+)
|
||||
def initialize
|
||||
super()
|
||||
add_tag(:CENTER, "<center>", "</center>")
|
||||
end
|
||||
|
||||
# handle <br/>
|
||||
def handle_special_BR(special)
|
||||
return "<br/>" if special.text[0,1] == '\\'
|
||||
special.text
|
||||
end
|
||||
|
||||
# We're invoked with a potential external hyperlink.
|
||||
# [mailto:] just gets inserted.
|
||||
# [http:] links are checked to see if they
|
||||
# reference an image. If so, that image gets inserted
|
||||
# using an <img> tag. Otherwise a conventional <a href>
|
||||
# is used.
|
||||
# [img:] insert a <tt><img></tt> tag
|
||||
# [link:] used to insert arbitrary <tt><a></tt> references
|
||||
# [anchor:] used to create an anchor
|
||||
def handle_special_HYPERLINK(special)
|
||||
text = special.text.strip
|
||||
return text[1..-1] if text[0,1] == '\\'
|
||||
url = special.text.strip
|
||||
if url =~ /([A-Za-z]+):(.*)/
|
||||
type = $1
|
||||
path = $2
|
||||
else
|
||||
type = "http"
|
||||
path = url
|
||||
url = "http://#{url}"
|
||||
end
|
||||
|
||||
case type
|
||||
when "http"
|
||||
if url =~ /\.(gif|png|jpg|jpeg|bmp)$/
|
||||
"<img src=\"#{url}\"/>"
|
||||
else
|
||||
"<a href=\"#{url}\">#{url.sub(%r{^\w+:/*}, '')}</a>"
|
||||
end
|
||||
when "img"
|
||||
"<img src=\"#{path}\"/>"
|
||||
when "link"
|
||||
"<a href=\"#{path}\">#{path}</a>"
|
||||
when "anchor"
|
||||
"<a name=\"#{path}\"></a>"
|
||||
else
|
||||
"<a href=\"#{url}\">#{url.sub(%r{^\w+:/*}, '')}</a>"
|
||||
end
|
||||
end
|
||||
|
||||
# Here's a hyperlink where the label is different to the URL
|
||||
# [[url label that may contain spaces]]
|
||||
#
|
||||
|
||||
def handle_special_TIDYLINK(special)
|
||||
text = special.text.strip
|
||||
return text[1..-1] if text[0,1] == '\\'
|
||||
unless text =~ /\[\[(\S+?)\s+(.+?)\]\]/
|
||||
return text
|
||||
end
|
||||
url = $1
|
||||
label = $2
|
||||
label = RDocFormatter.new(label).to_html
|
||||
label = label.split.select{|x| x =~ /\S/}.
|
||||
map{|x| x.chomp}.join(' ')
|
||||
|
||||
case url
|
||||
when /link:(\S+)/
|
||||
return %{<a href="#{$1}">#{label}</a>}
|
||||
when /img:(\S+)/
|
||||
return %{<img src="http://#{$1}" alt="#{label}" />}
|
||||
when /rubytalk:(\S+)/
|
||||
return %{<a href="http://ruby-talk.org/blade/#{$1}">#{label}</a>}
|
||||
when /rubygarden:(\S+)/
|
||||
return %{<a href="http://www.rubygarden.org/ruby?#{$1}">#{label}</a>}
|
||||
when /c2:(\S+)/
|
||||
return %{<a href="http://c2.com/cgi/wiki?#{$1}">#{label}</a>}
|
||||
when /isbn:(\S+)/
|
||||
return %{<a href="http://search.barnesandnoble.com/bookSearch/} +
|
||||
%{isbnInquiry.asp?isbn=#{$1}">#{label}</a>}
|
||||
end
|
||||
|
||||
unless url =~ /\w+?:/
|
||||
url = "http://#{url}"
|
||||
end
|
||||
|
||||
"<a href=\"#{url}\">#{label}</a>"
|
||||
end
|
||||
end
|
||||
|
||||
class RDocFormatter
|
||||
def initialize(text)
|
||||
@text = text
|
||||
end
|
||||
|
||||
def to_html
|
||||
markup = RDocMarkup.new
|
||||
h = HyperLinkHtml.new
|
||||
markup.convert(@text, h)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
880
libraries/redcloth_for_tex.rb
Executable file
880
libraries/redcloth_for_tex.rb
Executable file
|
@ -0,0 +1,880 @@
|
|||
# vim:ts=4:sw=4:
|
||||
# = RedCloth - Textile for Ruby
|
||||
#
|
||||
# (c) 2003 why the lucky stiff (and his puppet organizations.)
|
||||
#
|
||||
# (see http://www.textism.com/tools/textile/ for Textile)
|
||||
#
|
||||
# Based on (and also inspired by) both:
|
||||
#
|
||||
# PyTextile: http://diveintomark.org/projects/textile/textile.py.txt
|
||||
# Textism for PHP: http://www.textism.com/tools/textile/
|
||||
#
|
||||
#
|
||||
# == What is Textile?
|
||||
#
|
||||
# Textile is a simple formatting style for text
|
||||
# documents, loosely based on some HTML conventions.
|
||||
#
|
||||
# === Sample Textile Text
|
||||
#
|
||||
# h2. This is a title
|
||||
#
|
||||
# h3. This is a subhead
|
||||
#
|
||||
# This is a bit of paragraph.
|
||||
#
|
||||
# bq. This is a blockquote.
|
||||
#
|
||||
# === Writing Textile
|
||||
#
|
||||
# A Textile document consists of paragraphs. Paragraphs
|
||||
# can be specially formatted by adding a small instruction
|
||||
# to the beginning of the paragraph.
|
||||
#
|
||||
# h[n]. Header of size [n].
|
||||
# bq. Blockquote.
|
||||
# # Numeric list.
|
||||
# * Bulleted list.
|
||||
#
|
||||
# === Quick Phrase Modifiers
|
||||
#
|
||||
# Quick phrase modifiers are also included, to allow formatting
|
||||
# of small portions of text within a paragraph.
|
||||
#
|
||||
# _emphasis_
|
||||
# __italicized__
|
||||
# *strong*
|
||||
# **bold**
|
||||
# ??citation??
|
||||
# -deleted text-
|
||||
# +inserted text+
|
||||
# ^superscript^
|
||||
# ~subscript~
|
||||
# @code@
|
||||
# %(classname)span%
|
||||
#
|
||||
# ==notextile== (leave text alone)
|
||||
#
|
||||
# === Links
|
||||
#
|
||||
# To make a hypertext link, put the link text in "quotation
|
||||
# marks" followed immediately by a colon and the URL of the link.
|
||||
#
|
||||
# Optional: text in (parentheses) following the link text,
|
||||
# but before the closing quotation mark, will become a Title
|
||||
# attribute for the link, visible as a tool tip when a cursor is above it.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# "This is a link (This is a title) ":http://www.textism.com
|
||||
#
|
||||
# Will become:
|
||||
#
|
||||
# <a href="http://www.textism.com" title="This is a title">This is a link</a>
|
||||
#
|
||||
# === Images
|
||||
#
|
||||
# To insert an image, put the URL for the image inside exclamation marks.
|
||||
#
|
||||
# Optional: text that immediately follows the URL in (parentheses) will
|
||||
# be used as the Alt text for the image. Images on the web should always
|
||||
# have descriptive Alt text for the benefit of readers using non-graphical
|
||||
# browsers.
|
||||
#
|
||||
# Optional: place a colon followed by a URL immediately after the
|
||||
# closing ! to make the image into a link.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# !http://www.textism.com/common/textist.gif(Textist)!
|
||||
#
|
||||
# Will become:
|
||||
#
|
||||
# <img src="http://www.textism.com/common/textist.gif" alt="Textist" />
|
||||
#
|
||||
# With a link:
|
||||
#
|
||||
# !/common/textist.gif(Textist)!:http://textism.com
|
||||
#
|
||||
# Will become:
|
||||
#
|
||||
# <a href="http://textism.com"><img src="/common/textist.gif" alt="Textist" /></a>
|
||||
#
|
||||
# === Defining Acronyms
|
||||
#
|
||||
# HTML allows authors to define acronyms via the tag. The definition appears as a
|
||||
# tool tip when a cursor hovers over the acronym. A crucial aid to clear writing,
|
||||
# this should be used at least once for each acronym in documents where they appear.
|
||||
#
|
||||
# To quickly define an acronym in Textile, place the full text in (parentheses)
|
||||
# immediately following the acronym.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# ACLU(American Civil Liberties Union)
|
||||
#
|
||||
# Will become:
|
||||
#
|
||||
# <acronym title="American Civil Liberties Union">ACLU</acronym>
|
||||
#
|
||||
# === Adding Tables
|
||||
#
|
||||
# In Textile, simple tables can be added by seperating each column by
|
||||
# a pipe.
|
||||
#
|
||||
# |a|simple|table|row|
|
||||
# |And|Another|table|row|
|
||||
#
|
||||
# Attributes are defined by style definitions in parentheses.
|
||||
#
|
||||
# table(border:1px solid black).
|
||||
# (background:#ddd;color:red). |{}| | | |
|
||||
#
|
||||
# === Using RedCloth
|
||||
#
|
||||
# RedCloth is simply an extension of the String class, which can handle
|
||||
# Textile formatting. Use it like a String and output HTML with its
|
||||
# RedCloth#to_html method.
|
||||
#
|
||||
# doc = RedCloth.new "
|
||||
#
|
||||
# h2. Test document
|
||||
#
|
||||
# Just a simple test."
|
||||
#
|
||||
# puts doc.to_html
|
||||
|
||||
class String
|
||||
#
|
||||
# Flexible HTML escaping
|
||||
#
|
||||
def texesc!( mode )
|
||||
gsub!( '&', '\\\\&' )
|
||||
gsub!( '%', '\%' )
|
||||
gsub!( '$', '\$' )
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def table_of_contents(text, pages)
|
||||
text.gsub!( /^([#*]+? .*?)$(?![^#*])/m ) do |match|
|
||||
lines = match.split( /\n/ )
|
||||
last_line = -1
|
||||
depth = []
|
||||
lines.each_with_index do |line, line_id|
|
||||
if line =~ /^([#*]+) (.*)$/m
|
||||
tl,content = $~[1..2]
|
||||
content.gsub! /[\[\]]/, ""
|
||||
content.strip!
|
||||
|
||||
if depth.last
|
||||
if depth.last.length > tl.length
|
||||
(depth.length - 1).downto(0) do |i|
|
||||
break if depth[i].length == tl.length
|
||||
lines[line_id - 1] << "" # "\n\t\\end{#{ lT( depth[i] ) }}\n\t"
|
||||
depth.pop
|
||||
end
|
||||
end
|
||||
if !depth.last.nil? && !tl.length.nil? && depth.last.length == tl.length
|
||||
lines[line_id - 1] << ''
|
||||
end
|
||||
end
|
||||
|
||||
depth << tl unless depth.last == tl
|
||||
|
||||
subsection_depth = [depth.length - 1, 2].min
|
||||
|
||||
lines[line_id] = "\n\\#{ "sub" * subsection_depth }section{#{ content }}"
|
||||
lines[line_id] += "\n#{pages[content]}" if pages.keys.include?(content)
|
||||
|
||||
lines[line_id] = "\\pagebreak\n#{lines[line_id]}" if subsection_depth == 0
|
||||
|
||||
last_line = line_id
|
||||
|
||||
elsif line =~ /^\s+\S/
|
||||
last_line = line_id
|
||||
elsif line_id - last_line < 2 and line =~ /^\S/
|
||||
last_line = line_id
|
||||
end
|
||||
if line_id - last_line > 1 or line_id == lines.length - 1
|
||||
depth.delete_if do |v|
|
||||
lines[last_line] << "" # "\n\t\\end{#{ lT( v ) }}"
|
||||
end
|
||||
end
|
||||
end
|
||||
lines.join( "\n" )
|
||||
end
|
||||
end
|
||||
|
||||
class RedClothForTex < String
|
||||
|
||||
VERSION = '2.0.7'
|
||||
|
||||
#
|
||||
# Mapping of 8-bit ASCII codes to HTML numerical entity equivalents.
|
||||
# (from PyTextile)
|
||||
#
|
||||
TEXTILE_TAGS =
|
||||
|
||||
[[128, 8364], [129, 0], [130, 8218], [131, 402], [132, 8222], [133, 8230],
|
||||
[134, 8224], [135, 8225], [136, 710], [137, 8240], [138, 352], [139, 8249],
|
||||
[140, 338], [141, 0], [142, 0], [143, 0], [144, 0], [145, 8216], [146, 8217],
|
||||
[147, 8220], [148, 8221], [149, 8226], [150, 8211], [151, 8212], [152, 732],
|
||||
[153, 8482], [154, 353], [155, 8250], [156, 339], [157, 0], [158, 0], [159, 376]].
|
||||
|
||||
collect! do |a, b|
|
||||
[a.chr, ( b.zero? and "" or "&#{ b };" )]
|
||||
end
|
||||
|
||||
#
|
||||
# Regular expressions to convert to HTML.
|
||||
#
|
||||
A_HLGN = /(?:(?:<>|<|>|\=|[()]+)+)/
|
||||
A_VLGN = /[\-^~]/
|
||||
C_CLAS = '(?:\([^)]+\))'
|
||||
C_LNGE = '(?:\[[^\]]+\])'
|
||||
C_STYL = '(?:\{[^}]+\})'
|
||||
S_CSPN = '(?:\\\\\d+)'
|
||||
S_RSPN = '(?:/\d+)'
|
||||
A = "(?:#{A_HLGN}?#{A_VLGN}?|#{A_VLGN}?#{A_HLGN}?)"
|
||||
S = "(?:#{S_CSPN}?#{S_RSPN}|#{S_RSPN}?#{S_CSPN}?)"
|
||||
C = "(?:#{C_CLAS}?#{C_STYL}?#{C_LNGE}?|#{C_STYL}?#{C_LNGE}?#{C_CLAS}?|#{C_LNGE}?#{C_STYL}?#{C_CLAS}?)"
|
||||
# PUNCT = Regexp::quote( '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~' )
|
||||
PUNCT = Regexp::quote( '!"#$%&\'*+,-./:;=?@\\^_`|~' )
|
||||
HYPERLINK = '(\S+?)([^\w\s/;=\?]*?)(\s|$)'
|
||||
|
||||
GLYPHS = [
|
||||
# [ /([^\s\[{(>])?\'([dmst]\b|ll\b|ve\b|\s|:|$)/, '\1’\2' ], # single closing
|
||||
[ /([^\s\[{(>])\'/, '\1’' ], # single closing
|
||||
[ /\'(?=\s|s\b|[#{PUNCT}])/, '’' ], # single closing
|
||||
[ /\'/, '‘' ], # single opening
|
||||
# [ /([^\s\[{(])?"(\s|:|$)/, '\1”\2' ], # double closing
|
||||
[ /([^\s\[{(>])"/, '\1”' ], # double closing
|
||||
[ /"(?=\s|[#{PUNCT}])/, '”' ], # double closing
|
||||
[ /"/, '“' ], # double opening
|
||||
[ /\b( )?\.{3}/, '\1…' ], # ellipsis
|
||||
[ /\b([A-Z][A-Z0-9]{2,})\b(?:[(]([^)]*)[)])/, '<acronym title="\2">\1</acronym>' ], # 3+ uppercase acronym
|
||||
[ /(^|[^"][>\s])([A-Z][A-Z0-9 ]{2,})([^<a-z0-9]|$)/, '\1<span class="caps">\2</span>\3' ], # 3+ uppercase caps
|
||||
[ /(\.\s)?\s?--\s?/, '\1—' ], # em dash
|
||||
[ /\s->\s/, ' → ' ], # en dash
|
||||
[ /\s-\s/, ' – ' ], # en dash
|
||||
[ /(\d+) ?x ?(\d+)/, '\1×\2' ], # dimension sign
|
||||
[ /\b ?[(\[]TM[\])]/i, '™' ], # trademark
|
||||
[ /\b ?[(\[]R[\])]/i, '®' ], # registered
|
||||
[ /\b ?[(\[]C[\])]/i, '©' ] # copyright
|
||||
]
|
||||
|
||||
I_ALGN_VALS = {
|
||||
'<' => 'left',
|
||||
'=' => 'center',
|
||||
'>' => 'right'
|
||||
}
|
||||
|
||||
H_ALGN_VALS = {
|
||||
'<' => 'left',
|
||||
'=' => 'center',
|
||||
'>' => 'right',
|
||||
'<>' => 'justify'
|
||||
}
|
||||
|
||||
V_ALGN_VALS = {
|
||||
'^' => 'top',
|
||||
'-' => 'middle',
|
||||
'~' => 'bottom'
|
||||
}
|
||||
|
||||
QTAGS = [
|
||||
['**', 'bf'],
|
||||
['*', 'bf'],
|
||||
['??', 'cite'],
|
||||
['-', 'del'],
|
||||
['__', 'underline'],
|
||||
['_', 'em'],
|
||||
['%', 'span'],
|
||||
['+', 'ins'],
|
||||
['^', 'sup'],
|
||||
['~', 'sub']
|
||||
]
|
||||
|
||||
def self.available?
|
||||
if not defined? @@available
|
||||
begin
|
||||
@@available = system "pdflatex -version"
|
||||
rescue Errno::ENOENT
|
||||
@@available = false
|
||||
end
|
||||
end
|
||||
@@available
|
||||
end
|
||||
|
||||
#
|
||||
# Two accessor for setting security restrictions.
|
||||
#
|
||||
# This is a nice thing if you're using RedCloth for
|
||||
# formatting in public places (e.g. Wikis) where you
|
||||
# don't want users to abuse HTML for bad things.
|
||||
#
|
||||
# If +:filter_html+ is set, HTML which wasn't
|
||||
# created by the Textile processor will be escaped.
|
||||
#
|
||||
# If +:filter_styles+ is set, it will also disable
|
||||
# the style markup specifier. ('{color: red}')
|
||||
#
|
||||
attr_accessor :filter_html, :filter_styles
|
||||
|
||||
#
|
||||
# Accessor for toggling line folding.
|
||||
#
|
||||
# If +:fold_lines+ is set, single newlines will
|
||||
# not be converted to break tags.
|
||||
#
|
||||
attr_accessor :fold_lines
|
||||
|
||||
def initialize( string, restrictions = [] )
|
||||
restrictions.each { |r| method( "#{ r }=" ).call( true ) }
|
||||
super( string )
|
||||
end
|
||||
|
||||
#
|
||||
# Generate tex.
|
||||
#
|
||||
def to_tex( lite = false )
|
||||
|
||||
# make our working copy
|
||||
text = self.dup
|
||||
|
||||
@urlrefs = {}
|
||||
@shelf = []
|
||||
|
||||
# incoming_entities text
|
||||
fix_entities text
|
||||
clean_white_space text
|
||||
|
||||
get_refs text
|
||||
|
||||
no_textile text
|
||||
|
||||
unless lite
|
||||
lists text
|
||||
table text
|
||||
end
|
||||
|
||||
glyphs text
|
||||
|
||||
unless lite
|
||||
fold text
|
||||
block text
|
||||
end
|
||||
|
||||
retrieve text
|
||||
encode_entities text
|
||||
|
||||
text.gsub!(/\[\[(.*?)\]\]/, "\\1")
|
||||
text.gsub!(/_/, "\\_")
|
||||
text.gsub!( /<\/?notextile>/, '' )
|
||||
# text.gsub!( /x%x%/, '&' )
|
||||
# text.gsub!( /<br \/>/, "<br />\n" )
|
||||
text.strip!
|
||||
text
|
||||
|
||||
end
|
||||
|
||||
def pgl( text )
|
||||
GLYPHS.each do |re, resub|
|
||||
text.gsub! re, resub
|
||||
end
|
||||
end
|
||||
|
||||
def pba( text_in, element = "" )
|
||||
|
||||
return '' unless text_in
|
||||
|
||||
style = []
|
||||
text = text_in.dup
|
||||
if element == 'td'
|
||||
colspan = $1 if text =~ /\\(\d+)/
|
||||
rowspan = $1 if text =~ /\/(\d+)/
|
||||
style << "vertical-align:#{ v_align( $& ) };" if text =~ A_VLGN
|
||||
end
|
||||
|
||||
style << "#{ $1 };" if not @filter_styles and
|
||||
text.sub!( /\{([^}]*)\}/, '' )
|
||||
|
||||
lang = $1 if
|
||||
text.sub!( /\[([^)]+?)\]/, '' )
|
||||
|
||||
cls = $1 if
|
||||
text.sub!( /\(([^()]+?)\)/, '' )
|
||||
|
||||
style << "padding-left:#{ $1.length }em;" if
|
||||
text.sub!( /([(]+)/, '' )
|
||||
|
||||
style << "padding-right:#{ $1.length }em;" if text.sub!( /([)]+)/, '' )
|
||||
|
||||
style << "text-align:#{ h_align( $& ) };" if text =~ A_HLGN
|
||||
|
||||
cls, id = $1, $2 if cls =~ /^(.*?)#(.*)$/
|
||||
|
||||
atts = ''
|
||||
atts << " style=\"#{ style.join }\"" unless style.empty?
|
||||
atts << " class=\"#{ cls }\"" unless cls.to_s.empty?
|
||||
atts << " lang=\"#{ lang }\"" if lang
|
||||
atts << " id=\"#{ id }\"" if id
|
||||
atts << " colspan=\"#{ colspan }\"" if colspan
|
||||
atts << " rowspan=\"#{ rowspan }\"" if rowspan
|
||||
|
||||
atts
|
||||
end
|
||||
|
||||
def table( text )
|
||||
text << "\n\n"
|
||||
text.gsub!( /^(?:table(_?#{S}#{A}#{C})\. ?\n)?^(#{A}#{C}\.? ?\|.*?\|)\n\n/m ) do |matches|
|
||||
|
||||
tatts, fullrow = $~[1..2]
|
||||
tatts = pba( tatts, 'table' )
|
||||
rows = []
|
||||
|
||||
fullrow.
|
||||
split( /\|$/m ).
|
||||
delete_if { |x| x.empty? }.
|
||||
each do |row|
|
||||
|
||||
ratts, row = pba( $1, 'tr' ), $2 if row =~ /^(#{A}#{C}\. )(.*)/m
|
||||
|
||||
cells = []
|
||||
row.split( '|' ).each do |cell|
|
||||
ctyp = 'd'
|
||||
ctyp = 'h' if cell =~ /^_/
|
||||
|
||||
catts = ''
|
||||
catts, cell = pba( $1, 'td' ), $2 if cell =~ /^(_?#{S}#{A}#{C}\. )(.*)/
|
||||
|
||||
unless cell.strip.empty?
|
||||
cells << "\t\t\t<t#{ ctyp }#{ catts }>#{ cell }</t#{ ctyp }>"
|
||||
end
|
||||
end
|
||||
rows << "\t\t<tr#{ ratts }>\n#{ cells.join( "\n" ) }\n\t\t</tr>"
|
||||
end
|
||||
"\t<table#{ tatts }>\n#{ rows.join( "\n" ) }\n\t</table>\n\n"
|
||||
end
|
||||
end
|
||||
|
||||
def lists( text )
|
||||
text.gsub!( /^([#*]+?#{C} .*?)$(?![^#*])/m ) do |match|
|
||||
lines = match.split( /\n/ )
|
||||
last_line = -1
|
||||
depth = []
|
||||
lines.each_with_index do |line, line_id|
|
||||
if line =~ /^([#*]+)(#{A}#{C}) (.*)$/m
|
||||
tl,atts,content = $~[1..3]
|
||||
if depth.last
|
||||
if depth.last.length > tl.length
|
||||
(depth.length - 1).downto(0) do |i|
|
||||
break if depth[i].length == tl.length
|
||||
lines[line_id - 1] << "\n\t\\end{#{ lT( depth[i] ) }}\n\t"
|
||||
depth.pop
|
||||
end
|
||||
end
|
||||
if !depth.last.nil? && !tl.length.nil? && depth.last.length == tl.length
|
||||
lines[line_id - 1] << ''
|
||||
end
|
||||
end
|
||||
unless depth.last == tl
|
||||
depth << tl
|
||||
atts = pba( atts )
|
||||
lines[line_id] = "\t\\begin{#{ lT(tl) }}\n\t\\item #{ content }"
|
||||
else
|
||||
lines[line_id] = "\t\t\\item #{ content }"
|
||||
end
|
||||
last_line = line_id
|
||||
|
||||
elsif line =~ /^\s+\S/
|
||||
last_line = line_id
|
||||
elsif line_id - last_line < 2 and line =~ /^\S/
|
||||
last_line = line_id
|
||||
end
|
||||
if line_id - last_line > 1 or line_id == lines.length - 1
|
||||
depth.delete_if do |v|
|
||||
lines[last_line] << "\n\t\\end{#{ lT( v ) }}"
|
||||
end
|
||||
end
|
||||
end
|
||||
lines.join( "\n" )
|
||||
end
|
||||
end
|
||||
|
||||
def lT( text )
|
||||
text =~ /\#$/ ? 'enumerate' : 'itemize'
|
||||
end
|
||||
|
||||
def fold( text )
|
||||
text.gsub!( /(.+)\n(?![#*\s|])/, "\\1\\\\\\\\" )
|
||||
# text.gsub!( /(.+)\n(?![#*\s|])/, "\\1#{ @fold_lines ? ' ' : '<br />' }" )
|
||||
end
|
||||
|
||||
def block( text )
|
||||
pre = false
|
||||
find = ['bq','h[1-6]','fn\d+']
|
||||
|
||||
regexp_cue = []
|
||||
|
||||
lines = text.split( /\n/ ) + [' ']
|
||||
new_text =
|
||||
lines.collect do |line|
|
||||
pre = true if line =~ /<(pre|notextile)>/i
|
||||
find.each do |tag|
|
||||
line.gsub!( /^(#{ tag })(#{A}#{C})\.(?::(\S+))? (.*)$/ ) do |m|
|
||||
tag,atts,cite,content = $~[1..4]
|
||||
|
||||
atts = pba( atts )
|
||||
|
||||
if tag =~ /fn(\d+)/
|
||||
# tag = 'p';
|
||||
# atts << " id=\"fn#{ $1 }\""
|
||||
regexp_cue << [ /footnote\{#{$1}}/, "footnote{#{content}}" ]
|
||||
content = ""
|
||||
end
|
||||
|
||||
if tag =~ /h([1-6])/
|
||||
section_type = "sub" * [$1.to_i - 1, 2].min
|
||||
start = "\t\\#{section_type}section*{"
|
||||
tend = "}"
|
||||
end
|
||||
|
||||
if tag == "bq"
|
||||
cite = check_refs( cite )
|
||||
cite = " cite=\"#{ cite }\"" if cite
|
||||
start = "\t\\begin{quotation}\n\\noindent {\\em ";
|
||||
tend = "}\n\t\\end{quotation}";
|
||||
end
|
||||
|
||||
"#{ start }#{ content }#{ tend }"
|
||||
end unless pre
|
||||
end
|
||||
|
||||
#line.gsub!( /^(?!\t|<\/?pre|<\/?notextile|<\/?code|$| )(.*)/, "\t<p>\\1</p>" )
|
||||
|
||||
#line.gsub!( "<br />", "\n" ) if pre
|
||||
# pre = false if line =~ /<\/(pre|notextile)>/i
|
||||
|
||||
line
|
||||
end.join( "\n" )
|
||||
text.replace( new_text )
|
||||
regexp_cue.each { |pair| text.gsub!(pair.first, pair.last) }
|
||||
end
|
||||
|
||||
def span( text )
|
||||
QTAGS.each do |tt, ht|
|
||||
ttr = Regexp::quote( tt )
|
||||
text.gsub!(
|
||||
|
||||
/(^|\s|\>|[#{PUNCT}{(\[])
|
||||
#{ttr}
|
||||
(#{C})
|
||||
(?::(\S+?))?
|
||||
([^\s#{ttr}]+?(?:[^\n]|\n(?!\n))*?)
|
||||
([#{PUNCT}]*?)
|
||||
#{ttr}
|
||||
(?=[\])}]|[#{PUNCT}]+?|<|\s|$)/xm
|
||||
|
||||
) do |m|
|
||||
|
||||
start,atts,cite,content,tend = $~[1..5]
|
||||
atts = pba( atts )
|
||||
atts << " cite=\"#{ cite }\"" if cite
|
||||
|
||||
"#{ start }{\\#{ ht } #{ content }#{ tend }}"
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def links( text )
|
||||
text.gsub!( /
|
||||
([\s\[{(]|[#{PUNCT}])? # $pre
|
||||
" # start
|
||||
(#{C}) # $atts
|
||||
([^"]+?) # $text
|
||||
\s?
|
||||
(?:\(([^)]+?)\)(?="))? # $title
|
||||
":
|
||||
(\S+?) # $url
|
||||
(\/)? # $slash
|
||||
([^\w\/;]*?) # $post
|
||||
(?=\s|$)
|
||||
/x ) do |m|
|
||||
pre,atts,text,title,url,slash,post = $~[1..7]
|
||||
|
||||
url = check_refs( url )
|
||||
|
||||
atts = pba( atts )
|
||||
atts << " title=\"#{ title }\"" if title
|
||||
atts = shelve( atts ) if atts
|
||||
|
||||
"#{ pre }<a href=\"#{ url }#{ slash }\"#{ atts }>#{ text }</a>#{ post }"
|
||||
end
|
||||
end
|
||||
|
||||
def get_refs( text )
|
||||
text.gsub!( /(^|\s)\[(.+?)\]((?:http:\/\/|javascript:|ftp:\/\/|\/)\S+?)(?=\s|$)/ ) do |m|
|
||||
flag, url = $~[1..2]
|
||||
@urlrefs[flag] = url
|
||||
end
|
||||
end
|
||||
|
||||
def check_refs( text )
|
||||
@urlrefs[text] || text
|
||||
end
|
||||
|
||||
def image( text )
|
||||
text.gsub!( /
|
||||
\! # opening
|
||||
(\<|\=|\>)? # optional alignment atts
|
||||
(#{C}) # optional style,class atts
|
||||
(?:\. )? # optional dot-space
|
||||
([^\s(!]+?) # presume this is the src
|
||||
\s? # optional space
|
||||
(?:\(((?:[^\(\)]|\([^\)]+\))+?)\))? # optional title
|
||||
\! # closing
|
||||
(?::#{ HYPERLINK })? # optional href
|
||||
/x ) do |m|
|
||||
algn,atts,url,title,href,href_a1,href_a2 = $~[1..7]
|
||||
atts = pba( atts )
|
||||
atts << " align=\"#{ i_align( algn ) }\"" if algn
|
||||
atts << " title=\"#{ title }\"" if title
|
||||
atts << " alt=\"#{ title }\""
|
||||
# size = @getimagesize($url);
|
||||
# if($size) $atts.= " $size[3]";
|
||||
|
||||
href = check_refs( href ) if href
|
||||
url = check_refs( url )
|
||||
|
||||
out = ''
|
||||
out << "<a href=\"#{ href }\">" if href
|
||||
out << "<img src=\"#{ url }\"#{ atts } />"
|
||||
out << "</a>#{ href_a1 }#{ href_a2 }" if href
|
||||
|
||||
out
|
||||
end
|
||||
end
|
||||
|
||||
def code( text )
|
||||
text.gsub!( /
|
||||
(?:^|([\s\(\[{])) # 1 open bracket?
|
||||
@ # opening
|
||||
(?:\|(\w+?)\|)? # 2 language
|
||||
(\S(?:[^\n]|\n(?!\n))*?) # 3 code
|
||||
@ # closing
|
||||
(?:$|([\]})])|
|
||||
(?=[#{PUNCT}]{1,2}|
|
||||
\s)) # 4 closing bracket?
|
||||
/x ) do |m|
|
||||
before,lang,code,after = $~[1..4]
|
||||
lang = " language=\"#{ lang }\"" if lang
|
||||
"#{ before }<code#{ lang }>#{ code }</code>#{ after }"
|
||||
end
|
||||
end
|
||||
|
||||
def shelve( val )
|
||||
@shelf << val
|
||||
" <#{ @shelf.length }>"
|
||||
end
|
||||
|
||||
def retrieve( text )
|
||||
@shelf.each_with_index do |r, i|
|
||||
text.gsub!( " <#{ i + 1 }>", r )
|
||||
end
|
||||
end
|
||||
|
||||
def incoming_entities( text )
|
||||
## turn any incoming ampersands into a dummy character for now.
|
||||
## This uses a negative lookahead for alphanumerics followed by a semicolon,
|
||||
## implying an incoming html entity, to be skipped
|
||||
|
||||
text.gsub!( /&(?![#a-z0-9]+;)/i, "x%x%" )
|
||||
end
|
||||
|
||||
def encode_entities( text )
|
||||
## Convert high and low ascii to entities.
|
||||
# if $-K == "UTF-8"
|
||||
# encode_high( text )
|
||||
# else
|
||||
text.texesc!( :NoQuotes )
|
||||
# end
|
||||
end
|
||||
|
||||
def fix_entities( text )
|
||||
## de-entify any remaining angle brackets or ampersands
|
||||
text.gsub!( "\&", "&" )
|
||||
text.gsub!( "\%", "%" )
|
||||
end
|
||||
|
||||
def clean_white_space( text )
|
||||
text.gsub!( /\r\n/, "\n" )
|
||||
text.gsub!( /\t/, '' )
|
||||
text.gsub!( /\n{3,}/, "\n\n" )
|
||||
text.gsub!( /\n *\n/, "\n\n" )
|
||||
text.gsub!( /"$/, "\" " )
|
||||
end
|
||||
|
||||
def no_textile( text )
|
||||
text.gsub!( /(^|\s)==(.*?)==(\s|$)?/,
|
||||
'\1<notextile>\2</notextile>\3' )
|
||||
end
|
||||
|
||||
def footnote_ref( text )
|
||||
text.gsub!( /\[([0-9]+?)\](\s)?/,
|
||||
'\footnote{\1}\2')
|
||||
#'<sup><a href="#fn\1">\1</a></sup>\2' )
|
||||
end
|
||||
|
||||
def inline( text )
|
||||
image text
|
||||
links text
|
||||
code text
|
||||
span text
|
||||
end
|
||||
|
||||
def glyphs_deep( text )
|
||||
codepre = 0
|
||||
offtags = /(?:code|pre|kbd|notextile)/
|
||||
if text !~ /<.*>/
|
||||
# pgl text
|
||||
footnote_ref text
|
||||
else
|
||||
used_offtags = {}
|
||||
text.gsub!( /(?:[^<].*?(?=<[^\n]*?>|$)|<[^\n]*?>+)/m ) do |line|
|
||||
tagline = ( line =~ /^<.*>/ )
|
||||
|
||||
## matches are off if we're between <code>, <pre> etc.
|
||||
if tagline
|
||||
if line =~ /<(#{ offtags })>/i
|
||||
codepre += 1
|
||||
used_offtags[$1] = true
|
||||
line.texesc!( :NoQuotes ) if codepre - used_offtags.length > 0
|
||||
elsif line =~ /<\/(#{ offtags })>/i
|
||||
line.texesc!( :NoQuotes ) if codepre - used_offtags.length > 0
|
||||
codepre -= 1 unless codepre.zero?
|
||||
used_offtags = {} if codepre.zero?
|
||||
elsif @filter_html or codepre > 0
|
||||
line.texesc!( :NoQuotes )
|
||||
## line.gsub!( /<(\/?#{ offtags })>/, '<\1>' )
|
||||
end
|
||||
## do htmlspecial if between <code>
|
||||
elsif codepre > 0
|
||||
line.texesc!( :NoQuotes )
|
||||
## line.gsub!( /<(\/?#{ offtags })>/, '<\1>' )
|
||||
elsif not tagline
|
||||
inline line
|
||||
glyphs_deep line
|
||||
end
|
||||
|
||||
line
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def glyphs( text )
|
||||
text.gsub!( /"\z/, "\" " )
|
||||
## if no html, do a simple search and replace...
|
||||
if text !~ /<.*>/
|
||||
inline text
|
||||
end
|
||||
glyphs_deep text
|
||||
end
|
||||
|
||||
def i_align( text )
|
||||
I_ALGN_VALS[text]
|
||||
end
|
||||
|
||||
def h_align( text )
|
||||
H_ALGN_VALS[text]
|
||||
end
|
||||
|
||||
def v_align( text )
|
||||
V_ALGN_VALS[text]
|
||||
end
|
||||
|
||||
def encode_high( text )
|
||||
## mb_encode_numericentity($text, $cmap, $charset);
|
||||
end
|
||||
|
||||
def decode_high( text )
|
||||
## mb_decode_numericentity($text, $cmap, $charset);
|
||||
end
|
||||
|
||||
def textile_popup_help( name, helpvar, windowW, windowH )
|
||||
' <a target="_blank" href="http://www.textpattern.com/help/?item=' + helpvar + '" onclick="window.open(this.href, \'popupwindow\', \'width=' + windowW + ',height=' + windowH + ',scrollbars,resizable\'); return false;">' + name + '</a><br />'
|
||||
end
|
||||
|
||||
CMAP = [
|
||||
160, 255, 0, 0xffff,
|
||||
402, 402, 0, 0xffff,
|
||||
913, 929, 0, 0xffff,
|
||||
931, 937, 0, 0xffff,
|
||||
945, 969, 0, 0xffff,
|
||||
977, 978, 0, 0xffff,
|
||||
982, 982, 0, 0xffff,
|
||||
8226, 8226, 0, 0xffff,
|
||||
8230, 8230, 0, 0xffff,
|
||||
8242, 8243, 0, 0xffff,
|
||||
8254, 8254, 0, 0xffff,
|
||||
8260, 8260, 0, 0xffff,
|
||||
8465, 8465, 0, 0xffff,
|
||||
8472, 8472, 0, 0xffff,
|
||||
8476, 8476, 0, 0xffff,
|
||||
8482, 8482, 0, 0xffff,
|
||||
8501, 8501, 0, 0xffff,
|
||||
8592, 8596, 0, 0xffff,
|
||||
8629, 8629, 0, 0xffff,
|
||||
8656, 8660, 0, 0xffff,
|
||||
8704, 8704, 0, 0xffff,
|
||||
8706, 8707, 0, 0xffff,
|
||||
8709, 8709, 0, 0xffff,
|
||||
8711, 8713, 0, 0xffff,
|
||||
8715, 8715, 0, 0xffff,
|
||||
8719, 8719, 0, 0xffff,
|
||||
8721, 8722, 0, 0xffff,
|
||||
8727, 8727, 0, 0xffff,
|
||||
8730, 8730, 0, 0xffff,
|
||||
8733, 8734, 0, 0xffff,
|
||||
8736, 8736, 0, 0xffff,
|
||||
8743, 8747, 0, 0xffff,
|
||||
8756, 8756, 0, 0xffff,
|
||||
8764, 8764, 0, 0xffff,
|
||||
8773, 8773, 0, 0xffff,
|
||||
8776, 8776, 0, 0xffff,
|
||||
8800, 8801, 0, 0xffff,
|
||||
8804, 8805, 0, 0xffff,
|
||||
8834, 8836, 0, 0xffff,
|
||||
8838, 8839, 0, 0xffff,
|
||||
8853, 8853, 0, 0xffff,
|
||||
8855, 8855, 0, 0xffff,
|
||||
8869, 8869, 0, 0xffff,
|
||||
8901, 8901, 0, 0xffff,
|
||||
8968, 8971, 0, 0xffff,
|
||||
9001, 9002, 0, 0xffff,
|
||||
9674, 9674, 0, 0xffff,
|
||||
9824, 9824, 0, 0xffff,
|
||||
9827, 9827, 0, 0xffff,
|
||||
9829, 9830, 0, 0xffff,
|
||||
338, 339, 0, 0xffff,
|
||||
352, 353, 0, 0xffff,
|
||||
376, 376, 0, 0xffff,
|
||||
710, 710, 0, 0xffff,
|
||||
732, 732, 0, 0xffff,
|
||||
8194, 8195, 0, 0xffff,
|
||||
8201, 8201, 0, 0xffff,
|
||||
8204, 8207, 0, 0xffff,
|
||||
8211, 8212, 0, 0xffff,
|
||||
8216, 8218, 0, 0xffff,
|
||||
8218, 8218, 0, 0xffff,
|
||||
8220, 8222, 0, 0xffff,
|
||||
8224, 8225, 0, 0xffff,
|
||||
8240, 8240, 0, 0xffff,
|
||||
8249, 8250, 0, 0xffff,
|
||||
8364, 8364, 0, 0xffff
|
||||
]
|
||||
end
|
||||
|
75
libraries/url_rewriting_hack.rb
Executable file
75
libraries/url_rewriting_hack.rb
Executable file
|
@ -0,0 +1,75 @@
|
|||
# Below are some hacks to Rails internal classes that implement Instiki URLs scheme.
|
||||
# It is no doubt a bad practice to override internal implementation of anything.
|
||||
# When Rails implements some way to do it in the framework, this code should be replaced
|
||||
# with something more legitimate.
|
||||
|
||||
# In Instiki URLs are mapped to the ActionPack actions, possibly performed on a particular
|
||||
# web (sub-wiki) and page within that web.
|
||||
#
|
||||
# 1. Controller is always 'wiki'
|
||||
# 2. '/name1/' maps to action 'name1', unspecified web
|
||||
# Example: http://localhost/new_system/
|
||||
# 3. Special case of above, URI '/wiki/' maps to action 'index', because Rails sets this address
|
||||
# when default controller name is specified as 'wiki', and an application root
|
||||
# (http://localhost:2500/)is requested.
|
||||
# 4. '/name1/name2/' maps to web 'name1', action 'name2'
|
||||
# Example: http://localhost/mywiki/search/
|
||||
# 5. '/name1/name2/name3/' maps to web 'name1', action 'name2',
|
||||
# Example: http://localhost/mywiki/show/HomePage
|
||||
|
||||
|
||||
require 'dispatcher'
|
||||
|
||||
# Overrides Rails DispatchServlet.parse_uri
|
||||
class DispatchServlet
|
||||
|
||||
def self.parse_uri(path)
|
||||
ApplicationController.logger.debug "Parsing URI '#{path}'"
|
||||
component = /([-_a-zA-Z0-9]+)/
|
||||
case path.sub(%r{^/(?:fcgi|mruby|cgi)/}, "/")
|
||||
when '/wiki/'
|
||||
{ :web => nil, :controller => 'wiki', :action => 'index' }
|
||||
when %r{^/#{component}/?$}
|
||||
{ :web => nil, :controller => 'wiki', :action => $1 }
|
||||
when %r{^/#{component}/#{component}/?$}
|
||||
{ :web => $1, :controller => 'wiki', :action => $2 }
|
||||
when %r{^/#{component}/#{component}/#{component}/?$}
|
||||
{ :web => $1, :controller => 'wiki', :action => $2, :id => $3 }
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
require 'action_controller/url_rewriter.rb'
|
||||
|
||||
# Overrides parts of AP UrlRewriter to achieve the Instiki's legacy URL scheme
|
||||
module ActionController
|
||||
class UrlRewriter
|
||||
|
||||
VALID_OPTIONS << :web unless VALID_OPTIONS.include? :web
|
||||
|
||||
private
|
||||
|
||||
def resolve_aliases(options)
|
||||
options[:controller_prefix] = options[:web] unless options[:web].nil?
|
||||
options
|
||||
end
|
||||
|
||||
def controller_name(options, controller_prefix)
|
||||
ensure_slash_suffix(options, :controller_prefix)
|
||||
|
||||
controller_name = case options[:controller_prefix]
|
||||
when String: options[:controller_prefix]
|
||||
when false : ""
|
||||
when nil : controller_prefix || ""
|
||||
end
|
||||
# In Instiki we don't need the controller name (there is only one comtroller, anyway)
|
||||
# therefore the below line is commented out
|
||||
# controller_name << (options[:controller] + "/") if options[:controller]
|
||||
return controller_name
|
||||
end
|
||||
end
|
||||
end
|
18
natives/osx/desktop_launcher/AppDelegate.h
Executable file
18
natives/osx/desktop_launcher/AppDelegate.h
Executable file
|
@ -0,0 +1,18 @@
|
|||
/* AppDelegate */
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@interface AppDelegate : NSObject
|
||||
{
|
||||
IBOutlet NSMenu* statusMenu;
|
||||
NSTask* serverCommand;
|
||||
int processID;
|
||||
BOOL shouldOpenUntitled;
|
||||
|
||||
NSNetService* service;
|
||||
}
|
||||
- (IBAction)about:(id)sender;
|
||||
- (IBAction)goToHomepage:(id)sender;
|
||||
- (IBAction)goToInstikiOrg:(id)sender;
|
||||
- (IBAction)quit:(id)sender;
|
||||
@end
|
109
natives/osx/desktop_launcher/AppDelegate.mm
Executable file
109
natives/osx/desktop_launcher/AppDelegate.mm
Executable file
|
@ -0,0 +1,109 @@
|
|||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
#import "AppDelegate.h"
|
||||
|
||||
int launch_ruby (char const* cmd)
|
||||
{
|
||||
int pId, parentID = getpid();
|
||||
if((pId = fork()) == 0) // child
|
||||
{
|
||||
NSLog(@"set child (%d) to pgrp %d", getpid(), parentID);
|
||||
setpgrp(0, parentID);
|
||||
system(cmd);
|
||||
return 0;
|
||||
}
|
||||
else // parent
|
||||
{
|
||||
NSLog(@"started child process: %d", pId);
|
||||
return pId;
|
||||
}
|
||||
}
|
||||
|
||||
@implementation AppDelegate
|
||||
|
||||
- (NSString*)storageDirectory
|
||||
{
|
||||
NSString* dir = [NSHomeDirectory() stringByAppendingPathComponent:@"Library/Application Support/Instiki"];
|
||||
[[NSFileManager defaultManager] createDirectoryAtPath:dir attributes:nil];
|
||||
return dir;
|
||||
}
|
||||
|
||||
- (void)awakeFromNib
|
||||
{
|
||||
setpgrp(0, getpid());
|
||||
|
||||
if([[[[NSBundle mainBundle] infoDictionary] objectForKey:@"LSUIElement"] isEqualToString:@"1"])
|
||||
{
|
||||
NSStatusBar* bar = [NSStatusBar systemStatusBar];
|
||||
NSStatusItem* item = [[bar statusItemWithLength:NSVariableStatusItemLength] retain];
|
||||
[item setTitle:@"Wiki"];
|
||||
[item setHighlightMode:YES];
|
||||
[item setMenu:statusMenu];
|
||||
}
|
||||
|
||||
NSBundle* bundle = [NSBundle bundleForClass:[self class]];
|
||||
NSString* ruby = [bundle pathForResource:@"ruby" ofType:nil];
|
||||
NSString* script = [[bundle resourcePath] stringByAppendingPathComponent:@"rb_src/instiki.rb"];
|
||||
if(ruby && script)
|
||||
{
|
||||
NSString* cmd = [NSString stringWithFormat:
|
||||
@"%@ -I '%@' -I '%@' '%@' -s --storage='%@'",
|
||||
ruby,
|
||||
[[bundle resourcePath] stringByAppendingPathComponent:@"lib/ruby/1.8"],
|
||||
[[bundle resourcePath] stringByAppendingPathComponent:@"lib/ruby/1.8/powerpc-darwin"],
|
||||
script,
|
||||
[self storageDirectory]
|
||||
];
|
||||
NSLog(@"starting %@", cmd);
|
||||
processID = launch_ruby([cmd UTF8String]);
|
||||
}
|
||||
|
||||
/* public the service using rendezvous */
|
||||
service = [[NSNetService alloc]
|
||||
initWithDomain:@"" // default domain
|
||||
type:@"_http._tcp."
|
||||
name:[NSString stringWithFormat:@"%@'s Instiki", NSFullUserName()]
|
||||
port:2500];
|
||||
[service publish];
|
||||
}
|
||||
|
||||
- (void)applicationWillTerminate:(NSNotification*)aNotification
|
||||
{
|
||||
[service stop];
|
||||
[service release];
|
||||
|
||||
kill(0, SIGTERM);
|
||||
}
|
||||
|
||||
- (IBAction)about:(id)sender
|
||||
{
|
||||
[NSApp activateIgnoringOtherApps:YES];
|
||||
[NSApp orderFrontStandardAboutPanel:self];
|
||||
}
|
||||
|
||||
- (IBAction)goToHomepage:(id)sender
|
||||
{
|
||||
[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"http://localhost:2500/"]];
|
||||
}
|
||||
|
||||
- (IBAction)goToInstikiOrg:(id)sender
|
||||
{
|
||||
[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"http://www.instiki.org/"]];
|
||||
}
|
||||
|
||||
- (BOOL)applicationShouldOpenUntitledFile:(NSApplication*)sender
|
||||
{
|
||||
return shouldOpenUntitled ?: (shouldOpenUntitled = YES, NO);
|
||||
}
|
||||
|
||||
- (BOOL)applicationOpenUntitledFile:(NSApplication*)theApplication
|
||||
{
|
||||
return [self goToHomepage:self], YES;
|
||||
}
|
||||
|
||||
- (IBAction)quit:(id)sender
|
||||
{
|
||||
[NSApp terminate:self];
|
||||
}
|
||||
|
||||
@end
|
16
natives/osx/desktop_launcher/Credits.html
Executable file
16
natives/osx/desktop_launcher/Credits.html
Executable file
|
@ -0,0 +1,16 @@
|
|||
<dl>
|
||||
<dt>Engineering:</dt>
|
||||
<dd>Some people</dd>
|
||||
|
||||
<dt>Human Interface Design:</dt>
|
||||
<dd>Some other people</dd>
|
||||
|
||||
<dt>Testing:</dt>
|
||||
<dd>Hopefully not nobody</dd>
|
||||
|
||||
<dt>Documentation:</dt>
|
||||
<dd>Whoever</dd>
|
||||
|
||||
<dt>With special thanks to:</dt>
|
||||
<dd>Mom</dd>
|
||||
</dl>
|
2
natives/osx/desktop_launcher/English.lproj/.cvsignore
Executable file
2
natives/osx/desktop_launcher/English.lproj/.cvsignore
Executable file
|
@ -0,0 +1,2 @@
|
|||
*~.nib
|
||||
|
BIN
natives/osx/desktop_launcher/English.lproj/InfoPlist.strings
Executable file
BIN
natives/osx/desktop_launcher/English.lproj/InfoPlist.strings
Executable file
Binary file not shown.
13
natives/osx/desktop_launcher/English.lproj/MainMenu.nib/classes.nib
generated
Executable file
13
natives/osx/desktop_launcher/English.lproj/MainMenu.nib/classes.nib
generated
Executable file
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
IBClasses = (
|
||||
{
|
||||
ACTIONS = {about = id; goToHomepage = id; goToInstikiOrg = id; quit = id; };
|
||||
CLASS = AppDelegate;
|
||||
LANGUAGE = ObjC;
|
||||
OUTLETS = {statusMenu = NSMenu; };
|
||||
SUPERCLASS = NSObject;
|
||||
},
|
||||
{CLASS = FirstResponder; LANGUAGE = ObjC; SUPERCLASS = NSObject; }
|
||||
);
|
||||
IBVersion = 1;
|
||||
}
|
24
natives/osx/desktop_launcher/English.lproj/MainMenu.nib/info.nib
generated
Executable file
24
natives/osx/desktop_launcher/English.lproj/MainMenu.nib/info.nib
generated
Executable file
|
@ -0,0 +1,24 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IBDocumentLocation</key>
|
||||
<string>109 6 356 240 0 0 1440 878 </string>
|
||||
<key>IBEditorPositions</key>
|
||||
<dict>
|
||||
<key>206</key>
|
||||
<string>112 300 116 87 0 0 1440 878 </string>
|
||||
<key>29</key>
|
||||
<string>241 316 70 44 0 0 1440 878 </string>
|
||||
</dict>
|
||||
<key>IBFramework Version</key>
|
||||
<string>349.0</string>
|
||||
<key>IBOpenObjects</key>
|
||||
<array>
|
||||
<integer>206</integer>
|
||||
<integer>29</integer>
|
||||
</array>
|
||||
<key>IBSystem Version</key>
|
||||
<string>7H63</string>
|
||||
</dict>
|
||||
</plist>
|
BIN
natives/osx/desktop_launcher/English.lproj/MainMenu.nib/objects.nib
generated
Executable file
BIN
natives/osx/desktop_launcher/English.lproj/MainMenu.nib/objects.nib
generated
Executable file
Binary file not shown.
13
natives/osx/desktop_launcher/Info.plist
Executable file
13
natives/osx/desktop_launcher/Info.plist
Executable file
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
CFBundleDevelopmentRegion = English;
|
||||
CFBundleExecutable = Instiki;
|
||||
CFBundleIconFile = "";
|
||||
CFBundleIdentifier = "com.nextangle.instiki";
|
||||
CFBundleInfoDictionaryVersion = "6.0";
|
||||
CFBundlePackageType = APPL;
|
||||
CFBundleSignature = WIKI;
|
||||
CFBundleVersion = "0.9.0";
|
||||
LSUIElement = 1;
|
||||
NSMainNibFile = MainMenu;
|
||||
NSPrincipalClass = NSApplication;
|
||||
}
|
592
natives/osx/desktop_launcher/Instiki.xcode/project.pbxproj
Executable file
592
natives/osx/desktop_launcher/Instiki.xcode/project.pbxproj
Executable file
|
@ -0,0 +1,592 @@
|
|||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 39;
|
||||
objects = {
|
||||
080E96DDFE201D6D7F000001 = {
|
||||
children = (
|
||||
174B2765065CE31400ED6208,
|
||||
174B2766065CE31400ED6208,
|
||||
);
|
||||
isa = PBXGroup;
|
||||
name = Classes;
|
||||
refType = 4;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
089C165CFE840E0CC02AAC07 = {
|
||||
children = (
|
||||
089C165DFE840E0CC02AAC07,
|
||||
);
|
||||
isa = PBXVariantGroup;
|
||||
name = InfoPlist.strings;
|
||||
refType = 4;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
089C165DFE840E0CC02AAC07 = {
|
||||
fileEncoding = 10;
|
||||
isa = PBXFileReference;
|
||||
lastKnownFileType = text.plist.strings;
|
||||
name = English;
|
||||
path = English.lproj/InfoPlist.strings;
|
||||
refType = 4;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
//080
|
||||
//081
|
||||
//082
|
||||
//083
|
||||
//084
|
||||
//100
|
||||
//101
|
||||
//102
|
||||
//103
|
||||
//104
|
||||
1058C7A0FEA54F0111CA2CBB = {
|
||||
children = (
|
||||
1058C7A1FEA54F0111CA2CBB,
|
||||
);
|
||||
isa = PBXGroup;
|
||||
name = "Linked Frameworks";
|
||||
refType = 4;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
1058C7A1FEA54F0111CA2CBB = {
|
||||
fallbackIsa = PBXFileReference;
|
||||
isa = PBXFrameworkReference;
|
||||
lastKnownFileType = wrapper.framework;
|
||||
name = Cocoa.framework;
|
||||
path = /System/Library/Frameworks/Cocoa.framework;
|
||||
refType = 0;
|
||||
sourceTree = "<absolute>";
|
||||
};
|
||||
1058C7A2FEA54F0111CA2CBB = {
|
||||
children = (
|
||||
29B97325FDCFA39411CA2CEA,
|
||||
29B97324FDCFA39411CA2CEA,
|
||||
);
|
||||
isa = PBXGroup;
|
||||
name = "Other Frameworks";
|
||||
refType = 4;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
//100
|
||||
//101
|
||||
//102
|
||||
//103
|
||||
//104
|
||||
//170
|
||||
//171
|
||||
//172
|
||||
//173
|
||||
//174
|
||||
174B2765065CE31400ED6208 = {
|
||||
fileEncoding = 4;
|
||||
isa = PBXFileReference;
|
||||
lastKnownFileType = sourcecode.cpp.objcpp;
|
||||
path = AppDelegate.mm;
|
||||
refType = 4;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
174B2766065CE31400ED6208 = {
|
||||
fileEncoding = 4;
|
||||
isa = PBXFileReference;
|
||||
lastKnownFileType = sourcecode.c.h;
|
||||
path = AppDelegate.h;
|
||||
refType = 4;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
174B2767065CE31400ED6208 = {
|
||||
fileRef = 174B2765065CE31400ED6208;
|
||||
isa = PBXBuildFile;
|
||||
settings = {
|
||||
};
|
||||
};
|
||||
174B2768065CE31400ED6208 = {
|
||||
fileRef = 174B2766065CE31400ED6208;
|
||||
isa = PBXBuildFile;
|
||||
settings = {
|
||||
};
|
||||
};
|
||||
17BF6FD9067536EB003F37D6 = {
|
||||
children = (
|
||||
63B86D2F0673A5D300807E13,
|
||||
63B86D1A0673A5B200807E13,
|
||||
63B86D100673A58400807E13,
|
||||
);
|
||||
isa = PBXGroup;
|
||||
name = "Instiki Source";
|
||||
refType = 4;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
17C1C5CD065D3A3C003526E7 = {
|
||||
fileEncoding = 4;
|
||||
isa = PBXFileReference;
|
||||
lastKnownFileType = text.html;
|
||||
path = Credits.html;
|
||||
refType = 4;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
17C1C5CE065D3A3C003526E7 = {
|
||||
fileRef = 17C1C5CD065D3A3C003526E7;
|
||||
isa = PBXBuildFile;
|
||||
settings = {
|
||||
};
|
||||
};
|
||||
17C1C6E2065D458D003526E7 = {
|
||||
fileEncoding = 4;
|
||||
isa = PBXFileReference;
|
||||
lastKnownFileType = text.script.sh;
|
||||
path = MakeDMG.sh;
|
||||
refType = 4;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
17F6C11106629574007E0BD0 = {
|
||||
isa = PBXFileReference;
|
||||
lastKnownFileType = "compiled.mach-o.executable";
|
||||
name = ruby;
|
||||
path = /usr/local/bin/ruby;
|
||||
refType = 0;
|
||||
sourceTree = "<absolute>";
|
||||
};
|
||||
17F6C11206629574007E0BD0 = {
|
||||
fileRef = 17F6C11106629574007E0BD0;
|
||||
isa = PBXBuildFile;
|
||||
settings = {
|
||||
};
|
||||
};
|
||||
17F6C113066295D0007E0BD0 = {
|
||||
isa = PBXFileReference;
|
||||
lastKnownFileType = folder;
|
||||
name = ruby;
|
||||
path = /usr/local/lib/ruby;
|
||||
refType = 0;
|
||||
sourceTree = "<absolute>";
|
||||
};
|
||||
17F6C3A90662960F007E0BD0 = {
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = lib;
|
||||
dstSubfolderSpec = 7;
|
||||
files = (
|
||||
17F6C3CF066296B5007E0BD0,
|
||||
);
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
17F6C3CF066296B5007E0BD0 = {
|
||||
fileRef = 17F6C113066295D0007E0BD0;
|
||||
isa = PBXBuildFile;
|
||||
settings = {
|
||||
};
|
||||
};
|
||||
17F6C3D2066296E4007E0BD0 = {
|
||||
children = (
|
||||
17F6C11106629574007E0BD0,
|
||||
17F6C113066295D0007E0BD0,
|
||||
);
|
||||
isa = PBXGroup;
|
||||
name = "Ruby 1.8";
|
||||
refType = 4;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
//170
|
||||
//171
|
||||
//172
|
||||
//173
|
||||
//174
|
||||
//190
|
||||
//191
|
||||
//192
|
||||
//193
|
||||
//194
|
||||
19C28FACFE9D520D11CA2CBB = {
|
||||
children = (
|
||||
8D1107320486CEB800E47090,
|
||||
);
|
||||
isa = PBXGroup;
|
||||
name = Products;
|
||||
refType = 4;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
//190
|
||||
//191
|
||||
//192
|
||||
//193
|
||||
//194
|
||||
//290
|
||||
//291
|
||||
//292
|
||||
//293
|
||||
//294
|
||||
29B97313FDCFA39411CA2CEA = {
|
||||
buildSettings = {
|
||||
};
|
||||
buildStyles = (
|
||||
4A9504CCFFE6A4B311CA0CBA,
|
||||
4A9504CDFFE6A4B311CA0CBA,
|
||||
);
|
||||
hasScannedForEncodings = 1;
|
||||
isa = PBXProject;
|
||||
mainGroup = 29B97314FDCFA39411CA2CEA;
|
||||
projectDirPath = "";
|
||||
targets = (
|
||||
8D1107260486CEB800E47090,
|
||||
);
|
||||
};
|
||||
29B97314FDCFA39411CA2CEA = {
|
||||
children = (
|
||||
080E96DDFE201D6D7F000001,
|
||||
29B97315FDCFA39411CA2CEA,
|
||||
29B97317FDCFA39411CA2CEA,
|
||||
29B97323FDCFA39411CA2CEA,
|
||||
19C28FACFE9D520D11CA2CBB,
|
||||
17C1C6E2065D458D003526E7,
|
||||
);
|
||||
isa = PBXGroup;
|
||||
name = Instiki;
|
||||
path = "";
|
||||
refType = 4;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
29B97315FDCFA39411CA2CEA = {
|
||||
children = (
|
||||
32CA4F630368D1EE00C91783,
|
||||
29B97316FDCFA39411CA2CEA,
|
||||
);
|
||||
isa = PBXGroup;
|
||||
name = "Other Sources";
|
||||
path = "";
|
||||
refType = 4;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
29B97316FDCFA39411CA2CEA = {
|
||||
fileEncoding = 30;
|
||||
isa = PBXFileReference;
|
||||
lastKnownFileType = sourcecode.cpp.objcpp;
|
||||
path = main.mm;
|
||||
refType = 4;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
29B97317FDCFA39411CA2CEA = {
|
||||
children = (
|
||||
17BF6FD9067536EB003F37D6,
|
||||
17F6C3D2066296E4007E0BD0,
|
||||
8D1107310486CEB800E47090,
|
||||
089C165CFE840E0CC02AAC07,
|
||||
29B97318FDCFA39411CA2CEA,
|
||||
17C1C5CD065D3A3C003526E7,
|
||||
);
|
||||
isa = PBXGroup;
|
||||
name = Resources;
|
||||
path = "";
|
||||
refType = 4;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
29B97318FDCFA39411CA2CEA = {
|
||||
children = (
|
||||
29B97319FDCFA39411CA2CEA,
|
||||
);
|
||||
isa = PBXVariantGroup;
|
||||
name = MainMenu.nib;
|
||||
path = "";
|
||||
refType = 4;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
29B97319FDCFA39411CA2CEA = {
|
||||
isa = PBXFileReference;
|
||||
lastKnownFileType = wrapper.nib;
|
||||
name = English;
|
||||
path = English.lproj/MainMenu.nib;
|
||||
refType = 4;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
29B97323FDCFA39411CA2CEA = {
|
||||
children = (
|
||||
1058C7A0FEA54F0111CA2CBB,
|
||||
1058C7A2FEA54F0111CA2CBB,
|
||||
);
|
||||
isa = PBXGroup;
|
||||
name = Frameworks;
|
||||
path = "";
|
||||
refType = 4;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
29B97324FDCFA39411CA2CEA = {
|
||||
fallbackIsa = PBXFileReference;
|
||||
isa = PBXFrameworkReference;
|
||||
lastKnownFileType = wrapper.framework;
|
||||
name = AppKit.framework;
|
||||
path = /System/Library/Frameworks/AppKit.framework;
|
||||
refType = 0;
|
||||
sourceTree = "<absolute>";
|
||||
};
|
||||
29B97325FDCFA39411CA2CEA = {
|
||||
fallbackIsa = PBXFileReference;
|
||||
isa = PBXFrameworkReference;
|
||||
lastKnownFileType = wrapper.framework;
|
||||
name = Foundation.framework;
|
||||
path = /System/Library/Frameworks/Foundation.framework;
|
||||
refType = 0;
|
||||
sourceTree = "<absolute>";
|
||||
};
|
||||
//290
|
||||
//291
|
||||
//292
|
||||
//293
|
||||
//294
|
||||
//320
|
||||
//321
|
||||
//322
|
||||
//323
|
||||
//324
|
||||
32CA4F630368D1EE00C91783 = {
|
||||
fileEncoding = 4;
|
||||
isa = PBXFileReference;
|
||||
lastKnownFileType = sourcecode.c.h;
|
||||
path = Instiki_Prefix.pch;
|
||||
refType = 4;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
//320
|
||||
//321
|
||||
//322
|
||||
//323
|
||||
//324
|
||||
//4A0
|
||||
//4A1
|
||||
//4A2
|
||||
//4A3
|
||||
//4A4
|
||||
4A9504CCFFE6A4B311CA0CBA = {
|
||||
buildRules = (
|
||||
);
|
||||
buildSettings = {
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUGGING_SYMBOLS = YES;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_ENABLE_FIX_AND_CONTINUE = YES;
|
||||
GCC_GENERATE_DEBUGGING_SYMBOLS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
OPTIMIZATION_CFLAGS = "-O0";
|
||||
ZERO_LINK = YES;
|
||||
};
|
||||
isa = PBXBuildStyle;
|
||||
name = Development;
|
||||
};
|
||||
4A9504CDFFE6A4B311CA0CBA = {
|
||||
buildRules = (
|
||||
);
|
||||
buildSettings = {
|
||||
COPY_PHASE_STRIP = YES;
|
||||
GCC_ENABLE_FIX_AND_CONTINUE = NO;
|
||||
ZERO_LINK = NO;
|
||||
};
|
||||
isa = PBXBuildStyle;
|
||||
name = Deployment;
|
||||
};
|
||||
//4A0
|
||||
//4A1
|
||||
//4A2
|
||||
//4A3
|
||||
//4A4
|
||||
//630
|
||||
//631
|
||||
//632
|
||||
//633
|
||||
//634
|
||||
63B86D0F0673A53100807E13 = {
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = rb_src;
|
||||
dstSubfolderSpec = 7;
|
||||
files = (
|
||||
63B86D310673A5D600807E13,
|
||||
63B86D1C0673A5B600807E13,
|
||||
63B86D120673A59100807E13,
|
||||
);
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
63B86D100673A58400807E13 = {
|
||||
explicitFileType = folder;
|
||||
fileEncoding = 4;
|
||||
isa = PBXFileReference;
|
||||
name = app;
|
||||
path = /Users/duff/Source/rb_src/instiki/app;
|
||||
refType = 0;
|
||||
sourceTree = "<absolute>";
|
||||
};
|
||||
63B86D120673A59100807E13 = {
|
||||
fileRef = 63B86D100673A58400807E13;
|
||||
isa = PBXBuildFile;
|
||||
settings = {
|
||||
};
|
||||
};
|
||||
63B86D1A0673A5B200807E13 = {
|
||||
explicitFileType = folder;
|
||||
fileEncoding = 4;
|
||||
isa = PBXFileReference;
|
||||
name = libraries;
|
||||
path = /Users/duff/Source/rb_src/instiki/libraries;
|
||||
refType = 0;
|
||||
sourceTree = "<absolute>";
|
||||
};
|
||||
63B86D1C0673A5B600807E13 = {
|
||||
fileRef = 63B86D1A0673A5B200807E13;
|
||||
isa = PBXBuildFile;
|
||||
settings = {
|
||||
};
|
||||
};
|
||||
63B86D2F0673A5D300807E13 = {
|
||||
fileEncoding = 4;
|
||||
isa = PBXFileReference;
|
||||
lastKnownFileType = text.script.ruby;
|
||||
name = instiki.rb;
|
||||
path = /Users/duff/Source/rb_src/instiki/instiki.rb;
|
||||
refType = 0;
|
||||
sourceTree = "<absolute>";
|
||||
};
|
||||
63B86D310673A5D600807E13 = {
|
||||
fileRef = 63B86D2F0673A5D300807E13;
|
||||
isa = PBXBuildFile;
|
||||
settings = {
|
||||
};
|
||||
};
|
||||
//630
|
||||
//631
|
||||
//632
|
||||
//633
|
||||
//634
|
||||
//8D0
|
||||
//8D1
|
||||
//8D2
|
||||
//8D3
|
||||
//8D4
|
||||
8D1107260486CEB800E47090 = {
|
||||
buildPhases = (
|
||||
8D1107270486CEB800E47090,
|
||||
8D1107290486CEB800E47090,
|
||||
8D11072C0486CEB800E47090,
|
||||
8D11072E0486CEB800E47090,
|
||||
17F6C3A90662960F007E0BD0,
|
||||
63B86D0F0673A53100807E13,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
buildSettings = {
|
||||
FRAMEWORK_SEARCH_PATHS = "";
|
||||
GCC_ENABLE_TRIGRAPHS = NO;
|
||||
GCC_GENERATE_DEBUGGING_SYMBOLS = NO;
|
||||
GCC_PRECOMPILE_PREFIX_HEADER = YES;
|
||||
GCC_PREFIX_HEADER = Instiki_Prefix.pch;
|
||||
GCC_WARN_ABOUT_MISSING_PROTOTYPES = NO;
|
||||
GCC_WARN_FOUR_CHARACTER_CONSTANTS = NO;
|
||||
GCC_WARN_UNKNOWN_PRAGMAS = NO;
|
||||
HEADER_SEARCH_PATHS = "";
|
||||
INFOPLIST_FILE = Info.plist;
|
||||
INSTALL_PATH = "$(HOME)/Applications";
|
||||
LIBRARY_SEARCH_PATHS = "";
|
||||
OTHER_CFLAGS = "";
|
||||
OTHER_LDFLAGS = "";
|
||||
PRODUCT_NAME = Instiki;
|
||||
SECTORDER_FLAGS = "";
|
||||
WARNING_CFLAGS = "-Wmost -Wno-four-char-constants -Wno-unknown-pragmas";
|
||||
WRAPPER_EXTENSION = app;
|
||||
};
|
||||
dependencies = (
|
||||
);
|
||||
isa = PBXNativeTarget;
|
||||
name = Instiki;
|
||||
productInstallPath = "$(HOME)/Applications";
|
||||
productName = Instiki;
|
||||
productReference = 8D1107320486CEB800E47090;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
8D1107270486CEB800E47090 = {
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
8D1107280486CEB800E47090,
|
||||
174B2768065CE31400ED6208,
|
||||
);
|
||||
isa = PBXHeadersBuildPhase;
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
8D1107280486CEB800E47090 = {
|
||||
fileRef = 32CA4F630368D1EE00C91783;
|
||||
isa = PBXBuildFile;
|
||||
settings = {
|
||||
};
|
||||
};
|
||||
8D1107290486CEB800E47090 = {
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
8D11072A0486CEB800E47090,
|
||||
8D11072B0486CEB800E47090,
|
||||
17C1C5CE065D3A3C003526E7,
|
||||
17F6C11206629574007E0BD0,
|
||||
);
|
||||
isa = PBXResourcesBuildPhase;
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
8D11072A0486CEB800E47090 = {
|
||||
fileRef = 29B97318FDCFA39411CA2CEA;
|
||||
isa = PBXBuildFile;
|
||||
settings = {
|
||||
};
|
||||
};
|
||||
8D11072B0486CEB800E47090 = {
|
||||
fileRef = 089C165CFE840E0CC02AAC07;
|
||||
isa = PBXBuildFile;
|
||||
settings = {
|
||||
};
|
||||
};
|
||||
8D11072C0486CEB800E47090 = {
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
8D11072D0486CEB800E47090,
|
||||
174B2767065CE31400ED6208,
|
||||
);
|
||||
isa = PBXSourcesBuildPhase;
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
8D11072D0486CEB800E47090 = {
|
||||
fileRef = 29B97316FDCFA39411CA2CEA;
|
||||
isa = PBXBuildFile;
|
||||
settings = {
|
||||
ATTRIBUTES = (
|
||||
);
|
||||
};
|
||||
};
|
||||
8D11072E0486CEB800E47090 = {
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
8D11072F0486CEB800E47090,
|
||||
);
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
8D11072F0486CEB800E47090 = {
|
||||
fileRef = 1058C7A1FEA54F0111CA2CBB;
|
||||
isa = PBXBuildFile;
|
||||
settings = {
|
||||
};
|
||||
};
|
||||
8D1107310486CEB800E47090 = {
|
||||
fileEncoding = 4;
|
||||
isa = PBXFileReference;
|
||||
lastKnownFileType = text.plist;
|
||||
path = Info.plist;
|
||||
refType = 4;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
8D1107320486CEB800E47090 = {
|
||||
explicitFileType = wrapper.application;
|
||||
includeInIndex = 0;
|
||||
isa = PBXFileReference;
|
||||
path = Instiki.app;
|
||||
refType = 3;
|
||||
sourceTree = BUILT_PRODUCTS_DIR;
|
||||
};
|
||||
};
|
||||
rootObject = 29B97313FDCFA39411CA2CEA;
|
||||
}
|
7
natives/osx/desktop_launcher/Instiki_Prefix.pch
Executable file
7
natives/osx/desktop_launcher/Instiki_Prefix.pch
Executable file
|
@ -0,0 +1,7 @@
|
|||
//
|
||||
// Prefix header for all source files of the 'Instiki' target in the 'Instiki' project
|
||||
//
|
||||
|
||||
#ifdef __OBJC__
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#endif
|
9
natives/osx/desktop_launcher/MakeDMG.sh
Executable file
9
natives/osx/desktop_launcher/MakeDMG.sh
Executable file
|
@ -0,0 +1,9 @@
|
|||
#!/bin/sh
|
||||
|
||||
hdiutil create -size 12m -fs HFS+ -volname Instiki -ov /tmp/Instiki_12MB.dmg
|
||||
hdiutil mount /tmp/Instiki_12MB.dmg
|
||||
# strip ~/ruby/instiki/natives/osx/build/Instiki.app/Contents/MacOS/Instiki
|
||||
ditto ~/ruby/instiki/natives/osx/desktop_launcher/build/Instiki.app /Volumes/Instiki/Instiki.app
|
||||
hdiutil unmount /Volumes/Instiki
|
||||
hdiutil convert -format UDZO -o /tmp/Instiki.dmg /tmp/Instiki_12MB.dmg
|
||||
hdiutil internet-enable -yes /tmp/Instiki.dmg
|
14
natives/osx/desktop_launcher/main.mm
Executable file
14
natives/osx/desktop_launcher/main.mm
Executable file
|
@ -0,0 +1,14 @@
|
|||
//
|
||||
// main.mm
|
||||
// Instiki
|
||||
//
|
||||
// Created by Allan Odgaard on Thu May 20 2004.
|
||||
// Copyright (c) 2004 MacroMates. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
int main (int argc, char const* argv[])
|
||||
{
|
||||
return NSApplicationMain(argc, argv);
|
||||
}
|
16
natives/osx/desktop_launcher/version.plist
Executable file
16
natives/osx/desktop_launcher/version.plist
Executable file
|
@ -0,0 +1,16 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>BuildVersion</key>
|
||||
<string>17</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.1</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>0.1</string>
|
||||
<key>ProjectName</key>
|
||||
<string>NibPBTemplates</string>
|
||||
<key>SourceVersion</key>
|
||||
<string>1150000</string>
|
||||
</dict>
|
||||
</plist>
|
29
public/.htaccess
Normal file
29
public/.htaccess
Normal file
|
@ -0,0 +1,29 @@
|
|||
# General Apache options
|
||||
AddHandler fastcgi-script .fcgi
|
||||
AddHandler cgi-script .cgi
|
||||
Options +FollowSymLinks +ExecCGI
|
||||
|
||||
# Make sure that mod_ruby.c has been added and loaded as a module with Apache
|
||||
RewriteEngine On
|
||||
|
||||
# Change extension from .cgi to .fcgi to switch to FCGI and to .rb to switch to mod_ruby
|
||||
RewriteBase /dispatch.cgi
|
||||
|
||||
# Enable this rewrite rule to point to the controller/action that should serve root.
|
||||
# RewriteRule ^$ /wiki/ [R]
|
||||
|
||||
# Add missing slash
|
||||
RewriteRule ^([-_a-zA-Z0-9]+)$ /$1/ [R]
|
||||
|
||||
# Default rewriting rules.
|
||||
RewriteRule ^([-_a-zA-Z0-9]+)/([-_a-zA-Z0-9]+)/([0-9]+)$ ?controller=$1&action=$2&id=$3 [QSA,L]
|
||||
RewriteRule ^([-_a-zA-Z0-9]+)/([-_a-zA-Z0-9]+)$ ?controller=$1&action=$2 [QSA,L]
|
||||
RewriteRule ^([-_a-zA-Z0-9]+)/$ ?controller=$1&action=index [QSA,L]
|
||||
|
||||
RewriteRule ^([-_a-zA-Z0-9]+)/([-_a-zA-Z0-9]+)/([-_a-zA-Z0-9]+)/([0-9]+)$ ?module=$1&controller=$2&action=$3&id=$4 [QSA,L]
|
||||
RewriteRule ^([-_a-zA-Z0-9]+)/([-_a-zA-Z0-9]+)/([-_a-zA-Z0-9]+)$ ?module=$1&controller=$2&action=$3 [QSA,L]
|
||||
RewriteRule ^([-_a-zA-Z0-9]+)/([-_a-zA-Z0-9]+)/$ ?module=$1&controller=$2&action=index [QSA,L]
|
||||
|
||||
# You can also point these error messages to a controller/action
|
||||
ErrorDocument 500 /500.html
|
||||
ErrorDocument 404 /404.html
|
6
public/404.html
Normal file
6
public/404.html
Normal file
|
@ -0,0 +1,6 @@
|
|||
<html>
|
||||
<body>
|
||||
<h1>File not found</h1>
|
||||
<p>HTTP 404</p>
|
||||
</body>
|
||||
</html>
|
6
public/500.html
Normal file
6
public/500.html
Normal file
|
@ -0,0 +1,6 @@
|
|||
<html>
|
||||
<body>
|
||||
<h1>Application error</h1>
|
||||
<p>HTTP 500</p>
|
||||
</body>
|
||||
</html>
|
11
public/dispatch.rb
Executable file
11
public/dispatch.rb
Executable file
|
@ -0,0 +1,11 @@
|
|||
#!/usr/bin/ruby
|
||||
|
||||
require File.dirname(__FILE__) + '/../config/environment' unless defined?(RAILS_ROOT)
|
||||
|
||||
# If you're using RubyGems and mod_ruby, this require should be changed to an absolute path one,
|
||||
# like: "/usr/local/lib/ruby/gems/1.8/gems/rails-0.8.0/lib/dispatcher"
|
||||
# -- otherwise performance is severely impaired
|
||||
require 'dispatcher'
|
||||
|
||||
ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) }
|
||||
Dispatcher.dispatch
|
0
public/images/.images_go_here
Executable file
0
public/images/.images_go_here
Executable file
0
public/javascripts/.java_script_files_go_here
Executable file
0
public/javascripts/.java_script_files_go_here
Executable file
0
public/stylesheets/.CSS_stylesheets_go_here
Executable file
0
public/stylesheets/.CSS_stylesheets_go_here
Executable file
199
public/stylesheets/instiki.css
Executable file
199
public/stylesheets/instiki.css
Executable file
|
@ -0,0 +1,199 @@
|
|||
#Container {
|
||||
float: none;
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#Content {
|
||||
margin: 0;
|
||||
padding: 5px;
|
||||
text-align: left;
|
||||
border-top: none;
|
||||
float: left;
|
||||
}
|
||||
|
||||
body { background-color: #fff; color: #333; }
|
||||
|
||||
body, p, ol, ul, td {
|
||||
font-family: verdana, arial, helvetica, sans-serif;
|
||||
font-size: 13px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
a { color: #000; }
|
||||
|
||||
.newWikiWord { background-color: #eee; }
|
||||
.newWikiWord a:hover { background-color: white; }
|
||||
|
||||
a:visited { color: #666; }
|
||||
a:hover { color: #fff; background-color:#000; }
|
||||
|
||||
/* a.edit:link, a.edit:visited { color: #DA0006; } */
|
||||
|
||||
|
||||
h1, h2, h3 { color: #333; font-family: georgia, verdana; }
|
||||
h1 { font-size: 28px }
|
||||
h2 { font-size: 19px }
|
||||
h3 { font-size: 16px }
|
||||
|
||||
h1#pageName {
|
||||
margin: 5px 0px 0px 0px;
|
||||
padding: 0px 0px 0px 0px;
|
||||
line-height: 28px;
|
||||
}
|
||||
|
||||
h1#pageName small {
|
||||
color: grey;
|
||||
line-height: 10px;
|
||||
font-size: 10px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
a.nav, a.nav:link, a.nav:visited { color: #000; }
|
||||
a.nav:hover { color: #fff; background-color:#000; }
|
||||
|
||||
li { margin-bottom: 7px }
|
||||
|
||||
.navigation {
|
||||
margin-top: 5px;
|
||||
font-size : 12px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.navigation a:hover { color: #fff; background-color:#000; }
|
||||
|
||||
.navigation a {
|
||||
font-size: 11px;
|
||||
color: black;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.navigation small a {
|
||||
font-weight: normal;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.navOn{
|
||||
font-size: 11px;
|
||||
color: grey;
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.help {
|
||||
font-family: verdana, arial, helvetica, sans-serif;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.inputBox {
|
||||
font-family: verdana, arial, helvetica, sans-serif;
|
||||
font-size: 11px;
|
||||
background-color: #eee;
|
||||
padding: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
display: block;
|
||||
margin: 0px 0px 20px 0px;
|
||||
padding: 0px 30px;
|
||||
font-size:11px;
|
||||
line-height:17px;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
pre {
|
||||
background-color: #eee;
|
||||
padding: 10px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
ol.setup {
|
||||
font-size: 19px;
|
||||
font-family: georgia, verdana;
|
||||
padding-left: 25px;
|
||||
}
|
||||
|
||||
ol.setup li {
|
||||
margin-bottom: 20px
|
||||
}
|
||||
|
||||
.byline {
|
||||
font-size: 10px;
|
||||
font-style: italic;
|
||||
margin-bottom: 10px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.references {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.diffdel {
|
||||
background: pink;
|
||||
}
|
||||
|
||||
.diffins {
|
||||
background: lightgreen;
|
||||
}
|
||||
|
||||
#TextileHelp table {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
#TextileHelp table+h3 {
|
||||
margin-top: 11px;
|
||||
}
|
||||
|
||||
#TextileHelp table td {
|
||||
font-size: 11px;
|
||||
padding: 3px;
|
||||
vertical-align: top;
|
||||
border-top: 1px dotted #ccc;
|
||||
}
|
||||
|
||||
#TextileHelp table td.arrow {
|
||||
padding-right: 5px;
|
||||
padding-left: 10px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
#TextileHelp table td.label {
|
||||
font-weight: bold;
|
||||
white-space: nowrap;
|
||||
font-size: 10px;
|
||||
padding-right: 15px;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
#TextileHelp h3 {
|
||||
font-size: 11px;
|
||||
font-weight: bold;
|
||||
font-weight: normal;
|
||||
margin: 0 0 5px 0;
|
||||
padding: 5px 0 0 0;
|
||||
}
|
||||
|
||||
#TextileHelp p {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.rightHandSide {
|
||||
float: right;
|
||||
width: 147px;
|
||||
margin-left: 10px;
|
||||
padding-left: 20px;
|
||||
border-left: 1px dotted #ccc;
|
||||
}
|
||||
|
||||
.rightHandSide p {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.newsList {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.newsList p {
|
||||
margin-bottom:30px
|
||||
}
|
79
rakefile.rb
Executable file
79
rakefile.rb
Executable file
|
@ -0,0 +1,79 @@
|
|||
begin
|
||||
require 'rubygems'
|
||||
require 'rake/gempackagetask'
|
||||
rescue Exception
|
||||
nil
|
||||
end
|
||||
|
||||
ENV['RAILS_ENV'] = 'test'
|
||||
require 'config/environment'
|
||||
|
||||
require 'rake'
|
||||
require 'rake/clean'
|
||||
require 'rake/testtask'
|
||||
require 'rake/rdoctask'
|
||||
require 'rake/packagetask'
|
||||
require 'rake/contrib/rubyforgepublisher'
|
||||
require 'code_statistics'
|
||||
|
||||
desc 'Default Task'
|
||||
task :default => :test
|
||||
|
||||
CLEAN << 'pkg' << 'storage/2500' << 'doc' << 'html'
|
||||
|
||||
# Run the unit tests
|
||||
Rake::TestTask.new { |t|
|
||||
t.libs << 'libraries'
|
||||
t.libs << 'app/models'
|
||||
t.libs << 'vendor/bluecloth-1.0.0/lib'
|
||||
t.libs << 'vendor/madeleine-0.7.1/lib'
|
||||
t.libs << 'vendor/redcloth-2.0.11/lib'
|
||||
t.libs << 'vendor/rubyzip-0.5.6'
|
||||
t.pattern = 'test/**/*_test.rb'
|
||||
t.verbose = true
|
||||
}
|
||||
|
||||
if defined? GemPackageTask
|
||||
gemspec = eval(File.read('instiki.gemspec'))
|
||||
Rake::GemPackageTask.new(gemspec) do |p|
|
||||
p.gem_spec = gemspec
|
||||
p.need_tar = true
|
||||
p.need_zip = true
|
||||
end
|
||||
|
||||
# PKG_VERSION is defined in instiki.gemspec
|
||||
Rake::PackageTask.new("instiki", gemspec.version) do |p|
|
||||
p.need_tar = true
|
||||
p.need_zip = true
|
||||
# the list of glob expressions for files comes from instiki.gemspec
|
||||
p.package_files.include($__instiki_source_patterns)
|
||||
end
|
||||
|
||||
# Create a task to build the RDOC documentation tree.
|
||||
rd = Rake::RDocTask.new("rdoc") { |rdoc|
|
||||
rdoc.rdoc_dir = 'html'
|
||||
rdoc.title = 'Instiki -- The Wiki'
|
||||
rdoc.options << '--line-numbers --inline-source --main README'
|
||||
rdoc.rdoc_files.include(gemspec.files)
|
||||
rdoc.main = 'README'
|
||||
}
|
||||
else
|
||||
puts "Warning: without Rubygems packaging tasks are not available"
|
||||
end
|
||||
|
||||
desc "Publish RDOC to RubyForge"
|
||||
task :rubyforge => [:rdoc, :package] do
|
||||
Rake::RubyForgePublisher.new('instiki', 'alexeyv').upload
|
||||
end
|
||||
|
||||
desc "Report code statistics (KLOCs, etc)"
|
||||
task :stats do
|
||||
CodeStatistics.new(
|
||||
["Helpers", "app/helpers"],
|
||||
["Controllers", "app/controllers"],
|
||||
["Functionals", "test/functional"],
|
||||
["Models", "app/models"],
|
||||
["Units", "test/unit"],
|
||||
["Libraries", "libraries"]
|
||||
).to_s
|
||||
end
|
35
script/breakpointer
Executable file
35
script/breakpointer
Executable file
|
@ -0,0 +1,35 @@
|
|||
#!/usr/local/bin/ruby
|
||||
|
||||
RAILS_ROOT = File.expand_path(File.dirname(__FILE__) + '/..')
|
||||
|
||||
# Model subdirectories.
|
||||
ADDITIONAL_LOAD_PATHS = Dir["#{RAILS_ROOT}/app/models/[_a-z]*"]
|
||||
|
||||
# Followed by the standard includes.
|
||||
ADDITIONAL_LOAD_PATHS.concat %w(
|
||||
app
|
||||
app/models
|
||||
app/controllers
|
||||
app/helpers
|
||||
config
|
||||
libraries
|
||||
).map { |dir| "#{File.expand_path(File.join(RAILS_ROOT, dir))}" }
|
||||
|
||||
ADDITIONAL_LOAD_PATHS.concat %w(
|
||||
vendor/bluecloth-1.0.0/lib
|
||||
vendor/madeleine-0.7.1/lib
|
||||
vendor/redcloth-2.0.11/lib
|
||||
vendor/rubyzip-0.5.6
|
||||
vendor/actionpack/lib
|
||||
vendor/activesupport/lib
|
||||
vendor/railties/lib
|
||||
).map { |dir|
|
||||
"#{File.expand_path(File.join(RAILS_ROOT, dir))}"
|
||||
}.delete_if { |dir|
|
||||
puts dir
|
||||
not File.exist?(dir) }
|
||||
|
||||
# Prepend to $LOAD_PATH
|
||||
ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) }
|
||||
|
||||
require 'breakpoint_client'
|
83
script/server
Executable file
83
script/server
Executable file
|
@ -0,0 +1,83 @@
|
|||
#!/usr/bin/ruby
|
||||
|
||||
require 'webrick'
|
||||
require 'optparse'
|
||||
require 'fileutils'
|
||||
|
||||
pwd = File.expand_path(File.dirname(__FILE__) + "/..")
|
||||
|
||||
OPTIONS = {
|
||||
|
||||
# Overridable options
|
||||
:port => 2500,
|
||||
:ip => '127.0.0.1',
|
||||
:environment => 'production',
|
||||
:server_root => File.expand_path(File.dirname(__FILE__) + '/../public/'),
|
||||
:server_type => WEBrick::SimpleServer,
|
||||
:storage => "#{File.expand_path(FileUtils.pwd)}/storage",
|
||||
}
|
||||
|
||||
ARGV.options do |opts|
|
||||
script_name = File.basename($0)
|
||||
opts.banner = "Usage: ruby #{script_name} [options]"
|
||||
|
||||
opts.separator ''
|
||||
|
||||
opts.on('-p', '--port=port', Integer,
|
||||
'Runs Instiki on the specified port.',
|
||||
'Default: 2500') { |OPTIONS[:port]| }
|
||||
opts.on('-b', '--binding=ip', String,
|
||||
'Binds Rails to the specified ip.',
|
||||
'Default: 127.0.0.1') { |OPTIONS[:ip]| }
|
||||
opts.on('-i', '--index=controller', String,
|
||||
'Specifies an index controller that requests for root will go to (instead of congratulations screen).'
|
||||
) { |OPTIONS[:index_controller]| }
|
||||
opts.on('-e', '--environment=name', String,
|
||||
'Specifies the environment to run this server under (test/development/production).',
|
||||
'Default: production') { |OPTIONS[:environment]| }
|
||||
opts.on('-d', '--daemon',
|
||||
'Make Rails run as a Daemon (only works if fork is available -- meaning on *nix).'
|
||||
) { OPTIONS[:server_type] = WEBrick::Daemon }
|
||||
opts.on('-s', '--simple', '--simple-server',
|
||||
'[deprecated] Forces Instiki not to run as a Daemon if fork is available.',
|
||||
'Since version 0.10.0 this option is ignored.'
|
||||
) { puts "Warning: -s (--simple) option is deprecated. See instiki --help for details." }
|
||||
opts.on('-t', '--storage=storage', String,
|
||||
'Makes Instiki use the specified directory for storage.',
|
||||
'Default: ./storage/[port]') { |OPTIONS[:storage]| }
|
||||
opts.on('-v', '--verbose',
|
||||
'Enable debug-level logging'
|
||||
) { OPTIONS[:verbose] = true }
|
||||
|
||||
opts.separator ''
|
||||
|
||||
opts.on('-h', '--help',
|
||||
'Show this help message.') { puts opts; exit }
|
||||
|
||||
opts.parse!
|
||||
end
|
||||
|
||||
ENV['RAILS_ENV'] = OPTIONS[:environment]
|
||||
require File.expand_path(File.dirname(__FILE__) + '/../config/environment')
|
||||
|
||||
if OPTIONS[:verbose]
|
||||
ActionController::Base.logger.level = Logger::DEBUG
|
||||
end
|
||||
|
||||
OPTIONS[:index_controller] = 'wiki'
|
||||
require 'webrick_server'
|
||||
|
||||
if OPTIONS[:environment] == 'production'
|
||||
storage_path = OPTIONS[:storage] + "/" + OPTIONS[:port].to_s
|
||||
else
|
||||
storage_path = OPTIONS[:storage] + "/" + OPTIONS[:environment] + "/" + OPTIONS[:port].to_s
|
||||
end
|
||||
FileUtils.mkdir_p(storage_path)
|
||||
|
||||
puts "=> Starting Instiki on http://#{OPTIONS[:ip]}:#{OPTIONS[:port]}"
|
||||
puts "=> Data files are stored in #{storage_path}"
|
||||
|
||||
require 'application'
|
||||
WikiService.storage_path = storage_path
|
||||
ApplicationController.wiki = WikiService.instance
|
||||
DispatchServlet.dispatch(OPTIONS)
|
5
storage/.cvsignore
Executable file
5
storage/.cvsignore
Executable file
|
@ -0,0 +1,5 @@
|
|||
2500
|
||||
development
|
||||
test
|
||||
.cvsignore
|
||||
*.zip
|
0
storage/madeleine_snaps_goes_here
Executable file
0
storage/madeleine_snaps_goes_here
Executable file
587
test/functional/wiki_controller_test.rb
Executable file
587
test/functional/wiki_controller_test.rb
Executable file
|
@ -0,0 +1,587 @@
|
|||
#!/bin/env ruby
|
||||
|
||||
require File.dirname(__FILE__) + '/../test_helper'
|
||||
require 'wiki_controller'
|
||||
require 'rexml/document'
|
||||
|
||||
# Raise errors beyond the default web-based presentation
|
||||
class WikiController; def rescue_action(e) logger.error(e); raise e end; end
|
||||
|
||||
class WikiControllerTest < Test::Unit::TestCase
|
||||
|
||||
def setup
|
||||
setup_test_wiki
|
||||
setup_controller_test
|
||||
end
|
||||
|
||||
def tear_down
|
||||
tear_down_wiki
|
||||
end
|
||||
|
||||
|
||||
def test_authenticate
|
||||
@web.password = 'pswd'
|
||||
|
||||
r = process('authenticate', 'web' => 'wiki1', 'password' => 'pswd')
|
||||
assert_redirected_to :web => 'wiki1', :action => 'show', :id => 'HomePage'
|
||||
assert_equal ['pswd'], r.cookies['web_address']
|
||||
end
|
||||
|
||||
def test_authenticate
|
||||
@web.password = 'pswd'
|
||||
|
||||
r = process('authenticate', 'web' => 'wiki1', 'password' => 'wrong password')
|
||||
assert_redirected_to :action => 'login'
|
||||
assert_nil r.cookies['web_address']
|
||||
end
|
||||
|
||||
|
||||
def test_authors
|
||||
setup_wiki_with_three_pages
|
||||
@wiki.write_page('wiki1', 'BreakSortingOrder',
|
||||
"This page breaks the accidentally correct sorting order of authors",
|
||||
Time.now, Author.new('BreakingTheOrder', '127.0.0.2'))
|
||||
|
||||
r = process('authors', 'web' => 'wiki1')
|
||||
|
||||
assert_success
|
||||
assert_equal ['AnAuthor', 'BreakingTheOrder', 'Guest', 'TreeHugger'],
|
||||
r.template_objects['authors']
|
||||
end
|
||||
|
||||
|
||||
def test_cancel_edit
|
||||
setup_wiki_with_three_pages
|
||||
@oak.lock(Time.now, 'Locky')
|
||||
assert @oak.locked?(Time.now)
|
||||
|
||||
r = process('cancel_edit', 'web' => 'wiki1', 'id' => 'Oak')
|
||||
|
||||
assert_redirected_to :action => 'show', :id => 'Oak'
|
||||
assert !@oak.locked?(Time.now)
|
||||
end
|
||||
|
||||
|
||||
def test_create_system
|
||||
ApplicationController.wiki = WikiServiceWithNoPersistence.new
|
||||
assert !@controller.wiki.setup?
|
||||
|
||||
process('create_system', 'password' => 'a_password', 'web_name' => 'My Wiki',
|
||||
'web_address' => 'my_wiki')
|
||||
|
||||
assert_redirected_to :action => 'index'
|
||||
assert @controller.wiki.setup?
|
||||
assert_equal 'a_password', @controller.wiki.system[:password]
|
||||
assert_equal 1, @controller.wiki.webs.size
|
||||
new_web = @controller.wiki.webs['my_wiki']
|
||||
assert_equal 'My Wiki', new_web.name
|
||||
assert_equal 'my_wiki', new_web.address
|
||||
end
|
||||
|
||||
def test_create_system_already_setup
|
||||
wiki_before = @controller.wiki
|
||||
assert @controller.wiki.setup?
|
||||
|
||||
process 'create_system', 'password' => 'a_password', 'web_name' => 'My Wiki',
|
||||
'web_address' => 'my_wiki'
|
||||
|
||||
assert_redirected_to :action => 'index'
|
||||
assert_equal wiki_before, @controller.wiki
|
||||
# and no new wikis shuld be created either
|
||||
assert_equal 1, @controller.wiki.webs.size
|
||||
end
|
||||
|
||||
|
||||
def test_create_web
|
||||
@wiki.system[:password] = 'pswd'
|
||||
|
||||
process 'create_web', 'system_password' => 'pswd', 'name' => 'Wiki Two', 'address' => 'wiki2'
|
||||
|
||||
assert_redirected_to :web => 'wiki2', :action => 'show', :id => 'HomePage'
|
||||
wiki2 = @wiki.webs['wiki2']
|
||||
assert wiki2
|
||||
assert_equal 'Wiki Two', wiki2.name
|
||||
assert_equal 'wiki2', wiki2.address
|
||||
end
|
||||
|
||||
def test_create_web_default_password
|
||||
@wiki.system[:password] = nil
|
||||
|
||||
process 'create_web', 'system_password' => 'instiki', 'name' => 'Wiki Two', 'address' => 'wiki2'
|
||||
|
||||
assert_redirected_to :web => 'wiki2', :action => 'show', :id => 'HomePage'
|
||||
end
|
||||
|
||||
def test_create_web_failed_authentication
|
||||
@wiki.system[:password] = 'pswd'
|
||||
|
||||
process 'create_web', 'system_password' => 'wrong', 'name' => 'Wiki Two', 'address' => 'wiki2'
|
||||
|
||||
assert_redirected_to :web => nil, :action => 'index'
|
||||
assert_nil @wiki.webs['wiki2']
|
||||
end
|
||||
|
||||
|
||||
def test_edit
|
||||
r = process 'edit', 'web' => 'wiki1', 'id' => 'HomePage'
|
||||
assert_success
|
||||
assert_equal @wiki.read_page('wiki1', 'HomePage'), r.template_objects['page']
|
||||
end
|
||||
|
||||
def test_edit_page_locked_page
|
||||
@home.lock(Time.now, 'Locky')
|
||||
process 'edit', 'web' => 'wiki1', 'id' => 'HomePage'
|
||||
assert_redirected_to :action => 'locked'
|
||||
end
|
||||
|
||||
def test_edit_page_break_lock
|
||||
@home.lock(Time.now, 'Locky')
|
||||
process 'edit', 'web' => 'wiki1', 'id' => 'HomePage', 'break_lock' => 'y'
|
||||
assert_success
|
||||
assert @home.locked?(Time.now)
|
||||
end
|
||||
|
||||
def test_edit_unknown_page
|
||||
process 'edit', 'web' => 'wiki1', 'id' => 'UnknownPage', 'break_lock' => 'y'
|
||||
assert_redirected_to :action => 'index'
|
||||
end
|
||||
|
||||
|
||||
def test_export_html
|
||||
setup_wiki_with_three_pages
|
||||
|
||||
r = process 'export_html', 'web' => 'wiki1'
|
||||
|
||||
assert_success
|
||||
assert_equal 'application/zip', r.headers['Content-Type']
|
||||
assert_equal 'attachment', r.headers['Content-Disposition']
|
||||
# TODO assert contents of the output file
|
||||
end
|
||||
|
||||
def test_export_markup
|
||||
r = process 'export_markup', 'web' => 'wiki1'
|
||||
|
||||
assert_success
|
||||
assert_equal 'application/zip', r.headers['Content-Type']
|
||||
assert_equal 'attachment', r.headers['Content-Disposition']
|
||||
# TODO assert contents of the output file
|
||||
end
|
||||
|
||||
|
||||
def test_feeds
|
||||
process('feeds', 'web' => 'wiki1')
|
||||
end
|
||||
|
||||
def test_index
|
||||
process('index')
|
||||
assert_redirected_to :web => 'wiki1', :action => 'show', :id => 'HomePage'
|
||||
end
|
||||
|
||||
def test_index_multiple_webs
|
||||
@wiki.create_web('Test Wiki 2', 'wiki2')
|
||||
process('index')
|
||||
assert_redirected_to :action => 'web_list'
|
||||
end
|
||||
|
||||
def test_index_multiple_webs_web_explicit
|
||||
process('index', 'web' => 'wiki2')
|
||||
assert_redirected_to :web => 'wiki2', :action => 'show', :id => 'HomePage'
|
||||
end
|
||||
|
||||
def test_index_wiki_not_initialized
|
||||
ApplicationController.wiki = WikiServiceWithNoPersistence.new
|
||||
process('index')
|
||||
assert_redirected_to :action => 'new_system'
|
||||
end
|
||||
|
||||
|
||||
def test_list
|
||||
setup_wiki_with_three_pages
|
||||
|
||||
r = process('list', 'web' => 'wiki1')
|
||||
|
||||
assert_equal ['animals', 'trees'], r.template_objects['categories']
|
||||
assert_nil r.template_objects['category']
|
||||
assert_equal ['<a href="?category=animals">animals</a>', '<a href="?category=trees">trees</a>'],
|
||||
r.template_objects['category_links']
|
||||
assert_equal [@elephant, @home, @oak], r.template_objects['pages_in_category']
|
||||
end
|
||||
|
||||
|
||||
def test_locked
|
||||
@home.lock(Time.now, 'Locky')
|
||||
r = process('locked', 'web' => 'wiki1', 'id' => 'HomePage')
|
||||
assert_success
|
||||
assert_equal @home, r.template_objects['page']
|
||||
end
|
||||
|
||||
|
||||
def test_login
|
||||
r = process 'login', 'web' => 'wiki1'
|
||||
assert_success
|
||||
# this action goes straight to the templates
|
||||
end
|
||||
|
||||
|
||||
def test_new
|
||||
r = process('new', 'id' => 'NewPage', 'web' => 'wiki1')
|
||||
assert_success
|
||||
assert_equal 'AnonymousCoward', r.template_objects['author']
|
||||
assert_equal 'NewPage', r.template_objects['page_name']
|
||||
end
|
||||
|
||||
|
||||
def test_new_system
|
||||
ApplicationController.wiki = WikiServiceWithNoPersistence.new
|
||||
process('new_system')
|
||||
assert_success
|
||||
end
|
||||
|
||||
def test_new_system_system_already_initialized
|
||||
assert @wiki.setup?
|
||||
process('new_system')
|
||||
assert_redirected_to :action => 'index'
|
||||
end
|
||||
|
||||
|
||||
def test_new_web
|
||||
@wiki.system['password'] = 'pswd'
|
||||
process 'new_web'
|
||||
assert_success
|
||||
end
|
||||
|
||||
def test_new_web_no_password_set
|
||||
@wiki.system['password'] = nil
|
||||
process 'new_web'
|
||||
assert_redirected_to :action => 'index'
|
||||
end
|
||||
|
||||
|
||||
def test_print
|
||||
process('print', 'web' => 'wiki1', 'id' => 'HomePage')
|
||||
assert_success
|
||||
end
|
||||
|
||||
|
||||
def test_published
|
||||
@web.published = true
|
||||
|
||||
r = process('published', 'web' => 'wiki1', 'id' => 'HomePage')
|
||||
|
||||
assert_success
|
||||
assert_equal @home, r.template_objects['page']
|
||||
end
|
||||
|
||||
|
||||
def test_published_web_not_published
|
||||
@web.published = false
|
||||
|
||||
r = process('published', 'web' => 'wiki1', 'id' => 'HomePage')
|
||||
|
||||
assert_redirected_to :action => 'show', :id => 'HomePage'
|
||||
end
|
||||
|
||||
|
||||
def test_recently_revised
|
||||
r = process('recently_revised', 'web' => 'wiki1')
|
||||
assert_success
|
||||
|
||||
assert_equal [], r.template_objects['categories']
|
||||
assert_nil r.template_objects['category']
|
||||
assert_equal [@home], r.template_objects['pages_in_category']
|
||||
assert_equal 'the web', r.template_objects['set_name']
|
||||
assert_equal [], r.template_objects['category_links']
|
||||
end
|
||||
|
||||
def test_recently_revised_with_categorized_page
|
||||
page2 = @wiki.write_page('wiki1', 'Page2',
|
||||
"Page2 contents.\n" +
|
||||
"category: categorized",
|
||||
Time.now, Author.new('AnotherAuthor', '127.0.0.2'))
|
||||
|
||||
r = process('recently_revised', 'web' => 'wiki1')
|
||||
assert_success
|
||||
|
||||
assert_equal ['categorized'], r.template_objects['categories']
|
||||
# no category is specified in params
|
||||
assert_nil r.template_objects['category']
|
||||
assert_equal [@home, page2], r.template_objects['pages_in_category'],
|
||||
"Pages are not as expected: " +
|
||||
r.template_objects['pages_in_category'].map {|p| p.name}.inspect
|
||||
assert_equal 'the web', r.template_objects['set_name']
|
||||
assert_equal ['<a href="?category=categorized">categorized</a>'],
|
||||
r.template_objects['category_links']
|
||||
end
|
||||
|
||||
def test_recently_revised_with_categorized_page_multiple_categories
|
||||
setup_wiki_with_three_pages
|
||||
|
||||
r = process('recently_revised', 'web' => 'wiki1')
|
||||
assert_success
|
||||
|
||||
assert_equal ['animals', 'trees'], r.template_objects['categories']
|
||||
# no category is specified in params
|
||||
assert_nil r.template_objects['category']
|
||||
assert_equal [@elephant, @home, @oak], r.template_objects['pages_in_category'],
|
||||
"Pages are not as expected: " +
|
||||
r.template_objects['pages_in_category'].map {|p| p.name}.inspect
|
||||
assert_equal 'the web', r.template_objects['set_name']
|
||||
assert_equal ['<a href="?category=animals">animals</a>',
|
||||
'<a href="?category=trees">trees</a>'],
|
||||
r.template_objects['category_links']
|
||||
end
|
||||
|
||||
def test_recently_revised_with_specified_category
|
||||
setup_wiki_with_three_pages
|
||||
|
||||
r = process('recently_revised', 'web' => 'wiki1', 'category' => 'animals')
|
||||
assert_success
|
||||
|
||||
assert_equal ['animals', 'trees'], r.template_objects['categories']
|
||||
# no category is specified in params
|
||||
assert_equal 'animals', r.template_objects['category']
|
||||
assert_equal [@elephant], r.template_objects['pages_in_category']
|
||||
assert_equal "category 'animals'", r.template_objects['set_name']
|
||||
assert_equal ['<span class="selected">animals</span>', '<a href="?category=trees">trees</a>'],
|
||||
r.template_objects['category_links']
|
||||
end
|
||||
|
||||
|
||||
def test_remove_orphaned_pages
|
||||
setup_wiki_with_three_pages
|
||||
@wiki.system[:password] = 'pswd'
|
||||
orhan_page_linking_to_oak = @wiki.write_page('wiki1', 'Pine',
|
||||
"Refers to [[Oak]].\n" +
|
||||
"category: trees",
|
||||
Time.now, Author.new('TreeHugger', '127.0.0.2'))
|
||||
|
||||
r = process('remove_orphaned_pages', 'web' => 'wiki1', 'system_password' => 'pswd')
|
||||
|
||||
assert_redirected_to :action => 'list'
|
||||
assert_equal [@home, @oak], @web.select.sort,
|
||||
"Pages are not as expected: #{@web.select.sort.map {|p| p.name}.inspect}"
|
||||
|
||||
|
||||
# Oak is now orphan, second pass should remove it
|
||||
r = process('remove_orphaned_pages', 'web' => 'wiki1', 'system_password' => 'pswd')
|
||||
assert_redirected_to :action => 'list'
|
||||
assert_equal [@home], @web.select.sort,
|
||||
"Pages are not as expected: #{@web.select.sort.map {|p| p.name}.inspect}"
|
||||
|
||||
# third pass does not destroy HomePage
|
||||
r = process('remove_orphaned_pages', 'web' => 'wiki1', 'system_password' => 'pswd')
|
||||
assert_redirected_to :action => 'list'
|
||||
assert_equal [@home], @web.select.sort,
|
||||
"Pages are not as expected: #{@web.select.sort.map {|p| p.name}.inspect}"
|
||||
end
|
||||
|
||||
|
||||
def test_revision
|
||||
r = process 'revision', 'web' => 'wiki1', 'id' => 'HomePage', 'rev' => '0'
|
||||
|
||||
assert_success
|
||||
assert_equal @home, r.template_objects['page']
|
||||
assert_equal @home.revisions[0], r.template_objects['revision']
|
||||
end
|
||||
|
||||
|
||||
def test_rollback
|
||||
# rollback shows a form where a revision can be edited.
|
||||
# its assigns the same as or revision
|
||||
r = process 'revision', 'web' => 'wiki1', 'id' => 'HomePage', 'rev' => '0'
|
||||
|
||||
assert_success
|
||||
assert_equal @home, r.template_objects['page']
|
||||
assert_equal @home.revisions[0], r.template_objects['revision']
|
||||
end
|
||||
|
||||
|
||||
def test_rss_with_content
|
||||
setup_wiki_with_three_pages
|
||||
|
||||
r = process 'rss_with_content', 'web' => 'wiki1'
|
||||
|
||||
assert_success
|
||||
pages = r.template_objects['pages_by_revision']
|
||||
assert_equal [@home, @oak, @elephant], pages,
|
||||
"Pages are not as expected: #{pages.map {|p| p.name}.inspect}"
|
||||
assert !r.template_objects['hide_description']
|
||||
end
|
||||
|
||||
|
||||
def test_rss_with_headlines
|
||||
setup_wiki_with_three_pages
|
||||
|
||||
@request.host = 'localhost'
|
||||
@request.port = 8080
|
||||
|
||||
r = process 'rss_with_headlines', 'web' => 'wiki1'
|
||||
|
||||
assert_success
|
||||
pages = r.template_objects['pages_by_revision']
|
||||
assert_equal [@home, @oak, @elephant], pages,
|
||||
"Pages are not as expected: #{pages.map {|p| p.name}.inspect}"
|
||||
assert r.template_objects['hide_description']
|
||||
|
||||
xml = REXML::Document.new(r.body)
|
||||
|
||||
expected_page_links =
|
||||
['http://localhost:8080/wiki1/show/HomePage',
|
||||
'http://localhost:8080/wiki1/show/Oak',
|
||||
'http://localhost:8080/wiki1/show/Elephant']
|
||||
|
||||
assert_template_xpath_match '/rss/channel/link',
|
||||
'http://localhost:8080/wiki1/show/HomePage'
|
||||
assert_template_xpath_match '/rss/channel/item/guid', expected_page_links
|
||||
assert_template_xpath_match '/rss/channel/item/link', expected_page_links
|
||||
end
|
||||
|
||||
def test_save
|
||||
r = process 'save', 'web' => 'wiki1', 'id' => 'NewPage', 'content' => 'Contents of a new page',
|
||||
'author' => 'AuthorOfNewPage'
|
||||
|
||||
assert_redirected_to :web => 'wiki1', :action => 'show', :id => 'NewPage'
|
||||
assert_equal ['AuthorOfNewPage'], r.cookies['author'].value
|
||||
new_page = @wiki.read_page('wiki1', 'NewPage')
|
||||
assert_equal 'Contents of a new page', new_page.content
|
||||
assert_equal 'AuthorOfNewPage', new_page.author
|
||||
end
|
||||
|
||||
def test_save_new_revision_of_existing_page
|
||||
@home.lock(Time.now, 'Batman')
|
||||
|
||||
r = process 'save', 'web' => 'wiki1', 'id' => 'HomePage', 'content' => 'Revised HomePage',
|
||||
'author' => 'Batman'
|
||||
|
||||
assert_redirected_to :web => 'wiki1', :action => 'show', :id => 'HomePage'
|
||||
assert_equal ['Batman'], r.cookies['author'].value
|
||||
home_page = @wiki.read_page('wiki1', 'HomePage')
|
||||
assert_equal [home_page], @web.pages.values
|
||||
assert_equal 2, home_page.revisions.size
|
||||
assert_equal 'Revised HomePage', home_page.content
|
||||
assert_equal 'Batman', home_page.author
|
||||
assert !home_page.locked?(Time.now)
|
||||
end
|
||||
|
||||
def test_save_new_revision_of_existing_page
|
||||
@home.lock(Time.now, 'Batman')
|
||||
|
||||
r = process 'save', 'web' => 'wiki1', 'id' => 'HomePage', 'content' => 'Revised HomePage',
|
||||
'author' => 'Batman'
|
||||
|
||||
assert_redirected_to :web => 'wiki1', :action => 'show', :id => 'HomePage'
|
||||
assert_equal ['Batman'], r.cookies['author'].value
|
||||
home_page = @wiki.read_page('wiki1', 'HomePage')
|
||||
assert_equal [home_page], @web.pages.values
|
||||
assert_equal 2, home_page.revisions.size
|
||||
assert_equal 'Revised HomePage', home_page.content
|
||||
assert_equal 'Batman', home_page.author
|
||||
assert !home_page.locked?(Time.now)
|
||||
end
|
||||
|
||||
|
||||
def test_search
|
||||
setup_wiki_with_three_pages
|
||||
process 'search', 'web' => 'wiki1', 'query' => '\s[A-Z]ak'
|
||||
assert_redirected_to :action => 'show', :id => 'Oak'
|
||||
end
|
||||
|
||||
def test_search_multiple_results
|
||||
setup_wiki_with_three_pages
|
||||
|
||||
r = process 'search', 'web' => 'wiki1', 'query' => 'All about'
|
||||
|
||||
assert_success
|
||||
assert_equal 'All about', r.template_objects['query']
|
||||
assert_equal [@elephant, @oak], r.template_objects['results']
|
||||
end
|
||||
|
||||
def test_search_zero_results
|
||||
setup_wiki_with_three_pages
|
||||
|
||||
r = process 'search', 'web' => 'wiki1', 'query' => 'non-existant text'
|
||||
|
||||
assert_success
|
||||
assert_equal [], r.template_objects['results']
|
||||
end
|
||||
|
||||
|
||||
def test_show_page
|
||||
r = process('show', 'id' => 'HomePage', 'web' => 'wiki1')
|
||||
assert_success
|
||||
assert_match /First revision of the <a.*HomePage.*<\/a> end/, r.body
|
||||
end
|
||||
|
||||
def test_show_page_with_multiple_revisions
|
||||
@wiki.write_page('wiki1', 'HomePage', 'Second revision of the HomePage end', Time.now,
|
||||
Author.new('AnotherAuthor', '127.0.0.2'))
|
||||
|
||||
r = process('show', 'id' => 'HomePage', 'web' => 'wiki1')
|
||||
|
||||
assert_success
|
||||
assert_match /Second revision of the <a.*HomePage.*<\/a> end/, r.body
|
||||
end
|
||||
|
||||
def test_show_page_nonexistant_page
|
||||
process('show', 'id' => 'UnknownPage', 'web' => 'wiki1')
|
||||
assert_redirected_to :web => 'wiki1', :action => 'new', :id => 'UnknownPage'
|
||||
end
|
||||
|
||||
|
||||
def test_update_web
|
||||
@wiki.system[:password] = 'pswd'
|
||||
|
||||
process('update_web', 'system_password' => 'pswd',
|
||||
'web' => 'wiki1', 'address' => 'renamed_wiki1', 'name' => 'Renamed Wiki1',
|
||||
'markup' => 'markdown', 'color' => 'blue', 'additional_style' => 'whatever',
|
||||
'safe_mode' => 'y', 'password' => 'new_password', 'published' => 'y',
|
||||
'brackets_only' => 'y', 'count_pages' => 'y')
|
||||
|
||||
assert_redirected_to :web => 'renamed_wiki1', :action => 'show', :id => 'HomePage'
|
||||
assert_equal 'renamed_wiki1', @web.address
|
||||
assert_equal 'Renamed Wiki1', @web.name
|
||||
assert_equal :markdown, @web.markup
|
||||
assert_equal 'blue', @web.color
|
||||
assert @web.safe_mode
|
||||
assert_equal 'new_password', @web.password
|
||||
assert @web.published
|
||||
assert @web.brackets_only
|
||||
assert @web.count_pages
|
||||
end
|
||||
|
||||
|
||||
def test_web_list
|
||||
another_wiki = @wiki.create_web('Another Wiki', 'another_wiki')
|
||||
|
||||
r = process('web_list')
|
||||
|
||||
assert_success
|
||||
assert_equal [another_wiki, @web], r.template_objects['webs']
|
||||
end
|
||||
|
||||
|
||||
# Wiki fixture
|
||||
|
||||
def setup_test_wiki
|
||||
@wiki = ApplicationController.wiki = WikiServiceWithNoPersistence.new
|
||||
@web = @wiki.create_web('Test Wiki 1', 'wiki1')
|
||||
@home = @wiki.write_page('wiki1', 'HomePage', 'First revision of the HomePage end', Time.now,
|
||||
Author.new('AnAuthor', '127.0.0.1'))
|
||||
end
|
||||
|
||||
def setup_wiki_with_three_pages
|
||||
@oak = @wiki.write_page('wiki1', 'Oak',
|
||||
"All about oak.\n" +
|
||||
"category: trees",
|
||||
5.minutes.ago, Author.new('TreeHugger', '127.0.0.2'))
|
||||
@elephant = @wiki.write_page('wiki1', 'Elephant',
|
||||
"All about elephants.\n" +
|
||||
"category: animals",
|
||||
10.minutes.ago, Author.new('Guest', '127.0.0.2'))
|
||||
end
|
||||
|
||||
def tear_down_wiki
|
||||
ApplicationController.wiki = nil
|
||||
end
|
||||
|
||||
end
|
48
test/test_helper.rb
Executable file
48
test/test_helper.rb
Executable file
|
@ -0,0 +1,48 @@
|
|||
ENV['RAILS_ENV'] ||= 'test'
|
||||
require File.dirname(__FILE__) + '/../config/environment'
|
||||
require 'application'
|
||||
|
||||
require 'test/unit'
|
||||
require 'action_controller/test_process'
|
||||
|
||||
# Convenient setup method for Test::Unit::TestCase
|
||||
class Test::Unit::TestCase
|
||||
|
||||
private
|
||||
|
||||
def setup_controller_test(controller_class = nil, host = nil)
|
||||
if controller_class
|
||||
@controller = controller_class
|
||||
elsif self.class.to_s =~ /^(\w+Controller)Test$/
|
||||
@controller = Object::const_get($1)
|
||||
else
|
||||
raise "Cannot derive the name of controller under test from class name #{self.class}"
|
||||
end
|
||||
@request, @response = ActionController::TestRequest.new, ActionController::TestResponse.new
|
||||
@request.host = host || 'localhost'
|
||||
return @request, @response
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class WikiServiceWithNoPersistence
|
||||
include AbstractWikiService
|
||||
def initialize
|
||||
init_wiki_service
|
||||
end
|
||||
end
|
||||
|
||||
# With the new cookies infrastructure, @response.cookies['foo'] is no good anymore.
|
||||
# Pending implementation in Rails, here is a convenience method for accessing cookies from a test
|
||||
|
||||
module ActionController
|
||||
class TestResponse
|
||||
# Returns the response cookies, converted to a Hash of (name => CGI::Cookie) pairs
|
||||
# Example:
|
||||
#
|
||||
# assert_equal ['AuthorOfNewPage'], r.cookies['author'].value
|
||||
def cookies
|
||||
headers['cookie'].inject({}) { |hash, cookie| hash[cookie.name] = cookie; hash }
|
||||
end
|
||||
end
|
||||
end
|
21
test/unit/chunks/category_test.rb
Executable file
21
test/unit/chunks/category_test.rb
Executable file
|
@ -0,0 +1,21 @@
|
|||
require 'chunks/category'
|
||||
require 'chunks/match'
|
||||
require 'test/unit'
|
||||
|
||||
class CategoryTest < Test::Unit::TestCase
|
||||
include ChunkMatch
|
||||
|
||||
def test_single_category
|
||||
match(Category, 'category: test', :list => ['test'], :hidden => nil)
|
||||
match(Category, 'category : chunk test ', :list => ['chunk test'], :hidden => nil)
|
||||
match(Category, ':category: test', :list => ['test'], :hidden => ':')
|
||||
end
|
||||
|
||||
def test_multiple_categories
|
||||
match(Category, 'category: test, multiple', :list => ['test', 'multiple'], :hidden => nil)
|
||||
match(Category, 'category : chunk test , multi category,regression test case ',
|
||||
:list => ['chunk test','multi category','regression test case'], :hidden => nil
|
||||
)
|
||||
end
|
||||
|
||||
end
|
14
test/unit/chunks/nowiki_test.rb
Executable file
14
test/unit/chunks/nowiki_test.rb
Executable file
|
@ -0,0 +1,14 @@
|
|||
require 'chunks/nowiki'
|
||||
require 'chunks/match'
|
||||
require 'test/unit'
|
||||
|
||||
class NoWikiTest < Test::Unit::TestCase
|
||||
include ChunkMatch
|
||||
|
||||
def test_simple_nowiki
|
||||
match(NoWiki, 'This sentence contains <nowiki>[[raw text]]</nowiki>. Do not touch!',
|
||||
:plain_text => '[[raw text]]'
|
||||
)
|
||||
end
|
||||
|
||||
end
|
36
test/unit/chunks/wiki_test.rb
Executable file
36
test/unit/chunks/wiki_test.rb
Executable file
|
@ -0,0 +1,36 @@
|
|||
require 'chunks/wiki'
|
||||
require 'chunks/match'
|
||||
require 'test/unit'
|
||||
|
||||
class WikiTest < Test::Unit::TestCase
|
||||
include ChunkMatch
|
||||
|
||||
def test_simple
|
||||
match(WikiChunk::Word, 'This is a WikiWord okay?', :page_name => 'WikiWord')
|
||||
end
|
||||
|
||||
def test_escaped
|
||||
match(WikiChunk::Word, 'Do not link to an \EscapedWord',
|
||||
:page_name => 'EscapedWord', :escaped_text => 'EscapedWord'
|
||||
)
|
||||
end
|
||||
|
||||
def test_simple_brackets
|
||||
match(WikiChunk::Link, 'This is a [[bracketted link]]',
|
||||
:page_name => 'bracketted link', :escaped_text => nil
|
||||
)
|
||||
end
|
||||
|
||||
def test_complex_brackets
|
||||
match(WikiChunk::Link, 'This is a tricky link [[Sperberg-McQueen]]',
|
||||
:page_name => 'Sperberg-McQueen', :escaped_text => nil
|
||||
)
|
||||
end
|
||||
|
||||
# MDR: I'm not sure how to deal with this case just yet...
|
||||
#
|
||||
# def test_textile_link
|
||||
# assert_no_match(WikiChunk::Word.pattern, '"Here is a special link":SpecialLink')
|
||||
# end
|
||||
|
||||
end
|
80
test/unit/diff_test.rb
Executable file
80
test/unit/diff_test.rb
Executable file
|
@ -0,0 +1,80 @@
|
|||
require 'test/unit'
|
||||
require 'diff'
|
||||
|
||||
include Diff
|
||||
|
||||
class DiffTest < Test::Unit::TestCase
|
||||
def test_init
|
||||
assert(1 == 1, "tests working")
|
||||
assert_nothing_raised("object created") do
|
||||
s = SequenceMatcher.new "private Thread currentThread;",
|
||||
"private volatile Thread currentThread;",
|
||||
proc { |x| x == ' ' }
|
||||
end
|
||||
end
|
||||
|
||||
def test_matching_blocks
|
||||
s = SequenceMatcher.new "abxcd", "abcd"
|
||||
assert(s.get_matching_blocks == [[0, 0, 2], [3, 2, 2], [5, 4, 0]],
|
||||
"get_matching_blocks works")
|
||||
end
|
||||
|
||||
def test_ratio
|
||||
s = SequenceMatcher.new "abcd", "bcde"
|
||||
assert(s.ratio == 0.75, "ratio works")
|
||||
assert(s.quick_ratio == 0.75, "quick_ratio works")
|
||||
assert(s.real_quick_ratio == 1.0, "real_quick_ratio works")
|
||||
end
|
||||
|
||||
def test_longest_match
|
||||
s = SequenceMatcher.new(" abcd", "abcd abcd")
|
||||
assert(s.find_longest_match(0, 5, 0, 9) == [0, 4, 5],
|
||||
"find_longest_match works")
|
||||
s = SequenceMatcher.new()
|
||||
end
|
||||
|
||||
def test_opcodes
|
||||
s = SequenceMatcher.new("qabxcd", "abycdf")
|
||||
assert(s.get_opcodes == [
|
||||
[:delete, 0, 1, 0, 0],
|
||||
[:equal, 1, 3, 0, 2],
|
||||
[:replace, 3, 4, 2, 3],
|
||||
[:equal, 4, 6, 3, 5],
|
||||
[:insert, 6, 6, 5, 6]], "get_opcodes works")
|
||||
end
|
||||
|
||||
|
||||
def test_count_leading
|
||||
assert(Diff.count_leading(' abc', ' ') == 3,
|
||||
"count_leading works")
|
||||
end
|
||||
|
||||
def test_html2list
|
||||
a = "here is the original text"
|
||||
#p HTMLDiff.html2list(a)
|
||||
end
|
||||
|
||||
def test_html_diff
|
||||
a = "this was the original string"
|
||||
b = "this is the super string"
|
||||
assert_equal 'this <del class="diffmod">was </del>' +
|
||||
'<ins class="diffmod">is </ins>the ' +
|
||||
'<del class="diffmod">original </del>' +
|
||||
'<ins class="diffmod">super </ins>string',
|
||||
HTMLDiff.diff(a, b)
|
||||
end
|
||||
|
||||
def test_html_diff_with_multiple_paragraphs
|
||||
a = "<p>this was the original string</p>"
|
||||
b = "<p>this is</p>\r\n<p>the super string</p>\r\n<p>around the world</p>"
|
||||
|
||||
assert_equal(
|
||||
"<p>this <del class=\"diffmod\">was </del>" +
|
||||
"<ins class=\"diffmod\">is</ins></p>\r\n<p>the " +
|
||||
"<del class=\"diffmod\">original </del>" +
|
||||
"<ins class=\"diffmod\">super </ins>string</p>\r\n" +
|
||||
"<p><ins class=\"diffins\">around the world</ins></p>",
|
||||
HTMLDiff.diff(a, b)
|
||||
)
|
||||
end
|
||||
end
|
76
test/unit/page_test.rb
Executable file
76
test/unit/page_test.rb
Executable file
|
@ -0,0 +1,76 @@
|
|||
require "test/unit"
|
||||
require "web"
|
||||
require "page"
|
||||
|
||||
class MockWeb < Web
|
||||
def initialize() super('test','test') end
|
||||
def [](wiki_word) %w( MyWay ThatWay SmartEngine ).include?(wiki_word) end
|
||||
def refresh_pages_with_references(name) end
|
||||
end
|
||||
|
||||
class PageTest < Test::Unit::TestCase
|
||||
def setup
|
||||
@page = Page.new(
|
||||
MockWeb.new,
|
||||
"FirstPage",
|
||||
"HisWay would be MyWay in kinda ThatWay in HisWay though MyWay \\OverThere -- see SmartEngine in that SmartEngineGUI",
|
||||
Time.local(2004, 4, 4, 16, 50),
|
||||
"DavidHeinemeierHansson"
|
||||
)
|
||||
end
|
||||
|
||||
def test_basics
|
||||
assert_equal "First Page", @page.plain_name
|
||||
assert_equal "April 4, 2004", @page.pretty_revised_on
|
||||
end
|
||||
|
||||
def test_locking
|
||||
assert !@page.locked?(Time.local(2004, 4, 4, 16, 50))
|
||||
|
||||
@page.lock(Time.local(2004, 4, 4, 16, 30), "DavidHeinemeierHansson")
|
||||
|
||||
assert @page.locked?(Time.local(2004, 4, 4, 16, 50))
|
||||
assert !@page.locked?(Time.local(2004, 4, 4, 17, 1))
|
||||
|
||||
@page.unlock
|
||||
|
||||
assert !@page.locked?(Time.local(2004, 4, 4, 16, 50))
|
||||
end
|
||||
|
||||
def test_locking_duration
|
||||
@page.lock(Time.local(2004, 4, 4, 16, 30), "DavidHeinemeierHansson")
|
||||
|
||||
assert_equal 15, @page.lock_duration(Time.local(2004, 4, 4, 16, 45))
|
||||
end
|
||||
|
||||
def test_revision
|
||||
@page.revise("HisWay would be MyWay in kinda lame", Time.local(2004, 4, 4, 16, 55), "MarianneSyhler")
|
||||
assert_equal 2, @page.revisions.length, "Should have two revisions"
|
||||
assert_equal "MarianneSyhler", @page.author, "Mary should be the author now"
|
||||
assert_equal "DavidHeinemeierHansson", @page.revisions.first.author, "David was the first author"
|
||||
end
|
||||
|
||||
def test_rollback
|
||||
@page.revise("spot two", Time.now, "David")
|
||||
@page.revise("spot three", Time.now + 2000, "David")
|
||||
assert_equal 3, @page.revisions.length, "Should have three revisions"
|
||||
@page.rollback(1, Time.now)
|
||||
assert_equal "spot two", @page.content
|
||||
end
|
||||
|
||||
def test_continous_revision
|
||||
@page.revise("HisWay would be MyWay in kinda lame", Time.local(2004, 4, 4, 16, 55), "MarianneSyhler")
|
||||
assert_equal 2, @page.revisions.length
|
||||
|
||||
@page.revise("HisWay would be MyWay in kinda update", Time.local(2004, 4, 4, 16, 57), "MarianneSyhler")
|
||||
assert_equal 2, @page.revisions.length
|
||||
assert_equal "HisWay would be MyWay in kinda update", @page.revisions.last.content
|
||||
|
||||
@page.revise("HisWay would be MyWay in the house", Time.local(2004, 4, 4, 16, 58), "DavidHeinemeierHansson")
|
||||
assert_equal 3, @page.revisions.length
|
||||
assert_equal "HisWay would be MyWay in the house", @page.revisions.last.content
|
||||
|
||||
@page.revise("HisWay would be MyWay in my way", Time.local(2004, 4, 4, 17, 30), "DavidHeinemeierHansson")
|
||||
assert_equal 4, @page.revisions.length
|
||||
end
|
||||
end
|
67
test/unit/redcloth_for_tex_test.rb
Executable file
67
test/unit/redcloth_for_tex_test.rb
Executable file
|
@ -0,0 +1,67 @@
|
|||
require "test/unit"
|
||||
require "redcloth_for_tex"
|
||||
|
||||
class RedClothForTexTest < Test::Unit::TestCase
|
||||
def test_basics
|
||||
assert_equal '{\bf First Page}', RedClothForTex.new("*First Page*").to_tex
|
||||
assert_equal '{\em First Page}', RedClothForTex.new("_First Page_").to_tex
|
||||
assert_equal "\\begin{itemize}\n\t\\item A\n\t\t\\item B\n\t\t\\item C\n\t\\end{itemize}", RedClothForTex.new("* A\n* B\n* C").to_tex
|
||||
end
|
||||
|
||||
def test_blocks
|
||||
assert_equal '\section*{hello}', RedClothForTex.new("h1. hello").to_tex
|
||||
assert_equal '\subsection*{hello}', RedClothForTex.new("h2. hello").to_tex
|
||||
end
|
||||
|
||||
def test_table_of_contents
|
||||
|
||||
source = <<EOL
|
||||
* [[A]]
|
||||
** [[B]]
|
||||
** [[C]]
|
||||
* D
|
||||
** [[E]]
|
||||
*** F
|
||||
EOL
|
||||
|
||||
expected_result = <<EOL
|
||||
\\pagebreak
|
||||
|
||||
\\section{A}
|
||||
Abe
|
||||
|
||||
\\subsection{B}
|
||||
Babe
|
||||
|
||||
\\subsection{C}
|
||||
\\pagebreak
|
||||
|
||||
\\section{D}
|
||||
|
||||
\\subsection{E}
|
||||
|
||||
\\subsubsection{F}
|
||||
EOL
|
||||
expected_result.chop!
|
||||
assert_equal(expected_result, table_of_contents(source, 'A' => 'Abe', 'B' => 'Babe'))
|
||||
end
|
||||
|
||||
def test_entities
|
||||
assert_equal "Beck \\& Fowler are 100\\% cool", RedClothForTex.new("Beck & Fowler are 100% cool").to_tex
|
||||
end
|
||||
|
||||
def test_bracket_links
|
||||
assert_equal "such a Horrible Day, but I won't be Made Useless", RedClothForTex.new("such a [[Horrible Day]], but I won't be [[Made Useless]]").to_tex
|
||||
end
|
||||
|
||||
def test_footnotes_on_abbreviations
|
||||
assert_equal(
|
||||
"such a Horrible Day\\footnote{1}, but I won't be Made Useless",
|
||||
RedClothForTex.new("such a [[Horrible Day]][1], but I won't be [[Made Useless]]").to_tex
|
||||
)
|
||||
end
|
||||
|
||||
def test_subsection_depth
|
||||
assert_equal "\\subsubsection*{Hello}", RedClothForTex.new("h4. Hello").to_tex
|
||||
end
|
||||
end
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue