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

1012 doc automatically generate the responses of the interface wiki page #1023

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/broken-links-and-wiki.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ jobs:
update_wiki_page "Capabilities" capabilities-markdown
update_wiki_page "Dataflow Graph" wiki:df-graph
update_wiki_page "Query API" wiki:query-api
update_wiki_page "Interface" wiki:interface

if [ $CHANGED_ANY == "true" ]; then
git config --local user.email "action@github.com"
Expand All @@ -99,6 +100,7 @@ jobs:
with:
path: "wiki/"
token: ${{ secrets.GH_DEPLOY_WIKI }}
ignore: ''

- name: "🔎 Check the README for broken links"
uses: becheran/mlc@v0.18.0
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"capabilities-markdown": "ts-node src/documentation/print-capabilities-markdown.ts",
"wiki:df-graph": "ts-node src/documentation/print-dataflow-graph-wiki.ts",
"wiki:query-api": "ts-node src/documentation/print-query-wiki.ts",
"wiki:interface": "ts-node src/documentation/print-interface-wiki.ts",
"build": "tsc --project .",
"build:bundle-flowr": "npm run build && esbuild --bundle dist/src/cli/flowr.js --platform=node --bundle --minify --target=node18 --outfile=dist/src/cli/flowr.min.js",
"lint-local": "npx eslint --version && npx eslint src/ test/ --rule \"no-warning-comments: off\"",
Expand Down
88 changes: 88 additions & 0 deletions src/cli/flowr-main-options.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import type { OptionDefinition } from 'command-line-usage';
import { scripts } from './common/scripts-info';

let _scriptsText: string | undefined;

export function getScriptsText() {
if(_scriptsText === undefined) {
_scriptsText = Array.from(Object.entries(scripts).filter(([, { type }]) => type === 'master script'), ([k]) => k).join(', ');
}
return _scriptsText;
}

export const flowrMainOptionDefinitions: OptionDefinition[] = [
{ name: 'config-file', type: String, description: 'The name of the configuration file to use', multiple: false },
{
name: 'config-json',
type: String,
description: 'The flowR configuration to use, as a JSON string',
multiple: false
},
{
name: 'execute',
alias: 'e',
type: String,
description: 'Execute the given command and exit. Use a semicolon ";" to separate multiple commands.',
typeLabel: '{underline command}',
multiple: false
},
{
name: 'help',
alias: 'h',
type: Boolean,
description: 'Print this usage guide (or the guide of the corresponding script)'
},
{
name: 'no-ansi',
type: Boolean,
description: 'Disable ansi-escape-sequences in the output. Useful, if you want to redirect the output to a file.'
},
{
name: 'port',
type: Number,
description: 'The port to listen on, if --server is given.',
defaultValue: 1042,
typeLabel: '{underline port}'
},
{
name: 'r-path',
type: String,
description: 'The path to the R executable to use. Defaults to your PATH.',
multiple: false
},
{
name: 'r-session-access',
type: Boolean,
description: 'Allow to access the underlying R session when using flowR (security warning: this allows the execution of arbitrary R code!)'
},
{
name: 'script',
alias: 's',
type: String,
description: `The sub-script to run (${getScriptsText()})`,
multiple: false,
defaultOption: true,
typeLabel: '{underline files}',
defaultValue: undefined
},
{
name: 'server',
type: Boolean,
description: 'Do not drop into a repl, but instead start a server on the given port (default: 1042) and listen for messages.'
},
{
name: 'verbose',
alias: 'v',
type: Boolean,
description: 'Run with verbose logging (will be passed to the corresponding script)'
},
{
name: 'version',
alias: 'V',
type: Boolean,
description: 'Provide information about the version of flowR as well as its underlying R system and exit.'
},
{ name: 'ws', type: Boolean, description: 'If the server flag is set, use websocket for messaging' }
];

export const defaultConfigFile = 'flowr.json';
37 changes: 5 additions & 32 deletions src/cli/flowr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,11 @@ import { FlowRServer } from './repl/server/server';
import type { Server } from './repl/server/net';
import { NetServer, WebSocketServerWrapper } from './repl/server/net';
import { flowrVersion } from '../util/version';
import type { OptionDefinition } from 'command-line-usage';
import commandLineUsage from 'command-line-usage';
import { log, LogLevel } from '../util/log';
import { bold, ColorEffect, Colors, FontStyles, formatter, italic, setFormatter, voidFormatter } from '../util/ansi';
import commandLineArgs from 'command-line-args';
import commandLineUsage from 'command-line-usage';
import { parseConfig , setConfig, setConfigFile } from '../config';
import { parseConfig, setConfig, setConfigFile } from '../config';


