Skip to content

Commit 486d6fc

Browse files
committed
feat(cli): Add @tracespace/cli package
Closes #13
1 parent 83f9378 commit 486d6fc

12 files changed

Lines changed: 672 additions & 2 deletions

File tree

.travis.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ jobs:
2323
# deploy if tag starts with v
2424
if: tag =~ ^v
2525
node_js: node
26-
script: yarn run example
26+
script: yarn run example && yarn run docs
27+
after_success: echo "skipping coverage upload"
2728
# set npm credentials for npm publish
2829
before_deploy:
2930
- echo "//registry.npmjs.org/:_authToken=\${NPM_TOKEN}" > ~/.npmrc

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ tracespace is an open-source collection of tools to make looking at circuit boar
2727
[![gerber-parser][gerber-parser-badge]][gerber-parser] - Streaming Gerber/drill file parser
2828
[![whats-that-gerber][whats-that-gerber-badge]][whats-that-gerber] - Identify Gerber and drill files by filename
2929
[![@tracespace/fixtures][tracespace-fixtures-badge]][tracespace-fixtures] - Test fixtures for tracespace projects
30+
[![@tracespace/cli][tracespace-cli-badge]][tracespace-cli]
3031

3132
[pcb-stackup]: ./packages/pcb-stackup
3233
[pcb-stackup-core]: ./packages/pcb-stackup-core
@@ -35,13 +36,15 @@ tracespace is an open-source collection of tools to make looking at circuit boar
3536
[gerber-parser]: ./packages/gerber-parser
3637
[whats-that-gerber]: ./packages/whats-that-gerber
3738
[tracespace-fixtures]: ./packages/fixtures
39+
[tracespace-fixtures]: ./packages/cli
3840
[pcb-stackup-badge]: https://img.shields.io/npm/v/pcb-stackup.svg?label=pcb-stackup&style=flat-square&maxAge=3600
3941
[pcb-stackup-core-badge]: https://img.shields.io/npm/v/pcb-stackup-core.svg?label=pcb-stackup-core&style=flat-square&maxAge=3600
4042
[gerber-to-svg-badge]: https://img.shields.io/npm/v/gerber-to-svg.svg?label=gerber-to-svg&style=flat-square&maxAge=3600
4143
[gerber-plotter-badge]: https://img.shields.io/npm/v/gerber-plotter.svg?label=gerber-plotter&style=flat-square&maxAge=3600
4244
[gerber-parser-badge]: https://img.shields.io/npm/v/gerber-parser.svg?label=gerber-parser&style=flat-square&maxAge=3600
4345
[whats-that-gerber-badge]: https://img.shields.io/npm/v/whats-that-gerber.svg?label=whats-that-gerber&style=flat-square&maxAge=3600
4446
[tracespace-fixtures-badge]: https://img.shields.io/npm/v/@tracespace/fixtures.svg?label=@tracespace/fixtures&style=flat-square&maxAge=3600
47+
[tracespace-fixtures-badge]: https://img.shields.io/npm/v/@tracespace/cli.svg?label=@tracespace/cli&style=flat-square&maxAge=3600
4548

4649
## examples
4750

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"integration:server": "lerna run integration:server --parallel",
1616
"lint": "eslint '**/*.js'",
1717
"format": "prettier-eslint --write '**/*.js'",
18+
"docs": "lerna run docs",
1819
"example": "lerna run example",
1920
"prebump": "yarn run test",
2021
"bump": "lerna publish",

