fyp-report/main.ts

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();