Changes
4 changed files (+112/-38)
-
-
@@ -17,15 +17,47 @@ return null;}; }; export const regexp = ( export const oneOrMore = <A extends Node, B extends Node>( parser: Parser<A>, andThen: (nodes: readonly A[]) => B, ): Parser<B> => { const rec = ( text: string, position: number, rootParser: ParseText, ): readonly NonNullable<ReturnType<typeof parser>>[] => { const match = parser(text, position, rootParser); if (!match) { return []; } const [, nextPosition] = match; return [match, ...rec(text, nextPosition, rootParser)]; }; return (text, position, rootParser) => { const ret = rec(text, position, rootParser); if (ret.length === 0) { return null; } const [, lastPosition] = ret[ret.length - 1]; return [andThen(ret.map(([a]) => a)), lastPosition]; }; }; export const regexp = <T extends Node = Node>( pattern: RegExp, callback: ( match: string[], text: string, position: number, parseText: ParseText, ) => [Node, number] | null, ): Parser => ) => [T, number] | null, ): Parser<T> => (text, position, parseText) => { const match = text.substring(position).match(pattern);
-
@@ -51,15 +83,16 @@return parser(text, position, parseText); }; export const topOfLine = (parser: Parser): Parser => ( text, position, parseText, ) => { if (position > 0 && text.charAt(position - 1) !== "\n") { return null; } export const topOfLine = <T extends Node = Node>(parser: Parser<T>): Parser<T> => ( text, position, parseText, ) => { if (position > 0 && text.charAt(position - 1) !== "\n") { return null; } return parser(text, position, parseText); }; return parser(text, position, parseText); };
-
-
-
@@ -1,6 +1,6 @@import { NodeType } from "./types/Node.ts"; import { type Node, NodeType, type Quote } from "./types/Node.ts"; import { explicit, or, regexp, topOfLine } from "./combinator.ts"; import { explicit, oneOrMore, or, regexp, topOfLine } from "./combinator.ts"; const parseBold = explicit( regexp(
-
@@ -89,12 +89,20 @@ },), ); const parseSingleLineQuote = topOfLine( regexp(/^>(.*)(\n|$)/, (match, _text, position, parseText) => { const parseQuoteLine = topOfLine( regexp(/^>(.*)(\n>|\n|$)/, (match, _text, position, parseText) => { const [matchedText, content] = match; const repeatedGt = content.match(/^((>)+)(.*)$/); // If the next line is also starts with ">", do not include the character // (simulating RegExp's non-capturing group) const source = matchedText.replace(/\n>$/, "\n"); // `source` and `matchedText` are same unless the next line starts with ">" // due to the above line removes the ">". const isNextLineStartsWithGt = source !== matchedText; return [ { type: NodeType.Quote,
-
@@ -107,28 +115,39 @@ source: repeatedGt[1],}, ...parseText(repeatedGt[3]), ] : parseText(content), source: matchedText, // Only the last LF could be a terminator character of quote. // Non-last LFs should be parsed as well as `content`. : parseText(isNextLineStartsWithGt ? content + "\n" : content), source, }, position + matchedText.length, position + source.length, ]; }), ); const parseMultilineQuote = topOfLine( regexp(/^>>>([\s\S]+)$/, (match, _text, position, parseText) => { const [matchedText, content] = match; const parseQuote = or([ topOfLine( regexp(/^>>>([\s\S]+)$/, (match, _text, position, parseText) => { const [matchedText, content] = match; return [ { type: NodeType.Quote, children: parseText(content), source: matchedText, }, position + matchedText.length, ]; return [ { type: NodeType.Quote, children: parseText(content), source: matchedText, }, position + matchedText.length, ]; }), ), oneOrMore<Quote, Quote>(parseQuoteLine, (quotes) => { return { type: NodeType.Quote, children: quotes.map((quote) => quote.children).flat(), source: quotes.map((quote) => quote.source).join(""), }; }), ); ]); const parseEmoji = regexp( /^:([^:<`*#@!\s()$%]+):(:(skin-tone-.+?):)?/,
-
@@ -147,7 +166,7 @@ ];}, ); const parseLink = regexp( const parseLink = regexp<Node>( /^<([^\s<>][^\n<>]*?)(\|([^<>]+?))?>/, (match, _text, position, parseText) => { const [matchedText, link, _, label] = match;
-
@@ -209,8 +228,7 @@ parsePreText,parseCode, parseEmoji, parseItalic, parseMultilineQuote, parseSingleLineQuote, parseQuote, parseLink, parseStrike, ]);
-
-
-
@@ -7,6 +7,7 @@ bold,code, emoji, italic, quote, root, strike, text,
-
@@ -129,3 +130,25 @@ bold([text("Y")]),]), ); }); // https://github.com/pocka/slack-message-parser/issues/38 Deno.test(`#38 / parse multiline quote without >>>`, () => { const actual = parse( "This is unquoted text\n> This is quoted text\n> This is still quoted text\nThis is unquoted text again", ); assertEquals( actual, root([ text("This is unquoted text\n"), { ...quote([ text(" This is quoted text\n"), text(" This is still quoted text"), ], true), source: "> This is quoted text\n> This is still quoted text\n", }, text("This is unquoted text again"), ]), ); });
-
-
-
@@ -1,9 +1,9 @@import { Node } from "./Node.ts"; export type Parser = ( export type Parser<T extends Node = Node> = ( text: string, position: number, parseText: ParseText, ) => [Node, number] | null; ) => [T, number] | null; export type ParseText = (text: string) => Node[];
-