import { guard } from '../util/assert';
Expand All @@ -27,35 +26,10 @@ import { standardReplOutput } from './repl/commands/repl-main';
import { repl, replProcessAnswer } from './repl/core';
import { printVersionInformation } from './repl/commands/repl-version';
import { printVersionRepl } from './repl/print-version';

let _scriptsText: string | undefined;

function getScriptsText(){
if(_scriptsText === undefined) {
_scriptsText = Array.from(Object.entries(scripts).filter(([, { type }]) => type === 'master script'), ([k,]) => k).join(', ');
}
return _scriptsText;
}

import { defaultConfigFile, flowrMainOptionDefinitions, getScriptsText } from './flowr-main-options';

export const toolName = 'flowr';

export const optionDefinitions: OptionDefinition[] = [
{ name: 'config-file', type: String, description: 'The name of the configuration file to use', multiple: false },
{ name: 'config-json', type: String, description: 'The flowR configuration to use, as a JSON string', multiple: false },
{ name: 'execute', alias: 'e', type: String, description: 'Execute the given command and exit. Use a semicolon ";" to separate multiple commands.', typeLabel: '{underline command}', multiple: false },
{ name: 'help', alias: 'h', type: Boolean, description: 'Print this usage guide (or the guide of the corresponding script)' },
{ name: 'no-ansi', type: Boolean, description: 'Disable ansi-escape-sequences in the output. Useful, if you want to redirect the output to a file.' },
{ name: 'port' , type: Number, description: 'The port to listen on, if --server is given.', defaultValue: 1042, typeLabel: '{underline port}' },
{ name: 'r-path', type: String, description: 'The path to the R executable to use. Defaults to your PATH.', multiple: false },
{ name: 'r-session-access', type: Boolean, description: 'Allow to access the underlying R session when using flowR (security warning: this allows the execution of arbitrary R code!)' },
{ name: 'script', alias: 's', type: String, description: `The sub-script to run (${getScriptsText()})`, multiple: false, defaultOption: true, typeLabel: '{underline files}', defaultValue: undefined },
{ name: 'server', type: Boolean, description: 'Do not drop into a repl, but instead start a server on the given port (default: 1042) and listen for messages.' },
{ name: 'verbose', alias: 'v', type: Boolean, description: 'Run with verbose logging (will be passed to the corresponding script)' },
{ name: 'version', alias: 'V', type: Boolean, description: 'Provide information about the version of flowR as well as its underlying R system and exit.' },
{ name: 'ws', type: Boolean, description: 'If the server flag is set, use websocket for messaging' }
];

export interface FlowrCliOptions {
'config-file': string
'config-json': string
Expand Down Expand Up @@ -89,11 +63,11 @@ export const optionHelp = [
},
{
header: 'Options',
optionList: optionDefinitions
optionList: flowrMainOptionDefinitions
}
];

const options = commandLineArgs(optionDefinitions) as FlowrCliOptions;
const options = commandLineArgs(flowrMainOptionDefinitions) as FlowrCliOptions;

log.updateSettings(l => l.settings.minLevel = options.verbose ? LogLevel.Trace : LogLevel.Error);
log.info('running with options', options);
Expand All @@ -103,7 +77,6 @@ if(options['no-ansi']) {
setFormatter(voidFormatter);
}

