Skip to content

Commit ed2bb10

Browse files
committed
initial commit
0 parents  commit ed2bb10

20 files changed

+2308
-0
lines changed

bashmate.py

+334
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,334 @@
1+
#!/usr/bin/env python2
2+
# coding: utf-8
3+
4+
# This program is free software. It comes without any warranty, to
5+
# the extent permitted by applicable law. You can redistribute it
6+
# and/or modify it under the terms of the Do What The Fuck You Want
7+
# To Public License, Version 2, as published by Sam Hocevar. See
8+
# http://sam.zoy.org/wtfpl/COPYING for more details.
9+
10+
"""
11+
~~~~~~~~~~
12+
Bashfriend
13+
~~~~~~~~~~
14+
15+
Simnple Tool to manage Snippets / Templates for Shell Commands.
16+
Those Templates can be filled with values via a GUI.
17+
"""
18+
19+
import sys
20+
import json
21+
#import re
22+
from operator import itemgetter
23+
24+
from PyQt4.QtGui import (QMainWindow, QApplication, QPushButton, QDialog,
25+
QStringListModel, QFileDialog, QSortFilterProxyModel,
26+
QMessageBox)
27+
from PyQt4.QtCore import (Qt, QAbstractTableModel, QVariant, QModelIndex,
28+
QRegExp)
29+
30+
from ui_mainwindow import Ui_MainWindow
31+
from ui_commanddialog import Ui_CommandDialog
32+
33+
import icons_rc
34+
35+
36+
FILENAME = r"commands.json"
37+
FIELDNAME = itemgetter(1)
38+
39+
40+
class CommandTemplate(object):
41+
42+
def __init__(self, command, info="", names=[]):
43+
self.command = command
44+
self.info = info
45+
self.names = names
46+
47+
def __repr__(self):
48+
return "<CommandTemplate({0})>".format(self.command)
49+
50+
def render(self, **kwargs):
51+
return self.command.format(**kwargs)
52+
53+
54+
class CommandTemplateEncoder(json.JSONEncoder):
55+
"""
56+
JSON hook-class to serialize aCommandTemplate object
57+
"""
58+
59+
def default(self, obj):
60+
if isinstance(obj, CommandTemplate):
61+
return {
62+
"command": obj.command,
63+
"info": obj.info,
64+
"names": obj.names
65+
}
66+
return json.JSONEncoder.default(self, obj)
67+
68+
69+
class CommandModel(QAbstractTableModel):
70+
def __init__(self, commands, parent=None):
71+
QAbstractTableModel.__init__(self)
72+
self.commands = commands
73+
74+
def rowCount(self, parent=None):
75+
return len(self.commands)
76+
77+
def columnCount(self, parent=None):
78+
return 2
79+
80+
def data(self, index, role):
81+
command = self.currentCommand(index)
82+
if role == Qt.DisplayRole:
83+
if index.column() == 0:
84+
return command.command
85+
elif index.column() == 1:
86+
return command.info
87+
88+
def addCommand(self, command):
89+
index = self.createIndex(self.rowCount(), 0)
90+
self.beginInsertRows(QModelIndex(), index.row(), index.row())
91+
self.commands.append(command)
92+
self.insertRows(index.row(), 1)
93+
self.dataChanged.emit(index, index)
94+
self.endInsertRows()
95+
96+
def removeCommand(self, index):
97+
self.beginRemoveRows(QModelIndex(), index.row(), index.row())
98+
self.commands.pop(index.row())
99+
self.dataChanged.emit(index, index)
100+
self.endRemoveRows()
101+
102+
def currentCommand(self, index):
103+
return self.commands[index.row()] if index.row() > -1 else None
104+
105+
def headerData(self, section, orientation, role):
106+
if role != Qt.DisplayRole:
107+
return QVariant()
108+
if orientation == Qt.Horizontal:
109+
return QVariant(["Command", "Info"][section])
110+
111+
112+
class BindModel(QAbstractTableModel):
113+
def __init__(self, command, parent=None):
114+
QAbstractTableModel.__init__(self)
115+
self.names = command.names
116+
self.bindings = dict.fromkeys(self.names)
117+
118+
def rowCount(self, parent=None):
119+
return len(self.names)
120+
121+
def columnCount(self, parent=None):
122+
return 2
123+
124+
def data(self, index, role):
125+
name = self.currentName(index)
126+
if role == Qt.DisplayRole:
127+
if index.column() == 0:
128+
return name
129+
elif index.column() == 1:
130+
return self.bindings[name]
131+
132+
def setData(self, index, value, role=Qt.EditRole):
133+
if index.isValid() and index.column() == 1 and role == Qt.EditRole:
134+
name = self.currentName(index)
135+
self.bindings[name] = value.toString()
136+
self.dataChanged.emit(index, index)
137+
return True
138+
return False
139+
140+
def currentName(self, index):
141+
return self.names[index.row()]
142+
143+
def flags(self, index):
144+
return (
145+
Qt.ItemIsEnabled |
146+
Qt.ItemIsSelectable |
147+
Qt.ItemIsEditable
148+
)
149+
150+
def headerData(self, section, orientation, role):
151+
if role != Qt.DisplayRole:
152+
return QVariant()
153+
if orientation == Qt.Horizontal:
154+
return QVariant(["Name", "Value"][section])
155+
156+
157+
class CommandDialog(QDialog):
158+
159+
def __init__(self, command=None, parent=None):
160+
QDialog.__init__(self)
161+
self.ui = Ui_CommandDialog()
162+
self.ui.setupUi(self)
163+
164+
self.ui.namesView.setModel(QStringListModel(self))
165+
166+
self.ui.commandEdit.textChanged.connect(self.parseNames)
167+
#self.ui.nameEdit.textChanged.connect(self.activate)
168+
#self.ui.addButton.clicked.connect(self.addName)
169+
#self.ui.deleteButton.clicked.connect(self.deleteName)
170+
self.ui.deleteButton.setVisible(False)
171+
self.ui.addButton.setVisible(False)
172+
self.ui.nameEdit.setVisible(False)
173+
174+
if command:
175+
self.ui.commandEdit.setText(command.command)
176+
self.ui.infoEdit.setText(command.info)
177+
178+
@property
179+
def command(self):
180+
return CommandTemplate(unicode(self.ui.commandEdit.text()),
181+
unicode(self.ui.infoEdit.toPlainText()),
182+
map(unicode, self.ui.namesView.model().\
183+
stringList()))
184+
185+
#def activate(self, text):
186+
#state = False
187+
#if len(text) > 0:
188+
#state = True
189+
#self.ui.addButton.setEnabled(state)
190+
#self.ui.deleteButton.setEnabled(state)
191+
192+
#def addName(self):
193+
#model = self.ui.namesView.model()
194+
#model.insertRows(0, 1)
195+
#model.setData(model.createIndex(0, 0), QVariant(
196+
#self.ui.nameEdit.text()))
197+
198+
#def deleteName(self):
199+
#model = self.ui.namesView.model()
200+
#model.removeRows(self.ui.namesView.currentIndex().row(), 1)
201+
202+
def parseNames(self, text):
203+
try:
204+
names = [FIELDNAME(token) for token in unicode(text).\
205+
_formatter_parser() if FIELDNAME(token) is not None]
206+
# einzelne "{" oder "}"; beim Tippen tritt das logischer Weise auf
207+
except ValueError:
208+
pass
209+
else:
210+
# doppelte Einträge elemenieren
211+
#self.ui.namesView.model().setStringList(list(set(map(unicode,
212+
#self.ui.namesView.model().stringList())) | set(names)))
213+
self.ui.namesView.model().setStringList(list(set(names)))
214+
215+
216+
class MainWindow(QMainWindow):
217+
218+
def __init__(self, filename=None, parent=None):
219+
QMainWindow.__init__(self)
220+
self.ui = Ui_MainWindow()
221+
self.ui.setupUi(self)
222+
223+
if filename is not None:
224+
self.initCommandModel(load_commands(filename))
225+
else:
226+
self.initCommandModel([])
227+
228+
self.ui.action_Load.triggered.connect(self.loadCommands)
229+
self.ui.action_Save.triggered.connect(self.saveCommands)
230+
231+
self.ui.action_Add.triggered.connect(self.addCommand)
232+
self.ui.action_Edit.triggered.connect(self.editCommand)
233+
self.ui.action_Remove.triggered.connect(self.removeCommand)
234+
self.ui.action_Apply.triggered.connect(self.render)
235+
236+
self.ui.action_About_BashMate.triggered.connect(self.about)
237+
238+
self.ui.commandView.activated.connect(self.changeDetailsView)
239+
self.ui.applyButton.clicked.connect(self.render)
240+
self.ui.filterEdit.textChanged.connect(self.filter_changed)
241+
#self.ui.addButton.clicked.connect(self.addCommand)
242+
#self.ui.editButton.clicked.connect(self.editCommand)
243+
#self.ui.removeButton.clicked.connect(self.removeCommand)
244+
#self.ui.commandView.model().dataChanged.connect(self.change)
245+
#self.ui.commandView.activated.connect(self.activate)
246+
247+
def initCommandModel(self, commands=[]):
248+
self.cmdmodel = CommandModel(commands, self)
249+
proxymodel= QSortFilterProxyModel(self)
250+
proxymodel.setSourceModel(self.cmdmodel)
251+
proxymodel.setFilterKeyColumn(1)
252+
self.ui.commandView.setModel(proxymodel)
253+
254+
def loadCommands(self):
255+
filename = unicode(QFileDialog.getOpenFileName(self,
256+
u"Load CommandTemplate File", ".",
257+
u"CommandTemplate File (*.json)"))
258+
self.initCommandModel(load_commands(filename))
259+
260+
def saveCommands(self):
261+
filename = unicode(QFileDialog.getSaveFileName(self,
262+
u"Save CommandTemplate File", ".",
263+
u"CommandTemplate File (*.json)"))
264+
dump_commands(self.cmdmodel.commands, filename)
265+
266+
267+
def changeDetailsView(self, index):
268+
command = self.cmdmodel.currentCommand(index)
269+
self.ui.commandBrowser.setText(command.command)
270+
self.ui.infoBrowser.setText(command.info)
271+
self.ui.bindView.setModel(BindModel(command, self))
272+
273+
def render(self):
274+
command = self.cmdmodel.currentCommand(
275+
self.ui.commandView.currentIndex())
276+
bindings = self.ui.bindView.model().bindings
277+
self.ui.cmdEdit.setText(command.render(**bindings))
278+
279+
def addCommand(self):
280+
dialog = CommandDialog(parent=self)
281+
if dialog.exec_() == QDialog.Accepted:
282+
self.cmdmodel.addCommand(dialog.command)
283+
284+
def editCommand(self):
285+
command = self.cmdmodel.currentCommand(
286+
self.ui.commandView.currentIndex())
287+
dialog = CommandDialog(command, parent=self)
288+
if dialog.exec_() == QDialog.Accepted:
289+
commands = self.cmdmodel.commands
290+
commands[commands.index(command)] = dialog.command
291+
292+
def removeCommand(self):
293+
self.cmdmodel.removeCommand(self.ui.commandView.currentIndex())
294+
295+
def filter_changed(self, pattern):
296+
regExp = QRegExp(pattern, Qt.CaseInsensitive)
297+
self.ui.commandView.model().setFilterRegExp(regExp)
298+
299+
def change(self, left, right):
300+
print self.ui.commandView.currentIndex().row()
301+
self.changeDetailsView(self.ui.commandView.currentIndex())
302+
303+
def about(self):
304+
box = QMessageBox()
305+
box.setText(u"BashMate - the beginner firendly Shell Snippet Manager")
306+
box.setInformativeText(
307+
u"(c) 2011 by Christian Hausknecht <christian.hausknecht@gmx.de>")
308+
box.exec_()
309+
310+
311+
def load_commands(filename):
312+
commands = []
313+
with open(filename, "r") as infile:
314+
data = json.load(infile)
315+
for command in data:
316+
commands.append(CommandTemplate(command["command"], command["info"],
317+
command["names"]))
318+
return commands
319+
320+
321+
def dump_commands(commands, filename):
322+
with open(filename, "w") as outfile:
323+
data = json.dump(commands, outfile, indent=4,
324+
cls=CommandTemplateEncoder)
325+
326+
327+
if __name__ == "__main__":
328+
filename = None
329+
if len(sys.argv) > 1:
330+
filename = sys.argv[1]
331+
app = QApplication(sys.argv)
332+
widget = MainWindow(filename) if filename is not None else MainWindow()
333+
widget.show()
334+
sys.exit(app.exec_())

