Source code for afwf.handler

# -*- coding: utf-8 -*-

"""
Alfred workflow handler module.
"""

import typing as T
import subprocess
from pathlib import Path

import attrs
from attrs_mate import AttrsClass
from .vendor.better_pathlib import temp_cwd

from .script_filter import ScriptFilter


[docs]@attrs.define class Handler(AttrsClass): """ [CN] Handler 主要负责处理 Input Output 逻辑. 在 Alfred Workflow 的编程模型中, Input 是 搜索框的字符串. Output 则是 Drop Down menu 所需的 Item, 以及一些 Variables, Rerun Setting. 这些信息被放在 :class:`~afwf.script_filter.ScriptFilter` 这个 Data Container 类中. 你在 Script Filter 中的 Script 一栏中填写如下信息的时:: ${path_to_python} main.py '${handler_id} ${query}' 其行为等效于使用 ``${path_to_python}`` 的 Python 解释器, 调用跟 ``${handler_id}`` 所对应的 :class:`Handler` 类中的 ``handler(query)`` 方法. Handler 必须实现下面这些抽象方法. - :meth:`~afwf.handler.Handler.main` - :meth:`~afwf.handler.Handler.parse_query` - :meth:`~afwf.handler.Handler.encode_query` """ id = AttrsClass.ib_str(nullable=False)
[docs] def main(self, **kwargs) -> ScriptFilter: # pragma: no cover """ [CN] 用来处理 Script Filter 的具体业务逻辑的主方法. 是一个抽象方法. 你需要 override 这个方法 并给予具体的实现. 该方法可以接收任何自定义的参数, 并必须返回一个 :class:`~afwf.script_filter.ScriptFilter` 对象, 里面包含了Alfred 对话框里的 Drop Down Menu 中的 :class:`~afwf.item.Item` 对象. 在设计上, 这个方法应该着眼于核心业务逻辑, 并且可以被单元测试充分覆盖. 在单元测试中, 你可以调用这个方法传入各种可能得参数组合, 并返回一个 :class:`~afwf.script_filter.ScriptFilter`, 然后检查里面的 items 是否符合预期. """ raise NotImplementedError
[docs] def parse_query(self, query: str) -> T.Dict[str, T.Any]: # pragma: no cover """ [CN] 一个抽象方法. 用来解析 Script Filter 传入的 query 字符串. 返回的字典要和 :meth:`~afwf.handler.Handler.main` 中的参数一一对应. 注意, 即使是空字符串, 返回的字典也必须包含所有的参数. :param query: 你在 Alfred Workflow UI 中跟在 keyword 之后的 query 字符串. :return: 一个字典, 它和 :meth:`~afwf.handler.Handler.main` 中的参数一一对应. """ raise NotImplementedError
[docs] def encode_query(self, **kwargs) -> str: # pragma: no cover """ [CN] 一个抽象方法. 用来将结构化的参数编码为字符串. 该方法的参数要和 :meth:`~afwf.handler.Handler.main` 中的参数一一对应. """ raise NotImplementedError
[docs] def handler(self, query: str) -> ScriptFilter: # pragma: no cover """ [CN] 该方法对 :meth:`~afwf.handler.Handler.main` 进行的一层封装. 只不过接收的参数是一个 query string, 也就是你在 Script Filter Widget 里面定义的 ``/usr/bin/python3 main.py '{handler_id} {query}'`` 中的 query 部分. 该方法的主要工作是调用 :meth:`~afwf.handler.Handler.parse_query`, 将 query 解析成结构化的参数, 然后传给 :meth:`~afwf.handler.Handler.main` 进行处理. :param query: 你在 Alfred Workflow UI 中跟在 keyword 之后的 query 字符串. :return: :class:`~afwf.script_filter.ScriptFilter` 对象. """ return self.main(**self.parse_query(query))
[docs] def encode_run_script_command( self, bin_python: T.Union[str, Path, T.Any], **kwargs, ) -> str: # pragma: no cover """ 将 :meth:`~afwf.handler.Handler.main` 中的参数编码为 Alfred Workflow 中的 Run Script 的 bash command. 当你定义按下 Enter 后所对应的行为是执行某个复杂的 Python 函数的时候, 该方法就可以将 bash command 编码为 arg, 并传给 "Run Script" action. :param bin_python: 运行 Alfred Workflow 所用的 Python 解释器的绝对路径. :return: Alfred Workflow 中的 Script Filter 底层实际执行的 bash command 命令. """ return f"{bin_python} main.py '{self.id} {self.encode_query(**kwargs)}'"
[docs] def run_script_command( self, bin_python: T.Union[str, Path, T.Any], dir_workflow: T.Union[str, Path, T.Any], query: str, verbose: bool = False, ) -> T.Optional[str]: # pragma: no cover """ 模拟 Alfred Workflow UI 中的行为. 执行 UI 界面底层对应的 ``Script``. 当 UI 工作不符合预期时, 可以用这个方法 debug, 执行底层的 Python 代码. :param bin_python: 运行 Alfred Workflow 所用的 Python 解释器的绝对路径. :param dir_workflow: Alfred Workflow 中 ``/path/to/Alfred.alfredpreferences/workflows/user.workflow.ABCD1234-A1B2-C3D4-E5F6-A1B2C3D4E5F6`` 这个目录的绝对路径. :param query: 你在 Alfred UI 中输入的 query 内容. :param verbose: 是否打印出执行的 bash command. :return: Alfred Workflow 中的 Script Filter 底层实际执行的 bash command 命令 所返回的 JSON 字符串. 如果是 None 则表示该命令没有返回值. """ args = [ f"{bin_python}", "main.py", f"{self.id} {query}", ] cmd = f"{bin_python} main.py '{self.id} {query}'" if verbose: print(f"run: {cmd}") with temp_cwd(dir_workflow): res = subprocess.run(args, capture_output=True, check=True) response = res.stdout.decode("utf-8") if verbose: print(response) return response