Merge pull request #288 from lefou/asciidoctorj

Support AsciiDoc markup for all files + Plain text Readme's
This commit is contained in:
Naoki Takezoe
2014-04-04 13:09:45 +09:00
6 changed files with 112 additions and 10 deletions

View File

@@ -252,7 +252,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
}
private val readmeFiles = Seq("readme.md", "readme.markdown")
private val readmeFiles = view.helpers.renderableSuffixes.map(suffix => s"readme${suffix}") ++ Seq("readme.txt", "readme")
/**
* Provides HTML of the file list.
@@ -270,14 +270,16 @@ trait RepositoryViewerControllerBase extends ControllerBase {
//val revisions = Seq(if(revstr.isEmpty) repository.repository.defaultBranch else revstr, repository.branchList.head)
// get specified commit
JGitUtil.getDefaultBranch(git, repository, revstr).map { case (objectId, revision) =>
defining(JGitUtil.getRevCommitFromId(git, objectId)){ revCommit =>
// get files
defining(JGitUtil.getRevCommitFromId(git, objectId)) { revCommit =>
// get files
val files = JGitUtil.getFileList(git, revision, path)
val parentPath = if (path == ".") Nil else path.split("/").toList
// process README.md or README.markdown
val readme = files.find { file =>
readmeFiles.contains(file.name.toLowerCase)
}.map { file =>
file -> StringUtil.convertFromByteArray(JGitUtil.getContentFromId(
val path = (file.name :: parentPath.reverse).reverse
path -> StringUtil.convertFromByteArray(JGitUtil.getContentFromId(
Git.open(getRepositoryDir(repository.owner, repository.name)), file.id, true).get)
}

View File

@@ -0,0 +1,62 @@
package view
import org.asciidoctor.Asciidoctor
import org.asciidoctor.AttributesBuilder
import org.asciidoctor.OptionsBuilder
import org.asciidoctor.SafeMode
import org.htmlcleaner.HtmlCleaner
import org.htmlcleaner.HtmlNode
import org.htmlcleaner.SimpleHtmlSerializer
import org.htmlcleaner.TagNode
import org.htmlcleaner.TagNodeVisitor
object Asciidoc {
private[this] lazy val asciidoctor = Asciidoctor.Factory.create()
/**
* Converts Markdown of Wiki pages to HTML.
*/
def toHtml(filePath: List[String], asciidoc: String, branch: String, repository: service.RepositoryService.RepositoryInfo,
enableWikiLink: Boolean, enableRefsLink: Boolean)(implicit context: app.Context): String = {
val options = OptionsBuilder.options()
options.safe(SafeMode.SECURE)
val attributes = AttributesBuilder.attributes()
attributes.showTitle(true)
options.attributes(attributes.get())
val rendered = asciidoctor.render(asciidoc, options)
val path = filePath.reverse.tail.reverse match {
case Nil => ""
case p => p.mkString("", "/", "/")
}
val relativeUrlPrefix = s"${helpers.url(repository)}/blob/${branch}/${path}"
prefixRelativeUrls(rendered, relativeUrlPrefix)
}
private[this] val exceptionPrefixes = Seq("#", "/", "http://", "https://")
def prefixRelativeUrls(html: String, urlPrefix: String): String = {
val cleaner = new HtmlCleaner()
val node = cleaner.clean(html)
node.traverse(new TagNodeVisitor() {
override def visit(tagNode: TagNode, htmlNode: HtmlNode): Boolean = {
htmlNode match {
case tag: TagNode if tag.getName == "a" =>
Option(tag.getAttributeByName("href")) foreach { href =>
if (exceptionPrefixes.forall(p => !href.startsWith(p))) {
tag.addAttribute("href", s"${urlPrefix}${href}")
}
}
case _ =>
}
// continue traversal
true
}
})
new SimpleHtmlSerializer(cleaner.getProperties()).getAsString(node)
}
}

View File

@@ -27,6 +27,16 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
def plural(count: Int, singular: String, plural: String = ""): String =
if(count == 1) singular else if(plural.isEmpty) singular + "s" else plural
private[this] val renderersBySuffix: Seq[(String, (List[String], String, String, service.RepositoryService.RepositoryInfo, Boolean, Boolean, app.Context) => Html)] =
Seq(
".md" -> ((filePath, fileContent, branch, repository, enableWikiLink, enableRefsLink, context) => markdown(fileContent, repository, enableWikiLink, enableRefsLink)(context)),
".markdown" -> ((filePath, fileContent, branch, repository, enableWikiLink, enableRefsLink, context) => markdown(fileContent, repository, enableWikiLink, enableRefsLink)(context)),
".adoc" -> ((filePath, fileContent, branch, repository, enableWikiLink, enableRefsLink, context) => asciidoc(filePath, fileContent, branch, repository, enableWikiLink, enableRefsLink)(context)),
".asciidoc" -> ((filePath, fileContent, branch, repository, enableWikiLink, enableRefsLink, context) => asciidoc(filePath, fileContent, branch, repository, enableWikiLink, enableRefsLink)(context))
)
def renderableSuffixes: Seq[String] = renderersBySuffix.map(_._1)
/**
* Converts Markdown of Wiki pages to HTML.
*/
@@ -34,6 +44,25 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
enableWikiLink: Boolean, enableRefsLink: Boolean)(implicit context: app.Context): Html =
Html(Markdown.toHtml(value, repository, enableWikiLink, enableRefsLink))
def renderMarkup(filePath: List[String], fileContent: String, branch: String,
repository: service.RepositoryService.RepositoryInfo,
enableWikiLink: Boolean, enableRefsLink: Boolean)(implicit context: app.Context): Html = {
val fileNameLower = filePath.reverse.head.toLowerCase
renderersBySuffix.find { case (suffix, _) => fileNameLower.endsWith(suffix) } match {
case Some((_, handler)) => handler(filePath, fileContent, branch, repository, enableWikiLink, enableRefsLink, context)
case None => Html(
s"<tt>${
fileContent.split("(\\r\\n)|\\n").map(xml.Utility.escape(_)).mkString("<br/>")
}</tt>"
)
}
}
def asciidoc(filePath: List[String], value: String, branch: String, repository: service.RepositoryService.RepositoryInfo,
enableWikiLink: Boolean, enableRefsLink: Boolean)(implicit context: app.Context): Html =
Html(Asciidoc.toHtml(filePath, value, branch, repository, enableWikiLink, enableRefsLink))
/**
* Returns &lt;img&gt; which displays the avatar icon for the given user name.
* This method looks up Gravatar if avatar icon has not been configured in user settings.