Skip to content

Commit ed0ae16

Browse files
author
Simca
committed
Preparation work for script execution
1 parent de77beb commit ed0ae16

File tree

6 files changed

+148
-29
lines changed

6 files changed

+148
-29
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Licensed to the Apache Software Foundation (ASF) under one or more
2+
# contributor license agreements. See the NOTICE file distributed with
3+
# this work for additional information regarding copyright ownership.
4+
# The ASF licenses this file to You under the Apache License, Version 2.0
5+
# (the "License"); you may not use this file except in compliance with
6+
# the License. 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+
from openstack_cli.core.config import Configuration
17+
from openstack_cli.modules.apputils.discovery import CommandMetaInfo
18+
19+
__module__ = CommandMetaInfo("scripts", item_help="Manage user scripts",
20+
default_sub_command="list", exec_with_child=True)
21+
22+
23+
def __init__(conf: Configuration, **kwargs):
24+
print("Lol")
25+
# raise NotImplementedCommandException()
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Licensed to the Apache Software Foundation (ASF) under one or more
2+
# contributor license agreements. See the NOTICE file distributed with
3+
# this work for additional information regarding copyright ownership.
4+
# The ASF licenses this file to You under the Apache License, Version 2.0
5+
# (the "License"); you may not use this file except in compliance with
6+
# the License. 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+
from openstack_cli.core.config import Configuration
18+
from openstack_cli.core.dialogs import ask_open_directory
19+
from openstack_cli.modules.apputils.discovery import CommandMetaInfo
20+
21+
__module__ = CommandMetaInfo("list", item_help="List available scripts")
22+
__args__ = __module__.arg_builder\
23+
.add_default_argument("name", str, "dadad")\
24+
.add_argument("test", str, "", default="dda")
25+
26+
27+
def __init__(conf: Configuration, name: str , test: str):
28+
print(f"Args name: {name}; test: {test}")
29+
print(ask_open_directory("Lol"))

src/openstack_cli/core/dialogs.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ class filedialog(object):
3232
def askopenfilename(*args, **kwargs):
3333
return None
3434

35+
@staticmethod
36+
def askdirectory(*args, **kwargs):
37+
return None
38+
3539
if not tk_enabled:
3640
return None, None
3741

