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

Add server mode #27

Merged
merged 11 commits into from
Oct 2, 2018
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

### Added

- Add server mode provided by `--server` (`-s`) option ([#27](https://github.com/marp-team/marp-cli/pull/27))
- Add fonts for internationalization to Docker image ([#26](https://github.com/marp-team/marp-cli/pull/26))

## v0.0.10 - 2018-09-20
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ Under construction.
- [x] Support configuration file (like `.marprc`)
- [x] Watch mode
- [x] Auto-reload
- [ ] Server mode
- [x] Server mode
- [x] HTML templates
- [x] Template that has ready to actual presentation powered by [Bespoke](https://github.com/bespokejs/bespoke)
- [x] Select engine to use any Marpit based module
Expand Down
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,14 @@
"devDependencies": {
"@types/chokidar": "^1.7.5",
"@types/cosmiconfig": "^5.0.3",
"@types/express": "^4.16.0",
"@types/get-stdin": "^5.0.1",
"@types/jest": "^23.3.2",
"@types/jest-plugin-context": "^2.9.0",
"@types/node": "^10.10.1",
"@types/pug": "^2.0.4",
"@types/puppeteer": "^1.6.4",
"@types/supertest": "^2.0.6",
"@types/ws": "^6.0.1",
"@types/yargs": "^12.0.0",
"autoprefixer": "^9.1.5",
Expand Down Expand Up @@ -80,6 +82,7 @@
"stylelint-config-prettier": "^4.0.0",
"stylelint-config-standard": "^18.2.0",
"stylelint-scss": "^3.3.0",
"supertest": "^3.3.0",
"ts-jest": "23.10.0",
"ts-keycode-enum": "^1.0.6",
"tslib": "^1.9.3",
Expand All @@ -95,6 +98,7 @@
"chokidar": "^2.0.4",
"chrome-launcher": "^0.10.4",
"cosmiconfig": "^5.0.6",
"express": "^4.16.3",
"get-stdin": "^6.0.0",
"globby": "^8.0.1",
"import-from": "^2.1.0",
Expand Down
1 change: 1 addition & 0 deletions rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const external = [
'crypto',
'fs',
'path',
'url',
'chrome-launcher/dist/chrome-finder',
'yargs/yargs',
]
Expand Down
25 changes: 23 additions & 2 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,23 @@ type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>
type Overwrite<T, U> = Omit<T, Extract<keyof T, keyof U>> & U

export interface IMarpCLIArguments {
_?: string[]
allowLocalFiles?: boolean
configFile?: string
engine?: string
html?: boolean
inputDir?: string
output?: string
pdf?: boolean
server?: boolean
template?: string
theme?: string
themeSet?: string[]
watch?: boolean
}

export type IMarpCLIConfig = Overwrite<
Omit<IMarpCLIArguments, 'configFile'>,
Omit<IMarpCLIArguments, 'configFile' | '_'>,
{
engine?: ResolvableEngine | ResolvableEngine[]
html?: ConverterOption['html']
Expand Down Expand Up @@ -71,6 +73,8 @@ export class MarpCLIConfig {
})()
: undefined)

const server = this.pickDefined(this.args.server, this.conf.server) || false

const theme = await this.loadTheme()
const initialThemes = theme instanceof Theme ? [theme] : []

Expand All @@ -93,6 +97,7 @@ export class MarpCLIConfig {

return {
output,
server,
themeSet,
allowLocalFiles:
this.pickDefined(
Expand All @@ -115,15 +120,31 @@ export class MarpCLIConfig {
`${output}`.toLowerCase().endsWith('.pdf')
? ConvertType.pdf
: ConvertType.html,
watch: this.pickDefined(this.args.watch || this.conf.watch) || false,
watch:
this.pickDefined(this.args.watch, this.conf.watch) || server || false,
}
}

get files() {
return this.args.server || this.conf.server ? [] : this.args._ || []
}

private async inputDir(): Promise<string | undefined> {
const dir = (() => {
if (this.args.inputDir) return path.resolve(this.args.inputDir)
if (this.conf.inputDir)
return path.resolve(path.dirname(this.confPath!), this.conf.inputDir)

// Fallback to input arguments in server mode
if (this.args.server || this.conf.server) {
if (Array.isArray(this.args._)) {
if (this.args._.length > 1)
throw new CLIError(
'Server mode have to specify just one directory.'
)
if (this.args._.length === 1) return path.resolve(this.args._[0])
}
}
})()
if (dir === undefined) return undefined

Expand Down
25 changes: 16 additions & 9 deletions src/converter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,19 @@ export interface ConverterOption {
options: MarpitOptions
output?: string
readyScript?: string
server?: boolean
template: string
theme?: string
themeSet: ThemeSet
type: ConvertType
watch: boolean
}

export interface ConvertFileOption {
initial?: boolean
onConverted?: ConvertedCallback
}

export interface ConvertResult {
file: File
newFile: File
Expand Down Expand Up @@ -86,30 +92,31 @@ export class Converter {
})
}

async convertFile(file: File): Promise<ConvertResult> {
async convertFile(file: File, opts: ConvertFileOption = {}) {
const buffer = await file.load()
const template = await this.convert(buffer.toString(), file)
const newFile = file.convert(this.options.output, this.options.type)

newFile.buffer = new Buffer(template.result)

const result: ConvertResult = { file, newFile, template }
if (this.options.server && opts.initial) return result

if (this.options.type === ConvertType.pdf)
await this.convertFileToPDF(newFile)

await newFile.save()
return { file, newFile, template }
if (!this.options.server) await newFile.save()
if (opts.onConverted) opts.onConverted(result)

return result
}

async convertFiles(
files: File[],
onConverted: ConvertedCallback = () => {}
): Promise<void> {
async convertFiles(files: File[], opts: ConvertFileOption = {}) {
const { inputDir, output } = this.options

if (!inputDir && output && output !== '-' && files.length > 1)
error('Output path cannot specify with processing multiple files.')

for (const file of files) onConverted(await this.convertFile(file))
for (const file of files) await this.convertFile(file, opts)
}

private async convertFileToPDF(file: File) {
Expand Down
4 changes: 2 additions & 2 deletions src/file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import mkdirp from 'mkdirp'
import path from 'path'
import { tmpName } from 'tmp'

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

export enum FileType {
File,
Expand Down Expand Up @@ -121,7 +121,7 @@ export class File {
static async find(...pathes: string[]): Promise<File[]> {
return (await globby(pathes, {
absolute: true,
expandDirectories: { files: markdownExtensions },
expandDirectories: { files: markdownExtensions.map(ext => `*.${ext}`) },
})).map(p => new File(p))
}

Expand Down
53 changes: 41 additions & 12 deletions src/marp-cli.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { version as coreVersion } from '@marp-team/marp-core/package.json'
import chalk from 'chalk'
import { Argv } from 'yargs'
import yargs from 'yargs/yargs'
import * as cli from './cli'
import fromArguments, { IMarpCLIArguments } from './config'
import { Converter, ConvertedCallback } from './converter'
import { CLIError, error } from './error'
import { File, FileType } from './file'
import { Server } from './server'
import templates from './templates'
import watcher from './watcher'
import watcher, { Watcher } from './watcher'
import { name, version } from '../package.json'

enum OptionGroup {
Expand Down Expand Up @@ -66,6 +68,12 @@ export default async function(argv: string[] = []): Promise<number> {
group: OptionGroup.Basic,
type: 'boolean',
},
server: {
alias: 's',
describe: 'Enable server mode',
group: OptionGroup.Basic,
type: 'boolean',
},
pdf: {
describe: 'Convert slide deck into PDF',
group: OptionGroup.Converter,
Expand Down Expand Up @@ -114,14 +122,14 @@ export default async function(argv: string[] = []): Promise<number> {
}

// Initialize converter
const config = await fromArguments(<IMarpCLIArguments>args)
const config = await fromArguments(args)
const converter = new Converter(await config.converterOption())
const cvtOpts = converter.options

// Find target markdown files
const finder = async () => {
if (cvtOpts.inputDir) {
if (args._.length > 0) {
if (config.files.length > 0) {
cli.error('Cannot pass files together with input directory.')
return []
}
Expand All @@ -132,22 +140,25 @@ export default async function(argv: string[] = []): Promise<number> {

// Regular file finding powered by globby
return <File[]>(
[await File.stdin(), ...(await File.find(...args._))].filter(f => f)
[await File.stdin(), ...(await File.find(...config.files))].filter(
f => f
)
)
}

const files = await finder()
const { length } = files
const foundFiles = await finder()
const { length } = foundFiles

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

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

cli.info(`Converting ${length} markdown${length > 1 ? 's' : ''}...`)
if (!cvtOpts.server)
cli.info(`Converting ${length} markdown${length > 1 ? 's' : ''}...`)

// Convert markdown into HTML
const onConverted: ConvertedCallback = ret => {
Expand All @@ -159,16 +170,16 @@ export default async function(argv: string[] = []): Promise<number> {
}

try {
await converter.convertFiles(files, onConverted)
await converter.convertFiles(foundFiles, { onConverted, initial: true })
} catch (e) {
error(`Failed converting Markdown. (${e.message})`, e.errorCode)
}

// Watch mode
// Watch mode / Server mode
if (cvtOpts.watch) {
watcher(
[
...(cvtOpts.inputDir ? [cvtOpts.inputDir] : args._),
...(cvtOpts.inputDir ? [cvtOpts.inputDir] : config.files),
...cvtOpts.themeSet.fnForWatch,
],
{
Expand All @@ -179,8 +190,26 @@ export default async function(argv: string[] = []): Promise<number> {
onError: e =>
cli.error(`Failed converting Markdown. (${e.message})`),
},
mode: cvtOpts.server
? Watcher.WatchMode.Notify
: Watcher.WatchMode.Convert,
}
)

if (cvtOpts.server) {
const server = new Server(converter, { onConverted })
await server.start()

cli.info(
chalk.green(
`[Server mode] Start server listened at http://localhost:${
server.port
}/ ...`
)
)
} else {
cli.info(chalk.green('[Watch mode] Start watching...'))
}
}

return 0
Expand Down
Loading