packages/cli/README.md

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
# tracespace cli
2+
3+
[![npm][npm-badge]][npm]
4+
5+
> Render PCBs as SVGs from the comfort of your own terminal
6+
7+
The tracespace CLI provides a wrapper for [gerber-to-svg][] and [pcb-stackup][] so you can generate beautiful, precise SVG renders of printed circuit boards quickly and easily.
8+
9+
Part of the [tracespace][] collection of PCB visualization tools.
10+
11+
[gerber-to-svg]: ../gerber-to-svg
12+
[pcb-stackup]: ../pcb-stackup
13+
[tracespace]: https://github.com/tracespace/tracespace
14+
[npm]: https://www.npmjs.com/package/@tracespace/cli
15+
[npm-badge]: https://img.shields.io/npm/v/@tracespace/cli.svg?style=flat-square&maxAge=3600
16+
17+
## install
18+
19+
```shell
20+
npm install -g @tracespace/cli
21+
# or
22+
yarn global add @tracespace/cli
23+
```
24+
25+
## usage
26+
27+
```shell
28+
tracespace [options] <files...>
29+
```
30+
31+
You can also use [`npx`][npx] to run without installing globally
32+
33+
```shell
34+
npx @tracespace/cli [options] <files...>
35+
```
36+
37+
[npx]: https://github.com/zkat/npx
38+
39+
### options
40+
41+
All options can be specified using a config file (`.tracespacerc`, `.tracespacerc.json`, `tracespace.config.js`, etc.) or a `"tracespace"` key in `package.json`. Config will be loaded from the current working directory. See [cosmiconfig][] for additional acceptable config file names and formats.
42+
43+
[cosmiconfig]: https://github.com/davidtheclark/cosmiconfig
44+
45+
#### `-h`, `--help`
46+
47+
* Type: boolean
48+
* Description: prints version and usage then exits
49+
50+
```shell
51+
# Print usage
52+
tracespace --help
53+
```
54+
55+
#### `-v`, `--version`
56+
57+
* Type: boolean
58+
* Description: prints version then exits
59+
60+
```shell
61+
# Print version
62+
tracespace --version
63+
```
64+
65+
<!-- insert:docs:options -->
66+
67+
#### `-o`, `--out`, `config.out`
68+
69+
* Type: `string`
70+
* Default: `.`
71+
* Description: Output directory (or `-` for stdout)
72+
73+
```shell
74+
# Write SVGs into directory `./renders`
75+
tracespace --out=renders
76+
```
77+
78+
#### `-B`, `--noBoard`, `config.noBoard`
79+
80+
* Type: `boolean`
81+
* Default: `false`
82+
* Description: Skip rendering PCB top and bottom
83+
84+
```shell
85+
# Output only the individual layer renders
86+
tracespace -B
87+
```
88+
89+
#### `-L`, `--noLayer`, `config.noLayer`
90+
91+
* Type: `boolean`
92+
* Default: `false`
93+
* Description: Skip rendering individual Gerber and drill layers
94+
95+
```shell
96+
# Output only the top and bottom PCB renders
97+
tracespace -L
98+
```
99+
100+
#### `-f`, `--force`, `config.force`
101+
102+
* Type: `boolean`
103+
* Default: `false`
104+
* Description: Attempt to render files even if they're unrecognized
105+
106+
```shell
107+
# Attempt render even if whats-that-gerber cannot identify
108+
tracespace -B --force some-file.xyz
109+
```
110+
111+
#### `-g`, `--gerber`, `config.gerber`
112+
113+
* Type: `object`
114+
* Default: `{}`
115+
* Description: Options for all gerber files (passed to gerber-to-svg)
116+
117+
```shell
118+
# Set the color attribute of all Gerber SVGs
119+
tracespace -B -g.attributes.color=blue
120+
```
121+
122+
#### `-d`, `--drill`, `config.drill`
123+
124+
* Type: `object`
125+
* Default: `{}`
126+
* Description: Options for all drill files (passed to gerber-to-svg)
127+
128+
```shell
129+
# Set the color attribute of all drill SVGs
130+
tracespace -B -d.attributes.color=red
131+
```
132+
133+
#### `-b`, `--board`, `config.board`
134+
135+
* Type: `object`
136+
* Default: `{}`
137+
* Description: Options for PCB renders (passed to pcb-stackup)
138+
139+
```shell
140+
# Set the soldermask color of the board renders
141+
tracespace -b.color.sm="rgba(128,00,00,0.75)"
142+
```
143+
144+
#### `-l`, `--layer`, `config.layer`
145+
146+
* Type: `object`
147+
* Default: `{}`
148+
* Description: Override the layers options of a given file
149+
150+
> If you're using this option a lot, you may want to consider using a config file
151+
152+
```shell
153+
# Set layer type of `arduino-uno.drd` to `drl` layer and parse as a drill file
154+
tracespace -l.arduino-uno.drd.type=drl -l.arduino-uno.drd.options.filetype=drill
155+
```
156+
157+
#### `-q`, `--quiet`, `config.quiet`
158+
159+
* Type: `boolean`
160+
* Default: `false`
161+
* Description: Suppress informational output (info logs to stderr)
162+
163+
```shell
164+
# Do not print info to stderr
165+
tracespace --quiet
166+
```
167+
<!-- endinsert:docs:options -->

packages/cli/index.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#!/usr/bin/env node
2+
'use strict'
3+
4+
const cosmiconfig = require('cosmiconfig')
5+
const yargs = require('yargs')
6+
const debug = require('debug')('@tracespace/cli')
7+
const cli = require('./lib/cli')
8+
9+
debug('Searching for default config')
10+
11+
cosmiconfig('tracespace')
12+
.search()
13+
.then(rc => {
14+
debug('default config', rc)
15+
return rc ? Object.assign({_configFile: rc.filepath}, rc.config) : {}
16+
})
17+
.then(config => {
18+
const argv = process.argv.slice(2)
19+
debug('argv', argv)
20+
return cli(argv, config)
21+
})
22+
.then(
23+
() => {
24+
debug('cli ran successfully')
25+
process.exit(0)
26+
},
27+
error => {
28+
console.error(`Error: ${error.message}\n\nUsage:`)
29+
yargs.showHelp()
30+
process.exit(1)
31+
}
32+
)

