• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

dfint / csv-bisect-gui / 16000538642

01 Jul 2025 01:20PM UTC coverage: 60.652% (+1.3%) from 59.314%
16000538642

push

github

insolor
Fix new ruff warnings

27 of 78 branches covered (34.62%)

Branch coverage included in aggregate %.

0 of 3 new or added lines in 1 file covered. (0.0%)

215 of 321 relevant lines covered (66.98%)

0.67 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

39.52
/csv_bisect_gui/app.py
1
import platform
1✔
2
import shutil
1✔
3
import tkinter as tk
1✔
4
import traceback
1✔
5
from pathlib import Path
1✔
6
from tkinter import filedialog, messagebox, ttk
1✔
7
from typing import Any
1✔
8

9
import alternative_encodings
1✔
10
from tkinter_layout_helpers import pack_manager
1✔
11

12
from csv_bisect_gui.bisect_tool import BisectTool
1✔
13

14
alternative_encodings.register_all()
1✔
15

16
encodings = [
1✔
17
    "cp437",
18
    "cp850",
19
    "cp852",
20
    "cp857",
21
    "cp859",
22
    "cp866",
23
    "cp866i",
24
    "cp866u",
25
    "cp1251",
26
    "latin3",
27
    "latin9",
28
    "romaji",
29
    "viscii",
30
    "utf-8",
31
]
32

33
is_windows = platform.system() == "Windows"
1✔
34

35

36
class Window(tk.Tk):
1✔
37
    bisect_tool: BisectTool[str]
1✔
38

39
    file_types: list[tuple[str, str]]
1✔
40

41
    executable_path: Path | None
1✔
42
    csv_path: Path | None
1✔
43
    csv_backup_path: Path | None = None
1✔
44

45
    raw_data: list[bytes] | None
1✔
46

47
    def __init__(self, *args: list[Any], **kwargs: dict[str, Any]) -> None:
1✔
48
        super().__init__(*args, **kwargs)
1✔
49

50
        self.file_types = self.init_file_types()
1✔
51
        self.config(menu=self.create_main_menu())
1✔
52

53
        self.combo_encodings = self.init_combo_encodings()
1✔
54

55
        self.bisect_tool = BisectTool[str](self)
1✔
56
        self.bisect_tool.pack(fill=tk.BOTH, expand=True)
1✔
57

58
        with pack_manager(self, side=tk.LEFT, expand=True, fill=tk.X, padx=1) as toolbar:
1✔
59
            toolbar.pack_all(
1✔
60
                ttk.Button(text="Write selection to csv", command=self.write_csv),
61
                ttk.Button(text="Exclude selection from csv", command=self.exclude_from_csv),
62
                ttk.Button(text="Restore csv from backup", command=self.restore_backup),
63
            )
64

65
    def init_combo_encodings(self) -> ttk.Combobox:
1✔
66
        frame = tk.Frame(self)
1✔
67

68
        label = tk.Label(frame, text="csv encoding:")
1✔
69
        label.pack(side=tk.LEFT)
1✔
70

71
        combo_encodings = ttk.Combobox(frame, state="readonly")
1✔
72
        combo_encodings.config(values=encodings)
1✔
73
        combo_encodings.pack(fill=tk.X, expand=True)
1✔
74
        combo_encodings.set(encodings[0])
1✔
75
        combo_encodings.bind("<<ComboboxSelected>>", lambda _: self.load_csv())
1✔
76

77
        frame.pack(fill=tk.X)
1✔
78

79
        return combo_encodings
1✔
80

81
    @staticmethod
1✔
82
    def init_file_types() -> list[tuple[str, str]]:
1✔
83
        dwarfort = ("dwarfort", "dwarfort")
1✔
84
        file_types = [
1✔
85
            ("exe files", "*.exe"),
86
            dwarfort,
87
            ("csv files", "*.csv"),
88
            ("All files", "*.*"),
89
        ]
90

91
        if is_windows:
1!
92
            file_types.remove(dwarfort)
1✔
93

94
        return file_types
1✔
95

96
    def create_main_menu(self) -> tk.Menu:
1✔
97
        file_menu = tk.Menu(tearoff=False)
1✔
98
        file_menu.add_command(label="Open", command=self.select_file)
1✔
99
        file_menu.add_separator()
1✔
100
        file_menu.add_command(label="Exit", command=self.destroy)
1✔
101

102
        main_menu = tk.Menu()
1✔
103
        main_menu.add_cascade(label="File", menu=file_menu)
1✔
104
        return main_menu
1✔
105

106
    def select_file(self) -> None:
