Sunday, May 24, 2009

[Python & PyQt] Code Formatter

Download: CodeFormatter.zip

Introduction
In order to solve the layout problem of google code prettify in blogspot, I try to write a simple but useful tool for source code formating. So far, this program is only fully support python. For other programming languages, you can still get a little benefit from this tool.

This program will replace tab or white space with fixed size tab character. Besides, the special character '<' and '>' will be replaced by html code.

Code
UI
#! /usr/bin/python

##################################################################
# Project: Python Code Formatter
# Filename: ui_codeformatter.py
# Description: User interface for source code formatting
# Date: 2009.05.24
# Programmer: Lin Xin Yu
# E-mail: nxforce@yahoo.com
# Website:http://importcode.blogspot.com
##################################################################

#================= import modules ================================

from PyQt4 import QtCore, QtGui

#================= Ui_CodeFormatterClass Start ===================

class Ui_CodeFormatterClass(object):
    def setupUi(self, CodeFormatterClass):
        CodeFormatterClass.setObjectName("CodeFormatterClass")
        CodeFormatterClass.resize(800, 630)
        
        # plain text editor
        self.textEdit_raw = QtGui.QTextEdit(CodeFormatterClass)
        self.textEdit_raw.setGeometry(QtCore.QRect(10, 10, 780, 285))
        self.textEdit_raw.setObjectName("textEdit_raw")
        self.textEdit_raw.setTabStopWidth(20)
        
        # formatted text editor
        self.textEdit_fmt = QtGui.QTextEdit(CodeFormatterClass)
        self.textEdit_fmt.setGeometry(QtCore.QRect(10, 305, 780, 285))
        self.textEdit_fmt.setObjectName("textEdit_raw")
        self.textEdit_fmt.setTabStopWidth(20)
        
        # language type
        self.comboBox_langMode = QtGui.QComboBox(CodeFormatterClass)
        self.comboBox_langMode.setGeometry(QtCore.QRect(10, 600, 120, 25))
        self.comboBox_langMode.setObjectName("comboBox_langMode")
        self.comboBox_langMode.insertItem(0, 'Python')
        self.comboBox_langMode.insertItem(1, 'Other')
        
        # Tab Width label
        self.label_tabWidth = QtGui.QLabel(CodeFormatterClass)
        self.label_tabWidth.setGeometry(QtCore.QRect(140, 600, 70, 25))
        self.label_tabWidth.setObjectName("label_tabWidt")
        
        # Tab Width lineEdit
        self.lineEdit_tabWidth = QtGui.QLineEdit(CodeFormatterClass)
        self.lineEdit_tabWidth.setGeometry(QtCore.QRect(210, 600, 30, 25))
        self.lineEdit_tabWidth.setObjectName("lineEdit_tabWidth")
        self.lineEdit_tabWidth.setText('4')
        
        # 'Convert' button
        self.pushButton_convert = QtGui.QPushButton(CodeFormatterClass)
        self.pushButton_convert.setGeometry(QtCore.QRect(250, 600, 90, 25))
        self.pushButton_convert.setObjectName("pushButton_convert")
        
        # 'clear all' button
        self.pushButton_clear = QtGui.QPushButton(CodeFormatterClass)
        self.pushButton_clear.setGeometry(QtCore.QRect(350, 600, 90, 25))
        self.pushButton_clear.setObjectName("pushButton_clear")
        
        # 'Open' button
        self.pushButton_open = QtGui.QPushButton(CodeFormatterClass)
        self.pushButton_open.setGeometry(QtCore.QRect(450, 600, 90, 25))
        self.pushButton_open.setObjectName("pushButton_open")
        
        # 'SaveAs' button
        self.pushButton_saveAs = QtGui.QPushButton(CodeFormatterClass)
        self.pushButton_saveAs.setGeometry(QtCore.QRect(550, 600, 90, 25))
        self.pushButton_saveAs.setObjectName("pushButton_saveAs")
        
        # 'Copy to clipboard' button
        self.pushButton_clipboard = QtGui.QPushButton(CodeFormatterClass)
        self.pushButton_clipboard.setGeometry(QtCore.QRect(650, 600, 140, 25))
        self.pushButton_clipboard.setObjectName("pushButton_clipboard")
        
        # Open fileDialog
        self.fileDialog_open = QtGui.QFileDialog(CodeFormatterClass)
        
        # SaveAs fileDialog
        self.fileDialog_saveAs = QtGui.QFileDialog(CodeFormatterClass)
        
        self.retranslateUi(CodeFormatterClass)
        QtCore.QObject.connect(self.pushButton_convert, QtCore.SIGNAL("clicked()"), CodeFormatterClass.onConvertClicked)
        QtCore.QObject.connect(self.pushButton_clear, QtCore.SIGNAL("clicked()"), CodeFormatterClass.onClearAllClicked)
        QtCore.QObject.connect(self.pushButton_open,QtCore.SIGNAL("clicked()"),CodeFormatterClass.onOpenClicked)
        QtCore.QObject.connect(self.pushButton_saveAs, QtCore.SIGNAL("clicked()"), CodeFormatterClass.onSaveAsClicked)
        QtCore.QObject.connect(self.pushButton_clipboard, QtCore.SIGNAL("clicked()"), CodeFormatterClass.onCpClipboardClicked)
        QtCore.QObject.connect(self.lineEdit_tabWidth,QtCore.SIGNAL("returnPressed()"),CodeFormatterClass.onTabWidthSeted)
        QtCore.QMetaObject.connectSlotsByName(CodeFormatterClass)
        
    def retranslateUi(self, CodeFormatterClass):
        CodeFormatterClass.setWindowTitle(QtGui.QApplication.translate("CodeFormatterClass", "Code Formatter", None, QtGui.QApplication.UnicodeUTF8))
        self.pushButton_convert.setText(QtGui.QApplication.translate("CodeFormatterClass", "Convert", None, QtGui.QApplication.UnicodeUTF8))
        self.pushButton_clipboard.setText(QtGui.QApplication.translate("CodeFormatterClass", "Copy to Clipboard", None, QtGui.QApplication.UnicodeUTF8))
        self.pushButton_saveAs.setText(QtGui.QApplication.translate("CodeFormatterClass", "Save As...", None, QtGui.QApplication.UnicodeUTF8))
        self.pushButton_clear.setText(QtGui.QApplication.translate("CodeFormatterClass", "Clear All", None, QtGui.QApplication.UnicodeUTF8))
        self.label_tabWidth.setText(QtGui.QApplication.translate("CodeFormatterClass", "Tab width:", None, QtGui.QApplication.UnicodeUTF8))
        self.pushButton_open.setText(QtGui.QApplication.translate("CodeFormatterClass", "Open...", None, QtGui.QApplication.UnicodeUTF8))
        
