diff --git a/README.md b/README.md index a3d52b0..be2a1a7 100644 --- a/README.md +++ b/README.md @@ -7,4 +7,42 @@ This package is under development. We will release it soon in the future. # Installation -[fzf](https://github.com/junegunn/fzf) is required + +```shell +pip install wan # TODO: upload this to pip source +``` + +## config + +Please config your [notifiers](https://github.com/liiight/notifiers). +`wan` will read the setting in ` ~/.dotfiles/.notifers.yaml` as the arguments for notifiers. + +Here is a config example of telegram +```yaml +provider: telegram +kwargs: + chat_id: + token: +``` + +Other configs: +```yaml +log_level: DEBUG # the default level is INFO +``` + + +# Usage + +## Use in python code + +* Call the function in python code directly. +```python + +from wan import ntf; ntf('Finished') +``` + +* Call the function in shell directly +```shell +> sleep 10 ; wan ntf sleep finished +``` + diff --git a/docs/README.md b/docs/README.md index 9b88081..2010788 100644 --- a/docs/README.md +++ b/docs/README.md @@ -2,5 +2,5 @@ ## Telegram * get `token` from `@BotFather` by sending `/newbot` -* send `/start` to your chatbot to start charting. +* send `/start` to your chatbot to start chatting. * Get `chat_id` from `@myidbot` by sending `/getid` diff --git a/docs/RoadMap.md b/docs/RoadMap.md new file mode 100644 index 0000000..43068ad --- /dev/null +++ b/docs/RoadMap.md @@ -0,0 +1,25 @@ +# Plan + +## Development +- [ ] Start command after another process ends or become idle +- [ ] Draw the workflow. +- [ ] auto find my running process +- [ ] Run and wait the command +- [ ] GIF to demonstrate the usage of WAN +- [ ] documents(rst??) +- [ ] Opensource Licence? +- [ ] travies - CI +- [ ] wait until system become idle + +### optional +- [ ] Can we make fzf installed automatically? + +## Release +- [ ] pip release +- [ ] readthedocs??? + + + +## Promotion +- [ ] stackoverflow +- [ ] zhihu diff --git a/wan/__init__.py b/wan/__init__.py index 90b82a1..371eed9 100644 --- a/wan/__init__.py +++ b/wan/__init__.py @@ -7,10 +7,12 @@ from .utils import get_pid_via_fzf import psutil import time +import sys +import os class Notifier: - def __init__(self, config_path: str = '~/.dotfiles/.notifers.yaml'): + def __init__(self, config_path: str = '~/.dotfiles/.notifiers.yaml'): """__init__. Parameters @@ -24,6 +26,15 @@ def __init__(self, config_path: str = '~/.dotfiles/.notifers.yaml'): chat_id: token: ``` + + If you need proxy for your provider, please use the config below. + The env will be updated when running `ntf` method + [This solution](https://github.com/liiight/notifiers/issues/236) is proposed by notifiers + ``` + env: + HTTP_PROXY: 'http://IP:PORT' + HTTPS_PROXY: 'http://IP:PORT' + ``` """ # TODO: DEBUG mode path = Path(config_path).expanduser() @@ -34,39 +45,68 @@ def __init__(self, config_path: str = '~/.dotfiles/.notifers.yaml'): else: with path.open() as f: self.config = yaml.load(f, Loader=yaml.FullLoader) + logger.remove() + log_level = self.config.get('log_level', 'INFO') + logger.add(sys.stderr, level=log_level) + logger.debug(f"log level: {log_level}") self._provider = get_notifier(self.config['provider']) kwargs = self.config['kwargs'] self._ntf = partial(self._provider.notify, **kwargs) - def ntf(self, message): + self.env = self.config.get('env', {}) + + def ntf(self, *messages): + message = " ".join(messages) + logger.debug("Sending message: {}".format(message)) + if len(message) == 0: + logger.warning("Blank message.") + + # set proxy if needed + env_back = os.environ.copy() + os.environ.update(self.env) self._ntf(message=message) + for k, v in self.env.items(): + if k not in env_back: + del os.environ[k] + else: + os.environ[k] = env_back[k] - def wait(self, pid=None, message=None, idle=False, patience=20): + @staticmethod + def _get_process_info(pid): + process_info = ":" + try: + p = psutil.Process(pid) + except psutil.NoSuchProcess: + process_info = "" + else: + process_info = ":" + ' '.join(p.cmdline()) + return process_info + + def wait(self, pid=None, message=None, idle=False, patience=20, sleep=3): """wait. - wati the proces to stop or idle Parameters ---------- pid : pid + message : + message idle : will it notify me if the process become idle + patience : + How many idle status is ignored before reguard the process as stopped + sleep : + sleep """ logger.debug(f"Idle: {idle}") - process_info = ":" if pid is None: pid = get_pid_via_fzf() if pid is None: - logger.info('No process selected') + logger.info('No process selected, You can used --pid to specify the process') return - try: - p = psutil.Process(pid) - except psutil.NoSuchProcess: - process_info = "" - else: - process_info = ":" + ' '.join(p.cmdline()) + process_info = self._get_process_info(pid) logger.info(f'Process[{pid}{process_info}] selected') @@ -75,24 +115,30 @@ def wait(self, pid=None, message=None, idle=False, patience=20): try: p = psutil.Process(pid) except psutil.NoSuchProcess: - logger.debug(f'The process[PID] has ended') + logger.info(f'The process[PID] has ended') break else: - # TODO: get the information of subprocess + # TODO: get the information of subprocesses p_status = p.status() + logger.debug(f'status: {p_status}, patience: {cp}') if idle and p_status not in {psutil.STATUS_RUNNING, psutil.STATUS_DISK_SLEEP}: cp += 1 if cp > patience: - logger.debug(f'The process is not running, status: {p_status}') + logger.info(f'The process is not running, status: {p_status}') break else: cp = 0 - time.sleep(2) + time.sleep(sleep) if message is None: message = f'The Process[{pid}{process_info}] has stopped or become idle now.' self.ntf(message) +def ntf(message): + # notify with the call stack + Notifier().ntf(message) + + def run(): fire.Fire(Notifier) diff --git a/wan/utils.py b/wan/utils.py index 6d4209d..1505526 100644 --- a/wan/utils.py +++ b/wan/utils.py @@ -1,5 +1,8 @@ import subprocess from iterfzf import iterfzf +from loguru import logger +import os +import stat def iter_ps(): @@ -14,8 +17,18 @@ def get_pid_from_line(line): def get_pid_via_fzf(exact=True): - # TODO: make it can be selected with full match - return get_pid_from_line(iterfzf(iter_ps(), multi=False, exact=exact)) + try: + # FIXME: This is a bug from iterfzf. The fzf may not be executable + selected_line = iterfzf(iter_ps(), multi=False, exact=exact) + except PermissionError as e: + try: + os.chmod(e.filename, os.stat(e.filename).st_mode | stat.S_IEXEC) + except PermissionError: + logger.error(f'Please make {e.filename} executable(e.g `chmod a+x {e.filename}`).') + return None + else: + selected_line = iterfzf(iter_ps(), multi=False, exact=exact) + return get_pid_from_line(selected_line) if __name__ == "__main__":