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

Support conversion from standard input #4

Merged
merged 12 commits into from
Aug 25, 2018
10 changes: 5 additions & 5 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ references:

- restore_cache:
keys:
- v1-dependencies-{{ .Environment.CIRCLE_JOB }}-{{ checksum "yarn.lock" }}-{{ .Branch }}
- v1-dependencies-{{ .Environment.CIRCLE_JOB }}-{{ checksum "yarn.lock" }}-
- v1-dependencies-{{ .Environment.CIRCLE_JOB }}-
- dependencies-{{ .Environment.CI_CACHE_KEY }}-{{ .Environment.CIRCLE_JOB }}-{{ checksum "yarn.lock" }}-{{ .Branch }}
- dependencies-{{ .Environment.CI_CACHE_KEY }}-{{ .Environment.CIRCLE_JOB }}-{{ checksum "yarn.lock" }}-
- dependencies-{{ .Environment.CI_CACHE_KEY }}-{{ .Environment.CIRCLE_JOB }}-

- run: yarn install

- save_cache:
key: v1-dependencies-{{ .Environment.CIRCLE_JOB }}-{{ checksum "yarn.lock" }}-{{ .Branch }}
key: dependencies-{{ .Environment.CI_CACHE_KEY }}-{{ .Environment.CIRCLE_JOB }}-{{ checksum "yarn.lock" }}-{{ .Branch }}
paths:
- node_modules
- ~/.cache/yarn
Expand All @@ -32,7 +32,7 @@ references:

