pyPlugger行数カウントプラグイン ひと段落
今日は行数カウントプラグインの続きを作りました。
下記のソース(ちょっと長めなので、載せるかどうか迷ったんですが、のせちまいました。。)
今日書いたのは、
pyLineCounter()
countTargetLines()
という関です。これらの関数でスクリプトのコメント行数、空白行数、それ以外の行数を
求めるようにしました。
以下の処理を実行させた結果を表示させてみると、こんな感じになりました。
# -*- coding: utf-8 -*- ''' Pythonスクリプトの行数をカウントするプラグイン。 コメントの行数はカウントしない。 ''' import sys import os import pygtk if sys.platform != 'win32': pygtk.require('2.0') import gtk import gobject # # グローバル変数 g_treeModel = None g_dispModel = None g_Tree = None g_result = [] # 行数カウント関数 # def pyLineCounter(pyText): ''' Pythonスクリプトの行数をカウントする。 pyText: 処理対象のテキスト RETURN: 行数カウントの処理結果(タプル) タプルの情報は (ファイルパス文字列, (総行数, スクリプト行数, コメント行数, 空行数)) 総行数 --> スクリプト行数 + コメント行数 + 空行数 スクリプト行数 --> コメント行数、空行数を含まない行数 ''' emptys = 0 comments = 0 lines = pyText.splitlines() docStrBegin = False for ln in lines: lnStr = ln.strip() # 空行 if len(lnStr) == 0: emptys += 1 continue # コメント if lnStr[0] == '#': comments += 1 continue # ドキュメント文字列(開始) s = lnStr[:3] if s == "'''" or s == '"""': docStrBegin = not docStrBegin comments += 1 continue # ドキュメント文字列(終了) if docStrBegin: comments += 1 if "'''" in lnStr or '"""' in lnStr: docStrBegin = not docStrBegin return len(lines), len(lines) - emptys - comments, comments, emptys def countTargetLines(path, acceptExts): ''' 指定されたパスの行数をカウントする。 パスがファイルの場合には、そのファイルの行数をカウント。 パスがディレクトリの場合には、そのディレクトリ中のファイルを対象に 再帰的に処理を行う。 acceptExtsは、ディレクトリ中で処理対象とするファイルの拡張子。 RETURN: タプルのリスト。 それぞれのタプルの情報は、 (ファイルパス文字列, (総行数, スクリプト行数, コメント行数, 空行数)) 総行数 --> スクリプト行数 + コメント行数 + 空行数 スクリプト行数 --> コメント行数、空行数を含まない行数 ''' result = [] if path == None or len(path) == 0: return result # 渡されたパスがファイルパスの場合 if os.path.isfile(path): print '-- file path --' try: f = file(os.path.normpath(path), 'r') txt = f.read() f.close() count = pyLineCounter(txt) result.append( ( path, count ) ) except: pass # 渡されたパスがディレクトリの場合 elif os.path.isdir(path): print '-- directory path --' try: for p in os.listdir(path): ext = os.path.splitext(p)[1] if ext not in acceptExts: continue result += countTargetLines(os.path.join(path, p), acceptExts) except: pass else: pass return result # # ツリービューモデル # class TargetListModel: ''' 処理ターゲットリストのデータモデル ''' def __init__(self, targets=[]): ''' 初期化 ''' self.store = gtk.ListStore(gobject.TYPE_BOOLEAN, gobject.TYPE_STRING, gobject.TYPE_STRING) self.set_targets(targets) def get_model(self): ''' データモデルを取得 ''' return self.store def set_targets(self, targets): ''' 処理対象を追加。 保持しているデータを破棄したあとで追加される。 targets : リスト ''' self.clear_targets() self.append_targets(targets) def append_targets(self, targets): ''' 処理対象を追加。 保持しているデータはそのまま。 ''' for t in targets: self.store.append( (False, t, '0') ) def set_target_value(self, row, col, val): ''' ターゲットの値を設定する row : 行インデックス(0オリジン) col : カラムインデックス(0オリジン) val : 値 ''' aIter = self.store.get_iter( (row,) ) if aIter == None: return self.store.set_value(aIter, col, val) def get_targets(self): ''' 全てのデータをリストとして取得。 データは [(ターゲット名, 行数), ...] ''' targets =[] aIter = self.store.get_iter_first() while aIter != None: targets.append((self.store.get_value(aIter, 1), self.store.get_value(aIter, 2))) aIter = self.store.iter_next(aIter) return targets def remove_targets(self, rows): ''' 行指定でデータを削除する。 rows : 行インデックス(0オリジン) ''' for aIter in [self.store.get_iter(r) for r in rows]: self.store.remove(aIter) def clear_targets(self): ''' 全てのデータをクリアする。 ''' self.store.clear() class TargetDispModel: ''' 処理ターゲットの表示モデル ''' def construct_view(self, model): ''' 処理ターゲット表示用のツリービューを生成して返す。 ''' # ツリービューの生成 tree = gtk.TreeView(model) tree.set_rules_hint(True) tree.set_enable_tree_lines(True) tree.set_grid_lines(gtk.TREE_VIEW_GRID_LINES_VERTICAL) sel = tree.get_selection() sel.set_mode(gtk.SELECTION_MULTIPLE) # カラム0 (処理状況表示) render = gtk.CellRendererToggle() render.set_property('activatable', False) col = gtk.TreeViewColumn('Complete', render) col.add_attribute(render, 'active', 0) tree.append_column(col) # カラム1 (ターゲットファイルパス表示) render = gtk.CellRendererText() render.set_property('editable', False) col = gtk.TreeViewColumn('Target', render, text=1) col.set_resizable(True) col.set_min_width(220) tree.append_column(col) # カラム2(行数表示) render = gtk.CellRendererText() render.set_property('editable', False) col = gtk.TreeViewColumn('Lines', render, text=2) tree.append_column(col) return tree # # class DetailDialog: ''' 詳細表示ダイアログの表示 ''' def __init__(self, infos): ''' 初期化 infos: 行数カウントの処理結果(タプルのリスト) それぞれのタプルの情報は (パス文字列, (総行数, スクリプト行数, コメント行数, 空行数)) 総行数 --> スクリプト行数 + コメント行数 + 空行数 スクリプト行数 --> コメント行数、空行数を含まない行数 ''' # ダイアログを生成 self.dlog = gtk.Dialog('Detail', None, gtk.DIALOG_MODAL, (gtk.STOCK_OK, gtk.RESPONSE_OK)) self.dlog.set_size_request(400, 300) self.dlog.set_position(gtk.WIN_POS_CENTER) scroll = gtk.ScrolledWindow() textView = gtk.TextView() scroll.add(textView) detailText = '' tt = 0 ts = 0 tc = 0 te = 0 for path, (total, lines, comments, emptys) in infos: dname, fname = os.path.split(path) detailText += '- %s ( %s )\n' % (fname, dname) detailText += '\t-> Total : %d\n' % total detailText += '\t-> Source : %d\n' % lines detailText += '\t-> Comment: %d\n' % comments detailText += '\t-> Empty : %d\n' % emptys tt += total ts += lines tc += comments te += emptys detailText += '----------\n' detailText += '[Total] %d, [Source] %d, [Comment] %d, [Empty] %d\n' % (tt, ts, tc, te) textBuff = textView.get_buffer() textBuff.set_text(detailText) self.dlog.vbox.pack_start(scroll) # ダイアログの表示 self.dlog.show_all() def run(self): ''' ダイアログのイベントループ開始 ''' return self.dlog.run() def destroy(self): ''' ダイアログ破棄 ''' self.dlog.destroy() # # イベントハンドラ # def on_add_target(widget, tree, model): ''' 処理対象(ファイル)追加ボタンが押された場合の処理。 ファイル選択ダイアログを開いて、ファイルを選択させる。 現状では、拡張子が.pyのファイルのみ選択可能。 複数ファイル選択可能。 ''' if tree == None: return btList = ((gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OK, gtk.RESPONSE_OK)) chooser = gtk.FileChooserDialog(title='Select files', parent=None, action=gtk.FILE_CHOOSER_ACTION_OPEN, buttons=btList) chooser.set_position(gtk.WIN_POS_CENTER) chooser.set_select_multiple(True) ff = gtk.FileFilter() ff.set_name('python srouce') ff.add_pattern('*.py') chooser.add_filter(ff) response = chooser.run() if response == gtk.RESPONSE_OK: model.append_targets(chooser.get_filenames()) chooser.destroy() # # def on_add_target_dir(widget, tree, model): ''' 処理対象(ディレクトリ)追加ボタンが押された場合の処理。 ファイル選択ダイアログを開いて、ファイルを選択させる。 複数ファイル選択可能。 ''' btList = ((gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OK, gtk.RESPONSE_OK)) chooser = gtk.FileChooserDialog(title='Select files', parent=None, action=gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER, buttons=btList) chooser.set_position(gtk.WIN_POS_CENTER) chooser.set_select_multiple(True) response = chooser.run() if response == gtk.RESPONSE_OK: model.append_targets(chooser.get_filenames()) chooser.destroy() def on_delete_target(widget, tree, model): ''' 処理対象を削除。 リストの選択されている行を削除する。 ''' if tree == None: return sel = tree.get_selection() if sel == None: return model.remove_targets(sel.get_selected_rows()[1]) def on_start(widget, tree, model): ''' スタートボタンが押された場合の処理。 リストに登録されているファイルの行数カウント処理を 開始する。 ''' global g_result g_result = [] if model == None: return targets = model.get_targets() for row, (path, flg) in enumerate(targets): result = countTargetLines(path, ('.py')) g_result += result model.set_target_value(row, 0, True) count = 0 for path, (total, lines, comments, emptys) in result: count += lines model.set_target_value(row, 2, count) def on_detail(widget, event=None): ''' 詳細ダイアログを表示 ''' global g_result if len(g_result) == 0: return dlg = DetailDialog(g_result) response = dlg.run() dlg.destroy() #------------------------- # プラグインプロトコル関数 #------------------------- def get_author(): ''' プラグイン製作者名を返す pyPluggerのプラグインモジュールとしては必須ではないので省略可。 ''' return u'jkani4' def get_name(): ''' プラグイン名称を取得 pyPluggerのプラグインモジュールとしては必須の関数。 ''' return u'ScriptCounter' def get_description(): ''' プラグインについての説明文を取得 pyPluggerのプラグインモジュールとしては必須ではないので省略可。 ''' return u'Pythonスクリプトの行数をカウントするためのプラグイン。\nコメント行は含まれない。(ことを最終目標に..)' def get_version(): ''' プラグインバージョン(文字列)の取得 pyPluggerのプラグインモジュールとしては必須の関数。 ''' return u'0.5.0' def get_widgets(): ''' プラグインがアクティブになった際に呼び出される関数。 プラグインのGUIを生成して返す。 作成するGUIはPyGTKのウィジェットであること。 pyPluggerのプラグインモジュールとしては必須の関数。 ''' global g_treeModel global g_dispModel global g_tree base = gtk.VBox() base.set_border_width(5) g_treeModel = TargetListModel() g_dispModel = TargetDispModel() g_tree = g_dispModel.construct_view(g_treeModel.get_model()) scroll = gtk.ScrolledWindow() scroll.set_size_request(-1, 200) scroll.add(g_tree) base.pack_start(scroll, False, False, 4) hbox = gtk.HBox() base.pack_start(hbox, False, False, 4) bt = gtk.Button('Files') bt.connect('clicked', on_add_target, g_tree, g_treeModel) hbox.pack_start(bt, True, True, 4) bt = gtk.Button('Directorys') bt.connect('clicked', on_add_target_dir, g_tree, g_treeModel) hbox.pack_start(bt, True, True, 4) bt = gtk.Button('Del') bt.connect('clicked', on_delete_target, g_tree, g_treeModel) hbox.pack_start(bt, True, True, 4) bt = gtk.Button('Detail') bt.connect('clicked', on_detail) hbox.pack_start(bt, True, True, 4) bt = gtk.Button('Start') bt.connect('clicked', on_start, g_tree, g_treeModel) base.pack_start(bt, False, False, 8) return base def get_settings(): ''' プラグインの設定を保存する際に呼び出される関数。 プラグインのパラメータを辞書の形で返す。 リターンするデータはpickleがサポートしているデータで ある必要がある。 pyPluggerのプラグインモジュールとしては必須ではないので省略可。 ''' return {} def set_settings(settings): ''' プラグインの設定を復元する際に呼び出される関数。 渡された辞書settingのデータをもとに、設定を復元する処理 を記述する。 pyPluggerのプラグインモジュールとしては必須ではないので省略可。 setting: 関数get_settingsで返したデータと同じものが渡される ''' print 'set_settings', settings