Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 24 additions & 1 deletion node/cli.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
#!/usr/bin/env node
'use strict'

const { readFile, writeFile } = require('node:fs/promises')
const { readFile, stat, writeFile } = require('node:fs/promises')
const { resolve } = require('node:path')

const FORMATS = 'doc, docx, odt, pdf, ppt, pptx, rtf, epub, xlsx, ods, odp, csv'

Expand Down Expand Up @@ -113,11 +114,33 @@ async function readStdin() {
return Buffer.concat(chunks)
}

async function referToSameFile(input, output) {
if (resolve(input) === resolve(output)) return true
try {
const [input_stats, output_stats] = await Promise.all([stat(input), stat(output)])
return input_stats.dev === output_stats.dev && input_stats.ino === output_stats.ino
} catch (error) {
if (error.code === 'ENOENT') return false
throw error
}
}

async function main() {
const args = parseArgs(process.argv.slice(2))
if (args.input === null) {
fail(USAGE_ERROR, 'missing input: pass a document path, or - for stdin (see anydoc --help)')
}
if (args.input !== '-' && args.output !== null) {
let same_file
try {
same_file = await referToSameFile(args.input, args.output)
} catch (error) {
fail(CONVERSION_ERROR, error.message)
}
if (same_file) {
fail(USAGE_ERROR, 'output path must differ from the input path')
}
}

// Loaded after argument handling so --help and --version work even where
// no native binding is available.
Expand Down
38 changes: 37 additions & 1 deletion node/test.mjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Smoke test: the bindings load and every entry point round-trips a fixture.
import assert from 'node:assert/strict'
import { execFile } from 'node:child_process'
import { mkdtemp, readFile, rm } from 'node:fs/promises'
import { copyFile, link, mkdtemp, readFile, rm } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { fileURLToPath } from 'node:url'
Expand Down Expand Up @@ -111,6 +111,42 @@ test('cli writes to --output instead of stdout', async () => {
}
})

test('cli refuses to overwrite its input file', async () => {
const dir = await mkdtemp(join(tmpdir(), 'anydoc-cli-'))
try {
const input = join(dir, 'input.docx')
await copyFile(OUTLINE, input)
const before = await readFile(input)

const { code, stderr } = await runCli([input, '--output', input])

assert.equal(code, 2)
assert.match(stderr, /output.*input/i)
assert.deepEqual(await readFile(input), before)
} finally {
await rm(dir, { recursive: true, force: true })
}
})

test('cli refuses to overwrite its input through a hard link', async () => {
const dir = await mkdtemp(join(tmpdir(), 'anydoc-cli-'))
try {
const input = join(dir, 'input.docx')
const output = join(dir, 'output.md')
await copyFile(OUTLINE, input)
await link(input, output)
const before = await readFile(input)

const { code, stderr } = await runCli([input, '--output', output])

assert.equal(code, 2)
assert.match(stderr, /output.*input/i)
assert.deepEqual(await readFile(input), before)
} finally {
await rm(dir, { recursive: true, force: true })
}
})

test('cli reads stdin with an explicit format', async () => {
const child = promisify(execFile)(process.execPath, [CLI, '-', '--format', 'csv'])
child.child.stdin.end(await readFile(CSV))
Expand Down