-
marihachi authoredb0a91cee
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
util.ts 4.10 KiB
import { isMfmBlock, MfmNode, TEXT } from '../node';
export function mergeText(nodes: (MfmNode | string)[]): MfmNode[] {
const dest: MfmNode[] = [];
const storedChars: string[] = [];
/**
* Generate a text node from the stored chars, And push it.
*/
function generateText() {
if (storedChars.length > 0) {
dest.push(TEXT(storedChars.join('')));
storedChars.length = 0;
}
}
for (const node of nodes) {
if (typeof node == 'string') {
// Store the char.
storedChars.push(node);
}
else if (node.type == 'text') {
// Store the text.
storedChars.push(node.props.text);
}
else {
generateText();
dest.push(node);
}
}
generateText();
return dest;
}
export function stringifyNode(node: MfmNode): string {
switch(node.type) {
// block
case 'quote': {
return stringifyTree(node.children).split('\n').map(line => `> ${line}`).join('\n');
}
case 'search': {
return node.props.content;
}
case 'blockCode': {
return `\`\`\`${ node.props.lang ?? '' }\n${ node.props.code }\n\`\`\``;
}
case 'mathBlock': {
return `\\[\n${ node.props.formula }\n\\]`;
}
case 'center': {
return `<center>\n${ stringifyTree(node.children) }\n</center>`;
}
// inline
case 'emojiCode': {
return `:${ node.props.name }:`;
}
case 'unicodeEmoji': {
return node.props.emoji;
}
case 'bold': {
return `**${ stringifyTree(node.children) }**`;
}
case 'small': {
return `<small>${ stringifyTree(node.children) }</small>`;
}
case 'italic': {
return `<i>${ stringifyTree(node.children) }</i>`;
}
case 'strike': {
return `~~${ stringifyTree(node.children) }~~`;
}
case 'inlineCode': {
return `\`${ node.props.code }\``;
}
case 'mathInline': {
return `\\(${ node.props.formula }\\)`;
}
case 'mention': {
return node.props.acct;
}
case 'hashtag': {
return `#${ node.props.hashtag }`;
}
case 'url': {
return node.props.url;
}
case 'link': {
const prefix = node.props.silent ? '?' : '';
return `${ prefix }[${ stringifyTree(node.children) }](${ node.props.url })`;
}
case 'fn': {
const argFields = Object.keys(node.props.args).map(key => {
const value = node.props.args[key];
if (value === true) {
return key;
}
else {
return `${ key }=${ value }`;
}
});
const args = (argFields.length > 0) ? '.' + argFields.join(',') : '';
return `$[${ node.props.name }${ args } ${ stringifyTree(node.children) }]`;
}
case 'text': {
return node.props.text;
}
}
throw new Error('unknown mfm node');
}
enum stringifyState {
none = 0,
inline,
block
};
export function stringifyTree(nodes: MfmNode[]): string {
let dest: MfmNode[] = [];
let state: stringifyState = stringifyState.none;
for (const node of nodes) {
// 文脈に合わせて改行を追加する。
// none -> inline : No
// none -> block : No
// inline -> inline : No
// inline -> block : Yes
// block -> inline : Yes
// block -> block : Yes
let pushLf: boolean = true;
if (isMfmBlock(node)) {
if (state == stringifyState.none) {
pushLf = false;
}
state = stringifyState.block;
}
else {
if (state == stringifyState.none || state == stringifyState.inline) {
pushLf = false;
}
state = stringifyState.inline;
}
if (pushLf) {
dest.push(TEXT('\n'));
}
dest.push(node);
}
return dest.map(n => stringifyNode(n)).join('');
}
export function inspectOne(node: MfmNode, action: (node: MfmNode) => void) {
action(node);
if (node.children != null) {
for (const child of node.children) {
inspectOne(child, action);
}
}
}
//
// dynamic consuming
//
/*
1. If you want to consume 3 chars, call the setConsumeCount.
```
setConsumeCount(3);
```
2. And the rule to consume the input is as below:
```
rule = (&{ return consumeDynamically(); } .)+
```
*/
let consumeCount = 0;
/**
* set the length of dynamic consuming.
*/
export function setConsumeCount(count: number) {
consumeCount = count;
}
/**
* consume the input and returns matching result.
*/
export function consumeDynamically() {
const matched = (consumeCount > 0);
if (matched) {
consumeCount--;
}
return matched;
}