読者です 読者をやめる 読者になる 読者になる

ほんじゃら堂

めんどくさい仕事をラクにする作業自動化レシピ集

Pythonでコマンドプロンプトを拡張

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)