export const defaultConfigFile = 'flowr.json';
let usedConfig = false;
if(options['config-json']) {
const config = parseConfig(options['config-json']);
Expand Down
17 changes: 8 additions & 9 deletions src/cli/repl/commands/repl-commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@ import { lineageCommand } from './repl-lineage';
import { queryCommand, queryStarCommand } from './repl-query';

function printHelpForScript(script: [string, ReplCommand], f: OutputFormatter, starredVersion?: ReplCommand): string {
let base = ` ${bold(padCmd(':' + script[0] + (starredVersion ? '[*]' : '')
), f)}${script[1].description}`;
let base = ` ${bold(padCmd(':' + script[0] + (starredVersion ? '[*]' : '')), f)}${script[1].description}`;
if(starredVersion) {
base += ` (star: ${starredVersion.description})`;
}
Expand All @@ -31,7 +30,7 @@ function printHelpForScript(script: [string, ReplCommand], f: OutputFormatter, s

function printCommandHelp(formatter: OutputFormatter) {
const scriptHelp = [];
const cmds = commands();
const cmds = getReplCommands();
for(const c of Object.entries(cmds)) {
if(c[1].script || c[0].endsWith('*')) {
continue;
Expand Down Expand Up @@ -63,7 +62,7 @@ ${

Furthermore, you can directly call the following scripts which accept arguments. If you are unsure, try to add ${italic('--help', output.formatter)} after the command.
${
Array.from(Object.entries(commands())).filter(([, { script }]) => script).map(
Array.from(Object.entries(getReplCommands())).filter(([, { script }]) => script).map(
([command, { description }]) => ` ${bold(padCmd(':' + command), output.formatter)}${description}`).sort().join('\n')
}

Expand Down Expand Up @@ -102,7 +101,7 @@ function hasModule(path: string): boolean {
}
}

function commands() {
export function getReplCommands() {
if(commandsInitialized) {
return _commands;
}
Expand Down Expand Up @@ -153,7 +152,7 @@ let commandMapping: Record<string, string> | undefined = undefined;
function initCommandMapping() {
commandMapping = {};
commandNames = [];
for(const [command, { aliases }] of Object.entries(commands())) {
for(const [command, { aliases }] of Object.entries(getReplCommands())) {
guard(commandMapping[command] as string | undefined === undefined, `Command ${command} is already registered!`);
commandMapping[command] = command;
for(const alias of aliases) {
Expand All @@ -173,7 +172,7 @@ export function getCommand(command: string): ReplCommand | undefined {
if(commandMapping === undefined) {
initCommandMapping();
}
return commands()[(commandMapping as Record<string, string>)[command]];
return getReplCommands()[(commandMapping as Record<string, string>)[command]];
}

export function asOptionName(argument: string): string{
Expand All @@ -188,10 +187,10 @@ export function asOptionName(argument: string): string{
let _longestCommandName: number | undefined = undefined;
export function longestCommandName(): number {
if(_longestCommandName === undefined) {
_longestCommandName = Array.from(Object.keys(commands()), k => k.endsWith('*') ? k.length + 3 : k.length).reduce((p, n) => Math.max(p, n), 0);
_longestCommandName = Array.from(Object.keys(getReplCommands()), k => k.endsWith('*') ? k.length + 3 : k.length).reduce((p, n) => Math.max(p, n), 0);
}
return _longestCommandName;
}
function padCmd<T>(string: T) {
export function padCmd<T>(string: T) {
return String(string).padEnd(longestCommandName() + 2, ' ');
}
44 changes: 34 additions & 10 deletions src/cli/repl/server/messages/message-analysis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@ export interface FileAnalysisRequestMessage extends IdMessageBase {
* However, the name is only for debugging and bears no semantic meaning.
*/
filename?: string,
/**
/**
* The contents of the file, or an R expression itself (like `1 + 1`), give either this or the `filepath`.
* If you want to load multiple R files as one, either use `filepath` or concatenate the file-contents for this field.
*/
content?: string
/**
/**
* The filepath on the local machine, accessible to flowR, or simply. Give either this or the `content`.
* If you want to load multiple R files as one, either use this or concatenate the file-contents for the `content`.
*/
Expand All @@ -42,14 +42,14 @@ export interface FileAnalysisRequestMessage extends IdMessageBase {
export const requestAnalysisMessage: MessageDefinition<FileAnalysisRequestMessage> = {
type: 'request-file-analysis',
schema: Joi.object({
type: Joi.string().valid('request-file-analysis').required(),
id: Joi.string().optional(),
filetoken: Joi.string().optional(),
filename: Joi.string().optional(),
content: Joi.string().optional(),
filepath: Joi.alternatives(Joi.string(), Joi.array().items(Joi.string())).optional(),
cfg: Joi.boolean().optional(),
format: Joi.string().valid('json', 'n-quads').optional()
type: Joi.string().valid('request-file-analysis').required().description('The type of the message.'),
id: Joi.string().optional().description('You may pass an id to link requests with responses (they get the same id).'),
filetoken: Joi.string().optional().description('A unique token to identify the file for subsequent requests. Only use this if you plan to send more queries!'),
filename: Joi.string().optional().description('A human-readable name of the file, only for debugging purposes.'),
content: Joi.string().optional().description('The content of the file or an R expression (either give this or the filepath).'),
filepath: Joi.alternatives(Joi.string(), Joi.array().items(Joi.string())).optional().description('The path to the file(s) on the local machine (either give this or the content).'),
cfg: Joi.boolean().optional().description('If you want to extract the control flow information of the file.'),
format: Joi.string().valid('json', 'n-quads').optional().description('The format of the results, if missing we assume json.')
}).xor('content', 'filepath')
};

Expand Down Expand Up @@ -77,6 +77,14 @@ export interface FileAnalysisResponseMessageJson extends IdMessageBase {
cfg?: ControlFlowInformation
}

const jsonSchema = Joi.object({
type: Joi.string().valid('response-file-analysis').required().description('The type of the message.'),
id: Joi.string().optional().description('The id of the message, if you passed one in the request.'),
format: Joi.string().valid('json').required().description('The format of the results in json format.'),
results: Joi.object().required().description('The results of the analysis (one field per step).'),
cfg: Joi.object().optional().description('The control flow information of the file, only present if requested.')
}).description('The response in JSON format.');

/**
* Similar to {@link FileAnalysisResponseMessageJson} but using n-quads as serialization format.
*/
Expand All @@ -94,3 +102,19 @@ export interface FileAnalysisResponseMessageNQuads extends IdMessageBase {
*/
cfg?: string
}

const nquadsSchema = Joi.object({
type: Joi.string().valid('response-file-analysis').required().description('The type of the message.'),
id: Joi.string().optional().description('The id of the message, if you passed one in the request.'),
format: Joi.string().valid('n-quads').required().description('The format of the results in n-quads format.'),
results: Joi.object().required().description('The results of the analysis (one field per step). Quads are presented as string.'),
cfg: Joi.string().optional().description('The control flow information of the file, only present if requested.')
}).description('The response as n-quads.');

export const analysisResponseMessage: MessageDefinition<FileAnalysisResponseMessageJson | FileAnalysisResponseMessageNQuads> = {
type: 'response-file-analysis',
schema: Joi.alternatives(
jsonSchema,
nquadsSchema
).required().description('The response to a file analysis request (based on the `format` field).')
};
17 changes: 15 additions & 2 deletions src/cli/repl/server/messages/message-hello.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import type { VersionInformation } from '../../commands/repl-version';
import type { IdMessageBase } from './messages';
import type { IdMessageBase, MessageDefinition } from './messages';
import Joi from 'joi';

/**
* The hello message is automatically send by the sever upon connection.
* The hello message is automatically sent by the sever upon connection.
*/
export interface FlowrHelloResponseMessage extends IdMessageBase {
type: 'hello',
Expand All @@ -20,3 +21,15 @@ export interface FlowrHelloResponseMessage extends IdMessageBase {
versions: VersionInformation
}

export const helloMessageDefinition: MessageDefinition<FlowrHelloResponseMessage> = {
type: 'hello',
schema: Joi.object({
type: Joi.string().required().valid('hello').description('The type of the hello message.'),
id: Joi.any().forbidden().description('The id of the message is always undefined (as it is the initial message and not requested).'),
clientName: Joi.string().required().description('A unique name that is assigned to each client. It has no semantic meaning and is only used/useful for debugging.'),
versions: Joi.object({
flowr: Joi.string().required().description('The version of the flowr server running in semver format.'),
r: Joi.string().required().description('The version of the underlying R shell running in semver format.')
}).required()
}).required()
};
17 changes: 13 additions & 4 deletions src/cli/repl/server/messages/message-lineage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ export interface LineageRequestMessage extends IdMessageBase {
export const requestLineageMessage: MessageDefinition<LineageRequestMessage> = {
type: 'request-lineage',
schema: Joi.object({
type: Joi.string().valid('request-lineage').required(),
id: Joi.string().optional(),
filetoken: Joi.string().required(),
criterion: Joi.string().required()
type: Joi.string().valid('request-lineage').required().description('The type of the message.'),
id: Joi.string().optional().description('If you give the id, the response will be sent to the client with the same id.'),
filetoken: Joi.string().required().description('The filetoken of the file/data retrieved from the analysis request.'),
criterion: Joi.string().required().description('The criterion to start the lineage from.')
})
};

Expand All @@ -26,3 +26,12 @@ export interface LineageResponseMessage extends IdMessageBase {
/** The lineage of the given criterion. With this being the representation of a set, there is no guarantee about order. */
lineage: NodeId[]
}

export const responseLineageMessage: MessageDefinition<LineageResponseMessage> = {
type: 'response-lineage',
schema: Joi.object({
type: Joi.string().valid('response-lineage').required(),
id: Joi.string().optional().description('The id of the message, will be the same for the request.'),
lineage: Joi.array().items(Joi.string()).required().description('The lineage of the given criterion.')
})
};
Loading
Loading