401 lines
10 KiB
TypeScript
401 lines
10 KiB
TypeScript
|
import process, { stdout } from 'node:process';
|
||
|
import fs from 'fs/promises';
|
||
|
import { FileHandle } from 'node:fs/promises';
|
||
|
import nPath from 'node:path';
|
||
|
|
||
|
type Command = {
|
||
|
type: 'command'
|
||
|
start: number;
|
||
|
text: string;
|
||
|
name: string;
|
||
|
options: Text[];
|
||
|
inner: Text[];
|
||
|
};
|
||
|
|
||
|
type Comment = {
|
||
|
type: 'comment'
|
||
|
start: number;
|
||
|
text: string;
|
||
|
};
|
||
|
|
||
|
type Range = {
|
||
|
type: 'range'
|
||
|
start: number;
|
||
|
text: string;
|
||
|
};
|
||
|
|
||
|
type Token = Command | Range | Comment;
|
||
|
|
||
|
type Text = {
|
||
|
start: number,
|
||
|
tokens: Token[];
|
||
|
};
|
||
|
|
||
|
function processComment(data: string, start: number): {comment: Comment, i: number} {
|
||
|
|
||
|
let comment: Comment = {
|
||
|
type: 'comment',
|
||
|
start,
|
||
|
text: "",
|
||
|
};
|
||
|
|
||
|
for (let i = start; i < data.length; i++) {
|
||
|
const char = data[i];
|
||
|
comment.text += char;
|
||
|
if (char == '\n')
|
||
|
return {comment, i};
|
||
|
}
|
||
|
|
||
|
return {comment, i: data.length - 1};
|
||
|
}
|
||
|
|
||
|
function isValid(test: string) {
|
||
|
return test.match(/[a-zA-Z_\\*]/);
|
||
|
}
|
||
|
|
||
|
function processCommand(data: string, start: number): {command: Command, i: number} {
|
||
|
let command: Command = {
|
||
|
type: 'command',
|
||
|
start,
|
||
|
text: "/",
|
||
|
name: "",
|
||
|
options: [],
|
||
|
inner: [],
|
||
|
};
|
||
|
|
||
|
start++;
|
||
|
|
||
|
for (let i = start; i < data.length; i++) {
|
||
|
const char = data[i]
|
||
|
|
||
|
if (char == '[') {
|
||
|
if (!command.name)
|
||
|
command.name = data.substring(command.start + 1, i);
|
||
|
const {text, i: tempI} = processText(data, i, ']', true)
|
||
|
i = tempI;
|
||
|
command.options.push(text);
|
||
|
}
|
||
|
|
||
|
if (char == '{') {
|
||
|
if (!command.name)
|
||
|
command.name = data.substring(command.start + 1, i);
|
||
|
const {text, i: tempI} = processText(data, i, '}', true);
|
||
|
i = tempI;
|
||
|
command.inner.push(text);
|
||
|
}
|
||
|
|
||
|
if (!isValid(char)) {
|
||
|
if (!command.name)
|
||
|
command.name = data.substring(command.start + 1, i);
|
||
|
return {command, i: i - 1};
|
||
|
}
|
||
|
|
||
|
command.text += char;
|
||
|
}
|
||
|
|
||
|
return {command, i: data.length - 1};
|
||
|
}
|
||
|
|
||
|
function processText(data: string, start: number = 0, delimiter: string = "", exclude = false): {text: Text, i: number} {
|
||
|
let text: Text = {
|
||
|
start,
|
||
|
tokens: [],
|
||
|
}
|
||
|
|
||
|
let range: Range = {
|
||
|
type: 'range',
|
||
|
start,
|
||
|
text: "",
|
||
|
};
|
||
|
|
||
|
if (exclude)
|
||
|
start++;
|
||
|
|
||
|
for (let i = start; i < data.length; i++) {
|
||
|
const char = String(data[i]);
|
||
|
//process.stdout.write(char);
|
||
|
|
||
|
if (delimiter == char) {
|
||
|
if (delimiter && !exclude)
|
||
|
range.text += char;
|
||
|
|
||
|
if (range.text.length > 0) {
|
||
|
text.tokens.push(range);
|
||
|
}
|
||
|
|
||
|
return {text, i: i + 1};
|
||
|
}
|
||
|
|
||
|
if (char == '%') {
|
||
|
if (range.text.length > 0) {
|
||
|
text.tokens.push(range);
|
||
|
}
|
||
|
|
||
|
let {comment, i: tempI} = processComment(data, i);
|
||
|
|
||
|
i = tempI;
|
||
|
|
||
|
text.tokens.push(comment);
|
||
|
|
||
|
range = {
|
||
|
type: 'range',
|
||
|
start: i,
|
||
|
text: "",
|
||
|
};
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if (char == '\\') {
|
||
|
if (range.text.length > 0) {
|
||
|
text.tokens.push(range);
|
||
|
}
|
||
|
|
||
|
let {command, i: tempI} = processCommand(data, i);
|
||
|
|
||
|
i = tempI;
|
||
|
|
||
|
text.tokens.push(command);
|
||
|
|
||
|
range = {
|
||
|
type: 'range',
|
||
|
start: i,
|
||
|
text: ""
|
||
|
};
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
range.text += char;
|
||
|
}
|
||
|
|
||
|
if (delimiter) {
|
||
|
throw new Error(`Delimiter '${delimiter}'`);
|
||
|
}
|
||
|
|
||
|
return {text, i: data.length - 1, };
|
||
|
}
|
||
|
|
||
|
async function main() {
|
||
|
|
||
|
if (process.argv.length < 3) {
|
||
|
console.error("Not enogh arguments");
|
||
|
process.exit(1);
|
||
|
}
|
||
|
|
||
|
const path = process.argv[2];
|
||
|
const basePath =nPath.dirname(path);
|
||
|
|
||
|
const stat = await fs.stat(path);
|
||
|
|
||
|
if (!stat.isFile()) {
|
||
|
console.error(`'${path}' is not a file`);
|
||
|
process.exit(1);
|
||
|
}
|
||
|
|
||
|
const data = (await fs.readFile(path)).toString();
|
||
|
|
||
|
let {text} = processText(data);
|
||
|
|
||
|
const file = (await fs.open('results.txt', 'w'));
|
||
|
|
||
|
await printText(text, file, basePath);
|
||
|
}
|
||
|
|
||
|
function printItemize(text: Text, file: FileHandle, start: number): number {
|
||
|
for (let i = start; i < text.tokens.length; i++) {
|
||
|
const token = text.tokens[i];
|
||
|
|
||
|
if (token.type == 'range') {
|
||
|
file.write(token.text);
|
||
|
continue;
|
||
|
} else if (token.type == 'command') {
|
||
|
if (token.name == 'item') {
|
||
|
file.write('- ');
|
||
|
} else if (token.name == 'end') {
|
||
|
const inner = token.inner[0].tokens[0].text;
|
||
|
|
||
|
if (inner == 'itemize') {
|
||
|
return i;
|
||
|
}
|
||
|
|
||
|
console.log('Do not know how to handle!');
|
||
|
console.log(token);
|
||
|
process.exit(1);
|
||
|
} else {
|
||
|
console.log('Do not know how to handle!');
|
||
|
console.log(token);
|
||
|
process.exit(1);
|
||
|
}
|
||
|
} else if (token.type == 'comment') {
|
||
|
continue;
|
||
|
} else {
|
||
|
console.log('Do not know how to handle token type!');
|
||
|
console.log(token);
|
||
|
process.exit(1);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
throw new Error('Did not find end at itemize!');
|
||
|
}
|
||
|
|
||
|
async function printText(text: Text, file: FileHandle, basePath: string, foundStart: boolean = false, start: number = 0) {
|
||
|
for (let i = start; i < text.tokens.length; i++) {
|
||
|
const token = text.tokens[i];
|
||
|
|
||
|
if (!foundStart) {
|
||
|
|
||
|
if (token.type === 'command') {
|
||
|
|
||
|
if (token.name === 'begin') {
|
||
|
const inner = token.inner[0].tokens[0].text;
|
||
|
if (inner === 'document') {
|
||
|
foundStart = true;
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if (token.type === 'command') {
|
||
|
|
||
|
let inner: string | undefined = undefined;
|
||
|
|
||
|
switch (token.name) {
|
||
|
case 'includepdf':
|
||
|
case 'maketitle':
|
||
|
case 'newpage':
|
||
|
case 'tableofcontents':
|
||
|
case 'printbibliography':
|
||
|
case 'supercite':
|
||
|
case 'includegraphics':
|
||
|
case 'vspace*':
|
||
|
continue;
|
||
|
case 'cref':
|
||
|
file.write('Fig. 1');
|
||
|
continue;
|
||
|
|
||
|
case 'section':
|
||
|
case 'section*':
|
||
|
inner = token.inner[0]?.tokens[0]?.text;
|
||
|
|
||
|
if (!inner) {
|
||
|
console.log(token);
|
||
|
process.exit(1);
|
||
|
}
|
||
|
|
||
|
file.write('# ' + inner + '\n');
|
||
|
continue;
|
||
|
|
||
|
case 'subsection':
|
||
|
case 'subsection*':
|
||
|
inner = token.inner[0].tokens[0].text;
|
||
|
|
||
|
file.write('## ' + inner + '\n');
|
||
|
continue;
|
||
|
|
||
|
case 'subsubsection':
|
||
|
inner = token.inner[0].tokens[0].text;
|
||
|
file.write('## ' + inner + '\n');
|
||
|
continue;
|
||
|
|
||
|
case 'input':
|
||
|
inner = token.inner[0].tokens[0].text;
|
||
|
|
||
|
const path = basePath + '/' + inner + '.tex';
|
||
|
|
||
|
const nData = (await fs.readFile(path)).toString();
|
||
|
|
||
|
const {text: nText} = processText(nData);
|
||
|
|
||
|
await printText(nText, file, basePath, true);
|
||
|
continue;
|
||
|
|
||
|
case 'begin':
|
||
|
inner = token.inner[0].tokens[0].text;
|
||
|
|
||
|
switch(inner) {
|
||
|
case "figure":
|
||
|
case "minipage":
|
||
|
case "tabularx":
|
||
|
i = findEnd(inner, text, i);
|
||
|
continue;
|
||
|
|
||
|
case "itemize":
|
||
|
i = printItemize(text, file, i + 1);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
console.log('Do not know how to handle begin', inner);
|
||
|
process.exit(1);
|
||
|
|
||
|
case 'end':
|
||
|
inner = token.inner[0].tokens[0].text;
|
||
|
|
||
|
if (inner === 'document') {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
console.log('Do not know how to handle end', inner);
|
||
|
process.exit(1);
|
||
|
|
||
|
case 'verb':
|
||
|
|
||
|
const nextToken = text.tokens[i + 1];
|
||
|
const pText = nextToken?.text;
|
||
|
|
||
|
if (!pText) {
|
||
|
console.log('Something wrong!');
|
||
|
console.log(token);
|
||
|
console.log(nextToken);
|
||
|
process.exit(1);
|
||
|
}
|
||
|
|
||
|
let j = 1;
|
||
|
|
||
|
for (;j < nextToken.text.length;j++) {
|
||
|
if (nextToken.text[j] == '+') {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
i++;
|
||
|
|
||
|
file.write(nextToken.text.substring(1, j));
|
||
|
file.write(nextToken.text.substring(j + 1));
|
||
|
continue;
|
||
|
|
||
|
}
|
||
|
|
||
|
console.log("Don't know how to handle", token.name);
|
||
|
process.exit(1);
|
||
|
}
|
||
|
|
||
|
if (token.type === 'range') {
|
||
|
file.write(token.text);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function findEnd(target: string, text: Text, start: number): number {
|
||
|
for (let i = start; i < text.tokens.length; i++) {
|
||
|
const token = text.tokens[i];
|
||
|
|
||
|
if (token.type === 'command') {
|
||
|
if (token.name !== 'end') {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
const inner = token.inner[0].tokens[0].text;
|
||
|
|
||
|
if (inner == target) {
|
||
|
return i;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
throw new Error('Could not find end for ' + target);
|
||
|
}
|
||
|
|
||
|
main();
|