#================= Ui_CodeFormatterClass Start ===================

Main Program
#! /usr/bin/python

################################################################
# Project: Python Code Formatter
# Filename: CodeFormatter.py
# Description: An useful tool for source code formatting
# Date: 2009.05.24
# Programmer: Lin Xin Yu
# E-mail: nxforce@yahoo.com
# Website:http://importcode.blogspot.com
################################################################

#================= import modules ==============================
import re
import sys
import ui_codeformatter
from PyQt4 import QtCore, QtGui

#================= CodeFormatter Class Start ===================

class CodeFormatter(QtGui.QMainWindow):
    def __init__(self, parent = None):
        QtGui.QMainWindow.__init__(self, parent)
        self.ui = ui_codeformatter.Ui_CodeFormatterClass()
        self.ui.setupUi(self)
        
        self.ui.textEdit_fmt.setEnabled(False)
        
        # default tab width is 4 for all language
        self.tabWidth = 4
        
        # init raw text
        self.rawText = ''
        
        # init formated text
        self.fmtText = ''
        
    # other code phrasing
    def otherCodePhrasing(self):
        # add the start css tag of google code prettify
        self.fmtText = '<code class="prettyprint">'
        
        for line in self.rawText:
            self.fmtText += line.replace('<', '&lt;').replace('>','&gt;') + '\n'
            
        # add the end css tag of google code prettify
        self.fmtText += '</code>'
        
        self.ui.textEdit_fmt.setPlainText(self.fmtText)
        
    # python source code phrasing
    def pythonCodePhrasing(self):
        numOfTabs = 0
        numOfSpaces = 0
        
        # list for recording the white spaces
        list = [0]
        
        # add the start css tag of google code prettify
        self.fmtText = '<code class="prettyprint">'
        
        for line in self.rawText:
            numOfChrs = 0
            nSpaces = 0
            
            matchAnySpace = re.match('[\s]+', line)
            if(matchAnySpace):
                nSpaces = matchAnySpace.group().count('\t') * self.tabWidth
                nSpaces += matchAnySpace.group().count(' ')
                numOfChrs += len(matchAnySpace.group())
                
            tmpLine = line[numOfChrs:].replace('<', '&lt;').replace('>','&gt;')
            
            # if this line is newline only
            if(tmpLine == ''):
                self.fmtText += ' ' * (numOfTabs * self.tabWidth) + '\n'
                continue
                
            ### reserved function for worst case ###
            # if this line is commecnt
            #matchComment = re.match('^#', tmpLine)
            #if(matchComment):
            # self.fmtText += ' ' * (numOfTabs * self.tabWidth) + tmpLine + '\n'
            # continue
            
            # update tab character count by verifying the white spaces
            if(nSpaces > numOfSpaces):
                list.append(nSpaces)
                numOfTabs += 1
            elif(nSpaces < numOfSpaces):
                while list[len(list)-1] != nSpaces:
                    list.pop()
                    numOfTabs -= 1
                    
            numOfSpaces = nSpaces
            
            # when the line is a new class or function, reset all counts
            if(numOfChrs == 0):
                numOfSpaces = numOfTabs = 0
                
            self.fmtText += ' ' * (numOfTabs * self.tabWidth) + tmpLine + '\n'
            
        # add the end css tag of google code prettify
        self.fmtText += '</code>'
        
        self.ui.textEdit_fmt.setPlainText(self.fmtText)
        
    # SLOT for convert button
    def onConvertClicked(self):
        self.ui.textEdit_fmt.setEnabled(True)
        self.rawText = str(self.ui.textEdit_raw.toPlainText()).split('\n')
        
        if(self.ui.comboBox_langMode.currentIndex() == 0):
            self.pythonCodePhrasing()
            return True
            
        if(self.ui.comboBox_langMode.currentIndex() == 1):
            self.otherCodePhrasing()
            return True
            
    def onClearAllClicked(self):
        self.ui.textEdit_raw.setText('')
        self.ui.textEdit_fmt.setText('')
        self.ui.textEdit_raw.setFocus()
        
    def onCpClipboardClicked(self):
        self.ui.textEdit_fmt.selectAll()
        self.ui.textEdit_fmt.copy()
        mimeData = QtGui.QApplication.clipboard().mimeData();
        
    def onOpenClicked(self):
        filename = self.ui.fileDialog_open.getOpenFileName(self, 'Open file', QtCore.QDir.homePath()+'/Desktop')
        if(filename):
            fileIn = open(filename, 'r')
            rawText = fileIn.read()
            fileIn.close()
            self.ui.textEdit_raw.setPlainText(rawText)
            
    def onSaveAsClicked(self):
        filename = self.ui.fileDialog_saveAs.getSaveFileName(self, 'Save file', QtCore.QDir.homePath()+'/Desktop')
        if(filename):
            fileOut = open(filename, 'w')
            fileOut.write(self.fmtText)
            fileOut.close()
            
    def onTabWidthSeted(self):
        self.tabWidth = int(self.ui.lineEdit_tabWidth.text())
        
        
#================= CodeFormatter Class End ===================

if __name__ == '__main__':
    app = QtGui.QApplication(sys.argv)
    codeFormatter = CodeFormatter()
    codeFormatter.show()
    sys.exit(app.exec_())

Screenshot

Please make sure the tab width is correct before converting.

0 意見:

Post a Comment