From 6f5f5749f400a7569dd0801d93a5e8ad5ec54d87 Mon Sep 17 00:00:00 2001 From: Yuki Hattori Date: Sat, 25 Aug 2018 03:43:43 +0900 Subject: [PATCH 01/12] Support pipe from stdin with refactor File class --- package.json | 2 + src/converter.ts | 48 +++++++----------------- src/file.ts | 97 ++++++++++++++++++++++++++++++++++++++++++++++++ src/marp-cli.ts | 27 ++++++-------- yarn.lock | 6 +++ 5 files changed, 130 insertions(+), 50 deletions(-) create mode 100644 src/file.ts diff --git a/package.json b/package.json index 62b1ad79..83be52b3 100644 --- a/package.json +++ b/package.json @@ -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", @@ -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", diff --git a/src/converter.ts b/src/converter.ts index 66d91cd7..dbd2fce7 100644 --- a/src/converter.ts +++ b/src/converter.ts @@ -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 { @@ -23,10 +22,9 @@ 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 { @@ -34,9 +32,6 @@ export class Converter { constructor(opts: ConverterOption) { this.options = opts - - if (opts.type === ConvertType.pdf && opts.output === '-') - error('PDF cannot output to stdout.') } get template() { @@ -60,20 +55,18 @@ export class Converter { }) } - async convertFile(path: string): Promise { - const buffer = await new Promise((resolve, reject) => - fs.readFile(path, (e, data) => (e ? reject(e) : resolve(data))) - ) + async convertFile(file: File): Promise { + 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'], }) @@ -85,19 +78,15 @@ export class Converter { await browser.close() } } - return converted.result + return new Buffer(template.result) })() - if (output !== '-') - await new Promise((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 { if (this.options.output && this.options.output !== '-' && files.length > 1) @@ -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 diff --git a/src/file.ts b/src/file.ts new file mode 100644 index 00000000..6894347c --- /dev/null +++ b/src/file.ts @@ -0,0 +1,97 @@ +import fs from 'fs' +import getStdin from 'get-stdin' +import globby from 'globby' +import path from 'path' + +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 { + // Convert filename + if (output === undefined) { + const file = new File(this.convertExtension(extension)) + file.type = this.type + + return file + } + + // Output to stdout + if (output === '-') { + const file = new File('') + file.type = FileType.StandardIO + + return file + } + + return new File(output) + } + + async load() { + this.buffer = + this.buffer || + (await new Promise((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((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 { + return (await globby(pathes, { + absolute: true, + expandDirectories: { files: ['*.md', '*.mdown', '*.markdown'] }, + })).map(path => new File(path)) + } + + static async stdin(): Promise { + this.stdinBuffer = this.stdinBuffer || (await getStdin.buffer()) + if (this.stdinBuffer.length === 0) return undefined + + const file = new File('-') + + file.buffer = this.stdinBuffer + file.type = FileType.StandardIO + + return file + } +} diff --git a/src/marp-cli.ts b/src/marp-cli.ts index 121f517a..89c9040b 100644 --- a/src/marp-cli.ts +++ b/src/marp-cli.ts @@ -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' @@ -90,12 +89,12 @@ export default async function(argv: string[] = []): Promise { }) // Find target markdown files - const files = await globby(args._, { - absolute: true, - expandDirectories: { files: ['*.md', '*.mdown', '*.markdown'] }, - }) + const files = ( + [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') @@ -103,20 +102,16 @@ export default async function(argv: string[] = []): Promise { 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) diff --git a/yarn.lock b/yarn.lock index 66584eb2..0dd799ce 100644 --- a/yarn.lock +++ b/yarn.lock @@ -196,6 +196,12 @@ version "1.2.0" resolved "https://registry.yarnpkg.com/@types/events/-/events-1.2.0.tgz#81a6731ce4df43619e5c8c945383b3e62a89ea86" +"@types/get-stdin@^5.0.1": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@types/get-stdin/-/get-stdin-5.0.1.tgz#46afbcaf09e94fe025afa07ae994ac3168adbdf3" + dependencies: + "@types/node" "*" + "@types/jest@^23.3.1": version "23.3.1" resolved "https://registry.yarnpkg.com/@types/jest/-/jest-23.3.1.tgz#a4319aedb071d478e6f407d1c4578ec8156829cf" From bc3c1319de7d4ef3cf182f8e9cade75901c05a4b Mon Sep 17 00:00:00 2001 From: Yuki Hattori Date: Sat, 25 Aug 2018 04:03:08 +0900 Subject: [PATCH 02/12] Fix unavailable tests --- test/converter.ts | 60 +++++++++++++++++++++++------------------------ test/marp-cli.ts | 14 +++++++---- 2 files changed, 38 insertions(+), 36 deletions(-) diff --git a/test/converter.ts b/test/converter.ts index 28330708..1f196404 100644 --- a/test/converter.ts +++ b/test/converter.ts @@ -5,6 +5,7 @@ import path from 'path' import context from './_helpers/context' import { useSpy } from './_helpers/spy' import { Converter, ConvertType } from '../src/converter' +import { File, FileType } from '../src/file' import { bare as bareTpl } from '../src/templates' import { CLIError } from '../src/error' @@ -35,12 +36,6 @@ describe('Converter', () => { expect(new Converter(options).options).toMatchObject(options) }) - - it('throws error when convert type is PDF and output is stdout', () => { - expect(() => instance({ type: ConvertType.pdf, output: '-' })).toThrow( - CLIError - ) - }) }) describe('get #template', () => { @@ -92,7 +87,7 @@ describe('Converter', () => { describe('#convertFile', () => { it('rejects Promise when specified file is not found', () => { expect( - instance().convertFile('_NOT_FOUND_MARKDOWN_') + instance().convertFile(new File('_NOT_FOUND_MARKDOWN_')) ).rejects.toBeTruthy() }) @@ -102,17 +97,14 @@ describe('Converter', () => { return useSpy([write], async () => { write.mockImplementation((_, __, callback) => callback()) - const outputPath = `${onePath.slice(0, -3)}.html` - const result = await instance().convertFile(onePath) + const output = `${onePath.slice(0, -3)}.html` + await instance().convertFile(new File(onePath)) expect(write).toHaveBeenCalledWith( - outputPath, - result.result, + output, + expect.any(Buffer), expect.anything() ) - - expect(result.path).toBe(onePath) - expect(result.output).toBe(outputPath) }) }) @@ -123,28 +115,29 @@ describe('Converter', () => { write.mockImplementation((_, __, callback) => callback()) const output = './specified.html' - const result = await instance({ output }).convertFile(twoPath) + await instance({ output }).convertFile(new File(twoPath)) expect(write).toHaveBeenCalledWith( output, - result.result, + expect.any(Buffer), expect.anything() ) - - expect(result.path).toBe(twoPath) - expect(result.output).toBe(output) }) }) it('converts markdown file but not save when output is stdout', async () => { const write = jest.spyOn(fs, 'writeFile') + const stdout = jest.spyOn(process.stdout, 'write') - return useSpy([write], async () => { - const result = await instance({ output: '-' }).convertFile(threePath) + return useSpy([write, stdout], async () => { + const result = await instance({ output: '-' }).convertFile( + new File(threePath) + ) expect(write).not.toHaveBeenCalled() - expect(result.path).toBe(threePath) - expect(result.output).toBe('-') + expect(stdout).toHaveBeenCalledTimes(1) + expect(result.file.path).toBe(threePath) + expect(result.newFile.type).toBe(FileType.StandardIO) }) }) @@ -161,14 +154,14 @@ describe('Converter', () => { write.mockImplementation((_, __, callback) => callback()) const opts = { output: 'test.pdf' } - const result = await pdfInstance(opts).convertFile(onePath) + const ret = await pdfInstance(opts).convertFile(new File(onePath)) const pdf: Buffer = write.mock.calls[0][1] expect(write).toHaveBeenCalled() expect(write.mock.calls[0][0]).toBe('test.pdf') expect(pdf.toString('ascii', 0, 5)).toBe('%PDF-') - expect(result.output).toBe('test.pdf') - expect(result.result).toBe(write.mock.calls[0][1]) + expect(ret.newFile.path).toBe('test.pdf') + expect(ret.newFile.buffer).toBe(write.mock.calls[0][1]) }) }, 10000 @@ -184,7 +177,7 @@ describe('Converter', () => { return useSpy([write], async () => { write.mockImplementation((_, __, callback) => callback()) - await instance().convertFiles([onePath, twoPath]) + await instance().convertFiles([new File(onePath), new File(twoPath)]) expect(write).toHaveBeenCalledTimes(2) expect(write.mock.calls[0][0]).toBe(`${onePath.slice(0, -3)}.html`) expect(write.mock.calls[1][0]).toBe(`${twoPath.slice(0, -6)}.html`) @@ -193,20 +186,25 @@ describe('Converter', () => { it('throws CLIError when output is defined', () => expect( - instance({ output: 'test' }).convertFiles([onePath, twoPath]) + instance({ output: 'test' }).convertFiles([ + new File(onePath), + new File(twoPath), + ]) ).rejects.toBeInstanceOf(CLIError)) it('converts passed files when output is stdout', async () => { const write = jest.spyOn(fs, 'writeFile') + const stdout = jest.spyOn(process.stdout, 'write') - return useSpy([write], async () => { - const files = [onePath, twoPath] + return useSpy([write, stdout], async () => { + const files = [new File(onePath), new File(twoPath)] await instance({ output: '-' }).convertFiles(files, result => - expect(files.includes(result.path)).toBe(true) + expect(files.includes(result.file)).toBe(true) ) expect(write).not.toHaveBeenCalled() + expect(stdout).toHaveBeenCalledTimes(2) }) }) }) diff --git a/test/marp-cli.ts b/test/marp-cli.ts index 0c71519e..1506b09a 100644 --- a/test/marp-cli.ts +++ b/test/marp-cli.ts @@ -85,7 +85,9 @@ describe('Marp CLI', () => { write.mockImplementation((_, __, callback) => callback()) expect(await marpCli([onePath])).toBe(0) - expect(cliInfo).toHaveBeenCalledWith(expect.stringContaining('1 file')) + expect(cliInfo).toHaveBeenCalledWith( + expect.stringContaining('1 markdown') + ) expect(cliInfo).toHaveBeenCalledWith( expect.stringMatching(/1\.md => .+1\.html/) ) @@ -110,11 +112,11 @@ describe('Marp CLI', () => { context('with -o option', () => { it('converts file and output to stdout when -o is "-"', async () => { const cliInfo = jest.spyOn(cli, 'info') - const log = jest.spyOn(console, 'log') + const write = jest.spyOn(process.stdout, 'write') - return useSpy([cliInfo, log], async () => { + return useSpy([cliInfo, write], async () => { expect(await marpCli([onePath, '-o', '-'])).toBe(0) - expect(log).toHaveBeenCalledTimes(1) + expect(write).toHaveBeenCalledTimes(1) }) }) @@ -135,7 +137,9 @@ describe('Marp CLI', () => { converter.mockImplementation(() => []) expect(await marpCli([folder])).toBe(0) - expect(cliInfo).toHaveBeenCalledWith(expect.stringContaining('4 files')) + expect(cliInfo).toHaveBeenCalledWith( + expect.stringContaining('4 markdowns') + ) }) }) }) From 161bb29c037334568909689d285971d5d44f5206 Mon Sep 17 00:00:00 2001 From: Yuki Hattori Date: Sat, 25 Aug 2018 04:12:36 +0900 Subject: [PATCH 03/12] Increase global jest timeout seconds to 10sec --- jest.config.js | 1 + jest.setup.ts | 1 + test/converter.ts | 38 +++++++++++++++++--------------------- 3 files changed, 19 insertions(+), 21 deletions(-) create mode 100644 jest.setup.ts diff --git a/jest.config.js b/jest.config.js index 9ff8b3b9..98038838 100644 --- a/jest.config.js +++ b/jest.config.js @@ -2,6 +2,7 @@ module.exports = { collectCoverageFrom: ['src/**/*.{j,t}s'], coveragePathIgnorePatterns: ['/node_modules/', '.*\\.d\\.ts'], coverageThreshold: { global: { lines: 95 } }, + setupTestFrameworkScriptFile: '/jest.setup.ts', transform: { '^.*\\.ts$': 'ts-jest', '^.*\\.s?css$': '/test/_transformers/css.js', diff --git a/jest.setup.ts b/jest.setup.ts new file mode 100644 index 00000000..719a473b --- /dev/null +++ b/jest.setup.ts @@ -0,0 +1 @@ +jest.setTimeout(10000) diff --git a/test/converter.ts b/test/converter.ts index 1f196404..b255d5cc 100644 --- a/test/converter.ts +++ b/test/converter.ts @@ -145,27 +145,23 @@ describe('Converter', () => { const pdfInstance = (opts = {}) => instance({ ...opts, type: ConvertType.pdf }) - it( - 'converts markdown file into PDF', - async () => { - const write = jest.spyOn(fs, 'writeFile') - - return useSpy([write], async () => { - write.mockImplementation((_, __, callback) => callback()) - - const opts = { output: 'test.pdf' } - const ret = await pdfInstance(opts).convertFile(new File(onePath)) - const pdf: Buffer = write.mock.calls[0][1] - - expect(write).toHaveBeenCalled() - expect(write.mock.calls[0][0]).toBe('test.pdf') - expect(pdf.toString('ascii', 0, 5)).toBe('%PDF-') - expect(ret.newFile.path).toBe('test.pdf') - expect(ret.newFile.buffer).toBe(write.mock.calls[0][1]) - }) - }, - 10000 - ) + it('converts markdown file into PDF', async () => { + const write = jest.spyOn(fs, 'writeFile') + + return useSpy([write], async () => { + write.mockImplementation((_, __, callback) => callback()) + + const opts = { output: 'test.pdf' } + const ret = await pdfInstance(opts).convertFile(new File(onePath)) + const pdf: Buffer = write.mock.calls[0][1] + + expect(write).toHaveBeenCalled() + expect(write.mock.calls[0][0]).toBe('test.pdf') + expect(pdf.toString('ascii', 0, 5)).toBe('%PDF-') + expect(ret.newFile.path).toBe('test.pdf') + expect(ret.newFile.buffer).toBe(write.mock.calls[0][1]) + }) + }) }) }) From 71e76737e78d44a1a990fb0fd6d17e79f2ec5d87 Mon Sep 17 00:00:00 2001 From: Yuki Hattori Date: Sat, 25 Aug 2018 04:51:53 +0900 Subject: [PATCH 04/12] Revert "Increase global jest timeout seconds to 10sec" This reverts commit 161bb29c037334568909689d285971d5d44f5206. --- jest.config.js | 1 - jest.setup.ts | 1 - test/converter.ts | 38 +++++++++++++++++++++----------------- 3 files changed, 21 insertions(+), 19 deletions(-) delete mode 100644 jest.setup.ts diff --git a/jest.config.js b/jest.config.js index 98038838..9ff8b3b9 100644 --- a/jest.config.js +++ b/jest.config.js @@ -2,7 +2,6 @@ module.exports = { collectCoverageFrom: ['src/**/*.{j,t}s'], coveragePathIgnorePatterns: ['/node_modules/', '.*\\.d\\.ts'], coverageThreshold: { global: { lines: 95 } }, - setupTestFrameworkScriptFile: '/jest.setup.ts', transform: { '^.*\\.ts$': 'ts-jest', '^.*\\.s?css$': '/test/_transformers/css.js', diff --git a/jest.setup.ts b/jest.setup.ts deleted file mode 100644 index 719a473b..00000000 --- a/jest.setup.ts +++ /dev/null @@ -1 +0,0 @@ -jest.setTimeout(10000) diff --git a/test/converter.ts b/test/converter.ts index b255d5cc..1f196404 100644 --- a/test/converter.ts +++ b/test/converter.ts @@ -145,23 +145,27 @@ describe('Converter', () => { const pdfInstance = (opts = {}) => instance({ ...opts, type: ConvertType.pdf }) - it('converts markdown file into PDF', async () => { - const write = jest.spyOn(fs, 'writeFile') - - return useSpy([write], async () => { - write.mockImplementation((_, __, callback) => callback()) - - const opts = { output: 'test.pdf' } - const ret = await pdfInstance(opts).convertFile(new File(onePath)) - const pdf: Buffer = write.mock.calls[0][1] - - expect(write).toHaveBeenCalled() - expect(write.mock.calls[0][0]).toBe('test.pdf') - expect(pdf.toString('ascii', 0, 5)).toBe('%PDF-') - expect(ret.newFile.path).toBe('test.pdf') - expect(ret.newFile.buffer).toBe(write.mock.calls[0][1]) - }) - }) + it( + 'converts markdown file into PDF', + async () => { + const write = jest.spyOn(fs, 'writeFile') + + return useSpy([write], async () => { + write.mockImplementation((_, __, callback) => callback()) + + const opts = { output: 'test.pdf' } + const ret = await pdfInstance(opts).convertFile(new File(onePath)) + const pdf: Buffer = write.mock.calls[0][1] + + expect(write).toHaveBeenCalled() + expect(write.mock.calls[0][0]).toBe('test.pdf') + expect(pdf.toString('ascii', 0, 5)).toBe('%PDF-') + expect(ret.newFile.path).toBe('test.pdf') + expect(ret.newFile.buffer).toBe(write.mock.calls[0][1]) + }) + }, + 10000 + ) }) }) From a278339fbea68c119e3abc062428e898b7192304 Mon Sep 17 00:00:00 2001 From: Yuki Hattori Date: Sat, 25 Aug 2018 18:47:40 +0900 Subject: [PATCH 05/12] Refactor File class --- src/file.ts | 44 +++++++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/src/file.ts b/src/file.ts index 6894347c..c7535663 100644 --- a/src/file.ts +++ b/src/file.ts @@ -3,6 +3,8 @@ import getStdin from 'get-stdin' import globby from 'globby' import path from 'path' +const markdownExtensions = ['*.md', '*.mdown', '*.markdown', '*.markdn'] + export enum FileType { File, StandardIO, @@ -22,23 +24,16 @@ export class File { } convert(output: string | undefined, extension: string): File { - // Convert filename - if (output === undefined) { - const file = new File(this.convertExtension(extension)) - file.type = this.type - - return file - } + if (output === undefined) + return File.initialize( + this.convertExtension(extension), + f => (f.type = this.type) + ) - // Output to stdout - if (output === '-') { - const file = new File('') - file.type = FileType.StandardIO + if (output === '-') + return File.initialize('-', f => (f.type = FileType.StandardIO)) - return file - } - - return new File(output) + return File.initialize(output) } async load() { @@ -79,7 +74,7 @@ export class File { static async find(...pathes: string[]): Promise { return (await globby(pathes, { absolute: true, - expandDirectories: { files: ['*.md', '*.mdown', '*.markdown'] }, + expandDirectories: { files: markdownExtensions }, })).map(path => new File(path)) } @@ -87,11 +82,18 @@ export class File { this.stdinBuffer = this.stdinBuffer || (await getStdin.buffer()) if (this.stdinBuffer.length === 0) return undefined - const file = new File('-') - - file.buffer = this.stdinBuffer - file.type = FileType.StandardIO + return this.initialize('-', f => { + f.buffer = this.stdinBuffer + f.type = FileType.StandardIO + }) + } - return file + private static initialize( + filepath: string, + tap: (instance: File) => void = () => {} + ) { + const instance = new this(filepath) + tap(instance) + return instance } } From f2a0f5f534e69e29f994d250e657966a2ac92922 Mon Sep 17 00:00:00 2001 From: Yuki Hattori Date: Sat, 25 Aug 2018 19:57:12 +0900 Subject: [PATCH 06/12] Update .circleci/config.yml to be able to clear cache By using environments, it can change cache key on CircleCI. --- .circleci/config.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 8fe8f33c..dd1c6f22 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -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 From 53b700c438de482272aaba585b9bd3aa6ba3c85d Mon Sep 17 00:00:00 2001 From: Yuki Hattori Date: Sat, 25 Aug 2018 20:38:43 +0900 Subject: [PATCH 07/12] Improve test mock of CLI interface to work on CircleCI's 6.14.2 build --- test/marp-cli.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/test/marp-cli.ts b/test/marp-cli.ts index 1506b09a..6bf214d1 100644 --- a/test/marp-cli.ts +++ b/test/marp-cli.ts @@ -18,8 +18,11 @@ describe('Marp CLI', () => { const error = jest.spyOn(console, 'error') return useSpy([exit, log, error], async () => { - await marpCli(cmd) - expect(exit).toHaveBeenCalledWith(0) + exit.mockImplementation(code => { + throw new CLIError('EXIT', code) + }) + + expect(await marpCli(cmd)).toBe(0) const [logged] = log.mock.calls[0] expect(logged).toContain(`@marp-team/marp-cli v${version}`) @@ -37,6 +40,10 @@ describe('Marp CLI', () => { const error = jest.spyOn(console, 'error') return useSpy([exit, error], async () => { + exit.mockImplementation(code => { + throw new CLIError('EXIT', code) + }) + expect(await marpCli(cmd)).toBe(0) expect(error).toHaveBeenCalledWith(expect.stringContaining('Usage')) }) From 505592d9edbcb43717a98080aade8dca789208d2 Mon Sep 17 00:00:00 2001 From: Yuki Hattori Date: Sat, 25 Aug 2018 20:59:53 +0900 Subject: [PATCH 08/12] Stop mocking process.exit on help option --- test/marp-cli.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/test/marp-cli.ts b/test/marp-cli.ts index 6bf214d1..e7c80898 100644 --- a/test/marp-cli.ts +++ b/test/marp-cli.ts @@ -36,14 +36,9 @@ describe('Marp CLI', () => { const confirmHelp = (...cmd: string[]) => { it('outputs help to stderr', async () => { - const exit = jest.spyOn(process, 'exit') const error = jest.spyOn(console, 'error') - return useSpy([exit, error], async () => { - exit.mockImplementation(code => { - throw new CLIError('EXIT', code) - }) - + return useSpy([error], async () => { expect(await marpCli(cmd)).toBe(0) expect(error).toHaveBeenCalledWith(expect.stringContaining('Usage')) }) From dd1fdf4726c8b7e7dec5270353248598d890f4f6 Mon Sep 17 00:00:00 2001 From: Yuki Hattori Date: Sat, 25 Aug 2018 21:11:38 +0900 Subject: [PATCH 09/12] Use mockImplementationOnce instead of mockImplementation on mocking process.exit --- test/marp-cli.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/marp-cli.ts b/test/marp-cli.ts index e7c80898..bb01b243 100644 --- a/test/marp-cli.ts +++ b/test/marp-cli.ts @@ -18,7 +18,7 @@ describe('Marp CLI', () => { const error = jest.spyOn(console, 'error') return useSpy([exit, log, error], async () => { - exit.mockImplementation(code => { + exit.mockImplementationOnce(code => { throw new CLIError('EXIT', code) }) From 77ba7f86019a34b7e14d23bcd649de4473672c40 Mon Sep 17 00:00:00 2001 From: Yuki Hattori Date: Sat, 25 Aug 2018 21:32:50 +0900 Subject: [PATCH 10/12] Use runInBand option while running test on CircleCI --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index dd1c6f22..f5b1d9ee 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -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 From 115e9ef017bbd11582f2712c4eb8f7ed72902b0e Mon Sep 17 00:00:00 2001 From: Yuki Hattori Date: Sat, 25 Aug 2018 23:15:15 +0900 Subject: [PATCH 11/12] Add test of stdin input --- test/marp-cli.ts | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/test/marp-cli.ts b/test/marp-cli.ts index bb01b243..512aa2bb 100644 --- a/test/marp-cli.ts +++ b/test/marp-cli.ts @@ -1,9 +1,11 @@ import fs from 'fs' +import getStdin from 'get-stdin' import path from 'path' import context from './_helpers/context' import { useSpy } from './_helpers/spy' import marpCli from '../src/marp-cli' import * as cli from '../src/cli' +import { File } from '../src/file' import { Converter, ConvertType } from '../src/converter' import { CLIError } from '../src/error' @@ -114,11 +116,11 @@ describe('Marp CLI', () => { context('with -o option', () => { it('converts file and output to stdout when -o is "-"', async () => { const cliInfo = jest.spyOn(cli, 'info') - const write = jest.spyOn(process.stdout, 'write') + const stdout = jest.spyOn(process.stdout, 'write') - return useSpy([cliInfo, write], async () => { + return useSpy([cliInfo, stdout], async () => { expect(await marpCli([onePath, '-o', '-'])).toBe(0) - expect(write).toHaveBeenCalledTimes(1) + expect(stdout).toHaveBeenCalledTimes(1) }) }) @@ -145,4 +147,26 @@ describe('Marp CLI', () => { }) }) }) + + context('with passing from stdin', () => { + it('converts markdown came from stdin and outputs to stdout', async () => { + const cliInfo = jest.spyOn(cli, 'info') + const stdin = jest.spyOn(getStdin, 'buffer') + const stdout = jest.spyOn(process.stdout, 'write') + + await useSpy([cliInfo, stdin, stdout], async () => { + // Reset cached stdin buffer + // @ts-ignore + File.stdinBuffer = undefined + + stdin.mockImplementation(async () => new Buffer('# markdown')) + + expect(await marpCli()).toBe(0) + expect(cliInfo).toHaveBeenCalledWith( + expect.stringContaining(' => ') + ) + expect(stdout).toHaveBeenCalled() + }) + }) + }) }) From 981bf2b2210cdeaf22b0e8773b353dae022bc0c9 Mon Sep 17 00:00:00 2001 From: Yuki Hattori Date: Sat, 25 Aug 2018 23:19:32 +0900 Subject: [PATCH 12/12] Update CHANGELOG.md --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce5a2675..9326c27f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Added + +- Support conversion from standard input ([#4](https://github.com/marp-team/marp-cli/pull/4)) + ### Fixed - Fix incorrect CJK fonts in exported PDF ([#3](https://github.com/marp-team/marp-cli/pull/3))