packages/cli/lib/cli.js

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
'use strict'
2+
3+
const fs = require('fs')
4+
const path = require('path')
5+
const util = require('util')
6+
const common = require('common-prefix')
7+
const {get} = require('dot-prop')
8+
const glob = require('globby')
9+
const makeDir = require('make-dir')
10+
const debug = require('debug')('@tracespace/cli')
11+
12+
const gerberToSvg = require('gerber-to-svg')
13+
const pcbStackup = require('pcb-stackup')
14+
const whatsThatGerber = require('whats-that-gerber')
15+
const yargs = require('yargs')
16+
17+
const {description, version} = require('../package.json')
18+
const examples = require('./examples')
19+
const options = require('./options')
20+
const resolve = require('./resolve')
21+
22+
const writeFile = util.promisify(fs.writeFile)
23+
const stackup = util.promisify(pcbStackup)
24+
25+
module.exports = function cli (processArgv, config) {
26+
const argv = yargs
27+
.usage('$0 [options] <files...>', `${description}\nv${version}`, yargs => {
28+
yargs.positional('files', {
29+
coerce: files => files.map(resolve),
30+
describe:
31+
"Filenames, directories, or globs to a PCB's Gerber/drill files",
32+
type: 'string'
33+
})
34+
35+
examples.forEach(e => yargs.example(e.cmd, e.desc))
36+
37+
yargs.epilog(
38+
`You may also specify options in the current working directory using a config file in (.tracespacerc, .tracespacerc.json, tracespace.config.js, etc.) or a "tracespace" key in package.json`
39+
)
40+
})
41+
.config(config)
42+
.options(options)
43+
.version()
44+
.help()
45+
.alias({help: 'h', version: 'v'})
46+
.fail((error, message, yargs) => {
47+
throw new Error(error)
48+
})
49+
.parse(processArgv)
50+
51+
debug('argv', argv)
52+
53+
const info = message => !argv.quiet && console.warn(message)
54+
55+
if (config._configFile) info(`Config loaded from ${config._configFile}`)
56+
57+
return glob(argv.files)
58+
.then(renderFiles)
59+
.then(writeRenders)
60+
61+
function renderFiles (filenames) {
62+
const layers = filenames.map(makeLayerFromFilename).filter(_ => _)
63+
64+
if (!layers.length) throw new Error(`No valid Gerber or drill files found`)
65+
66+
return stackup(layers, argv.board)
67+
}
68+
69+
function makeLayerFromFilename (filename) {
70+
const basename = path.basename(filename)
71+
const type = getType(basename)
72+
73+
if (type === 'drw' && !argv.force) {
74+
info(`Skipping ${basename} (unable to identify type)`)
75+
return null
76+
}
77+
78+
info(`Rendering ${basename} as ${whatsThatGerber.getFullName(type)}`)
79+
80+
const gerber = fs.createReadStream(filename)
81+
const options = getOptions(basename, type)
82+
83+
debug(filename, type, options)
84+
85+
return {type, gerber, options, filename: basename}
86+
}
87+
88+
function getType (basename) {
89+
const defaultType = whatsThatGerber(basename)
90+
91+
return get(argv.layer, `${basename}.type`, defaultType)
92+
}
93+
94+
function getOptions (basename, type) {
95+
const defaultOptions = type === 'drl' ? argv.drill : argv.gerber
96+
97+
return get(argv.layer, `${basename}.options`, defaultOptions)
98+
}
99+
100+
function writeRenders (stackup) {
101+
const name = inferBoardName(stackup)
102+
const ensureDir =
103+
argv.out !== options.out.STDOUT ? makeDir(argv.out) : Promise.resolve()
104+
105+
return ensureDir.then(() =>
106+
Promise.all([
107+
!argv.noBoard && writeOutput(`${name}.top.svg`, stackup.top.svg),
108+
!argv.noBoard && writeOutput(`${name}.bottom.svg`, stackup.bottom.svg),
109+
...stackup.layers
110+
.filter(_ => !argv.noLayer)
111+
.map(layer =>
112+
writeOutput(
113+
`${layer.filename}.${layer.type}.svg`,
114+
gerberToSvg.render(layer.converter, layer.options.attributes)
115+
)
116+
)
117+
])
118+
)
119+
}
120+
121+
function writeOutput (name, contents) {
122+
if (argv.out === options.out.STDOUT) return console.log(contents)
123+
124+
const filename = path.join(argv.out, name)
125+
info(`Writing ${filename}`)
126+
return writeFile(filename, contents)
127+
}
128+
}
129+
130+
function inferBoardName (stackup) {
131+
const names = stackup.layers.map(ly =>
132+
path.basename(ly.filename, path.extname(ly.filename))
133+
)
134+
return common(names) || 'board'
135+
}

packages/cli/lib/examples.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
'use strict'
2+
3+
const options = require('./options')
4+
5+
module.exports = [
6+
{
7+
cmd: '$0',
8+
desc: 'Render files in `cwd` and output to `cwd`'
9+
},
10+
{
11+
cmd: '$0 --out=-',
12+
desc: 'Render files in `cwd` and output to `stdout`'
13+
},
14+
...Object.keys(options).map(name => options[name].example)
15+
]

0 commit comments

Comments
 (0)