@cocalc/project
Version:
CoCalc: project daemon
103 lines (87 loc) • 2.84 kB
text/typescript
/*
* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.
* License: AGPLv3 s.t. "Commons Clause" – see LICENSE.md for details
*/
import { writeFile, readFile, unlink } from "fs";
import { file } from "tmp";
import { callback } from "awaiting";
import { spawn } from "child_process";
interface ParserOptions {
parser?: string;
tabWidth?: number;
useTabs?: boolean;
util?: string;
}
function close(proc, cb): void {
proc.on("close", (code) => cb(undefined, code));
}
// TODO: diversify this via options to support autopep8, black (requires python 3.6), and others...
function yapf(input_path) {
return spawn("yapf", ["-i", input_path]);
}
// from a full stacktrace, only show user the last line (encodes some reason and line number) ... everything else does not help.
function tail(str?: string, lines = 4): string {
if (str == null) {
return "Problem running formatter.";
} else {
return (
str
.trim()
.split(/\r?\n/)
.slice(-lines)
.filter((x) => x.trim().length > 0)
.join("\n") || ""
);
}
}
export async function python_format(
input: string,
options: ParserOptions,
logger: any
): Promise<string> {
// create input temp file
const input_path: string = await callback(file);
try {
await callback(writeFile, input_path, input);
// spawn the python formatter
const util = options.util || "yapf";
if (util !== "yapf") {
throw new Error(
"This project only supports 'yapf' for formatting Python"
);
}
const py_formatter = yapf(input_path);
py_formatter.on("error", (err) => {
// ATTN do not throw an error here, because this is triggered by the subprocess!
logger.debug(
`Formatting utility exited with error no ${(err as any).errno}`
);
});
// stdout/err capture
let stdout: string = "";
let stderr: string = "";
// read data as it is produced.
py_formatter.stdout.on("data", (data) => (stdout += data.toString()));
py_formatter.stderr.on("data", (data) => (stderr += data.toString()));
// wait for subprocess to close.
const code = await callback(close, py_formatter);
// only last line
// stdout = last_line(stdout);
if (code) {
if (code === -2) {
// ENOENT
throw new Error(`Formatting utility "${util}" is not installed`);
}
stderr = tail(stderr);
const err_msg = `Python formatter "${util}" exited with code ${code}:\n${stdout}\n${stderr}`;
logger.debug(`format python error: ${err_msg}`);
throw new Error(err_msg);
}
// all fine, we read from the temp file
const output: Buffer = await callback(readFile, input_path);
const s: string = output.toString("utf-8");
return s;
} finally {
unlink(input_path, () => {});
}
}