/*
documentr - Edit, maintain, and present software documentation on the web.
Copyright (C) 2012-2013 Maik Schreiber
This program 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 3 of the License, or
(at your option) any later version.
This program 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.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.pegdown;
import java.util.concurrent.TimeUnit;
import org.apache.commons.lang3.StringUtils;
import org.parboiled.Rule;
import org.parboiled.annotations.Cached;
import org.parboiled.common.ArrayBuilder;
import org.parboiled.support.StringBuilderVar;
import org.parboiled.support.StringVar;
import org.parboiled.support.Var;
import de.blizzy.documentr.markdown.MacroNode;
import de.blizzy.documentr.markdown.PageHeaderNode;
import de.blizzy.documentr.markdown.VerbatimNodeWithType;
public class DocumentrParser extends Parser {
private static final int PEGDOWN_OPTIONS = Extensions.ALL -
Extensions.QUOTES - Extensions.SMARTS - Extensions.SMARTYPANTS;
public DocumentrParser() {
super(PEGDOWN_OPTIONS, TimeUnit.MILLISECONDS.convert(10, TimeUnit.SECONDS),
Parser.DefaultParseRunnerProvider);
}
@Override
public Rule NonLinkInline() {
return FirstOf(new ArrayBuilder<Rule>()
.add(
BodyMacro(), StandaloneMacro(), PageHeader(),
Str(), Endline(), UlOrStarLine(), Space(), StrongOrEmph(), Image(), Code(), InlineHtml(),
Entity(), EscapedChar())
.addNonNulls(ext(QUOTES) ? new Rule[] { SingleQuoted(), DoubleQuoted(), DoubleAngleQuoted() } : null)
.addNonNulls(ext(SMARTS) ? new Rule[] { Smarts() } : null)
.add(Symbol()).get());
}
public Rule StandaloneMacro() {
StringVar macroName = new StringVar();
StringVar params = new StringVar();
return NodeSequence(
"{{", //$NON-NLS-1$
MacroNameAndParameters(macroName, params, true),
push(new MacroNode(getSimpleMacroName(macroName.get()), params.get())),
"/}}"); //$NON-NLS-1$
}
String getSimpleMacroName(String macroName) {
return StringUtils.substringBefore(macroName, ":"); //$NON-NLS-1$
}
public Rule BodyMacro() {
StringVar macroName = new StringVar();
StringVar params = new StringVar();
StringBuilderVar inner = new StringBuilderVar();
return NodeSequence(
"{{", //$NON-NLS-1$
MacroNameAndParameters(macroName, params, false),
"}}", //$NON-NLS-1$
push(getContext().getCurrentIndex()),
BodyMacroBody(inner, macroName),
"{{/", //$NON-NLS-1$
MacroName(macroName),
"}}", //$NON-NLS-1$
push(new MacroNode(getSimpleMacroName(macroName.get()), params.get(),
withIndicesShifted(parseInternal(inner.appended("\n\n")), (Integer) pop()).getChildren()))); //$NON-NLS-1$
}
public Rule BodyMacroBody(StringBuilderVar inner, StringVar macroName) {
return Sequence(
ZeroOrMore(BlankLine()),
ZeroOrMore(
TestNot(Sequence("{{/", MacroName(macroName), "}}")), //$NON-NLS-1$ //$NON-NLS-2$
ANY,
inner.append(match())),
ZeroOrMore(BlankLine()));
}
public Rule MacroNameAndParameters(StringVar macroName, StringVar params, boolean standalone) {
return Sequence(
MacroName(macroName),
Optional(
Sequence(
Spacechar(),
MacroParameters(params, standalone)
)
)
);
}
public Rule MacroName(StringVar macroName) {
return Sequence(
Sequence(
OneOrMore(Alphanumeric()),
Optional(Sequence(':', OneOrMore(Digit())))
),
macroName.isSet() && match().equals(macroName.get()) ||
macroName.isNotSet() && macroName.set(match()));
}
public Rule MacroParameters(StringVar params, boolean standalone) {
return Sequence(
OneOrMore(
TestNot(standalone ? "/}}" : "}}"), //$NON-NLS-1$ //$NON-NLS-2$
ANY),
params.isSet() && match().equals(params.get()) ||
params.isNotSet() && params.set(match()));
}
@Override
@Cached
public Rule LinkSource() {
StringBuilderVar url = new StringBuilderVar();
return FirstOf(
Sequence('(', LinkSource(), ')'),
Sequence('<', LinkSource(), '>'),
Sequence('#', AnchorName(url)),
Sequence(TestNot(AnyOf("#")), Url(url)), //$NON-NLS-1$
push(StringUtils.EMPTY)
);
}
public Rule AnchorName(StringBuilderVar url) {
return Sequence(AnchorNameChars(url), push("#" + url.getString())); //$NON-NLS-1$
}
public Rule Url(StringBuilderVar url) {
return Sequence(UrlChars(url), push(url.getString()));
}
public Rule AnchorNameChars(StringBuilderVar url) {
return OneOrMore(
Sequence(NoneOf("()<>[]#"), url.append(matchedChar())) //$NON-NLS-1$
);
}
public Rule UrlChars(StringBuilderVar url) {
return OneOrMore(
FirstOf(
Sequence('\\', AnyOf("()"), url.append(matchedChar())), //$NON-NLS-1$
Sequence(
TestNot(AnyOf("()>")), //$NON-NLS-1$
FirstOf(Nonspacechar(), Spacechar(), '|'),
url.append(matchedChar()))
)
);
}
public Rule PageHeader() {
StringBuilderVar inner = new StringBuilderVar();
return NodeSequence(
"{{:header:}}", //$NON-NLS-1$
push(getContext().getCurrentIndex()),
PageHeaderBody(inner),
"{{:/header:}}", //$NON-NLS-1$
push(new PageHeaderNode(
withIndicesShifted(parseInternal(inner.appended("\n\n")), (Integer) pop()).getChildren()))); //$NON-NLS-1$
}
public Rule PageHeaderBody(StringBuilderVar inner) {
return Sequence(
ZeroOrMore(BlankLine()),
ZeroOrMore(
TestNot("{{:/header:}}"), //$NON-NLS-1$
ANY,
inner.append(match())),
ZeroOrMore(BlankLine()));
}
// adapted from https://github.com/sirthias/pegdown/pull/60
@Override
public Rule FencedCodeBlock() {
StringBuilderVar text = new StringBuilderVar();
Var<Integer> markerLength = new Var<Integer>();
StringBuilderVar codeType = new StringBuilderVar();
StringBuilderVar title = new StringBuilderVar();
return NodeSequence(
CodeFenceWithTypeAndTitle(markerLength, codeType, title),
TestNot(CodeFence(markerLength)), // prevent empty matches
ZeroOrMore(BlankLine(), text.append('\n')),
OneOrMore(TestNot(Newline(), CodeFence(markerLength)), ANY, text.append(matchedChar())),
Newline(),
push(new VerbatimNodeWithType(
text.appended('\n').getString(), codeType.getString(), title.getString())),
CodeFence(markerLength)
);
}
@Cached
public Rule CodeFenceWithTypeAndTitle(Var<Integer> markerLength, StringBuilderVar codeType, StringBuilderVar title) {
return Sequence(
FirstOf(NOrMore('~', 3), NOrMore('`', 3)),
(markerLength.isSet() && matchLength() == markerLength.get()) ||
(markerLength.isNotSet() && markerLength.set(matchLength())),
Optional(
Sequence(
CodeType(codeType),
Optional(
Sequence(
':',
CodeTitle(title)
)
)
)
),
Newline()
);
}
public Rule CodeType(StringBuilderVar codeType) {
return OneOrMore(
TestNot(FirstOf(Newline(), ':')),
ANY, codeType.append(matchedChar())
);
}
public Rule CodeTitle(StringBuilderVar title) {
return OneOrMore(
TestNot(Newline()),
ANY, title.append(matchedChar())
);
}
@Override
public Rule CodeFence(Var<Integer> markerLength) {
return CodeFenceWithTypeAndTitle(markerLength, new StringBuilderVar(), new StringBuilderVar());
}
}