瀏覽代碼

Refactor: Ugly but it works

bryan 4 年之前
父節點
當前提交
dd22e82d07
共有 1 個文件被更改,包括 216 次插入96 次删除
  1. 216 96
      openwrt-build-gui.py

+ 216 - 96
openwrt-build-gui.py

@@ -1,133 +1,253 @@
 #!/usr/bin/env python3
-
 import tkinter as tk
 from tkinter import filedialog
 from tkinter import ttk
 import os.path
-import pandas
+import csv
+import json
 import gzip
 import urllib.request
 
-# This will enable file caching and some dev output
-debug=True
+#####################################################################
+######################## VARIABLES ##################################
+#####################################################################
 
+# Enable file caching and some dev output
+debug=True
 
-class Main(tk.Frame):
-    """This module generates information for the tkinter gui"""
+#####################################################################
+######################## FUNCTIONS ##################################
+#####################################################################
+
+def get_toh():
+    """
+    Retrieves the openwrt.org table of hardware and returns it as a list of dicts
+    """
+    toh_cache_file = "sources/toh.tsv"
+    if debug is True and os.path.isfile(toh_cache_file):
+        with open(toh_cache_file) as infile:
+            toh = json.load(infile)
+    else:
+        toh_gz_handle = urllib.request.urlopen("https://openwrt.org/_media/toh_dump_tab_separated.gz")
+        toh_handle = gzip.open(toh_gz_handle, mode='rt', encoding='ISO-8859-1')
+        toh_dict = csv.DictReader(toh_handle, delimiter='\t')
+        # Convert the DictReader object to a native list of dicts
+        toh = list(toh_dict)
+
+    # Sanitize it
+    junk = ['', 'nan', 'NULL', '-', '?', '¿', ' ']
+    toh = [d for d in toh if d['target'] not in junk and d['subtarget'] not in junk]
+
+    # Save to cache file
+    with open(toh_cache_file, 'w') as outfile:
+        json.dump(toh, outfile)
+
+    return toh
+
+#####################################################################
+######################## CLASSES ####################################
+#####################################################################
+
+class DeviceFrame(tk.Frame):
+    """This class renders the device information frame"""
 
     def __init__(self, parent, *args, **kwargs):
         super().__init__(parent, *args, **kwargs)
 
