diff --git a/docs/guides/global_tasks.rst b/docs/guides/global_tasks.rst
new file mode 100644
index 000000000..76efd608b
--- /dev/null
+++ b/docs/guides/global_tasks.rst
@@ -0,0 +1,41 @@
+Global tasks
+============
+
+This guide covers how to use poethepoet as a global task runner, for private user level tasks instead of shared project level tasks. Global tasks are available anywhere, and serve a similar purpose to shell aliases or scripts on the ``PATH`` — but as poe tasks.
+
+There are two steps required to make this work:
+
+1. Create a project somewhere central such as ``~/.poethepoet`` where you define tasks that you want to have globally accessible
+2. Configure an alias in your shell's startup script such as ``alias goe="poe -C ~/.poethepoet"``.
+
+The project at ``~/.poethepoet`` can be a regular poetry project including dependencies or just a file with tasks.
+
+You can choose any location to define the tasks, and whatever name you like for the global poe alias.
+
+.. warning::
+
+ For this to work Poe the Poet must be installed globally such as via pipx or homebrew.
+
+
+Shell completions for global tasks
+----------------------------------
+
+If you uze zsh or fish then the usual completion script should just work with your alias (as long as it was created with poethepoet >=0.28.0).
+
+However for bash you'll need to generate a new completion script for the alias specifying the alias and the path to you global tasks like so:
+
+.. code-block:: bash
+
+ # System bash
+ poe _bash_completion goe ~/.poethepoet > /etc/bash_completion.d/goe.bash-completion
+
+ # Homebrew bash
+ poe _bash_completion goe ~/.poethepoet > $(brew --prefix)/etc/bash_completion.d/goe.bash-completion
+
+.. note::
+
+ These examples assume your global poe alias is ``goe``, and your global tasks live at ``~/.poethepoet``.
+
+How to ensure installed bash completions are enabled may vary depending on your system.
+
+
diff --git a/docs/guides/index.rst b/docs/guides/index.rst
index 3e2cf09c3..d4e230e46 100644
--- a/docs/guides/index.rst
+++ b/docs/guides/index.rst
@@ -11,4 +11,5 @@ This section contains guides for using the various features of Poe the Poet.
args_guide
composition_guide
include_guide
+ global_tasks
library_guide
diff --git a/docs/index.rst b/docs/index.rst
index 494dbe61c..7fb2575b4 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -122,7 +122,6 @@ By default poe will detect when you're inside a project with a pyproject.toml in
In all cases the path to project root (where the pyproject.toml resides) will be available as :sh:`$POE_ROOT` within the command line and process. The variable :sh:`$POE_PWD` contains the original working directory from which poe was run.
-
.. |poetry_link| raw:: html
poetry
@@ -131,3 +130,4 @@ In all cases the path to project root (where the pyproject.toml resides) will be
pipx
+Using this feature you can also define :doc:`global tasks<./guides/global_tasks>` that are not associated with any particular project.
diff --git a/docs/installation.rst b/docs/installation.rst
index ade55b960..653e4804d 100644
--- a/docs/installation.rst
+++ b/docs/installation.rst
@@ -107,7 +107,6 @@ Fish
poe _fish_completion > (brew --prefix)/share/fish/vendor_completions.d/poe.fish
-
Supported python versions
-------------------------
diff --git a/poethepoet/__init__.py b/poethepoet/__init__.py
index 029df2a86..2be2e9842 100644
--- a/poethepoet/__init__.py
+++ b/poethepoet/__init__.py
@@ -5,30 +5,46 @@
def main():
import sys
+ from pathlib import Path
- if len(sys.argv) == 2 and sys.argv[1].startswith("_"):
+ if len(sys.argv) > 1 and sys.argv[1].startswith("_"):
first_arg = sys.argv[1]
+ second_arg = next(iter(sys.argv[2:]), "")
+ third_arg = next(iter(sys.argv[3:]), "")
+
if first_arg in ("_list_tasks", "_describe_tasks"):
- _list_tasks()
+ _list_tasks(target_path=str(Path(second_arg).expanduser().resolve()))
return
+
+ target_path = ""
+ if second_arg:
+ if not second_arg.isalnum():
+ raise ValueError(f"Invalid alias: {second_arg!r}")
+
+ if third_arg:
+ if not Path(third_arg).expanduser().resolve().exists():
+ raise ValueError(f"Invalid path: {third_arg!r}")
+
+ target_path = str(Path(third_arg).resolve())
+
if first_arg == "_zsh_completion":
from .completion.zsh import get_zsh_completion_script
- print(get_zsh_completion_script())
+ print(get_zsh_completion_script(name=second_arg))
return
+
if first_arg == "_bash_completion":
from .completion.bash import get_bash_completion_script
- print(get_bash_completion_script())
+ print(get_bash_completion_script(name=second_arg, target_path=target_path))
return
+
if first_arg == "_fish_completion":
from .completion.fish import get_fish_completion_script
- print(get_fish_completion_script())
+ print(get_fish_completion_script(name=second_arg))
return
- from pathlib import Path
-
from .app import PoeThePoet
app = PoeThePoet(cwd=Path().resolve(), output=sys.stdout)
@@ -37,7 +53,7 @@ def main():
raise SystemExit(result)
-def _list_tasks():
+def _list_tasks(target_path: str = ""):
"""
A special task accessible via `poe _list_tasks` for use in shell completion
@@ -48,7 +64,7 @@ def _list_tasks():
from .config import PoeConfig
config = PoeConfig()
- config.load(strict=False)
+ config.load(target_path, strict=False)
task_names = (task for task in config.task_names if task and task[0] != "_")
print(" ".join(task_names))
except Exception:
diff --git a/poethepoet/completion/bash.py b/poethepoet/completion/bash.py
index 01f0129aa..d9cb084dd 100644
--- a/poethepoet/completion/bash.py
+++ b/poethepoet/completion/bash.py
@@ -1,4 +1,4 @@
-def get_bash_completion_script() -> str:
+def get_bash_completion_script(name: str = "", target_path: str = "") -> str:
"""
A special task accessible via `poe _bash_completion` that prints a basic bash
completion script for the presently available poe tasks
@@ -7,14 +7,18 @@ def get_bash_completion_script() -> str:
# TODO: see if it's possible to support completion of global options anywhere as
# nicely as for zsh
+ name = name or "poe"
+ func_name = f"_{name}_complete"
+
return "\n".join(
(
- "_poe_complete() {",
+ func_name + "() {",
" local cur",
' cur="${COMP_WORDS[COMP_CWORD]}"',
- ' COMPREPLY=( $(compgen -W "$(poe _list_tasks)" -- ${cur}) )',
+ f" COMPREPLY=( $(compgen -W \"$(poe _list_tasks '{target_path}')\""
+ + " -- ${cur}) )",
" return 0",
"}",
- "complete -o default -F _poe_complete poe",
+ f"complete -o default -F {func_name} {name}",
)
)
diff --git a/poethepoet/completion/fish.py b/poethepoet/completion/fish.py
index fea6b4264..d64471835 100644
--- a/poethepoet/completion/fish.py
+++ b/poethepoet/completion/fish.py
@@ -1,4 +1,4 @@
-def get_fish_completion_script() -> str:
+def get_fish_completion_script(name: str = "") -> str:
"""
A special task accessible via `poe _fish_completion` that prints a basic fish
completion script for the presently available poe tasks
@@ -8,16 +8,28 @@ def get_fish_completion_script() -> str:
# - support completion of global options (with help) only if no task provided
# without having to call poe for every option which would be too slow
# - include task help in (dynamic) task completions
- # - maybe just use python for the whole of the __list_poe_tasks logic?
+
+ name = name or "poe"
+ func_name = f"__list_{name}_tasks"
return "\n".join(
(
- "function __list_poe_tasks",
+ "function " + func_name,
+ " # Check if `-C target_path` have been provided",
+ " set target_path ''",
" set prev_args (commandline -pco)",
- ' set tasks (poe _list_tasks | string split " ")',
+ " for i in (seq (math (count $prev_args) - 1))",
+ " set j (math $i + 1)",
+ " set k (math $i + 2)",
+ ' if test "$prev_args[$j]" = "-C" && test "$prev_args[$k]" != ""',
+ ' set target_path "$prev_args[$k]"',
+ " break",
+ " end",
+ " end",
+ " set tasks (poe _list_tasks $target_path | string split ' ')",
" set arg (commandline -ct)",
" for task in $tasks",
- ' if test "$task" != poe && contains $task $prev_args',
+ f' if test "$task" != {name} && contains $task $prev_args',
# TODO: offer $task specific options
' complete -C "ls $arg"',
" return 0",
@@ -27,6 +39,6 @@ def get_fish_completion_script() -> str:
" echo $task",
" end",
"end",
- "complete -c poe --no-files -a '(__list_poe_tasks)'",
+ f"complete -c {name} --no-files -a '({func_name})'",
)
)
diff --git a/poethepoet/completion/zsh.py b/poethepoet/completion/zsh.py
index 29f934f4d..c528eb92f 100644
--- a/poethepoet/completion/zsh.py
+++ b/poethepoet/completion/zsh.py
@@ -1,7 +1,7 @@
from typing import Any, Iterable, Set
-def get_zsh_completion_script() -> str:
+def get_zsh_completion_script(name: str = "") -> str:
"""
A special task accessible via `poe _zsh_completion` that prints a zsh completion
script for poe generated from the argparses config
@@ -10,6 +10,8 @@ def get_zsh_completion_script() -> str:
from ..app import PoeThePoet
+ name = name or "poe"
+
# build and interogate the argument parser as the normal cli would
app = PoeThePoet(cwd=Path().resolve())
parser = app.ui.build_parser()
@@ -25,6 +27,9 @@ def format_exclusions(excl_option_strings):
# format the zsh completion script
args_lines = [" _arguments -C"]
for option in global_options:
+ if option.help == "==SUPPRESS==":
+ continue
+
# help and version are special cases that dont go with other args
if option.dest in ["help", "version"]:
options_part = (
@@ -67,20 +72,58 @@ def format_exclusions(excl_option_strings):
args_lines.append(f'"{options_part}[{option.help}]"')
- args_lines.append('"1: :($TASKS)"')
+ args_lines.append('"1: :($tasks)"')
+ args_lines.append('": :($tasks)"') # needed to complete task after global options
args_lines.append('"*::arg:->args"')
+ target_path_logic = """
+ local DIR_ARGS=("-C" "--directory" "--root")
+
+ local target_path=""
+ local tasks=()
+
+ for ((i=2; i<${#words[@]}; i++)); do
+ # iter arguments passed so far
+ if (( $DIR_ARGS[(Ie)${words[i]}] )); then
+ # arg is one of DIR_ARGS, so the next arg should be the target_path
+ if (( ($i+1) >= ${#words[@]} )); then
+ # this is the last arg, the next one should be path
+ _files
+ return
+ fi
+ target_path="${words[i+1]}"
+ tasks=($(poe _list_tasks $target_path))
+ i=$i+1
+ elif [[ "${words[i]}" != -* ]] then
+ if (( ${#tasks[@]}<1 )); then
+ # get the list of tasks if we didn't already
+ tasks=($(poe _list_tasks $target_path))
+ fi
+ if (( $tasks[(Ie)${words[i]}] )); then
+ # a task has been given so complete with files
+ _files
+ return
+ fi
+ fi
+ done
+
+ if (( ${#tasks[@]}<1 )); then
+ # get the list of tasks if we didn't already
+ tasks=($(poe _list_tasks $target_path))
+ fi
+ """
+
return "\n".join(
[
- "#compdef _poe poe\n",
- "function _poe {",
+ f"#compdef _{name} {name}\n",
+ f"function _{name} " "{",
+ target_path_logic,
' local ALL_EXLC=("-h" "--help" "--version")',
- " local TASKS=($(poe _list_tasks))",
"",
" \\\n ".join(args_lines),
"",
# Only offer filesystem based autocompletions after a task is specified
- " if (($TASKS[(Ie)$line[1]])); then",
+ " if (($tasks[(Ie)$line[1]])); then",
" _files",
" fi",
"}",
diff --git a/poethepoet/config.py b/poethepoet/config.py
index f865a66f4..6a1562f58 100644
--- a/poethepoet/config.py
+++ b/poethepoet/config.py
@@ -372,7 +372,7 @@ def load(self, target_path: Optional[Union[Path, str]] = None, strict: bool = Tr
config_path = self.find_config_file(
target_path=Path(target_path) if target_path else None,
- search_parent=target_path is None,
+ search_parent=not target_path,
)
self._project_dir = config_path.parent
diff --git a/pyproject.toml b/pyproject.toml
index b9f8711bc..07320b6d1 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -198,5 +198,5 @@ fixable = ["E", "F", "I"]
[build-system]
-requires = ["poetry-core"]
+requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"