bash-script-maker 1.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
bash_script_maker.py ADDED
@@ -0,0 +1,822 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Bash-Script-Maker - Ein GUI-Programm zur Erstellung von Bash-Scripts
5
+ """
6
+
7
+ import tkinter as tk
8
+ from tkinter import scrolledtext, messagebox
9
+ import ttkbootstrap as ttk
10
+ from ttkbootstrap.constants import *
11
+ from ttkbootstrap.tooltip import ToolTip
12
+ import subprocess
13
+ import os
14
+ import sys
15
+ import webbrowser
16
+ from datetime import datetime
17
+ from tkinter import font as tkfont
18
+ from syntax_highlighter import EnhancedScriptEditor
19
+ from localization import _, save_language_setting
20
+ import configparser
21
+ from custom_dialogs import askopenfilename, asksaveasfilename
22
+
23
+
24
+ class FontSettingsDialog(ttk.Toplevel):
25
+ def __init__(self, parent, editor):
26
+ super().__init__(master=parent)
27
+ self.title(_("Schriftart anpassen"))
28
+ self.geometry("350x300") # Feste Startgröße
29
+ self.editor = editor
30
+
31
+ # Aktuelle Schriftart ermitteln
32
+ font_tuple = editor.base_font
33
+ current_font_family = font_tuple[0]
34
+ current_font_size = font_tuple[1]
35
+
36
+ # UI Elemente
37
+ container = ttk.Frame(self, padding=15)
38
+ container.pack(fill=tk.BOTH, expand=True)
39
+
40
+ # Schriftfamilie
41
+ font_frame = ttk.Labelframe(container, text=_("Schriftart"), padding=10)
42
+ font_frame.pack(fill=tk.X, pady=5)
43
+
44
+ # Empfohlene Schriftarten für Code
45
+ preferred_fonts = [
46
+ "Source Code Pro",
47
+ "Consolas",
48
+ "Courier New",
49
+ "DejaVu Sans Mono",
50
+ "Liberation Mono",
51
+ "Menlo",
52
+ "Monaco",
53
+ ]
54
+
55
+ try:
56
+ # Versuche, System-Schriftarten zu laden
57
+ system_fonts = sorted(
58
+ [f for f in tkfont.families() if not f.startswith("@")]
59
+ )
60
+ all_fonts = sorted(list(set(preferred_fonts + system_fonts)))
61
+ except Exception:
62
+ # Fallback, falls das Laden fehlschlägt
63
+ all_fonts = sorted(preferred_fonts)
64
+
65
+ if not all_fonts:
66
+ # Fallback, falls gar keine Schriftarten gefunden werden
67
+ all_fonts = ["Courier New", "Consolas", "Monaco"]
68
+ ttk.Label(
69
+ font_frame,
70
+ text=_(
71
+ "Keine System-Schriftarten gefunden.\nBitte installieren Sie Schriftart-Pakete (z.B. 'ttf-mscorefonts-installer')."
72
+ ),
73
+ bootstyle="warning",
74
+ ).pack(pady=5)
75
+
76
+ self.font_var = tk.StringVar(value=current_font_family)
77
+ self.font_combo = ttk.Combobox(
78
+ font_frame, textvariable=self.font_var, values=all_fonts
79
+ )
80
+ self.font_combo.pack(fill=tk.X)
81
+
82
+ # Schriftgröße
83
+ size_frame = ttk.Labelframe(container, text=_("Schriftgröße"), padding=10)
84
+ size_frame.pack(fill=tk.X, pady=5)
85
+
86
+ self.size_var = tk.IntVar(value=current_font_size)
87
+ self.size_spinbox = ttk.Spinbox(
88
+ size_frame, from_=8, to=72, textvariable=self.size_var
89
+ )
90
+ self.size_spinbox.pack(fill=tk.X)
91
+
92
+ # Buttons
93
+ button_frame = ttk.Frame(container)
94
+ button_frame.pack(fill=tk.X, pady=(10, 0))
95
+
96
+ apply_btn = ttk.Button(
97
+ button_frame,
98
+ text=_("Anwenden"),
99
+ command=self.apply_settings,
100
+ bootstyle="success",
101
+ )
102
+ apply_btn.pack(side=tk.RIGHT, padx=(5, 0))
103
+
104
+ close_btn = ttk.Button(
105
+ button_frame,
106
+ text=_("Schließen"),
107
+ command=self.destroy,
108
+ bootstyle="secondary",
109
+ )
110
+ close_btn.pack(side=tk.RIGHT)
111
+
112
+ def apply_settings(self):
113
+ family = self.font_var.get()
114
+ size = self.size_var.get()
115
+ if family and size:
116
+ self.editor.update_font(family, size)
117
+
118
+
119
+ class AboutDialog(ttk.Toplevel):
120
+ def __init__(self, parent):
121
+ super().__init__(parent)
122
+ self.title(_("Über Bash-Script-Maker"))
123
+ self.geometry("450x350")
124
+
125
+ container = ttk.Frame(self, padding=20)
126
+ container.pack(fill=tk.BOTH, expand=True)
127
+
128
+ title_label = ttk.Label(
129
+ container,
130
+ text="Bash-Script-Maker",
131
+ font=("", 18, "bold"),
132
+ bootstyle="primary",
133
+ )
134
+ title_label.pack(pady=(0, 10))
135
+
136
+ desc_text = _("Ein GUI-Programm zur einfachen Erstellung von Bash-Scripts.")
137
+ desc_label = ttk.Label(container, text=desc_text, wraplength=400)
138
+ desc_label.pack(pady=(0, 20))
139
+
140
+ features_frame = ttk.Labelframe(container, text=_("Funktionen"), padding=15)
141
+ features_frame.pack(fill=tk.BOTH, expand=True, pady=5)
142
+
143
+ features = [
144
+ _("Visuelle Script-Erstellung"),
145
+ _("Zenity-Dialog-Integration"),
146
+ _("Syntax-Hervorhebung"),
147
+ _("Script-Ausführung"),
148
+ _("Anpassbare Schriftart & Themes"),
149
+ ]
150
+ for feature in features:
151
+ ttk.Label(features_frame, text=f"• {feature}").pack(anchor=tk.W)
152
+
153
+ # Version dynamisch aus VERSION-Datei lesen (Fallback zu pyproject)
154
+ version_text = "1.0"
155
+ try:
156
+ base_dir = os.path.dirname(os.path.abspath(__file__))
157
+ version_file = os.path.join(base_dir, "VERSION")
158
+ if os.path.exists(version_file):
159
+ with open(version_file, "r", encoding="utf-8") as vf:
160
+ version_text = vf.read().strip()
161
+ else:
162
+ # Optionaler Fallback: pyproject.toml parsen
163
+ pyproj = os.path.join(base_dir, "pyproject.toml")
164
+ if os.path.exists(pyproj):
165
+ import re
166
+
167
+ with open(pyproj, "r", encoding="utf-8") as pf:
168
+ content = pf.read()
169
+ m = re.search(r"^version\s*=\s*\"([^\"]+)\"", content, re.M)
170
+ if m:
171
+ version_text = m.group(1)
172
+ except Exception:
173
+ pass
174
+
175
+ version_label = ttk.Label(
176
+ container, text=_("Version: {}").format(version_text), bootstyle="secondary"
177
+ )
178
+ version_label.pack(pady=(15, 0))
179
+
180
+
181
+ class ScriptInfoDialog(ttk.Toplevel):
182
+ def __init__(self, parent, script_name, content):
183
+ super().__init__(parent)
184
+ self.title(_("Script-Info"))
185
+ self.geometry("400x250")
186
+
187
+ container = ttk.Frame(self, padding=20)
188
+ container.pack(fill=tk.BOTH, expand=True)
189
+
190
+ title_label = ttk.Label(
191
+ container, text=script_name, font=("", 16, "bold"), bootstyle="info"
192
+ )
193
+ title_label.pack(pady=(0, 15))
194
+
195
+ # Berechnungen
196
+ lines = len(content.split("\n"))
197
+ chars = len(content)
198
+ size_bytes = len(content.encode("utf-8"))
199
+
200
+ # Info-Grid
201
+ info_frame = ttk.Frame(container)
202
+ info_frame.pack(fill=tk.X)
203
+
204
+ ttk.Label(info_frame, text=_("Zeilen:"), font=("", 10, "bold")).grid(
205
+ row=0, column=0, sticky=tk.W, padx=5, pady=2
206
+ )
207
+ ttk.Label(info_frame, text=f"{lines}").grid(
208
+ row=0, column=1, sticky=tk.W, padx=5, pady=2
209
+ )
210
+ ttk.Label(info_frame, text=_("Zeichen:"), font=("", 10, "bold")).grid(
211
+ row=1, column=0, sticky=tk.W, padx=5, pady=2
212
+ )
213
+ ttk.Label(info_frame, text=f"{chars}").grid(
214
+ row=1, column=1, sticky=tk.W, padx=5, pady=2
215
+ )
216
+ ttk.Label(info_frame, text=_("Größe:"), font=("", 10, "bold")).grid(
217
+ row=2, column=0, sticky=tk.W, padx=5, pady=2
218
+ )
219
+ ttk.Label(info_frame, text=f"{size_bytes} Bytes").grid(
220
+ row=2, column=1, sticky=tk.W, padx=5, pady=2
221
+ )
222
+
223
+ close_btn = ttk.Button(
224
+ container, text=_("Schließen"), command=self.destroy, bootstyle="secondary"
225
+ )
226
+ close_btn.pack(side=tk.BOTTOM, pady=(20, 0))
227
+
228
+
229
+ class BashScriptMaker:
230
+ def __init__(self, root):
231
+ self.root = root
232
+ self.root.title("Bash-Script-Maker")
233
+ self.root.geometry("1200x800")
234
+
235
+ # Variablen für den letzten Pfad
236
+ self.last_path = os.path.expanduser("~")
237
+
238
+ # Script-Variablen
239
+ self.current_script = ""
240
+ self.script_name = "mein_script.sh"
241
+
242
+ # GUI erstellen
243
+ self.create_menu()
244
+ main_container = self.create_main_interface()
245
+ self.create_command_palette(main_container)
246
+ self.create_script_editor(main_container)
247
+
248
+ # Willkommensnachricht
249
+ self.update_script_content(
250
+ "#!/bin/bash\n# Erstellt mit Bash-Script-Maker\n# "
251
+ + datetime.now().strftime("%Y-%m-%d %H:%M:%S")
252
+ + "\n\n"
253
+ )
254
+
255
+ def create_menu(self):
256
+ """Erstellt das Menü der Anwendung"""
257
+ menubar = tk.Menu(self.root)
258
+ self.root.config(menu=menubar)
259
+
260
+ # Datei-Menü
261
+ file_menu = tk.Menu(menubar, tearoff=0)
262
+ menubar.add_cascade(label=_("Datei"), menu=file_menu)
263
+ file_menu.add_command(
264
+ label=_("Neu"), command=self.new_script, accelerator="Ctrl+N"
265
+ )
266
+ file_menu.add_command(
267
+ label=_("Öffnen"), command=self.open_script, accelerator="Ctrl+O"
268
+ )
269
+ file_menu.add_command(
270
+ label=_("Speichern"), command=self.save_script, accelerator="Ctrl+S"
271
+ )
272
+ file_menu.add_command(
273
+ label=_("Speichern unter"),
274
+ command=self.save_script_as,
275
+ accelerator="Ctrl+Shift+S",
276
+ )
277
+ file_menu.add_separator()
278
+ file_menu.add_command(
279
+ label=_("Beenden"), command=self.root.quit, accelerator="Ctrl+Q"
280
+ )
281
+
282
+ # Bearbeiten-Menü
283
+ edit_menu = tk.Menu(menubar, tearoff=0)
284
+ menubar.add_cascade(label=_("Bearbeiten"), menu=edit_menu)
285
+ edit_menu.add_command(
286
+ label=_("Rückgängig"),
287
+ command=lambda: self.text_editor.edit_undo(),
288
+ accelerator="Ctrl+Z",
289
+ )
290
+ edit_menu.add_command(
291
+ label=_("Wiederholen"),
292
+ command=lambda: self.text_editor.edit_redo(),
293
+ accelerator="Ctrl+Y",
294
+ )
295
+ edit_menu.add_separator()
296
+ edit_menu.add_command(
297
+ label=_("Ausschneiden"),
298
+ command=lambda: self.text_editor.event_generate("<<Cut>>"),
299
+ accelerator="Ctrl+X",
300
+ )
301
+ edit_menu.add_command(
302
+ label=_("Kopieren"),
303
+ command=lambda: self.text_editor.event_generate("<<Copy>>"),
304
+ accelerator="Ctrl+C",
305
+ )
306
+ edit_menu.add_command(
307
+ label=_("Einfügen"),
308
+ command=lambda: self.text_editor.event_generate("<<Paste>>"),
309
+ accelerator="Ctrl+V",
310
+ )
311
+
312
+ # Skript-Menü
313
+ script_menu = tk.Menu(menubar, tearoff=0)
314
+ menubar.add_cascade(label=_("Skript"), menu=script_menu)
315
+ script_menu.add_command(
316
+ label=_("Ausführen"), command=self.execute_script, accelerator="F5"
317
+ )
318
+ script_menu.add_command(
319
+ label=_("Als ausführbar markieren"), command=self.make_executable
320
+ )
321
+ script_menu.add_separator()
322
+ script_menu.add_command(
323
+ label=_("Skript-Info anzeigen"), command=self.show_script_info
324
+ )
325
+
326
+ # Einstellungen-Menü
327
+ settings_menu = tk.Menu(menubar, tearoff=0)
328
+ menubar.add_cascade(label=_("Einstellungen"), menu=settings_menu)
329
+ settings_menu.add_command(
330
+ label=_("Schriftart anpassen..."), command=self.open_font_dialog
331
+ )
332
+ # --- Sprachauswahl ---
333
+ language_menu = tk.Menu(settings_menu, tearoff=0)
334
+ settings_menu.add_cascade(label=_("Sprache"), menu=language_menu)
335
+ language_menu.add_command(
336
+ label="Deutsch", command=lambda: self.change_language("de")
337
+ )
338
+ language_menu.add_command(
339
+ label="English", command=lambda: self.change_language("en")
340
+ )
341
+
342
+ # Hilfe-Menü
343
+ help_menu = tk.Menu(menubar, tearoff=0)
344
+ menubar.add_cascade(label=_("Hilfe"), menu=help_menu)
345
+ help_menu.add_command(
346
+ label=_("Dokumentation (README)"), command=self.open_documentation
347
+ )
348
+ help_menu.add_command(label="Secure-Bits Blog", command=self.open_blog)
349
+ help_menu.add_separator()
350
+ help_menu.add_command(label=_("Über"), command=self.show_about)
351
+
352
+ # Tastenkombinationen
353
+ self.root.bind("<Control-n>", lambda e: self.new_script())
354
+ self.root.bind("<Control-o>", lambda e: self.open_script())
355
+ self.root.bind("<Control-s>", lambda e: self.save_script())
356
+ self.root.bind("<Control-Shift-S>", lambda e: self.save_script_as())
357
+ self.root.bind("<Control-q>", lambda e: self.root.quit())
358
+ self.root.bind("<F5>", lambda e: self.execute_script())
359
+
360
+ def create_main_interface(self):
361
+ """Erstellt die Hauptbenutzeroberfläche und den Container für die Haupt-Widgets."""
362
+ # Statusleiste ganz unten platzieren
363
+ self.status_var = tk.StringVar()
364
+ self.status_var.set(_("Bereit"))
365
+ status_bar = ttk.Label(
366
+ self.root,
367
+ textvariable=self.status_var,
368
+ relief=tk.SUNKEN,
369
+ anchor=tk.W,
370
+ bootstyle="inverse-dark",
371
+ padding=5,
372
+ )
373
+ status_bar.pack(side=tk.BOTTOM, fill=tk.X)
374
+
375
+ # Toolbar ganz oben platzieren
376
+ toolbar = ttk.Frame(self.root)
377
+ toolbar.pack(side=tk.TOP, fill=tk.X, padx=5, pady=(5, 0))
378
+
379
+ # Toolbar-Inhalte - LINKS
380
+ btn_new = ttk.Button(
381
+ toolbar, text=_("Neu"), command=self.new_script, bootstyle="primary"
382
+ )
383
+ btn_new.pack(side=tk.LEFT, padx=(0, 5), pady=20)
384
+ ToolTip(btn_new, text=_("Erstellt ein neues, leeres Script (Ctrl+N)"))
385
+ btn_open = ttk.Button(
386
+ toolbar, text=_("Öffnen"), command=self.open_script, bootstyle="secondary"
387
+ )
388
+ btn_open.pack(side=tk.LEFT, padx=(5, 0), pady=20)
389
+ ToolTip(btn_open, text=_("Öffnet ein vorhandenes Script (Ctrl+O)"))
390
+ btn_save = ttk.Button(
391
+ toolbar, text=_("Speichern"), command=self.save_script, bootstyle="info"
392
+ )
393
+ btn_save.pack(side=tk.LEFT, padx=(5, 0), pady=20)
394
+ ToolTip(btn_save, text=_("Speichert das aktuelle Script (Ctrl+S)"))
395
+ btn_exec = ttk.Button(
396
+ toolbar,
397
+ text=_("Ausführen"),
398
+ command=self.execute_script,
399
+ bootstyle="success",
400
+ )
401
+ btn_exec.pack(
402
+ side=tk.LEFT, padx=(5, 15), pady=20
403
+ ) # Extra Abstand nach dem letzten Button
404
+ ToolTip(btn_exec, text=_("Führt das aktuelle Script aus (F5)"))
405
+
406
+ # Separator für visuelle Trennung
407
+ separator = ttk.Separator(toolbar, orient=tk.VERTICAL)
408
+ separator.pack(side=tk.LEFT, fill="y", padx=5, pady=20)
409
+
410
+ # Toolbar-Inhalte - RECHTS
411
+ script_name_label = ttk.Label(toolbar, text=_("Script-Name:"))
412
+ script_name_label.pack(side=tk.LEFT, padx=(5, 5), pady=20)
413
+
414
+ self.name_entry = ttk.Entry(toolbar, width=40)
415
+ self.name_entry.pack(
416
+ side=tk.LEFT, padx=(0, 10), fill=tk.X, expand=True, pady=20
417
+ )
418
+ self.name_entry.insert(0, self.script_name)
419
+ self.name_entry.bind("<KeyRelease>", self.update_script_name)
420
+
421
+ # Hauptcontainer für linke und rechte Spalte (füllt den Rest des Platzes)
422
+ main_container = ttk.Frame(self.root)
423
+ main_container.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
424
+
425
+ main_container.grid_rowconfigure(0, weight=1)
426
+ main_container.grid_columnconfigure(1, weight=1) # Rechte Spalte soll wachsen
427
+
428
+ return main_container
429
+
430
+ def create_command_palette(self, parent):
431
+ """Erstellt die Befehlspalette mit verfügbaren Komponenten"""
432
+ left_frame = ttk.LabelFrame(parent, text=_("Befehle & Komponenten"), padding=5)
433
+ left_frame.grid(row=0, column=0, sticky="nswe")
434
+
435
+ self.notebook = ttk.Notebook(left_frame, bootstyle="primary")
436
+ self.notebook.pack(fill=tk.BOTH, expand=True)
437
+
438
+ # Grundbefehle Tab
439
+ basic_tab = ttk.Frame(self.notebook)
440
+ self.notebook.add(basic_tab, text=_("Grundlagen"))
441
+
442
+ basic_commands = [
443
+ ("#!/bin/bash", _("Shebang")),
444
+ ('echo "Hallo Welt"', _("Echo-Befehl")),
445
+ ('read -p "Eingabe: " variable', _("Eingabe lesen")),
446
+ ("if [ ]; then\nfi", _("If-Bedingung")),
447
+ ("for i in {1..10}; do\necho $i\ndone", _("For-Schleife")),
448
+ ("while [ ]; do\ndone", _("While-Schleife")),
449
+ ("case $var in\n pattern) ;;\n *) ;;\nesac", _("Case-Anweisung")),
450
+ ("function name() {\n}", _("Funktion")),
451
+ ]
452
+
453
+ self.create_command_buttons(basic_tab, basic_commands)
454
+
455
+ # Zenity Tab
456
+ zenity_tab = ttk.Frame(self.notebook)
457
+ self.notebook.add(zenity_tab, text="Zenity")
458
+
459
+ zenity_commands = [
460
+ ('zenity --info --text="Info"', _("Info-Dialog")),
461
+ ('zenity --error --text="Fehler"', _("Fehler-Dialog")),
462
+ ('zenity --warning --text="Warnung"', _("Warnungs-Dialog")),
463
+ ('zenity --question --text="Frage"', _("Frage-Dialog")),
464
+ ('result=$(zenity --entry --text="Eingabe")', _("Eingabedialog")),
465
+ ("file=$(zenity --file-selection)", _("Dateiauswahl")),
466
+ ('zenity --progress --text="Fortschritt"', _("Fortschrittsbalken")),
467
+ (
468
+ 'zenity --list --title="Liste" --column="Option"',
469
+ _("Listen-Dialog"),
470
+ ),
471
+ ]
472
+
473
+ self.create_command_buttons(zenity_tab, zenity_commands)
474
+
475
+ # System Tab
476
+ system_tab = ttk.Frame(self.notebook)
477
+ self.notebook.add(system_tab, text=_("System"))
478
+
479
+ system_commands = [
480
+ ("ls -la", _("Dateien auflisten")),
481
+ ("pwd", _("Aktuelles Verzeichnis")),
482
+ ("cd /pfad", _("Verzeichnis wechseln")),
483
+ ("mkdir neuer_ordner", _("Ordner erstellen")),
484
+ ("rm -rf datei", _("Datei/Ordner löschen")),
485
+ ("cp quelle ziel", _("Kopieren")),
486
+ ("mv quelle ziel", _("Verschieben")),
487
+ ("chmod +x script.sh", _("Ausführbar machen")),
488
+ ("ps aux", _("Prozesse anzeigen")),
489
+ ("kill PID", _("Prozess beenden")),
490
+ ]
491
+
492
+ self.create_command_buttons(system_tab, system_commands)
493
+
494
+ # Variablen Tab
495
+ variables_tab = ttk.Frame(self.notebook)
496
+ self.notebook.add(variables_tab, text=_("Variablen"))
497
+
498
+ variable_commands = [
499
+ ('variable="wert"', _("Variable zuweisen")),
500
+ ("echo $variable", _("Variable ausgeben")),
501
+ ("${variable:-default}", _("Variable mit Default")),
502
+ ("${#variable}", _("String-Länge")),
503
+ ("${variable:0:5}", _("String extrahieren")),
504
+ ("array=(wert1 wert2)", _("Array erstellen")),
505
+ ("echo ${array[0]}", _("Array-Element")),
506
+ ("echo ${#array[@]}", _("Array-Länge")),
507
+ ]
508
+
509
+ self.create_command_buttons(variables_tab, variable_commands)
510
+
511
+ def create_command_buttons(self, parent, commands):
512
+ """Erstellt Buttons für Befehle"""
513
+ canvas = tk.Canvas(parent)
514
+ scrollbar = ttk.Scrollbar(parent, orient="vertical", command=canvas.yview)
515
+ scrollable_frame = ttk.Frame(canvas)
516
+
517
+ scrollable_frame.bind(
518
+ "<Configure>", lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
519
+ )
520
+
521
+ canvas.create_window((0, 0), window=scrollable_frame, anchor="nw")
522
+ canvas.configure(yscrollcommand=scrollbar.set)
523
+
524
+ for cmd, desc in commands:
525
+ btn = ttk.Button(
526
+ scrollable_frame,
527
+ text=desc,
528
+ command=lambda c=cmd: self.insert_command(c),
529
+ bootstyle="secondary",
530
+ )
531
+ btn.pack(fill=tk.X, pady=2, padx=5)
532
+ ToolTip(btn, text=_("Einfügen: {}").format(cmd.splitlines()[0]))
533
+
534
+ canvas.pack(side="left", fill="both", expand=True)
535
+ scrollbar.pack(side="right", fill="y")
536
+
537
+ def create_script_editor(self, parent):
538
+ """Erstellt den Script-Editor"""
539
+ editor_container = ttk.Frame(parent)
540
+ editor_container.grid(row=0, column=1, sticky="nswe")
541
+ editor_container.grid_rowconfigure(0, weight=1)
542
+ editor_container.grid_columnconfigure(0, weight=1)
543
+
544
+ right_frame = ttk.LabelFrame(
545
+ editor_container, text=_("Script-Editor"), padding=5
546
+ )
547
+ right_frame.grid(row=0, column=0, sticky="nswe")
548
+
549
+ self.text_editor = EnhancedScriptEditor(
550
+ right_frame, wrap=tk.WORD, autohide=True, vbar=True, hbar=True
551
+ )
552
+ self.text_editor.pack(fill=tk.BOTH, expand=True)
553
+
554
+ # Support-Bereich unter dem Editor
555
+ self.create_support_area(editor_container)
556
+
557
+ def create_support_area(self, parent):
558
+ """Erstellt den Bereich für die Support-Buttons."""
559
+ support_frame = ttk.LabelFrame(
560
+ parent, text=_(" Unterstützung "), bootstyle="secondary"
561
+ )
562
+ support_frame.grid(row=4, column=0, sticky="ew", padx=10, pady=(0, 10))
563
+ support_frame.grid_columnconfigure((0, 1, 2), weight=1)
564
+
565
+ # --- Button-Erstellung ohne fehlerhafte Bilder ---
566
+
567
+ # GitHub Sponsors Button
568
+ github_btn = ttk.Button(
569
+ support_frame,
570
+ text="GitHub Sponsors",
571
+ bootstyle="success-outline",
572
+ command=lambda: self.open_link("https://github.com/sponsors/securebitsorg"),
573
+ )
574
+ github_btn.grid(row=0, column=0, padx=10, pady=10, sticky="ew")
575
+ ToolTip(github_btn, text=_("Unterstütze das Projekt auf GitHub Sponsors"))
576
+
577
+ # Patreon Button
578
+ patreon_btn = ttk.Button(
579
+ support_frame,
580
+ text="Patreon",
581
+ bootstyle="info-outline",
582
+ command=lambda: self.open_link("https://patreon.com/securebits"),
583
+ )
584
+ patreon_btn.grid(row=0, column=1, padx=10, pady=10, sticky="ew")
585
+ ToolTip(patreon_btn, text=_("Unterstütze das Projekt auf Patreon"))
586
+
587
+ # Ko-fi Button
588
+ kofi_btn = ttk.Button(
589
+ support_frame,
590
+ text="Ko-fi",
591
+ bootstyle="warning-outline",
592
+ command=lambda: self.open_link("https://ko-fi.com/securebits"),
593
+ )
594
+ kofi_btn.grid(row=0, column=2, padx=10, pady=10, sticky="ew")
595
+ ToolTip(kofi_btn, text=_("Spendiere einen Kaffee auf Ko-fi"))
596
+
597
+ def insert_command(self, command):
598
+ """Fügt einen Befehl in den Editor ein"""
599
+ self.text_editor.insert_command_at_cursor(command)
600
+ self.status_var.set(_("Befehl eingefügt: {}...").format(command[:30]))
601
+
602
+ def update_script_content(self, content):
603
+ """Aktualisiert den Script-Inhalt"""
604
+ self.text_editor.delete(1.0, tk.END)
605
+ self.text_editor.insert(1.0, content)
606
+
607
+ def update_script_name(self, event=None):
608
+ """Aktualisiert den Script-Namen"""
609
+ self.script_name = self.name_entry.get()
610
+
611
+ def new_script(self):
612
+ """Erstellt ein neues Script"""
613
+ if messagebox.askyesno(
614
+ _("Neues Script"),
615
+ _(
616
+ "Möchten Sie ein neues Script erstellen? Nicht gespeicherte Änderungen gehen verloren."
617
+ ),
618
+ ):
619
+ self.script_name = "mein_script.sh"
620
+ self.name_entry.delete(0, tk.END)
621
+ self.name_entry.insert(0, self.script_name)
622
+ self.update_script_content(
623
+ "#!/bin/bash\n# "
624
+ + _("Erstellt mit Bash-Script-Maker")
625
+ + "\n# "
626
+ + datetime.now().strftime("%Y-%m-%d %H:%M:%S")
627
+ + "\n\n"
628
+ )
629
+ self.status_var.set(_("Neues Script erstellt"))
630
+
631
+ def open_script(self):
632
+ """Öffnet ein vorhandenes Script"""
633
+ file_path = askopenfilename(
634
+ parent=self.root,
635
+ title=_("Script öffnen"),
636
+ initialdir=self.last_path,
637
+ filetypes=[(_("Bash Scripts"), "*.sh"), (_("Alle Dateien"), "*.*")],
638
+ )
639
+ if file_path:
640
+ self.last_path = os.path.dirname(file_path)
641
+ try:
642
+ with open(file_path, "r", encoding="utf-8") as file:
643
+ content = file.read()
644
+ self.update_script_content(content)
645
+ self.script_name = os.path.basename(file_path)
646
+ self.name_entry.delete(0, tk.END)
647
+ self.name_entry.insert(0, self.script_name)
648
+ self.status_var.set(_("Script geladen: {}").format(file_path))
649
+ except Exception as e:
650
+ messagebox.showerror(
651
+ _("Fehler"), _("Fehler beim Laden des Scripts: {}").format(str(e))
652
+ )
653
+
654
+ def save_script(self):
655
+ """Speichert das aktuelle Script"""
656
+ script_dir = os.path.dirname(os.path.abspath(__file__))
657
+ file_path = os.path.join(script_dir, self.script_name)
658
+
659
+ try:
660
+ content = self.text_editor.get(1.0, tk.END).strip()
661
+ with open(file_path, "w", encoding="utf-8") as file:
662
+ file.write(content)
663
+ self.status_var.set(_("Script gespeichert: {}").format(file_path))
664
+ except Exception as e:
665
+ messagebox.showerror(
666
+ _("Fehler"), _("Fehler beim Speichern: {}").format(str(e))
667
+ )
668
+
669
+ def save_script_as(self):
670
+ """Speichert das Script unter einem neuen Namen"""
671
+ file_path = asksaveasfilename(
672
+ parent=self.root,
673
+ title=_("Script speichern unter"),
674
+ initialdir=self.last_path,
675
+ initialfile=self.script_name,
676
+ defaultextension=".sh",
677
+ filetypes=[(_("Bash Scripts"), "*.sh"), (_("Alle Dateien"), "*.*")],
678
+ )
679
+ if file_path:
680
+ self.last_path = os.path.dirname(file_path)
681
+ try:
682
+ content = self.text_editor.get(1.0, tk.END).strip()
683
+ with open(file_path, "w", encoding="utf-8") as file:
684
+ file.write(content)
685
+ self.script_name = os.path.basename(file_path)
686
+ self.name_entry.delete(0, tk.END)
687
+ self.name_entry.insert(0, self.script_name)
688
+ self.status_var.set(_("Script gespeichert: {}").format(file_path))
689
+ except Exception as e:
690
+ messagebox.showerror(
691
+ _("Fehler"), _("Fehler beim Speichern: {}").format(str(e))
692
+ )
693
+
694
+ def execute_script(self):
695
+ """Führt das aktuelle Script aus"""
696
+ script_dir = os.path.dirname(os.path.abspath(__file__))
697
+ script_path = os.path.join(script_dir, self.script_name)
698
+
699
+ # Script zuerst speichern
700
+ self.save_script()
701
+
702
+ try:
703
+ # Script ausführbar machen
704
+ os.chmod(script_path, 0o755)
705
+
706
+ # Script ausführen
707
+ result = subprocess.run(
708
+ [script_path], capture_output=True, text=True, cwd=script_dir
709
+ )
710
+
711
+ # Ergebnis anzeigen
712
+ output_window = tk.Toplevel(self.root)
713
+ output_window.title(_("Script-Ausgabe"))
714
+ output_window.geometry("600x400")
715
+
716
+ text_output = scrolledtext.ScrolledText(output_window, wrap=tk.WORD)
717
+ text_output.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
718
+
719
+ if result.stdout:
720
+ text_output.insert(tk.END, "STDOUT:\n" + result.stdout + "\n\n")
721
+ if result.stderr:
722
+ text_output.insert(tk.END, "STDERR:\n" + result.stderr + "\n\n")
723
+ if result.returncode == 0:
724
+ text_output.insert(
725
+ tk.END,
726
+ _("Script erfolgreich beendet (Exit-Code: {})").format(
727
+ result.returncode
728
+ ),
729
+ )
730
+ else:
731
+ text_output.insert(
732
+ tk.END,
733
+ _("Script mit Fehler beendet (Exit-Code: {})").format(
734
+ result.returncode
735
+ ),
736
+ )
737
+
738
+ text_output.config(state=tk.DISABLED)
739
+
740
+ except Exception as e:
741
+ messagebox.showerror(
742
+ _("Fehler"), _("Fehler beim Ausführen des Scripts: {}").format(str(e))
743
+ )
744
+
745
+ def make_executable(self):
746
+ """Macht das Script ausführbar"""
747
+ script_dir = os.path.dirname(os.path.abspath(__file__))
748
+ script_path = os.path.join(script_dir, self.script_name)
749
+
750
+ try:
751
+ os.chmod(script_path, 0o755)
752
+ self.status_var.set(_("Script ist nun ausführbar"))
753
+ except Exception as e:
754
+ messagebox.showerror(
755
+ _("Fehler"),
756
+ _("Fehler beim Ändern der Berechtigungen: {}").format(str(e)),
757
+ )
758
+
759
+ def show_script_info(self):
760
+ """Zeigt Informationen über das aktuelle Script"""
761
+ content = self.text_editor.get(1.0, tk.END)
762
+ ScriptInfoDialog(self.root, self.script_name, content)
763
+
764
+ def show_about(self):
765
+ """Zeigt About-Dialog"""
766
+ about_text = _(
767
+ "Bash-Script-Maker ist ein GUI-Programm zur einfachen Erstellung von Bash-Scripts.\n\n"
768
+ "Erstellt mit Python und Tkinter"
769
+ )
770
+
771
+ messagebox.showinfo(_("Über Bash-Script-Maker"), about_text)
772
+
773
+ def open_documentation(self):
774
+ """Öffnet die README.md Datei im Standard-Browser."""
775
+ try:
776
+ readme_path = os.path.abspath("README.md")
777
+ webbrowser.open(f"file://{readme_path}")
778
+ except Exception as e:
779
+ messagebox.showerror(
780
+ _("Fehler"), _("Konnte die README.md nicht öffnen: {}").format(e)
781
+ )
782
+
783
+ def open_blog(self):
784
+ """Öffnet den Secure-Bits Blog im Standard-Browser."""
785
+ try:
786
+ webbrowser.open("https://secure-bits.org")
787
+ except Exception as e:
788
+ messagebox.showerror(
789
+ _("Fehler"), _("Konnte den Blog nicht öffnen: {}").format(e)
790
+ )
791
+
792
+ def open_font_dialog(self):
793
+ """Öffnet den Dialog zur Schriftart-Anpassung."""
794
+ FontSettingsDialog(self.root, self.text_editor)
795
+
796
+ def change_language(self, language_code):
797
+ """Speichert die ausgewählte Sprache und fordert zum Neustart auf."""
798
+ save_language_setting(language_code)
799
+ response = messagebox.showinfo(
800
+ _("Neustart erforderlich"),
801
+ _(
802
+ "Die Sprache wurde geändert. Die Anwendung wird jetzt neu gestartet, um die Änderungen zu übernehmen."
803
+ ),
804
+ )
805
+
806
+ if response == "ok":
807
+ # Starte die Anwendung neu
808
+ python = sys.executable
809
+ os.execl(python, python, *sys.argv)
810
+
811
+
812
+ def main():
813
+ # Die Sprache wird jetzt automatisch beim Import von 'localization' geladen.
814
+ root = ttk.Window(themename="superhero")
815
+ # Entfernt das Standard-Icon aus der Titelleiste
816
+ root.iconphoto(False, tk.PhotoImage())
817
+ app = BashScriptMaker(root)
818
+ root.mainloop()
819
+
820
+
821
+ if __name__ == "__main__":
822
+ main()