コマンドの出力をひたすら表示するウィンドウ


Python付属ライブラリに、CGIHTTPServerと言う物があります。

python -m CGIHTTPServer

を、HTMLなどを置いたディレクトリで実行し、

ブラウザで"http://localhost:8000/"を開くと、ローカルでCGIやHTMLをテスト出来るというものです。


大変重宝なのですが、Windowsだと、CGIHTTPServerのためにDOS窓が開くと邪魔です。


かといって、pythonwでDOS窓なしだと、ちょっとさみしい。


そこで、普段はタスクトレイに格納し、必要なときだけCGIHTTPServerの結果を表示するスクリプトを作ってみました。



#iconizebat.pyw
#本体
#encoding:shift-jis
from __future__ import with_statement, division, print_function
from myutil import *

import wx
import yaml

class IconizeFrame(wx.Frame):  
    def __init__(self, parent, cfg):
        self.cfg = copy.copy(cfg)
        wx.Frame.__init__(self, parent)  
          
        # タスクトレイに入れるアイコン  
        self.ico = wx.Icon(cfg.icon, wx.BITMAP_TYPE_ICO)  
        self.tb_ico=wx.TaskBarIcon()  
        self.tb_ico.SetIcon(self.ico, self.cfg.tips)
        
        # タスクトレイアイコンのイベント関連付け  
        self.tb_ico.Bind(wx.EVT_TASKBAR_LEFT_UP,  self.OnTbiLeftUp)  
        self.Bind(wx.EVT_ICONIZE, self.OnIconized)
        self.Bind(wx.EVT_CLOSE, self.OnClose)
    
    def OnIconized(self, evt):  
        self.Hide()
        self.tb_ico.SetIcon(self.ico, self.cfg.tips)
    
    def OnTbiLeftUp(self, evt):  
        self.Iconize(False) # <= 必須!! Very Important  
        self.Show(True)  
        self.Raise()
##        self.tb_ico.RemoveIcon()
    
    def OnClose(self, evt):
        self.Iconize()


class ReadingThread(threading.Thread):
    def __init__(self, istream, estream, write):
        attributesFromDict(locals())
        threading.Thread.__init__(self)
        self.keep_going = True
    
    def stop(self):
        self.keep_going = False
        self.join()
        
    def run(self):
        while self.keep_going:
            if self.istream.CanRead():
                text = self.istream.read()
                self.write(text)
            if self.estream.CanRead():
                text = self.estream.read()
                self.write(text)
            time.sleep(0.15)
    
    
class PopenFrame(IconizeFrame):
    ID_KILL = wx.NewId()
    ID_EXIT = wx.ID_EXIT
    def __init__(self, parent, cfg):
        IconizeFrame.__init__(self, parent, cfg)
        
        title = "[running]{0}".format(self.cfg.title)
        self.SetTitle(title)
        
        style = (
            wx.TE_MULTILINE | 
            wx.TE_READONLY |
            wx.HSCROLL 
        )
        self.logText = wx.TextCtrl(self, style=style)
        sz = wx.BoxSizer()
        sz.Add(self.logText, 1, wx.EXPAND)
        self.SetSizer(sz)
        
        self.menu = wx.Menu()
        self.menu.Append(self.ID_KILL, "&Kill", "Kill")
        self.menu.Append(self.ID_EXIT, "&Exit", "Exit")
        
        self.Bind(wx.EVT_MENU, self.OnMenu)
        
        mbar = wx.MenuBar()
        mbar.Append(self.menu, "File")
        self.SetMenuBar(mbar)
        
        self.tb_ico.Bind(wx.EVT_TASKBAR_RIGHT_UP, self.OnTbiRightUp)
        self.Bind(wx.EVT_END_PROCESS, self.OnProcessEnded)
        
        #プロセス作成
        self.process = wx.Process.Open(
            self.cfg.cmdline,
            wx.EXEC_ASYNC
        )
        if self.cfg.log:
            self.logfp = open(self.cfg.logpath, "w")
        else:
            self.logfp = Null()
        def write(s):
            self.logfp.write(s)
            self.logfp.flush()
            self.logText.AppendText(s)

        self.readingthread = ReadingThread(
            self.process.GetInputStream(),
            self.process.GetErrorStream(),
            write
        )
        self.readingthread.start()
        
        self.Show(self.cfg.show)
    
    def OnTbiRightUp(self, event):
        self.PopupMenu(self.menu)
    
    def ExitMainLoop(self):
        self.tb_ico.RemoveIcon()
        self.logfp.close()
        wx.GetApp().ExitMainLoop()

    def OnMenu(self, event):
        id = event.GetId()
        if id == self.ID_EXIT:
            self.Kill()
            self.ExitMainLoop()
        elif id == self.ID_KILL:
            self.Kill()
    
    def Kill(self):
        if self.process is not None:
            self.process.CloseOutput()
            title = "[killed]{0}".format(self.cfg.title)
            self.SetTitle(title)
            self.readingthread.stop()
    
    def OnProcessEnded(self, event):
        self.readingthread.stop()
        self.process.Destroy()
        self.process = None
        
        ret = event.GetExitCode()
        title = "[finished:{1}]{0}".format(self.cfg.title, ret)
        self.SetTitle(title)
        
        quit = {
            "show" : partial(self.quitWithMessage, False),
            "show_if_error" : partial(self.quitWithMessage, True),
            "stay" : self.stay,
        }[self.cfg.quit_type]
        
        quit(ret)
    
    def quitWithMessage(self, only_if_error, return_code):
        if not only_if_error or return_code!=0:
            self.Show()
            wx.MessageBox(
                "return code={0}".format(return_code), 
                parent=self
            )
        self.ExitMainLoop()
    
    def stay(self, return_code):
        pass
    
def loadcfg(cfgpath):
    path = unicode
    class choice(object):
        def __init__(self, t, values):
            attributesFromDict(locals())
        
        def __call__(self, v):
            v = self.t(v)
            if v not in self.values:
                raise ValueError(v)
            return v
    
    types = dict(
        cmdline = path,
        show = bool,
        icon = path,
        cwd  = path,
        log = bool,
        logpath=path,
        quit_type=choice(unicode, "show show_if_error stay".split()),
        title=unicode,
        tips=unicode
    )
    
    with open(cfgpath) as fp:
        d = yaml.load(fp.read())
    
    r = {}
    for k, t in types.iteritems():
        r[k] = t(d[k])
    return Bunch(**d)
    
    
def main():
    cfgpath = sys.argv[1]
    cfg = loadcfg(cfgpath)
    
    os.chdir(cfg.cwd)
    app=wx.PySimpleApp()
    frame=PopenFrame(None, cfg)
    app.MainLoop()
    
if __name__ == "__main__":
    main()
#CGIHTTPServer.yaml
#設定ファイル

cmdline : 'python -u -m CGIHTTPServer'

show : True

icon : "C:\\Python26\\DLLs\\pyc.ico"

cwd : "d:\\Owner\\My Documents\\WebPage\\dist"

log : False

logpath : "test_log.txt"

quit_type : "show"

title : "CGIHTTPServer"

tips : "CGIHTTPServer"



使用例

python iconizebat.pyw CGIHTTPServer.yaml



最初はCGIHTTPServerのプロセスを、subprocess.Popen
で作ろうとしたのですが、ご覧の通りwx.Processを使っています。


Windows版のsubprocess.Popenではサブプロセスの標準出力を、
リアルタイムでは手に入れられないみたいでした。
サブプロセスが終了すると、一気に入ってくるみたいです。