diff --git a/kidiff_gui.py b/kidiff_gui.py index 14bd851..4833ead 100755 --- a/kidiff_gui.py +++ b/kidiff_gui.py @@ -4,7 +4,13 @@ # held in a suitable version control repository and produce a graphical diff # of generated svg files in a web browser. -# TODO Place all template text/css text in external files +# TODO [ ] Place all template text/css text in external files +# TODO [ ] Improve display of artifacts in diff choice window +# TODO [ ] Consider changing GUI elements to wxPython +# TODO [*] Manage any missing SCM paths - reflect available SCM progs in splash screen +# TODO [ ] Adjust
for three pane output to have white outer border, not filter colour +# TODO [ ] Improve three pane output layout, perhaps with diff tree on LHS and not underneath +# import os import time @@ -19,8 +25,14 @@ import tkUI from tkUI import * import http.server import socketserver +socketserver.TCPServer.allow_reuse_address = True +# ------------------------------------------------------------------------- # NOTE Adjust these paths to suit your setup +# If you do not use one (or more) of these SCMs, please set to '' +# This program attempts to auto-identify which SCM is in use. +# In the event of multiple SCMs being in use in one repository, the order of priority +# is Fossil > Git > SVN. gitProg = '/usr/bin/git' fossilProg = '/usr/local/bin/fossil' @@ -30,9 +42,15 @@ webDir = '/web' diffProg = '/usr/bin/diff' plotProg = '/usr/local/bin/plotPCB2_DIMS.py' -PORT = 9090 -Handler = http.server.SimpleHTTPRequestHandler +# ------------------------------------------------------------------------- +# NOTE Adjust this port to suit your requirements. Must be >1000. + +PORT = 9090 + + +# ------------------------------------------------------------------------- +# NOTE Please adjust these colours to suit your requirements. layerCols = { 'F_Cu': "#952927", @@ -59,6 +77,14 @@ layerCols = { 'F_CrtYd': "#A7A7A7", } +Handler = http.server.SimpleHTTPRequestHandler + + +# ------------------------------------------HTML Template Blocks------------------------------------------- +# +# NOTE These should go into external files to clean up and seperate the code +# + tail = '''
@@ -937,49 +963,52 @@ def getSCM(prjctPath): scm = '' # Check if git - gitCmd = 'cd ' + prjctPath + ' && ' + gitProg + ' status' - git = Popen( - gitCmd, - shell=True, - stdin=PIPE, - stdout=PIPE, - stderr=PIPE, - close_fds=True) - stdout, stderr = git.communicate() - git.wait() - if ((stdout.decode('utf-8') != '') & (stderr.decode('utf-8') == '')): - scm = 'Git' + if (gitProg != ''): + gitCmd = 'cd ' + prjctPath + ' && ' + gitProg + ' status' + git = Popen( + gitCmd, + shell=True, + stdin=PIPE, + stdout=PIPE, + stderr=PIPE, + close_fds=True) + stdout, stderr = git.communicate() + git.wait() + if ((stdout.decode('utf-8') != '') & (stderr.decode('utf-8') == '')): + scm = 'Git' # check if Fossil - fossilCmd = 'cd ' + prjctPath + ' && ' + fossilProg + ' status' - fossil = Popen( - fossilCmd, - shell=True, - stdin=PIPE, - stdout=PIPE, - stderr=PIPE, - close_fds=True) - stdout, stderr = fossil.communicate() - fossil.wait() - print(stdout.decode('utf-8'),"stdERROR=", stderr.decode('utf-8')) - # if ((stdout.decode('utf-8') != '') & (stderr.decode('utf-8') == '')): + if (fossilProg != ''): + fossilCmd = 'cd ' + prjctPath + ' && ' + fossilProg + ' status' + fossil = Popen( + fossilCmd, + shell=True, + stdin=PIPE, + stdout=PIPE, + stderr=PIPE, + close_fds=True) + stdout, stderr = fossil.communicate() + fossil.wait() + print(stdout.decode('utf-8'),"stdERROR=", stderr.decode('utf-8')) + # if ((stdout.decode('utf-8') != '') & (stderr.decode('utf-8') == '')): - if (stdout.decode('utf-8') != ''): - scm = 'Fossil' + if (stdout.decode('utf-8') != ''): + scm = 'Fossil' # check if SVN - svnCmd = 'cd ' + prjctPath + ' && ' + svnProg + ' log | perl -l40pe "s/^-+/\n/"' - svn = Popen( - svnCmd, - shell=True, - stdin=PIPE, - stdout=PIPE, - stderr=PIPE, - close_fds=True) - stdout, stderr = svn.communicate() - svn.wait() - if ((stdout.decode('utf-8') != '') & (stderr.decode('utf-8') == '')): - scm = 'SVN' + if (svnProg != ''): + svnCmd = 'cd ' + prjctPath + ' && ' + svnProg + ' log | perl -l4svn log0pe "s/^-+/\n/"' + svn = Popen( + svnCmd, + shell=True, + stdin=PIPE, + stdout=PIPE, + stderr=PIPE, + close_fds=True) + stdout, stderr = svn.communicate() + svn.wait() + if ((stdout.decode('utf-8') != '') & (stderr.decode('utf-8') == '')): + scm = 'SVN' return scm @@ -988,7 +1017,18 @@ def fossilDiff(path, kicadPCB): '''Returns list of Fossil artifacts from a directory containing a *.kicad_pcb file.''' + # NOTE Assemble a list of artefacts. Unfortunatly, Fossil doesn't give any easily configurable length. + # NOTE Using the -W option results in multiline diffs + # NOTE 'fossil -finfo' looks like this + # 2017-05-19 [21d331ea6b] Preliminary work on CvPCB association and component values (user: johnpateman, artifact: [1100d6e077], branch: Ver_3V3) + # 2017-05-07 [2d1e20f431] Initial commit (user: johnpateman, artifact: [24336219cc], branch: trunk) + # NOTE 'fossil -finfo -b' looks like this + # 21d331ea6b 2017-05-19 johnpate Ver_3V3 Preliminary work on CvPCB association a + # 2d1e20f431 2017-05-07 johnpate trunk Initial commit + # TODO Consider parsing the output of fossil finfo and split off date, artifactID, mesage, user and branch + fossilCmd = 'cd ' + path + ' && ' + fossilProg + ' finfo -b ' + kicadPCB + fossil = Popen( fossilCmd, shell=True, @@ -999,7 +1039,8 @@ def fossilDiff(path, kicadPCB): stdout, _ = fossil.communicate() fossil.wait() line = (stdout.decode('utf-8').splitlines()) - fArtifacts = [a.replace(' ', ' ', 4) for a in line] + # fArtifacts = [a.replace(' ', ' ', 4) for a in line] + fArtifacts = [a.replace(' ', '\t', 4) for a in line] return fArtifacts @@ -1042,10 +1083,8 @@ def svnDiff(path, kicadPCB): def makeSVG(d1, d2, prjctName, prjctPath, reqLayers): '''Hands off required .kicad_pcb files to "plotPCB2.py" - and generate .svg files. Does not use the 'reqLayers as the plotting routine - is written in python2 and can't seem to pass layer list easily. Routine is - v quick so no major overhead in plotting unescessay layers to svg. Easiest to - write it to a 'layers' file in the output directory''' + and generate .svg files. Routine is + v quick so all layers are plotted to svg.''' print("Generating .svg files") @@ -1421,13 +1460,23 @@ def popup_showinfo(progress): p = Label(gui, Text=display) p.pack() +def scmAvailable(): + SCMS = '' + if (fossilProg != ''): + SCMS = SCMS + "Fossil \n" + if (gitProg != ''): + SCMS = SCMS + "Git \n" + if (svnProg != ''): + SCMS = SCMS + "SVN " + + return (SCMS) class Handler(http.server.SimpleHTTPRequestHandler): def __init__(self, *args, **kwargs): super().__init__(*args, directory=os.path.realpath(prjctPath + plotDir), **kwargs) -class Splash(tk.Toplevel): +class Select(tk.Toplevel): def __init__(self, parent): tk.Toplevel.__init__(self, parent) tk.Toplevel.withdraw(self) @@ -1435,7 +1484,7 @@ class Splash(tk.Toplevel): action = messagebox.askokcancel( self, message="Select a *.kicad_pcb file under version control", - detail="Git, Fossil or SVN supported") + detail="Available: \n\n" + SCMS) self.update() if action == "cancel": self.quit() @@ -1450,11 +1499,16 @@ def startWebServer(): if __name__ == "__main__": - gui = tk.Tk() + SCMS = scmAvailable() + print(SCMS) + if (SCMS == ""): + print("You need to have at least one SCM program path identified in lines 32 - 40") + exit() + gui = tk.Tk(SCMS) gui.withdraw() gui.update() - splash = Splash(gui) - splash.destroy() + Select = Select(gui) + Select.destroy() prjctPath, prjctName = getProject() gui.update() gui.deiconify() @@ -1472,13 +1526,12 @@ if __name__ == "__main__": if scm == 'SVN': artifacts = svnDiff(prjctPath, prjctName) if scm == '': - print("This project does not appear to be under version control") + print("This project is either not under version control or you have not set the path to the approriate SCM program in lines 32-40") sys.exit(0) - dpi, d1, d2, layers = tkUI.runGUI(artifacts, prjctName, prjctPath, scm) + d1, d2 = tkUI.runGUI(artifacts, prjctName, prjctPath, scm) - print("Resolution (dpi) : ", dpi.get()) print("Commit1", d1) print("Commit2", d2) diff --git a/tkUI.py b/tkUI.py index df7576c..bc1b814 100644 --- a/tkUI.py +++ b/tkUI.py @@ -5,7 +5,7 @@ import tkinter as tk from tkinter import * from tkinter import filedialog, ttk, messagebox -global resolution, buttons, root, commitTop, commitBottom +global root, commitTop, commitBottom def runProgram(): @@ -13,16 +13,16 @@ def runProgram(): root.quit() -class Splash(tk.Toplevel): - def __init__(self, parent): - tk.Toplevel.__init__(self, parent) - self.title("Kicad Visual Diff") - action = messagebox.askokcancel(self, - message="Select a *.kicad_pcb file under version control", detail="Git, Fossil or SVN supported") +# class Splash(tk.Toplevel): +# def __init__(self, parent): +# tk.Toplevel.__init__(self, parent) +# self.title("Kicad Visual Diff") +# action = messagebox.askokcancel(self, +# message="Select a *.kicad_pcb file under version control", detail="Git, Fossil or SVN supported") - self.update() - if action == "cancel": - self.quit() +# self.update() +# if action == "cancel": +# self.quit() def CurSelect(event): @@ -32,16 +32,16 @@ def CurSelect(event): picked = widget.get(selection) source = ((str(widget).split('.'))[1])[-1:] # TOP window is 3 - if source == '3': + if source == '2': commitTop = picked # BOTTOM window is 4 - elif source == '4': + elif source == '3': commitBottom = picked def runGUI(checkouts_top, prjctName, prjctPath, scm): - global resolution, buttons, root, commitTop, commitBottom + global root, commitTop, commitBottom checkouts_bottom = checkouts_top[:] @@ -50,27 +50,24 @@ def runGUI(checkouts_top, prjctName, prjctPath, scm): root.configure(background='#ececec') root.title("Kicad Visual Diff") - root.geometry('1200x700') + # root.geometry('1200x700') frame1 = tk.LabelFrame(root, text=scm, width=1000, height=50, bd=1, background='#ececec') - frame2 = tk.LabelFrame(root, text="Layers", width=200, + frame2 = tk.LabelFrame(root, text="Commit 1", width=1000, height=200, bd=1, background='#ececec') - frame3 = tk.LabelFrame(root, text="Commit 1", width=400, + frame3 = tk.LabelFrame(root, text="Commit 2", width=1000, height=200, bd=1, background='#ececec') - frame4 = tk.LabelFrame(root, text="Commit 2", width=400, - height=200, bd=1, background='#ececec') - frame5 = tk.LabelFrame(root, text="Resolution (dpi)", width=1000, + frame4 = tk.LabelFrame(root, width=1000, height=50, bd=0, background='#ececec') - frame1.grid(row=0, column=0, columnspan=2, padx=5, sticky='N E W S') - frame2.grid(row=1, column=0, rowspan=2, padx=5, sticky='N E W S') - frame3.grid(row=1, column=1, padx=5, sticky='N E W S') - frame4.grid(row=2, column=1, padx=5, sticky='N E W S') - frame5.grid(row=3, column=0, columnspan=2, padx=5, sticky='N E W S') + frame1.grid(row=0, column=0, padx=25, sticky='N E W S') + frame2.grid(row=1, column=0, padx=25, sticky='N E W') + frame3.grid(row=2, column=0, padx=25, sticky='N EW') + frame4.grid(row=3, column=0, padx=25, sticky='N E W S') - root.grid_columnconfigure(0, weight=1) - root.grid_columnconfigure(1, weight=10) + # root.grid_columnconfigure(0, weight=1) + # root.grid_columnconfigure(1, weight=10) root.grid_rowconfigure(0, minsize=50, weight=1) root.grid_rowconfigure(1, minsize=200, weight=2) @@ -80,42 +77,17 @@ def runGUI(checkouts_top, prjctName, prjctPath, scm): tk.Label(frame1, text=prjctPath, bg='#ececec').pack(side=LEFT, padx=10) tk.Label(frame1, text=prjctName, bg='#ececec').pack(side=LEFT, padx=10) - buttons = {'Top layer': '1', - 'Bottom layer': '1', - 'Paste bottom': '1', - 'Paste top': '1', - 'Silk top': '1', - 'Silk bottom': '1', - 'Mask top': '1', - 'Mask bottom': '1', - 'Edge cuts': '1', - 'Margin': '1', - 'Inner1': '1', - 'Inner2': '1', - 'Dwgs_User': '1', - 'Comments_User': '1', - 'ECO1': '1', - 'ECO2': '1', - 'Fab bottom': '1', - 'Fab top': '1', - 'Adhesive bottom': '1', - 'Adhesive top': '1', - 'Courtyard bottom': '1', - 'Courtyard top': '1' - } - initLayers = [] - - for b in buttons: - buttons[b] = Variable() - buttons[b].set(1) - l = ttk.Checkbutton( - frame2, text=b, variable=buttons[b], onvalue=1, offvalue=0).pack(anchor='w') commitTop = Variable() - listTop = Listbox(frame3, bd=0, selectmode=SINGLE, exportselection=False, font='TkFixedFont') + listTop = Listbox( + frame2, + bd=0, + selectmode=SINGLE, + exportselection=False, + font='TkFixedFont') listTop.grid(column=0, row=0, sticky=(N, E, W)) - scrollTop = ttk.Scrollbar(frame3, orient=VERTICAL, command=listTop.yview) + scrollTop = ttk.Scrollbar(frame2, orient=VERTICAL, command=listTop.yview) scrollTop.grid(column=1, row=0, sticky=(N, E, W)) listTop['yscrollcommand'] = scrollTop.set @@ -123,59 +95,38 @@ def runGUI(checkouts_top, prjctName, prjctPath, scm): commitBottom = Variable() listBottom = Listbox( - frame4, + frame3, bd=0, selectmode=SINGLE, exportselection=False, font='TkFixedFont') listBottom.grid(column=0, row=0, sticky=(N, E, W)) scrollBottom = ttk.Scrollbar( - frame4, orient=VERTICAL, command=listBottom.yview) + frame3, orient=VERTICAL, command=listBottom.yview) scrollBottom.grid(column=1, row=0, sticky=(N, E, W)) listBottom['yscrollcommand'] = scrollBottom.set listBottom.bind('<>', CurSelect) + frame2.grid_columnconfigure(0, weight=1) + frame2.grid_columnconfigure(1, weight=0) + frame2.grid_rowconfigure(0, weight=1) + frame3.grid_columnconfigure(0, weight=1) frame3.grid_columnconfigure(1, weight=0) frame3.grid_rowconfigure(0, weight=1) - frame4.grid_columnconfigure(0, weight=1) - frame4.grid_columnconfigure(1, weight=0) - frame4.grid_rowconfigure(0, weight=1) - buttonOK = ttk.Button( - frame5, text="OK", command=runProgram, default='active') - buttonOK.grid(column=7, row=0, sticky='w', pady=10) + frame4, text="OK", command=runProgram, default='active') + buttonOK.grid(column=2, row=0, sticky='w', pady=10) - buttonCancel = ttk.Button(frame5, text="Cancel", command=quit) - buttonCancel.grid(column=6, row=0, sticky='e', pady=10) + buttonCancel = ttk.Button(frame4, text="Cancel", command=quit) + buttonCancel.grid(column=1, row=0, sticky='e', pady=10) - resolution = IntVar() - resolution.set(300) - button100 = ttk.Radiobutton( - frame5, text="100", variable=resolution, value=100) - button300 = ttk.Radiobutton( - frame5, text="300", variable=resolution, value=300, ) - button600 = ttk.Radiobutton( - frame5, text="600", variable=resolution, value=600) - button1000 = ttk.Radiobutton( - frame5, text="1000", variable=resolution, value=1000) + frame4.grid_columnconfigure(0, weight=0) + frame4.grid_columnconfigure(1, weight=0) + frame4.grid_columnconfigure(2, weight=10) - button100.grid(column=1, row=0) - button300.grid(column=2, row=0) - button600.grid(column=3, row=0) - button1000.grid(column=4, row=0) - - frame5.grid_columnconfigure(0, weight=0) - frame5.grid_columnconfigure(1, weight=0) - frame5.grid_columnconfigure(2, weight=0) - frame5.grid_columnconfigure(3, weight=0) - frame5.grid_columnconfigure(4, weight=0) - frame5.grid_columnconfigure(5, weight=5) - frame5.grid_columnconfigure(6, weight=0) - frame5.grid_columnconfigure(7, weight=1) - frame5.grid_rowconfigure(0, weight=1) for line in checkouts_top: listTop.insert(END, line) @@ -198,4 +149,4 @@ def runGUI(checkouts_top, prjctName, prjctPath, scm): root.update() root.mainloop() - return(resolution, commitTop, commitBottom, buttons) + return(commitTop, commitBottom)