Skip to content
This repository was archived by the owner on Jul 30, 2025. It is now read-only.

Commit c17c1e6

Browse files
committed
fix: user-installed plugins cannot always REPL.qexec other plugins
Fixes #2963
1 parent cee791e commit c17c1e6

File tree

19 files changed

+460
-275
lines changed

19 files changed

+460
-275
lines changed

.eslintignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ packages/core/index.js
99
packages/core/api/
1010
packages/core/dist/
1111
packages/core/core/
12+
packages/core/commands/
1213
packages/core/main/
1314
packages/core/models/
1415
packages/core/plugins/
@@ -21,3 +22,5 @@ plugins/*/dist/
2122
**/*/node_modules/
2223
**/*.d.ts
2324
plugins/plugin-apache-composer/tests/data/composer/composer-source-expect-errors/if-bad.js
25+
kui-stage
26+
*flycheck*.js

packages/core/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ build
22
dist
33
/api/
44
/core/
5+
/commands/
56
/main/
67
/models/
78
/plugins/

packages/core/src/api/commands.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import * as Plugins from '../models/plugin'
2828
import * as Entity from '../models/entity'
2929
import * as Sidecar from '../webapp/views/sidecar'
3030
import { optionsToString } from '@kui-shell/core/core/utility'
31+
import * as Context from '../commands/context'
3132

3233
export namespace Commands {
3334
export import Arguments = _Commands.EvaluatorArgs
@@ -39,6 +40,8 @@ export namespace Commands {
3940
export import ExecOptions = _ExecOptions.ExecOptions
4041
export import withLanguage = _ExecOptions.withLanguage
4142

43+
export import getCurrentContext = Context.getCurrentContext
44+
4245
export import ExecType = _Commands.ExecType
4346
export import Registrar = _Commands.CommandRegistrar
4447
export import ParsedOptions = _Commands.ParsedOptions

packages/core/src/commands/context.ts

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/*
2+
* Copyright 2017-19 IBM Corporation
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import { theme } from '../core/settings'
18+
19+
/**
20+
* Parse a serialized command context
21+
*
22+
*/
23+
function parseCommandContext(str: string): string[] {
24+
if (str) {
25+
try {
26+
// let's assume str is of the form '["a", "b"]'
27+
return JSON.parse(str)
28+
} catch (err1) {
29+
// ok, that didn't work; let's see if it's of the form '/a/b'
30+
try {
31+
return str.split('/').filter(_ => _)
32+
} catch (err2) {
33+
console.error(`Could not parse command context ${str}`, err1, err2)
34+
}
35+
}
36+
}
37+
return undefined
38+
}
39+
40+
/**
41+
* The default command execution context. For example, if the
42+
* execution context is /foo/bar, and there is a command /foo/bar/baz,
43+
* then the issuance of a command "baz" will resolve to /foo/bar/baz.
44+
*
45+
* The default can be overridden either by changing the next line, or
46+
* by calling `setDefaultCommandContext`.
47+
*
48+
*/
49+
let _defaultContext: string[] =
50+
(theme && theme.defaultContext) || parseCommandContext(process.env.KUI_COMMAND_CONTEXT) || []
51+
52+
/**
53+
* The command context model, defaulting to the _defaultContext, which
54+
* can be overridden via `setDefaultCommandContext`.
55+
*
56+
*/
57+
export const Context = {
58+
current: _defaultContext
59+
}
60+
61+
/**
62+
* When commands are executed, the command resolver will use a
63+
* fallback prefix. This routine tries to discover, from the
64+
* environment, what the default fallback prefix should be.
65+
*
66+
*/
67+
export const setDefaultCommandContext = (commandContext: string[]) => {
68+
Context.current = _defaultContext = commandContext
69+
}
70+
71+
export function getCurrentContext() {
72+
return Context.current
73+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright 2017-19 IBM Corporation
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import { CommandHandlerWithEvents, CommandTreeResolution } from '../models/command'
18+
19+
/**
20+
* A call to `read` may or may not have successfully resolved the
21+
* given `argv.` This method clarifies the situation.
22+
*
23+
*/
24+
export function isSuccessfulCommandResolution(
25+
resolution: CommandTreeResolution
26+
): resolution is CommandHandlerWithEvents {
27+
return (resolution as CommandHandlerWithEvents).eval !== undefined
28+
}

packages/core/src/commands/tree.ts

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
/*
2+
* Copyright 2017-19 IBM Corporation
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import { CatchAllHandler, Command, CommandTree, CommandTreeResolution, Disambiguator } from '../models/command'
18+
19+
import { ExecOptions } from '../models/execOptions'
20+
21+
/**
22+
* The exported interface around the internal command model
23+
* implementation.
24+
*
25+
*/
26+
interface CommandModel {
27+
catchalls: CatchAllHandler[]
28+
29+
/**
30+
* Look up a command handler for the given `argv`. This is the main
31+
* Read part of a REPL.
32+
*
33+
*/
34+
read(argv: string[], execOptions: ExecOptions): Promise<CommandTreeResolution>
35+
36+
/**
37+
* Call the given callback function `fn` for each node in the command tree
38+
*
39+
*/
40+
forEachNode(fn: (command: Command) => void): void
41+
}
42+
43+
/**
44+
* The internal implementation of the command tree
45+
*
46+
*/
47+
export class CommandModelImpl implements CommandModel {
48+
/** root of the tree model */
49+
private readonly _root: CommandTree = this.newTree()
50+
public get root(): CommandTree {
51+
return this._root
52+
}
53+
54+
/** map from command name to disambiguations */
55+
private readonly _disambiguator: Disambiguator = {}
56+
public get disambiguator(): Disambiguator {
57+
return this._disambiguator
58+
}
59+
60+
/** handlers for command not found */
61+
private readonly _catchalls: CatchAllHandler[] = []
62+
public get catchalls(): CatchAllHandler[] {
63+
return this._catchalls
64+
}
65+
66+
/**
67+
* Look up a command handler for the given `argv`. This is the main
68+
* Read part of a REPL.
69+
*
70+
*/
71+
public async read(argv: string[], execOptions: ExecOptions): Promise<CommandTreeResolution> {
72+
const { read: readImpl } = await import('../core/command-tree')
73+
return readImpl(this.root, argv, undefined, undefined, execOptions)
74+
}
75+
76+
/**
77+
* Call the given callback function `fn` for each node in the command tree
78+
*
79+
*/
80+
public forEachNode(fn: (command: Command) => void) {
81+
const iter = (root: Command) => {
82+
if (root) {
83+
fn(root)
84+
if (root.children) {
85+
for (const cmd in root.children) {
86+
iter(root.children[cmd])
87+
}
88+
}
89+
}
90+
}
91+
92+
iter(this.root)
93+
}
94+
95+
private newTree(): CommandTree {
96+
return {
97+
$: undefined,
98+
key: '/',
99+
route: '/',
100+
children: {},
101+
parent: undefined
102+
}
103+
}
104+
}
105+
106+
interface WindowWithModel extends Window {
107+
_kuiCommandModel: CommandModelImpl
108+
}
109+
110+
function isWindowWithModel(win: Window): win is WindowWithModel {
111+
return win && (win as WindowWithModel)._kuiCommandModel !== undefined
112+
}
113+
114+
/**
115+
* Global state representing the current tree of registered commands
116+
*
117+
*/
118+
let theCommandModel: CommandModelImpl
119+
120+
/**
121+
* @return the command tree model for internal consumption within this
122+
* file
123+
*
124+
*/
125+
export function getModelInternal(): CommandModelImpl {
126+
return (typeof window !== 'undefined' && isWindowWithModel(window) && window._kuiCommandModel) || theCommandModel
127+
}
128+
129+
/**
130+
* @return the command tree model for public consumption within the
131+
* rest of @kui-shell/core. For the model we present to plugins, see
132+
* `ImplForPlugins` in command-tree.ts
133+
*
134+
*/
135+
export function getModel(): CommandModel {
136+
return getModelInternal()
137+
}
138+
139+
interface WindowWithModel extends Window {
140+
_kuiCommandModel: CommandModelImpl
141+
}
142+
143+
export function init() {
144+
theCommandModel = new CommandModelImpl()
145+
146+
if (typeof window !== 'undefined') {
147+
;((window as any) as WindowWithModel)._kuiCommandModel = theCommandModel
148+
}
149+
}
150+
151+
export function initIfNeeded() {
152+
if (!theCommandModel) {
153+
init()
154+
}
155+
}

0 commit comments

Comments
 (0)