matrix-wug/x2i/index.ts

167 lines
4.6 KiB
TypeScript
Raw Permalink Normal View History

2018-12-03 02:12:20 +01:00
import * as fs from "fs";
import * as yaml from "js-yaml";
import * as OuterXRegExp from "xregexp";
interface IRawReplaceKey {
raw: ReplaceKey;
}
interface INestedKey {
delimiters: [string, string];
translations: Replacer[];
}
type ReplaceKey = [string, string];
type Replacer = ReplaceKey | INestedKey | IRawReplaceKey;
type CompiledReplacer = [
RegExp,
string | ((m: { [key: string]: string }) => string),
string
];
interface IMatchInstructions {
keys?: CompiledReplacer[];
2018-12-03 02:12:20 +01:00
join?(left: string, match: string, right: string): string;
matchFunction?: Function;
2018-12-03 02:12:20 +01:00
}
const regex = OuterXRegExp(
`(?:(^|[\`\\p{White_Space}])) # must be preceded by whitespace or surrounded by code brackets
([A-Za-z]*) # key, to lower (2)
([/[]) # bracket left (3)
(\\S|\\S.*?\\S) # body (4)
([/\\]]) # bracket right (5)
(?=$|[\`\\p{White_Space}\\pP]) # must be followed by a white space or punctuation`,
"gmx");
const defaultMatchAction = (left: string, match: string, right: string) => left + match + right;
const defaultMatchFunction = (match: string, keys: ([RegExp, string | ((m: {[key: string]: string;}) => string), string][])) => OuterXRegExp.replaceEach(match, keys as (RegExp | string)[][])
2019-03-07 02:23:12 +01:00
import * as chr from "./dictionaries/chr";
2019-03-15 01:16:51 +01:00
import * as nav from "./dictionaries/nav";
2018-12-03 02:12:20 +01:00
const matchType: { [key: string]: IMatchInstructions } = {
chr: {
join: (_, match) => `- ${match}`,
matchFunction: chr.convert,
},
2019-03-15 01:16:51 +01:00
nav: {
join: (_, match) => `- ${match}`,
matchFunction: nav.convert,
},
2019-03-01 02:45:09 +01:00
ik: {
join: (_, match) => `- ${match}`,
2019-03-07 02:23:12 +01:00
keys: readKeys("./x2i/dictionaries/ik-keys.yaml"),
2019-03-01 02:45:09 +01:00
},
2018-12-03 02:12:20 +01:00
p: {
join: (_, match) => `*${match}`,
2019-03-07 02:23:12 +01:00
keys: readKeys("./x2i/dictionaries/apie-keys.yaml"),
2018-12-03 02:12:20 +01:00
},
x: {
2019-03-07 02:23:12 +01:00
keys: readKeys("./x2i/dictionaries/x2i-keys.yaml"),
2018-12-03 02:12:20 +01:00
},
z: {
2019-03-07 02:23:12 +01:00
keys: readKeys("./x2i/dictionaries/z2i-keys.yaml"),
2018-12-03 02:12:20 +01:00
},
2019-02-28 13:08:25 +01:00
i: {
2019-03-01 02:45:09 +01:00
join: (_, match) => `- ${match}`,
2019-03-07 02:23:12 +01:00
keys: readKeys("./x2i/dictionaries/i-keys.yaml"),
2019-02-28 13:08:25 +01:00
},
2018-12-03 02:12:20 +01:00
};
/**
* Read translation keys from file. Escapes strings first.
*
* @param fpath File to key definitions. (yaml, utf8)
* @returns Compiled keys.
*/
function readKeys(fpath: string) {
var keys: any = yaml
2018-12-03 02:12:20 +01:00
.safeLoad(fs.readFileSync(fpath, "utf8"))
return keys
2018-12-03 02:12:20 +01:00
.map(compileKey)
.filter(Boolean) as CompiledReplacer[];
}
/**
* Compiles a plain object into a regexp thing.
*
* @param entry Regex and replacement pair, or delimited match object.
*/
function compileKey(entry: Replacer): CompiledReplacer | undefined {
if (Array.isArray(entry)) {
const [key, val] = entry;
return [OuterXRegExp(OuterXRegExp.escape(key)), val, "all"];
}
// don't escape key
if ("raw" in entry) {
const [key, val] = entry.raw;
return [OuterXRegExp(key), val, "all"];
}
// is a dict
try {
const {
delimiters: [left, right],
translations,
} = entry;
return [
OuterXRegExp(`${OuterXRegExp.escape(left)}(?<inside>.*?)${OuterXRegExp.escape(right)}`),
m => OuterXRegExp.replaceEach(
m.inside,
translations.map(compileKey) as (RegExp | string)[][],
),
"all"];
} catch (e) {
console.log(`${entry} is not an array or a proper object, ignoring`);
}
}
/**
* Convert four-tuple of Strings into a specified "official" representation
*
* @param key What kind of conversion key is appropriate
* @param left Left bracket
* @param match Body
* @param right Right bracket
* @returns Converted item, if any.
*/
export function force(key: string, left: string, match: string, right: string) {
const lowerKey = key.toLowerCase();
if (!(lowerKey in matchType)) return;
const { keys, join, matchFunction } = matchType[lowerKey];
if (keys || matchFunction) {
2018-12-03 02:12:20 +01:00
const action = join || defaultMatchAction;
const doFunction = matchFunction || defaultMatchFunction;
2018-12-03 02:12:20 +01:00
// need to use `as (RegExp | string)[][]` because the provided typings are too generic
return action(left, doFunction(match, keys), right);
2018-12-03 02:12:20 +01:00
}
}
/**
* Grab all x2i strings in message string.
*
* @param content Full message that may or may not contain x2i strings
* @returns Converted representations
*/
export default function x2i(content: string) {
const results: string[] = [];
OuterXRegExp.forEach(content, regex, match => {
const parts = match.slice(2, 6);
if (parts.length === 4) {
const [k, l, m, r] = parts;
const converted = force(k, l, m, r); // eg x, [, text, ]
if (converted) {
results.push(converted);
}
}
});
return results.join(" ");
}