Files
gollum/lib/gollum/wiki.rb
T
bootstraponline 1e768734ef Disable MathJax by default.
GitHub.com doesn't support MathJax. The parser is not MathJax aware which causes problems.

--mathjax enables MathJax.
2012-08-22 13:31:21 -06:00

717 lines
24 KiB
Ruby

module Gollum
class Wiki
include Pagination
class << self
# Sets the page class used by all instances of this Wiki.
attr_writer :page_class
# Sets the file class used by all instances of this Wiki.
attr_writer :file_class
# Sets the markup class used by all instances of this Wiki.
attr_writer :markup_classes
# Sets the default ref for the wiki.
attr_accessor :default_ref
# Sets the default name for commits.
attr_accessor :default_committer_name
# Sets the default email for commits.
attr_accessor :default_committer_email
# Array of chars to substitute whitespace for when trying to locate file in git repo.
attr_accessor :default_ws_subs
# Sets sanitization options. Set to false to deactivate
# sanitization altogether.
attr_writer :sanitization
# Sets sanitization options. Set to false to deactivate
# sanitization altogether.
attr_writer :history_sanitization
# Hash for setting different default wiki options
# These defaults can be overridden by options passed directly to initialize()
attr_accessor :default_options
# Gets the page class used by all instances of this Wiki.
# Default: Gollum::Page.
def page_class
@page_class ||
if superclass.respond_to?(:page_class)
superclass.page_class
else
::Gollum::Page
end
end
# Gets the file class used by all instances of this Wiki.
# Default: Gollum::File.
def file_class
@file_class ||
if superclass.respond_to?(:file_class)
superclass.file_class
else
::Gollum::File
end
end
# Gets the markup class used by all instances of this Wiki.
# Default: Gollum::Markup
def markup_classes
@markup_classes ||=
if superclass.respond_to?(:markup_classes)
superclass.markup_classes
else
Hash.new(::Gollum::Markup)
end
end
# Gets the default markup class used by all instances of this Wiki.
# Kept for backwards compatibility until Gollum v2.x
def markup_class(language=:default)
markup_classes[language]
end
# Sets the default markup class used by all instances of this Wiki.
# Kept for backwards compatibility until Gollum v2.x
def markup_class=(default)
@markup_classes = Hash.new(default).update(markup_classes)
default
end
alias_method :default_markup_class, :markup_class
alias_method :default_markup_class=, :markup_class=
# Gets the default sanitization options for current pages used by
# instances of this Wiki.
def sanitization
if @sanitization.nil?
@sanitization = Sanitization.new
end
@sanitization
end
# Gets the default sanitization options for older page revisions used by
# instances of this Wiki.
def history_sanitization
if @history_sanitization.nil?
@history_sanitization = sanitization ?
sanitization.history_sanitization :
false
end
@history_sanitization
end
end
self.default_ref = 'master'
self.default_committer_name = 'Anonymous'
self.default_committer_email = 'anon@anon.com'
self.default_ws_subs = ['_','-']
self.default_options = {}
# The String base path to prefix to internal links. For example, when set
# to "/wiki", the page "Hobbit" will be linked as "/wiki/Hobbit". Defaults
# to "/".
attr_reader :base_path
# Gets the sanitization options for current pages used by this Wiki.
attr_reader :sanitization
# Gets the sanitization options for older page revisions used by this Wiki.
attr_reader :history_sanitization
# Gets the String ref in which all page files reside.
attr_reader :ref
# Gets the String directory in which all page files reside.
attr_reader :page_file_dir
# Gets the Array of chars to sub for ws in filenames.
attr_reader :ws_subs
# Gets the boolean live preview value.
attr_reader :live_preview
# Public: Initialize a new Gollum Repo.
#
# path - The String path to the Git repository that holds the Gollum
# site.
# options - Optional Hash:
# :universal_toc - Table of contents on all pages. Default: false
# :live_preview - Livepreview editing for markdown files. Default: true
# :base_path - String base path for all Wiki links.
# Default: "/"
# :page_class - The page Class. Default: Gollum::Page
# :file_class - The file Class. Default: Gollum::File
# :markup_classes - A hash containing the markup Classes for each
# document type. Default: { Gollum::Markup }
# :sanitization - An instance of Sanitization.
# :page_file_dir - String the directory in which all page files reside
# :ref - String the repository ref to retrieve pages from
# :ws_subs - Array of chars to sub for ws in filenames.
# :mathjax - Set to false to disable mathjax.
#
# Returns a fresh Gollum::Repo.
def initialize(path, options = {})
options = self.class.default_options.merge(options)
if path.is_a?(GitAccess)
options[:access] = path
path = path.path
end
@path = path
@repo_is_bare = options[:repo_is_bare]
@page_file_dir = options[:page_file_dir]
@access = options[:access] || GitAccess.new(path, @page_file_dir, @repo_is_bare)
@base_path = options[:base_path] || "/"
@page_class = options[:page_class] || self.class.page_class
@file_class = options[:file_class] || self.class.file_class
@markup_classes = options[:markup_classes] || self.class.markup_classes
@repo = @access.repo
@ref = options[:ref] || self.class.default_ref
@sanitization = options[:sanitization] || self.class.sanitization
@ws_subs = options[:ws_subs] ||
self.class.default_ws_subs
@history_sanitization = options[:history_sanitization] ||
self.class.history_sanitization
@live_preview = options.fetch(:live_preview, true)
@universal_toc = options.fetch(:universal_toc, false)
@mathjax = options[:mathjax] || false
end
# Public: check whether the wiki's git repo exists on the filesystem.
#
# Returns true if the repo exists, and false if it does not.
def exist?
@access.exist?
end
# Public: Get the formatted page for a given page name, version, and dir.
#
# name - The human or canonical String page name of the wiki page.
# version - The String version ID to find (default: @ref).
# dir - The directory String relative to the repo.
#
# Returns a Gollum::Page or nil if no matching page was found.
def page(name, version = @ref, dir = nil)
version = @ref if version.nil?
@page_class.new(self).find(name, version, dir)
end
# Public: Convenience method instead of calling page(name, nil, dir).
#
# name - The human or canonical String page name of the wiki page.
# version - The String version ID to find (default: @ref).
# dir - The directory String relative to the repo.
#
# Returns a Gollum::Page or nil if no matching page was found.
def paged(name, dir = nil, version = @ref)
page(name, version, dir)
end
# Public: Get the static file for a given name.
#
# name - The full String pathname to the file.
# version - The String version ID to find (default: @ref).
#
# Returns a Gollum::File or nil if no matching file was found.
def file(name, version = @ref)
@file_class.new(self).find(name, version)
end
# Public: Create an in-memory Page with the given data and format. This
# is useful for previewing what content will look like before committing
# it to the repository.
#
# name - The String name of the page.
# format - The Symbol format of the page.
# data - The new String contents of the page.
#
# Returns the in-memory Gollum::Page.
def preview_page(name, data, format)
page = @page_class.new(self)
ext = @page_class.format_to_ext(format.to_sym)
name = @page_class.cname(name) + '.' + ext
blob = OpenStruct.new(:name => name, :data => data)
page.populate(blob)
page.version = @access.commit('master')
page
end
# Public: Write a new version of a page to the Gollum repo root.
#
# name - The String name of the page.
# format - The Symbol format of the page.
# data - The new String contents of the page.
# commit - The commit Hash details:
# :message - The String commit message.
# :name - The String author full name.
# :email - The String email address.
# :parent - Optional Grit::Commit parent to this update.
# :tree - Optional String SHA of the tree to create the
# index from.
# :committer - Optional Gollum::Committer instance. If provided,
# assume that this operation is part of batch of
# updates and the commit happens later.
#
# Returns the String SHA1 of the newly written version, or the
# Gollum::Committer instance if this is part of a batch update.
def write_page(name, format, data, commit = {})
multi_commit = false
committer = if obj = commit[:committer]
multi_commit = true
obj
else
Committer.new(self, commit)
end
filename = Gollum::Page.cname(name)
committer.add_to_index('', filename, format, data)
committer.after_commit do |index, sha|
@access.refresh
index.update_working_dir('', filename, format)
end
multi_commit ? committer : committer.commit
end
# Public: Update an existing page with new content. The location of the
# page inside the repository will not change. If the given format is
# different than the current format of the page, the filename will be
# changed to reflect the new format.
#
# page - The Gollum::Page to update.
# name - The String extension-less name of the page.
# format - The Symbol format of the page.
# data - The new String contents of the page.
# commit - The commit Hash details:
# :message - The String commit message.
# :name - The String author full name.
# :email - The String email address.
# :parent - Optional Grit::Commit parent to this update.
# :tree - Optional String SHA of the tree to create the
# index from.
# :committer - Optional Gollum::Committer instance. If provided,
# assume that this operation is part of batch of
# updates and the commit happens later.
#
# Returns the String SHA1 of the newly written version, or the
# Gollum::Committer instance if this is part of a batch update.
def update_page(page, name, format, data, commit = {})
name ||= page.name
format ||= page.format
dir = ::File.dirname(page.path)
dir = '' if dir == '.'
filename = (rename = page.name != name) ?
Gollum::Page.cname(name) : page.filename_stripped
multi_commit = false
committer = if obj = commit[:committer]
multi_commit = true
obj
else
Committer.new(self, commit)
end
if !rename && page.format == format
committer.add(page.path, normalize(data))
else
committer.delete(page.path)
committer.add_to_index(dir, filename, format, data, :allow_same_ext)
end
committer.after_commit do |index, sha|
@access.refresh
index.update_working_dir(dir, page.filename_stripped, page.format)
index.update_working_dir(dir, filename, format)
end
multi_commit ? committer : committer.commit
end
# Public: Delete a page.
#
# page - The Gollum::Page to delete.
# commit - The commit Hash details:
# :message - The String commit message.
# :name - The String author full name.
# :email - The String email address.
# :parent - Optional Grit::Commit parent to this update.
# :tree - Optional String SHA of the tree to create the
# index from.
# :committer - Optional Gollum::Committer instance. If provided,
# assume that this operation is part of batch of
# updates and the commit happens later.
#
# Returns the String SHA1 of the newly written version, or the
# Gollum::Committer instance if this is part of a batch update.
def delete_page(page, commit)
multi_commit = false
committer = if obj = commit[:committer]
multi_commit = true
obj
else
Committer.new(self, commit)
end
committer.delete(page.path)
committer.after_commit do |index, sha|
dir = ::File.dirname(page.path)
dir = '' if dir == '.'
@access.refresh
index.update_working_dir(dir, page.filename_stripped, page.format)
end
multi_commit ? committer : committer.commit
end
# Public: Applies a reverse diff for a given page. If only 1 SHA is given,
# the reverse diff will be taken from its parent (^SHA...SHA). If two SHAs
# are given, the reverse diff is taken from SHA1...SHA2.
#
# page - The Gollum::Page to delete.
# sha1 - String SHA1 of the earlier parent if two SHAs are given,
# or the child.
# sha2 - Optional String SHA1 of the child.
# commit - The commit Hash details:
# :message - The String commit message.
# :name - The String author full name.
# :email - The String email address.
# :parent - Optional Grit::Commit parent to this update.
#
# Returns a String SHA1 of the new commit, or nil if the reverse diff does
# not apply.
def revert_page(page, sha1, sha2 = nil, commit = {})
if sha2.is_a?(Hash)
commit = sha2
sha2 = nil
end
patch = full_reverse_diff_for(page, sha1, sha2)
committer = Committer.new(self, commit)
parent = committer.parents[0]
committer.options[:tree] = @repo.git.apply_patch(parent.sha, patch)
return false unless committer.options[:tree]
committer.after_commit do |index, sha|
@access.refresh
files = []
if page
files << [page.path, page.filename_stripped, page.format]
else
# Grit::Diff can't parse reverse diffs.... yet
patch.each_line do |line|
if line =~ %r{^diff --git b/.+? a/(.+)$}
path = $1
ext = ::File.extname(path)
name = ::File.basename(path, ext)
if format = ::Gollum::Page.format_for(ext)
files << [path, name, format]
end
end
end
end
files.each do |(path, name, format)|
dir = ::File.dirname(path)
dir = '' if dir == '.'
index.update_working_dir(dir, name, format)
end
end
committer.commit
end
# Public: Applies a reverse diff to the repo. If only 1 SHA is given,
# the reverse diff will be taken from its parent (^SHA...SHA). If two SHAs
# are given, the reverse diff is taken from SHA1...SHA2.
#
# sha1 - String SHA1 of the earlier parent if two SHAs are given,
# or the child.
# sha2 - Optional String SHA1 of the child.
# commit - The commit Hash details:
# :message - The String commit message.
# :name - The String author full name.
# :email - The String email address.
#
# Returns a String SHA1 of the new commit, or nil if the reverse diff does
# not apply.
def revert_commit(sha1, sha2 = nil, commit = {})
revert_page(nil, sha1, sha2, commit)
end
# Public: Lists all pages for this wiki.
#
# treeish - The String commit ID or ref to find (default: @ref)
#
# Returns an Array of Gollum::Page instances.
def pages(treeish = nil)
tree_list(treeish || @ref)
end
# Public: Lists all non-page files for this wiki.
#
# treeish - The String commit ID or ref to find (default: @ref)
#
# Returns an Array of Gollum::File instances.
def files(treeish = nil)
file_list(treeish || @ref)
end
# Public: Returns the number of pages accessible from a commit
#
# ref - A String ref that is either a commit SHA or references one.
#
# Returns a Fixnum
def size(ref = nil)
tree_map_for(ref || @ref).inject(0) do |num, entry|
num + (@page_class.valid_page_name?(entry.name) ? 1 : 0)
end
rescue Grit::GitRuby::Repository::NoSuchShaFound
0
end
# Public: Search all pages for this wiki.
#
# query - The string to search for
#
# Returns an Array with Objects of page name and count of matches
def search(query)
args = [{}, '-i', '-c', query, @ref, '--']
args << '--' << @page_file_dir if @page_file_dir
results = {}
@repo.git.grep(*args).split("\n").each do |line|
result = line.split(':')
result_1 = result[1]
file_name = result_1.chomp(::File.extname(result_1))
results[file_name] = result[2].to_i
end
# Use git ls-files '*query*' to search for file names. Grep only searches file content.
# Spaces are converted to dashes when saving pages to disk.
@repo.git.ls_files({}, "*#{ query.gsub(' ', '-') }*").split("\n").each do |line|
file_name = line.chomp(::File.extname(line))
# If there's not already a result for file_name then
# the value is nil and nil.to_i is 0.
results[file_name] = results[file_name].to_i + 1;
end
results.map do |key,val|
{ :count => val, :name => key }
end
end
# Public: All of the versions that have touched the Page.
#
# options - The options Hash:
# :page - The Integer page number (default: 1).
# :per_page - The Integer max count of items to return.
#
# Returns an Array of Grit::Commit.
def log(options = {})
@repo.log(@ref, nil, log_pagination_options(options))
end
# Public: Refreshes just the cached Git reference data. This should
# be called after every Gollum update.
#
# Returns nothing.
def clear_cache
@access.refresh
end
# Public: Creates a Sanitize instance using the Wiki's sanitization
# options.
#
# Returns a Sanitize instance.
def sanitizer
if options = sanitization
@sanitizer ||= options.to_sanitize
end
end
# Public: Creates a Sanitize instance using the Wiki's history sanitization
# options.
#
# Returns a Sanitize instance.
def history_sanitizer
if options = history_sanitization
@history_sanitizer ||= options.to_sanitize
end
end
#########################################################################
#
# Internal Methods
#
#########################################################################
# The Grit::Repo associated with the wiki.
#
# Returns the Grit::Repo.
attr_reader :repo
# The String path to the Git repository that holds the Gollum site.
#
# Returns the String path.
attr_reader :path
# Gets the page class used by all instances of this Wiki.
attr_reader :page_class
# Gets the file class used by all instances of this Wiki.
attr_reader :file_class
# Gets the markup class used by all instances of this Wiki.
attr_reader :markup_classes
# Toggles display of universal table of contents
attr_reader :universal_toc
# Toggles mathjax.
attr_reader :mathjax
# Normalize the data.
#
# data - The String data to be normalized.
#
# Returns the normalized data String.
def normalize(data)
data.gsub(/\r/, '')
end
# Assemble a Page's filename from its name and format.
#
# name - The String name of the page (should be pre-canonicalized).
# format - The Symbol format of the page.
#
# Returns the String filename.
def page_file_name(name, format)
name + '.' + @page_class.format_to_ext(format)
end
# Fill an array with a list of pages.
#
# ref - A String ref that is either a commit SHA or references one.
#
# Returns a flat Array of Gollum::Page instances.
def tree_list(ref)
if sha = @access.ref_to_sha(ref)
commit = @access.commit(sha)
tree_map_for(sha).inject([]) do |list, entry|
next list unless @page_class.valid_page_name?(entry.name)
list << entry.page(self, commit)
end
else
[]
end
end
# Fill an array with a list of files.
#
# ref - A String ref that is either a commit SHA or references one.
#
# Returns a flat Array of Gollum::File instances.
def file_list(ref)
if sha = @access.ref_to_sha(ref)
commit = @access.commit(sha)
tree_map_for(sha).inject([]) do |list, entry|
next list if entry.name.start_with?('_')
next list if @page_class.valid_page_name?(entry.name)
list << entry.file(self, commit)
end
else
[]
end
end
# Creates a reverse diff for the given SHAs on the given Gollum::Page.
#
# page - The Gollum::Page to scope the patch to, or a String Path.
# sha1 - String SHA1 of the earlier parent if two SHAs are given,
# or the child.
# sha2 - Optional String SHA1 of the child.
#
# Returns a String of the reverse Diff to apply.
def full_reverse_diff_for(page, sha1, sha2 = nil)
sha1, sha2 = "#{sha1}^", sha1 if sha2.nil?
args = [{:R => true}, sha1, sha2]
if page
args << '--' << (page.respond_to?(:path) ? page.path : page.to_s)
end
repo.git.native(:diff, *args)
end
# Creates a reverse diff for the given SHAs.
#
# sha1 - String SHA1 of the earlier parent if two SHAs are given,
# or the child.
# sha2 - Optional String SHA1 of the child.
#
# Returns a String of the reverse Diff to apply.
def full_reverse_diff(sha1, sha2 = nil)
full_reverse_diff_for(nil, sha1, sha2)
end
# Gets the default name for commits.
#
# Returns the String name.
def default_committer_name
@default_committer_name ||= \
@repo.config['user.name'] || self.class.default_committer_name
end
# Gets the default email for commits.
#
# Returns the String email address.
def default_committer_email
@default_committer_email ||= \
@repo.config['user.email'] || self.class.default_committer_email
end
# Gets the commit object for the given ref or sha.
#
# ref - A string ref or SHA pointing to a valid commit.
#
# Returns a Grit::Commit instance.
def commit_for(ref)
@access.commit(ref)
rescue Grit::GitRuby::Repository::NoSuchShaFound
end
# Finds a full listing of files and their blob SHA for a given ref. Each
# listing is cached based on its actual commit SHA.
#
# ref - A String ref that is either a commit SHA or references one.
# ignore_page_file_dir - Boolean, if true, searches all files within the git repo, regardless of dir/subdir
#
# Returns an Array of BlobEntry instances.
def tree_map_for(ref, ignore_page_file_dir=false)
if ignore_page_file_dir && !@page_file_dir.nil?
@root_access ||= GitAccess.new(path, nil, @repo_is_bare)
@root_access.tree(ref)
else
@access.tree(ref)
end
rescue Grit::GitRuby::Repository::NoSuchShaFound
[]
end
def inspect
%(#<#{self.class.name}:#{object_id} #{@repo.path}>)
end
end
end