Skip to content

Commit

Permalink
Merge pull request #136 from nat-n/development
Browse files Browse the repository at this point in the history
Release PR 0.19.0
  • Loading branch information
nat-n authored Mar 22, 2023
2 parents 287f8c5 + 977c061 commit 6fe8a47
Show file tree
Hide file tree
Showing 27 changed files with 252 additions and 89 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ jobs:
matrix:
os: [Ubuntu, MacOS, Windows]
python-version: ['3.7', '3.8', '3.9', '3.10', '3.11']
exclude: # TODO: remove this when pip is fixed
- os: Windows
python-version: '3.11'

runs-on: ${{ matrix.os }}-latest
steps:
- uses: actions/checkout@v3
Expand Down
37 changes: 33 additions & 4 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,8 @@ If extra arguments are passed to task on the command line (and no CLI args are
declared), then they will be available within the called python function via
:python:`sys.argv`.

If the target python function is an async function then it will be exectued with :python:`asyncio.run`.

Calling standard library functions
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Expand Down Expand Up @@ -1064,6 +1066,34 @@ This works by setting the argument values as environment variables for the subta
which can be read at runtime, but also referenced in the task definition as
demonstrated in the above example for a *ref* task and *script* task.

Passing free arguments in addition to named arguments
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

If no args are defined for a cmd task then any cli arguments that are provided are
simply appended to the command. If named arguments are defined then one can still
provide additional free arguments to the command by separating them from the defined
arguments with a double dash token :sh:`--`.

For example given a task like:

.. code-block:: toml
[tool.poe.tasks.lint]
cmd = "ruff check ${target_dir}"
args = { target_dir = { options = ["--target", "-t"], default = "." }}
calling the task like so:

.. code-block:: sh
poe lint -t tests -- --fix
will result in poe parsing the target_dir cli option, but appending the :sh:`--fix`
flag to the ruff command without attempting to interpret it.

Passing :sh:`--` in the arguments list to any other task type will simple result in any
subsequent arguments being ignored.

Project-wide configuration options
==================================

Expand Down Expand Up @@ -1461,11 +1491,10 @@ macOS, linux and windows.
Contributing
============

There's plenty to do, come say hi in
`the issues <https://github.com/nat-n/poethepoet/issues>`_! 👋
There's plenty to do, come say hi in `the discussions <https://github.com/nat-n/poethepoet/discussions>`_ or
`open an issue <https://github.com/nat-n/poethepoet/issues>`_! 👋

Also check out the
`CONTRIBUTING.MD <https://github.com/nat-n/poethepoet/blob/main/.github/CONTRIBUTING.md>`_ 🤓
Also check out the `CONTRIBUTING.MD <https://github.com/nat-n/poethepoet/blob/main/.github/CONTRIBUTING.md>`_ 🤓

Licence
=======
Expand Down
2 changes: 1 addition & 1 deletion poethepoet/__version__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "0.18.1"
__version__ = "0.19.0"
27 changes: 18 additions & 9 deletions poethepoet/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
from .exceptions import ExecutionError, PoeException

if TYPE_CHECKING:

