hello-lights 0.3.0 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/ci.yml +116 -0
- package/README.md +47 -3
- package/cli.js +3 -0
- package/lib/cli/commander-options.js +127 -0
- package/lib/cli/exec-command.js +42 -0
- package/lib/cli/exec-file-command.js +40 -0
- package/lib/cli/index.js +1 -0
- package/lib/cli/repl-command.js +127 -0
- package/lib/cli/serve/index.js +72 -0
- package/lib/cli/serve/public/index.html +42 -0
- package/lib/cli/serve/public/main.js +41 -0
- package/lib/cli/serve/public/style.css +97 -0
- package/lib/cli/yargs.js +20 -0
- package/package.json +24 -22
- package/lib/commands/doc-peg-parser.js +0 -801
- package/lib/commands/formatter-peg-parser.js +0 -1065
- package/lib/commands/peg-parser.js +0 -1006
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
branches: [master]
|
|
6
|
+
push:
|
|
7
|
+
branches: [master]
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
build:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
|
|
19
|
+
- uses: actions/setup-node@v4
|
|
20
|
+
with:
|
|
21
|
+
node-version: 22
|
|
22
|
+
|
|
23
|
+
- name: Install system dependencies
|
|
24
|
+
run: sudo apt-get update && sudo apt-get install -y libusb-1.0-0-dev libudev-dev
|
|
25
|
+
|
|
26
|
+
- run: npm ci
|
|
27
|
+
|
|
28
|
+
- run: npm run lint
|
|
29
|
+
|
|
30
|
+
- run: npm run coverage
|
|
31
|
+
|
|
32
|
+
- run: npm run coverage:text
|
|
33
|
+
|
|
34
|
+
- run: npm run build:web
|
|
35
|
+
|
|
36
|
+
- name: Upload web artifact
|
|
37
|
+
if: github.event_name == 'push'
|
|
38
|
+
uses: actions/upload-pages-artifact@v3
|
|
39
|
+
with:
|
|
40
|
+
path: web
|
|
41
|
+
|
|
42
|
+
deploy:
|
|
43
|
+
needs: build
|
|
44
|
+
if: github.event_name == 'push'
|
|
45
|
+
runs-on: ubuntu-latest
|
|
46
|
+
|
|
47
|
+
permissions:
|
|
48
|
+
pages: write
|
|
49
|
+
id-token: write
|
|
50
|
+
|
|
51
|
+
environment:
|
|
52
|
+
name: github-pages
|
|
53
|
+
url: ${{ steps.deploy.outputs.page_url }}
|
|
54
|
+
|
|
55
|
+
steps:
|
|
56
|
+
- name: Deploy to GitHub Pages
|
|
57
|
+
id: deploy
|
|
58
|
+
uses: actions/deploy-pages@v4
|
|
59
|
+
|
|
60
|
+
publish:
|
|
61
|
+
needs: build
|
|
62
|
+
if: github.event_name == 'push' && !contains(github.event.head_commit.message, '[skip ci]')
|
|
63
|
+
runs-on: ubuntu-latest
|
|
64
|
+
|
|
65
|
+
permissions:
|
|
66
|
+
contents: write
|
|
67
|
+
id-token: write
|
|
68
|
+
|
|
69
|
+
steps:
|
|
70
|
+
- uses: actions/checkout@v4
|
|
71
|
+
with:
|
|
72
|
+
token: ${{ secrets.GITHUB_TOKEN }}
|
|
73
|
+
|
|
74
|
+
- uses: actions/setup-node@v4
|
|
75
|
+
with:
|
|
76
|
+
node-version: 22
|
|
77
|
+
registry-url: https://registry.npmjs.org
|
|
78
|
+
|
|
79
|
+
- name: Install system dependencies
|
|
80
|
+
run: sudo apt-get update && sudo apt-get install -y libusb-1.0-0-dev libudev-dev
|
|
81
|
+
|
|
82
|
+
- run: npm ci
|
|
83
|
+
|
|
84
|
+
- run: npm install -g npm@latest
|
|
85
|
+
|
|
86
|
+
- name: Determine version bump type
|
|
87
|
+
id: bump
|
|
88
|
+
run: |
|
|
89
|
+
COMMIT_MSG="${{ github.event.head_commit.message }}"
|
|
90
|
+
if echo "$COMMIT_MSG" | grep -qiE '\[major\]|BREAKING CHANGE'; then
|
|
91
|
+
echo "type=major" >> $GITHUB_OUTPUT
|
|
92
|
+
elif echo "$COMMIT_MSG" | grep -qi '\[minor\]'; then
|
|
93
|
+
echo "type=minor" >> $GITHUB_OUTPUT
|
|
94
|
+
else
|
|
95
|
+
echo "type=patch" >> $GITHUB_OUTPUT
|
|
96
|
+
fi
|
|
97
|
+
|
|
98
|
+
- name: Bump version
|
|
99
|
+
id: version
|
|
100
|
+
run: |
|
|
101
|
+
git config user.name "github-actions[bot]"
|
|
102
|
+
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
103
|
+
npm version ${{ steps.bump.outputs.type }} -m "v%s [skip ci]"
|
|
104
|
+
echo "new_version=$(node -p 'require("./package.json").version')" >> $GITHUB_OUTPUT
|
|
105
|
+
|
|
106
|
+
- name: Push version bump
|
|
107
|
+
run: git push --follow-tags
|
|
108
|
+
|
|
109
|
+
- name: Publish to npm
|
|
110
|
+
run: npm publish --provenance
|
|
111
|
+
|
|
112
|
+
- name: Create GitHub Release
|
|
113
|
+
uses: softprops/action-gh-release@v2
|
|
114
|
+
with:
|
|
115
|
+
tag_name: v${{ steps.version.outputs.new_version }}
|
|
116
|
+
generate_release_notes: true
|
package/README.md
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
# hello-lights
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/hello-lights)
|
|
4
|
-
[](https://david-dm.org/jordao76/hello-lights)
|
|
6
|
-
[](https://david-dm.org/jordao76/hello-lights#info=devDependencies)
|
|
4
|
+
[](https://github.com/jordao76/hello-lights/actions/workflows/ci.yml)
|
|
7
5
|
[](https://github.com/jordao76/hello-lights/blob/master/LICENSE.md)
|
|
8
6
|
|
|
9
7
|
> Commands to control a traffic light
|
|
@@ -39,6 +37,52 @@ Check out the available commands [here](https://jordao76.github.io/hello-lights)
|
|
|
39
37
|
|
|
40
38
|
For the documentation look [here](https://jordao76.github.io/hello-lights/doc/index.html).
|
|
41
39
|
|
|
40
|
+
## Development
|
|
41
|
+
|
|
42
|
+
Install dependencies:
|
|
43
|
+
|
|
44
|
+
```sh
|
|
45
|
+
$ npm install
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### npm scripts
|
|
49
|
+
|
|
50
|
+
| Script | Description |
|
|
51
|
+
|---|---|
|
|
52
|
+
| `npm test` | Run all tests (generates PEG parsers first) |
|
|
53
|
+
| `npm run lint` | Lint source and test files |
|
|
54
|
+
| `npm run coverage` | Run tests with coverage instrumentation |
|
|
55
|
+
| `npm run coverage:text` | Print a text coverage summary to the terminal |
|
|
56
|
+
| `npm run coverage:open` | Generate and open an HTML coverage report |
|
|
57
|
+
| `npm run build:doc` | Generate JSDoc documentation into `web/doc/` |
|
|
58
|
+
| `npm run build:web` | Build all web assets (PEG parsers, browserify bundle, docs) |
|
|
59
|
+
| `npm run doc` | Build and open the documentation in the browser |
|
|
60
|
+
| `npm run web` | Build and open the browser demo |
|
|
61
|
+
| `npm run mocha-grep <pattern>` | Run only tests matching a pattern |
|
|
62
|
+
| `npm run cli` | Run the CLI locally |
|
|
63
|
+
|
|
64
|
+
### CLI
|
|
65
|
+
|
|
66
|
+
Run the CLI locally with `npm run cli`:
|
|
67
|
+
|
|
68
|
+
```sh
|
|
69
|
+
$ npm run cli -- exec bounce 300 # execute a command
|
|
70
|
+
$ npm run cli -- exec-file ./cmds.clj # execute commands from a file
|
|
71
|
+
$ npm run cli -- repl # start an interactive REPL
|
|
72
|
+
$ npm run cli -- serve # start the HTTP server on port 9000
|
|
73
|
+
$ npm run cli -- serve --port 3000 # start the HTTP server on a custom port
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Use `--help` for the full list of options, including `--serial-num` to target a specific device and `--selector multi` to control multiple traffic lights at once.
|
|
77
|
+
|
|
78
|
+
## CI
|
|
79
|
+
|
|
80
|
+
The CI workflow runs on every push and pull request to `master`. It has three jobs:
|
|
81
|
+
|
|
82
|
+
- **Build** -- lints, runs tests with coverage, and builds all web assets.
|
|
83
|
+
- **Deploy** -- on push to `master`, deploys the `web/` directory to GitHub Pages.
|
|
84
|
+
- **Publish** -- on push to `master`, bumps the package version, publishes to npm with provenance via trusted publishing, and creates a GitHub Release. The version bump type is determined from the commit message: `[major]` or `BREAKING CHANGE` for major, `[minor]` for minor, and patch by default.
|
|
85
|
+
|
|
42
86
|
## License
|
|
43
87
|
|
|
44
88
|
Licensed under the [MIT license](https://github.com/jordao76/hello-lights/blob/master/LICENSE.md).
|
package/cli.js
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const {Commander} = require('..');
|
|
4
|
+
const {MetaFormatter, CodeFormatter} = require('..').commands;
|
|
5
|
+
|
|
6
|
+
/////////////////////////////////////////////////////////////////
|
|
7
|
+
|
|
8
|
+
const logger = {
|
|
9
|
+
log: (...args) => {
|
|
10
|
+
console.log(chalk.gray(...args));
|
|
11
|
+
},
|
|
12
|
+
error: (...args) => {
|
|
13
|
+
console.error(chalk.red(...args));
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/////////////////////////////////////////////////////////////////
|
|
18
|
+
|
|
19
|
+
class ChalkCodeFormatter extends CodeFormatter {
|
|
20
|
+
|
|
21
|
+
formatCommand(text) {
|
|
22
|
+
return chalk.blue(text);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
formatIdentifier(text) {
|
|
26
|
+
if (['red', 'yellow', 'green'].indexOf(text) >= 0) return chalk[text](text);
|
|
27
|
+
return chalk.blue(text);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
formatVariable(text) {
|
|
31
|
+
return chalk.green(text);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
formatNumber(text) {
|
|
35
|
+
return chalk.magenta(text);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
formatString(text) {
|
|
39
|
+
return chalk.cyan(text);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
formatComment(text) {
|
|
43
|
+
return chalk.gray(text);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
class ChalkMetaFormatter extends MetaFormatter {
|
|
49
|
+
|
|
50
|
+
constructor() {
|
|
51
|
+
super(new ChalkCodeFormatter());
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
formatName(name) {
|
|
55
|
+
return chalk.yellow(name);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
formatParam(param) {
|
|
59
|
+
return chalk.cyan(super.formatParam(param));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
formatReturn($return) {
|
|
63
|
+
return chalk.magenta(super.formatReturn($return));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
formatInlineCode(code) {
|
|
67
|
+
return chalk.cyan(`${code.trim()}`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/////////////////////////////////////////////////////////////////
|
|
73
|
+
|
|
74
|
+
function resolveDeviceManager(options) {
|
|
75
|
+
const clewareDevicePath = '../devices/cleware-switch1';
|
|
76
|
+
let devicePath;
|
|
77
|
+
if (options.devicePath) {
|
|
78
|
+
devicePath = path.resolve(options.devicePath);
|
|
79
|
+
} else {
|
|
80
|
+
// for now, it is always the case that: options.deviceType === 'cleware'
|
|
81
|
+
devicePath = clewareDevicePath;
|
|
82
|
+
}
|
|
83
|
+
const {Manager} = require(devicePath);
|
|
84
|
+
return Manager;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/////////////////////////////////////////////////////////////////
|
|
88
|
+
|
|
89
|
+
function resolveCommander(options) {
|
|
90
|
+
return Commander[options.selector]({
|
|
91
|
+
logger,
|
|
92
|
+
formatter: new ChalkMetaFormatter(),
|
|
93
|
+
manager: resolveDeviceManager(options),
|
|
94
|
+
serialNum: options.serialNum
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/////////////////////////////////////////////////////////////////
|
|
99
|
+
|
|
100
|
+
function define(yargs) {
|
|
101
|
+
yargs
|
|
102
|
+
.option('device-type', {
|
|
103
|
+
alias: 'd',
|
|
104
|
+
describe: 'device type to use',
|
|
105
|
+
choices: ['cleware'],
|
|
106
|
+
default: 'cleware',
|
|
107
|
+
hidden: true }) // un-hide when more than one option exists
|
|
108
|
+
.option('device-path', {
|
|
109
|
+
alias: 'p',
|
|
110
|
+
describe: 'device type path to use, overrides --device',
|
|
111
|
+
normalize: true,
|
|
112
|
+
hidden: true }) // hide advanced option
|
|
113
|
+
.option('serial-num', {
|
|
114
|
+
alias: 'n',
|
|
115
|
+
describe: 'serial number of device to use (only for "single" selector)' })
|
|
116
|
+
.option('selector', {
|
|
117
|
+
alias: 's',
|
|
118
|
+
describe: 'selector type to use',
|
|
119
|
+
choices: ['single', 'multi'],
|
|
120
|
+
default: 'single' });
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/////////////////////////////////////////////////////////////////
|
|
124
|
+
|
|
125
|
+
module.exports = {define, resolveCommander};
|
|
126
|
+
|
|
127
|
+
/////////////////////////////////////////////////////////////////
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/////////////////////////////////////////////////////////////////
|
|
2
|
+
|
|
3
|
+
const commanderOptions = require('./commander-options');
|
|
4
|
+
|
|
5
|
+
/////////////////////////////////////////////////////////////////
|
|
6
|
+
|
|
7
|
+
async function exec(options, cmd, cdr = []) {
|
|
8
|
+
let commander = commanderOptions.resolveCommander(options);
|
|
9
|
+
cdr.unshift(cmd);
|
|
10
|
+
let command = cdr.join(' ');
|
|
11
|
+
await commander.run(command);
|
|
12
|
+
commander.close();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/////////////////////////////////////////////////////////////////
|
|
16
|
+
|
|
17
|
+
const commandSpec = {
|
|
18
|
+
|
|
19
|
+
command: 'exec <cmd>',
|
|
20
|
+
describe: 'executes a command',
|
|
21
|
+
|
|
22
|
+
builder: yargs =>
|
|
23
|
+
yargs.positional('cmd', { describe: 'command to execute' }),
|
|
24
|
+
|
|
25
|
+
handler: argv =>
|
|
26
|
+
exec(argv, argv.cmd, argv._.slice(1)) // argv._ includes 'exec' at index 0
|
|
27
|
+
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/////////////////////////////////////////////////////////////////
|
|
31
|
+
|
|
32
|
+
function define(yargs) {
|
|
33
|
+
yargs
|
|
34
|
+
.command(commandSpec)
|
|
35
|
+
.example('$0 exec bounce 300', '# executes the `bounce 300` command');
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/////////////////////////////////////////////////////////////////
|
|
39
|
+
|
|
40
|
+
module.exports = {define};
|
|
41
|
+
|
|
42
|
+
/////////////////////////////////////////////////////////////////
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/////////////////////////////////////////////////////////////////
|
|
2
|
+
|
|
3
|
+
const commanderOptions = require('./commander-options');
|
|
4
|
+
|
|
5
|
+
/////////////////////////////////////////////////////////////////
|
|
6
|
+
|
|
7
|
+
async function execFile(options, filePath) {
|
|
8
|
+
let commander = commanderOptions.resolveCommander(options);
|
|
9
|
+
await commander.runFile(filePath);
|
|
10
|
+
commander.close();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/////////////////////////////////////////////////////////////////
|
|
14
|
+
|
|
15
|
+
const commandSpec = {
|
|
16
|
+
|
|
17
|
+
command: 'exec-file <file-path>',
|
|
18
|
+
describe: 'executes commands in a file',
|
|
19
|
+
|
|
20
|
+
builder: yargs =>
|
|
21
|
+
yargs.positional('file-path', { describe: 'file to execute' }),
|
|
22
|
+
|
|
23
|
+
handler: argv =>
|
|
24
|
+
execFile(argv, argv.filePath)
|
|
25
|
+
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/////////////////////////////////////////////////////////////////
|
|
29
|
+
|
|
30
|
+
function define(yargs) {
|
|
31
|
+
yargs
|
|
32
|
+
.command(commandSpec)
|
|
33
|
+
.example('$0 exec-file ./my-file.clj', '# executes the file');
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/////////////////////////////////////////////////////////////////
|
|
37
|
+
|
|
38
|
+
module.exports = {define};
|
|
39
|
+
|
|
40
|
+
/////////////////////////////////////////////////////////////////
|
package/lib/cli/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
module.exports = require('./yargs');
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/////////////////////////////////////////////////////////////////
|
|
2
|
+
|
|
3
|
+
const repl = require('repl');
|
|
4
|
+
|
|
5
|
+
/////////////////////////////////////////////////////////////////
|
|
6
|
+
|
|
7
|
+
class CommanderRepl {
|
|
8
|
+
|
|
9
|
+
constructor(commander) {
|
|
10
|
+
this.commander = commander;
|
|
11
|
+
this.multiline = false;
|
|
12
|
+
this.logger = this.commander.logger;
|
|
13
|
+
this.manager = this.commander.manager;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
formatCommandNames() {
|
|
17
|
+
let names = this.commander.commandNames;
|
|
18
|
+
let parts = [' '];
|
|
19
|
+
names.forEach((name, i) => {
|
|
20
|
+
parts.push(` ${name}`);
|
|
21
|
+
if ((i + 1) % 8 === 0) parts.push('\n ');
|
|
22
|
+
});
|
|
23
|
+
return parts.join('');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
help(commandName) {
|
|
27
|
+
if (commandName === undefined) {
|
|
28
|
+
this.logger.log([
|
|
29
|
+
`Commands for the traffic light`,
|
|
30
|
+
`> help`,
|
|
31
|
+
`> help [command name]`,
|
|
32
|
+
`> check device` + (this.supportsNewDevice() ? '\n> new device' : ''),
|
|
33
|
+
`> exit | quit`,
|
|
34
|
+
`> [command]`,
|
|
35
|
+
`> { [... multi line command] }`,
|
|
36
|
+
` available commands:`,
|
|
37
|
+
this.formatCommandNames()
|
|
38
|
+
].join('\n'));
|
|
39
|
+
} else {
|
|
40
|
+
this.commander.help(commandName);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
execute(text, context, filename, callback) {
|
|
45
|
+
text = text.trim();
|
|
46
|
+
let match;
|
|
47
|
+
if (!text) {
|
|
48
|
+
} else if (text === 'cancel') {
|
|
49
|
+
this.commander.cancel();
|
|
50
|
+
} else if (text === 'help') {
|
|
51
|
+
this.help();
|
|
52
|
+
} else if (match = text.match(/^help\s+(.+)/)) { // eslint-disable-line no-cond-assign
|
|
53
|
+
this.help(match[1]);
|
|
54
|
+
} else if (text === 'exit' || text === 'quit') {
|
|
55
|
+
this.commander.cancel();
|
|
56
|
+
this.commander.close();
|
|
57
|
+
this.logger.log('Bye');
|
|
58
|
+
process.exit(0);
|
|
59
|
+
} else if (text === 'check device') {
|
|
60
|
+
this.commander.logInfo();
|
|
61
|
+
} else if (this.supportsNewDevice() && text === 'new device') {
|
|
62
|
+
this.newDevice();
|
|
63
|
+
} else if (text.startsWith('{')) {
|
|
64
|
+
this.multiline = true;
|
|
65
|
+
return this.execute(text.replace('{', ''), context, filename, callback);
|
|
66
|
+
} else if (this.multiline && text.endsWith('}')) {
|
|
67
|
+
this.multiline = false;
|
|
68
|
+
this.commander.run(text.replace('}', ''));
|
|
69
|
+
} else if (this.multiline) {
|
|
70
|
+
return callback(new repl.Recoverable());
|
|
71
|
+
} else {
|
|
72
|
+
this.commander.run(text);
|
|
73
|
+
}
|
|
74
|
+
return callback();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
launch() {
|
|
78
|
+
this.help();
|
|
79
|
+
let server = repl.start({
|
|
80
|
+
prompt: '> ',
|
|
81
|
+
eval: (...args) => this.execute(...args)
|
|
82
|
+
});
|
|
83
|
+
server.on('exit', () => process.exit(0));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
supportsNewDevice() {
|
|
87
|
+
return !!this.manager.newDevice;
|
|
88
|
+
}
|
|
89
|
+
newDevice() {
|
|
90
|
+
this.manager.newDevice();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/////////////////////////////////////////////////////////////////
|
|
96
|
+
|
|
97
|
+
const commanderOptions = require('./commander-options');
|
|
98
|
+
|
|
99
|
+
/////////////////////////////////////////////////////////////////
|
|
100
|
+
|
|
101
|
+
function main(options) {
|
|
102
|
+
let commander = commanderOptions.resolveCommander(options);
|
|
103
|
+
let commanderRepl = new CommanderRepl(commander);
|
|
104
|
+
commanderRepl.launch();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/////////////////////////////////////////////////////////////////
|
|
108
|
+
|
|
109
|
+
const commandSpec = {
|
|
110
|
+
command: 'repl',
|
|
111
|
+
describe: 'starts the REPL',
|
|
112
|
+
handler: main
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
/////////////////////////////////////////////////////////////////
|
|
116
|
+
|
|
117
|
+
function define(yargs) {
|
|
118
|
+
yargs
|
|
119
|
+
.command(commandSpec)
|
|
120
|
+
.example('$0 repl', '# starts a REPL');
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
/////////////////////////////////////////////////////////////////
|
|
124
|
+
|
|
125
|
+
module.exports = {define, CommanderRepl};
|
|
126
|
+
|
|
127
|
+
/////////////////////////////////////////////////////////////////
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/////////////////////////////////////////////////////////////////
|
|
2
|
+
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const commanderOptions = require('../commander-options');
|
|
5
|
+
|
|
6
|
+
/////////////////////////////////////////////////////////////////
|
|
7
|
+
|
|
8
|
+
function createApp(commander) {
|
|
9
|
+
const express = require('express');
|
|
10
|
+
const app = express();
|
|
11
|
+
|
|
12
|
+
app.use(express.static(path.join(__dirname, 'public')));
|
|
13
|
+
app.post('/run', (req, res) => {
|
|
14
|
+
let commandStr = '';
|
|
15
|
+
req.on('data', data => commandStr += data);
|
|
16
|
+
req.on('end', () => {
|
|
17
|
+
commander.run(commandStr);
|
|
18
|
+
res.statusCode = 202; // accepted
|
|
19
|
+
res.end();
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
return app;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function serve(options) {
|
|
27
|
+
const commander = commanderOptions.resolveCommander(options);
|
|
28
|
+
const app = createApp(commander);
|
|
29
|
+
const server = app.listen(options.port, () => console.log(`Server listening on port ${options.port}`));
|
|
30
|
+
server.on('error', err => {
|
|
31
|
+
if (err.code === 'EADDRINUSE') {
|
|
32
|
+
console.error(`Error: port ${options.port} is already in use`);
|
|
33
|
+
} else {
|
|
34
|
+
console.error(`Error: ${err.message}`);
|
|
35
|
+
}
|
|
36
|
+
process.exit(1);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/////////////////////////////////////////////////////////////////
|
|
41
|
+
|
|
42
|
+
const commandSpec = {
|
|
43
|
+
|
|
44
|
+
command: 'serve',
|
|
45
|
+
describe: 'starts the HTTP server for remote light control',
|
|
46
|
+
|
|
47
|
+
builder: yargs =>
|
|
48
|
+
yargs.option('port', {
|
|
49
|
+
alias: 'P',
|
|
50
|
+
describe: 'port to listen on',
|
|
51
|
+
default: 9000,
|
|
52
|
+
type: 'number'
|
|
53
|
+
}),
|
|
54
|
+
|
|
55
|
+
handler: argv => serve(argv)
|
|
56
|
+
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/////////////////////////////////////////////////////////////////
|
|
60
|
+
|
|
61
|
+
function define(yargs) {
|
|
62
|
+
yargs
|
|
63
|
+
.command(commandSpec)
|
|
64
|
+
.example('$0 serve', '# starts the server on port 9000')
|
|
65
|
+
.example('$0 serve --port 3000', '# starts the server on port 3000');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/////////////////////////////////////////////////////////////////
|
|
69
|
+
|
|
70
|
+
module.exports = {define, createApp};
|
|
71
|
+
|
|
72
|
+
/////////////////////////////////////////////////////////////////
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html>
|
|
3
|
+
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="utf-8">
|
|
6
|
+
<title>Hello Lights</title>
|
|
7
|
+
<link rel="stylesheet" href="style.css">
|
|
8
|
+
<script src="main.js"></script>
|
|
9
|
+
</head>
|
|
10
|
+
|
|
11
|
+
<body>
|
|
12
|
+
|
|
13
|
+
<div class="container">
|
|
14
|
+
|
|
15
|
+
<div class="header">
|
|
16
|
+
<h2>hello-lights</h2>
|
|
17
|
+
</div>
|
|
18
|
+
|
|
19
|
+
<div class="command">
|
|
20
|
+
<div>
|
|
21
|
+
<textarea id="command" name="command" spellcheck="false"></textarea>
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
<div class="toolbar">
|
|
26
|
+
<div class="buttons">
|
|
27
|
+
<button id="run">Run</button>
|
|
28
|
+
<button id="cancel">Cancel</button>
|
|
29
|
+
<button id="reset">Reset</button>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
<div class="footer">
|
|
34
|
+
By <a href="https://github.com/jordao76">Jordão</a>.
|
|
35
|
+
Check it out on <a href="https://github.com/jordao76/hello-lights">GitHub</a>.
|
|
36
|
+
</div>
|
|
37
|
+
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
</body>
|
|
41
|
+
|
|
42
|
+
</html>
|