Апдейт к www.linux.org.ru/view-message.jsp?msgid=4068051
#!/usr/bin/python
# -*- coding: UTF-8 -*-
import sys
import os
import stat
import glob
import difflib
import string
import subprocess
from PyQt4 import QtGui, QtCore
from pygments import highlight
from pygments.lexers import DiffLexer
from pygments.formatters import HtmlFormatter
CWD = os.getcwd()
EDITOR = 'gvim -f'
PATCH = 'patch -p0'
DIR = '/etc'
def usage():
print "Usage: %s directory" % sys.argv[0]
# find all *.new files in /etc
def findnew (top = '.', depthfirst = True):
os.chdir(top)
names = glob.glob('*.new')
if not depthfirst:
yield top, names
for name in names:
try:
st = os.lstat(os.path.join(top, name))
except os.error:
continue
if stat.S_ISDIR(st.st_mode):
for (newtop, children) in findnew (os.path.join(top, name), depthfirst):
yield newtop, children
if depthfirst:
yield top, names
class MainWindow(QtGui.QWidget):
newlist = None # QListWidget
diff = None # QTextEdit
edit = None # QPushButton
accept = None # QPushButton
reject = None # QPushButton
patch = None # QPushButton
newfile = None # /etc/file.new
oldfile = None # /etc/file
@QtCore.pyqtSlot()
def on_partial_diff(self):
cursor = self.diff.textCursor()
start = cursor.selectionStart()
end = cursor.selectionEnd()
# starting and ending line numbers for the selection
start_line = self.diff.document().findBlock(start).firstLineNumber()
end_line = self.diff.document().findBlock(end).firstLineNumber()
fulldiff = self.diff.toPlainText().split('\n');
max_lines = len(fulldiff) - 1
# two first lines
partdiff = fulldiff[0] + '\n' + fulldiff[1] + '\n'
# looking for start of diff block (@@ -16,7 +16,7 @@)
if start_line < 2:
start_line = 2
else:
while fulldiff[start_line][0:2] != '@@':
start_line = start_line - 1
# looking for end of diff block (next @@ ... @@ or EOF)
if end_line <= start_line:
end_line = start_line + 1
while end_line < max_lines:
if fulldiff[end_line][0:2] == '@@':
break
end_line = end_line + 1
for l in range(start_line, end_line):
partdiff = partdiff + fulldiff[l] + '\n'
# apply partial patch
pipe = subprocess.Popen(PATCH, shell=True, stdin=subprocess.PIPE)
pipe.stdin.writelines(partdiff)
pipe.stdin.close()
self.reloaddiff(self.newfile)
def enable_buttons(self):
n = self.newlist.count()
self.edit.setEnabled(n > 0)
self.accept.setEnabled(n > 0)
self.reject.setEnabled(n > 0)
@QtCore.pyqtSlot()
def on_selection_changed(self):
self.patch.setEnabled(
self.diff.textCursor().selectionStart() != self.diff.textCursor().selectionEnd()
)
@QtCore.pyqtSlot()
def on_accept(self):
os.rename(self.newfile, self.oldfile)
self.newlist.takeItem(self.newlist.currentRow())
self.enable_buttons()
@QtCore.pyqtSlot()
def on_reject(self):
os.remove(self.newfile)
self.newlist.takeItem(self.newlist.currentRow())
self.enable_buttons()
@QtCore.pyqtSlot()
def on_edit(self):
self.accept.setEnabled(False)
self.newlist.setEnabled(False)
p = subprocess.Popen(EDITOR + ' ' + self.oldfile, shell=True)
sts = os.waitpid(p.pid, 0)
self.reloaddiff(self.newfile)
self.newlist.setEnabled(True)
self.accept.setEnabled(True)
@QtCore.pyqtSlot(QtCore.QString)
def reloaddiff(self, newfile):
self.diff.clear()
self.newfile = str(newfile)
self.oldfile = self.newfile[0:self.newfile.find('.new')]
if self.newfile:
tolines = open(self.newfile, 'U').readlines()
fromlines = open(self.oldfile, 'U').readlines()
difflines = difflib.unified_diff(fromlines, tolines, self.oldfile, self.newfile)
difftext = string.join(difflines, '')
self.edit.setToolTip(EDITOR + ' ' + self.oldfile)
self.reject.setToolTip('Remove ' + self.newfile)
self.accept.setToolTip(self.newfile + ' -> ' + self.oldfile)
self.accept.setEnabled(bool(difftext))
colored_rtf = highlight(difftext, DiffLexer(), HtmlFormatter(full=True))
self.diff.setText(colored_rtf)
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
mvbox = QtGui.QVBoxLayout() # main layout
split = QtGui.QSplitter(QtCore.Qt.Horizontal, self) # left-right splitter
rpart = QtGui.QWidget(split) # right side
lpart = QtGui.QWidget(split) # left side
rvbox = QtGui.QVBoxLayout() # right side layout
lvbox = QtGui.QVBoxLayout() # left side layout
rpart.setLayout(rvbox)
lpart.setLayout(lvbox)
self.diff = QtGui.QTextEdit() # to show diff
self.diff.setReadOnly(True)
self.newlist = QtGui.QListWidget() # to show all *.new files
# Load all *.new files
for (basepath, children) in findnew(DIR):
for child in children:
self.newlist.addItem(os.path.join(basepath, child))
os.chdir(CWD)
quit = QtGui.QPushButton('&Quit', self)
self.accept = QtGui.QPushButton('&Accept', self)
self.reject = QtGui.QPushButton('&Reject', self)
self.edit = QtGui.QPushButton('&Edit old...', self)
self.patch = QtGui.QPushButton('A&pply selection', self)
self.patch.setEnabled(False)
self.patch.setToolTip('Patch using block with selected text')
lvbox.addWidget(self.diff) # diff is on the left side
lvbox.addWidget(self.patch) # "apply patch" button (can be invisible)
buttons = QtGui.QHBoxLayout() # buttons are below it
lvbox.addLayout(buttons) # buttons layout
buttons.addWidget(self.accept)
buttons.addWidget(self.reject)
buttons.addWidget(self.edit)
rvbox.addWidget(self.newlist) # file list is on the right side
rvbox.addWidget(quit) # quit button is below it
mvbox.addWidget(split) # split is the main widget of main window
split.addWidget(lpart) # add the left side on the split
split.addWidget(rpart) # add the right side on the split
self.resize(640, 480)
self.setWindowTitle("What\'s new in %s ?" % DIR)
self.setLayout(mvbox)
self.connect(quit, QtCore.SIGNAL('clicked()'), QtGui.qApp, QtCore.SLOT('quit()'))
self.connect(self.edit, QtCore.SIGNAL('clicked()'), self, QtCore.SLOT('on_edit()'))
self.connect(self.accept, QtCore.SIGNAL('clicked()'), self, QtCore.SLOT('on_accept()'))
self.connect(self.reject, QtCore.SIGNAL('clicked()'), self, QtCore.SLOT('on_reject()'))
self.connect(self.diff, QtCore.SIGNAL('selectionChanged()'), self, QtCore.SLOT('on_selection_changed()'))
self.connect(self.newlist, QtCore.SIGNAL('currentTextChanged(QString)'),
self, QtCore.SLOT('reloaddiff(QString)'))
self.newlist.setCurrentRow(0)
self.enable_buttons()
if len(sys.argv) > 2:
usage()
sys.exit(1)
elif len(sys.argv) == 2:
DIR = sys.argv[1]
app = QtGui.QApplication(sys.argv)
win = MainWindow()
win.show()
sys.exit(app.exec_())