2019年10月8日火曜日

python prompt-toolkit で簡単なメニュを造りたい

自分でちょっとずつ作っている cuekoma(python) は、年に数回しか使わない。 たぶん、そんな理由で、今年の秋のツーリング準備で使おうとしたら、使い方を忘れていた。 健忘症なのか痴呆症なのか。

コードを読んだりしてる時間が結構無駄。 なので、少しメニュー化することを真剣に考えようと思った次第。

が、今回は間に合うわけもないので、雛形だけ作ってみた。 省メモリ、省codeにしたいので、いわゆる、GUI のメニューでは無い。 しかも、ダイアログなんかも使わないシンプルなやつ。

やりたい事は、

  1. スクリプトを走らせると、
  2. 簡単な概要を表示して(configparserではない)、
  3. ジョブを選択して、
  4. 必要な設定ファイルを選択して、
  5. 実行する。

という、単純なもの。なにげに、 MS-DOS を思い起させるヤツだ。

使ったのは、 python-prompt-toolkit で、html のような設定で色付けとか、太字とかも可能。そして、logzero と使い分けることも。

現状は以下。

稚拙な source が下。

#!~/.pyenv/shims/python
"""Viking data file handling tool."""

from __future__ import unicode_literals

import os
import sys
import logging
from pathlib import Path

import logzero
from fuzzyfinder import fuzzyfinder
from prompt_toolkit import HTML, prompt
from prompt_toolkit import print_formatted_text as pprint
from prompt_toolkit.history import FileHistory
from prompt_toolkit.completion import Completer, Completion, WordCompleter
from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
from prompt_toolkit.formatted_text import FormattedText

# script run path
os.chdir(Path("~/Documents/proj/gpxs").expanduser())
# the Python 3.6 or above, os.chdir() can deal with Path object directly.

LOG_FORMAT = "%(color)s[%(module)s:%(lineno)3d]%(end_color)s %(message)s"
FORMATTER = logzero.LogFormatter(fmt=LOG_FORMAT)
logzero.setup_default_logger(formatter=FORMATTER)
logzero.logfile("./_logs/pp-menu.log", maxBytes=3e5, backupCount=3)
logzero.loglevel(logging.DEBUG)

def get_keypars(key_par, in_buf, key_nam):
    """Parse key parameters of this application."""

    if in_buf[0] != "#" and in_buf[0] != "\n":
        if in_buf[0] in ["["]:
            key_nam = in_buf.split("[")[1].split("]")[0]
            if key_nam not in key_par.values():
                key_par[key_nam] = {"cate": key_nam}
        else:
            key_par[key_nam][in_buf.split("=")[0].strip()] = in_buf.split("=")[
                1
            ].strip()

    return key_par, key_nam


def read_ppmenu_ini(ini_f):
    """Read script setting ini file."""

    with open(ini_f, "r") as in_file:
        key_par, key_nam = {}, ""
        for in_buf in in_file:
            key_par, key_nam = get_keypars(key_par, in_buf, key_nam)

    return key_par


def show_app_para(app_parm):
    """Show script parameters and description."""

    _str = "run  dir : %s " % str(Path.cwd().relative_to(Path.home()))
    pprint(HTML(' - <skyblue>%s</skyblue> (application\'s ini file is hear)' %  _str))

    tour_path = Path(app_parm["app"]['top_dir'])
    _str = "data dir : %s " % str(tour_path.relative_to(Path.home()))
    pprint(HTML(' - <skyblue>%s</skyblue> (user\'s data(in/out) directory.)' %  _str))

    _str = "co_poi.vik : 個人的なPOIを収めている viking のデータファイル."
    pprint(HTML('\n - <skyblue>%s</skyblue>' %  _str))
    _str = "\n  簡単な説明を付け加えておく方が良いと、後悔している。\n"
    pprint(HTML('             <skyblue>%s</skyblue>' %  _str))