-        # Get Table of Hardware (toh) from openwrt.org and save to a pandas df
-        self.toh_df = self.get_toh_df()
-
-        # Target menu
-        self.target = tk.StringVar()
-        self.targets, self.target_menu = self.gen_target_menu()
-        self.target.set(self.targets[0])
-
-        # Subtarget menu
-        self.subtarget = tk.StringVar()
-        self.subtargets, self.subtarget_menu = self.gen_subtarget_menu()
-        self.subtarget.set(self.subtargets[0])
-        self.target.trace("w", lambda var_name, var_index, operation: self.gen_subtarget_menu(regen=True))
-
-        # Backup file
-        self.backup_file = tk.StringVar()
-        self.backup_file_entry = ttk.Entry(self, textvariable=self.backup_file)
-        self.backup_file_button = ttk.Button(self, text="Select backup file", command=self.open_sysbackup)
-
-        # Device Info
-        self.device_info = tk.StringVar()
-
-
-        # Layout
-        self.target_menu.grid(row=0, column=0, sticky=(tk.W + tk.E))
-        self.subtarget_menu.grid(row=0, column=1, sticky=(tk.W + tk.E))
-        self.backup_file_entry.grid(row=1, column=1, sticky=(tk.W + tk.E))
-        self.backup_file_button.grid(row=1, column=0, sticky=(tk.W))
-        self.columnconfigure(1, weight=1)
-
-
-    
-    def get_toh_df(self):
-
-        cache_file = "sources/toh.tsv"
-
-        if debug is True and os.path.isfile(cache_file):
-            with open(cache_file) as infile:
-                try:
-                    toh_df = pandas.read_csv(infile)
-                except:
-                    os.remove(cache_file)
-        else:
-            toh_gz_handle = urllib.request.urlopen("https://openwrt.org/_media/toh_dump_tab_separated.gz")
-            toh_handle = gzip.open(toh_gz_handle)
-            toh_df = pandas.read_csv(toh_handle, sep="\t", encoding = "ISO-8859-1")
-            toh_df = toh_df[(toh_df.target.notnull()) & 
-                                (toh_df.subtarget.notnull()) &
-                                (toh_df.target != '¿') &
-                                (toh_df.subtarget != '¿')]
-            if debug is True:
-                with open(cache_file, 'w') as outfile:
-                    toh_df.to_csv(outfile)
-        return toh_df
+        # Make a dict to hold our widgets
+        self.widgets = {}
+
+        # Get the Table of Hardware (toh) data frame from openwrt.org
+        self.toh = get_toh()
+
+        # Create device frame and widgets
+        self.device_frame = tk.LabelFrame(self, text="Select Device")
+        self.device_frame.grid(row=0, column=0, sticky=(tk.W + tk.E))
+        self.info_frame = tk.LabelFrame(self, text="Info")
+        self.info_frame.grid(row=1, column=0, sticky=(tk.W + tk.E))
+        self.version_frame = tk.LabelFrame(self, text="Version")
+        self.version_frame.grid(row=2, column=0, sticky=(tk.W + tk.E))
+
+        self.targets_widget(parent)
+        self.subtargets_widget(parent)
+        self.info_widget(parent)
+        self.version_widget(parent)
+
+        # Create some traces to repopulate the subtarget menu and info widget
+        parent.target.trace("w", lambda var_name, var_index, operation: self.subtargets_widget(parent, regen=True))
+        parent.subtarget.trace("w", lambda var_name, var_index, operation: self.info_widget(parent, regen=True))
+
+        # Place the widgets in the device frame
+        self.widgets['Target'].grid(row=0, column=0)
+        self.widgets['Subtarget'].grid(row=1, column=0)
+        self.widgets['Version'].grid(row=0, column=0)
+        self.widgets['Info'].grid(row=0, column=1)
         
 
-    def gen_target_menu(self, *args, **kwargs):
-        targets = sorted(set(self.toh_df.target.tolist()))
-        target_menu = ttk.OptionMenu(self, self.target, targets[0], *targets)
-        return targets, target_menu
-
+    def targets_widget(self, parent):
+        """A Combobox of targets from the ToH"""
+
+        targets = [k['target'] for k in self.toh]
+        targets = sorted(set(targets))
+        parent.target.set(targets[0])
+
+        self.widgets['Target'] = LabelInput(
+            self.device_frame,
+            label='Target:',
+            input_class=ttk.Combobox,
+            input_var=parent.target,
+            options_list=targets
+        )
+
+
+    def subtargets_widget(self, parent, regen=None):
+        """A Combobox of subtargets of the current target"""
+        regen = regen or False
+        subtargets = [d['subtarget'] for d in self.toh if d['target'] == parent.target.get()]
+        subtargets = sorted(set(subtargets))
+        parent.subtarget.set(subtargets[0])
+        if regen is True:
+            self.widgets['Subtarget'].input['values'] = subtargets
+            return
+        self.widgets['Subtarget'] = LabelInput(
+            self.device_frame,
+            label='Subtarget:',
+            input_class=ttk.Combobox,
+            input_var=parent.subtarget,
+            options_list=subtargets
+        )
+
+
+    def info_widget(self, parent, regen=None):
+        """An InfoBox of info about the current subtarget"""
+        regen = regen or False
+        # Add any stat that you wish to display to this list
+        stats = {'devicetype': 'Type', 'brand': 'Brand', 'model': 'Model', 'cpu': 'CPU', 'supportedsincerel': 'First Release', 
+            'supportedcurrentrel': 'Current Release', 'packagearchitecture': 'Arch',  'wikideviurl': 'Wiki'}
+        for device in self.toh:
+            if device['target'] == parent.target.get() and device['subtarget'] == parent.subtarget.get():
+                break
+        # Stringify
+        stats_str = '\n'.join('{}: {}'.format(stats[k], v) for k, v in device.items() if k in stats)
+        parent.subtarget_info.set(stats_str)
+        if regen is True:
+            return
+        self.widgets['Info'] = LabelInput(
+            self.device_frame,
+            label='',
+            input_class=ttk.Label,
+            input_var=parent.subtarget_info,
+            textvariable=parent.subtarget_info,
+        )
+
+
+    def version_widget(self, parent):
+        """A Combobox of version numbers"""
+        # A space-delim'd string of version numbers TODO: make automatic from scraped data (repo tags?)
+        versions = 'snapshot 12.09'
+
+        parent.version.set('snapshot')
+
+        self.widgets['Version'] = LabelInput(
+            self.version_frame,
+            label='',
+            input_class=ttk.Combobox,
+            input_var=parent.version,
+            options_list=versions
+        )
+
+
+ 
+class LabelInput(tk.Frame):
+    """A widget containing a label and input together."""
+
+    def __init__(self, parent, *args,  label='', input_class=ttk.Entry,
+                 input_var=None, input_args=None, label_args=None, options_list=None,
+                 **kwargs):
+        super().__init__(parent)
+        input_args = input_args or {}
+        label_args = label_args or {}
+        options_list = options_list or []
+        self.variable = input_var
+
+        if input_class in (ttk.Checkbutton, ttk.Button, ttk.Radiobutton):
+            input_args["text"] = label
+            input_args["variable"] = input_var
+        else:
+            self.label = ttk.Label(self, text=label, **label_args)
+            self.label.grid(row=0, column=0, sticky=(tk.W + tk.E))
+            input_args["textvariable"] = input_var
+            if input_class in (ttk.OptionMenu, ttk.Combobox):
+                input_args["values"] = options_list
+            
+
+        if input_class is ttk.OptionMenu:
+            self.input = input_class(self, self.variable, *options_list)
+        else:
+            self.input = input_class(self, **input_args)
 
