Skip to content

Commit

Permalink
tui: add vr command
Browse files Browse the repository at this point in the history
  • Loading branch information
oliver-sanders committed Mar 22, 2023
1 parent ac1b7f2 commit fd7b39f
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 51 deletions.
7 changes: 7 additions & 0 deletions cylc/flow/tui/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,7 @@ def create_overlay(self, widget, kwargs):
See `urwid` docs for details.
"""
# create the overlay
kwargs = {'width': 'pack', 'height': 'pack', **kwargs}
overlay = urwid.Overlay(
urwid.LineBox(
Expand All @@ -503,9 +504,15 @@ def create_overlay(self, widget, kwargs):
top=self.stack * 5,
**kwargs,
)

# add it into the overlay stack
self.loop.widget = overlay
self.stack += 1

# force urwid to render the overlay now rather than waiting until the
# event loop becomes idle
self.loop.draw_screen()

def close_topmost(self):
"""Remove the topmost frame or uit the app if none present."""
if self.stack <= 0:
Expand Down
102 changes: 63 additions & 39 deletions cylc/flow/tui/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

from functools import partial
from subprocess import Popen, PIPE
import sys

Expand Down Expand Up @@ -84,7 +85,6 @@
MUTATIONS = {
'workflow': [
'pause',
'resume',
'reload',
'stop',
],
Expand Down Expand Up @@ -130,6 +130,46 @@
}


def cli_cmd(*cmd):
"""Issue a CLI command.
Args:
cmd:
The command without the 'cylc' prefix'.
Rasies:
ClientError:
In the event of mishap for consistency with the network
client alternative.
"""
proc = Popen( # nosec (command constructed internally, no untrusted input)
['cylc', *cmd],
stderr=PIPE,
stdout=PIPE,
text=True,
)
out, err = proc.communicate()
if proc.returncode != 0:
raise ClientError(err)


def _clean(workflow):
# for now we will exit tui when the workflow is cleaned
# this will change when tui supports multiple workflows
cli_cmd('clean', workflow)
sys.exit(0)


OFFLINE_MUTATIONS = {
'workflow': {
'play': partial(cli_cmd, 'play'),
'clean': _clean,
'reinstall-reload': partial(cli_cmd, 'vr', '--yes'),
}
}


def generate_mutation(mutation, arguments):
graphql_args = ', '.join([
f'${argument}: {ARGUMENT_TYPES[argument]}'
Expand All @@ -149,10 +189,16 @@ def generate_mutation(mutation, arguments):
'''


def list_mutations(selection):
def list_mutations(client, selection):
context = extract_context(selection)
selection_type = list(context)[-1]
return MUTATIONS.get(selection_type, [])
ret = []
if client:
# add the online mutations
ret.extend(MUTATIONS.get(selection_type, []))
# add the offline mutations
ret.extend(OFFLINE_MUTATIONS.get(selection_type, []))
return sorted(ret)


def context_to_variables(context):
Expand Down Expand Up @@ -181,6 +227,18 @@ def context_to_variables(context):


def mutate(client, mutation, selection):
if mutation in OFFLINE_MUTATIONS['workflow']:
offline_mutate(mutation, selection)
elif client:
online_mutate(client, mutation, selection)
else:
raise Exception(
f'Cannot peform command {mutation} on a stopped workflow'
' or invalid command.'
)


def online_mutate(client, mutation, selection):
"""Issue a mutation over a network interface."""
context = extract_context(selection)
variables = context_to_variables(context)
Expand All @@ -199,39 +257,5 @@ def offline_mutate(mutation, selection):
context = extract_context(selection)
variables = context_to_variables(context)
for workflow in variables['workflow']:
if mutation == 'play':
cli_cmd('play', workflow)
elif mutation == 'clean': # noqa: SIM106
cli_cmd('clean', workflow)
# tui only supports single-workflow display ATM so
# clean should shut down the program
sys.exit()
elif mutation in list_mutations(selection): # noqa: SIM106
# this is an "online" mutation -> ignore
pass
else:
raise Exception(f'Invalid mutation: {mutation}')


def cli_cmd(*cmd):
"""Issue a CLI command.
Args:
cmd:
The command without the 'cylc' prefix'.
Rasies:
ClientError:
In the event of mishap for consistency with the network
client alternative.
"""
proc = Popen( # nosec (command constructed internally, no untrusted input)
['cylc', *cmd],
stderr=PIPE,
stdout=PIPE,
text=True,
)
out, err = proc.communicate()
if proc.returncode != 0:
raise ClientError(err)
# NOTE: this currently only supports workflow mutations
OFFLINE_MUTATIONS['workflow'][mutation](workflow)
27 changes: 15 additions & 12 deletions cylc/flow/tui/overlay.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@
from cylc.flow.tui.data import (
list_mutations,
mutate,
offline_mutate,
)
from cylc.flow.tui.util import (
get_task_icon
Expand Down Expand Up @@ -218,16 +217,14 @@ def context(app):

def _mutate(mutation, _):
nonlocal app
app.open_overlay(partial(progress, text='Running Command'))
try:
if app.client:
mutate(app.client, mutation, selection)
else:
offline_mutate(mutation, selection)
mutate(app.client, mutation, selection)
except ClientError as exc:
# app.set_header([('workflow_error', str(exc))])
app.open_overlay(partial(error, text=str(exc)))
else:
app.close_topmost()
app.close_topmost()

widget = urwid.ListBox(
urwid.SimpleFocusListWalker(
Expand All @@ -245,18 +242,14 @@ def _mutate(mutation, _):
mutation,
on_press=partial(_mutate, mutation)
)
for mutation in (
list_mutations(selection)
if app.client
else ['play', 'clean']
)
for mutation in list_mutations(app.client, selection)
]
)
)

return (
widget,
{'width': 30, 'height': 15}
{'width': 30, 'height': 20}
)


Expand All @@ -270,3 +263,13 @@ def error(app, text=''):
]),
{'width': 50, 'height': 40}
)


def progress(app, text='Working'):
"""An overlay for presenting a running action."""
return (
urwid.ListBox([
urwid.Text(text),
]),
{'width': 30, 'height': 10}
)

0 comments on commit fd7b39f

Please sign in to comment.