class MyfileCompleter(Completer):
    """Set Completer."""

    def __init__(self, globbed_path):
       self.globbed_path = globbed_path
       self.inifile_list = [f.name for f in self.globbed_path.glob("*.ini")]
       self.num_setfile = len(self.inifile_list)

    def get_completions(self, document, complete_event):
        """Set Completer."""
        word_before_cursor = document.get_word_before_cursor(WORD=True)
        matches = fuzzyfinder(word_before_cursor, self.inifile_list)
        for m in matches:
            yield Completion(m, start_position=-len(word_before_cursor))


def select_ini_file(globbed_path):
    """Select tour ini file."""

    _s_text = FormattedText([
        ('#cccccc bold', "- Select ini file. "),
        ('#9999cc', '( %d ini files in dir.)\n' % MyfileCompleter(globbed_path).num_setfile),
        ('#7766cc', "   (<tab>key show list)\n  >  "),
    ])

    user_input = prompt(
        _s_text,
        history=FileHistory('pp-menu-ini-history.txt'),
        auto_suggest=AutoSuggestFromHistory(),
        completer=MyfileCompleter(globbed_path),
    )

    _s_text = FormattedText([
        ('#d15fee italic', '  >> INI file : '),
        ('#e066ff', user_input),
        ('#d15fee italic', '.\n'),
    ])
    pprint(_s_text)

    return Path(globbed_path / user_input)


def select_job_category():
    """Select job."""

    job_list = [
        "10: co-poi.vik (change waypoint symbol to default)",
        "11: co-poi.vik (re-build from vildata. require co_poi_lay.set)",
        "12: co-poi.vik (copy to custom POI dir. require to_garmin_poi.set) ",
        "13: conv. to vildata, from all gpx  data(check ...)",
        "14: conv. to vildata, from all json data(check ...)",
        "99: quit",
    ]
    job_index = [int(x.split(':')[0]) for x in job_list]

    _job_list_text = '\n   ' + '\n   '.join(job_list) + ''

    _s_text = FormattedText([
        ('#cccccc bold', '- Select job : \n'),
        ('#7799dd', _job_list_text),
        ('#7766cc', "\n\n   <tab>key select from list."),
        (' ', '\n\n  > '),
    ])

    job_completer = WordCompleter(job_list, ignore_case=True, )
    user_input = prompt(
        _s_text,
        history=FileHistory("pp-menu-job-history.txt"),
        completer=job_completer
    )

    _s_text = FormattedText([
        ('#d15fee italic', '  >> JOB: '),
        ('#e066ff', user_input),
        ('#d15fee italic', '.\n'),
    ])
    pprint(_s_text)

    job_number = user_input.split(':')[0]

    if int(job_number) not in job_index:
        # while でループへ。
        logzero.logger.info("!! select wrong job !!")
        sys.exit()

    if int(job_number) == 99:
        pprint(HTML('\n<b> * ばいばい *</b>\n'))
        sys.exit()

    return job_number


def main():
    """Read config and set and select select job."""

    pprint(HTML('\n<b><i>* menu test using python prompt toolkit .............</i></b>\n'))

    # スクリプトの共通パラメタの読み込み
    ppmenu_ini_file = Path.cwd() / "pp-menu.ini"
    app_parm = read_ppmenu_ini(ppmenu_ini_file)
    show_app_para(app_parm)

    # ジョブの選択
    job_number = select_job_category()

    # 個別設定ファイルの選択
    globbed_path = Path(app_parm['app']['top_dir'])
    tour_setfile = select_ini_file(globbed_path)

    logzero.logger.info(tour_setfile)
    logzero.logger.info(job_number)


if __name__ == "__main__":
    main()
    sys.exit()

年末にかけて、ちょっとづつ進めようと思っている。 メニュー化で整理しようとすると、色々、`あれ` という事が噴出してるから。

0 件のコメント:

コメントを投稿

Emacs の lsp の設定、なう(202310)

前回さらしてから、さらに1年。そう、3年めになる。 が、今回は一段と自信がない。 環境は、 Debian GNU/Linux 12 (bookworm) + emacs(29.1)。consult + company。 embark は未だに使ってない。 用途は、メモ と ...