- run:
name: Jest
command: yarn test:coverage --ci --maxWorkers=2 --reporters=default --reporters=jest-junit
command: yarn test:coverage --ci --runInBand --reporters=default --reporters=jest-junit
environment:
JEST_JUNIT_OUTPUT: tmp/test-results/jest.xml

Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
},
"devDependencies": {
"@babel/core": "^7.0.0-rc.1",
"@types/get-stdin": "^5.0.1",
"@types/jest": "^23.3.1",
"@types/node": "^10.7.1",
"@types/pug": "^2.0.4",
Expand Down Expand Up @@ -84,6 +85,7 @@
"@marp-team/marpit": "^0.0.12",
"chalk": "^2.4.1",
"chrome-launcher": "^0.10.2",
"get-stdin": "^6.0.0",
"globby": "^8.0.1",
"is-wsl": "^1.1.0",
"os-locale": "^3.0.0",
Expand Down
48 changes: 14 additions & 34 deletions src/converter.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { Marpit, MarpitOptions } from '@marp-team/marpit'
import * as chromeFinder from 'chrome-launcher/dist/chrome-finder'
import fs from 'fs'
import path from 'path'
import puppeteer, { PDFOptions } from 'puppeteer-core'
import { error } from './error'
import { File } from './file'
import templates, { TemplateResult } from './templates'

export enum ConvertType {
Expand All @@ -23,20 +22,16 @@ export interface ConverterOption {
}

export interface ConvertResult {
output: string
path: string
rendered: TemplateResult['rendered']
result: Buffer | TemplateResult['result']
file: File
newFile: File
template: TemplateResult
}

export class Converter {
readonly options: ConverterOption

constructor(opts: ConverterOption) {
this.options = opts

if (opts.type === ConvertType.pdf && opts.output === '-')
error('PDF cannot output to stdout.')
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By this change, we no longer restrict to output PDF into stdout. The default output path will decide by where came input from.

}

get template() {
Expand All @@ -60,20 +55,18 @@ export class Converter {
})
}

async convertFile(path: string): Promise<ConvertResult> {
const buffer = await new Promise<Buffer>((resolve, reject) =>
fs.readFile(path, (e, data) => (e ? reject(e) : resolve(data)))
)
async convertFile(file: File): Promise<ConvertResult> {
const buffer = await file.load()
const template = this.convert(buffer.toString())
const newFile = file.convert(this.options.output, this.options.type)

const converted = this.convert(buffer.toString())
const output = this.outputPath(path, this.options.type)
const result = await (async () => {
newFile.buffer = await (async () => {
if (this.options.type === ConvertType.pdf) {
const browser = await Converter.runBrowser()

try {
const page = await browser.newPage()
await page.goto(`data:text/html,${converted.result}`, {
await page.goto(`data:text/html,${template.result}`, {
waitUntil: ['domcontentloaded', 'networkidle0'],
})

Expand All @@ -85,19 +78,15 @@ export class Converter {
await browser.close()
}
}
return converted.result
return new Buffer(template.result)
})()

if (output !== '-')
await new Promise<void>((resolve, reject) =>
fs.writeFile(output, result, e => (e ? reject(e) : resolve()))
)

return { output, path, result, rendered: converted.rendered }
await newFile.save()
return { file, newFile, template }
}

async convertFiles(
files: string[],
files: File[],
onConverted: (result: ConvertResult) => void = () => {}
): Promise<void> {
if (this.options.output && this.options.output !== '-' && files.length > 1)
Expand All @@ -118,15 +107,6 @@ export class Converter {
return engine
}

private outputPath(from: string, extension: string): string {
if (this.options.output) return this.options.output

return path.join(
path.dirname(from),
`${path.basename(from, path.extname(from))}.${extension}`
)
}

private static runBrowser() {
const finder: () => string[] = require('is-wsl')
? chromeFinder.wsl
Expand Down
99 changes: 99 additions & 0 deletions src/file.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import fs from 'fs'
import getStdin from 'get-stdin'
import globby from 'globby'
import path from 'path'

const markdownExtensions = ['*.md', '*.mdown', '*.markdown', '*.markdn']

export enum FileType {
File,
StandardIO,
}

export class File {
buffer?: Buffer
type: FileType = FileType.File
readonly path: string

constructor(filepath: string) {
this.path = filepath
}

get absolutePath() {
return path.resolve(this.path)
}

convert(output: string | undefined, extension: string): File {
if (output === undefined)
return File.initialize(
this.convertExtension(extension),
f => (f.type = this.type)
)

if (output === '-')
return File.initialize('-', f => (f.type = FileType.StandardIO))

return File.initialize(output)
}

async load() {
this.buffer =
this.buffer ||
(await new Promise<Buffer>((resolve, reject) =>
fs.readFile(this.path, (e, buf) => (e ? reject(e) : resolve(buf)))
))

return this.buffer
}

relativePath(from: string = process.cwd()) {
return path.relative(from, this.absolutePath)
}

async save() {
switch (this.type) {
case FileType.File:
await new Promise<void>((resolve, reject) =>
fs.writeFile(this.path, this.buffer, e => (e ? reject(e) : resolve()))
)
break
case FileType.StandardIO:
process.stdout.write(this.buffer!)
}
}

private convertExtension(extension: string): string {
return path.join(
path.dirname(this.path),
`${path.basename(this.path, path.extname(this.path))}.${extension}`
)
}

private static stdinBuffer?: Buffer

static async find(...pathes: string[]): Promise<File[]> {
return (await globby(pathes, {
absolute: true,
expandDirectories: { files: markdownExtensions },
})).map(path => new File(path))
}

static async stdin(): Promise<File | undefined> {
this.stdinBuffer = this.stdinBuffer || (await getStdin.buffer())
if (this.stdinBuffer.length === 0) return undefined

return this.initialize('-', f => {
f.buffer = this.stdinBuffer
f.type = FileType.StandardIO
})
}

private static initialize(
filepath: string,
tap: (instance: File) => void = () => {}
) {
const instance = new this(filepath)
tap(instance)
return instance
}
}
27 changes: 11 additions & 16 deletions src/marp-cli.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import { Marp } from '@marp-team/marp-core'
import { version as coreVersion } from '@marp-team/marp-core/package.json'
import globby from 'globby'
import osLocale from 'os-locale'
import path from 'path'
import { Argv } from 'yargs'
import yargs from 'yargs/yargs'
import * as cli from './cli'
import { Converter, ConvertType } from './converter'
import { CLIError, error } from './error'
import { File, FileType } from './file'
import { MarpReadyScript } from './ready'
import templates from './templates'
import { name, version } from '../package.json'
Expand Down Expand Up @@ -90,33 +89,29 @@ export default async function(argv: string[] = []): Promise<number> {
})

// Find target markdown files
const files = await globby(args._, {
absolute: true,
expandDirectories: { files: ['*.md', '*.mdown', '*.markdown'] },
})
const files = <File[]>(
[await File.stdin(), ...(await File.find(...args._))].filter(f => f)
)
const { length } = files

if (files.length === 0) {
if (length === 0) {
if (args._.length > 0)
cli.warn('Not found processable Markdown file(s).\n')

program.showHelp()
return args._.length > 0 ? 1 : 0
}

const plural = files.length > 1 ? 's' : ''
cli.info(`Converting ${files.length} file${plural}...`)
cli.info(`Converting ${length} markdown${length > 1 ? 's' : ''}...`)

// Convert markdown into HTML
try {
await converter.convertFiles(files, ret => {
const from = path.relative(process.cwd(), ret.path)
const output =
ret.output === '-'
? '[stdout]'
: path.relative(process.cwd(), ret.output)
const { file, newFile } = ret
const output = (f: File, io: string) =>
f.type === FileType.StandardIO ? `<${io}>` : f.relativePath()

cli.info(`${from} => ${output}`)
if (ret.output === '-') console.log(ret.result)
cli.info(`${output(file, 'stdin')} => ${output(newFile, 'stdout')}`)
})
} catch (e) {
error(`Failed converting Markdown. (${e.message})`, e.errorCode)
Expand Down
Loading