Markdown: recognize special note, promo, and aside blocks Extend the supported markdown syntax with a special aside format useful for documentation writing. *** note Pay special attention here! You may have to watch out for crazy developers. *** A note block starts with *** on its own line and ends with *** on its own line. Everything inside of the block is read as Markdown and reprocessed through a recursive call whose AST is inlined. Three kinds of notes are recognized, which are given different CSS: *** note Some sort of a warning you should watch out for. *** *** promo Look here this may interest you. *** *** aside The author felt you should read this here, but really it's just a distraction and maybe should be omitted or moved to another page buried under a link. *** Change-Id: Icbfd27d3afd7d0137e37b7d5e821938c70207cf1
diff --git a/gitiles-servlet/src/main/java/com/google/gitiles/doc/DivNode.java b/gitiles-servlet/src/main/java/com/google/gitiles/doc/DivNode.java new file mode 100644 index 0000000..a84a041 --- /dev/null +++ b/gitiles-servlet/src/main/java/com/google/gitiles/doc/DivNode.java
@@ -0,0 +1,42 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gitiles.doc; + +import org.pegdown.ast.Node; +import org.pegdown.ast.ParaNode; +import org.pegdown.ast.SuperNode; + +import java.util.List; + +/** Block note to render as {@code <div class="{clazz}">}. */ +public class DivNode extends SuperNode { + private final String style; + + DivNode(String style, List<Node> list) { + super(list.size() == 1 && list.get(0) instanceof ParaNode + ? ((ParaNode) list.get(0)).getChildren() + : list); + this.style = style; + } + + public String getStyleName() { + return style; + } + + @Override + public void accept(org.pegdown.ast.Visitor visitor) { + ((Visitor) visitor).visit(this); + } +}
diff --git a/gitiles-servlet/src/main/java/com/google/gitiles/doc/GitilesMarkdown.java b/gitiles-servlet/src/main/java/com/google/gitiles/doc/GitilesMarkdown.java index b79bf4d..43c589f 100644 --- a/gitiles-servlet/src/main/java/com/google/gitiles/doc/GitilesMarkdown.java +++ b/gitiles-servlet/src/main/java/com/google/gitiles/doc/GitilesMarkdown.java
@@ -17,15 +17,19 @@ import com.google.gitiles.GitilesView; import org.parboiled.Rule; +import org.parboiled.support.StringBuilderVar; import org.pegdown.Parser; import org.pegdown.ParsingTimeoutException; import org.pegdown.PegDownProcessor; +import org.pegdown.ast.Node; import org.pegdown.ast.RootNode; import org.pegdown.plugins.BlockPluginParser; import org.pegdown.plugins.PegDownPlugins; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.List; + /** * Additional markdown extensions known to Gitiles. * <p> @@ -66,13 +70,18 @@ return new PegDownProcessor(MD_OPTIONS, plugins); } + private PegDownProcessor parser; + GitilesMarkdown() { super(MD_OPTIONS, 2000L, DefaultParseRunnerProvider); } @Override public Rule[] blockPluginRules() { - return new Rule[]{ toc() }; + return new Rule[]{ + note(), + toc(), + }; } public Rule toc() { @@ -80,4 +89,32 @@ string("[TOC]"), push(new TocNode())); } + + public Rule note() { + StringBuilderVar body = new StringBuilderVar(); + return NodeSequence( + string("***"), Sp(), typeOfNote(), Newline(), + oneOrMore( + testNot(string("***"), Newline()), + Line(body)), + string("***"), Newline(), + push(new DivNode(popAsString(), parse(body)))); + } + + public Rule typeOfNote() { + return firstOf( + sequence(string("note"), push(match())), + sequence(string("promo"), push(match())), + sequence(string("aside"), push(match()))); + } + + public List<Node> parse(StringBuilderVar body) { + // The pegdown code doesn't provide enough visibility to directly + // use its existing parsing rules. Recurse manually for inner text + // parsing within a block. + if (parser == null) { + parser = newParser(); + } + return parser.parseMarkdown(body.getChars()).getChildren(); + } }
diff --git a/gitiles-servlet/src/main/java/com/google/gitiles/doc/MarkdownToHtml.java b/gitiles-servlet/src/main/java/com/google/gitiles/doc/MarkdownToHtml.java index a3feb6c..1086bab 100644 --- a/gitiles-servlet/src/main/java/com/google/gitiles/doc/MarkdownToHtml.java +++ b/gitiles-servlet/src/main/java/com/google/gitiles/doc/MarkdownToHtml.java
@@ -100,6 +100,13 @@ } @Override + public void visit(DivNode node) { + html.open("div").attribute("class", node.getStyleName()); + visitChildren(node); + html.close("div"); + } + + @Override public void visit(HeaderNode node) { String tag = "h" + node.getLevel(); html.open(tag);
diff --git a/gitiles-servlet/src/main/java/com/google/gitiles/doc/Visitor.java b/gitiles-servlet/src/main/java/com/google/gitiles/doc/Visitor.java index b8cf64e..19a68cc 100644 --- a/gitiles-servlet/src/main/java/com/google/gitiles/doc/Visitor.java +++ b/gitiles-servlet/src/main/java/com/google/gitiles/doc/Visitor.java
@@ -15,5 +15,6 @@ package com.google.gitiles.doc; public interface Visitor extends org.pegdown.ast.Visitor { + void visit(DivNode node); void visit(TocNode node); }
diff --git a/gitiles-servlet/src/main/resources/com/google/gitiles/static/doc.css b/gitiles-servlet/src/main/resources/com/google/gitiles/static/doc.css index e047f59..c324786 100644 --- a/gitiles-servlet/src/main/resources/com/google/gitiles/static/doc.css +++ b/gitiles-servlet/src/main/resources/com/google/gitiles/static/doc.css
@@ -164,4 +164,33 @@ } th { background-color: #f5f5f5; -} \ No newline at end of file +} + +.note, .promo, .aside { + border: 1px solid; + border-radius: 4px; + margin: 10px 0; + padding: 10px; +} +.note { + background: #fffbe4; + border-color: #f8f6e6; +} +.promo { + background: #f6f9ff; + border-color: #eff2f9; +} +.aside { + background: #f9f9f9; + border-color: #f2f2f2; +} +.note p:first-child, +.promo p:first-child, +.aside p:first-child { + margin-top: 0; +} +.note p:last-child, +.promo p:last-child, +.aside p:last-child { + margin-bottom: 0; +}