Merge branch 'develop' into master

This commit is contained in:
marihachi 2021-04-15 17:58:57 +09:00
commit d238c1dad4
4 changed files with 210 additions and 14 deletions

View file

@ -1,6 +1,6 @@
{
"name": "mfm-js",
"version": "0.14.1",
"version": "0.15.0",
"description": "An MFM parser implementation with PEG.js",
"main": "./built/index.js",
"types": "./built/index.d.ts",
@ -12,6 +12,7 @@
"tsc": "tsc",
"tsd": "tsd",
"parse": "node ./built/cli/parse",
"parse-plain": "node ./built/cli/parsePlain",
"test": "mocha -r ts-node/register 'test/**/*.ts' && npm run tsd"
},
"repository": {

46
src/cli/parsePlain.ts Normal file
View file

@ -0,0 +1,46 @@
import { performance } from 'perf_hooks';
import inputLine, { InputCanceledError } from './misc/inputLine';
import { parsePlain } from '..';
async function entryPoint() {
console.log('intaractive plain parser');
while (true) {
let input: string;
try {
input = await inputLine('> ');
}
catch (err) {
if (err instanceof InputCanceledError) {
console.log('bye.');
return;
}
throw err;
}
// replace special chars
input = input
.replace(/\\n/g, '\n')
.replace(/\\t/g, '\t')
.replace(/\\u00a0/g, '\u00a0');
try {
const parseTimeStart = performance.now();
const result = parsePlain(input);
const parseTimeEnd = performance.now();
console.log(JSON.stringify(result));
const parseTime = (parseTimeEnd - parseTimeStart).toFixed(3);
console.log(`parsing time: ${parseTime}ms`);
}
catch (err) {
console.log('parsing error:');
console.log(err);
}
console.log();
}
}
entryPoint()
.catch(err => {
console.log(err);
process.exit(1);
});

View file

@ -66,7 +66,7 @@ fullParser
= nodes:(&. n:(block / inline) { return n; })* { return mergeText(nodes); }
plainParser
= nodes:(&. n:(emojiCode / unicodeEmoji / text) { return n; })* { return mergeText(nodes); }
= nodes:(&. n:(emojiCode / unicodeEmoji / plainText) { return n; })* { return mergeText(nodes); }
inlineParser
= nodes:(&. n:inline { return n; })* { return mergeText(nodes); }
@ -165,7 +165,7 @@ inline
/ url
/ link
/ fn
/ text
/ inlineText
// inline: emoji code
@ -219,16 +219,22 @@ small
// inline: italic
italic
= italicTag
/ italicAlt
italicTag
= "<i>" content:(!"</i>" i:inline { return i; })+ "</i>"
{
return ITALIC(mergeText(content));
}
/ "*" content:$(!"*" ([a-z0-9]i / _))+ "*"
italicAlt
= "*" content:$(!"*" ([a-z0-9]i / _))+ "*" &(EOF / LF / _)
{
const parsedContent = applyParser(content, 'inlineParser');
return ITALIC(parsedContent);
}
/ "_" content:$(!"_" ([a-z0-9]i / _))+ "_"
/ "_" content:$(!"_" ([a-z0-9]i / _))+ "_" &(EOF / LF / _)
{
const parsedContent = applyParser(content, 'inlineParser');
return ITALIC(parsedContent);
@ -289,7 +295,7 @@ mentionHostPart
// inline: hashtag
hashtag
= "#" content:hashtagContent
= "#" !("\uFE0F"? "\u20E3") content:hashtagContent
{
return HASHTAG(content);
}
@ -382,7 +388,13 @@ fnArg
// inline: text
text
inlineText
= !(LF / _) . &(hashtag / mention / italicAlt) . { return text(); } // hashtag, mention, italic ignore
/ . /* text node */
// inline: text (for plainParser)
plainText
= . /* text node */
//

View file

@ -4,7 +4,29 @@ import {
TEXT, CENTER, FN, UNI_EMOJI, MENTION, EMOJI_CODE, HASHTAG, N_URL, BOLD, SMALL, ITALIC, STRIKE, QUOTE, MATH_BLOCK, SEARCH, CODE_BLOCK, LINK
} from '../built/index';
describe('parser', () => {
describe('PlainParser', () => {
describe('text', () => {
it('basic', () => {
const input = 'abc';
const output = [TEXT('abc')];
assert.deepStrictEqual(mfm.parsePlain(input), output);
});
it('ignore hashtag', () => {
const input = 'abc#abc';
const output = [TEXT('abc#abc')];
assert.deepStrictEqual(mfm.parsePlain(input), output);
});
it('keycap number sign', () => {
const input = 'abc#⃣abc';
const output = [TEXT('abc'), UNI_EMOJI('#️⃣'), TEXT('abc')];
assert.deepStrictEqual(mfm.parsePlain(input), output);
});
});
});
describe('FullParser', () => {
describe('text', () => {
it('普通のテキストを入力すると1つのテキストードが返される', () => {
const input = 'abc';
@ -222,6 +244,12 @@ describe('parser', () => {
const output = [TEXT('今起きた'), UNI_EMOJI('😇')];
assert.deepStrictEqual(mfm.parse(input), output);
});
it('keycap number sign', () => {
const input = 'abc#⃣123';
const output = [TEXT('abc'), UNI_EMOJI('#️⃣'), TEXT('123')];
assert.deepStrictEqual(mfm.parse(input), output);
});
});
describe('big', () => {
@ -338,7 +366,7 @@ describe('parser', () => {
});
});
describe('italic 1', () => {
describe('italic tag', () => {
it('basic', () => {
const input = '<i>abc</i>';
const output = [
@ -376,7 +404,7 @@ describe('parser', () => {
});
});
describe('italic 2', () => {
describe('italic alt 1', () => {
it('basic', () => {
const input = '*abc*';
const output = [
@ -386,6 +414,54 @@ describe('parser', () => {
];
assert.deepStrictEqual(mfm.parse(input), output);
});
it('basic 2', () => {
const input = 'before *abc* after';
const output = [
TEXT('before '),
ITALIC([
TEXT('abc')
]),
TEXT(' after')
];
assert.deepStrictEqual(mfm.parse(input), output);
});
it('ignore a italic syntax if the before char is neither a space nor an LF', () => {
const input = 'before*abc*after';
const output = [TEXT('before*abc*after')];
assert.deepStrictEqual(mfm.parse(input), output);
});
});
describe('italic alt 2', () => {
it('basic', () => {
const input = '_abc_';
const output = [
ITALIC([
TEXT('abc')
])
];
assert.deepStrictEqual(mfm.parse(input), output);
});
it('basic 2', () => {
const input = 'before _abc_ after';
const output = [
TEXT('before '),
ITALIC([
TEXT('abc')
]),
TEXT(' after')
];
assert.deepStrictEqual(mfm.parse(input), output);
});
it('ignore a italic syntax if the before char is neither a space nor an LF', () => {
const input = 'before_abc_after';
const output = [TEXT('before_abc_after')];
assert.deepStrictEqual(mfm.parse(input), output);
});
});
// strike
@ -394,12 +470,73 @@ describe('parser', () => {
// mathInline
// mention
describe('mention', () => {
it('basic', () => {
const input = '@abc';
const output = [MENTION('abc', null, '@abc')];
assert.deepStrictEqual(mfm.parse(input), output);
});
it('basic 2', () => {
const input = 'before @abc after';
const output = [TEXT('before '), MENTION('abc', null, '@abc'), TEXT(' after')];
assert.deepStrictEqual(mfm.parse(input), output);
});
it('basic remote', () => {
const input = '@abc@misskey.io';
const output = [MENTION('abc', 'misskey.io', '@abc@misskey.io')];
assert.deepStrictEqual(mfm.parse(input), output);
});
it('basic remote 2', () => {
const input = 'before @abc@misskey.io after';
const output = [TEXT('before '), MENTION('abc', 'misskey.io', '@abc@misskey.io'), TEXT(' after')];
assert.deepStrictEqual(mfm.parse(input), output);
});
it('basic remote 3', () => {
const input = 'before\n@abc@misskey.io\nafter';
const output = [TEXT('before\n'), MENTION('abc', 'misskey.io', '@abc@misskey.io'), TEXT('\nafter')];
assert.deepStrictEqual(mfm.parse(input), output);
});
it('ignore format of mail address', () => {
const input = 'abc@example.com';
const output = [TEXT('abc@example.com')];
assert.deepStrictEqual(mfm.parse(input), output);
});
});
describe('hashtag', () => {
it('and unicode emoji', () => {
const input = '#⃣abc123#abc';
const output = [UNI_EMOJI('#️⃣'), TEXT('abc123'), HASHTAG('abc')];
it('basic', () => {
const input = '#abc';
const output = [HASHTAG('abc')];
assert.deepStrictEqual(mfm.parse(input), output);
});
it('basic 2', () => {
const input = 'before #abc after';
const output = [TEXT('before '), HASHTAG('abc'), TEXT(' after')];
assert.deepStrictEqual(mfm.parse(input), output);
});
it('with keycap number sign', () => {
const input = '#⃣abc123 #abc';
const output = [UNI_EMOJI('#️⃣'), TEXT('abc123 '), HASHTAG('abc')];
assert.deepStrictEqual(mfm.parse(input), output);
});
it('with keycap number sign 2', () => {
const input = `abc
#abc`;
const output = [TEXT('abc\n'), UNI_EMOJI('#️⃣'), TEXT('abc')];
assert.deepStrictEqual(mfm.parse(input), output);
});
it('ignore a hashtag if the before char is neither a space nor an LF', () => {
const input = 'abc#abc';
const output = [TEXT('abc#abc')];
assert.deepStrictEqual(mfm.parse(input), output);
});
});