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()

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

Emacs の window レイアウト をショートカットで

emacs を使っていて、dired とかでファイル操作を行なう時、 windowを複数並べて作業するのだが、この状態にするのが面倒だと感じている。 で、いつものように、コピペメインで、hydraのメニューに追加してみた。

やった事は、

  1. window をカレント一つにしてから
  2. 現在編集中のファイルを左右に分割し
  3. 他方に移動して、既定のdirectory を dired で開く。
  4. さらに、上下に分割して、
  5. counsel-bookmark を実行

というもの。当然、元ネタがあるわけで、 Using display-buffer-alist « Simplify d.o.o. という記事に出会ったから。

こいつを、ほんのチョット変更して組み込んだだけ。以下。

(defun my/work-lay(image-dir)
  "Split windows for image-file handling."
  (interactive)
  ;;
  (when (/= (count-windows) 1)
    (delete-other-windows)
    )
  ;; Create new window right of the current one
  (split-window-right 102)
  ;; Go to next window
  (other-window 1)
  ;; open image stocked directory
  (find-file image-dir)
  ;; Create new window below current one
  (split-window-below)
  ;; Go to previous window
  (other-window 1)
  ;; select bookmark
  (counsel-bookmark)
  )

これを、hydraのメニューに以下のようなアイテムを追加して終了。

("w1" (my/work-lay "~/Documents/ownCloud/imag") "work-org")
("w2" (my/work-lay "~/Documents/ownCloud/hugo") "work-hugo")

まだ自分にすら馴染んでない。きっとアップグレードするとか、別の手段に移行することになるかと。少しづつ、少しづつ。

自転車のビンディングシューズ、購入

ビンディングシューズを購入した。

インナーがボロボロで、ロングだと痛くなる。この状態で、ごまかしながら1年使ったが、 そろそろいいんじゃない?ということで、 誕生日(うれしくないけど)だという言い訳で、購入を決めた。 しかし、高くなってるなあ。

自分には Northwave (代理店 ウインクレル株式会社) が 結構合っていて、今回4足め(サンダル込み)。通販で購入するのも、そうゆう理由から。ビンディングはShimanoのSPD。

購入できるものを纏めると、以下。

  • グレードというか、価格というか
    • XCは、 origin < razer < revel < ghost < extreme
    • AMは、 escape Evo < outcross < spider2plus < enduro MID

これまで使っていたやつは、XCの一番底辺のグレード。さすがにカタログには無い。 きっと、origin が相当すると思う 今回は、AMにした。年齢的にも用途からも、フラペ対応が必要だろうというのが理由。悩んだけど。

カタログに、「ペダルの力がかかる部分を硬く、土踏まず部分は柔軟に」とあったので、 ビンディングからの突き上げ感対策をになっているにちがいないというのが決め手。 「28%耐磨耗性に優れる」ミシュランのソールにも興味だ。

定価は 168 で、あまぞんで 173だ。外通だと、70〜80ユーロぐらい。サイズによって値段が変ったりする。 貧乏人は当然、外通。

開封して、ぱっと見の印象は、かなりしっかり、つまり堅そう。

これまで使ってたヤツと並べてみる。

  • まず、散歩に使った印象

    足首というか踝まわりのサポートが無いハイキングシューズ。 ソールは堅く登山靴に近いかも。スニーカーとはいえない。包み込むようなホールド感も弱い。

    これまでとは足型が異なるようで、ワイド。もう半サイズ小さくても良かったかもと思うが、ハーフサイズの展開は無い。

    シマノのワイドぐらいはあるかもしれない(過去に一度経験)。足の指が動かせる。薄手の靴下は止めた方がよさそう。 夏場は困るかもしれない。

    ラバーソールなので、やはり重量はある。

  • 100キロほどのライド(3回)。

    ソールはしっかりしていて、SPDの突き上げを「点」で感じるのは僅か。カタログにあるように、きちんと補強されていると思う。使い込んでも継続するかは、少々不安。

    ベルクロの締め付け調整というか、足の甲まわりをフィットさせるのが難しい。 足型が異るようで、甲まわりの断面形状が、これまでをΩ型とするなら、今回は半円とう感じ。 足型によっては苦労するのかもしれない。 まあ、これまで使ってたやつも、最初はなじみが悪かったと記憶してるので、 素材が柔らかく馴染むのを待とう。

    ペダル(DIXNA FP、スポットクリップレスペダル、片面フラット)との相性が良いのか、ペダル上の安定感(内外への踏ん張り感)が良い。 しかもフラット面とクリップ面との差が小さくなるのが結構有難かったりする。

    ただし、ダイレクト感は、これまでより劣る。

    靴の中で、足首の方はある程度固定されるが、爪先の方は少し動かせるぐらいの余裕がある。で、 爪先側を動かしてペダルの踏み具合に変化をつけることが可能みたい。ちょっと新鮮で意外。

    気温30度ぐらいなら、我慢できないような蒸せは感じない。

    インソールを交換。これまで使ってた(Super feet 6年ほど使用)のを移植。通気性は悪くなるが、安定感は増す。

    まだまだ、クリート位置の調整中。

まあ、馴染んでくるまで、1年ぐらいはかかりそうなので、気長に付き合おうと思う。

麻のボディタオル

2018年の秋(まだ、自転車を封印してない)、 近江上布伝統産業会館 で、興味からボディタオルを購入した。 お、よかった。: 自然派パン工房 ふるさとの道 ほぼ毎日風呂で使ってきて、ついに寿命がきたようだ。 お店の方に、「糸が痩せて破れてくる」まで使える、と...