WindowsのコマンドプロンプトをPythonで拡張するwxPythonアプリケーションを作った。
Linuxシェルでも動くかもしれないが、試していない。
pythonスクリプトと、シェルコマンドが使える。コマンドを入力すると、
最初に指定ディレクトリ内のpythonスクリプトを検索し、存在しない場合は
シェルコマンドとして実行する。
まだcdコマンドしか作っていないので、正しく動作するかどうか。
いつかちゃんと作って、タブ機能も実装しよう。
多少修正した。
下記のページを参考に文字コード取得メソッドを作らせていただいた。
http://www.freia.jp/taka/blog/571
pyrompt.py
# coding: utf-8 import os import re import sys import subprocess import wx class Pyrompt(wx.Frame): LEFT_PROMPT_END = ">" KEY_CTRL = -96 def __init__(self, parent, id, title): """初期化処理""" # 画面初期化 wx.Frame.__init__(self, parent, -1, title, size = (500, 400)) box = wx.BoxSizer(wx.VERTICAL) # メニューバー作成 menu_bar = wx.MenuBar() file_menu = wx.Menu() quit_menuitem = wx.MenuItem(file_menu, 105, '&Quit', 'Quit Pyrompt') file_menu.AppendItem(quit_menuitem) menu_bar.Append(file_menu, "&File") self.SetMenuBar(menu_bar) # タブ作成 notebook = wx.Notebook(self, -1, style=wx.RIGHT) tab1 = wx.Panel(notebook, -1) tab1.SetFocus() notebook.AddPage(tab1, 'Prompt1') box.Add(notebook, 1, wx.EXPAND) # プロンプト作成 tab_box = wx.BoxSizer(wx.VERTICAL) self.view = wx.TextCtrl(tab1, -1, '', size=(-1, -1), style=wx.TE_MULTILINE | wx.TE_PROCESS_ENTER | wx.TE_CHARWRAP) self.view.SetEditable(False) tab_box.Add(self.view, 1, wx.EXPAND) tab1.SetSizer(tab_box) # イベント設定 self.Bind(wx.EVT_CLOSE, self.QuitApplication) self.Bind(wx.EVT_MENU, self.QuitApplication, id=105) self.view.Bind(wx.EVT_CHAR, self.OnTextEnter) self.Centre() self.Show(True) self.view.SetValue(self.get_left_prompt()) self.current_line_number = 0 self.start_position = len(self.LEFT_PROMPT_END) # コマンドロード self.load_commands() # ホームディレクトリに移動 os.chdir(wx.GetHomeDir()) def OnTextEnter(self, event): """テキスト入力イベント""" keycode = event.GetKeyCode() view = event.GetEventObject() mousePoint = view.GetInsertionPoint() (x,y) = view.PositionToXY(mousePoint) if keycode in (wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER): end_position = view.GetInsertionPoint() command = view.GetRange(self.start_position, end_position) view.AppendText("\n") self.execute_command(command) view.AppendText(self.get_left_prompt()) (x,y) = view.PositionToXY(view.GetInsertionPoint()) self.current_line_number = y self.start_position = view.GetInsertionPoint() else: if keycode in range(32,127): # visible characters view.WriteText(chr(keycode)) elif keycode == wx.WXK_BACK: # backspace if self.current_line_number < y or len(self.get_left_prompt()) < x: view.Remove(mousePoint-1,mousePoint) elif keycode == wx.WXK_DELETE: # delete view.Remove(mousePoint,mousePoint+1) elif keycode == wx.WXK_LEFT: # left key if self.current_line_number < y or len(self.get_left_prompt()) < x: view.SetInsertionPoint(view.XYToPosition(x-1,y)) elif keycode == wx.WXK_RIGHT: # right key line_length = view.GetLineLength(y) if x < line_length: view.SetInsertionPoint(view.XYToPosition(x+1,y)) elif keycode == wx.WXK_UP: # up key pass elif keycode == wx.WXK_DOWN: # down key pass elif keycode == wx.WXK_HOME: # home key if y == self.current_line_number: view.SetInsertionPoint(view.XYToPosition(len(self.get_left_prompt()), y)) else: view.SetInsertionPoint(view.XYToPosition(0, y)) elif keycode == wx.WXK_END: # end key view.SetInsertionPoint(view.XYToPosition(view.GetLineLength(y), y)) elif keycode in range(1,27): # Ctrl+a-z pass def execute_command(self, statement): """pythonスクリプトコマンドを実行""" (command, arg_list) = self.split_statement(statement) if command: if self.command_list.has_key(command): (res, message) =self.command_list[command].execute(self, arg_list) self.view.AppendText(message) self.view.AppendText("\n") else: res = self.execute_oscommand(statement) self.view.AppendText("RETURN CODE: %d" % (res,)) self.view.AppendText("\n") def execute_oscommand(self, command): """OSコマンドを実行""" p = subprocess.Popen(command, shell=True, cwd=os.getcwd(), stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) (stdouterr, stdin) = (p.stdout, p.stdin) while True: line = stdouterr.readline() if not line: break line_data = line.rstrip() self.view.AppendText(unicode(line_data, self.get_charset(line_data)).encode("utf-8")) self.view.AppendText("\n") res = p.wait() return res def QuitApplication(self, event): """確認してアプリケーションを終了""" dlg = wx.MessageDialog(self, "Are you sure you want to Exit?", '', wx.YES_NO | wx.YES_DEFAULT | wx.CANCEL | wx.ICON_QUESTION) val = dlg.ShowModal() if val == wx.ID_YES: self.exit_app() elif wx.ID_CANCEL: dlg.Destroy() else: dlg.Destroy() def exit_app(self): """アプリケーションを終了""" wx.Exit() def split_statement(self, statement): """入力をコマンドと引数リストに分割""" statement = statement.strip() command = None arg_list = [] if statement: m = re.search(r"^([a-zA-Z0-9_]+) (.+)", statement) if m: command = m.group(1) arg_list = self.split_args(m.group(2)) else: command = statement return (command, arg_list) def split_args(self, args): """引数をリストに分割。クオートを考慮""" arg_list = [] if args: arg_buffer = "" in_quote = False in_squote = False in_dquote = False for c in args: if c != '"' and c != "'" and c != " ": arg_buffer = arg_buffer + c elif c == " " and in_quote: arg_buffer = arg_buffer + c elif c == " ": arg_list.append(arg_buffer) arg_buffer = "" if c == '"': in_dquote = not in_dquote elif c == "'": in_squote = not in_squote if in_squote or in_dquote: in_quote = True else: in_quote = False if len(arg_buffer) > 0: arg_list.append(arg_buffer) return arg_list def load_commands(self): """pythonコマンドをロード""" try: self.command_list = {} file_name_list = os.listdir(os.path.join(os.path.dirname(os.path.abspath(__file__)), "pybin")) for name in file_name_list: m = re.search(r"([a-zA-Z0-9]+).py", name) if m and name != "__init__.py": cmd_name = m.group(1) self.command_list[cmd_name] = self.import_module("pybin.%s" % cmd_name) except Exception, e: self.message_dlg(e) def import_module(self, name): """モジュールを動的にインポート""" mod = __import__(name) components = name.split('.') for comp in components[1:]: mod = getattr(mod, comp) return mod def get_left_prompt(self): """プロンプトを表示""" return self.LEFT_PROMPT_END def message_dlg(self, message): """デバッグ用ダイアログ表示""" dlg = wx.MessageDialog(self, message, "Debug", wx.OK) dlg.ShowModal() dlg.Destroy() def get_charset(self, data): """文字コードを判別""" f = lambda d, enc: d.decode(enc) and enc try: return f(data, 'utf-8') except: pass try: return f(data, 'shift-jis') except: pass try: return f(data, 'euc-jp') except: pass try: return f(data, 'iso2022-jp') except: pass return None app = wx.App() Pyrompt(None, -1, 'Pyrompt ver.1.0') app.MainLoop()
pybin/cd.py
import os def execute(app, arg_list): if arg_list: if len(arg_list) == 1: dirpath = arg_list[0] if os.path.exists(dirpath): os.chdir(dirpath) message = "Moved to %s" % dirpath return (0,message) else: message = "Directory does not exist: %s" % dirpath return (1,message) else: message = "Incorrect argument: %s" % (",".join(arg_list)) return (1,message) else: message = "Destination dir required." return (1,message)