from .config import PoeConfig
from .context import RunContext
from .task import PoeTask
Expand Down Expand Up @@ -48,7 +47,13 @@ def __init__(
self.ui = PoeUi(output=output)
self.poetry_env_path = poetry_env_path

def __call__(self, cli_args: Sequence[str]) -> int:
def __call__(self, cli_args: Sequence[str], internal: bool = False) -> int:
"""
:param internal:
indicates that this is an internal call to run poe, e.g. from a
plugin hook.
"""

self.ui.parse_args(cli_args)

if self.ui["version"]:
Expand All @@ -71,7 +76,7 @@ def __call__(self, cli_args: Sequence[str]) -> int:
self.print_help()
return 0

if not self.resolve_task():
if not self.resolve_task(internal):
return 1

assert self.task
Expand All @@ -80,7 +85,7 @@ def __call__(self, cli_args: Sequence[str]) -> int:
else:
return self.run_task() or 0

def resolve_task(self) -> bool:
def resolve_task(self, allow_hidden: bool = False) -> bool:
from .task import PoeTask

task = tuple(self.ui["task"])
Expand All @@ -93,7 +98,7 @@ def resolve_task(self) -> bool:
self.print_help(error=PoeException(f"Unrecognised task {task_name!r}"))
return False

if task_name.startswith("_"):
if task_name.startswith("_") and not allow_hidden:
self.print_help(
error=PoeException(
"Tasks prefixed with `_` cannot be executed directly"
Expand All @@ -111,7 +116,7 @@ def run_task(self, context: Optional["RunContext"] = None) -> Optional[int]:
context = self.get_run_context()
try:
assert self.task
return self.task.run(context=context, extra_args=self.ui["task"][1:])
return self.task.run(context=context, extra_args=self.task.invocation[1:])
except PoeException as error:
self.print_help(error=error)
return 1
Expand Down Expand Up @@ -173,8 +178,11 @@ def print_help(
from .task.args import PoeTaskArgs

if isinstance(error, str):
error == PoeException(error)
tasks_help: Dict[str, Tuple[str, Sequence[Tuple[Tuple[str, ...], str]]]] = {
error = PoeException(error)

tasks_help: Dict[
str, Tuple[str, Sequence[Tuple[Tuple[str, ...], str, str]]]
] = {
task_name: (
(
content.get("help", ""),
Expand All @@ -185,4 +193,5 @@ def print_help(
)
for task_name, content in self.config.tasks.items()
}
self.ui.print_help(tasks=tasks_help, info=info, error=error) # type: ignore

self.ui.print_help(tasks=tasks_help, info=info, error=error)
2 changes: 1 addition & 1 deletion poethepoet/env/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def get(self, envfile_path_str: str) -> Dict[str, str]:

result = {}

envfile_path = self._project_dir.joinpath(envfile_path_str)
envfile_path = self._project_dir.joinpath(Path(envfile_path_str).expanduser())
if envfile_path.is_file():
try:
with envfile_path.open() as envfile:
Expand Down
4 changes: 2 additions & 2 deletions poethepoet/env/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def __init__(
}

if parent_env is None:
# Get env vars from envfile referenced in global options
# Get env vars from envfile(s) referenced in global options
global_envfile = self._config.global_envfile
if isinstance(global_envfile, str):
self._vars.update(self.envfiles.get(global_envfile))
Expand Down Expand Up @@ -83,7 +83,7 @@ def for_task(
"""
result = EnvVarsManager(self._config, self._ui, parent_env=self)

# Include env vars from envfile referenced in task options
# Include env vars from envfile(s) referenced in task options
if isinstance(task_envfile, str):
result.update(self.envfiles.get(task_envfile))
elif isinstance(task_envfile, list):
Expand Down
7 changes: 7 additions & 0 deletions poethepoet/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
from typing import Optional


class PoeException(RuntimeError):
cause: Optional[str]

def __init__(self, msg, *args):
self.msg = msg
self.cause = args[0].args[0] if args else None
Expand All @@ -14,6 +19,8 @@ class ExpressionParseError(PoeException):


class ExecutionError(RuntimeError):
cause: Optional[str]

def __init__(self, msg, *args):
self.msg = msg
self.cause = args[0].args[0] if args else None
Expand Down
10 changes: 9 additions & 1 deletion poethepoet/executor/base.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import os
import shutil
import sys
from pathlib import Path
from typing import (
Expand Down Expand Up @@ -126,8 +127,15 @@ def execute(
self, cmd: Sequence[str], input: Optional[bytes] = None, use_exec: bool = False
) -> int:
"""
Execute the given cmd
Execute the given cmd.
"""

# Attempt to explicitly resolve the target executable, because we can't count
# on the OS to do this consistently.
resolved_executable = shutil.which(cmd[0])
if resolved_executable:
cmd = (resolved_executable, *cmd[1:])

return self._execute_cmd(cmd, input=input, use_exec=use_exec)

def _execute_cmd(
Expand Down
6 changes: 5 additions & 1 deletion poethepoet/executor/poetry.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,11 @@ def _get_poetry_virtualenv(self, force: bool = True):
def _poetry_cmd(self):
import shutil

return shutil.which("poetry") or "poetry"
from_path = shutil.which("poetry")
if from_path:
return str(Path(from_path).resolve())

return "poetry"

def _virtualenv_creation_disabled(self):
exec_cache = self.context.exec_cache
Expand Down
2 changes: 1 addition & 1 deletion poethepoet/helpers/python.py
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,7 @@ def _get_name_source_segment(source: str, node: ast.Name):
performant in common cases.
"""
if sys.version_info.minor >= 8:
return ast.get_source_segment(source, node) # type: ignore
return ast.get_source_segment(source, node)

partial_result = (
re.split(r"(?:\r\n|\r|\n)", source)[node.lineno - 1]
Expand Down
8 changes: 5 additions & 3 deletions poethepoet/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@ def _activate(self, application: Application) -> None:
command_prefix,
)
for task_name, task in poe_tasks.items():
if task_name.startswith("_"):
continue
self._register_command(
application, task_name, task, f"{command_prefix} "
)
Expand Down Expand Up @@ -220,7 +222,7 @@ def command_event_handler(
import shlex

task_status = PoeCommand.get_poe(application, event.io)(
cli_args=shlex.split(task)
cli_args=shlex.split(task), internal=True
)

if task_status:
Expand All @@ -235,7 +237,7 @@ def _monkey_patch_cleo(self, prefix: str, task_names: List[str]):
"""
Cleo is quite opinionated about CLI structure and loose about how options are
used, and so doesn't currently support invidual commands having their own way of
interpreting arguments, and forces them in inherit certain options from the
interpreting arguments, and forces them to inherit certain options from the
application. This is a problem for poe which requires that global options are
provided before the task name, and everything after the task name is interpreted
ONLY in terms of the task.
Expand All @@ -245,7 +247,7 @@ def _monkey_patch_cleo(self, prefix: str, task_names: List[str]):
option parsing are effectively disabled following any occurance of a "--" on the
command line, but parsing of the command name still works! Thus the solution is
to detect when it is a command from this plugin that is about to be executed and
insert the "--" token and the start of the tokens list of the ArgvInput instance
insert the "--" token at the start of the tokens list of the ArgvInput instance
that the application is about to read the CLI options from.
Hopefully this doesn't get broken by a future update to poetry or cleo :S
Expand Down
11 changes: 9 additions & 2 deletions poethepoet/task/args.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,18 @@ def _get_arg_options_list(arg: ArgParams, name: Optional[str] = None):
@classmethod
def get_help_content(
cls, args_def: Optional[ArgsDef]
) -> List[Tuple[Tuple[str, ...], str]]:
) -> List[Tuple[Tuple[str, ...], str, str]]:
if args_def is None:
return []

def format_default(arg) -> str:
default = arg.get("default")
if default:
return f"[default: {default}]"
return ""

return [
(arg["options"], arg.get("help", ""))
(arg["options"], arg.get("help", ""), format_default(arg))
for arg in cls._normalize_args_def(args_def)
]

Expand Down
29 changes: 16 additions & 13 deletions poethepoet/task/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,7 @@ def __init__(
):
self.name = name
self.content = content
if capture_stdout:
self.options = dict(options, capture_stdout=True)
else:
self.options = options
self.options = dict(options, capture_stdout=True) if capture_stdout else options
self._ui = ui
self._config = config
self._is_windows = sys.platform == "win32"
Expand Down Expand Up @@ -199,6 +196,21 @@ def resolve_task_type(

return None

def get_named_arg_values(self, env: "EnvVarsManager") -> Dict[str, str]:
try:
split_index = self.invocation.index("--")
parse_args = self.invocation[1:split_index]
except ValueError:
parse_args = self.invocation[1:]

if self.named_args is None:
self.named_args = self._parse_named_args(parse_args, env)

if not self.named_args:
return {}

return self.named_args

def _parse_named_args(
self, extra_args: Sequence[str], env: "EnvVarsManager"
) -> Optional[Dict[str, str]]:
Expand All @@ -209,15 +221,6 @@ def _parse_named_args(
return PoeTaskArgs(args_def, self.name, env).parse(extra_args)
return None

def get_named_arg_values(self, env: "EnvVarsManager") -> Dict[str, str]:
if self.named_args is None:
self.named_args = self._parse_named_args(self.invocation[1:], env)

if not self.named_args:
return {}

return self.named_args

def run(
self,
context: "RunContext",
Expand Down
16 changes: 11 additions & 5 deletions poethepoet/task/cmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,18 @@ def _handle_run(
env.update(named_arg_values)

if named_arg_values:
# If named arguments are defined then it doesn't make sense to pass extra
# args to the command, because they've already been parsed
cmd = self._resolve_args(context, env)
else:
cmd = (*self._resolve_args(context, env), *extra_args)
# If named arguments are defined then pass only arguments following a double
# dash token: `--`
try:
split_index = extra_args.index("--")
extra_args = extra_args[split_index + 1 :]
except ValueError:
extra_args = tuple()

cmd = (*self._resolve_args(context, env), *extra_args)

self._print_action(" ".join(cmd), context.dry)

return context.get_executor(self.invocation, env, self.options).execute(
cmd, use_exec=self.options.get("use_exec", False)
)
Expand Down
1 change: 1 addition & 0 deletions poethepoet/task/ref.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ def _handle_run(
import shlex

invocation = tuple(shlex.split(env.fill_template(self.content.strip())))
extra_args = [*invocation[1:], *extra_args]
task = self.from_config(invocation[0], self._config, self._ui, invocation)
return task.run(context=context, extra_args=extra_args, parent_env=env)

Expand Down
Loading

0 comments on commit 6fe8a47

Please sign in to comment.