dkinst 0.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.
- dkinst/__init__.py +4 -0
- dkinst/cli.py +246 -0
- dkinst/installers/__init__.py +0 -0
- dkinst/installers/_base.py +257 -0
- dkinst/installers/helpers/__init__.py +0 -0
- dkinst/installers/helpers/tesseract_ocr_manager.py +411 -0
- dkinst/installers/tesseract_ocr.py +63 -0
- dkinst-0.1.0.dist-info/METADATA +80 -0
- dkinst-0.1.0.dist-info/RECORD +13 -0
- dkinst-0.1.0.dist-info/WHEEL +5 -0
- dkinst-0.1.0.dist-info/entry_points.txt +2 -0
- dkinst-0.1.0.dist-info/licenses/LICENSE +21 -0
- dkinst-0.1.0.dist-info/top_level.txt +1 -0
dkinst/__init__.py
ADDED
dkinst/cli.py
ADDED
@@ -0,0 +1,246 @@
|
|
1
|
+
"""Command-line driver for dkinst."""
|
2
|
+
import sys
|
3
|
+
import pkgutil
|
4
|
+
from importlib import import_module
|
5
|
+
import argparse
|
6
|
+
from pathlib import Path
|
7
|
+
import subprocess
|
8
|
+
|
9
|
+
from rich.console import Console
|
10
|
+
from rich.table import Table
|
11
|
+
|
12
|
+
from installers._base import BaseInstaller
|
13
|
+
from installers import _base
|
14
|
+
import installers
|
15
|
+
|
16
|
+
console = Console()
|
17
|
+
|
18
|
+
|
19
|
+
VERSION: str = "0.1.0"
|
20
|
+
|
21
|
+
|
22
|
+
def _get_installers() -> list[BaseInstaller]:
|
23
|
+
"""get list of tuples (name, instance) for every subclass found in dkinst.installers.*"""
|
24
|
+
# import every *.py file so its classes are defined
|
25
|
+
for _, stem_name, _ in pkgutil.iter_modules(installers.__path__):
|
26
|
+
module_string: str = f"{installers.__name__}.{stem_name}"
|
27
|
+
import_module(module_string)
|
28
|
+
|
29
|
+
# collect subclasses
|
30
|
+
installers_list: list[BaseInstaller] = []
|
31
|
+
for subclass in BaseInstaller.__subclasses__():
|
32
|
+
if subclass is not BaseInstaller:
|
33
|
+
installer = subclass()
|
34
|
+
installers_list.append(installer)
|
35
|
+
|
36
|
+
return installers_list
|
37
|
+
|
38
|
+
|
39
|
+
def cmd_available() -> None:
|
40
|
+
"""List every known installer with metadata."""
|
41
|
+
table = Table(show_header=True, header_style="bold magenta")
|
42
|
+
table.add_column("Name", style="bold")
|
43
|
+
table.add_column("Platforms")
|
44
|
+
table.add_column("Methods")
|
45
|
+
table.add_column("Manual Arguments")
|
46
|
+
|
47
|
+
# collect all installers
|
48
|
+
installers_list: list[BaseInstaller] = _get_installers()
|
49
|
+
|
50
|
+
methods: list[str]
|
51
|
+
for installer in installers_list:
|
52
|
+
methods = _base.get_known_methods(installer)
|
53
|
+
|
54
|
+
manual_args = _base._extract_helper_args(installer, methods)
|
55
|
+
table.add_row(
|
56
|
+
installer.name,
|
57
|
+
", ".join(installer.platforms) or "—",
|
58
|
+
", ".join(methods) or "—",
|
59
|
+
", ".join(manual_args) or "—",
|
60
|
+
)
|
61
|
+
|
62
|
+
console.print(table)
|
63
|
+
|
64
|
+
|
65
|
+
def _make_parser() -> argparse.ArgumentParser:
|
66
|
+
description: str = (
|
67
|
+
"Den K Simple Installer\n"
|
68
|
+
f"{VERSION}\n"
|
69
|
+
"\n"
|
70
|
+
"Arguments:\n"
|
71
|
+
" install <installer> Install the script with the given name.\n"
|
72
|
+
" update <installer> Update the script with the given name.\n"
|
73
|
+
" uninstall <installer> Uninstall the script with the given name.\n"
|
74
|
+
" manual <installer> If manual method is available for specific installer, "
|
75
|
+
" you can use it to execute the helper script with its parameters.\n"
|
76
|
+
"\n"
|
77
|
+
" manual <installer> <args> Execute the helper script with its parameters.\n"
|
78
|
+
" manual <installer> help Show help for manual arguments of the helper script.\n"
|
79
|
+
" available List all available installers.\n"
|
80
|
+
" edit-config Open the configuration file in the default editor.\n"
|
81
|
+
" You can change the base installation path here.\n"
|
82
|
+
" help Show this help message.\n"
|
83
|
+
"\n"
|
84
|
+
"You can use help for any sub-command to see its specific usage.\n"
|
85
|
+
"Examples:\n"
|
86
|
+
" dkinst help\n"
|
87
|
+
" dkinst install help\n"
|
88
|
+
" dkinst update help\n"
|
89
|
+
" dkinst uninstall help\n"
|
90
|
+
"\n"
|
91
|
+
"==============================\n"
|
92
|
+
"\n"
|
93
|
+
)
|
94
|
+
|
95
|
+
parser = argparse.ArgumentParser(
|
96
|
+
prog="dkinst",
|
97
|
+
description=description,
|
98
|
+
formatter_class=argparse.RawTextHelpFormatter,
|
99
|
+
usage=argparse.SUPPRESS,
|
100
|
+
add_help=False
|
101
|
+
)
|
102
|
+
sub = parser.add_subparsers(dest="sub", required=False)
|
103
|
+
|
104
|
+
for subcmd in _base.ALL_METHODS:
|
105
|
+
# Make <script> optional so `dkinst install help` works
|
106
|
+
sc = sub.add_parser(subcmd, add_help=False)
|
107
|
+
sc.add_argument(
|
108
|
+
"script",
|
109
|
+
nargs="?", # optional to allow `install help`
|
110
|
+
help="installer script name or 'help'",
|
111
|
+
)
|
112
|
+
# Everything after <script> is handed untouched to the installer
|
113
|
+
sc.add_argument("installer_args", nargs=argparse.REMAINDER)
|
114
|
+
|
115
|
+
sub.add_parser("available")
|
116
|
+
sub.add_parser("edit-config")
|
117
|
+
sub.add_parser("help")
|
118
|
+
return parser
|
119
|
+
|
120
|
+
|
121
|
+
def main() -> int:
|
122
|
+
"""
|
123
|
+
Entrypoint for the `dkinst` CLI.
|
124
|
+
|
125
|
+
Supported commands
|
126
|
+
------------------
|
127
|
+
dkinst help
|
128
|
+
dkinst available
|
129
|
+
dkinst install <script> [extra args passed through]
|
130
|
+
dkinst update <script> [extra args passed through]
|
131
|
+
dkinst uninstall <script> [extra args passed through]
|
132
|
+
"""
|
133
|
+
parser = _make_parser() # builds the ArgumentParser shown earlier
|
134
|
+
|
135
|
+
# If no arguments, show the top-level help and exit successfully
|
136
|
+
passed_arguments = sys.argv[1:]
|
137
|
+
if not passed_arguments:
|
138
|
+
parser.print_help()
|
139
|
+
return 0
|
140
|
+
|
141
|
+
namespace = parser.parse_args() # plain parsing now
|
142
|
+
|
143
|
+
if namespace.sub == "help":
|
144
|
+
parser.print_help()
|
145
|
+
return 0
|
146
|
+
|
147
|
+
if namespace.sub == "available":
|
148
|
+
cmd_available()
|
149
|
+
return 0
|
150
|
+
|
151
|
+
if namespace.sub == "edit-config":
|
152
|
+
config_path: str = str(Path(__file__).parent / "config.toml")
|
153
|
+
subprocess.run(["notepad", config_path])
|
154
|
+
return 0
|
155
|
+
|
156
|
+
# Methods from the Known Methods list
|
157
|
+
if namespace.sub in _base.ALL_METHODS:
|
158
|
+
method = namespace.sub
|
159
|
+
|
160
|
+
# No script provided OR explicitly asked for help
|
161
|
+
if namespace.script is None or namespace.script == "help":
|
162
|
+
# `manual help` should show the fixed, centralized manual help
|
163
|
+
if method == "manual":
|
164
|
+
BaseInstaller._show_manual_help()
|
165
|
+
return 0
|
166
|
+
BaseInstaller._show_help(method)
|
167
|
+
return 0
|
168
|
+
|
169
|
+
# From here on, a specific installer was provided
|
170
|
+
installer_name = namespace.script
|
171
|
+
extras = namespace.installer_args or []
|
172
|
+
|
173
|
+
for inst in _get_installers():
|
174
|
+
# Find the provided installer.
|
175
|
+
if inst.name != installer_name:
|
176
|
+
continue
|
177
|
+
|
178
|
+
inst._platforms_known()
|
179
|
+
|
180
|
+
# Now check if the current platform is supported by this installer.
|
181
|
+
current_platform = sys.platform
|
182
|
+
if current_platform in _base.PLATFORM_CONVERTION:
|
183
|
+
installers_kommon_platform = _base.PLATFORM_CONVERTION[current_platform]
|
184
|
+
if installers_kommon_platform not in inst.platforms:
|
185
|
+
console.print(f"This installer [{inst.name}] does not support your platform [{current_platform}].", style='red', markup=False)
|
186
|
+
return 1
|
187
|
+
else:
|
188
|
+
console.print(f"This platform is unknown: [{current_platform}]", style='red', markup=False)
|
189
|
+
return 1
|
190
|
+
|
191
|
+
# Processing the 'manual' method.
|
192
|
+
if method == 'manual':
|
193
|
+
installer_methods = _base.get_known_methods(inst)
|
194
|
+
if 'manual' not in installer_methods:
|
195
|
+
console.print(f"No 'manual' method available for the installer: [{inst.name}]", style='red', markup=False)
|
196
|
+
return 1
|
197
|
+
|
198
|
+
# Use the helper parser for this installer, if available
|
199
|
+
helper_parser = _base._get_helper_parser(inst, installer_methods)
|
200
|
+
if helper_parser is None:
|
201
|
+
console.print(f"No manual argparser available for [{inst.name}].", style='red', markup=False)
|
202
|
+
return 1
|
203
|
+
|
204
|
+
if (
|
205
|
+
# Installer-specific help: [dkinst <method> <installer> help]
|
206
|
+
len(extras) == 1 and extras[0] == "help"
|
207
|
+
) or (
|
208
|
+
# Manual installer execution without arguments: dkinst manual <installer>
|
209
|
+
# show helper parser help if available.
|
210
|
+
len(extras) == 0
|
211
|
+
):
|
212
|
+
helper_parser.print_help()
|
213
|
+
|
214
|
+
# Regular arguments execution of the manual method.
|
215
|
+
# Parse just the extras, not the whole argv
|
216
|
+
try:
|
217
|
+
parsed = helper_parser.parse_args(extras)
|
218
|
+
except SystemExit:
|
219
|
+
# argparse already printed usage/error; treat as handled
|
220
|
+
return 2
|
221
|
+
# If your installers accept kwargs:
|
222
|
+
target_helper = inst.helper
|
223
|
+
return target_helper.main(**vars(parsed))
|
224
|
+
|
225
|
+
# For all the other methods that aren't manual.
|
226
|
+
if len(extras) == 1 and extras[0] == "help":
|
227
|
+
inst._show_help(method)
|
228
|
+
return 0
|
229
|
+
|
230
|
+
# Normal execution: call method and pass through extras (if any)
|
231
|
+
target = getattr(inst, method)
|
232
|
+
|
233
|
+
if extras:
|
234
|
+
return target(*extras)
|
235
|
+
else:
|
236
|
+
return target()
|
237
|
+
|
238
|
+
console.print(f"No installer found with the name: [{installer_name}]", style='red', markup=False)
|
239
|
+
return 0
|
240
|
+
|
241
|
+
# should never get here: argparse enforces valid sub-commands
|
242
|
+
parser.error(f"Unknown command {namespace.sub!r}")
|
243
|
+
|
244
|
+
|
245
|
+
if __name__ == "__main__":
|
246
|
+
sys.exit(main())
|
File without changes
|
@@ -0,0 +1,257 @@
|
|
1
|
+
import inspect
|
2
|
+
import argparse
|
3
|
+
import tomllib
|
4
|
+
from pathlib import Path
|
5
|
+
from types import ModuleType
|
6
|
+
from typing import Literal
|
7
|
+
|
8
|
+
|
9
|
+
INSTALLATION_PATH_PORTABLE_WINDOWS: str = "C:\\dkinst" # Installation path for portable files on Windows that don't have a default location.
|
10
|
+
|
11
|
+
KNOWN_SUPPORTED_PLATFORMS: list[str] = ["windows", "debian"]
|
12
|
+
PLATFORM_CONVERTION: dict[str, str] = { # Convert sys.platform to installer platform names.
|
13
|
+
"linux": "linux",
|
14
|
+
"linux2": "linux",
|
15
|
+
"darwin": "macos",
|
16
|
+
"win32": "windows",
|
17
|
+
"cygwin": "windows",
|
18
|
+
"msys": "windows",
|
19
|
+
"freebsd": "freebsd",
|
20
|
+
"openbsd": "freebsd",
|
21
|
+
"netbsd": "freebsd",
|
22
|
+
"aix": "aix",
|
23
|
+
"sunos5": "solaris",
|
24
|
+
}
|
25
|
+
|
26
|
+
KNOWN_METHODS: list[str] | None = None
|
27
|
+
CUSTOM_METHODS: list[str] = ["manual"]
|
28
|
+
ALL_METHODS: list[str] | None = None
|
29
|
+
|
30
|
+
|
31
|
+
class BaseInstaller:
|
32
|
+
def __init__(self):
|
33
|
+
# The name of the installation script that will be used by the main script to install.
|
34
|
+
self.name: str = "baseinstall"
|
35
|
+
# The description of the installation script.
|
36
|
+
self.description: str = "Base Installer"
|
37
|
+
# The version of the installation script.
|
38
|
+
self.version: str = "1.0.0"
|
39
|
+
# The platforms supported by this installer.
|
40
|
+
self.platforms: list[str] = []
|
41
|
+
# The helper module that provides additional functionality for this installer.
|
42
|
+
# Providing the helper module will automatically introduce the 'manual()' method on the 'cli.py' level.
|
43
|
+
self.helper: ModuleType | None = None
|
44
|
+
|
45
|
+
self.base_path: str = INSTALLATION_PATH_PORTABLE_WINDOWS
|
46
|
+
self.dir_path: str | None = None # Path to the installation directory of the installed application, if applicable. Example: Path(self.base_path) / self.name
|
47
|
+
self.exe_path: str | None = None # Path to the main executable of the installed application, if applicable. Example: Path(self.dir_path) / "app.exe"
|
48
|
+
|
49
|
+
def install(self):
|
50
|
+
raise NotImplementedError("Subclasses should implement this method.")
|
51
|
+
|
52
|
+
def uninstall(self):
|
53
|
+
raise NotImplementedError("Method not implemented by the subclass. Uninstall manually.")
|
54
|
+
|
55
|
+
def update(self):
|
56
|
+
raise NotImplementedError("Method not implemented by the subclass. Update manually.")
|
57
|
+
|
58
|
+
@staticmethod
|
59
|
+
def _show_help(
|
60
|
+
method: Literal["install", "uninstall", "update"]
|
61
|
+
) -> None:
|
62
|
+
"""
|
63
|
+
Print default help for a given method. Can be called as:
|
64
|
+
BaseInstaller._show_help("install")
|
65
|
+
inst._show_help("install") # also works (staticmethod)
|
66
|
+
"""
|
67
|
+
m = (method or "").lower()
|
68
|
+
|
69
|
+
header = {
|
70
|
+
"install": "Install — download and set up an application/installer.",
|
71
|
+
"uninstall": "Uninstall — remove an application installed by this tool.",
|
72
|
+
"update": "Update — update an application to the latest supported version.",
|
73
|
+
"manual": "Manual — run the installer helper directly with custom args.",
|
74
|
+
}.get(m, "dkinst — helpers and installers")
|
75
|
+
|
76
|
+
lines: list[str] = [header, ""]
|
77
|
+
|
78
|
+
if m == "install":
|
79
|
+
lines += [
|
80
|
+
"Usage:",
|
81
|
+
" dkinst install help",
|
82
|
+
" dkinst install <installer> help",
|
83
|
+
" dkinst install <installer> [args...]",
|
84
|
+
"",
|
85
|
+
"Notes:",
|
86
|
+
" • Use `dkinst available` to see all installers.",
|
87
|
+
" • `install <installer> help` shows details for that installer’s install flow.",
|
88
|
+
" • Extra [args...] are passed to the installer/its helper if supported.",
|
89
|
+
]
|
90
|
+
elif m == "uninstall":
|
91
|
+
lines += [
|
92
|
+
"Usage:",
|
93
|
+
" dkinst uninstall help",
|
94
|
+
" dkinst uninstall <installer> help",
|
95
|
+
" dkinst uninstall <installer> [args...]",
|
96
|
+
"",
|
97
|
+
"Notes:",
|
98
|
+
" • Some installers support silent removal flags; check per-installer help.",
|
99
|
+
" • Extra [args...] are passed through if the installer supports them.",
|
100
|
+
]
|
101
|
+
elif m == "update":
|
102
|
+
lines += [
|
103
|
+
"Usage:",
|
104
|
+
" dkinst update help",
|
105
|
+
" dkinst update <installer> help",
|
106
|
+
" dkinst update <installer> [args...]",
|
107
|
+
"",
|
108
|
+
"Notes:",
|
109
|
+
" • If an installer doesn’t support in-place updates, it may reinstall.",
|
110
|
+
" • Extra [args...] are passed through if supported by the installer.",
|
111
|
+
]
|
112
|
+
else:
|
113
|
+
lines += [
|
114
|
+
"Usage:",
|
115
|
+
" dkinst help",
|
116
|
+
" dkinst available",
|
117
|
+
" dkinst <install|uninstall|update|manual> help",
|
118
|
+
" dkinst <install|uninstall|update|manual> <installer> help",
|
119
|
+
" dkinst <install|uninstall|update|manual> <installer> [args...]",
|
120
|
+
]
|
121
|
+
|
122
|
+
print("\n".join(lines))
|
123
|
+
|
124
|
+
@staticmethod
|
125
|
+
def _show_manual_help():
|
126
|
+
lines: list[str] = []
|
127
|
+
lines += [
|
128
|
+
"Usage:",
|
129
|
+
" dkinst manual help",
|
130
|
+
" dkinst manual <installer> help",
|
131
|
+
" dkinst manual <installer> <helper-args...>",
|
132
|
+
"",
|
133
|
+
"Notes:",
|
134
|
+
" • `manual` exposes the installer’s helper parser directly.",
|
135
|
+
" • `<installer> help` will print that helper’s argparse usage if available.",
|
136
|
+
" • Use this when you need fine-grained flags not covered by the defaults.",
|
137
|
+
]
|
138
|
+
|
139
|
+
print("\n".join(lines))
|
140
|
+
|
141
|
+
def _platforms_known(self):
|
142
|
+
"""
|
143
|
+
Check if the current platform list is known list.
|
144
|
+
"""
|
145
|
+
|
146
|
+
for platform in self.platforms:
|
147
|
+
if platform not in KNOWN_SUPPORTED_PLATFORMS:
|
148
|
+
raise ValueError(f"Platform '{platform}' is not known.")
|
149
|
+
|
150
|
+
|
151
|
+
def get_base_known_methods() -> list[str]:
|
152
|
+
"""Return a list of known methods that can be called on installers."""
|
153
|
+
all_methods = inspect.getmembers(BaseInstaller, predicate=inspect.isroutine)
|
154
|
+
|
155
|
+
filtered_methods: list[str] = []
|
156
|
+
for method_name, bound_method in all_methods:
|
157
|
+
if not method_name.startswith("_"):
|
158
|
+
filtered_methods.append(method_name)
|
159
|
+
|
160
|
+
global KNOWN_METHODS
|
161
|
+
if KNOWN_METHODS is None:
|
162
|
+
KNOWN_METHODS = filtered_methods
|
163
|
+
|
164
|
+
global ALL_METHODS
|
165
|
+
ALL_METHODS = CUSTOM_METHODS + KNOWN_METHODS
|
166
|
+
|
167
|
+
return filtered_methods
|
168
|
+
# Run this at module load time to initialize KNOWN_METHODS
|
169
|
+
get_base_known_methods()
|
170
|
+
|
171
|
+
|
172
|
+
def assign_base_paths_from_config() -> None:
|
173
|
+
"""Assign the base_path of all installers from the configuration file."""
|
174
|
+
|
175
|
+
working_path: Path = Path(__file__).parent.parent
|
176
|
+
config_path: Path = working_path / "config.toml"
|
177
|
+
|
178
|
+
with open(str(config_path), "rb") as f:
|
179
|
+
config_content: dict = tomllib.load(f)
|
180
|
+
|
181
|
+
global INSTALLATION_PATH_PORTABLE_WINDOWS
|
182
|
+
INSTALLATION_PATH_PORTABLE_WINDOWS = config_content["windows_portable_installation_dir"]
|
183
|
+
assign_base_paths_from_config()
|
184
|
+
|
185
|
+
|
186
|
+
def get_known_methods(installer: BaseInstaller) -> list[str]:
|
187
|
+
"""Return a list of known methods that can be called on the given installer."""
|
188
|
+
|
189
|
+
# All the subclasses of BaseInstaller will have most of the methods of BaseInstaller.
|
190
|
+
# But not all, if a method is not overridden, it will be present in subclass as a bound method.
|
191
|
+
# So we filter out the methods that are not overridden.
|
192
|
+
filtered_methods: list[str] = []
|
193
|
+
for method_name in KNOWN_METHODS:
|
194
|
+
if getattr(installer.__class__, method_name) is not getattr(BaseInstaller, method_name):
|
195
|
+
filtered_methods.append(method_name)
|
196
|
+
|
197
|
+
# If 'helper' attribute is not None, this means that the helper module was provided, add the 'manual()' method.
|
198
|
+
if installer.helper:
|
199
|
+
filtered_methods = filtered_methods + CUSTOM_METHODS
|
200
|
+
|
201
|
+
return filtered_methods
|
202
|
+
|
203
|
+
|
204
|
+
def _get_helper_parser(
|
205
|
+
installer: BaseInstaller,
|
206
|
+
methods: list[str] = None
|
207
|
+
) -> argparse.ArgumentParser | None:
|
208
|
+
"""
|
209
|
+
If installer overrides `manual`, try to import
|
210
|
+
`installers.helpers.<installer.name>` and harvest its ArgumentParser.
|
211
|
+
|
212
|
+
:param installer: The installer instance to check.
|
213
|
+
:param methods: Optional list of methods to check against. If not provided, will gather known methods.
|
214
|
+
:return: An ArgumentParser instance if available, or None if nothing could be found.
|
215
|
+
"""
|
216
|
+
|
217
|
+
if not methods:
|
218
|
+
methods = get_known_methods(installer)
|
219
|
+
|
220
|
+
if "manual" not in methods:
|
221
|
+
return None # no manual method, nothing to return
|
222
|
+
if installer.helper is None:
|
223
|
+
return None # no helper, nothing to return
|
224
|
+
|
225
|
+
# Is there a parser function in the helper?
|
226
|
+
parse_args_callable = getattr(installer.helper, "_make_parser", None)
|
227
|
+
if parse_args_callable is None:
|
228
|
+
return None # couldn’t obtain one
|
229
|
+
|
230
|
+
parser: argparse.ArgumentParser = parse_args_callable()
|
231
|
+
return parser
|
232
|
+
|
233
|
+
|
234
|
+
def _extract_helper_args(
|
235
|
+
installer: BaseInstaller,
|
236
|
+
methods: list[str] = None
|
237
|
+
) -> list[str]:
|
238
|
+
"""
|
239
|
+
If installer overrides `manual`, try to import
|
240
|
+
`installers.helpers.<installer.name>` and harvest its ArgumentParser and get its tokens (arguments).
|
241
|
+
|
242
|
+
:param installer: The installer instance to check.
|
243
|
+
:param methods: Optional list of methods to check against. If not provided, will gather known methods.
|
244
|
+
:return: A list of CLI tokens that can be used with the installer.
|
245
|
+
Example: ['--prefix', '--force', 'target'], or [] if nothing could be found.
|
246
|
+
"""
|
247
|
+
parser: argparse.ArgumentParser | None = _get_helper_parser(installer, methods)
|
248
|
+
if not parser:
|
249
|
+
return []
|
250
|
+
|
251
|
+
tokens: list[str] = []
|
252
|
+
for act in parser._actions:
|
253
|
+
if act.option_strings: # flags like -f/--force
|
254
|
+
tokens.append("/".join(act.option_strings))
|
255
|
+
else: # positionals
|
256
|
+
tokens.append(act.dest)
|
257
|
+
return tokens
|
File without changes
|
@@ -0,0 +1,411 @@
|
|
1
|
+
import tempfile
|
2
|
+
import subprocess
|
3
|
+
import ctypes
|
4
|
+
import json
|
5
|
+
import os
|
6
|
+
from pathlib import Path
|
7
|
+
import shutil
|
8
|
+
import sys
|
9
|
+
import time
|
10
|
+
import urllib.request
|
11
|
+
|
12
|
+
from rich.console import Console
|
13
|
+
|
14
|
+
from atomicshop.wrappers import githubw
|
15
|
+
|
16
|
+
console = Console()
|
17
|
+
|
18
|
+
|
19
|
+
SCRIPT_NAME: str = "TesseractOCR Manager"
|
20
|
+
AUTHOR: str = "Denis Kras"
|
21
|
+
VERSION: str = "1.0.0"
|
22
|
+
RELEASE_COMMENT: str = "Initial release"
|
23
|
+
|
24
|
+
|
25
|
+
# Constants for GitHub wrapper.
|
26
|
+
RELEASE_STRING_PATTERN: str = "*tesseract*exe"
|
27
|
+
|
28
|
+
# Constants for Tesseract installation on Windows.
|
29
|
+
WINDOWS_TESSERACT_DEFAULT_INSTALLATION_DIRECTORY: str = r"C:\Program Files\Tesseract-OCR"
|
30
|
+
|
31
|
+
|
32
|
+
# Constants for vcpkg and Tesseract compilation.
|
33
|
+
VCPKG_DIR: Path = Path.home() / "vcpkg"
|
34
|
+
TRIPLET: str = "x64-windows" # or "x64-windows-static"
|
35
|
+
PORT: str = f"tesseract[training-tools]:{TRIPLET}"
|
36
|
+
VSWHERE_EXE: Path = Path(
|
37
|
+
os.environ.get("ProgramFiles(x86)", r"C:\Program Files (x86)")
|
38
|
+
) / "Microsoft Visual Studio" / "Installer" / "vswhere.exe"
|
39
|
+
TESSERACT_VCPKG_TOOLS_DIR: Path = VCPKG_DIR / "installed" / TRIPLET / "tools" / "tesseract"
|
40
|
+
TESSERACT_VCPKG_TOOLS_EXE: Path = TESSERACT_VCPKG_TOOLS_DIR / 'tesseract.exe'
|
41
|
+
TESSDATA_DIR: Path = TESSERACT_VCPKG_TOOLS_DIR / "tessdata"
|
42
|
+
|
43
|
+
|
44
|
+
TESSERACT_GITHUB_WRAPPER: githubw.GitHubWrapper = githubw.GitHubWrapper(
|
45
|
+
user_name="tesseract-ocr",
|
46
|
+
repo_name="tesseract",
|
47
|
+
branch="main"
|
48
|
+
)
|
49
|
+
|
50
|
+
|
51
|
+
def get_latest_installer_version() -> str:
|
52
|
+
"""
|
53
|
+
Get the latest Tesseract OCR installer version from GitHub.
|
54
|
+
This function fetches the latest release information from the Tesseract OCR GitHub repository.
|
55
|
+
It returns the version number of the latest installer available.
|
56
|
+
"""
|
57
|
+
|
58
|
+
latest_release: str = TESSERACT_GITHUB_WRAPPER.get_latest_release_version(
|
59
|
+
asset_pattern=RELEASE_STRING_PATTERN,
|
60
|
+
)
|
61
|
+
|
62
|
+
return latest_release
|
63
|
+
|
64
|
+
|
65
|
+
def use_installer(
|
66
|
+
set_path: bool = False
|
67
|
+
) -> None:
|
68
|
+
"""
|
69
|
+
Install Tesseract OCR on Windows.
|
70
|
+
This function downloads the latest available Tesseract installer from GitHub and installs it.
|
71
|
+
It also adds the Tesseract installation directory to the system PATH.
|
72
|
+
The latest installer maybe lower than the latest available version, so if you need the latest you will need
|
73
|
+
to use the compiler version.
|
74
|
+
|
75
|
+
:param set_path: If True, adds the Tesseract installation directory to the system PATH.
|
76
|
+
"""
|
77
|
+
|
78
|
+
temp_file_path: str = tempfile.gettempdir()
|
79
|
+
tesseract_installer = TESSERACT_GITHUB_WRAPPER.download_latest_release(
|
80
|
+
target_directory=temp_file_path,
|
81
|
+
asset_pattern=RELEASE_STRING_PATTERN,
|
82
|
+
find_latest_available_asset=True
|
83
|
+
)
|
84
|
+
|
85
|
+
# The Admin needed to install Tesseract.
|
86
|
+
subprocess.check_call([tesseract_installer, "/S"])
|
87
|
+
|
88
|
+
# Add Tesseract to the PATH.
|
89
|
+
if set_path:
|
90
|
+
subprocess.check_call(["setx", "PATH", f"%PATH%;{WINDOWS_TESSERACT_DEFAULT_INSTALLATION_DIRECTORY}"])
|
91
|
+
|
92
|
+
|
93
|
+
def get_latest_compiled_version() -> str:
|
94
|
+
"""
|
95
|
+
Get the latest compiled version of Tesseract OCR.
|
96
|
+
This function fetches the latest release information from the Tesseract OCR GitHub repository
|
97
|
+
and returns the version number of the latest compiled release.
|
98
|
+
"""
|
99
|
+
|
100
|
+
latest_release: str = TESSERACT_GITHUB_WRAPPER.get_latest_release_version()
|
101
|
+
return latest_release
|
102
|
+
|
103
|
+
|
104
|
+
def compile_exe(
|
105
|
+
set_path: bool = False
|
106
|
+
) -> int:
|
107
|
+
"""
|
108
|
+
Compile the ltest Tesseract version from source.
|
109
|
+
"""
|
110
|
+
|
111
|
+
def run(cmd, *, ok=(0,), **kw):
|
112
|
+
print(f"[+] {cmd}")
|
113
|
+
process_instance = subprocess.run(cmd, shell=True, **kw)
|
114
|
+
if process_instance.returncode not in ok:
|
115
|
+
raise subprocess.CalledProcessError(process_instance.returncode, cmd)
|
116
|
+
|
117
|
+
def is_admin():
|
118
|
+
# noinspection PyUnresolvedReferences
|
119
|
+
return bool(ctypes.windll.shell32.IsUserAnAdmin())
|
120
|
+
|
121
|
+
def have(exe):
|
122
|
+
return shutil.which(exe) is not None
|
123
|
+
|
124
|
+
def have_msvc():
|
125
|
+
if not VSWHERE_EXE.exists():
|
126
|
+
return False
|
127
|
+
|
128
|
+
try:
|
129
|
+
out = subprocess.check_output(
|
130
|
+
f'"{VSWHERE_EXE}" -latest -products Microsoft.VisualStudio.Product.BuildTools -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -format json',
|
131
|
+
shell=True, text=True)
|
132
|
+
return bool(json.loads(out))
|
133
|
+
except subprocess.CalledProcessError:
|
134
|
+
return False
|
135
|
+
|
136
|
+
def install_build_tools() -> int:
|
137
|
+
if have_msvc():
|
138
|
+
return 0
|
139
|
+
|
140
|
+
print("Installing Visual Studio 2022 Build Tools + C++ workload …")
|
141
|
+
url = "https://aka.ms/vs/17/release/vs_BuildTools.exe"
|
142
|
+
with tempfile.TemporaryDirectory() as td:
|
143
|
+
exe = Path(td) / "vs_BuildTools.exe"
|
144
|
+
urllib.request.urlretrieve(url, exe)
|
145
|
+
run(
|
146
|
+
f'"{exe}" --passive --wait --norestart '
|
147
|
+
'--add Microsoft.VisualStudio.Workload.VCTools --includeRecommended',
|
148
|
+
ok=(0, 3010, 1641))
|
149
|
+
print("Waiting for Build Tools to register …")
|
150
|
+
for _ in range(60):
|
151
|
+
if have_msvc():
|
152
|
+
return 0
|
153
|
+
time.sleep(5)
|
154
|
+
|
155
|
+
console.print("Timed out waiting for Build Tools to register.", style="red")
|
156
|
+
return 1
|
157
|
+
|
158
|
+
if os.name != "nt":
|
159
|
+
console.print("This script is for Windows only.", style="red")
|
160
|
+
return 1
|
161
|
+
if not is_admin():
|
162
|
+
console.print("This script requires administrative privileges to run.", style="red")
|
163
|
+
return 1
|
164
|
+
|
165
|
+
execution_result: int = install_build_tools()
|
166
|
+
if execution_result != 0:
|
167
|
+
return execution_result
|
168
|
+
|
169
|
+
if not have("git"):
|
170
|
+
console.print("Git is not installed. Please install Git for Windows.", style="red")
|
171
|
+
return 1
|
172
|
+
|
173
|
+
if not VCPKG_DIR.exists():
|
174
|
+
run(f'git clone https://github.com/microsoft/vcpkg "{VCPKG_DIR}"')
|
175
|
+
else:
|
176
|
+
console.print(f"vcpkg exists in [{VCPKG_DIR}]. Updating...", style="blue")
|
177
|
+
run(f'git -C "{VCPKG_DIR}" pull')
|
178
|
+
run(f'"{VCPKG_DIR / "bootstrap-vcpkg.bat"}"')
|
179
|
+
|
180
|
+
vcpkg = VCPKG_DIR / "vcpkg.exe"
|
181
|
+
console.print(f"Creating {PORT} port in vcpkg...", style="blue")
|
182
|
+
run(f'"{vcpkg}" install {PORT} --disable-metrics')
|
183
|
+
run(f'"{vcpkg}" integrate install --disable-metrics')
|
184
|
+
|
185
|
+
os.makedirs(TESSDATA_DIR, exist_ok=True)
|
186
|
+
|
187
|
+
if set_path:
|
188
|
+
run(f'setx PATH "%PATH%;{TESSERACT_VCPKG_TOOLS_DIR}"')
|
189
|
+
# os.environ["PATH"] = os.environ.get("PATH", "") + os.pathsep + str(TESSERACT_VCPKG_TOOLS_DIR)
|
190
|
+
run(f'setx TESSDATA_PREFIX "{TESSDATA_DIR}"')
|
191
|
+
# os.environ["TESSDATA_PREFIX"] = str(TESSDATA_DIR)
|
192
|
+
|
193
|
+
console.print("\nDone. Open a NEW CMD terminal and run [tesseract --version] to verify the installation.", style="green", markup=False)
|
194
|
+
return 0
|
195
|
+
|
196
|
+
|
197
|
+
def get_environment_path() -> str | None:
|
198
|
+
"""
|
199
|
+
Check if Tesseract is set in the environment variables.
|
200
|
+
This function checks if the Tesseract command is available in the system PATH.
|
201
|
+
Returns the path of tesseract if exists, otherwise None.
|
202
|
+
"""
|
203
|
+
return shutil.which("tesseract")
|
204
|
+
|
205
|
+
|
206
|
+
def get_executable_version(exe_path: str) -> str:
|
207
|
+
"""
|
208
|
+
Get the version of the Tesseract executable.
|
209
|
+
This function runs the Tesseract command with the `--version` flag and returns the version string.
|
210
|
+
If the executable is not found, it returns an empty string.
|
211
|
+
"""
|
212
|
+
try:
|
213
|
+
result = subprocess.run([exe_path, "--version"], capture_output=True, text=True)
|
214
|
+
if result.returncode == 0:
|
215
|
+
string_result = result.stdout.strip()
|
216
|
+
full_version: str = string_result.split('\n')[0] # Get the first line which contains the version.
|
217
|
+
numeric_string: str = full_version.split(' ')[-1] # Get the last part which is the version number.
|
218
|
+
return numeric_string
|
219
|
+
else:
|
220
|
+
return ""
|
221
|
+
except FileNotFoundError:
|
222
|
+
return ""
|
223
|
+
|
224
|
+
|
225
|
+
def _make_parser():
|
226
|
+
import argparse
|
227
|
+
parser = argparse.ArgumentParser(description="Install Tesseract OCR on Windows.")
|
228
|
+
parser.add_argument(
|
229
|
+
"-i", "--installer_usage", action="store_true",
|
230
|
+
help="Use the latest Tesseract installer from GitHub Releases.")
|
231
|
+
parser.add_argument(
|
232
|
+
"-c", "--compile_portable", action="store_true",
|
233
|
+
help="Compile the latest Tesseract version from source in GitHub Releases. "
|
234
|
+
"This could take a while, an hour or more, depending on your system."
|
235
|
+
"This installs Visual Studio Build Tools and all the tesseract dependencies.")
|
236
|
+
parser.add_argument(
|
237
|
+
"-iv", "--installer_version_string_fetch", action="store_true",
|
238
|
+
help="Fetch the latest Tesseract installer version string from GitHub Releases.")
|
239
|
+
parser.add_argument(
|
240
|
+
"-cv", "--compile_version_string_fetch", action="store_true",
|
241
|
+
help="Fetch the latest Tesseract compiled version string from GitHub Releases.")
|
242
|
+
parser.add_argument(
|
243
|
+
"--get-path", action="store_true",
|
244
|
+
help="Return the path of [tesseract] command from the PATH.")
|
245
|
+
parser.add_argument(
|
246
|
+
"--set-path", action="store_true",
|
247
|
+
help="set the path of [tesseract] command to the PATH, based on any of the installation methods.")
|
248
|
+
parser.add_argument(
|
249
|
+
"-f", "--force", action="store_true",
|
250
|
+
help="Force any action without asking.")
|
251
|
+
parser.add_argument(
|
252
|
+
"--exe-path", type=str, default=None,
|
253
|
+
help="Path to the Tesseract executable if you want to set it manually. If you set any of the above installation methods, the version will checked against the latest available version in GitHub Releases, and you will be asked if you want to update it.")
|
254
|
+
|
255
|
+
return parser
|
256
|
+
|
257
|
+
|
258
|
+
def main(
|
259
|
+
installer_usage: bool = False,
|
260
|
+
compile_portable: bool = False,
|
261
|
+
installer_version_string_fetch: bool = False,
|
262
|
+
compile_version_string_fetch: bool = False,
|
263
|
+
get_path: bool = False,
|
264
|
+
set_path: bool = False,
|
265
|
+
force: bool = False,
|
266
|
+
exe_path: str = None
|
267
|
+
) -> int:
|
268
|
+
|
269
|
+
if (installer_version_string_fetch + compile_version_string_fetch + get_path) > 1:
|
270
|
+
print("You cannot more than 1 argument of [--installer_version_string_fetch], [--compile_version_string_fetch], [--get-path] arguments at the same time.")
|
271
|
+
return 1
|
272
|
+
|
273
|
+
if installer_version_string_fetch or compile_version_string_fetch or get_path:
|
274
|
+
result: str = ""
|
275
|
+
selected_argument: str = ""
|
276
|
+
if installer_version_string_fetch:
|
277
|
+
result = get_latest_installer_version()
|
278
|
+
selected_argument = 'installer_version_string_fetch'
|
279
|
+
if compile_version_string_fetch:
|
280
|
+
result = get_latest_compiled_version()
|
281
|
+
selected_argument = 'compile_version_string_fetch'
|
282
|
+
if get_path:
|
283
|
+
result = get_environment_path()
|
284
|
+
selected_argument = 'get_path'
|
285
|
+
|
286
|
+
print(result)
|
287
|
+
|
288
|
+
if any([installer_usage, compile_portable, set_path]):
|
289
|
+
print(f"You need to remove the [--{selected_argument}] argument, to use other arguments...")
|
290
|
+
|
291
|
+
return 0
|
292
|
+
|
293
|
+
if compile_portable and installer_usage:
|
294
|
+
print("You cannot use both [--installer_usage] and [--compile_portable] arguments at the same time.")
|
295
|
+
return 1
|
296
|
+
|
297
|
+
current_environment_path: str = get_environment_path()
|
298
|
+
|
299
|
+
if exe_path:
|
300
|
+
# If 'current_environment_path' is set and not None, and 'set_path' is True to change the PATH to provided 'exe_path' and
|
301
|
+
# the provided 'exe_path' is different from the current one, ask the user if they want to change it.
|
302
|
+
if current_environment_path and set_path and current_environment_path.lower() != exe_path.lower() and not force:
|
303
|
+
print(f"Current System Environment PATH Tesseract executable path: {current_environment_path}\n"
|
304
|
+
f"Do you want to update it with provided executable path?: {exe_path}\n"
|
305
|
+
f"(y/n)")
|
306
|
+
if input().strip().lower() != 'y':
|
307
|
+
print("Exiting without updating the path.")
|
308
|
+
return 0
|
309
|
+
|
310
|
+
if compile_portable:
|
311
|
+
latest_compiled_version: str = get_latest_compiled_version()
|
312
|
+
provided_exe_version: str = get_executable_version(exe_path)
|
313
|
+
|
314
|
+
executable_parent_path: str = os.path.dirname(exe_path)
|
315
|
+
tessdata_parent_path: str = os.path.join(executable_parent_path, "tessdata")
|
316
|
+
|
317
|
+
# Check if the provided executable path exists and only then overwrite.
|
318
|
+
if latest_compiled_version != provided_exe_version:
|
319
|
+
if os.path.exists(exe_path) and not force:
|
320
|
+
console.print(f"The provided Tesseract executable version: [{provided_exe_version}] "
|
321
|
+
f"is not the latest available version: [{latest_compiled_version}]. "
|
322
|
+
f"Do you want to update it and overwrite? (y/n)", style="yellow")
|
323
|
+
if input().strip().lower() != 'y':
|
324
|
+
print("Exiting without updating the provided tesseract executable.")
|
325
|
+
return 0
|
326
|
+
|
327
|
+
execution_result: int = compile_exe(set_path=False)
|
328
|
+
if execution_result != 0:
|
329
|
+
console.print("Failed to compile Tesseract from source. "
|
330
|
+
"Please check the logs for more details.", style="red")
|
331
|
+
return execution_result
|
332
|
+
|
333
|
+
# Backing up the current version of Tesseract executable. But backup only if the folder exists, since if it is not it's new installation.
|
334
|
+
if os.path.exists(executable_parent_path):
|
335
|
+
parent_of_the_current_parent_path: str = os.path.dirname(executable_parent_path)
|
336
|
+
exe_parent_dir_name: str = os.path.basename(executable_parent_path)
|
337
|
+
backup_path: str = os.path.join(parent_of_the_current_parent_path, f"{exe_parent_dir_name}_{provided_exe_version}_backup")
|
338
|
+
# Rename the current executable directory to back up.
|
339
|
+
shutil.move(executable_parent_path, backup_path)
|
340
|
+
print(f"Backed up the current Tesseract executable to: {backup_path}")
|
341
|
+
|
342
|
+
# Create new empty directory for the new Tesseract executable.
|
343
|
+
os.makedirs(executable_parent_path, exist_ok=True)
|
344
|
+
|
345
|
+
# Copy all the files from the compiled Tesseract directory to the provided executable path.
|
346
|
+
for item in TESSERACT_VCPKG_TOOLS_DIR.iterdir():
|
347
|
+
if item.is_file():
|
348
|
+
shutil.copy(item, executable_parent_path)
|
349
|
+
elif item.is_dir():
|
350
|
+
shutil.copytree(item, os.path.join(executable_parent_path, item.name), dirs_exist_ok=True)
|
351
|
+
else:
|
352
|
+
print(f"The provided Tesseract executable version: {provided_exe_version} "
|
353
|
+
f"is already the latest available version: {latest_compiled_version}. "
|
354
|
+
f"No need to update.")
|
355
|
+
|
356
|
+
# Remove the old executable from the PATH if it exists.
|
357
|
+
if not current_environment_path or current_environment_path.lower() != exe_path.lower():
|
358
|
+
# print(f"Removing old Tesseract executable path from the PATH: {current_environment_path}")
|
359
|
+
# subprocess.check_call(["setx", "PATH", f"%PATH%;{str(Path(current_environment_path).parent)}"])
|
360
|
+
|
361
|
+
# Set the new Tesseract executable path and TESSDATA_PREFIX.
|
362
|
+
subprocess.check_call(["setx", "PATH", f"%PATH%;{executable_parent_path}"])
|
363
|
+
subprocess.check_call(["setx", "TESSDATA_PREFIX", tessdata_parent_path])
|
364
|
+
print(f"Tesseract executable path set to: {exe_path}")
|
365
|
+
print(f"TESSDATA_PREFIX set to: {tessdata_parent_path}")
|
366
|
+
return 0
|
367
|
+
|
368
|
+
if installer_usage:
|
369
|
+
print("This option is not implemented yet, please use the [--compile_portable] option to compile Tesseract from source.")
|
370
|
+
return 0
|
371
|
+
|
372
|
+
if compile_portable:
|
373
|
+
if not force:
|
374
|
+
print("Compiling Tesseract from source can take time ~2h. "
|
375
|
+
"Do you want to continue? (y/n)")
|
376
|
+
if input().strip().lower() != 'y':
|
377
|
+
return 0
|
378
|
+
|
379
|
+
if current_environment_path != TESSERACT_VCPKG_TOOLS_EXE and set_path:
|
380
|
+
print(f"Current Tesseract executable path: {current_environment_path}\n"
|
381
|
+
f"Do you want to update it with compiled executable path?: {TESSERACT_VCPKG_TOOLS_EXE}?\n"
|
382
|
+
f"(y/n)")
|
383
|
+
if input().strip().lower() != 'y':
|
384
|
+
print("Exiting without compiling Tesseract.")
|
385
|
+
return 0
|
386
|
+
|
387
|
+
execution_result: int = compile_exe(set_path=set_path)
|
388
|
+
if execution_result != 0:
|
389
|
+
print("Failed to compile Tesseract from source. "
|
390
|
+
"Please check the logs for more details.")
|
391
|
+
return execution_result
|
392
|
+
else:
|
393
|
+
print("Tesseract OCR compiled successfully.")
|
394
|
+
return 0
|
395
|
+
|
396
|
+
return 0
|
397
|
+
|
398
|
+
|
399
|
+
if __name__ == '__main__':
|
400
|
+
parser = _make_parser()
|
401
|
+
args = parser.parse_args()
|
402
|
+
sys.exit(main(
|
403
|
+
installer_usage=args.installer_usage,
|
404
|
+
compile_portable=args.compile_portable,
|
405
|
+
installer_version_string_fetch=args.installer_version_string_fetch,
|
406
|
+
compile_version_string_fetch=args.compile_version_string_fetch,
|
407
|
+
get_path=args.get_path,
|
408
|
+
set_path=args.set_path,
|
409
|
+
force=args.force,
|
410
|
+
exe_path=args.exe_path
|
411
|
+
))
|
@@ -0,0 +1,63 @@
|
|
1
|
+
from pathlib import Path
|
2
|
+
from types import ModuleType
|
3
|
+
from typing import Literal
|
4
|
+
|
5
|
+
from . import _base
|
6
|
+
from . helpers import tesseract_ocr_manager
|
7
|
+
|
8
|
+
|
9
|
+
class TesseractOCR(_base.BaseInstaller):
|
10
|
+
def __init__(self):
|
11
|
+
super().__init__()
|
12
|
+
self.name: str = Path(__file__).stem
|
13
|
+
self.description: str = "Tesseract OCR Installer"
|
14
|
+
self.version: str = tesseract_ocr_manager.VERSION
|
15
|
+
self.platforms: list = ["windows"]
|
16
|
+
self.helper: ModuleType = tesseract_ocr_manager
|
17
|
+
|
18
|
+
self.dir_path: str = str(Path(self.base_path) / self.name)
|
19
|
+
self.exe_path: str = str(Path(self.dir_path) / "tesseract.exe")
|
20
|
+
|
21
|
+
def install(
|
22
|
+
self,
|
23
|
+
force: bool = False
|
24
|
+
):
|
25
|
+
tesseract_ocr_manager.main(
|
26
|
+
compile_portable=True,
|
27
|
+
set_path=True,
|
28
|
+
exe_path=self.exe_path,
|
29
|
+
force=force
|
30
|
+
)
|
31
|
+
|
32
|
+
def update(
|
33
|
+
self,
|
34
|
+
force: bool = False
|
35
|
+
):
|
36
|
+
self.install(force=force)
|
37
|
+
|
38
|
+
def _show_help(
|
39
|
+
self,
|
40
|
+
method: Literal["install", "uninstall", "update"]
|
41
|
+
) -> None:
|
42
|
+
if method == "install":
|
43
|
+
method_help: str = (
|
44
|
+
"This method uses the [tesseract_ocr_manager.py] with the following arguments:\n"
|
45
|
+
" --compile-portable - compile the latest tesseract executable.\n"
|
46
|
+
" --set-path - set system PATH variable to provided executable.\n"
|
47
|
+
f' --exe-path "{self.exe_path}" - Specify the target executable\n'
|
48
|
+
"\n"
|
49
|
+
" --force - force reinstallation/recompilation of the latest version even if executable is already present.\n"
|
50
|
+
" This one is used only if you provide it explicitly to the 'install' command. Example:\n"
|
51
|
+
" dkinst install tesseract_ocr force\n"
|
52
|
+
"\n"
|
53
|
+
"You can also use the 'manual' method to provide custom arguments to the helper script.\n"
|
54
|
+
"Example:\n"
|
55
|
+
" dkinst manual tesseract_ocr help\n"
|
56
|
+
" dkinst manual tesseract_ocr --compile-portable --set-path\n"
|
57
|
+
"\n"
|
58
|
+
)
|
59
|
+
print(method_help)
|
60
|
+
elif method == "update":
|
61
|
+
print("In this installer 'update()' is the same as 'install()'.")
|
62
|
+
else:
|
63
|
+
raise ValueError(f"Unknown method '{method}'.")
|
@@ -0,0 +1,80 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: dkinst
|
3
|
+
Version: 0.1.0
|
4
|
+
Summary: Den K Simple Installer
|
5
|
+
Author: Denis Kras
|
6
|
+
License-Expression: MIT
|
7
|
+
Project-URL: Homepage, https://github.com/denis-kras/dkinst
|
8
|
+
Classifier: Intended Audience :: Developers
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
10
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
11
|
+
Requires-Python: >=3.12
|
12
|
+
Description-Content-Type: text/markdown
|
13
|
+
License-File: LICENSE
|
14
|
+
Requires-Dist: wheel
|
15
|
+
Requires-Dist: rich==14.1.0
|
16
|
+
Dynamic: license-file
|
17
|
+
|
18
|
+
<h1 align="center">Den K Simple Installer - dkinst</h1>
|
19
|
+
|
20
|
+
|
21
|
+
|
22
|
+
<!-- ABOUT THE PROJECT -->
|
23
|
+
## About The Project
|
24
|
+
|
25
|
+
|
26
|
+
dkinst is a simple installer mostly for my professional needs, in case I need a one-click installer for a library or a tool.
|
27
|
+
So, I will not search for a setup script/line or write one.
|
28
|
+
It is written in Python, currently supports 3.12. Hope to support newer versions in the near future.
|
29
|
+
|
30
|
+
|
31
|
+
<!-- GETTING STARTED -->
|
32
|
+
## Getting Started
|
33
|
+
|
34
|
+
To get a local copy up and running follow these simple steps.
|
35
|
+
|
36
|
+
### Installation Windows
|
37
|
+
|
38
|
+
1. Install Python 3.12.10 or use the silent installer that will download and install it for you.<br>
|
39
|
+
1.1. Download the file: install_python_as_admin.cmd<br>
|
40
|
+
1.2. Run CMD as admin.<br>
|
41
|
+
1.3. In CMD run the script with '3.12.10' argument:
|
42
|
+
```cmd
|
43
|
+
install_python_as_admin.cmd 3.12.10
|
44
|
+
```
|
45
|
+
<br>
|
46
|
+
2. Install the library using pip:
|
47
|
+
```cmd
|
48
|
+
pip install dkinst
|
49
|
+
```
|
50
|
+
|
51
|
+
### Installation Linux
|
52
|
+
|
53
|
+
Ubuntu 24 already comes with python 3.12, just use pip to install the library:
|
54
|
+
```sh
|
55
|
+
pip install dkinst
|
56
|
+
```
|
57
|
+
|
58
|
+
<!-- USAGE EXAMPLES -->
|
59
|
+
## Usage
|
60
|
+
|
61
|
+
Execute in CMD:
|
62
|
+
```cmd
|
63
|
+
dkinst
|
64
|
+
dkinst help
|
65
|
+
```
|
66
|
+
For full help and examples about installation commands.
|
67
|
+
|
68
|
+
|
69
|
+
|
70
|
+
<!-- LICENSE -->
|
71
|
+
## License
|
72
|
+
|
73
|
+
Distributed under the MIT License. See `LICENSE.txt` for more information.
|
74
|
+
|
75
|
+
|
76
|
+
|
77
|
+
<!-- HISTORY -->
|
78
|
+
## History
|
79
|
+
|
80
|
+
[History.md](https://github.com/BugSec-Official/atomicshop/blob/main/History.md#history)
|
@@ -0,0 +1,13 @@
|
|
1
|
+
dkinst/__init__.py,sha256=TCgwn6zw4K2i8qTszGSbHd30uBQW4giS4E8GEd8OglY,78
|
2
|
+
dkinst/cli.py,sha256=ChBAkoPI-mifHlO4dOpIgDBFKd1zUfZM2FEIxoQuO9s,9238
|
3
|
+
dkinst/installers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
4
|
+
dkinst/installers/_base.py,sha256=_3t0_xkbHRvFtxpDkLkZw9_pizIEiK5sXTYMIWK3jUw,10278
|
5
|
+
dkinst/installers/tesseract_ocr.py,sha256=LqcOJCTQJU3xPz3wm_nQI5Bt6bB5dgDPzf7WykpSM6A,2524
|
6
|
+
dkinst/installers/helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
7
|
+
dkinst/installers/helpers/tesseract_ocr_manager.py,sha256=-Br9vHbqcQHRsNlDkakw5mLMxMBTydqXCblhpcBmp0A,17734
|
8
|
+
dkinst-0.1.0.dist-info/licenses/LICENSE,sha256=ohlj1rmsTHdctr-wyqmP6kbFC6Sff8pJd29v3pruZ18,1088
|
9
|
+
dkinst-0.1.0.dist-info/METADATA,sha256=TNOweiIXtwWbwPk-1vzq0WDLN0Mzx-SeosGW516BN5Y,2018
|
10
|
+
dkinst-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
11
|
+
dkinst-0.1.0.dist-info/entry_points.txt,sha256=YNgO3GufKMdA_oRqWEGAVh6k00oIuPItqqChoUtU278,43
|
12
|
+
dkinst-0.1.0.dist-info/top_level.txt,sha256=_dGNrME6z6Ihv2sxGC4hfAlnVuhoDlKwx-97LZ2xtQ0,7
|
13
|
+
dkinst-0.1.0.dist-info/RECORD,,
|
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2025 denis-kras
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
@@ -0,0 +1 @@
|
|
1
|
+
dkinst
|