Skip to content
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

Fix write EPIPE error #75

Merged
merged 6 commits into from
May 27, 2019
Merged
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
2 changes: 1 addition & 1 deletion src/gdoc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export async function parse(
file: VFile,
fetch: boolean = true
): Promise<stencila.Node> {
const json = dump(file)
const json = await dump(file)
const gdoc = JSON.parse(json)
return parseDocument(gdoc, fetch)
}
Expand Down
2 changes: 1 addition & 1 deletion src/html.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export const mediaTypes = ['text/html']
* @returns A promise that resolves to a `stencila.Node`
*/
export async function parse(file: VFile): Promise<stencila.Node> {
const html = dump(file)
const html = await dump(file)
const dom = new jsdom.JSDOM(html)
const document = dom.window.document
collapse(document)
Expand Down
17 changes: 9 additions & 8 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -299,18 +299,19 @@ export async function write(
/**
* Convert content from one format to another.
*
* @param inp The input content (raw or file path).
* @param out The output file path.
* @param input The input content (raw or file path).
* @param outputPath The output file path.
* @param options Conversion options e.g `from` and `to`: to specify the formats to convert from/to
* @returns The converted content, or file path (for converters that only write to files).
*/
export async function convert(
inp: string,
out?: string,
input: string,
outputPath?: string,
options: { [key: string]: any } = {}
): Promise<string | undefined> {
const node = await read(inp, options.from)
let file = await unparse(node, out, options.to)
if (out) await vfile.write(file, out)
return file.contents ? vfile.dump(file) : file.path
const inputFile = vfile.create(input)
const node = await parse(inputFile, input, options.from)
const outputFile = await unparse(node, outputPath, options.to)
if (outputPath) await vfile.write(outputFile, outputPath)
return outputFile.contents ? vfile.dump(outputFile) : outputFile.path
}
2 changes: 1 addition & 1 deletion src/json.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { dump, load, VFile } from './vfile'
export const mediaTypes = ['application/json']

export async function parse(file: VFile): Promise<stencila.Node> {
return JSON.parse(dump(file))
return JSON.parse(await dump(file))
}

export async function unparse(node: stencila.Node): Promise<VFile> {
Expand Down
2 changes: 1 addition & 1 deletion src/json5.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export const mediaTypes = ['application/json5']
* @returns A promise that resolves to a Stencila `Node`
*/
export async function parse(file: VFile): Promise<stencila.Node> {
return json5.parse(dump(file))
return json5.parse(await dump(file))
}

/**
Expand Down
26 changes: 17 additions & 9 deletions src/pandoc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import childProcess from 'child_process'
import { pandocDataDir, pandocPath } from './boot'
import * as Pandoc from './pandoc-types'
import * as rpng from './rpng'
import { create, dump, load, VFile, write } from './vfile'
import { create, load, VFile, write } from './vfile'

export { InputFormat, OutputFormat } from './pandoc-types'

Expand All @@ -26,11 +26,13 @@ export async function parse(
ensureFile: boolean = false
): Promise<stencila.Node> {
const args = [`--from=${from}`, `--to=json`].concat(options)
let content = dump(file)

let content = file.contents
if (!content || ensureFile) {
if (ensureFile && !file.path) throw new Error('Must supply a file')
args.push(`${file.path}`)
}

const json = await run(content, args)
const pdoc = JSON.parse(json)
return parseDocument(pdoc)
Expand Down Expand Up @@ -62,7 +64,6 @@ export async function unparse(
const pdoc = unparseArticle(node as stencila.Article)
await Promise.all(unparsePromises)

const json = JSON.stringify(pdoc)
const args = [`--from=json`, `--to=${to}`].concat(options)
if ((filePath && filePath !== '-') || ensureFile) {
let output
Expand All @@ -74,12 +75,13 @@ export async function unparse(
args.push(`--output=${output}`)
}

const json = JSON.stringify(pdoc)
const content = await run(json, args)

// If content was output, then load that into a vfile,
// otherwise the vfile, simply has path to the file created
// If content was outputted, then load that into a vfile,
// otherwise the vfile simply has path to the file created
if (content) return load(content)
else return create({ path: filePath })
else return create(undefined, { path: filePath })
}

/**
Expand All @@ -95,13 +97,14 @@ let unparsePromises: Promise<any>[] = []
/**
* Run the Pandoc binary
*/
function run(input: string, args: string[]): Promise<string> {
function run(input: string | Buffer, args: string[]): Promise<string> {
args.push(`--data-dir=${pandocDataDir}`)
if (process.env.DEBUG) {
console.log(`Running ${pandocPath} with args:\n ${args.join('\n ')}`)
}
return new Promise((resolve, reject) => {
const child = childProcess.spawn(pandocPath, args)

let stdout = ''
let stderr = ''
child.stdout.on('data', data => {
Expand All @@ -121,8 +124,13 @@ function run(input: string, args: string[]): Promise<string> {
child.on('error', err => {
reject(err)
})
child.stdin.write(input)
child.stdin.end()

if (input && input.length) {
child.stdin.write(input, err => {
if (err) return reject(err)
child.stdin.end()
})
}
})
}

Expand Down
4 changes: 2 additions & 2 deletions src/tdp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export const extNames = [
export async function parse(file: VFile): Promise<stencila.Node> {
let pkg: datapackage.Package
if (file.path) pkg = await datapackage.Package.load(file.path)
else pkg = await datapackage.Package.load(JSON.parse(dump(file)))
else pkg = await datapackage.Package.load(JSON.parse(await dump(file)))

// Parse resources
const parts = await Promise.all(pkg.resources.map(
Expand Down Expand Up @@ -199,7 +199,7 @@ async function unparseCreativeWork(
profile: 'tabular-data-resource',
name: datatable.name || 'Unnamed',

data: dump(await csv.unparse(datatable)),
data: await dump(await csv.unparse(datatable)),
format: 'csv',
mediatype: 'text/csv',
encoding: 'utf-8',
Expand Down
26 changes: 21 additions & 5 deletions src/vfile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,17 @@ export type VFile = vfile.VFile
/**
* Create a virtual file
*
* @param options to `vfile` see https://github.com/vfile/vfile#vfileoptions
* @param contents Raw string contents of `VFile`, or file path
* @param options Options to `vfile` see https://github.com/vfile/vfile#vfileoptions
*/
export function create(options: any = {}): VFile {
export function create(
contents?: string,
options: { [key: string]: any } = {}
): VFile {
if (contents) {
if (isPath(contents)) options = { ...options, path: contents }
else options = { ...options, contents }
}
return vfile(options)
}

Expand All @@ -30,10 +38,18 @@ export function load(contents: VFileContents): VFile {
/**
* Dump a string from the contents of a virtual files
*
* If the file has no `contents` but does have a file
* `path`, then `read` the virtual file.
*
* @param file The virtual file to dump
*/
export function dump(vfile: VFile): string {
return vfile.contents ? vfile.contents.toString() : ''
export async function dump(vfile: VFile): Promise<string> {
if (vfile.contents) return vfile.toString()
if (vfile.path) {
const readFile = await toVFile.read(vfile.path)
return readFile.toString()
}
return ''
}

/**
Expand Down Expand Up @@ -66,7 +82,7 @@ export async function write(vfile: VFile, path: string): Promise<void> {
if (!path) throw new Error('Argument `path` is required')

if (path === '-') {
console.log(dump(vfile))
console.log(await dump(vfile))
} else if (path && vfile.contents) {
vfile.path = path
await toVFile.write(vfile)
Expand Down
2 changes: 1 addition & 1 deletion src/yaml.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { dump, load, VFile } from './vfile'
export const mediaTypes = ['text/yaml']

export async function parse(file: VFile): Promise<stencila.Node> {
return yaml.safeLoad(dump(file))
return yaml.safeLoad(await dump(file))
}

export async function unparse(node: stencila.Node): Promise<VFile> {
Expand Down
6 changes: 3 additions & 3 deletions tests/csv.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ test('parse', async () => {
})

test('unparse', async () => {
expect(dump(await unparse(simple.node))).toEqual(simple.content)
expect(dump(await unparse(named.node))).toEqual(named.content)
expect(dump(await unparse(formulas.node))).toEqual(formulas.content)
expect(await dump(await unparse(simple.node))).toEqual(simple.content)
expect(await dump(await unparse(named.node))).toEqual(named.content)
expect(await dump(await unparse(formulas.node))).toEqual(formulas.content)
})
2 changes: 1 addition & 1 deletion tests/gdoc.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ test('parse', async () => {
})

test('unparse', async () => {
const u = async (node: any) => JSON.parse(dump(await unparse(node)))
const u = async (node: any) => JSON.parse(await dump(await unparse(node)))
expect(await u(kitchenSink.node)).toEqual(kitchenSink.gdoc)
})

Expand Down
2 changes: 1 addition & 1 deletion tests/html.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ test('parse', async () => {
})

test('unparse', async () => {
expect(dump(await unparse(kitchenSink.node))).toEqual(kitchenSink.html)
expect(await dump(await unparse(kitchenSink.node))).toEqual(kitchenSink.html)
})

// An example intended for testing progressively added parser/unparser pairs
Expand Down
Binary file added tests/issues/74-write-epipe.docx
Binary file not shown.
33 changes: 33 additions & 0 deletions tests/issues/74-write-epipe.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import fs from 'fs-extra'
import path from 'path'
import { convert } from '../../src'

test('issue 74', async () => {
/**
* The [issue](https://github.com/stencila/convert/issues/74)
* was reported with respect to converting to HTML
* but it also occurs when converting to JSON.
*
* $ npx ts-node --files src/cli ./tests/issues/74-write-epipe.docx --to json
*
* Error: write EPIPE
* at WriteWrap.afterWrite (net.js:836:14)
*
* This suggests it's a problem within `read` and
* [this issue](https://github.com/nodejs/node/issues/947)
* suggest that it is a problem with use of `stdin` of `stdout`
* within `pandoc.run`.
*/

const docx = path.join(__dirname, '74-write-epipe.docx')
const html = path.join(__dirname, '74-write-epipe.html')

// Before fix, this fails with `Error: write EPIPE`
// https://travis-ci.org/stencila/convert/jobs/537572367#L172
// https://ci.appveyor.com/project/nokome/convert/builds/24825307#L66
convert(docx, html)

// Cleanup
await fs.remove(html)
await fs.remove(docx + '.media')
})
4 changes: 2 additions & 2 deletions tests/pandoc.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ test('parse', async () => {
})

test('unparse', async () => {
const u = async (node: any) => JSON.parse(dump(await unparse(node)))
const u = async (node: any) => JSON.parse(await dump(await unparse(node)))
expect(await u(kitchenSink.node)).toEqual(kitchenSink.pdoc)
})

Expand Down Expand Up @@ -566,7 +566,7 @@ test('rpngs', async () => {

expect(await parse(load(JSON.stringify(pdoc)))).toEqual(node)
// Skipping this until resolve how to deal with output RPNG paths
//expect(JSON.parse(dump(await unparse(node)))).toEqual(pdoc)
//expect(JSON.parse(await dump(await unparse(node)))).toEqual(pdoc)
})

// A very simple test of the approach to typing Pandoc nodes
Expand Down
4 changes: 2 additions & 2 deletions tests/tdp.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,11 +137,11 @@ test('parse', async () => {
})

test('unparse', async () => {
const actual = JSON.parse(dump(await unparse(periodic.node)))
const actual = JSON.parse(await dump(await unparse(periodic.node)))
// Pretend that we unparsed to a a filePath i.e. that
// the data was written to disk.
delete actual.resources[0].data
actual.resources[0].path = 'periodic-table.csv'
const expected = JSON.parse(dump(await read(periodic.file)))
const expected = JSON.parse(await dump(await read(periodic.file)))
expect(actual).toEqual(expected)
})