@@ -48,3 +52,11 @@ def ask_open_file(title: str = "Select file", allowed_extensions=(('All files',
4852
return filedialog.askopenfilename(title=title, filetypes=allowed_extensions)
4953
else:
5054
return input(f"{title}: ")
55+
56+
def ask_open_directory(title: str = "Coose folder") -> str:
57+
root, filedialog = get_tk()
58+
59+
if filedialog:
60+
return filedialog.askdirectory(title=title)
61+
else:
62+
return input(f"{title}: ")

src/openstack_cli/core/shell.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,6 @@ def _f12_commands(channel: paramiko.Channel):
3939
print(f"\n Character code debugging is: {F12MENU}")
4040
if F12MENU:
4141
print("> ", end='', flush=True)
42-
elif _k == FUNC_KEYS.F5.value:
43-
channel.resize_pty(*get_terminal_size())
4442
elif _k == (99,): # C:
4543
SIGINT = True
4644
elif _k == (105,): # I
@@ -98,12 +96,13 @@ def __buffered_reader(stdread: paramiko.ChannelFile, stdwrite: TextIO):
9896
import time
9997
channel: paramiko.Channel = stdread.channel
10098
while not SIGINT and not channel.exit_status_ready():
101-
time.sleep(0.2)
10299
if channel.recv_ready():
103100
r, w, x = select.select([channel], [], [], 0.0)
104101
if len(r) > 0:
105102
stdwrite.buffer.write(channel.recv(1024))
106103
stdwrite.flush()
104+
else:
105+
time.sleep(0.2)
107106

108107
SIGINT = True
109108

@@ -125,16 +124,15 @@ def __window_size_change_handler(channel: paramiko.Channel):
125124
time.sleep(1)
126125
nwidth, nheight = get_terminal_size()
127126
if nwidth != width or nheight != height:
128-
width = nwidth
129-
height = nheight
127+
width, height = nwidth, nheight
130128
channel.resize_pty(width=width, height=height)
131129

132130

133131
def shell(channel: paramiko.Channel):
134132
stdin: paramiko.ChannelFile = channel.makefile_stdin("wb")
135133
stdout: paramiko.ChannelFile = channel.makefile("r")
136134
stderr: paramiko.ChannelFile = channel.makefile_stderr("r")
137-
print("Tip: F12 + I to show connection info, F12+C to close connection, F12+F5 force PTY size update")
135+
print("Tip: F12 + I to show connection info, F12+C to close connection")
138136

139137
stdoutReader = Thread(target=__buffered_reader, name="stdoutReader", args=(stdout, sys.stdout))
140138
stderrReader = Thread(target=__buffered_reader, name="stderrReader", args=(stderr, sys.stderr))
@@ -156,8 +154,11 @@ def shell(channel: paramiko.Channel):
156154
stdinWriter.start()
157155

158156
stdoutReader.join()
159-
print("Closing ssh session...")
160-
channel.close()
161157
finally:
158+
print("Closing ssh session...")
159+
try:
160+
channel.close()
161+
except:
162+
pass
162163
signal.signal(signal.SIGINT, orig_sigint)
163164

src/openstack_cli/modules/apputils/discovery/__init__.py

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -109,42 +109,54 @@ def command_arguments(self) -> List[str]:
109109
def kwargs_name(self) -> List[str]:
110110
return list(self._options.kwargs.keys())
111111

112-
def _get_command(self, injected_args: dict = None, fail_on_unknown: bool = False) -> CommandModule:
112+
def _get_command(self, injected_args: dict = None, fail_on_unknown: bool = False) -> List[CommandModule]:
113113
if not self._options.args:
114114
raise NoCommandException(None, "No command passed, unable to continue")
115115

116+
command_chain: List[CommandModule] = []
116117
command: CommandModule = self._modules[self._options.args[0]] # [command name]
117118
command_args = self._options.args[1:]
118119

120+
# Process all command in sub-command chain [command1] [command2] .. [last command]
119121
while command_args:
120122
if command_args[0] not in command.subcommand_names:
121123
break
122124
command_name = command_args[0]
123125
command_args = command_args[1:]
124126

125-
command = command.get_subcommand(command_name) # [command name] {subcommand}
127+
if command.meta_info.exec_with_child:
128+
command_chain.append(command)
129+
command = command.get_subcommand(command_name)
126130

131+
# Now process the last command "default sub-command" forward if present
127132
if command.meta_info.default_sub_command and \
128133
command.meta_info.default_sub_command in command.subcommand_names:
129134

130-
command = command.get_subcommand(command.meta_info.default_sub_command) # [command name] {subcommand} {sub}
135+
if command.meta_info.exec_with_child:
136+
command_chain.append(command)
137+
command = command.get_subcommand(command.meta_info.default_sub_command)
131138

139+
command_chain.append(command)
132140
inj_args = set(injected_args.keys()) if injected_args else set()
133-
command.set_argument(command_args, self._options.kwargs, inj_args, fail_on_unknown)
141+
for _command in command_chain:
142+
_require_transform = True if _command == command else False
143+
_command.set_argument(command_args, self._options.kwargs, inj_args, fail_on_unknown, not _require_transform)
134144

135-
return command
145+
return command_chain
136146

137147
def execute_command(self, injected_args: dict = None):
138148
try:
139-
command = self._get_command(injected_args)
140-
command.execute(injected_args)
149+
commands = self._get_command(injected_args)
150+
for command in commands:
151+
command.execute(injected_args)
141152
except CommandArgumentException as e:
142153
raise NoCommandException(None, f"Application arguments exception: {str(e)}\n")
143154

144155
async def execute_command_async(self, injected_args: dict = None):
145156
try:
146-
command = self._get_command()
147-
await command.execute_async(injected_args)
157+
commands = self._get_command()
158+
for command in commands:
159+
await command.execute_async(injected_args)
148160
except CommandArgumentException as e:
149161
raise NoCommandException(None, f"Application arguments exception: {str(e)}\n")
150162

@@ -168,8 +180,9 @@ def start_application(self, kwargs: dict = None):
168180
# ToDO: add default command to be executed if no passed
169181
self.__inject_help_command()
170182
try:
171-
command = self._get_command(injected_args=kwargs, fail_on_unknown=True)
172-
command.execute(injected_args=kwargs)
183+
commands = self._get_command(injected_args=kwargs, fail_on_unknown=True)
184+
for command in commands:
185+
command.execute(injected_args=kwargs)
173186
except NotImplementedCommandException:
174187
sys.stdout.write(generate_help(self._modules, self._options, *self.__get_modules_from_args()))
175188
pass

src/openstack_cli/modules/apputils/discovery/commands.py

Lines changed: 49 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -157,12 +157,24 @@ def get_default_argument(self, index: int) -> CommandArgumentItem:
157157

158158

159159
class CommandMetaInfo(object):
160-
def __init__(self, name: str, item_help: str = "", default_sub_command: str = "", **kwargs):
160+
def __init__(self,
161+
name: str,
162+
item_help: str = "",
163+
default_sub_command: str = "",
164+
exec_with_child: bool = False,
165+
**kwargs):
166+
"""
167+
:arg name Name of the command
168+
:arg item_help Help description of the command
169+
:arg default_sub_command In case if command have sub-commands, name of sub-command to execute on requesting base command
170+
:arg exec_with_child execute base command first, then sub-command. Valid to make command-wide checks.
171+
"""
161172
self._name = name
162173
self._arguments = CommandArgumentsBuilder()
163174
self._help = item_help
164175
self._kwargs = kwargs
165176
self._default_sub_command = default_sub_command
177+
self._exec_with_child = exec_with_child
166178

167179
@property
168180
def options(self) -> dict:
@@ -180,6 +192,15 @@ def help(self) -> str:
180192
def default_sub_command(self) -> str:
181193
return self._default_sub_command
182194

195+
@property
196+
def exec_with_child(self) -> bool:
197+
"""
198+
Execute base command first, then sub-command. Valid to make command-wide checks
199+
200+
Base command would be feeded with same commands as sub-command with disabled meta-info check
201+
"""
202+
return self._exec_with_child
203+
183204
@property
184205
def arguments(self) -> dict:
185206
return self._arguments.arguments
@@ -288,20 +309,38 @@ def __init__(self, meta_info: CommandMetaInfo, classpath: str, import_name: str,
288309
if parent:
289310
self.__meta_info.arg_builder.merge(parent.__meta_info.arg_builder)
290311

291-
def set_argument(self, args: list, kwargs: dict, injected_args: set = None, fail_on_unknown=False):
312+
def set_argument(self, args: list, kwargs: dict, injected_args: set = None,
313+
fail_on_unknown=False,
314+
skip_transform: bool = False):
315+
"""
316+
317+
:arg args list of default arguments
318+
:arg kwargs: list of kwargs
319+
:arg injected_args: list of added argumants
320+
:arg fail_on_unknown: option to throw exception if unknown argument found
321+
:arg skip_transform: skip argument transformation according to command meta. Usefull if the command is not
322+
the last in the execution chain
323+
324+
"""
292325
if not injected_args:
293326
injected_args = set()
294327

295-
args = self.__meta_info.transform_default_arguments(args, fail_on_unknown=fail_on_unknown)
296-
args.update(self.__meta_info.transform_arguments(kwargs, injected_args, fail_on_unknown=fail_on_unknown))
328+
if not skip_transform:
329+
args = self.__meta_info.transform_default_arguments(args, fail_on_unknown=fail_on_unknown)
330+
args.update(self.__meta_info.transform_arguments(kwargs, injected_args, fail_on_unknown=fail_on_unknown))
297331

298-
f_args = self.entry_point_args
332+
f_args = self.entry_point_args
299333

300-
if len(f_args) - len(set(f_args) & injected_args) != len(set(args.keys()) & set(f_args)):
301-
raise CommandArgumentException("Function \"{}\" from module {} doesn't implement all arguments in the"
302-
" signature or implements unknown definition".format(
303-
self.__entry_point.__name__, self.__classpath
304-
))
334+
if len(f_args) - len(set(f_args) & injected_args) != len(set(args.keys()) & set(f_args)):
335+
raise CommandArgumentException("Function \"{}\" from module {} doesn't implement all arguments in the"
336+
" signature or implements unknown definition".format(
337+
self.__entry_point.__name__, self.__classpath
338+
))
339+
else:
340+
args = {
341+
'_': args
342+
}
343+
args.update(kwargs)
305344
self.__args = args
306345

307346
def add_subcommand(self, cmds):

0 commit comments

Comments
 (0)