cmd.json

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
[
2+
{
3+
"info": "Listet alle Dateien einer Endung {suffix} auf",
4+
"command": "ls *.{suffix}",
5+
"names": [
6+
"suffix"
7+
]
8+
},
9+
{
10+
"info": "Erstellt ein Backup der Datei {fpath} mit der Endung {suffix}",
11+
"command": "cp {fpath} {fpath}.{suffix}",
12+
"names": [
13+
"fpath",
14+
"suffix"
15+
]
16+
},
17+
{
18+
"info": "Erstellt ein neues Verzeichnis {dirname}",
19+
"command": "mkdir {dirname}",
20+
"names": [
21+
"dirname"
22+
]
23+
}
24+
]

icons.qrc

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<RCC>
2+
<qresource prefix="icons">
3+
<file alias="terminal.png">icons/terminal.png</file>
4+
<file alias="exit.png">icons/application-exit.png</file>
5+
<file alias="edit.png">icons/document-edit.png</file>
6+
<file alias="new.png">icons/document-new.png</file>
7+
<file alias="open.png">icons/document-open.png</file>
8+
<file alias="save.png">icons/document-save.png</file>
9+
<file alias="delete.png">icons/edit-delete.png</file>
10+
<file alias="apply.png">icons/dialog-ok-apply.png</file>
11+
<file alias="ok.png">icons/dialog-ok.png</file>
12+
<file alias="cancel.png">icons/dialog-cancel.png</file>
13+
<file alias="apply2.png">icons/debug-step-into-instruction.png</file>
14+
</qresource>
15+
</RCC>

icons/application-exit.png

1.72 KB
Loading

icons/debug-step-into-instruction.png

1.22 KB
Loading

icons/dialog-cancel.png

2.16 KB
Loading

icons/dialog-ok-apply.png

1.19 KB
Loading

icons/dialog-ok.png

1.22 KB
Loading

icons/document-edit.png

1.66 KB
Loading

icons/document-new.png

1.38 KB
Loading

icons/document-open.png

1.06 KB
Loading

icons/document-save.png

1.23 KB
Loading

icons/edit-delete.png

1.3 KB
Loading

icons/terminal.png

1.09 KB
Loading

0 commit comments

Comments
 (0)