PyCodeTools 0.1.0__tar.gz

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.
@@ -0,0 +1,121 @@
1
+ Metadata-Version: 2.4
2
+ Name: PyCodeTools
3
+ Version: 0.1.0
4
+ Summary: Run Python scripts with extra scripting helpers, and package them into .exe files from a simple GUI.
5
+ Author: Your Name
6
+ License: MIT
7
+ Project-URL: Homepage, https://pct.ct.ws
8
+ Keywords: cli,scripting,pyinstaller,tkinter,build tools
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Environment :: Console
13
+ Classifier: Topic :: Software Development :: Build Tools
14
+ Requires-Python: >=3.8
15
+ Description-Content-Type: text/markdown
16
+ Provides-Extra: build
17
+ Requires-Dist: pyinstaller>=6.0; extra == "build"
18
+
19
+ # PyCodeTools (PCT)
20
+
21
+ Run Python scripts with a few extra scripting helpers, and package them
22
+ into standalone `.exe` files from a simple Tkinter GUI — all from the
23
+ command line.
24
+
25
+ ## Install
26
+
27
+ ```bash
28
+ pip install PyCodeTools
29
+ ```
30
+
31
+ This gives you two equivalent commands: `pycodetools` and `pct`.
32
+
33
+ ## Enabling PCT in a script
34
+
35
+ Add this as the **first line** of your `.py` file:
36
+
37
+ ```python
38
+ pycodetools = {true}PCT.enable
39
+ print("pycodetools")
40
+ ```
41
+
42
+ That first line is what PCT looks for. It's still valid, harmless Python
43
+ (just a variable assignment) if someone runs the file normally with
44
+ `python file.py` — but when you run it through PCT, PCT detects it,
45
+ prints a confirmation, and unlocks extra helpers for your script.
46
+
47
+ ## Running a script
48
+
49
+ ```bash
50
+ pct myscript.py
51
+ # or explicitly:
52
+ pct run myscript.py
53
+ # or the long form:
54
+ pycodetools myscript.py
55
+ ```
56
+
57
+ If the enable line is found, you'll see:
58
+
59
+ ```
60
+ {true}PCT.enable
61
+ PyCodeTools is now enabled for this run.
62
+ Running: py myscript.py
63
+ ----------------------------------------
64
+ pycodetools
65
+ ```
66
+
67
+ ## Extra helpers in your script
68
+
69
+ Once enabled, your script can use the extra helper functions by
70
+ importing the package:
71
+
72
+ ```python
73
+ pycodetools = {true}PCT.enable
74
+ import pycodetools as pct
75
+
76
+ if pct.enabled:
77
+ pct.title("My App")
78
+ pct.banner("Welcome!")
79
+ print(pct.color("This is red text", "red"))
80
+ pct.timer(3)
81
+ if pct.confirm("Keep going?"):
82
+ print("Continuing...")
83
+ pct.pause()
84
+ ```
85
+
86
+ Available helpers:
87
+
88
+ | Function | What it does |
89
+ |---|---|
90
+ | `pct.title(text)` | Sets the terminal window title |
91
+ | `pct.clear()` | Clears the terminal |
92
+ | `pct.pause(msg=...)` | "Press Enter to continue..." prompt |
93
+ | `pct.banner(text)` | Prints a boxed banner |
94
+ | `pct.color(text, name)` | Returns ANSI-colored text |
95
+ | `pct.timer(seconds)` | Countdown timer |
96
+ | `pct.confirm(prompt)` | Yes/No prompt, returns bool |
97
+ | `pct.enabled` | `True` if the enable line was detected |
98
+
99
+ ## Config / Build GUI
100
+
101
+ ```bash
102
+ pct config myscript.py
103
+ ```
104
+
105
+ Opens a Tkinter window with two tabs:
106
+
107
+ - **Dependencies** — pick from a preset list of common packages (or type
108
+ a custom one) and install them with pip.
109
+ - **Build .exe** — compile the script into a standalone executable using
110
+ [PyInstaller](https://pyinstaller.org/), with options for one-file
111
+ builds, hiding the console window, and naming the output.
112
+
113
+ Building `.exe` files requires PyInstaller:
114
+
115
+ ```bash
116
+ pip install PyCodeTools[build]
117
+ ```
118
+
119
+ ## License
120
+
121
+ MIT
@@ -0,0 +1,103 @@
1
+ # PyCodeTools (PCT)
2
+
3
+ Run Python scripts with a few extra scripting helpers, and package them
4
+ into standalone `.exe` files from a simple Tkinter GUI — all from the
5
+ command line.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ pip install PyCodeTools
11
+ ```
12
+
13
+ This gives you two equivalent commands: `pycodetools` and `pct`.
14
+
15
+ ## Enabling PCT in a script
16
+
17
+ Add this as the **first line** of your `.py` file:
18
+
19
+ ```python
20
+ pycodetools = {true}PCT.enable
21
+ print("pycodetools")
22
+ ```
23
+
24
+ That first line is what PCT looks for. It's still valid, harmless Python
25
+ (just a variable assignment) if someone runs the file normally with
26
+ `python file.py` — but when you run it through PCT, PCT detects it,
27
+ prints a confirmation, and unlocks extra helpers for your script.
28
+
29
+ ## Running a script
30
+
31
+ ```bash
32
+ pct myscript.py
33
+ # or explicitly:
34
+ pct run myscript.py
35
+ # or the long form:
36
+ pycodetools myscript.py
37
+ ```
38
+
39
+ If the enable line is found, you'll see:
40
+
41
+ ```
42
+ {true}PCT.enable
43
+ PyCodeTools is now enabled for this run.
44
+ Running: py myscript.py
45
+ ----------------------------------------
46
+ pycodetools
47
+ ```
48
+
49
+ ## Extra helpers in your script
50
+
51
+ Once enabled, your script can use the extra helper functions by
52
+ importing the package:
53
+
54
+ ```python
55
+ pycodetools = {true}PCT.enable
56
+ import pycodetools as pct
57
+
58
+ if pct.enabled:
59
+ pct.title("My App")
60
+ pct.banner("Welcome!")
61
+ print(pct.color("This is red text", "red"))
62
+ pct.timer(3)
63
+ if pct.confirm("Keep going?"):
64
+ print("Continuing...")
65
+ pct.pause()
66
+ ```
67
+
68
+ Available helpers:
69
+
70
+ | Function | What it does |
71
+ |---|---|
72
+ | `pct.title(text)` | Sets the terminal window title |
73
+ | `pct.clear()` | Clears the terminal |
74
+ | `pct.pause(msg=...)` | "Press Enter to continue..." prompt |
75
+ | `pct.banner(text)` | Prints a boxed banner |
76
+ | `pct.color(text, name)` | Returns ANSI-colored text |
77
+ | `pct.timer(seconds)` | Countdown timer |
78
+ | `pct.confirm(prompt)` | Yes/No prompt, returns bool |
79
+ | `pct.enabled` | `True` if the enable line was detected |
80
+
81
+ ## Config / Build GUI
82
+
83
+ ```bash
84
+ pct config myscript.py
85
+ ```
86
+
87
+ Opens a Tkinter window with two tabs:
88
+
89
+ - **Dependencies** — pick from a preset list of common packages (or type
90
+ a custom one) and install them with pip.
91
+ - **Build .exe** — compile the script into a standalone executable using
92
+ [PyInstaller](https://pyinstaller.org/), with options for one-file
93
+ builds, hiding the console window, and naming the output.
94
+
95
+ Building `.exe` files requires PyInstaller:
96
+
97
+ ```bash
98
+ pip install PyCodeTools[build]
99
+ ```
100
+
101
+ ## License
102
+
103
+ MIT
@@ -0,0 +1,36 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "PyCodeTools"
7
+ version = "0.1.0"
8
+ description = "Run Python scripts with extra scripting helpers, and package them into .exe files from a simple GUI."
9
+ readme = "README.md"
10
+ requires-python = ">=3.8"
11
+ license = { text = "MIT" }
12
+ authors = [
13
+ { name = "Your Name" }
14
+ ]
15
+ keywords = ["cli", "scripting", "pyinstaller", "tkinter", "build tools"]
16
+ classifiers = [
17
+ "Programming Language :: Python :: 3",
18
+ "License :: OSI Approved :: MIT License",
19
+ "Operating System :: OS Independent",
20
+ "Environment :: Console",
21
+ "Topic :: Software Development :: Build Tools",
22
+ ]
23
+ dependencies = []
24
+
25
+ [project.optional-dependencies]
26
+ build = ["pyinstaller>=6.0"]
27
+
28
+ [project.scripts]
29
+ pycodetools = "pycodetools.cli:main"
30
+ pct = "pycodetools.cli:main"
31
+
32
+ [project.urls]
33
+ Homepage = "https://pct.ct.ws"
34
+
35
+ [tool.setuptools.packages.find]
36
+ where = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,121 @@
1
+ Metadata-Version: 2.4
2
+ Name: PyCodeTools
3
+ Version: 0.1.0
4
+ Summary: Run Python scripts with extra scripting helpers, and package them into .exe files from a simple GUI.
5
+ Author: Your Name
6
+ License: MIT
7
+ Project-URL: Homepage, https://pct.ct.ws
8
+ Keywords: cli,scripting,pyinstaller,tkinter,build tools
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Environment :: Console
13
+ Classifier: Topic :: Software Development :: Build Tools
14
+ Requires-Python: >=3.8
15
+ Description-Content-Type: text/markdown
16
+ Provides-Extra: build
17
+ Requires-Dist: pyinstaller>=6.0; extra == "build"
18
+
19
+ # PyCodeTools (PCT)
20
+
21
+ Run Python scripts with a few extra scripting helpers, and package them
22
+ into standalone `.exe` files from a simple Tkinter GUI — all from the
23
+ command line.
24
+
25
+ ## Install
26
+
27
+ ```bash
28
+ pip install PyCodeTools
29
+ ```
30
+
31
+ This gives you two equivalent commands: `pycodetools` and `pct`.
32
+
33
+ ## Enabling PCT in a script
34
+
35
+ Add this as the **first line** of your `.py` file:
36
+
37
+ ```python
38
+ pycodetools = {true}PCT.enable
39
+ print("pycodetools")
40
+ ```
41
+
42
+ That first line is what PCT looks for. It's still valid, harmless Python
43
+ (just a variable assignment) if someone runs the file normally with
44
+ `python file.py` — but when you run it through PCT, PCT detects it,
45
+ prints a confirmation, and unlocks extra helpers for your script.
46
+
47
+ ## Running a script
48
+
49
+ ```bash
50
+ pct myscript.py
51
+ # or explicitly:
52
+ pct run myscript.py
53
+ # or the long form:
54
+ pycodetools myscript.py
55
+ ```
56
+
57
+ If the enable line is found, you'll see:
58
+
59
+ ```
60
+ {true}PCT.enable
61
+ PyCodeTools is now enabled for this run.
62
+ Running: py myscript.py
63
+ ----------------------------------------
64
+ pycodetools
65
+ ```
66
+
67
+ ## Extra helpers in your script
68
+
69
+ Once enabled, your script can use the extra helper functions by
70
+ importing the package:
71
+
72
+ ```python
73
+ pycodetools = {true}PCT.enable
74
+ import pycodetools as pct
75
+
76
+ if pct.enabled:
77
+ pct.title("My App")
78
+ pct.banner("Welcome!")
79
+ print(pct.color("This is red text", "red"))
80
+ pct.timer(3)
81
+ if pct.confirm("Keep going?"):
82
+ print("Continuing...")
83
+ pct.pause()
84
+ ```
85
+
86
+ Available helpers:
87
+
88
+ | Function | What it does |
89
+ |---|---|
90
+ | `pct.title(text)` | Sets the terminal window title |
91
+ | `pct.clear()` | Clears the terminal |
92
+ | `pct.pause(msg=...)` | "Press Enter to continue..." prompt |
93
+ | `pct.banner(text)` | Prints a boxed banner |
94
+ | `pct.color(text, name)` | Returns ANSI-colored text |
95
+ | `pct.timer(seconds)` | Countdown timer |
96
+ | `pct.confirm(prompt)` | Yes/No prompt, returns bool |
97
+ | `pct.enabled` | `True` if the enable line was detected |
98
+
99
+ ## Config / Build GUI
100
+
101
+ ```bash
102
+ pct config myscript.py
103
+ ```
104
+
105
+ Opens a Tkinter window with two tabs:
106
+
107
+ - **Dependencies** — pick from a preset list of common packages (or type
108
+ a custom one) and install them with pip.
109
+ - **Build .exe** — compile the script into a standalone executable using
110
+ [PyInstaller](https://pyinstaller.org/), with options for one-file
111
+ builds, hiding the console window, and naming the output.
112
+
113
+ Building `.exe` files requires PyInstaller:
114
+
115
+ ```bash
116
+ pip install PyCodeTools[build]
117
+ ```
118
+
119
+ ## License
120
+
121
+ MIT
@@ -0,0 +1,11 @@
1
+ README.md
2
+ pyproject.toml
3
+ src/PyCodeTools.egg-info/PKG-INFO
4
+ src/PyCodeTools.egg-info/SOURCES.txt
5
+ src/PyCodeTools.egg-info/dependency_links.txt
6
+ src/PyCodeTools.egg-info/entry_points.txt
7
+ src/PyCodeTools.egg-info/requires.txt
8
+ src/PyCodeTools.egg-info/top_level.txt
9
+ src/pycodetools/__init__.py
10
+ src/pycodetools/cli.py
11
+ src/pycodetools/gui.py
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ pct = pycodetools.cli:main
3
+ pycodetools = pycodetools.cli:main
@@ -0,0 +1,3 @@
1
+
2
+ [build]
3
+ pyinstaller>=6.0
@@ -0,0 +1 @@
1
+ pycodetools
@@ -0,0 +1,104 @@
1
+ """
2
+ PyCodeTools (PCT)
3
+ =================
4
+
5
+ A tiny helper toolkit + CLI for running Python scripts with a few
6
+ extra scripting conveniences, and a GUI for turning them into .exe
7
+ files with PyInstaller.
8
+
9
+ Enabling PCT in a script
10
+ -------------------------
11
+ Put this as the first line of your script::
12
+
13
+ pycodetools = {true}PCT.enable
14
+
15
+ When you run the file with the ``pycodetools`` (or ``pct``) command,
16
+ PCT will detect that line and print a confirmation, then run your
17
+ script with the extra helpers available via::
18
+
19
+ import pycodetools as pct
20
+
21
+ Extra helpers available once enabled
22
+ -------------------------------------
23
+ - pct.title("My App") -> sets the terminal window title
24
+ - pct.clear() -> clears the terminal screen
25
+ - pct.pause(msg="...") -> "Press Enter to continue..." style pause
26
+ - pct.banner("text") -> prints a nice banner
27
+ - pct.color("text", "red") -> returns text wrapped in ANSI color codes
28
+ - pct.timer(seconds) -> countdown in the terminal
29
+ - pct.confirm("Continue?") -> yes/no prompt returning bool
30
+ - pct.enabled -> True if PCT enable line was detected
31
+ """
32
+
33
+ from __future__ import annotations
34
+
35
+ import os
36
+ import sys
37
+ import time
38
+
39
+ __version__ = "0.1.0"
40
+
41
+ # Set to True by the pycodetools launcher when it detects the enable line.
42
+ # Scripts run directly with `python file.py` will have this as False,
43
+ # since the enable line by itself is just a harmless variable assignment.
44
+ enabled: bool = False
45
+
46
+ _COLORS = {
47
+ "red": "31",
48
+ "green": "32",
49
+ "yellow": "33",
50
+ "blue": "34",
51
+ "magenta": "35",
52
+ "cyan": "36",
53
+ "white": "37",
54
+ }
55
+
56
+
57
+ def title(text: str) -> None:
58
+ """Set the terminal window title (Windows + most ANSI terminals)."""
59
+ if os.name == "nt":
60
+ os.system(f"title {text}")
61
+ else:
62
+ sys.stdout.write(f"\33]0;{text}\a")
63
+ sys.stdout.flush()
64
+
65
+
66
+ def clear() -> None:
67
+ """Clear the terminal screen."""
68
+ os.system("cls" if os.name == "nt" else "clear")
69
+
70
+
71
+ def pause(msg: str = "Press Enter to continue...") -> None:
72
+ """Pause execution until the user presses Enter."""
73
+ input(msg)
74
+
75
+
76
+ def banner(text: str, char: str = "=") -> None:
77
+ """Print a simple banner around text."""
78
+ line = char * (len(text) + 4)
79
+ print(line)
80
+ print(f"{char} {text} {char}")
81
+ print(line)
82
+
83
+
84
+ def color(text: str, name: str = "white") -> str:
85
+ """Wrap text in an ANSI color code (returns a string, doesn't print)."""
86
+ code = _COLORS.get(name.lower(), "37")
87
+ return f"\033[{code}m{text}\033[0m"
88
+
89
+
90
+ def timer(seconds: int, label: str = "Starting in") -> None:
91
+ """Countdown timer printed to the terminal."""
92
+ for i in range(seconds, 0, -1):
93
+ print(f"\r{label} {i}...", end="", flush=True)
94
+ time.sleep(1)
95
+ print(f"\r{label} 0... ")
96
+
97
+
98
+ def confirm(prompt: str = "Continue?") -> bool:
99
+ """Ask a yes/no question, returns True/False."""
100
+ ans = input(f"{prompt} [y/N]: ").strip().lower()
101
+ return ans in ("y", "yes")
102
+
103
+
104
+ ENABLE_LINE = "pycodetools = {true}PCT.enable"
@@ -0,0 +1,145 @@
1
+ """
2
+ Command line interface for PyCodeTools.
3
+
4
+ Usage:
5
+ pycodetools <file.py> Run a script (checks for the enable line)
6
+ pycodetools run <file.py> Same as above, explicit form
7
+ pycodetools config <file.py> Open the Tkinter build/config GUI
8
+ pct <file.py> Alias for pycodetools
9
+ pct config <file.py> Alias for pycodetools config
10
+
11
+ The enable line
12
+ ----------------
13
+ If the first non-blank line of the target file is exactly:
14
+
15
+ pycodetools = {true}PCT.enable
16
+
17
+ PCT will print a confirmation and run the script with the
18
+ `pycodetools` helper module's `enabled` flag set to True, so the
19
+ script can do:
20
+
21
+ import pycodetools as pct
22
+ if pct.enabled:
23
+ pct.banner("PCT features on")
24
+ """
25
+
26
+ from __future__ import annotations
27
+
28
+ import argparse
29
+ import sys
30
+ from pathlib import Path
31
+
32
+ from . import ENABLE_LINE
33
+ from . import __version__
34
+
35
+
36
+ def _read_first_meaningful_line(path: Path) -> str:
37
+ with path.open("r", encoding="utf-8", errors="replace") as f:
38
+ for line in f:
39
+ stripped = line.strip()
40
+ if stripped:
41
+ return stripped
42
+ return ""
43
+
44
+
45
+ def _run_file(file_path: str) -> int:
46
+ path = Path(file_path)
47
+ if not path.exists():
48
+ print(f"pct: error: no such file: {file_path}", file=sys.stderr)
49
+ return 1
50
+ if path.suffix != ".py":
51
+ print(f"pct: warning: '{file_path}' doesn't end in .py, running anyway")
52
+
53
+ first_line = _read_first_meaningful_line(path)
54
+ is_enabled = first_line == ENABLE_LINE
55
+
56
+ # Set the enabled flag on our own module before the target script
57
+ # imports it, so `import pycodetools as pct; pct.enabled` works.
58
+ import pycodetools as pct_module
59
+ pct_module.enabled = is_enabled
60
+
61
+ if is_enabled:
62
+ print("{true}PCT.enable")
63
+ print("PyCodeTools is now enabled for this run.")
64
+ else:
65
+ print("PyCodeTools: enable line not found, running as a plain script.")
66
+
67
+ print(f"Running: py {path.name}\n" + ("-" * 40))
68
+
69
+ source = path.read_text(encoding="utf-8", errors="replace")
70
+ if is_enabled:
71
+ # The enable line uses PCT's special "{true}PCT.enable" marker
72
+ # syntax, which is not valid Python. Replace just that first
73
+ # line with a real, equivalent assignment so the rest of the
74
+ # file still runs normally.
75
+ lines = source.splitlines(keepends=True)
76
+ for idx, line in enumerate(lines):
77
+ if line.strip() == ENABLE_LINE:
78
+ lines[idx] = "pycodetools = True\n"
79
+ break
80
+ source = "".join(lines)
81
+
82
+ code = compile(source, str(path), "exec")
83
+
84
+ old_argv = sys.argv
85
+ sys.argv = [str(path)]
86
+ try:
87
+ exec(code, {"__name__": "__main__", "__file__": str(path)})
88
+ except SystemExit as e:
89
+ return int(e.code) if isinstance(e.code, int) else 0
90
+ finally:
91
+ sys.argv = old_argv
92
+ return 0
93
+
94
+
95
+ def _open_config(file_path: str) -> int:
96
+ path = Path(file_path)
97
+ if not path.exists():
98
+ print(f"pct: error: no such file: {file_path}", file=sys.stderr)
99
+ return 1
100
+
101
+ from .gui import launch_config_gui
102
+ launch_config_gui(path)
103
+ return 0
104
+
105
+
106
+ def main(argv=None) -> int:
107
+ argv = argv if argv is not None else sys.argv[1:]
108
+
109
+ parser = argparse.ArgumentParser(
110
+ prog="pct",
111
+ description="PyCodeTools (PCT) - run and package Python scripts.",
112
+ )
113
+ parser.add_argument(
114
+ "--version", action="version", version=f"PyCodeTools {__version__}"
115
+ )
116
+ if argv and argv[0] == "--version":
117
+ print(f"PyCodeTools {__version__}")
118
+ return 0
119
+
120
+ if not argv:
121
+ parser.print_help()
122
+ return 0
123
+
124
+ if argv[0] == "run":
125
+ if len(argv) < 2:
126
+ print("pct: error: 'run' requires a file argument", file=sys.stderr)
127
+ return 2
128
+ return _run_file(argv[1])
129
+
130
+ if argv[0] == "config":
131
+ if len(argv) < 2:
132
+ print("pct: error: 'config' requires a file argument", file=sys.stderr)
133
+ return 2
134
+ return _open_config(argv[1])
135
+
136
+ if argv[0] in ("-h", "--help"):
137
+ parser.print_help()
138
+ return 0
139
+
140
+ # Bare `pct file.py`
141
+ return _run_file(argv[0])
142
+
143
+
144
+ if __name__ == "__main__":
145
+ sys.exit(main())
@@ -0,0 +1,213 @@
1
+ """
2
+ Tkinter GUI launched by `pct config <file.py>`.
3
+
4
+ Lets you:
5
+ - Pick dependencies from a preset list (or add custom ones) and
6
+ pip-install them.
7
+ - Compile the target script into a standalone .exe using PyInstaller.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import subprocess
13
+ import sys
14
+ import threading
15
+ from pathlib import Path
16
+
17
+ import tkinter as tk
18
+ from tkinter import ttk, messagebox, scrolledtext
19
+
20
+ # A starter list of common packages people often need. Not exhaustive -
21
+ # custom packages can be typed in too.
22
+ COMMON_DEPENDENCIES = [
23
+ "requests",
24
+ "numpy",
25
+ "pandas",
26
+ "matplotlib",
27
+ "pillow",
28
+ "flask",
29
+ "django",
30
+ "beautifulsoup4",
31
+ "pygame",
32
+ "opencv-python",
33
+ "scikit-learn",
34
+ "tqdm",
35
+ "pyinstaller",
36
+ "python-dotenv",
37
+ "colorama",
38
+ "rich",
39
+ "sqlalchemy",
40
+ "pytest",
41
+ "pyyaml",
42
+ "click",
43
+ ]
44
+
45
+
46
+ class PCTConfigApp:
47
+ def __init__(self, root: tk.Tk, target_file: Path):
48
+ self.root = root
49
+ self.target_file = target_file
50
+ self.root.title(f"PyCodeTools Config - {target_file.name}")
51
+ self.root.geometry("560x600")
52
+ self.root.minsize(480, 500)
53
+
54
+ self._build_ui()
55
+
56
+ # ---------- UI construction ----------
57
+
58
+ def _build_ui(self) -> None:
59
+ pad = {"padx": 10, "pady": 6}
60
+
61
+ header = ttk.Label(
62
+ self.root,
63
+ text=f"Configuring: {self.target_file}",
64
+ font=("Segoe UI", 10, "bold"),
65
+ wraplength=520,
66
+ )
67
+ header.pack(fill="x", **pad)
68
+
69
+ notebook = ttk.Notebook(self.root)
70
+ notebook.pack(fill="both", expand=True, padx=10, pady=6)
71
+
72
+ deps_tab = ttk.Frame(notebook)
73
+ build_tab = ttk.Frame(notebook)
74
+ notebook.add(deps_tab, text="Dependencies")
75
+ notebook.add(build_tab, text="Build .exe")
76
+
77
+ self._build_deps_tab(deps_tab)
78
+ self._build_build_tab(build_tab)
79
+
80
+ # Shared log/output box at the bottom
81
+ log_label = ttk.Label(self.root, text="Output:")
82
+ log_label.pack(anchor="w", padx=10)
83
+ self.log_box = scrolledtext.ScrolledText(self.root, height=10, state="disabled")
84
+ self.log_box.pack(fill="both", expand=False, padx=10, pady=(0, 10))
85
+
86
+ def _build_deps_tab(self, parent: ttk.Frame) -> None:
87
+ ttk.Label(parent, text="Pick dependencies to install:").pack(
88
+ anchor="w", padx=10, pady=(10, 2)
89
+ )
90
+
91
+ list_frame = ttk.Frame(parent)
92
+ list_frame.pack(fill="both", expand=True, padx=10, pady=4)
93
+
94
+ self.deps_listbox = tk.Listbox(
95
+ list_frame, selectmode="multiple", height=10, exportselection=False
96
+ )
97
+ for dep in COMMON_DEPENDENCIES:
98
+ self.deps_listbox.insert("end", dep)
99
+ scrollbar = ttk.Scrollbar(
100
+ list_frame, orient="vertical", command=self.deps_listbox.yview
101
+ )
102
+ self.deps_listbox.configure(yscrollcommand=scrollbar.set)
103
+ self.deps_listbox.pack(side="left", fill="both", expand=True)
104
+ scrollbar.pack(side="right", fill="y")
105
+
106
+ custom_frame = ttk.Frame(parent)
107
+ custom_frame.pack(fill="x", padx=10, pady=6)
108
+ ttk.Label(custom_frame, text="Custom package:").pack(side="left")
109
+ self.custom_dep_entry = ttk.Entry(custom_frame)
110
+ self.custom_dep_entry.pack(side="left", fill="x", expand=True, padx=6)
111
+
112
+ install_btn = ttk.Button(
113
+ parent, text="Install Selected", command=self._install_dependencies
114
+ )
115
+ install_btn.pack(pady=6)
116
+
117
+ def _build_build_tab(self, parent: ttk.Frame) -> None:
118
+ ttk.Label(
119
+ parent,
120
+ text="Compile this script into a standalone .exe using PyInstaller.",
121
+ wraplength=500,
122
+ ).pack(anchor="w", padx=10, pady=(10, 6))
123
+
124
+ opts_frame = ttk.LabelFrame(parent, text="Options")
125
+ opts_frame.pack(fill="x", padx=10, pady=6)
126
+
127
+ self.onefile_var = tk.BooleanVar(value=True)
128
+ ttk.Checkbutton(
129
+ opts_frame, text="Single file (--onefile)", variable=self.onefile_var
130
+ ).pack(anchor="w", padx=8, pady=2)
131
+
132
+ self.console_var = tk.BooleanVar(value=True)
133
+ ttk.Checkbutton(
134
+ opts_frame,
135
+ text="Show console window (uncheck for --noconsole/GUI apps)",
136
+ variable=self.console_var,
137
+ ).pack(anchor="w", padx=8, pady=2)
138
+
139
+ name_frame = ttk.Frame(opts_frame)
140
+ name_frame.pack(fill="x", padx=8, pady=4)
141
+ ttk.Label(name_frame, text="Output name:").pack(side="left")
142
+ self.exe_name_entry = ttk.Entry(name_frame)
143
+ self.exe_name_entry.insert(0, self.target_file.stem)
144
+ self.exe_name_entry.pack(side="left", fill="x", expand=True, padx=6)
145
+
146
+ build_btn = ttk.Button(
147
+ parent, text="Build .exe", command=self._build_exe
148
+ )
149
+ build_btn.pack(pady=10)
150
+
151
+ # ---------- Logging helper ----------
152
+
153
+ def _log(self, text: str) -> None:
154
+ self.log_box.configure(state="normal")
155
+ self.log_box.insert("end", text + "\n")
156
+ self.log_box.see("end")
157
+ self.log_box.configure(state="disabled")
158
+
159
+ def _run_in_thread(self, cmd: list[str]) -> None:
160
+ def worker():
161
+ self._log(f"$ {' '.join(cmd)}")
162
+ try:
163
+ proc = subprocess.Popen(
164
+ cmd,
165
+ stdout=subprocess.PIPE,
166
+ stderr=subprocess.STDOUT,
167
+ text=True,
168
+ )
169
+ for line in proc.stdout: # type: ignore[union-attr]
170
+ self._log(line.rstrip())
171
+ proc.wait()
172
+ if proc.returncode == 0:
173
+ self._log("Done.")
174
+ else:
175
+ self._log(f"Exited with code {proc.returncode}")
176
+ except FileNotFoundError as e:
177
+ self._log(f"Error: {e}")
178
+ except Exception as e: # noqa: BLE001
179
+ self._log(f"Error: {e}")
180
+
181
+ threading.Thread(target=worker, daemon=True).start()
182
+
183
+ # ---------- Actions ----------
184
+
185
+ def _install_dependencies(self) -> None:
186
+ selected_indices = self.deps_listbox.curselection()
187
+ packages = [self.deps_listbox.get(i) for i in selected_indices]
188
+ custom = self.custom_dep_entry.get().strip()
189
+ if custom:
190
+ packages.append(custom)
191
+
192
+ if not packages:
193
+ messagebox.showinfo("PyCodeTools", "No packages selected.")
194
+ return
195
+
196
+ cmd = [sys.executable, "-m", "pip", "install", *packages]
197
+ self._run_in_thread(cmd)
198
+
199
+ def _build_exe(self) -> None:
200
+ name = self.exe_name_entry.get().strip() or self.target_file.stem
201
+ cmd = [sys.executable, "-m", "PyInstaller"]
202
+ if self.onefile_var.get():
203
+ cmd.append("--onefile")
204
+ if not self.console_var.get():
205
+ cmd.append("--noconsole")
206
+ cmd.extend(["--name", name, str(self.target_file)])
207
+ self._run_in_thread(cmd)
208
+
209
+
210
+ def launch_config_gui(target_file: Path) -> None:
211
+ root = tk.Tk()
212
+ PCTConfigApp(root, target_file)
213
+ root.mainloop()