From 640376df6d9c69ec61170c9fb3dfc7ae46e36e28 Mon Sep 17 00:00:00 2001 From: James Roper Date: Tue, 2 Apr 2013 17:16:50 +1100 Subject: [PATCH 1/3] Merge basic plugin functionality from typesafehub --- build.sbt | 2 +- src/main/java/org/pegdown/Parser.java | 11 +- .../java/org/pegdown/PegDownProcessor.java | 27 ++++- .../java/org/pegdown/ToHtmlSerializer.java | 20 +++- .../pegdown/plugins/BlockPluginParser.java | 32 +++++ .../pegdown/plugins/InlinePluginParser.java | 32 +++++ .../org/pegdown/plugins/PegDownPlugins.java | 113 ++++++++++++++++++ .../plugins/ToHtmlSerializerPlugin.java | 39 ++++++ .../java/org/pegdown/BlockPluginNode.java | 16 +++ .../java/org/pegdown/InlinePluginNode.java | 16 +++ src/test/java/org/pegdown/PluginParser.java | 49 ++++++++ src/test/resources/pegdown/Plugins.html | 5 + src/test/resources/pegdown/Plugins.md | 5 + .../org/pegdown/AbstractPegDownSpec.scala | 11 +- src/test/scala/org/pegdown/PegDownSpec.scala | 28 +++++ 15 files changed, 397 insertions(+), 9 deletions(-) create mode 100644 src/main/java/org/pegdown/plugins/BlockPluginParser.java create mode 100644 src/main/java/org/pegdown/plugins/InlinePluginParser.java create mode 100644 src/main/java/org/pegdown/plugins/PegDownPlugins.java create mode 100644 src/main/java/org/pegdown/plugins/ToHtmlSerializerPlugin.java create mode 100644 src/test/java/org/pegdown/BlockPluginNode.java create mode 100644 src/test/java/org/pegdown/InlinePluginNode.java create mode 100644 src/test/java/org/pegdown/PluginParser.java create mode 100644 src/test/resources/pegdown/Plugins.html create mode 100644 src/test/resources/pegdown/Plugins.md diff --git a/build.sbt b/build.sbt index 77dfcea..34910c8 100644 --- a/build.sbt +++ b/build.sbt @@ -72,4 +72,4 @@ pomExtra := sirthias Mathias Doenitz - \ No newline at end of file + diff --git a/src/main/java/org/pegdown/Parser.java b/src/main/java/org/pegdown/Parser.java index 9c645bc..1fe6831 100644 --- a/src/main/java/org/pegdown/Parser.java +++ b/src/main/java/org/pegdown/Parser.java @@ -32,6 +32,7 @@ import org.parboiled.support.Var; import org.pegdown.ast.*; import org.pegdown.ast.SimpleNode.Type; +import org.pegdown.plugins.PegDownPlugins; import java.util.ArrayList; import java.util.Arrays; @@ -63,14 +64,20 @@ public ParseRunner get(Rule rule) { protected final int options; protected final long maxParsingTimeInMillis; protected final ParseRunnerProvider parseRunnerProvider; + protected final PegDownPlugins plugins; final List abbreviations = new ArrayList(); final List references = new ArrayList(); long parsingStartTimeStamp = 0L; - public Parser(Integer options, Long maxParsingTimeInMillis, ParseRunnerProvider parseRunnerProvider) { + public Parser(Integer options, Long maxParsingTimeInMillis, ParseRunnerProvider parseRunnerProvider, PegDownPlugins plugins) { this.options = options; this.maxParsingTimeInMillis = maxParsingTimeInMillis; this.parseRunnerProvider = parseRunnerProvider; + this.plugins = plugins; + } + + public Parser(Integer options, Long maxParsingTimeInMillis, ParseRunnerProvider parseRunnerProvider) { + this(options, maxParsingTimeInMillis, parseRunnerProvider, PegDownPlugins.NONE); } public RootNode parse(char[] source) { @@ -98,6 +105,7 @@ public Rule Block() { return Sequence( ZeroOrMore(BlankLine()), FirstOf(new ArrayBuilder() + .add(plugins.getBlockPluginRules()) .add(BlockQuote(), Verbatim()) .addNonNulls(ext(ABBREVIATIONS) ? Abbreviation() : null) .add(Reference(), HorizontalRule(), Heading(), OrderedList(), BulletList(), HtmlBlock()) @@ -569,6 +577,7 @@ public Rule NonAutoLinkInline() { public Rule NonLinkInline() { return FirstOf(new ArrayBuilder() + .add(plugins.getInlinePluginRules()) .add(Str(), Endline(), UlOrStarLine(), Space(), StrongOrEmph(), Image(), Code(), InlineHtml(), Entity(), EscapedChar()) .addNonNulls(ext(QUOTES) ? new Rule[]{SingleQuoted(), DoubleQuoted(), DoubleAngleQuoted()} : null) diff --git a/src/main/java/org/pegdown/PegDownProcessor.java b/src/main/java/org/pegdown/PegDownProcessor.java index ed9dd1b..e77ed48 100644 --- a/src/main/java/org/pegdown/PegDownProcessor.java +++ b/src/main/java/org/pegdown/PegDownProcessor.java @@ -23,6 +23,7 @@ import org.parboiled.Parboiled; import org.pegdown.ast.RootNode; +import org.pegdown.plugins.PegDownPlugins; /** * A clean and lightweight Markdown-to-HTML filter based on a PEG parser implemented with parboiled. @@ -57,16 +58,38 @@ public PegDownProcessor(long maxParsingTimeInMillis) { * @param options the flags of the extensions to enable as a bitmask */ public PegDownProcessor(int options) { - this(options, DEFAULT_MAX_PARSING_TIME); + this(options, DEFAULT_MAX_PARSING_TIME, PegDownPlugins.NONE); } /** * Creates a new processor instance with the given {@link org.pegdown.Extensions} and parsing timeout. * * @param options the flags of the extensions to enable as a bitmask + * @param maxParsingTimeInMillis the parsing timeout */ public PegDownProcessor(int options, long maxParsingTimeInMillis) { - this(Parboiled.createParser(Parser.class, options, maxParsingTimeInMillis, Parser.DefaultParseRunnerProvider)); + this(options, maxParsingTimeInMillis, PegDownPlugins.NONE); + } + + /** + * Creates a new processor instance with the given {@link org.pegdown.Extensions} and plugins. + * + * @param options the flags of the extensions to enable as a bitmask + * @param plugins the plugins to use + */ + public PegDownProcessor(int options, PegDownPlugins plugins) { + this(options, DEFAULT_MAX_PARSING_TIME, plugins); + } + + /** + * Creates a new processor instance with the given {@link org.pegdown.Extensions}, parsing timeout and plugins. + * + * @param options the flags of the extensions to enable as a bitmask + * @param maxParsingTimeInMillis the parsing timeout + * @param plugins the plugins to use + */ + public PegDownProcessor(int options, long maxParsingTimeInMillis, PegDownPlugins plugins) { + this(Parboiled.createParser(Parser.class, options, maxParsingTimeInMillis, Parser.DefaultParseRunnerProvider, plugins)); } /** diff --git a/src/main/java/org/pegdown/ToHtmlSerializer.java b/src/main/java/org/pegdown/ToHtmlSerializer.java index b4225f5..73b59bc 100644 --- a/src/main/java/org/pegdown/ToHtmlSerializer.java +++ b/src/main/java/org/pegdown/ToHtmlSerializer.java @@ -20,12 +20,12 @@ import org.parboiled.common.StringUtils; import org.pegdown.ast.*; +import org.pegdown.plugins.ToHtmlSerializerPlugin; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.ServiceLoader; import java.util.TreeMap; import static org.parboiled.common.Preconditions.checkArgNotNull; @@ -36,6 +36,7 @@ public class ToHtmlSerializer implements Visitor { protected final Map references = new HashMap(); protected final Map abbreviations = new HashMap(); protected final LinkRenderer linkRenderer; + protected final List plugins; protected TableNode currentTableNode; protected int currentTableColumn; @@ -44,16 +45,26 @@ public class ToHtmlSerializer implements Visitor { protected Map verbatimSerializers; public ToHtmlSerializer(LinkRenderer linkRenderer) { + this(linkRenderer, Collections.emptyList()); + } + + public ToHtmlSerializer(LinkRenderer linkRenderer, List plugins) { this.linkRenderer = linkRenderer; + this.plugins = plugins; this.verbatimSerializers = Collections.singletonMap(VerbatimSerializer.DEFAULT, DefaultVerbatimSerializer.INSTANCE); } public ToHtmlSerializer(final LinkRenderer linkRenderer, final Map verbatimSerializers) { + this(linkRenderer, verbatimSerializers, Collections.emptyList()); + } + + public ToHtmlSerializer(final LinkRenderer linkRenderer, final Map verbatimSerializers, final List plugins) { this.linkRenderer = linkRenderer; this.verbatimSerializers = new HashMap(verbatimSerializers); if(!this.verbatimSerializers.containsKey(VerbatimSerializer.DEFAULT)) { this.verbatimSerializers.put(VerbatimSerializer.DEFAULT, DefaultVerbatimSerializer.INSTANCE); } + this.plugins = plugins; } public String toHtml(RootNode astRoot) { @@ -338,8 +349,13 @@ public void visit(SuperNode node) { } public void visit(Node node) { + for (ToHtmlSerializerPlugin plugin : plugins) { + if (plugin.visit(node, this, printer)) { + return; + } + } // override this method for processing custom Node implementations - throw new RuntimeException("Not implemented"); + throw new RuntimeException("Don't know how to handle node " + node); } // helpers diff --git a/src/main/java/org/pegdown/plugins/BlockPluginParser.java b/src/main/java/org/pegdown/plugins/BlockPluginParser.java new file mode 100644 index 0000000..6ef7ff7 --- /dev/null +++ b/src/main/java/org/pegdown/plugins/BlockPluginParser.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2010-2011 Mathias Doenitz + * + * Based on peg-markdown (C) 2008-2010 John MacFarlane + * + * 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 org.pegdown.plugins; + +import org.parboiled.Rule; + +/** + * A parser that provides block parser rules for pegdown. A pegdown plugin should implement this, along with + * {@link org.parboiled.BaseParser} or {@link org.pegdown.Parser} if it wants to use the pegdown utilities. + * + * This interface is intended for use with {@link PegDownPlugins.Builder#withPlugin(Class, Object...)}, in order for + * Java plugins to easily be registered with a parser. + */ +public interface BlockPluginParser { + Rule[] blockPluginRules(); +} diff --git a/src/main/java/org/pegdown/plugins/InlinePluginParser.java b/src/main/java/org/pegdown/plugins/InlinePluginParser.java new file mode 100644 index 0000000..2b8f03d --- /dev/null +++ b/src/main/java/org/pegdown/plugins/InlinePluginParser.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2010-2011 Mathias Doenitz + * + * Based on peg-markdown (C) 2008-2010 John MacFarlane + * + * 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 org.pegdown.plugins; + +import org.parboiled.Rule; + +/** + * A parser that provides inline parser rules for pegdown. A pegdown plugin should implement this, along with + * {@link org.parboiled.BaseParser} or {@link org.pegdown.Parser} if it wants to use the pegdown utilities. + * + * This interface is intended for use with {@link PegDownPlugins.Builder#withPlugin(Class, Object...)}, in order for + * Java plugins to easily be registered with a parser. + */ +public interface InlinePluginParser { + Rule[] inlinePluginRules(); +} diff --git a/src/main/java/org/pegdown/plugins/PegDownPlugins.java b/src/main/java/org/pegdown/plugins/PegDownPlugins.java new file mode 100644 index 0000000..22223e6 --- /dev/null +++ b/src/main/java/org/pegdown/plugins/PegDownPlugins.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2010-2011 Mathias Doenitz + * + * Based on peg-markdown (C) 2008-2010 John MacFarlane + * + * 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 org.pegdown.plugins; + +import org.parboiled.BaseParser; +import org.parboiled.Parboiled; +import org.parboiled.Rule; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Encapsulates the plugins provided to pegdown. + * + * Construct this using @{link PegdownPlugins#builder}, and then passing in either the Java plugin classes, or + * precompiled rules (for greater control, or if using Scala rules). + */ +public class PegDownPlugins { + + private final Rule[] inlinePluginRules; + private final Rule[] blockPluginRules; + + private PegDownPlugins(Rule[] inlinePluginRules, Rule[] blockPluginRules) { + this.inlinePluginRules = inlinePluginRules; + this.blockPluginRules = blockPluginRules; + } + + public Rule[] getInlinePluginRules() { + return inlinePluginRules; + } + + public Rule[] getBlockPluginRules() { + return blockPluginRules; + } + + public static Builder builder() { + return new Builder(); + } + + /** + * Create a builder that is a copy of the existing plugins + */ + public static Builder builder(PegDownPlugins like) { + return builder().withInlinePluginRules(like.getInlinePluginRules()).withBlockPluginRules(like.getBlockPluginRules()); + } + + /** + * Convenience reference to no plugins. + */ + public static PegDownPlugins NONE = builder().build(); + + public static class Builder { + private final List inlinePluginRules = new ArrayList(); + private final List blockPluginRules = new ArrayList(); + + public Builder() { + } + + public Builder withInlinePluginRules(Rule... inlinePlugins) { + this.inlinePluginRules.addAll(Arrays.asList(inlinePlugins)); + return this; + } + + public Builder withBlockPluginRules(Rule... blockPlugins) { + this.blockPluginRules.addAll(Arrays.asList(blockPlugins)); + return this; + } + + /** + * Add a plugin parser. This should either implement {@link InlinePluginParser} or {@link BlockPluginParser}, + * or both. The parser will be enhanced by parboiled before its rules are extracted and registered here. + * + * @param pluginParser the plugin parser class. + * @param arguments the arguments to pass to the constructor of that class. + */ + public Builder withPlugin(Class> pluginParser, Object... arguments) { + // First, check that the parser implements one of the parser interfaces + if (!(InlinePluginParser.class.isAssignableFrom(pluginParser) || + BlockPluginParser.class.isAssignableFrom(pluginParser))) { + throw new IllegalArgumentException("Parser plugin must implement a parser plugin interface to be useful"); + } + BaseParser parser = Parboiled.createParser(pluginParser, arguments); + if (parser instanceof InlinePluginParser) { + withInlinePluginRules(((InlinePluginParser) parser).inlinePluginRules()); + } + if (parser instanceof BlockPluginParser) { + withBlockPluginRules(((BlockPluginParser) parser).blockPluginRules()); + } + return this; + } + + public PegDownPlugins build() { + return new PegDownPlugins(inlinePluginRules.toArray(new Rule[0]), blockPluginRules.toArray(new Rule[0])); + } + } +} diff --git a/src/main/java/org/pegdown/plugins/ToHtmlSerializerPlugin.java b/src/main/java/org/pegdown/plugins/ToHtmlSerializerPlugin.java new file mode 100644 index 0000000..58de94c --- /dev/null +++ b/src/main/java/org/pegdown/plugins/ToHtmlSerializerPlugin.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2010-2011 Mathias Doenitz + * + * Based on peg-markdown (C) 2008-2010 John MacFarlane + * + * 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 org.pegdown.plugins; + +import org.pegdown.Printer; +import org.pegdown.ast.Node; +import org.pegdown.ast.Visitor; + +/** + * A plugin for the {@link org.pegdown.ToHtmlSerializer} + */ +public interface ToHtmlSerializerPlugin { + + /** + * Visit the given node + * + * @param node The node to visit + * @param visitor The visitor, for delegating back to handling children, etc + * @param printer The printer to print output to + * @return true if this plugin knew how to serialize the node, false otherwise + */ + boolean visit(Node node, Visitor visitor, Printer printer); +} diff --git a/src/test/java/org/pegdown/BlockPluginNode.java b/src/test/java/org/pegdown/BlockPluginNode.java new file mode 100644 index 0000000..51fae1d --- /dev/null +++ b/src/test/java/org/pegdown/BlockPluginNode.java @@ -0,0 +1,16 @@ +package org.pegdown; + +import org.pegdown.ast.Node; +import org.pegdown.ast.TextNode; +import org.pegdown.ast.Visitor; + +public class BlockPluginNode extends TextNode { + public BlockPluginNode(String text) { + super(text); + } + + @Override + public void accept(Visitor visitor) { + visitor.visit((Node) this); + } +} diff --git a/src/test/java/org/pegdown/InlinePluginNode.java b/src/test/java/org/pegdown/InlinePluginNode.java new file mode 100644 index 0000000..f22c884 --- /dev/null +++ b/src/test/java/org/pegdown/InlinePluginNode.java @@ -0,0 +1,16 @@ +package org.pegdown; + +import org.pegdown.ast.Node; +import org.pegdown.ast.TextNode; +import org.pegdown.ast.Visitor; + +public class InlinePluginNode extends TextNode { + public InlinePluginNode(String text) { + super(text); + } + + @Override + public void accept(Visitor visitor) { + visitor.visit((Node) this); + } +} diff --git a/src/test/java/org/pegdown/PluginParser.java b/src/test/java/org/pegdown/PluginParser.java new file mode 100644 index 0000000..4d3e584 --- /dev/null +++ b/src/test/java/org/pegdown/PluginParser.java @@ -0,0 +1,49 @@ +package org.pegdown; + +import org.parboiled.BaseParser; +import org.parboiled.Rule; +import org.parboiled.support.StringBuilderVar; +import org.pegdown.plugins.BlockPluginParser; +import org.pegdown.plugins.InlinePluginParser; + +public class PluginParser extends Parser implements InlinePluginParser, BlockPluginParser { + + public PluginParser() { + super(ALL, 1000l, DefaultParseRunnerProvider); + } + + @Override + public Rule[] blockPluginRules() { + return new Rule[] {BlockPlugin()}; + } + + @Override + public Rule[] inlinePluginRules() { + return new Rule[] {InlinePlugin()}; + } + + public Rule InlinePlugin() { + StringBuilderVar text = new StringBuilderVar(); + return NodeSequence( + Ch('%'), + OneOrMore(TestNot(Ch('%')), BaseParser.ANY, text.append(matchedChar())), + push(new InlinePluginNode(text.getString())), + Ch('%') + ); + } + + public Rule BlockPlugin() { + StringBuilderVar text = new StringBuilderVar(); + return NodeSequence( + BlockPluginMarker(), + OneOrMore(TestNot(Newline(), BlockPluginMarker()), BaseParser.ANY, text.append(matchedChar())), + Newline(), + push(new BlockPluginNode(text.appended('\n').getString())), + BlockPluginMarker() + ); + } + + public Rule BlockPluginMarker() { + return Sequence(NOrMore('%', 3), Newline()); + } +} diff --git a/src/test/resources/pegdown/Plugins.html b/src/test/resources/pegdown/Plugins.html new file mode 100644 index 0000000..a4c4747 --- /dev/null +++ b/src/test/resources/pegdown/Plugins.html @@ -0,0 +1,5 @@ +
+A block plugin +
+ +

An inline plugin

\ No newline at end of file diff --git a/src/test/resources/pegdown/Plugins.md b/src/test/resources/pegdown/Plugins.md new file mode 100644 index 0000000..15db206 --- /dev/null +++ b/src/test/resources/pegdown/Plugins.md @@ -0,0 +1,5 @@ +%%% +A block plugin +%%% + +%An inline plugin% \ No newline at end of file diff --git a/src/test/scala/org/pegdown/AbstractPegDownSpec.scala b/src/test/scala/org/pegdown/AbstractPegDownSpec.scala index 2a94fdb..642a525 100644 --- a/src/test/scala/org/pegdown/AbstractPegDownSpec.scala +++ b/src/test/scala/org/pegdown/AbstractPegDownSpec.scala @@ -12,9 +12,8 @@ import ast.Node abstract class AbstractPegDownSpec extends Specification { def test(testName: String)(implicit processor: PegDownProcessor) { - val expectedUntidy = FileUtils.readAllTextFromResource(testName + ".html") - require(expectedUntidy != null, "Test '" + testName + "' not found") - test(testName, tidy(expectedUntidy)) + implicit val htmlSerializer = new ToHtmlSerializer(new LinkRenderer) + testWithSerializer(testName) } def test(testName: String, expectedOutput: String, htmlSerializer: ToHtmlSerializer = null)(implicit processor: PegDownProcessor) { @@ -39,6 +38,12 @@ abstract class AbstractPegDownSpec extends Specification { normalize(tidyHtml) === normalize(expectedOutput) } + def testWithSerializer(testName: String)(implicit processor: PegDownProcessor, htmlSerializer: ToHtmlSerializer) { + val expectedUntidy = FileUtils.readAllTextFromResource(testName + ".html") + require(expectedUntidy != null, "Test '" + testName + "' not found") + test(testName, tidy(expectedUntidy), htmlSerializer) + } + def testAST(testName: String)(implicit processor: PegDownProcessor) { val markdown = FileUtils.readAllCharsFromResource(testName + ".md") require(markdown != null, "Test '" + testName + "' not found") diff --git a/src/test/scala/org/pegdown/PegDownSpec.scala b/src/test/scala/org/pegdown/PegDownSpec.scala index dc60a56..176f47a 100644 --- a/src/test/scala/org/pegdown/PegDownSpec.scala +++ b/src/test/scala/org/pegdown/PegDownSpec.scala @@ -1,11 +1,13 @@ package org.pegdown +import ast.{Visitor, Node} import org.parboiled.Parboiled import Extensions._ import org.pegdown.ast.VerbatimNode import org.parboiled.common.FileUtils import java.util.Collections import scala.collection.immutable.HashMap +import plugins.{ToHtmlSerializerPlugin, PegDownPlugins} class PegDownSpec extends AbstractPegDownSpec { @@ -108,6 +110,32 @@ class PegDownSpec extends AbstractPegDownSpec { runWithSerializerMap("pegdown/GFM_Fenced_Code_Blocks", serializerMap, "_reversed_all") } } + + "allow custom plugins" in { + import scala.collection.JavaConversions._ + implicit val processor = new PegDownProcessor(Parboiled.createParser[Parser, AnyRef](classOf[Parser], + new java.lang.Integer(ALL), new java.lang.Long(1000), Parser.DefaultParseRunnerProvider, + PegDownPlugins.builder().withPlugin(classOf[PluginParser]).build())) + implicit val htmlSerializer = new ToHtmlSerializer(new LinkRenderer, List(new ToHtmlSerializerPlugin { + def visit(node: Node, visitor: Visitor, printer: Printer) = node match { + case blockPlugin: BlockPluginNode => { + printer.print("
") + printer.print(blockPlugin.getText) + printer.print("
") + true + } + case inlinePlugin: InlinePluginNode => { + printer.print("") + printer.print(inlinePlugin.getText) + printer.print("") + true + } + case _ => false + } + })) + + testWithSerializer("pegdown/Plugins") + } } } From 12ac5995996e2704009005ecfbba02d18222ac1c Mon Sep 17 00:00:00 2001 From: Jakub Jirutka Date: Sun, 23 Jun 2013 13:14:48 +0200 Subject: [PATCH 2/3] Update README --- README.markdown | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/README.markdown b/README.markdown index 96791f8..65b2090 100644 --- a/README.markdown +++ b/README.markdown @@ -4,7 +4,7 @@ Introduction _pegdown_ is a pure Java library for clean and lightweight [Markdown] processing based on a [parboiled] PEG parser. _pegdown_ is nearly 100% compatible with the original Markdown specification and fully passes the original Markdown test suite. -On top of the standard Markdown feature set _pegdown_ implements a number of extensions similar to what other popular Markdown processors offer. +On top of the standard Markdown feature set _pegdown_ implements a number of extensions similar to what other popular Markdown processors offer. You can also extend _pegdown_ by your own plugins! Currently _pegdown_ supports the following extensions over standard Markdown: * SMARTS: Beautifies apostrophes, ellipses ("..." and ". . .") and dashes ("--" and "---") @@ -62,6 +62,18 @@ concurrent accesses, since neither the [PegDownProcessor] nor the underlying par See for the pegdown API documentation. +Plugins +------- + +Since parsing and serialisation are two different things, there are two different plugin mechanisms, one for the parser, and one for the [ToHtmlSerializer]. Most plugins would probably implement both, but it is possible that a plugin might just implement the parser plugin interface. + +For the parser, there are two plugin points, one for inline plugins (inside a paragraph), and one for block plugins. These are provided to the parser using the [PegDownPlugins] class. For convenience of use, this comes with its own builder. You can either pass individual rules to this builder (which is what you probably would do if you were using Scala rules), but you can also pass it a parboiled Java parser class which implements either [InlinePluginParser] or [BlockPluginParser] or both. [PegDownPlugins] will enhance this parser for you, so as a user of a plugin you just need to pass the class to it (and the arguments for that classes constructor, if any). To implement the plugin, you would write a normal parboiled parser, and implement the appropriate parser plugin interface. You can extend the pegdown parser, this is useful if you want to reuse any of its rules. + +For the serializer, there is [ToHtmlSerializerPlugin] interface. It is called when a node that the [ToHtmlSerializer] doesn't know how to process is encountered (i.e. one produced by a parser plugin). Its `accept` method is passed the node, the visitor (so if the node contains child nodes, they can be rendered using the parent), and the printer for the plugin to print to. The `accept` method returns true if it knew how to handle the node, or false if otherwise, and the [ToHtmlSerializer] loops through each plugin, breaking when it reaches one that returns true, and if it finds none, throws an exception like it used to. + +As an very simple example you might want to take a look at the [sources of the PluginParser test class][PluginParser]. + + Parsing Timeouts ---------------- @@ -116,3 +128,8 @@ Along with any patches, please state that the patch is your original work and th [idea-markdown plugin]: https://github.com/nicoulaj/idea-markdown [SBT]: http://www.scala-sbt.org/ [Node]: http://www.decodified.com/pegdown/api/org/pegdown/ast/Node.html + [PegDownPlugins]: http://github.com/sirthias/pegdown/blob/master/src/main/java/org/pegdown/plugins/PegDownPlugins.java + [InlinePluginParser]: http://github.com/sirthias/pegdown/blob/master/src/main/java/org/pegdown/plugins/InlinePluginParser.java + [BlockPluginParser]: http://github.com/sirthias/pegdown/blob/master/src/main/java/org/pegdown/plugins/BlockPluginParser.java + [ToHtmlSerializerPlugin]: http://github.com/sirthias/pegdown/blob/master/src/main/java/org/pegdown/plugins/ToHtmlSerializerPlugin.java + [PluginParser]: http://github.com/sirthias/pegdown/blob/master/src/test/java/org/pegdown/PluginParser.java From cd57daa848ffe087baf55ba71268b19f9223d2ea Mon Sep 17 00:00:00 2001 From: Jakub Jirutka Date: Sun, 23 Jun 2013 13:15:27 +0200 Subject: [PATCH 3/3] Prepare for release 1.4.0 --- CHANGELOG | 7 ++++++- README.markdown | 2 +- build.sbt | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index b57c69d..89a33b5 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,8 @@ +Version 1.4.0 (2013-06-23) +-------------------------- +- Added basic plugin support (see #77, thx to James Roper and also Jakub Jirutka) + + Version 1.3.0 (2013-06-04) -------------------------- - Upgraded to parboiled 1.1.5 @@ -134,4 +139,4 @@ Version 0.8.1.0 (2010-06-08) Version 0.8.0.1 (2010-04-30) ---------------------------- -first public release \ No newline at end of file +first public release diff --git a/README.markdown b/README.markdown index 65b2090..ea10f3b 100644 --- a/README.markdown +++ b/README.markdown @@ -34,7 +34,7 @@ Installation You have two options: * Download the JAR for the latest version from [here](http://repo1.maven.org/maven2/org/pegdown/pegdown/). - _pegdown_ 1.3.0 has only one dependency: [parboiled for Java][parboiled], version 1.1.5. + _pegdown_ 1.4.0 has only one dependency: [parboiled for Java][parboiled], version 1.1.5. * The pegdown artifact is also available from maven central with group id **org.pegdown** and artifact-id **pegdown**. diff --git a/build.sbt b/build.sbt index 34910c8..d894f73 100644 --- a/build.sbt +++ b/build.sbt @@ -1,6 +1,6 @@ name := "pegdown" -version := "1.3.0" +version := "1.4.0" homepage := Some(new URL("http://pegdown.org"))