Skip to content

Commit

Permalink
Merge pull request cylc#5731 from oliver-sanders/tui++
Browse files Browse the repository at this point in the history
Tui 1.0
  • Loading branch information
oliver-sanders authored Nov 27, 2023
2 parents 0a19dc9 + c303aa9 commit ddeaa97
Show file tree
Hide file tree
Showing 60 changed files with 4,150 additions and 575 deletions.
1 change: 1 addition & 0 deletions changes.d/5731.feat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Major upgrade to `cylc tui` which now supports larger workflows and can browse installed workflows.
2 changes: 1 addition & 1 deletion cylc/flow/data_store_mgr.py
Original file line number Diff line number Diff line change
Expand Up @@ -1587,7 +1587,7 @@ def insert_job(self, name, cycle_point, status, job_conf):
name=tproxy.name,
cycle_point=tproxy.cycle_point,
execution_time_limit=job_conf.get('execution_time_limit'),
platform=job_conf.get('platform')['name'],
platform=job_conf['platform']['name'],
job_runner_name=job_conf.get('job_runner_name'),
)
# Not all fields are populated with some submit-failures,
Expand Down
1 change: 1 addition & 0 deletions cylc/flow/option_parsers.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
)

WORKFLOW_ID_ARG_DOC = ('WORKFLOW', 'Workflow ID')
OPT_WORKFLOW_ID_ARG_DOC = ('[WORKFLOW]', 'Workflow ID')
WORKFLOW_ID_MULTI_ARG_DOC = ('WORKFLOW ...', 'Workflow ID(s)')
WORKFLOW_ID_OR_PATH_ARG_DOC = ('WORKFLOW | PATH', 'Workflow ID or path')
ID_MULTI_ARG_DOC = ('ID ...', 'Workflow/Cycle/Family/Task ID(s)')
Expand Down
77 changes: 23 additions & 54 deletions cylc/flow/scripts/tui.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,34 +15,35 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

"""cylc tui WORKFLOW
"""cylc tui [WORKFLOW]
View and control running workflows in the terminal.
(Tui = Terminal User Interface)
WARNING: Tui is experimental and may break with large flows.
An upcoming change to the way Tui receives data from the scheduler will make it
much more efficient in the future.
Tui allows you to monitor and interact with workflows in a manner similar
to the GUI.
Press "h" whilst running Tui to bring up the help screen, use the arrow
keys to navigage.
"""
# TODO: remove this warning once Tui is delta-driven
# https://github.com/cylc/cylc-flow/issues/3527

from getpass import getuser
from textwrap import indent
from typing import TYPE_CHECKING
from urwid import html_fragment
from typing import TYPE_CHECKING, Optional

from cylc.flow.id import Tokens
from cylc.flow.id_cli import parse_id
from cylc.flow.option_parsers import (
WORKFLOW_ID_ARG_DOC,
OPT_WORKFLOW_ID_ARG_DOC,
CylcOptionParser as COP,
)
from cylc.flow.terminal import cli_function
from cylc.flow.tui import TUI
from cylc.flow.tui.util import suppress_logging
from cylc.flow.tui.app import (
TuiApp,
TREE_EXPAND_DEPTH
# ^ a nasty solution
)

if TYPE_CHECKING:
Expand All @@ -55,57 +56,25 @@
def get_option_parser() -> COP:
parser = COP(
__doc__,
argdoc=[WORKFLOW_ID_ARG_DOC],
argdoc=[OPT_WORKFLOW_ID_ARG_DOC],
# auto_add=False, NOTE: at present auto_add can not be turned off
color=False
)

parser.add_option(
'--display',
help=(
'Specify the display technology to use.'
' "raw" for interactive in-terminal display.'
' "html" for non-interactive html output.'
),
action='store',
choices=['raw', 'html'],
default='raw',
)
parser.add_option(
'--v-term-size',
help=(
'The virtual terminal size for non-interactive'
'--display options.'
),
action='store',
default='80,24'
)

return parser


@cli_function(get_option_parser)
def main(_, options: 'Values', workflow_id: str) -> None:
workflow_id, *_ = parse_id(
workflow_id,
constraint='workflows',
)
screen = None
if options.display == 'html':
TREE_EXPAND_DEPTH[0] = -1 # expand tree fully
screen = html_fragment.HtmlGenerator()
screen.set_terminal_properties(256)
screen.register_palette(TuiApp.palette)
html_fragment.screenshot_init(
[tuple(map(int, options.v_term_size.split(',')))],
[]
def main(_, options: 'Values', workflow_id: Optional[str] = None) -> None:
# get workflow ID if specified
if workflow_id:
workflow_id, *_ = parse_id(
workflow_id,
constraint='workflows',
)
tokens = Tokens(workflow_id)
workflow_id = tokens.duplicate(user=getuser()).id

try:
TuiApp(workflow_id, screen=screen).main()

if options.display == 'html':
for fragment in html_fragment.screenshot_collect():
print(fragment)
except KeyboardInterrupt:
# start Tui
with suppress_logging(), TuiApp().main(workflow_id):
pass
32 changes: 29 additions & 3 deletions cylc/flow/tui/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,12 +106,26 @@


class Bindings:
"""Represets key bindings for the Tui app."""

def __init__(self):
self.bindings = []
self.groups = {}

def bind(self, keys, group, desc, callback):
"""Register a key binding.
Args:
keys:
The keys to bind.
group:
The group to which this binding should belong.
desc:
Description for this binding, used to generate help.
callback:
The thing to call when this binding is pressed.
"""
if group not in self.groups:
raise ValueError(f'Group {group} not registered.')
binding = {
Expand All @@ -124,6 +138,15 @@ def bind(self, keys, group, desc, callback):
self.groups[group]['bindings'].append(binding)

def add_group(self, group, desc):
"""Add a new binding group.
Args:
group:
The name of the group.
desc:
A description of the group, used to generate help.
"""
self.groups[group] = {
'name': group,
'desc': desc,
Expand All @@ -134,6 +157,12 @@ def __iter__(self):
return iter(self.bindings)

def list_groups(self):
"""List groups and the bindings in them.
Yields:
(group_name, [binding, ...])
"""
for name, group in self.groups.items():
yield (
group,
Expand All @@ -143,6 +172,3 @@ def list_groups(self):
if binding['group'] == name
]
)


BINDINGS = Bindings()
Loading

0 comments on commit ddeaa97

Please sign in to comment.