-
Notifications
You must be signed in to change notification settings - Fork 177
code style #94
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
code style #94
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,57 +1,54 @@ | ||
import {open} from "node:fs/promises"; | ||
import {spawn} from "node:child_process"; | ||
import {join} from "node:path"; | ||
import {getStats, prepareOutput} from "./files.js"; | ||
import {renameSync, unlinkSync} from "node:fs"; | ||
import {type Stats} from "node:fs"; | ||
import {constants, open, rename, unlink} from "node:fs/promises"; | ||
import {maybeStat, prepareOutput} from "./files.js"; | ||
|
||
const runningCommands = new Map<string, Promise<void>>(); | ||
|
||
export async function runCommand(commandPath: string, outputPath: string) { | ||
export interface Loader { | ||
path: string; | ||
stats: Stats; | ||
} | ||
|
||
export async function runLoader(commandPath: string, outputPath: string) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It could really be any kind of command I suppose, not just a loader. If we want the naming to be consistent, we could change "commandPath" to loaderPath There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we want to make further changes here, I’d consider having findLoader return a Loader implementation with a run method. Then the caller would say loader.run instead of runLoader, and you wouldn’t have to pass in the command path again. |
||
if (runningCommands.has(commandPath)) return runningCommands.get(commandPath); | ||
const command = new Promise<void>((resolve, reject) => { | ||
const command = (async () => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do these changes fix the bug that Fil saw with multiple FileAttachments calls for the same file? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It does! ref. #89 (comment) ; I've built the repro and can confirm that it crashes on main and doesn't crash with this branch. |
||
const outputTempPath = outputPath + ".tmp"; | ||
prepareOutput(outputTempPath).then(() => | ||
open(outputTempPath, "w").then((cacheFd) => { | ||
const cacheFileStream = cacheFd.createWriteStream({highWaterMark: 1024 * 1024}); | ||
try { | ||
const subprocess = spawn(commandPath, [], { | ||
argv0: commandPath, | ||
//cwd: dirname(commandPath), // TODO: Need to change commandPath to be relative this? | ||
windowsHide: true, | ||
stdio: ["ignore", "pipe", "inherit"] | ||
// timeout: // time in ms | ||
// signal: // abort signal | ||
}); | ||
subprocess.stdout.on("data", (data) => cacheFileStream.write(data)); | ||
subprocess.on("error", (error) => console.error(`${commandPath}: ${error.message}`)); | ||
subprocess.on("close", (code) => { | ||
cacheFd.close().then(() => { | ||
if (code === 0) { | ||
renameSync(outputTempPath, outputPath); | ||
} else { | ||
unlinkSync(outputTempPath); | ||
} | ||
resolve(); | ||
}, reject); | ||
}); | ||
} catch (error) { | ||
reject(error); | ||
} finally { | ||
runningCommands.delete(commandPath); | ||
} | ||
}) | ||
); | ||
}); | ||
await prepareOutput(outputTempPath); | ||
const cacheFd = await open(outputTempPath, "w"); | ||
const cacheFileStream = cacheFd.createWriteStream({highWaterMark: 1024 * 1024}); | ||
const subprocess = spawn(commandPath, [], { | ||
argv0: commandPath, | ||
//cwd: dirname(commandPath), // TODO: Need to change commandPath to be relative this? | ||
windowsHide: true, | ||
stdio: ["ignore", "pipe", "inherit"] | ||
// timeout: // time in ms | ||
// signal: // abort signal | ||
}); | ||
subprocess.stdout.pipe(cacheFileStream); | ||
const code = await new Promise((resolve, reject) => { | ||
subprocess.on("error", reject); // (error) => console.error(`${commandPath}: ${error.message}`)); | ||
subprocess.on("close", resolve); | ||
}); | ||
await cacheFd.close(); | ||
if (code === 0) { | ||
await rename(outputTempPath, outputPath); | ||
} else { | ||
await unlink(outputTempPath); | ||
} | ||
})(); | ||
command.finally(() => runningCommands.delete(commandPath)); | ||
runningCommands.set(commandPath, command); | ||
return command; | ||
} | ||
|
||
export async function findLoader(root: string, name: string) { | ||
export async function findLoader(name: string): Promise<Loader | undefined> { | ||
// TODO: It may be more efficient use fs.readdir | ||
for (const ext of [".js", ".ts", ".sh"]) { | ||
const path = join(root, name) + ext; | ||
const stats = await getStats(path); | ||
if (stats) return {path, stats}; | ||
const path = name + ext; | ||
const stats = await maybeStat(path); | ||
if (stats && stats.mode & constants.S_IXUSR) { | ||
return {path, stats}; | ||
} | ||
} | ||
return {}; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm used to seeing "maybe" on conditional updates, not on get/find method, and when it is used it seems like it should modify a verb-object, like "maybeCommit" or "maybeCommitNotebook". We're free to create whatever convention we want, but "maybeStat" and "maybeLoader" sound strange to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think “findLoader” is a fine name, and I’m happy to revert that back. The main thing is that I wanted it to return null or undefined instead of an empty object (because this provides a more obvious way of checking existence). And I think “find” reasonably implies that it can return null or undefined (like array.find).
For “maybe” stat, it’s for parity with the underlying stat method, which at least in my experience is often used as a verb, as in “stat’ing a file”. We’ve used the “maybe” pattern extensively in Plot, but mostly for parsing/normalize user input to some trusted representation. I like the “maybe” pattern here because it indicates conditionality: you might not get the stats returned.