diff --git a/changes.d/6071.fix.md b/changes.d/6071.fix.md new file mode 100644 index 0000000000..534b424188 --- /dev/null +++ b/changes.d/6071.fix.md @@ -0,0 +1 @@ +`cylc config` now shows xtrigger function signatures. diff --git a/changes.d/6072.feat.md b/changes.d/6072.feat.md new file mode 100644 index 0000000000..1031386cda --- /dev/null +++ b/changes.d/6072.feat.md @@ -0,0 +1 @@ +Nano Syntax Highlighting now available. diff --git a/cylc/flow/etc/syntax/cylc.nanorc b/cylc/flow/etc/syntax/cylc.nanorc new file mode 100644 index 0000000000..f1a94f0ebc --- /dev/null +++ b/cylc/flow/etc/syntax/cylc.nanorc @@ -0,0 +1,30 @@ +# How to use this file: +# cylc get-resources cylc.nanorc ~/.config/nano/cylc.nanorc +# Add the following to ~/.nanorc +# include ~/.config/nano/cylc.nanorc + +# Supports `.cylc` files +syntax "Cylc" "\.cylc$" + +## Multiline +color yellow start="\"\"\"" end="\"\"\"" +color yellow start="'''" end="'''" +color yellow start="\[" end="\]" + +## Values +color yellow "=(.*)$" +color green "=[^>]" +color brightmagenta "=>|&|\|\\" + +## Valid headings +color green "^\s*\[.*\]" +color green "^\s*\[\[.*\]\]" +color green "^\s*\[\[\[.*\]\]\]" + +## Comments (keep at the end of this file!) +color cyan "#.*$" + +## Jinja2 +icolor brightcyan "^#!Jinja2" +color brightcyan "\{%.*%\}" +color brightcyan "\{\{.*\}\}" diff --git a/cylc/flow/subprocctx.py b/cylc/flow/subprocctx.py index d76e34ce9a..45b4cb2095 100644 --- a/cylc/flow/subprocctx.py +++ b/cylc/flow/subprocctx.py @@ -197,3 +197,17 @@ def get_signature(self): args = self.func_args + [ "%s=%s" % (i, self.func_kwargs[i]) for i in skeys] return "%s(%s)" % (self.func_name, ", ".join([str(a) for a in args])) + + def dump(self) -> str: + """Output for logging.""" + return SubProcContext.__str__(self) + + def __str__(self) -> str: + """ + >>> str(SubFuncContext('label', 'my_func', [1, 2], {'a': 3})) + 'my_func(1, 2, a=3):10.0' + """ + return f"{self.get_signature()}:{self.intvl}" + + def __repr__(self) -> str: + return f"<{type(self).__name__} {self}>" diff --git a/cylc/flow/subprocpool.py b/cylc/flow/subprocpool.py index 9cde3f7c9d..29a236b2f9 100644 --- a/cylc/flow/subprocpool.py +++ b/cylc/flow/subprocpool.py @@ -26,7 +26,7 @@ from threading import RLock from time import time from subprocess import DEVNULL, run # nosec -from typing import Any, Callable, List, Optional +from typing import TYPE_CHECKING, Any, Callable, List, Optional, Set from cylc.flow import LOG, iter_entry_points from cylc.flow.cfgspec.glbl_cfg import glbl_cfg @@ -37,10 +37,15 @@ log_platform_event, get_platform, ) +from cylc.flow.subprocctx import SubFuncContext from cylc.flow.task_events_mgr import TaskJobLogsRetrieveContext from cylc.flow.task_proxy import TaskProxy from cylc.flow.wallclock import get_current_time_string +if TYPE_CHECKING: + from subprocess import Popen + from cylc.flow.subprocctx import SubProcContext + _XTRIG_MOD_CACHE: dict = {} _XTRIG_FUNC_CACHE: dict = {} @@ -221,9 +226,15 @@ def _is_stopping(self): return self.stopping def _proc_exit( - self, proc, err_xtra, ctx, - callback, callback_args, bad_hosts=None, - callback_255=None, callback_255_args=None + self, + proc: 'Popen[bytes]', + err_xtra: str, + ctx: 'SubProcContext', + callback: Callable, + callback_args: list, + bad_hosts: Optional[Set[str]] = None, + callback_255: Optional[Callable] = None, + callback_255_args: Optional[list] = None, ): """Get ret_code, out, err of exited command, and call its callback.""" ctx.ret_code = proc.wait() @@ -236,7 +247,9 @@ def _proc_exit( if ctx.err is None: ctx.err = '' ctx.err += err + err_xtra - LOG.debug(ctx) + LOG.debug( + ctx.dump() if isinstance(ctx, SubFuncContext) else ctx + ) self._run_command_exit( ctx, bad_hosts=bad_hosts, callback=callback, callback_args=callback_args, @@ -487,7 +500,9 @@ def _run_command_init( @classmethod def _run_command_exit( - cls, ctx, bad_hosts=None, + cls, + ctx: 'SubProcContext', + bad_hosts: Optional[Set[str]] = None, callback: Optional[Callable] = None, callback_args: Optional[List[Any]] = None, callback_255: Optional[Callable] = None, diff --git a/tests/integration/test_subprocctx.py b/tests/integration/test_subprocctx.py index a7f9dd50bb..c4d0c4ca28 100644 --- a/tests/integration/test_subprocctx.py +++ b/tests/integration/test_subprocctx.py @@ -17,6 +17,7 @@ """ from logging import DEBUG +from textwrap import dedent async def test_log_xtrigger_stdout( @@ -37,13 +38,14 @@ async def test_log_xtrigger_stdout( # Create an xtrigger: xt_lib = run_dir / id_ / 'lib/python/myxtrigger.py' xt_lib.parent.mkdir(parents=True, exist_ok=True) - xt_lib.write_text( - "from sys import stderr\n\n\n" - "def myxtrigger():\n" - " print('Hello World')\n" - " print('Hello Hades', file=stderr)\n" - " return True, {}" - ) + xt_lib.write_text(dedent(r""" + from sys import stderr + + def myxtrigger(): + print('Hello World') + print('Hello Hades', file=stderr) + return True, {} + """)) schd = scheduler(id_) async with start(schd, level=DEBUG) as log: # Set off check for x-trigger: diff --git a/tests/unit/scripts/test_config.py b/tests/unit/scripts/test_config.py index affa774313..55d1b69dcd 100644 --- a/tests/unit/scripts/test_config.py +++ b/tests/unit/scripts/test_config.py @@ -14,17 +14,22 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +import asyncio import os from pathlib import Path -from types import SimpleNamespace +from textwrap import dedent from typing import Any, Optional, List -from cylc.flow.exceptions import InputError import pytest -from pytest import param -from cylc.flow.scripts.config import get_config_file_hierarchy from cylc.flow.cfgspec.globalcfg import GlobalConfig +from cylc.flow.option_parsers import Options +from cylc.flow.scripts.config import ( + _main, + get_config_file_hierarchy, + get_option_parser, +) +from cylc.flow.workflow_files import WorkflowFiles Fixture = Any @@ -200,3 +205,38 @@ def test_cylc_site_conf_path_env_var( GlobalConfig.get_inst() assert capload == files + + +def test_cylc_config_xtriggers(tmp_run_dir, capsys: pytest.CaptureFixture): + """Test `cylc config` outputs any xtriggers properly""" + run_dir: Path = tmp_run_dir('constellation') + flow_file = run_dir / WorkflowFiles.FLOW_FILE + flow_file.write_text(dedent(""" + [scheduler] + allow implicit tasks = True + [scheduling] + initial cycle point = 2020-05-05 + [[xtriggers]] + clock_1 = wall_clock(offset=PT1H):PT4S + rotund = xrandom(90, 2) + [[graph]] + R1 = @rotund => foo + """)) + option_parser = get_option_parser() + + asyncio.run( + _main(option_parser, Options(option_parser)(), 'constellation') + ) + assert capsys.readouterr().out == dedent("""\ + [scheduler] + allow implicit tasks = True + [scheduling] + initial cycle point = 2020-05-05 + [[xtriggers]] + clock_1 = wall_clock(offset=PT1H):4.0 + rotund = xrandom(90, 2):10.0 + [[graph]] + R1 = @rotund => foo + [runtime] + [[root]] + """)