1✔
107
        file_path = filedialog.askopenfilename(
×
108
            title="Select an executable or a csv file",
109
            filetypes=self.file_types,
110
        )
111

112
        if not file_path:
×
113
            return
×
114

115
        self.process_selected_file(Path(file_path))
×
116

117
    def process_selected_file(self, file_path: Path) -> None:
1✔
118
        file_path = Path(file_path)
×
119
        if file_path.suffix == ".csv":
×
120
            self.executable_path = None
×
121
            self.csv_path = file_path
×
122
        elif file_path.suffix == ".exe" or file_path.name == "dwarfort":
×
123
            self.executable_path = file_path
×
124
            self.csv_path = file_path.parent / "dfint_data" / "dfint_dictionary.csv"
×
125
            if not self.csv_path.exists():
×
126
                self.csv_path = file_path.parent / "dfint-data" / "dictionary.csv"
×
127
        else:
128
            self.executable_path = None
×
129
            self.csv_path = None
×
130

131
        if self.csv_path and self.csv_path.is_file:
×
132
            self.csv_backup_path = self.csv_path.with_suffix(".bac")
×
133
            self.backup_csv()
×
134

135
            self.raw_data = None
×
136
            self.load_csv()
×
137

138
    def load_csv(self) -> None:
1✔
139
        if not self.csv_path:
×
140
            return
×
141

142
        if not self.raw_data:
×
143
            with open(self.csv_path, "rb") as file:
×
144
                self.raw_data = file.readlines()
×
145

146
        encoding = self.combo_encodings.get()
×
147
        try:
×
148
            decoded = [item.rstrip(b"\r\n").decode(encoding) for item in self.raw_data]
×
149
            self.bisect_tool.strings = decoded
×
150
        except UnicodeDecodeError:
×
151
            messagebox.showerror("ERROR", f"Failed to decode using {encoding} encoding")
×
152

153
    def write_csv(self) -> None:
1✔
154
        if not self.csv_path:
×
155
            return
×
156

157
        with open(self.csv_path, "wb") as file:
×
158
            for node in self.bisect_tool.selected_nodes:
×
NEW
159
                file.writelines(self.raw_data[node.slice])
×
160

161
    def exclude_from_csv(self) -> None:
1✔
162
        if not self.csv_path:
×
163
            return
×
164

165
        selection = list(self.bisect_tool.selected_nodes)
×
166

167
        if len(selection) > 1:
×
168
            messagebox.showerror("ERROR", "Select one row")
×
169
            return
×
170

171
        node = selection[0]
×
172
        selection_slice = node.slice
×
173

174
        before_selection = slice(0, selection_slice.start)
×
175
        after_selection = slice(selection_slice.stop, -1)
×
176

177
        with open(self.csv_path, "wb") as file:
×
NEW
178
            file.writelines(self.raw_data[before_selection])
×
179

NEW
180
            file.writelines(self.raw_data[after_selection])
×
181

182
    def backup_csv(self) -> None:
1✔
183
        if self.csv_backup_path.exists() and self.csv_backup_path.read_bytes() == self.csv_path.read_bytes():
×
184
            return
×
185

186
        self.check_backup()
×
187

188
        shutil.copyfile(self.csv_path, self.csv_backup_path)
×
189

190
    def restore_backup(self) -> None:
1✔
191
        if not self.csv_backup_path:
×
192
            return
×
193

194
        shutil.copyfile(self.csv_backup_path, self.csv_path)
×
195

196
    def check_backup(self) -> None:
1✔
197
        """
198
        Check if the backup file exists. If it does, ask the user if they want to restore backup.
199
        """
200
        if not self.csv_backup_path.exists():
×
201
            return
×
202

203
        response = messagebox.askyesno(
×
204
            "BACKUP EXISTS",
205
            "Backup of the csv file is already exists.\n"
206
            "Restore backup? (otherwise the backup file will be overwritten)",
207
        )
208

209
        if response == tk.YES:
×
210
            self.restore_backup()
×
211

212
    def report_callback_exception(self, exc, val, tb) -> None:  # noqa: ANN001
1✔
213
        if issubclass(exc, KeyboardInterrupt):
×
214
            self.quit()
×
215
            return
×
216

217
        super().report_callback_exception(exc, val, tb)
×
218

219
        filename, line, *_ = traceback.extract_tb(tb).pop()
×
220
        messagebox.showerror("Unhandled Exception", f"{exc.__name__}: {val}\n{filename}, line: {line}")
×
221

222

223
def main() -> None:
1✔
224
    Window().mainloop()
×
225

226

227
if __name__ == "__main__":
228
    main()
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc