Skip to content

Commit

Permalink
Merge pull request #14 from kieler/cfr/cli-serve
Browse files Browse the repository at this point in the history
Feat: Add `serve` command to the CLI
  • Loading branch information
christoph-fricke authored Sep 16, 2021
2 parents 43a09d0 + f885204 commit 0f08fc2
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 50 deletions.
3 changes: 2 additions & 1 deletion applications/klighd-cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,14 @@
"watch": "run-p --print-label \"watch:*\"",
"watch:app": "webpack --watch",
"watch:server": "tsc -w -p ./tsconfig.server.json",
"prepackage": "echo 'exports.VERSION = \"'\"$npm_package_version\"'\";' > ./lib/version.js",
"package": "pkg -c pkg.json ./lib/klighd.js",
"start": "node ./lib/main.js",
"socket": "node ./lib/main.js --ls_port=5007"
},
"dependencies": {
"@kieler/klighd-core": "0.1.0",
"commander": "^7.2.0",
"commander": "^8.1.0",
"fastify": "^3.15.0",
"fastify-static": "^4.0.1",
"fastify-websocket": "^3.1.0",
Expand Down
143 changes: 95 additions & 48 deletions applications/klighd-cli/server/klighd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,59 +18,77 @@
// link to view the diagram for a given file.

import { Command } from "commander";
import open from "open";
import { pathToFileURL } from "url";
import getPort from "get-port";
import openUrl from "open";
import { pathToFileURL } from "url";
import { parseIntOrUndefined } from "./helpers";
import { createServer } from "./server";
import { VERSION } from "./version";

const program = new Command();
program
.version("0.1.0")
.description(
"Starts a webserver to view a diagram for a given file. The diagram is generated by a separate language server which is not bundled with this executable."
)
.addHelpText(
"after",
`
Alternatively the path to a language server jar can be configured with the environment
variable LS_PATH. The path option takes precedence over the environment variable.
If both a path and a port for a language server is provided, the webserver will
use the port to connect to the listening language server.
const setupInformation = `
Alternatively, the path to a language server jar can be configured with the
environment variable LS_PATH. The path option takes precedence over the environment
variable. If both a path and a port for a language server is provided, the webserver
will use the port to connect to the listening language server.
Example calls:
$ klighd --ls_port 5007 ./ABRO.sctx
$ klighd --ls_path ../language-server.jar ./example.elkt`
);
$ ${process.title} --ls_port 5007 ./ABRO.sctx
$ ${process.title} --ls_path ../language-server.jar ./example.elkt
$ ${process.title} serve -p 8000
`;

const program = new Command(process.title);

program
.arguments("<file>")
.option("--clear", "Clear persisted data. Resets all previously stored diagram options.")
.option(
"--ls_port <port>",
"Uses a language server for the synthesis that listens on the given port for connections."
)
.option(
"--ls_path <path>",
"Starts a language server for the synthesis located as a jar at the given path."
.version(VERSION)
.description(
"Starts a web server to view a KLighD synthesized diagram.\nThe KLighD synthesis must be provided by a separate language server."
)
.option("--no-fit", "Do not resize the diagram to fit the viewport when it is redrawn.")
.action(async (file, options, program: Command) => {
// A path to a language can be configured by an environment variable.
// The lsp_path option for the command takes a higher preference.
const lsPath: string | undefined = options.ls_path ?? process.env.LS_PATH;
const lsPort = parseIntOrUndefined(options.ls_port);

// Fail fast if no possible LS connection is provided
if (!lsPath && !lsPort) {
console.error(
"Please provide either a path to a language server jar or a port for a listening language server!\n"
);
program.help({ error: true });
.addHelpText("after", setupInformation)
.configureHelp({ sortOptions: true, sortSubcommands: true });

// Global options
program
.option("--ls_port <port>", "port used to connect to a language server")
.option("--ls_path <path>", "path to a language server jar")
.option("-p, --port <port>", "port used for the diagram-view server");

const serve = new Command("serve")
.description("start the diagram-view server without any specific file")
.action(async () => {
// The global options only exists on the program object and not on the program passed in as the last argument.
// In case these Commands are refactored into another file, a factory function must be used to receive the "original program".
const port = parseIntOrUndefined(program.opts().port);
const [lsPath, lsPort] = getLanguageServerAccess(program);

try {
const finalPort = await findPort(port);
const server = createServer({ logging: "error", lsPort, lsPath });

const url = await server.listen(finalPort);

// Minimal CLI output so it is still understandable but also parsable in scripts.
console.log("Server listening at:", url);
} catch (error) {
console.error(error);
process.exit(1);
}
});

const open = new Command("open")
.description("start and open a diagram view for the given file")
.arguments("<file>")
.option("--clear", "clear persisted data")
.option("--no-fit", "do not resize the diagram to fit the viewport")
.action(async (file, options) => {
// The global options only exists on the program object and not on the program passed in as the last argument.
// In case these Commands are refactored into another file, a factory function must be used to receive the "original program".
const port = parseIntOrUndefined(program.opts().port);
const [lsPath, lsPort] = getLanguageServerAccess(program);

// options.fit is true when the --no-fit flag is absent.
// See https://www.npmjs.com/package/commander#other-option-types-negatable-boolean-and-booleanvalue
// Local command options
const resizeToFit = options.fit;
const clearData = options.clear;

Expand All @@ -79,15 +97,12 @@ program
clearData ? "&clearData=true" : ""
}`;

const fileUrl = pathToFileURL(file);
const server = createServer({ logging: "warn", lsPort, lsPath });

// Listen on a random, available port
try {
// Find an available port. Persisted options (localStorage) are stored per origin, which is port dependent.
const port = await getPort({ port: [7000, 7001, 7002, 7003, 7004, 7005] });
const addr = await server.listen(port);
const fileUrl = pathToFileURL(file);
const finalPort = await findPort(port);
const server = createServer({ logging: "warn", lsPort, lsPath });

const addr = await server.listen(finalPort);
const url = `${addr}?source=${fileUrl}${preferences}`;

console.log("\n==============================\n");
Expand All @@ -96,11 +111,43 @@ program
console.log(url);
console.log("\n==============================\n");

open(url);
openUrl(url);
} catch (error) {
console.error(error);
process.exit(1);
}
});

// Add commands
program.addCommand(serve);
program.addCommand(open, { isDefault: true });

program.parse(process.argv);

////////////////////////////////////////////////////////////////////////////////
// Helper Functions

/** Returns an available ports. A provided port overwrites the result regardless of whether or not it is available. */
async function findPort(port?: number): Promise<number> {
return port ?? (await getPort({ port: [7000, 7001, 7002, 7003, 7004, 7005] }));
}

/** Ensures that either a path or port is specified for the LS. Reads the options from the global options. */
function getLanguageServerAccess(
global: typeof program
): [path: string | undefined, port: number | undefined] {
// A path to a language can be configured by an environment variable.
// The lsp_path option for the command takes a higher preference.
const lsPath: string | undefined = global.opts().ls_path ?? process.env.LS_PATH;
const lsPort = parseIntOrUndefined(global.opts().ls_port);

// Fail fast if no possible LS connection is provided
if (!lsPath && !lsPort) {
console.error(
"Please provide either a path to a language server jar or a port for a listening language server!\n"
);
global.help({ error: true });
}

return [lsPath, lsPort];
}
21 changes: 21 additions & 0 deletions applications/klighd-cli/server/version.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* KIELER - Kiel Integrated Environment for Layout Eclipse RichClient
*
* http://rtsys.informatik.uni-kiel.de/kieler
*
* Copyright 2021 by
* + Kiel University
* + Department of Computer Science
* + Real-Time and Embedded Systems Group
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*/

// THE CONTENT OF THIS FILE IS A PLACEHOLDER FOR TYPESCRIPT. IT WILL BE REPLACED
// WITH THE ACTUAL PACKAGE VERSION WHEN THE CLI IS PACKAGED.

export const VERSION = "unknown";
7 changes: 6 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2252,11 +2252,16 @@ commander@^6.1.0:
resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c"
integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==

commander@^7.0.0, commander@^7.2.0:
commander@^7.0.0:
version "7.2.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7"
integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==

commander@^8.1.0:
version "8.1.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-8.1.0.tgz#db36e3e66edf24ff591d639862c6ab2c52664362"
integrity sha512-mf45ldcuHSYShkplHHGKWb4TrmwQadxOn7v4WuhDJy0ZVoY5JFajaRDKD0PNe5qXzBX0rhovjTnP6Kz9LETcuA==

commondir@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
Expand Down

0 comments on commit 0f08fc2

Please sign in to comment.