Created
November 22, 2015 13:50
-
-
Save ben-hearn-sb/bd25face0beb0f00397f to your computer and use it in GitHub Desktop.
file runner revised.
Patched up a couple of bugs
Added if name = main functionality
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
__author__ = 'ben.hearn' | |
""" | |
- File Runner is a handy Qt UI class that helps with constant batch processing on files or directories with a twist! | |
- You can apply any external script you have written to process the files | |
How to use: | |
- Simply drag in the directories you want to iterate over into the directories window | |
- Type in your desired file format | |
- Drag in the script you want to process your files into the script window | |
- Type in the name of the function you want to run | |
- Choose whether or not you want the program to recursively process all sub directories | |
- Select your desired directories and your desired script | |
- Click Run. It's as easy as that! | |
Your input functions: | |
- To allow the program to work straight off the bat the function you input must be structured like so: | |
def myTestFunction(filePath): | |
# Do something with filePath | |
- Alternatively you can edit the class so it passes in the list of files and you can iterate the list inside your external script | |
Edit the class however you see fit :) | |
Enjoy! | |
""" | |
from PyQt4 import QtCore, QtGui | |
import sys | |
import os | |
import ast | |
import importlib | |
import ntpath | |
class DirectoryFunctions(QtGui.QDialog): | |
def __init__(self): | |
QtGui.QDialog.__init__(self) | |
qAppData = str(QtGui.QDesktopServices.storageLocation(QtGui.QDesktopServices.DataLocation)) | |
self.appDataDir = os.path.join(qAppData, 'CUSTOM_DIR') | |
if not os.path.exists(self.appDataDir): | |
os.makedirs(self.appDataDir) | |
self.inFocusWidget = None | |
self.allowedScriptExts = ['py'] | |
self.dirIn = QtGui.QDialog() | |
self.dirIn.resize(850, 450) | |
self.dirIn.setWindowTitle("File Runner") | |
self.dirIn.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint) | |
width = self.dirIn.width() | |
# Master display is the table view that shows our | |
self.masterDisplay = QtGui.QTableWidget() | |
self.masterDisplay.setColumnCount(2) | |
self.masterDisplay.setHorizontalHeaderLabels(['Directories', 'Format']) | |
# Setting the stretch to expand with the table | |
self.masterDisplay.horizontalHeader().setResizeMode(0, QtGui.QHeaderView.Interactive) | |
self.masterDisplay.horizontalHeader().resizeSection(0, width/2) | |
self.masterDisplay.horizontalHeader().setResizeMode(1, QtGui.QHeaderView.Stretch) | |
self.masterDisplay.verticalHeader().hide() | |
self.masterDisplay.setObjectName('master_display') | |
self.masterDisplay.itemPressed .connect(lambda: self.setFocusWidget(self.masterDisplay)) | |
# Script layout | |
self.scriptPaths = QtGui.QTableWidget() | |
self.scriptPaths.setColumnCount(3) | |
self.scriptPaths.setHorizontalHeaderLabels(['Script Name', 'Function Name', 'Recursive']) | |
self.scriptPaths.horizontalHeader().setResizeMode(0, QtGui.QHeaderView.Interactive) | |
self.scriptPaths.horizontalHeader().resizeSection(0, width/2) | |
self.scriptPaths.horizontalHeader().setResizeMode(1, QtGui.QHeaderView.Stretch) | |
self.scriptPaths.horizontalHeader().setResizeMode(2, QtGui.QHeaderView.Stretch) | |
self.scriptPaths.setSelectionMode(QtGui.QAbstractItemView.SingleSelection) | |
self.scriptPaths.verticalHeader().hide() | |
self.scriptPaths.setObjectName('script_paths') | |
self.scriptPaths.itemPressed .connect(lambda: self.setFocusWidget(self.scriptPaths)) | |
# Create target layout | |
btnRun = QtGui.QPushButton('Run On Selected') | |
targetLayout = QtGui.QHBoxLayout() | |
targetLayout.addWidget(btnRun) | |
# Create appdata button layout | |
btnCreateAppData = QtGui.QPushButton('Save Locations') | |
btnRemoveDir = QtGui.QPushButton('Remove Entry') | |
appLayout = QtGui.QHBoxLayout() | |
appLayout.addWidget(btnCreateAppData) | |
appLayout.addWidget(btnRemoveDir) | |
masterLayout = QtGui.QVBoxLayout() | |
masterLayout.addWidget(self.masterDisplay) | |
masterLayout.addWidget(self.scriptPaths) | |
masterLayout.addLayout(targetLayout) | |
masterLayout.addLayout(appLayout) | |
self.masterDisplay.setAcceptDrops(True) | |
self.masterDisplay.dragEnterEvent = self.dragEnterEvent | |
self.masterDisplay.dragMoveEvent = self.dragMoveEvent | |
self.masterDisplay.dropEvent = self.dropEvent | |
#self.masterDisplay.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) | |
#self.masterDisplay.customContextMenuRequested.connect(self.openMenu) | |
self.scriptPaths.setAcceptDrops(True) | |
self.scriptPaths.dragEnterEvent = self.dragEnterEvent | |
self.scriptPaths.dragMoveEvent = self.dragMoveEvent | |
self.scriptPaths.dropEvent = self.scriptDropEvent | |
btnCreateAppData.pressed.connect(self.createAppData) | |
btnRemoveDir.pressed.connect(self.removeDir) | |
btnRun.pressed.connect(self.runOnSelected) | |
self.dirIn.setLayout(masterLayout) | |
self.setupUI() | |
def show(self): | |
self.dirIn.show() | |
# ----------------------------------------------------------------------------------------------- # | |
def setFocusWidget(self, table): | |
self.inFocusWidget = table | |
""" Overidden QT drag/drop events """ | |
def dragEnterEvent(self, event): | |
event.accept() | |
def dragMoveEvent(self, event): | |
event.accept() | |
def dropEvent(self, event): | |
md = event.mimeData() | |
if md.hasUrls(): | |
for url in md.urls(): | |
urlPath = url.toLocalFile().toLocal8Bit().data() | |
self.masterDisplay.insertRow(self.masterDisplay.rowCount()) | |
rowNum = self.masterDisplay.rowCount()-1 | |
item = QtGui.QTableWidgetItem(urlPath) | |
formatItem = QtGui.QTableWidgetItem('') | |
item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) | |
formatItem.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsEditable) | |
self.masterDisplay.setItem(rowNum, 0, item) | |
event.accept() | |
else: | |
event.ignore() | |
# ----------------------------------------------------------------------------------------------- # | |
def scriptDropEvent(self, event): | |
md = event.mimeData() | |
if md.hasUrls(): | |
for url in md.urls(): | |
urlPath = url.toLocalFile().toLocal8Bit().data() | |
if not urlPath.split('.')[-1] in self.allowedScriptExts: | |
continue | |
self.scriptPaths.insertRow(self.scriptPaths.rowCount()) | |
rowNum = self.scriptPaths.rowCount()-1 | |
item = QtGui.QTableWidgetItem(urlPath) | |
functionNameItem = QtGui.QTableWidgetItem('') | |
item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) | |
functionNameItem.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsEditable) | |
self.scriptPaths.setItem(rowNum, 0, item) | |
pWidget = self.setCenterCheckbox() | |
self.scriptPaths.setCellWidget(rowNum, 2, pWidget) | |
event.accept() | |
else: | |
event.ignore() | |
def setCenterCheckbox(self, checked=False): | |
""" Sets a centred checkbox """ | |
recursiveCheckBox = QtGui.QCheckBox() | |
recursiveCheckBox.setChecked(checked) | |
pWidget = QtGui.QWidget() | |
pLayout = QtGui.QHBoxLayout() | |
pLayout.addWidget(recursiveCheckBox) | |
pLayout.setAlignment(QtCore.Qt.AlignCenter) | |
pLayout.setContentsMargins(0,0,0,0) | |
pWidget.setLayout(pLayout) | |
return pWidget | |
# ----------------------------------------------------------------------------------------------- # | |
def unpackCheckBox(self, table, row, column): | |
return table.cellWidget(row, column).layout().itemAt(0).widget().isChecked() | |
# ----------------------------------------------------------------------------------------------- # | |
def chooseDir(self): | |
""" Allows the user to choose a new directory when you right click """ | |
dirPath = self.openWindowsBrowser() | |
indices = self.masterDisplay.selectedIndexes() | |
if dirPath: | |
dirPath = self.fixPathing(dirPath) | |
if not os.path.isdir(dirPath): | |
return | |
for i in indices: | |
row = i.row() | |
self.createQtContent(dirPath, row, 0, True) | |
# ----------------------------------------------------------------------------------------------- # | |
def addFormat(self, formatString=''): | |
""" Adds the format string to the format column """ | |
indices = self.masterDisplay.selectedIndexes() | |
for i in indices: | |
row = i.row() | |
column = i.column() | |
self.addFormatItem(row, column, formatString) | |
# ----------------------------------------------------------------------------------------------- # | |
def addFormatItem(self, row, column, exportFormat): | |
""" Adds a Qt item to the """ | |
formatItem = QtGui.QTableWidgetItem(exportFormat) | |
formatItem.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) | |
self.masterDisplay.setItem(row, column, formatItem) | |
# ----------------------------------------------------------------------------------------------- # | |
def openWindowsBrowser(self): | |
dirPath = QtGui.QFileDialog.getExistingDirectory(None, 'Select Directory') | |
return dirPath | |
# ----------------------------------------------------------------------------------------------- # | |
def createAppData(self): | |
""" We create an appdata file in this function to store the source level directories and export location """ | |
appDataFile = os.path.join(self.appDataDir, 'custom_dirs.txt') | |
appDataDict = {} | |
scriptDataDict = {} | |
tableWidgets = {self.masterDisplay:appDataDict, self.scriptPaths:scriptDataDict} | |
for table in tableWidgets: | |
for r in range(table.rowCount()): | |
for c in range(table.columnCount()): | |
if c == 0: | |
source = str(table.item(r, c).text()) | |
elif c == 1: | |
if table.item(r, c) is not None: | |
format_function = str(table.item(r, c).text()) | |
else: | |
format_function = '' | |
elif c == 2: | |
checkBoxResult = self.unpackCheckBox(table, r, c) | |
format_function += '**' + str(checkBoxResult) | |
tableWidgets[table].update({source:format_function}) | |
with open(appDataFile, 'w+') as appDataFile: | |
appDataFile.write(str(appDataDict)+'\n') | |
appDataFile.write(str(scriptDataDict)) | |
# ----------------------------------------------------------------------------------------------- # | |
def setupUI(self): | |
""" Sets up the table UI on first boot """ | |
appDataFile = os.path.join(self.appDataDir, 'custom_dirs.txt') | |
exportDirs = None | |
scriptPaths = None | |
if not os.path.exists(appDataFile): | |
return | |
else: | |
with open(appDataFile) as f: | |
i = 0 | |
for line in f: | |
if i == 0: | |
exportDirs = ast.literal_eval(line) | |
elif i == 1: | |
scriptPaths = ast.literal_eval(line) | |
i += 1 | |
dicts = [exportDirs, scriptPaths] | |
for index, d in enumerate(dicts): | |
if index == 0: | |
table = self.masterDisplay | |
elif index == 1: | |
table = self.scriptPaths | |
if len(d) > 0: | |
for index, source in enumerate(d): | |
table.insertRow(index) | |
content = d[source] | |
# If our table is script_paths we need to split our value to get our true/false checkbox value | |
if table.objectName() == 'script_paths': | |
temp = d[source].split('**') | |
content = temp[0] | |
checked = temp[1] | |
if checked == 'True': | |
checked = True | |
else: | |
checked = False | |
pWidget = self.setCenterCheckbox(checked) | |
table.setCellWidget(index, 2, pWidget) | |
self.createQtContent(source, index, 0, table) | |
self.createQtContent(content, index, 1, table) | |
# ----------------------------------------------------------------------------------------------- # | |
def createQtContent(self, content, row, column, table): | |
""" Creates a QTableWidgetItem """ | |
item = QtGui.QTableWidgetItem(content) | |
if column == 1: | |
item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsEditable) | |
else: | |
item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) | |
table.setItem(row, column, item) | |
# ----------------------------------------------------------------------------------------------- # | |
def fixPathing(self, filePath): | |
""" Replaces pathing slashes """ | |
return filePath.replace('\\', '/') | |
# ----------------------------------------------------------------------------------------------- # | |
def removeDir(self): | |
""" Removes the directory from the UI and saves the file | |
Important tip is to remove rows incrementally in reverse """ | |
try: | |
indices = self.inFocusWidget.selectedIndexes() | |
except AttributeError: | |
QtGui.QMessageBox.information(self.masterDisplay, 'Info','Please ensure widget is active') | |
return | |
# Get all row index | |
indexes = [] | |
for i in indices: | |
indexes.append(i.row()) | |
# Reverse sort rows indexes | |
indexes = sorted(indexes, reverse=True) | |
# Delete rows | |
for rowidx in indexes: | |
print 'removing', rowidx | |
self.inFocusWidget.removeRow(rowidx) | |
# ----------------------------------------------------------------------------------------------- # | |
def runOnSelected(self): | |
""" Runs over the selected directories | |
Runs some checks on directories, file pathing, format etc. | |
""" | |
indices = self.masterDisplay.selectedIndexes() | |
scriptPathIndices = self.scriptPaths.selectedItems() | |
if len(indices) < 1 or len(scriptPathIndices)< 1: | |
QtGui.QMessageBox.information(self.masterDisplay, 'Info','Please select a script and relevant directories') | |
return | |
row = scriptPathIndices[0].row() | |
scriptPath = str(self.scriptPaths.item(row, 0).text()) | |
scriptDir = os.path.dirname(scriptPath) | |
scriptName = ntpath.basename(scriptPath).split('.')[0] | |
functionName = str(self.scriptPaths.item(row, 1).text()) | |
recursive = self.unpackCheckBox(self.scriptPaths, row, 2) | |
if not os.path.exists(scriptPath): | |
QtGui.QMessageBox.information(self.masterDisplay, 'Info','Your script no longer lives in this location\nPlease remove entry and choose new location') | |
return | |
if not scriptDir in sys.path: | |
sys.path.append(scriptDir) | |
try: | |
externalFunc = getattr(importlib.import_module(scriptName), functionName) | |
except AttributeError: | |
QtGui.QMessageBox.information(self.masterDisplay, 'Info','Please check script pathing and function name is correct') | |
return | |
print 'Running' | |
checkDirs = self.checkExistingDirs(indices) | |
if len(checkDirs) > 0: | |
nonExistent = '' | |
for d in checkDirs: | |
nonExistent += d + '\n' | |
QtGui.QMessageBox.information(self.masterDisplay, 'Info','One or more selected dirs does not exist\n%s'%nonExistent) | |
return | |
# Once all our checks are done we can run the main chunk of the function | |
for i in indices: | |
row = i.row() | |
sourceDir = self.masterDisplay.item(row, 0) | |
formatString = self.masterDisplay.item(row, 1) | |
if sourceDir is None or formatString is None or sourceDir == '' or formatString == '': | |
QtGui.QMessageBox.information(self.masterDisplay, 'Info','Cannot run without a source dir or format') | |
return | |
sourceDir = str(sourceDir.text()) | |
formatString = str(formatString.text()) | |
for root, dirs, files in os.walk(sourceDir): | |
self.runExternalFunction(root, files, formatString, externalFunc) | |
if recursive == False: | |
break | |
QtGui.QMessageBox.information(self.masterDisplay, 'Info', 'Process complete') | |
def checkExistingDirs(self, indices): | |
nonExistent = [] | |
for i in indices: | |
row = i.row() | |
sourceDir = str(self.masterDisplay.item(row, 0).text()) | |
if not os.path.exists(sourceDir): | |
nonExistent.append(sourceDir) | |
return nonExistent | |
def runExternalFunction(self, root, files, format, func): | |
if len(files) == 0: | |
return | |
for f in files: | |
if os.path.splitext(f)[-1] == format: | |
myFile = os.path.join(root, f) | |
func(myFile) | |
def main(): | |
""" Setting our Qt application up and setting a name for it """ | |
app = QtGui.QApplication(sys.argv) | |
app.setApplicationName("FILE_RUNNER") | |
window = DirectoryFunctions() | |
window.show() | |
sys.exit(app.exec_()) | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment