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-1.1.0.dist-info/METADATA +408 -0
- bash_script_maker-1.1.0.dist-info/RECORD +8 -0
- bash_script_maker-1.1.0.dist-info/WHEEL +5 -0
- bash_script_maker-1.1.0.dist-info/entry_points.txt +2 -0
- bash_script_maker-1.1.0.dist-info/licenses/LICENSE +39 -0
- bash_script_maker-1.1.0.dist-info/top_level.txt +2 -0
- bash_script_maker.py +822 -0
- syntax_highlighter.py +1026 -0
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()
|