-    def gen_subtarget_menu(self, regen=False, *args, **kwargs):
-        subtargets_df = self.toh_df[self.toh_df.target == self.target.get()]
-        subtargets = sorted(set(subtargets_df.subtarget.tolist()))
-        if regen is False:
-            subtarget_menu = ttk.OptionMenu(self, self.subtarget, subtargets[0], *subtargets)
-            return subtargets, subtarget_menu
-        else: # Regen
-            self.subtarget_menu["menu"].delete(0, "end")
-            for subtarget in subtargets:
-                self.subtarget_menu["menu"].add_command(label=subtarget, command=lambda value=subtarget: self.subtarget.set(subtarget))
-            self.subtarget.set(subtargets[0])
+        self.input.grid(row=1, column=0, sticky=(tk.W + tk.E))
+        self.columnconfigure(0, weight=1)
 
+    def grid(self, sticky=(tk.E + tk.W), **kwargs):
+        super().grid(sticky=sticky, **kwargs)
 
-    def open_sysbackup(self):
-        file = filedialog.askopenfile(mode ='r', title="Select Backup File", filetypes=[("OpenWRT sysbackup file", ".tar.gz")])
-        if file is not None:
-            self.backup_file.set(file.name)
+    def get(self):
+        if self.variable:
+            return self.variable.get()
+        elif type(self.input) == tk.Text:
+            return self.input.get('1.0', tk.END)
+        else:
+            return self.input.get()
+
+    def set(self, value, *args, **kwargs):
+        if type(self.variable) == tk.BooleanVar:
+                self.variable.set(bool(value))
+        elif self.variable:
+                self.variable.set(value, *args, **kwargs)
+        elif type(self.input).__name__.endswith('button'):
+            if value:
+                self.input.select()
+            else:
+                self.input.deselect()
+        elif type(self.input) == tk.Text:
+            self.input.delete('1.0', tk.END)
+            self.input.insert('1.0', value)
+        else:
+            self.input.delete(0, tk.END)
+            self.input.insert(0, value)
 
-        
 
 
 class GUI(tk.Tk):
-    """Create the GUI"""
+    """Application root window"""
 
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
 
         # Set the window properties
         self.title("openwrt-build")
-        self.geometry("1280x960")
+        self.geometry("800x600")
         self.resizable(width=False, height=False)
 
-        # Define the UI
-        Main(self).grid(sticky=(tk.E + tk.W + tk.N + tk.S))
-        self.columnconfigure(0, weight=1)
+        ttk.Label(
+            self,
+            text="OpenWRT Image Builder",
+            font=("TkDefaultFont", 16)
+        ).grid(row=0)
+
+        # Let's store the global tk vars in this top-level class
+        self.target = tk.StringVar()
+        self.subtarget = tk.StringVar()
+        self.subtarget_info = tk.StringVar()
+        self.version = tk.StringVar()
+
+        # Add each frame class
+        self.device_frame = DeviceFrame(self)
+        self.device_frame.grid(row=1, column=0, sticky=(tk.W + tk.E))
 
 
 if __name__ == '__main__':
     app = GUI()
     app.mainloop()
 
-
-
-#
-#        , usecols=['brand',
-#         'model', 
-#          'supportedsincerel', 
-#           'supportedcurrentrel', 
-#          'target', 
-#        'subtarget']
-#