tklib.tkscript_macro のソースコード

"""
概要: tkScriptクラスとその関連機能を提供し、独自のスクリプト言語の実行を管理するモジュール。
詳細説明:
    このモジュールは、INIファイル形式のスクリプトを解析し、定義されたメニューやボタンのアクションを実行するための機能を提供します。
    変数置換、外部コマンド実行、ファイル操作、GUIダイアログの表示など、多岐にわたるコマンドをサポートします。
関連リンク:
    :doc:`tkscript_macro_usage`
"""
import os
#import platform
import sys
import re
import glob
import subprocess
import shutil

from tklib.tkobject import tkObject
from tklib.tkparams import tkParams
from tklib.tkutils import pint, pfloat
from tklib.tkutils import del_quote, split_two, split_command_line, quote_command_if_space
from tklib.tkfile import get_encoding, open_chardet
import tklib.tkgui.tktkinter as tktkinter


#=========================
# Application base class
#=========================


[ドキュメント] class tkScript(tkObject): """ 概要: スクリプト実行エンジンの中核となるクラス。 詳細説明: マクロスクリプトファイルの読み込み、解析、およびコマンドの実行を管理します。 スクリプトファイル内のセクション(メインメニューやボタン)を特定し、 それらに紐づけられた一連のコマンドを実行する機能を提供します。 """ def __init__(self, script_files = [], **args): """ 概要: tkScriptクラスのインスタンスを初期化する。 詳細説明: スクリプトファイルのリストと初期設定を基にオブジェクトを構築します。 内部で変数を管理するための辞書 `self.vars` を初期化し、ファイル存在チェックモードを設定します。 :param script_files: list[str], optional: 実行対象となるスクリプトファイルのパスのリスト。デフォルトは空のリスト。 :param args: dict: 親クラスtkObjectに渡される追加のキーワード引数。 """ super().__init__() self.script_files = script_files self.vars = { "check_file_mode": "auto" } self.script_inf = {} self.update(**args)
[ドキュメント] def read_cont_lines(self, fp): """ 概要: バックスラッシュで連結された複数行のスクリプト行を読み込む。 詳細説明: ファイルポインタ `fp` から一行ずつ読み込み、行末がバックスラッシュ ('\\') で終わる場合は次の行と連結します。 これにより、スクリプトファイル内で論理的に一行として扱われる複数行のコマンドを正しく処理できます。 :param fp: file object: 読み込み対象のファイルポインタ。 :returns: str: 連結された一行の文字列。ファイル終端に達した場合は空文字列を返します。 """ s = '' while 1: line = fp.readline() if not line: return s line = line.strip() n = len(line) if n == 0 or line[n-1] != '\\': s += ' ' + line break s += ' ' + line[0:n-1] return s.strip()
[ドキュメント] def decompose_section(self, section): """ 概要: スクリプトセクション文字列をメニュー名、インデックス、タスクに分解する。 詳細説明: `[ButtonXX.MenuName].Task` や `ButtonXX.MenuName` のような形式のセクション文字列を解析し、 メニュー名、ボタンインデックス、および関連タスク(例: 'help', 'dbl_click')を抽出します。 これにより、スクリプト内の特定のセクションをプログラム的に参照できるようになります。 :param section: str: 分解するスクリプトセクションの文字列。 :returns: tuple[str, int | str, str]: (メニュー名, ボタンインデックス (intまたは空文字列), タスク名 (strまたは空文字列)) のタプル。 """ if section[0] != '[': m = re.search(r'Button(\d+)\.(\S.+)$', section) if m: return m.groups()[1], pint(m.groups()[0]), '' else: return section, '', '' m = re.search(r'\[\s*Button(\d+)\.(\S.+)\s*\]', section) if m: menu = m.groups()[1] idx = m.groups()[0] m = re.search(r'\]\.(\S*)', section) if m: return menu, pint(idx), m.groups()[0] else: return menu, pint(idx), '' return section, '', ''
[ドキュメント] def find_file_from_mainmenu(self, app, main_menu, task = None, script_files = None): """ 概要: メインメニューセクションを定義しているスクリプトファイルと行を見つける。 詳細説明: 指定されたメインメニュー名 (`[MenuName]`) またはそれに付随するタスク (`[MenuName].task`) を含むスクリプトファイルを `script_files` のリストから探し、 そのファイルパス、ファイルポインタ、および一致した行を返します。 :param app: tkLauncherApp: アプリケーションのインスタンス。変数の解決や警告表示に使用されます。 :param main_menu: str: 検索するメインメニューの名前。 :param task: str | None, optional: メインメニューに関連付けられたタスク(例: 'help')。デフォルトはNone。 :param script_files: list[str] | None, optional: 検索対象となるスクリプトファイルのパスのリスト。指定しない場合はself.script_filesを使用します。 :returns: tuple[str | None, file object | None, str | None]: (ファイルパス, ファイルポインタ, 一致した行) のタプル。見つからない場合は (None, None, None) を返します。 """ main_menu_reg = re.sub(r'([\[\]\(\)\.\\\-])', r'\\\1', main_menu) # print("m=", main_menu_reg) for path in script_files: # print(f"a Read [{path}]") try: fp = open_chardet(path, 'r', def_encoding = 'shift_jis') except: app.print_warning(f"Warning in tkscript_macto.find_file_from_mainmenu(): Can not read [{path}]") continue while 1: line = fp.readline() if not line: break line = line.strip() if line == '' or line[0] != '[': continue # Check [Boot] etc # print(f" line={line}") if line == main_menu: # print(f"91 idx=None: return {path=} {line=}") return path, fp, line # Check [section] match = re.match(r'\[\s*(\S.*)\s*\](.*?)\s*$', line) if not match: continue sec = app.p(match.groups()[0]) task2 = match.groups()[1] if task2 != "" and task2[0] == '.': task2 = task2[1:] # print(f" 100 sec={sec}") if sec == main_menu and (task is None or task == ''): return path, fp, line if sec == main_menu and task2 == task: return path, fp, line return None, None, None
[ドキュメント] def find_file_from_button(self, app, main_menu, idx = None, task = None, script_files = None): """ 概要: ボタンセクションを定義しているスクリプトファイルと行を見つける。 詳細説明: 指定されたメインメニュー名とボタンインデックス (`[ButtonXX.MenuName]`) または それに付随するタスク (`[ButtonXX.MenuName].task`) を含むスクリプトファイルを `script_files` のリストから探し、そのファイルパス、ファイルポインタ、および一致した行を返します。 :param app: tkLauncherApp: アプリケーションのインスタンス。変数の解決や警告表示に使用されます。 :param main_menu: str: 検索するボタンのメインメニュー名。 :param idx: int | None, optional: 検索するボタンのインデックス(1から始まる)。デフォルトはNone。 :param task: str | None, optional: ボタンに関連付けられたタスク(例: 'help', 'dbl_click')。デフォルトはNone。 :param script_files: list[str] | None, optional: 検索対象となるスクリプトファイルのパスのリスト。指定しない場合はself.script_filesを使用します。 :returns: tuple[str | None, file object | None, str | None]: (ファイルパス, ファイルポインタ, 一致した行) のタプル。見つからない場合は (None, None, None) を返します。 """ # print("") # print("find_file_from_button") # print(f"112: main_manu={main_menu} idx={idx} task={task}") # print("script_files=", script_files) main_menu_reg = re.sub(r'([\[\]\(\)\.\\\-])', r'\\\1', main_menu) # print(f"119: main_menu_reg={main_menu_reg}") for path in script_files: # print(f"a Read [{path}]") try: fp = open_chardet(path, 'r', def_encoding = 'shift_jis') except: app.print_warning(f"Warning in tkscript_macto.find_file_from_button(): Can not read [{path}]") continue while 1: line = fp.readline() if not line: break line = line.strip() if line == '' or line[0] != '[': continue # print("") # print("138: line=", line) # print(f" test main_menu={main_menu}") # Check [ButtonXX.AAA] match = re.match(r'\[Button(\d+)\.(.*)\s*]', line) if not match: # print("143: no match") continue sec = app.p(match.groups()[1]) idx2 = pint(match.groups()[0]) # print(f"149 {sec=} {idx=} {idx2=}") if sec != main_menu and sec != app.p(main_menu): continue # print(f" 152: passed main_menu {main_menu}") if idx is None: # print(f"155 idx=None: return path={path} line={line}") return path, fp, line else: # print(f"157 idx!=None") # print(f" test idx idx={idx} vs idx2={idx2}") if idx2 == pint(idx): # print(f" 122 test task for task={task}") if task is None or task == '': # print(f"1 idx!=None task=None: return {path=} {line=}") return path, fp, line # Check [ButtonXX.AAA].help/dbl_click # print(f"{line=}") match = re.search(r'\]\.(\S+)', line) if match: t = match.groups()[0] # print(f" 170: t={t} task={task}:") if match and task == t: # print(f"1 idx!=None task={t}: return {path=} {line}") return path, fp, line else: # print(f"175: No match: return {path=} {line}") # return path, fp, line continue return None, None, None
[ドキュメント] def find_file_from_menu(self, app, main_menu, idx = None, task = None, script_files = None): """ 概要: 指定されたメニュー、ボタンインデックス、タスクに対応するスクリプトファイルとセクション行を見つける。 詳細説明: `find_file_from_mainmenu` と `find_file_from_button` を内部的に呼び出し、 メインメニュー、または特定のボタンセクションを検索します。 スクリプト実行時にどのファイル内のどのセクションを参照するかを決定する際に使用されます。 :param app: tkLauncherApp: アプリケーションのインスタンス。変数の解決や警告表示に使用されます。 :param main_menu: str: 検索するメインメニューまたはボタンのメインメニュー名。 :param idx: int | None, optional: 検索するボタンのインデックス(1から始まる)。メインメニュー検索の場合はNone。 :param task: str | None, optional: メニューやボタンに関連付けられたタスク。デフォルトはNone。 :param script_files: list[str] | None, optional: 検索対象となるスクリプトファイルのパスのリスト。指定しない場合はself.script_filesを使用します。 :returns: tuple[str | None, file object | None, str | None]: (ファイルパス, ファイルポインタ, 一致した行) のタプル。見つからない場合は (None, None, None) を返します。 """ # print(f"68 main_menu={main_menu}, idx={idx} task={task}") # print("script_files=", script_files) # print("idx=", main_menu, idx, task) if script_files is None: script_files = self.script_files # print("script_files=", script_files) # print(f"tkscript_macro.find_file_from_menu(): menu={main_menu} idx=", idx, " task=", task) if idx is None: # print("184") path, fp, line = self.find_file_from_mainmenu(app, main_menu, task, script_files = script_files) else: # print("187") path, fp, line = self.find_file_from_button(app, main_menu, idx, task, script_files = script_files) # print("p=", path, fp, line) return path, fp, line
[ドキュメント] def get_main_menus(self, app): """ 概要: 登録されているすべてのメインメニューを読み込み、その情報を取得する。 詳細説明: `self.script_files` に指定されたすべてのスクリプトファイルを走査し、 `[ButtonXX.MenuName]` 形式のセクションを抽出してメインメニューのリストを作成します。 また、各メニューがどのファイルに定義されているかの情報も `self.script_inf` に格納します。 スクリプトファイル内に 'exit_if_not_exist' コマンドが存在する場合、条件に応じて処理を中断することがあります。 :param app: tkLauncherApp: アプリケーションのインスタンス。警告表示などに使用されます。 :returns: list[str]: 検出されたメインメニュー名のリスト。 """ # print("f=", self.script_files) print("") print("Read menus") menus = {} for path in self.script_files: print(f" Read [{path}]") try: fp = open_chardet(path, 'r') # fp = open(path, 'r') except: app.print_warning("") app.print_warning(f"Warning in tkscript_macto.get_main_menus(): Can not read [{path}]") continue is_header = True while 1: line = fp.readline() if not line: break if line == '': continue if is_header: if 'exit_if_not_exist' in line: a = line.split() a = self.convert_vars(app, a) if len(a) >= 2 and not os.path.exists(a[1]): print(f" tkscript_macro.get_main_menus(): Exit by exit_if_not_exist [{a[1]}]") break if line[0] == '[': is_header = False else: continue match = re.match(r'\[\s*Button(\d+)\.\s*(.*)\s*\]', line) if not match: continue idx = match.groups()[0] menu = match.groups()[1] # print(" line:", line.strip()) if menus.get(menu, None) is None: menus[menu] = { "paths": [path] } else: menus[menu]["paths"].append(path) fp.close() self.script_inf = {} for menu in menus: self.script_inf[menu] = { "paths": set(menus[menu]["paths"]) } m = app.p(menu) if m != menu: self.script_inf[m] = self.script_inf[menu] # for menu in self.script_inf.keys(): # print(f"{menu}: ", self.script_inf[menu]) ms = list(menus.keys()) # print("ms=", ms) # exit() return ms
[ドキュメント] def get_caption(self, app, main_menu): """ 概要: 指定されたメインメニューのキャプション文字列を取得する。 詳細説明: `main_menu` に対応するスクリプトセクションを検索し、そのセクション内で定義されている `Caption=` コマンドの値を読み取って返します。 キャプションが見つからない場合は 'no caption' を返します。 :param app: tkLauncherApp: アプリケーションのインスタンス。変数の解決などに使用されます。 :param main_menu: str: キャプションを取得するメインメニューの名前。 :returns: str | None: メインメニューのキャプション文字列。見つからない場合はNone。 """ # print(f"{main_menu=}") inf = self.script_inf.get(main_menu, None) if inf: path, fp, line = \ self.find_file_from_menu(app, main_menu, idx = None, task = None, script_files = inf["paths"]) else: path, fp, line = self.find_file_from_menu(app, main_menu, idx = None, task = None) if fp is None: return None fp.close() # print("get_submenus; read", path) fp = open_chardet(path, 'r') # fp = open(path, 'r') if not fp: return None main_menu_reg = re.sub(r'([\[\]\(\)\.\-\\])', r'\\\1', main_menu) # print(f"{main_menu_reg=}") submenus = [] caption = 'no caption' while 1: line = fp.readline() if not line: break line = line.strip() # print(f"{line=}") # print(f"{main_menu_reg=}", '\[RButton6\]') # match = re.match(r'\[RButton6\]', line) match = re.match(main_menu_reg, line) if match: pass elif not match: continue match = re.match(r'\[Button\d+\.(.*)\s*]', line) if not match: continue sec = app.p(match.groups()[0]) if sec != main_menu: continue pos = fp.tell() while 1: line = fp.readline() if not line: break line = line.strip() if line == '': continue if line[0] == '[' or (len(line) >= 3 and line[0:3].lower() == 'end'): fp.seek(pos) break match = re.match(r'\s*Caption\s*=\s*(.*)\s*$', line, re.IGNORECASE) if not match: continue caption = match.groups()[0] break fp.close() return caption
[ドキュメント] def get_submenus(self, app, main_menu): """ 概要: 指定されたメインメニューに関連するすべてのサブメニュー(ボタン)の情報を取得する。 詳細説明: `main_menu` に対応するスクリプトセクションを検索し、そのセクションに含まれる `[ButtonXX.MenuName]` 形式のサブセクションを解析します。 各サブメニューについて、インデックス、タスク、キャプションなどの情報を抽出し、リストとして返します。 この処理中、`set`, `join_path`, `use_os_path_sep`, `check_exist` などのコマンドが実行され、 アプリケーションの状態や変数が更新されることがあります。 特に `check_exist` は、ボタンのキャプションに '!!!' を追加する形でファイルの存在状況を反映します。 :param app: tkLauncherApp: アプリケーションのインスタンス。変数の解決やコマンド実行に使用されます。 :param main_menu: str: サブメニューを取得するメインメニューの名前。 :returns: list[dict]: サブメニュー情報のリスト。各辞書は {"idx": int, "task": str, "caption": str} の形式です。 """ # print("") # print("get_submenus: 345 in") # print("{main_menu=}") inf = self.script_inf.get(main_menu, None) if inf: path, fp, line = \ self.find_file_from_menu(app, main_menu, idx = None, task = None, script_files = inf["paths"]) else: path, fp, line = self.find_file_from_menu(app, main_menu, idx = None, task = None) # print(f"{path=} {line=}") if fp is None: return [] fp.close() # print("get_submenus; read", path) fp = open_chardet(path, 'r') # fp = open(path, 'r') if not fp: return [] submenus = [] while 1: line = fp.readline() if not line: break # print(f"369: {line=}") match = re.match(r'\[Button(\d+)\.(.*)\s*]', line) if not match: continue sec = app.p(match.groups()[1]) if sec != main_menu: continue idx = match.groups()[0] match = re.search(r'\]\.(\S+)', line) if match: task = match.groups()[0] else: task = '' caption = 'none' pos = fp.tell() while 1: line = fp.readline() if not line: break # print(f"394: {line=}") line = line.strip() if line == '': continue if line[0] == '[' or (len(line) >= 3 and line[0:3].lower() == 'end'): fp.seek(pos) break cmd, argline, args = split_command_line(line) cmd = cmd.lower() nargs = len(args) app.alert_var_error = False if cmd == 'set' and nargs >= 1: argline, = self.convert_vars(app, [argline]) app.set(argline) elif cmd == 'join_path': args = self.convert_vars(app, args) app.join_path(args) elif cmd == 'use_os_path_sep' and nargs >= 1: args = self.convert_vars(app, args) app.use_os_path_sep(args[0]) elif cmd == "check_exist": path, = self.convert_vars(app, [args[0]]) # print(" 416: check_exist: ", line) # print(" ", args) # print(" ", path) status = self.vars.get('check_file_mode', 'auto') # print(f"413: path={path} status={status} exist=", os.path.exists(path)) if status == 'false': caption = '!!! ' + caption elif not os.path.exists(path): # print(f"tkscript_macro.get_submenus(): 417 {path} does not exist") caption = '!!! ' + caption app.alert_var_error = True match = re.match(r'\s*Caption\s*=\s*(.*)\s*$', line, re.IGNORECASE) if not match: continue caption = match.groups()[0] # break submenus.append({ "idx": pint(idx), "task": task, "caption": caption }) fp.close() # print("341: submenus=", submenus) return submenus
[ドキュメント] def update_script_vars(self, app, cparams): """ 概要: アプリケーション変数と環境変数をスクリプトエンジンにロードする。 詳細説明: `app.svars` からパラメーター辞書を取得し、これに環境変数やアプリケーション固有のパス情報を追加します。 また、コマンドライン引数も `$0`, `$1`, ..., `$a` の形で変数として利用可能にします。 これらの変数は、後続のスクリプトコマンドで参照できるように `self.vars` にすべて小文字のキーで格納されます。 :param app: tkLauncherApp: アプリケーションのインスタンス。各種変数情報を提供します。 :param cparams: tkParams: アプリケーションの構成パラメータ。 :returns: dict: 更新されたスクリプト変数辞書 (self.vars)。 """ svars = app.svars vars = svars.get_param_dict() env = app.env argv = app.argv vars.update(env) keys = ["os_name", "tkProg_Root", "tklib_Root", "pythonlib_path", "perllib_path", "tkprog_path", "tkprog_X_path", "internal_editor_path", "tkapp_path", "tkapp_open_path", "tkapp_etc_path", "script_dir", "group", "user", "user_nospace" ] # "python_path", "perl_path"] for key in keys: vars[key] = app.get(key, None) keys = ["script_list_name", "script_list_path"] for key in keys: vars[key] = cparams.get(key, None) vars['p'] = app.script_path vars['i'] = app.inifile vars['s'] = os.getcwd() vars['n'] = '\n' s = '' for i in range(len(argv)): vars[f"{i}"] = argv[i] s += argv[i] + ' ' vars['a'] = s.strip() self.vars = {} for key in vars.keys(): skey = key.lower() self.vars[skey] = vars[key] # print(f"{self.vars=}") return self.vars
[ドキュメント] def show_message(self, app, message): """ 概要: 指定されたメッセージをアプリケーションのメッセージ表示領域に表示する。 詳細説明: アプリケーションのGUIにメッセージを表示するために、`app` インスタンスの `show_message` メソッドを呼び出します。 これは、スクリプト実行中にユーザーへの通知やデバッグ情報を提供するのに使用されます。 :param app: tkLauncherApp: アプリケーションのインスタンス。メッセージ表示機能を提供します。 :param message: str: 表示するメッセージ文字列。 :returns: None """ app.show_message(message)
[ドキュメント] def convert_a_var(self, app, var): r""" 概要: 単一の文字列に含まれる変数を実際の値に置換する。 詳細説明: 入力文字列 `var` 内の `$varname` または `$(varname)` 形式の変数を、 `app.s_engine.vars` に格納されている対応する値に置換します。 バックスラッシュでエスケープされたドル記号 `\$` はそのまま `$ ` として扱われます。 未定義の変数が存在し、`app.alert_var_error` がTrueの場合、エラーダイアログが表示されます。 :param app: tkLauncherApp: アプリケーションのインスタンス。変数辞書とエラー表示機能を提供します。 :param var: str: 変数置換を行う対象の文字列。 :returns: str: 変数が置換された後の文字列。 """ idebug = 0 vars = app.s_engine.vars ret = '' rest = var # print("") # print(f"499 var=[{var}] rest=[{rest}]") while 1: # Separate by '$' # print(f"509: rest=[{rest}] ret=[{ret}]") m = re.match(r'(.*?)\$(.*)$', rest, flags = re.MULTILINE | re.DOTALL) if not m: ret = ret + rest # print(f"512 hit (.*?)$(.*) return ret=[{ret}]") return ret g = m.groups() pre = g[0] n = len(pre) rest = g[1] if idebug >= 2: print(f" 401: pre={pre} rest={rest}") # print(f"528 pre=[{pre}] rest=[{rest}] ret=[{ret}] n={n}") if n > 0 and pre[n-1] == '\\': # for '\$' => '$' ret += pre[0:n-1] + '$' continue elif rest == '': # for '$ ' => '$ ' ret += pre + '$' elif len(rest) == 1: # for '$x' => '[x]' if $x exists, but => '$x' if not val = vars.get(rest, '$' + rest) # rest was processed so should be replaced with '' rest = '' ret += pre + val # Case rest = 'abc\$def', should be converted to 'abc$def' else: # print(f"ret, pre, rest=[{ret}] [{pre}] [{rest}]") ret += pre # Case rest = 'abc$def', should be converted to 'abc$def' m = re.match(r'(\w\w+.*)$', rest) if m: # print(f"{ret=} {rest=}", m.groups()) ret += '$' continue # Case rest = 'abc$i def', should be converted to 'abc[ini_path] def' m = re.match(r'([a-zA-Z])([^\w_].*)?$', rest, flags = re.MULTILINE | re.DOTALL) if m: g = m.groups() n = len(g) varname = g[0].lower() rest = g[1] if rest is None: rest = '' # print(f"545 hit abc$i def for varname={varname} rest={rest}") val = vars.get(varname, None) # print(f" 413:{var=} {val=}") if val is None and len(varname) > 1: if app.alert_var_error: app.show_error_dialog(f"tkscript_macro.convert_a_var(): Invalid varname [${varname}]") val = f"??{varname}??" # else: # val = '$' + varname if val is not None: ret += val continue # Case rest = 'abc$(tkProgRoot)def', should be converted to 'abc[tkProgRoot]def' m = re.match(r'\(([\w\.\:\_\-]+)\)(.*)$', rest, flags = re.MULTILINE | re.DOTALL) if m: # print("566 hit abc$(cdf)") g = m.groups() varname = g[0].lower() rest = g[1] val = vars.get(varname, None) # print(f"572 varname=[{varname}] val=", val) if val is None and len(varname) > 1: if app.alert_var_error: app.show_error_dialog(f"tkscript_macro.convert_a_var(): Invalid varname [${varname}]") val = f"??{varname}??" elif val is None: val = f"??{varname}??" if type(val) is int or type(val) is float: ret += f"{val}" else: ret += val continue return ret
[ドキュメント] def convert_vars(self, app, vars): """ 概要: 文字列のリストに含まれるすべての変数を実際の値に置換する。 詳細説明: 与えられた文字列のリスト `vars` の各要素に対して `convert_a_var` メソッドを適用し、 含まれる変数表記(例: `$var`, `$(var)`) を対応する値で置換します。 このメソッドは、コマンドの引数を実行前に前処理するために使用されます。 :param app: tkLauncherApp: アプリケーションのインスタンス。変数置換機能を提供します。 :param vars: list[str]: 変数置換を行う対象の文字列のリスト。 :returns: list[str]: 変数が置換された後の文字列のリスト。 """ # print("") # print("") # print(f"607: convertvars: vars=[{vars}]") for i in range(len(vars)): # print(f"**594 i={i}: [{vars[i]}]") vars[i] = self.convert_a_var(app, vars[i]) # print("**596 ", vars[i]) return vars
[ドキュメント] def execute_external_command(self, app, cmd, args, is_print = False): """ 概要: 外部コマンドを実行する。 詳細説明: 指定されたコマンドと引数を使用して外部プログラムを実行します。 コマンドがPython (.py) または Perl (.pl) スクリプトの場合、 それぞれのインタープリタのパスを前置して実行します。 コマンドライン引数は適切に引用符で囲まれ、`os.system()` を使用して同期的に実行されます。 デバッグモードの場合や `is_print` がTrueの場合、実行されるコマンドラインが表示されます。 実行前にユーザーに確認を求めるダイアログが表示されることもあります。 :param app: tkLauncherApp: アプリケーションのインスタンス。変数、設定、GUI機能を提供します。 :param cmd: str: 実行するコマンド。 :param args: list[str]: コマンドに渡す引数のリスト。 :param is_print: bool, optional: 実行するコマンドラインをコンソールに表示するかどうか。デフォルトはFalse。 :returns: int: コマンドの終了コード。エラーが発生した場合は -1 を返します。 """ # print(f"487 {cmd=} {args=}") vars = app.s_engine.vars python_path = quote_command_if_space(vars.get("python_path", "python"), special_chars = "|><") perl_path = quote_command_if_space(vars.get("perl_path", "perl"), special_chars = "|><") cmd0 = python_path #実行コマンド文字列の作成 cmd = quote_command_if_space(cmd, special_chars = "|><") #引数を連結 command_line = '' argline = '' if re.search(r"\.py[\"\']?$", cmd): #cmdがpythonスクリプトの場合、python_pathを追加 command_line += python_path + ' ' + cmd elif re.search(r"\.pl[\"\']?$", cmd): #cmdがperlスクリプトの場合、perl_pathを追加 command_line += perl_path + ' ' + cmd else: #その他 command_line += quote_command_if_space(cmd, special_chars = "|><*?+") # f'"{cmd}"' # command_line += quote_command_if_space(cmd, special_chars = "|><") # f'"{cmd}"' cmd0 = quote_command_if_space(cmd, special_chars = "|><") # print(f"519 cmd={cmd}") # print(f"520 command_line={command_line}") for p in args: # print(f"525 p={p}") if 'start' in cmd: p = quote_command_if_space(p, special_chars = "|><&") else: p = quote_command_if_space(p, special_chars = "|><") # print(f"526 p={p}") command_line += ' ' + p argline += ' ' + p # print(f"526 command_line={command_line}") command_line = command_line.strip() argline = argline.strip() app.set_command_line(command_line) # print(f"529 command_line={command_line}") if is_print: print(f"524 command_line={command_line}]") # print(f"{cmd0=}") # print(f"{argline=}") # print(f"528 command_line={command_line}]") if app.confirm(): idlg = tktkinter.tkInputDialog(master = app.root_window, app = app, title = "Execute?", message = f"Execute this command?", width = 50, def_val = command_line, is_print = False) if not idlg.ret: print("tkLauncherApp.execute_external_command(): Cancelled") return -1 command_line = idlg.ret # 最初に os.system() をためす。同期実行 if app.config.debug or is_print: print(f"** Execute os.system({command_line})") ret = os.system(command_line) self.proc = None if ret == 0: if app.config.debug or is_print: print(f"os.system succeeded (ret={ret}) cmd=[{command_line}]") return ret else: if app.config.debug or is_print: app.print_warning("") app.print_warning(f"Error in tkscript_macro.execute_external_command(): os.system faieled (ret={ret}) cmd=[{command_line}]") app.print_warning("") #非同期実行は管理しない。startコマンドを使う """ # Popenを試す。非同期実行 ret = 1 try: cmd0 = del_quote(cmd0) # print(f"Popen: cmd0={cmd0} argline={argline}") if argline is None or argline == '': self.proc = subprocess.Popen([cmd0]) else: self.proc = subprocess.Popen([cmd0, argline]) ret = self.proc except: # Popenのあとsystemを試したりもしていた # print(f"system: command_line={command_line}") ret = os.system(command_line) >> 8 self.proc = None """ # print("ret=", ret) return ret
[ドキュメント] def execute_a_command(self, app, cparams, line, fp = None, event = None): """ 概要: スクリプトの1行のコマンドを実行する。 詳細説明: 指定されたスクリプト行 `line` を解析し、対応する内部コマンドまたは外部コマンドを実行します。 コマンドは `cmd arg1 arg2 ...` の形式で、変数が含まれる場合は実行前に置換されます。 サポートされる主な内部コマンドは以下の通りです。 - `rem` / `#` / `:` / `;`: コメント行として無視されます。 - `bye`: アプリケーションを終了します。 - `end`: 現在のスクリプトセクションの実行を終了します。 - `call menu.idx.action`: 別のスクリプトセクションを呼び出して実行します。 - `debug on/off`: デバッグモードのオン/オフを切り替えます。 - `confirm on/off`: 外部コマンド実行前の確認ダイアログのオン/オフを切り替えます。 - `load_menu`: メニューを再ロードします。 - `read_ini`, `write_ini`, `read_ini_all`, `read_ini_to_vars`/`load_dotenv`: INIファイル操作。 - `print_all`, `echo`, `print`: メッセージ出力。 - `get_cur_dir`, `chdir`/`cd`, `mkdir`/`md`, `rmdir`/`rd`, `copy`/`cp`, `move`/`mv`, `delete`/`del`/`rm`: ファイル・ディレクトリ操作。 - `get_cur_menu`, `get_cur_menu_file`, `get_cur_button_idx`, `get_cur_button_caption`: 現在のメニュー情報取得。 - `split_str`: 文字列の分割。 - `show_window`, `set_title`: ウィンドウ操作。 - `save_config`: 設定の保存。 - `del_quote`, `remove_comment`, `get_first_word`, `get_last_word`: 文字列処理。 - `set`, `list_append`, `list_append_if_exist`, `set_if_blank`, `set_if_not_blank`, `set_if_null`, `set_if_not_null`, `set_path`, `join_path`: 変数操作。 - `use_os_path_sep`, `get_drive`, `get_directory`, `get_directory_without_drive`, `get_last_directory`, `get_upper_directory`, `get_filename`, `get_filebody`, `get_ext`: パス操作。 - `get_open_file_name`, `get_save_file_name`, `get_open_dir_name`, `choose_if_not_exist`: ファイル/ディレクトリ選択ダイアログ。 - `search_files`, `search_latest_file`: ファイル検索。 - `escape_reg`: 正規表現エスケープ。 - `replace`: 文字列置換。 - `read_labels`: ラベルの読み込み。 - `get_file_list`, `get_dir_list`: ファイル/ディレクトリリストの取得。 - `set_dialog_values`, `set_dialog_var`, `set_file_list`: ダイアログ値の設定。 - `setup`, `copy_config2scars`, `copy_svars2config`: 設定同期。 - `color_dialog`, `font_dialog`, `input`, `select_dialog`: ダイアログ表示。 - `eval`: 式の評価。 - `add_tooltip`, `create_menu`, `add_context_menu`, `show_context_menu`: GUI要素操作。 - `new_dialog`, `add_dialog`, `custom_dialog`, `custom_dialog_modeless`, `message_dialog`, `ask_yesno_dialog`, `ask_okcancel_dialog`: カスタムダイアログ操作。 - `set_message`, `set_selected_file`, `set_command_line`, `set_argument`, `add_path`: アプリケーション状態設定。 - `get_app_path`: アプリケーションパス取得。 - `check_file_mode`, `check_exist`, `exit_if_defined`, `exit_if_not_defined`, `exit_if_exist`, `exit_if_not_exist`: 条件付き終了。 - `wait_process`: 外部プロセスの終了を待機。 上記以外のコマンドは `execute_external_command` を介して外部プロセスとして実行されます。 :param app: tkLauncherApp: アプリケーションのインスタンス。各種機能を提供します。 :param cparams: tkParams: アプリケーションの構成パラメータ。 :param line: str: 実行するスクリプトの1行。 :param fp: file object | None, optional: 現在読み込み中のスクリプトファイルのファイルポインタ。`call` コマンドで再帰呼び出しする際に使用されます。デフォルトはNone。 :param event: tkinter.Event | None, optional: GUIイベントオブジェクト。`show_context_menu` などGUI関連コマンドで使用されます。デフォルトはNone。 :returns: int: コマンドの実行結果。1は成功、0以下はエラーまたは終了を意味します。-1はエラー、または `end` コマンドによるセクション終了を示します。 """ # print("vars=", self.vars) # print(f"681: [{line}]") line = line.strip() if line == '': return 1 if line[0] in '#:;': return 1 # if app.config.debug: # print(f" cmd_line_org: [{line}]") line_org = line # print(f"706 line=[{line}]") line, = self.convert_vars(app, [line]) # print(f"696 line=[{line}]") # if app.config.debug: # print(f" cmd_line_conv: [{line}]") cmd, argline, args = split_command_line(line) # print(f"701 cmd: {cmd=} {argline=} {args=}") # if app.config.debug: # print(f"586 cmd: [{cmd}] [{argline}]:", args) # print(f"705 cmd: [{cmd}] [{argline}]:", args) # cmd, argline = self.convert_vars(app, [cmd, argline]) # print(f"720 args=", args) args = self.convert_vars(app, args) # print(f"707 args=", args) cmd_org = cmd cmd = cmd.lower() nargs = len(args) # app.set_command_line(line) def check_nargs(cmd, key, nargs, min_nargs): if cmd != key: return False if nargs < min_nargs: app.print_warning("") app.print_warning(f"Error: cmd={key} has {nargs} argments but {min_nargs} args are required") app.print_warning("") return False return True ret = 1 if app.config.debug: print(f" cmd_conv: cmd=[{cmd}]") print(f" argline=[{argline}]:") for i in range(len(args)): print(f" args[{i}]: [{args[i]}]") # print(f"730 cmd=[{cmd}]") if cmd == 'rem': return 1 elif re.match(r'\s*caption\s*=', cmd): pass elif cmd == 'bye': # app.terminate("Terminated by command BYE") app.on_closing(do_confirm = False) elif cmd == 'end': return -1 elif cmd == 'call' and nargs >= 1: try: fp.close() fp = None except: pass menu, idx, action = self.decompose_section(args[0]) if idx is None or idx == "": idxm1 = None else: idxm1 = pint(idx) - 1 # ret = self.execute(app, cparams, menu, idxm1, action = action, i_mouse_button = '1') ret = self.execute(app, cparams, menu, idxm1, action = action, i_mouse_button = '1', script_files = self.script_files) if ret is not None and (type(ret) is int and ret <= 0): return ret elif cmd == 'debug' and nargs >= 1: if args[0].lower() == 'on': app.set_debug(True) else: app.set_debug(False) elif cmd == 'confirm' and nargs >= 1: if args[0].lower() == 'on': app.set_confirm(True) else: app.set_confirm(False) elif cmd == 'load_menu': app.call('none', 'initialize_launcher_menus', app, cparams) elif check_nargs(cmd, 'read_ini', nargs, 4): # elif cmd == 'read_ini' and nargs >= 4: app.read_ini(args) elif check_nargs(cmd, 'write_ini', nargs, 4): # elif cmd == 'write_ini' and nargs >= 4: app.write_ini(args) elif cmd == 'read_ini_all' and nargs >= 2: app.read_ini_all(args) elif (cmd == 'read_ini_to_vars' or cmd == 'load_dotenv') and nargs >= 1: app.read_ini_to_vars(args) elif cmd == 'print_all' and nargs >= 1: app.print_all(args) elif cmd == 'get_cur_dir' and nargs >= 1: cwd = os.getcwd() app.set_var(args[0], cwd) elif (cmd == 'chdir' or cmd == 'cd') and nargs >= 1: if os.path.isdir(args[0]): os.chdir(args[0]) elif (cmd == 'mkdir' or cmd == 'md') and nargs >= 1: for d in args: print(f"*** mkdir {d}") os.mkdir(d) elif (cmd == 'rmdir' or cmd == 'rd') and nargs >= 1: for d in args: print(f"*** rm {d}") os.rmdir(d) elif (cmd == 'copy' or cmd == 'cp') and nargs >= 2: target = args[nargs-1] for fm in args[:nargs-1]: for f in glob.glob(fm): print(f"*** copy {f} to {target}") shutil.copy2(f, target) elif (cmd == 'move' or cmd == 'mv') and nargs >= 2: target = args[nargs-1] for fm in args[:nargs-1]: for f in glob.glob(fm): print(f"*** move {f} to {target}") shutil.move(f, target) elif (cmd == 'delete' or cmd == 'del' or cmd == 'rm') and nargs >= 1: for fm in args: for f in glob.glob(fm): print(f"*** delete {f}") os.remove(f) elif check_nargs(cmd, 'echo', nargs, 1): app.echo(argline) elif check_nargs(cmd, 'print', nargs, 1): app.echo(argline) elif cmd == "get_cur_menu" and nargs >= 1: app.get_cur_menu(args[0]) elif cmd == "get_cur_menu_file" and nargs >= 1: app.get_cur_menu_file(args[0]) elif cmd == "get_cur_button_idx" and nargs >= 1: app.get_cur_button_idx(args[0]) elif cmd == "get_cur_button_caption" and nargs >= 1: app.get_cur_button_caption(args[0]) elif cmd == "split_str" and nargs >= 4: app.split_str(args) elif cmd == "show_window" and nargs >= 1: app.show_window(args[0].lower()) elif cmd == "set_title" and nargs >= 1: app.set_window_title(argline) elif cmd == "save_config": app.save_config(argline) elif cmd == "del_quote" and nargs >= 2: app.del_quote(args[0]) elif cmd == "remove_comment" and nargs >= 1: app.remove_comment(args[0]) elif cmd == "get_first_word" and nargs >= 3: app.get_first_word(args) elif cmd == "get_last_word" and nargs >= 3: app.get_last_word(args) elif check_nargs(cmd, 'set', nargs, 1): app.set(argline) elif check_nargs(cmd, 'list_append', nargs, 2): app.list_append(args) elif check_nargs(cmd, 'list_append_if_exist', nargs, 2): app.list_append(args, check_exist = True) elif cmd == "set_if_blank" and nargs >= 1: app.set_if_blank(argline) elif cmd == "set_if_not_blank" and nargs >= 1: app.set_if_not_blank(argline) elif cmd == "set_if_null" and nargs >= 1: app.set_if_null(argline) elif cmd == "set_if_not_null" and nargs >= 1: app.set_if_not_null(argline) elif cmd == "set_path" and nargs >= 1: app.set_path(argline) elif check_nargs(cmd, 'join_path', nargs, 3): app.join_path(args) elif cmd == 'use_os_path_sep' and nargs >= 1: app.use_os_path_sep(args[0]) elif cmd == 'get_drive' and nargs >= 2: app.get_path_part('drive', args[0], args[1]) elif cmd == 'get_directory' and nargs >= 2: app.get_path_part('dir', args[0], args[1]) elif cmd == 'get_directory_without_drive' and nargs >= 2: app.get_path_part('dir_without_drive', args[0], args[1]) elif cmd == 'get_last_directory' and nargs >= 2: app.get_path_part('last_dir', args[0], args[1]) elif cmd == 'get_upper_directory' and nargs >= 2: app.get_path_part('upper_dir', args[0], args[1]) elif cmd == 'get_filename' and nargs >= 2: app.get_path_part('filename', args[0], args[1]) elif cmd == 'get_filebody' and nargs >= 2: app.get_path_part('filebody', args[0], args[1]) elif cmd == 'get_ext' and nargs >= 2: app.get_path_part('ext', args[0], args[1]) elif cmd == 'get_cur_dir' and nargs >= 1: app.get_cur_dir(args[0]) elif cmd == "get_open_file_name": if app.get_open_file_name(args) <= 0: return -1 elif cmd == "get_open_file_name_continue": app.get_open_file_name(args, False) elif cmd == "get_save_file_name": if app.get_save_file_name(args) <= 0: return -1 elif cmd == "get_save_file_name_continue": app.get_save_file_name(args, False) elif cmd == "get_open_dir_name": if app.get_open_dir_name(args) <= 0: return -1 elif cmd == "get_open_dir_name_continue": app.get_open_dir_name(args, False) elif check_nargs(cmd, 'choose_if_not_exist', nargs, 2): if app.choose_if_not_exist(args) <= 0: return -1 elif check_nargs(cmd, 'search_files', nargs, 3): app.search_files(args) elif check_nargs(cmd, 'search_latest_file', nargs, 3): app.search_latest_file(args) elif check_nargs(cmd, 'escape_reg', nargs, 2): app.escape_reg(args) elif check_nargs(cmd, 'replace', nargs, 4): app.replace(args) elif cmd == "read_labels" and nargs >= 2: ret = app.read_labels(args) if type(ret) is int and ret <= 0: return -1 elif cmd == "get_file_list" and nargs >= 1: app.get_file_list(args, target = 'file') elif cmd == "get_dir_list" and nargs >= 1: app.get_file_list(args, target = 'dir') elif cmd == "set_dialog_values" and nargs >= 2: ret = app.set_dialog_values(args) if type(ret) is int and ret <= 0: return -1 elif cmd == "set_dialog_var" and nargs >= 2: app.set_dialog_var(args) elif cmd == "set_file_list" and nargs >= 1: app.get_file_list(args) elif cmd == 'setup': app.setup(args) elif cmd == 'copy_config2scars': app.copy_config2scars(args) elif cmd == 'copy_svars2config': app.copy_svars2config(args) elif cmd == 'color_dialog' and nargs >= 1: ret = app.show_color_dialog(args) if ret <= 0: return -1 elif cmd == 'font_dialog' and nargs >= 1: ret = app.show_font_dialog(args) if ret <= 0: return -1 elif cmd == "input": ret = app.input(args) if ret <= 0: return -1 elif cmd == 'select_dialog' and nargs >= 1: ret = app.show_select_dialog(args) # print("ret=", ret) if ret <= 0: return -1 elif check_nargs(cmd, 'eval', nargs, 2): app.eval(args) elif check_nargs(cmd, 'add_tooltip', nargs, 2): app.add_tooltip(args) elif check_nargs(cmd, 'create_menu', nargs, 3): app.create_menu(args, self, cparams, fp) elif check_nargs(cmd, 'add_context_menu', nargs, 2): app.add_context_menu(args) elif check_nargs(cmd, 'show_context_menu', nargs, 1): app.show_context_menu(args, event) elif cmd == 'new_dialog': app.new_dialog() elif cmd == 'add_dialog' and nargs >= 1: app.add_dialog(args) elif cmd == 'custom_dialog' and nargs >= 1: ret = app.show_custom_dialog(args) if ret <= 0: return -1 elif cmd == 'custom_dialog_modeless' and nargs >= 1: ret = app.show_custom_dialog_modeless(args) if ret <= 0: return -1 elif cmd == 'message_dialog' and nargs >= 1: app.show_message_dialog(argline) elif cmd == 'ask_yesno_dialog' and nargs >= 1: ret = app.ask_yesno_dialog(argline) # print("ret=", ret) if not ret: print("Cancelled") return -1 elif cmd == 'ask_okcancel_dialog' and nargs >= 1: ret = app.ask_okcancel_dialog(argline) # print("ret=", ret) if not ret: print("Cancelled") return -1 elif cmd == 'set_message' and nargs >= 1: app.set_message(argline) elif cmd == 'set_selected_file' and nargs >= 1: app.set_selected_file(argline) elif cmd == 'set_command_line' and nargs >= 1: app.set_command_line(argline) elif cmd == 'set_argument' and nargs >= 1: app.set_argument(argline) elif cmd == 'add_path' and nargs >= 1: app.add_path(args) elif check_nargs(cmd, 'get_app_path', nargs, 3): ret = app.get_app_path(args) if not ret: print(f"tkscript_macro.execute_a_command(): File select cancelled") return -1 elif cmd == 'check_file_mode' and nargs >= 1: app.check_file_mode(args) elif cmd == 'check_exist' and nargs >= 1: ret = app.check_exist(args) if not ret: return -1 elif cmd == 'exit_if_defined' and nargs >= 1: if not app.exit_if_defined(True, args[0]): return -1 elif cmd == 'exit_if_not_defined' and nargs >= 1: if not app.exit_if_defined(False, args[0]): return -1 elif cmd == 'exit_if_defined' and nargs >= 1: if not app.exit_if_defined(True, args[0]): return -1 elif cmd == 'exit_if_not_exist' and nargs >= 1: if not app.exit_if_exist(False, args[0]): return -1 elif cmd == 'exit_if_exist' and nargs >= 1: if not app.exit_if_exist(True, args[0]): return -1 elif cmd == 'exit_if_not_exist' and nargs >= 1: if not app.exit_if_exist(False, args[0]): return -1 elif cmd == 'wait_process' and self.proc: while True: ret = self.proc.poll() if ret is None: pass # print("waiting for finish") else: break else: # print(f"981: [{cmd_org}] ", args) app.set_command_line_org(line_org) ret = self.execute_external_command(app, cmd_org, args) # print("ret=", ret) if type(ret) is int and ret < 0: app.print_warning("") app.print_warning(f"Error in tkscript_macro.execute_external_command(): Could not execute {cmd_org}") return -1 return 1
# idx: Button number in this program starting from 0 # idx1: Button number in the script file starting from 1
[ドキュメント] def execute(self, app, cparams, menu, idx, action, i_mouse_button, script_files = None, event = None, max_num = None): """ 概要: 指定されたメニュー/ボタンセクションに関連付けられたスクリプトを実行する。 詳細説明: `menu`、`idx`(0から始まるボタンインデックス)、および `action` に基づいて、 適切なスクリプトセクションを検索し、そのセクション内のコマンドを順次実行します。 実行中、各行は `execute_a_command` によって処理され、内部コマンドまたは外部コマンドとしてディスパッチされます。 `OnErrorAction` の設定に応じてエラー時の挙動が変化します。 :param app: tkLauncherApp: アプリケーションのインスタンス。各種機能、変数、状態を提供します。 :param cparams: tkParams: アプリケーションの構成パラメータ。 :param menu: str: 実行対象のメインメニュー名。 :param idx: int | None: 実行対象のボタンの0から始まるインデックス。メインメニュー自体を実行する場合はNone。 :param action: str: 実行対象のアクション名(例: 'help', 'dbl_click')。アクションがない場合は空文字列。 :param i_mouse_button: str | None: マウスボタンのインデックス(例: '1')。ログ出力にのみ使用されます。 :param script_files: list[str] | None, optional: 検索対象となるスクリプトファイルのパスのリスト。指定しない場合はself.script_filesまたはself.script_infのパスを使用します。 :param event: tkinter.Event | None, optional: GUIイベントオブジェクト。特定のコマンドで使用されます。デフォルトはNone。 :param max_num: int | None, optional: 実行するコマンドの最大数(デバッグ用)。このメソッドでは現在使用されていません。デフォルトはNone。 :returns: int: 実行結果。1は成功、0以下はエラーまたは終了を意味します。-1はエラー、または `end` コマンドによるセクション終了を示します。 """ if script_files is None: inf = self.script_inf.get(menu, None) if inf: script_files = inf["paths"] # print("path=", path) else: script_files = self.script_files # print("null path") if idx is not None: idx1 = idx + 1 else: idx1 = None if idx is None: section = f"[{menu}]" elif action == '': section = f"[Button{idx1}.{menu}]" else: section = f"[Button{idx1}.{menu}].{action}" print("") if i_mouse_button is None: app.print_warning(f"Execute '{section}'") # app.print_warning(f"Execute '{section}' in {script_files}") else: if action is None: app.print_warning(f"Execute '{section}' by click mouse button #{i_mouse_button}") # app.print_warning(f"Execute '{section}' in {script_files} by click mouse button #{i_mouse_button}") else: app.print_warning(f"Execute '{section}.{action}' by click mouse button #{i_mouse_button}") # app.print_warning(f"Execute '{section}.{action}' in {script_files} by click mouse button #{i_mouse_button}") self.OnErrorAction = 'stop' app.update_script_vars(cparams) self.update_script_vars(app, cparams) # ENV{'ExecutingSection'} = $section; # print("menu=", menu, idx1, action) # print("script_files=", script_files) path, fp, line = self.find_file_from_menu(app, menu, idx = idx1, task = action, script_files = script_files) if fp is None: if action == '': action = 'default' app.print_warning(f"Warning in tkscript_macro.execute(): Can not find file for {menu} Button#{idx1} for {action}") return -1 app.print_warning(f" in file [{path}]") match = re.search(r'\[Button\d+\.(.*)\]', line) if match: section = match.groups()[0] print(f" '{section}' is found in [{path}]") ret = 1 while 1: line = self.read_cont_lines(fp) if line == '' or line[0] in '#:;': continue if line.lower() == 'bye' or re.match(r'Bye[\s\r]', line, re.IGNORECASE): app.terminate() # print(f"1052: execute [{line}]") ret = self.execute_a_command(app, cparams, line, fp, event = event) if ret <= 0: # END command break if ret < 0: if self.OnErrorAction == 'continue': continue break # print(" cmd:", line) fp.close() return ret