KL-Py 0.0.1__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.
- KL_Py/KL_Py.py +4400 -0
- KL_Py/__init__.py +1 -0
- KL_Py/hindGui/__init__.py +16529 -0
- KL_Py/hindGui/_utils.py +140 -0
- KL_Py/hindGui/elements/base.py +1055 -0
- KL_Py/hindGui/elements/button.py +947 -0
- KL_Py/hindGui/elements/calendar.py +222 -0
- KL_Py/hindGui/elements/canvas.py +122 -0
- KL_Py/hindGui/elements/center.py +44 -0
- KL_Py/hindGui/elements/checkbox.py +240 -0
- KL_Py/hindGui/elements/column.py +443 -0
- KL_Py/hindGui/elements/combo.py +329 -0
- KL_Py/hindGui/elements/error.py +47 -0
- KL_Py/hindGui/elements/frame.py +273 -0
- KL_Py/hindGui/elements/graph.py +817 -0
- KL_Py/hindGui/elements/helpers.py +202 -0
- KL_Py/hindGui/elements/image.py +324 -0
- KL_Py/hindGui/elements/input.py +310 -0
- KL_Py/hindGui/elements/list_box.py +392 -0
- KL_Py/hindGui/elements/menu.py +192 -0
- KL_Py/hindGui/elements/multiline.py +685 -0
- KL_Py/hindGui/elements/option_menu.py +168 -0
- KL_Py/hindGui/elements/pane.py +127 -0
- KL_Py/hindGui/elements/progress_bar.py +304 -0
- KL_Py/hindGui/elements/radio.py +252 -0
- KL_Py/hindGui/elements/separator.py +64 -0
- KL_Py/hindGui/elements/sizegrip.py +34 -0
- KL_Py/hindGui/elements/slider.py +199 -0
- KL_Py/hindGui/elements/spin.py +252 -0
- KL_Py/hindGui/elements/status_bar.py +165 -0
- KL_Py/hindGui/elements/stretch.py +26 -0
- KL_Py/hindGui/elements/tab.py +697 -0
- KL_Py/hindGui/elements/table.py +463 -0
- KL_Py/hindGui/elements/text.py +413 -0
- KL_Py/hindGui/elements/tree.py +493 -0
- KL_Py/hindGui/replace.py +45 -0
- KL_Py/hindGui/tray.py +358 -0
- KL_Py/hindGui/window.py +2906 -0
- KL_Py/requests/__init__.py +184 -0
- KL_Py/requests/__version__.py +14 -0
- KL_Py/requests/_internal_utils.py +50 -0
- KL_Py/requests/adapters.py +719 -0
- KL_Py/requests/api.py +157 -0
- KL_Py/requests/auth.py +314 -0
- KL_Py/requests/certifi/__init__.py +4 -0
- KL_Py/requests/certifi/__main__.py +12 -0
- KL_Py/requests/certifi/core.py +83 -0
- KL_Py/requests/certifi/py.typed +0 -0
- KL_Py/requests/certs.py +17 -0
- KL_Py/requests/charset_normalizer/__init__.py +48 -0
- KL_Py/requests/charset_normalizer/__main__.py +6 -0
- KL_Py/requests/charset_normalizer/api.py +671 -0
- KL_Py/requests/charset_normalizer/cd.py +421 -0
- KL_Py/requests/charset_normalizer/cli/__init__.py +8 -0
- KL_Py/requests/charset_normalizer/cli/__main__.py +363 -0
- KL_Py/requests/charset_normalizer/constant.py +2031 -0
- KL_Py/requests/charset_normalizer/legacy.py +80 -0
- KL_Py/requests/charset_normalizer/md.py +744 -0
- KL_Py/requests/charset_normalizer/models.py +359 -0
- KL_Py/requests/charset_normalizer/py.typed +0 -0
- KL_Py/requests/charset_normalizer/utils.py +420 -0
- KL_Py/requests/charset_normalizer/version.py +8 -0
- KL_Py/requests/compat.py +106 -0
- KL_Py/requests/cookies.py +561 -0
- KL_Py/requests/exceptions.py +151 -0
- KL_Py/requests/help.py +134 -0
- KL_Py/requests/hooks.py +33 -0
- KL_Py/requests/idna/__init__.py +45 -0
- KL_Py/requests/idna/codec.py +122 -0
- KL_Py/requests/idna/compat.py +15 -0
- KL_Py/requests/idna/core.py +437 -0
- KL_Py/requests/idna/idnadata.py +4309 -0
- KL_Py/requests/idna/intranges.py +57 -0
- KL_Py/requests/idna/package_data.py +1 -0
- KL_Py/requests/idna/py.typed +0 -0
- KL_Py/requests/idna/uts46data.py +8841 -0
- KL_Py/requests/models.py +1039 -0
- KL_Py/requests/packages.py +23 -0
- KL_Py/requests/sessions.py +831 -0
- KL_Py/requests/status_codes.py +128 -0
- KL_Py/requests/structures.py +99 -0
- KL_Py/requests/urllib3/__init__.py +211 -0
- KL_Py/requests/urllib3/_base_connection.py +165 -0
- KL_Py/requests/urllib3/_collections.py +487 -0
- KL_Py/requests/urllib3/_request_methods.py +278 -0
- KL_Py/requests/urllib3/_version.py +34 -0
- KL_Py/requests/urllib3/connection.py +1099 -0
- KL_Py/requests/urllib3/connectionpool.py +1178 -0
- KL_Py/requests/urllib3/contrib/__init__.py +0 -0
- KL_Py/requests/urllib3/contrib/emscripten/__init__.py +17 -0
- KL_Py/requests/urllib3/contrib/emscripten/connection.py +260 -0
- KL_Py/requests/urllib3/contrib/emscripten/fetch.py +726 -0
- KL_Py/requests/urllib3/contrib/emscripten/request.py +22 -0
- KL_Py/requests/urllib3/contrib/emscripten/response.py +277 -0
- KL_Py/requests/urllib3/contrib/pyopenssl.py +564 -0
- KL_Py/requests/urllib3/contrib/socks.py +228 -0
- KL_Py/requests/urllib3/exceptions.py +335 -0
- KL_Py/requests/urllib3/fields.py +341 -0
- KL_Py/requests/urllib3/filepost.py +89 -0
- KL_Py/requests/urllib3/http2/__init__.py +53 -0
- KL_Py/requests/urllib3/http2/connection.py +356 -0
- KL_Py/requests/urllib3/http2/probe.py +87 -0
- KL_Py/requests/urllib3/poolmanager.py +651 -0
- KL_Py/requests/urllib3/py.typed +2 -0
- KL_Py/requests/urllib3/response.py +1480 -0
- KL_Py/requests/urllib3/util/__init__.py +42 -0
- KL_Py/requests/urllib3/util/connection.py +137 -0
- KL_Py/requests/urllib3/util/proxy.py +43 -0
- KL_Py/requests/urllib3/util/request.py +263 -0
- KL_Py/requests/urllib3/util/response.py +101 -0
- KL_Py/requests/urllib3/util/retry.py +549 -0
- KL_Py/requests/urllib3/util/ssl_.py +527 -0
- KL_Py/requests/urllib3/util/ssl_match_hostname.py +159 -0
- KL_Py/requests/urllib3/util/ssltransport.py +271 -0
- KL_Py/requests/urllib3/util/timeout.py +275 -0
- KL_Py/requests/urllib3/util/url.py +469 -0
- KL_Py/requests/urllib3/util/util.py +42 -0
- KL_Py/requests/urllib3/util/wait.py +124 -0
- KL_Py/requests/utils.py +1086 -0
- KL_Py/when.py +144 -0
- kl_py-0.0.1.dist-info/METADATA +15 -0
- kl_py-0.0.1.dist-info/RECORD +125 -0
- kl_py-0.0.1.dist-info/WHEEL +5 -0
- kl_py-0.0.1.dist-info/licenses/LICENSE +21 -0
- kl_py-0.0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import sys
|
|
5
|
+
import typing
|
|
6
|
+
from json import dumps
|
|
7
|
+
from os.path import abspath, basename, dirname, join, realpath
|
|
8
|
+
from platform import python_version
|
|
9
|
+
from unicodedata import unidata_version
|
|
10
|
+
|
|
11
|
+
import charset_normalizer.md as md_module
|
|
12
|
+
from charset_normalizer import from_fp
|
|
13
|
+
from charset_normalizer.models import CliDetectionResult
|
|
14
|
+
from charset_normalizer.version import __version__
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def query_yes_no(question: str, default: str = "yes") -> bool:
|
|
18
|
+
"""Ask a yes/no question via input() and return the answer as a bool."""
|
|
19
|
+
prompt = " [Y/n] " if default == "yes" else " [y/N] "
|
|
20
|
+
|
|
21
|
+
while True:
|
|
22
|
+
choice = input(question + prompt).strip().lower()
|
|
23
|
+
if not choice:
|
|
24
|
+
return default == "yes"
|
|
25
|
+
if choice in ("y", "yes"):
|
|
26
|
+
return True
|
|
27
|
+
if choice in ("n", "no"):
|
|
28
|
+
return False
|
|
29
|
+
print("Please respond with 'y' or 'n'.")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class FileType:
|
|
33
|
+
"""Factory for creating file object types
|
|
34
|
+
|
|
35
|
+
Instances of FileType are typically passed as type= arguments to the
|
|
36
|
+
ArgumentParser add_argument() method.
|
|
37
|
+
|
|
38
|
+
Keyword Arguments:
|
|
39
|
+
- mode -- A string indicating how the file is to be opened. Accepts the
|
|
40
|
+
same values as the builtin open() function.
|
|
41
|
+
- bufsize -- The file's desired buffer size. Accepts the same values as
|
|
42
|
+
the builtin open() function.
|
|
43
|
+
- encoding -- The file's encoding. Accepts the same values as the
|
|
44
|
+
builtin open() function.
|
|
45
|
+
- errors -- A string indicating how encoding and decoding errors are to
|
|
46
|
+
be handled. Accepts the same value as the builtin open() function.
|
|
47
|
+
|
|
48
|
+
Backported from CPython 3.12
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
def __init__(
|
|
52
|
+
self,
|
|
53
|
+
mode: str = "r",
|
|
54
|
+
bufsize: int = -1,
|
|
55
|
+
encoding: str | None = None,
|
|
56
|
+
errors: str | None = None,
|
|
57
|
+
):
|
|
58
|
+
self._mode = mode
|
|
59
|
+
self._bufsize = bufsize
|
|
60
|
+
self._encoding = encoding
|
|
61
|
+
self._errors = errors
|
|
62
|
+
|
|
63
|
+
def __call__(self, string: str) -> typing.IO: # type: ignore[type-arg]
|
|
64
|
+
# the special argument "-" means sys.std{in,out}
|
|
65
|
+
if string == "-":
|
|
66
|
+
if "r" in self._mode:
|
|
67
|
+
return sys.stdin.buffer if "b" in self._mode else sys.stdin
|
|
68
|
+
elif any(c in self._mode for c in "wax"):
|
|
69
|
+
return sys.stdout.buffer if "b" in self._mode else sys.stdout
|
|
70
|
+
else:
|
|
71
|
+
msg = f'argument "-" with mode {self._mode}'
|
|
72
|
+
raise ValueError(msg)
|
|
73
|
+
|
|
74
|
+
# all other arguments are used as file names
|
|
75
|
+
try:
|
|
76
|
+
return open(string, self._mode, self._bufsize, self._encoding, self._errors)
|
|
77
|
+
except OSError as e:
|
|
78
|
+
message = f"can't open '{string}': {e}"
|
|
79
|
+
raise argparse.ArgumentTypeError(message)
|
|
80
|
+
|
|
81
|
+
def __repr__(self) -> str:
|
|
82
|
+
args = self._mode, self._bufsize
|
|
83
|
+
kwargs = [("encoding", self._encoding), ("errors", self._errors)]
|
|
84
|
+
args_str = ", ".join(
|
|
85
|
+
[repr(arg) for arg in args if arg != -1]
|
|
86
|
+
+ [f"{kw}={arg!r}" for kw, arg in kwargs if arg is not None]
|
|
87
|
+
)
|
|
88
|
+
return f"{type(self).__name__}({args_str})"
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def cli_detect(argv: list[str] | None = None) -> int:
|
|
92
|
+
"""
|
|
93
|
+
CLI assistant using ARGV and ArgumentParser
|
|
94
|
+
:param argv:
|
|
95
|
+
:return: 0 if everything is fine, anything else equal trouble
|
|
96
|
+
"""
|
|
97
|
+
parser = argparse.ArgumentParser(
|
|
98
|
+
description="The Real First Universal Charset Detector. "
|
|
99
|
+
"Discover originating encoding used on text file. "
|
|
100
|
+
"Normalize text to unicode."
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
parser.add_argument(
|
|
104
|
+
"files", type=FileType("rb"), nargs="+", help="File(s) to be analysed"
|
|
105
|
+
)
|
|
106
|
+
parser.add_argument(
|
|
107
|
+
"-v",
|
|
108
|
+
"--verbose",
|
|
109
|
+
action="store_true",
|
|
110
|
+
default=False,
|
|
111
|
+
dest="verbose",
|
|
112
|
+
help="Display complementary information about file if any. "
|
|
113
|
+
"Stdout will contain logs about the detection process.",
|
|
114
|
+
)
|
|
115
|
+
parser.add_argument(
|
|
116
|
+
"-a",
|
|
117
|
+
"--with-alternative",
|
|
118
|
+
action="store_true",
|
|
119
|
+
default=False,
|
|
120
|
+
dest="alternatives",
|
|
121
|
+
help="Output complementary possibilities if any. Top-level JSON WILL be a list.",
|
|
122
|
+
)
|
|
123
|
+
parser.add_argument(
|
|
124
|
+
"-n",
|
|
125
|
+
"--normalize",
|
|
126
|
+
action="store_true",
|
|
127
|
+
default=False,
|
|
128
|
+
dest="normalize",
|
|
129
|
+
help="Permit to normalize input file. If not set, program does not write anything.",
|
|
130
|
+
)
|
|
131
|
+
parser.add_argument(
|
|
132
|
+
"-m",
|
|
133
|
+
"--minimal",
|
|
134
|
+
action="store_true",
|
|
135
|
+
default=False,
|
|
136
|
+
dest="minimal",
|
|
137
|
+
help="Only output the charset detected to STDOUT. Disabling JSON output.",
|
|
138
|
+
)
|
|
139
|
+
parser.add_argument(
|
|
140
|
+
"-r",
|
|
141
|
+
"--replace",
|
|
142
|
+
action="store_true",
|
|
143
|
+
default=False,
|
|
144
|
+
dest="replace",
|
|
145
|
+
help="Replace file when trying to normalize it instead of creating a new one.",
|
|
146
|
+
)
|
|
147
|
+
parser.add_argument(
|
|
148
|
+
"-f",
|
|
149
|
+
"--force",
|
|
150
|
+
action="store_true",
|
|
151
|
+
default=False,
|
|
152
|
+
dest="force",
|
|
153
|
+
help="Replace file without asking if you are sure, use this flag with caution.",
|
|
154
|
+
)
|
|
155
|
+
parser.add_argument(
|
|
156
|
+
"-i",
|
|
157
|
+
"--no-preemptive",
|
|
158
|
+
action="store_true",
|
|
159
|
+
default=False,
|
|
160
|
+
dest="no_preemptive",
|
|
161
|
+
help="Disable looking at a charset declaration to hint the detector.",
|
|
162
|
+
)
|
|
163
|
+
parser.add_argument(
|
|
164
|
+
"-t",
|
|
165
|
+
"--threshold",
|
|
166
|
+
action="store",
|
|
167
|
+
default=0.2,
|
|
168
|
+
type=float,
|
|
169
|
+
dest="threshold",
|
|
170
|
+
help="Define a custom maximum amount of noise allowed in decoded content. 0. <= noise <= 1.",
|
|
171
|
+
)
|
|
172
|
+
parser.add_argument(
|
|
173
|
+
"--version",
|
|
174
|
+
action="version",
|
|
175
|
+
version="Charset-Normalizer {} - Python {} - Unicode {} - SpeedUp {}".format(
|
|
176
|
+
__version__,
|
|
177
|
+
python_version(),
|
|
178
|
+
unidata_version,
|
|
179
|
+
"OFF" if md_module.__file__.lower().endswith(".py") else "ON",
|
|
180
|
+
),
|
|
181
|
+
help="Show version information and exit.",
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
args = parser.parse_args(argv)
|
|
185
|
+
|
|
186
|
+
if args.replace is True and args.normalize is False:
|
|
187
|
+
if args.files:
|
|
188
|
+
for my_file in args.files:
|
|
189
|
+
my_file.close()
|
|
190
|
+
print("Use --replace in addition of --normalize only.", file=sys.stderr)
|
|
191
|
+
return 1
|
|
192
|
+
|
|
193
|
+
if args.force is True and args.replace is False:
|
|
194
|
+
if args.files:
|
|
195
|
+
for my_file in args.files:
|
|
196
|
+
my_file.close()
|
|
197
|
+
print("Use --force in addition of --replace only.", file=sys.stderr)
|
|
198
|
+
return 1
|
|
199
|
+
|
|
200
|
+
if args.threshold < 0.0 or args.threshold > 1.0:
|
|
201
|
+
if args.files:
|
|
202
|
+
for my_file in args.files:
|
|
203
|
+
my_file.close()
|
|
204
|
+
print("--threshold VALUE should be between 0. AND 1.", file=sys.stderr)
|
|
205
|
+
return 1
|
|
206
|
+
|
|
207
|
+
x_ = []
|
|
208
|
+
|
|
209
|
+
for my_file in args.files:
|
|
210
|
+
matches = from_fp(
|
|
211
|
+
my_file,
|
|
212
|
+
threshold=args.threshold,
|
|
213
|
+
explain=args.verbose,
|
|
214
|
+
preemptive_behaviour=args.no_preemptive is False,
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
best_guess = matches.best()
|
|
218
|
+
|
|
219
|
+
if best_guess is None:
|
|
220
|
+
print(
|
|
221
|
+
'Unable to identify originating encoding for "{}". {}'.format(
|
|
222
|
+
my_file.name,
|
|
223
|
+
(
|
|
224
|
+
"Maybe try increasing maximum amount of chaos."
|
|
225
|
+
if args.threshold < 1.0
|
|
226
|
+
else ""
|
|
227
|
+
),
|
|
228
|
+
),
|
|
229
|
+
file=sys.stderr,
|
|
230
|
+
)
|
|
231
|
+
x_.append(
|
|
232
|
+
CliDetectionResult(
|
|
233
|
+
abspath(my_file.name),
|
|
234
|
+
None,
|
|
235
|
+
[],
|
|
236
|
+
[],
|
|
237
|
+
"Unknown",
|
|
238
|
+
[],
|
|
239
|
+
False,
|
|
240
|
+
1.0,
|
|
241
|
+
0.0,
|
|
242
|
+
None,
|
|
243
|
+
True,
|
|
244
|
+
)
|
|
245
|
+
)
|
|
246
|
+
else:
|
|
247
|
+
x_.append(
|
|
248
|
+
CliDetectionResult(
|
|
249
|
+
abspath(my_file.name),
|
|
250
|
+
best_guess.encoding,
|
|
251
|
+
best_guess.encoding_aliases,
|
|
252
|
+
[
|
|
253
|
+
cp
|
|
254
|
+
for cp in best_guess.could_be_from_charset
|
|
255
|
+
if cp != best_guess.encoding
|
|
256
|
+
],
|
|
257
|
+
best_guess.language,
|
|
258
|
+
best_guess.alphabets,
|
|
259
|
+
best_guess.bom,
|
|
260
|
+
best_guess.percent_chaos,
|
|
261
|
+
best_guess.percent_coherence,
|
|
262
|
+
None,
|
|
263
|
+
True,
|
|
264
|
+
)
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
if len(matches) > 1 and args.alternatives:
|
|
268
|
+
for el in matches:
|
|
269
|
+
if el != best_guess:
|
|
270
|
+
x_.append(
|
|
271
|
+
CliDetectionResult(
|
|
272
|
+
abspath(my_file.name),
|
|
273
|
+
el.encoding,
|
|
274
|
+
el.encoding_aliases,
|
|
275
|
+
[
|
|
276
|
+
cp
|
|
277
|
+
for cp in el.could_be_from_charset
|
|
278
|
+
if cp != el.encoding
|
|
279
|
+
],
|
|
280
|
+
el.language,
|
|
281
|
+
el.alphabets,
|
|
282
|
+
el.bom,
|
|
283
|
+
el.percent_chaos,
|
|
284
|
+
el.percent_coherence,
|
|
285
|
+
None,
|
|
286
|
+
False,
|
|
287
|
+
)
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
if args.normalize is True:
|
|
291
|
+
if best_guess.encoding.startswith("utf") is True:
|
|
292
|
+
print(
|
|
293
|
+
'"{}" file does not need to be normalized, as it already came from unicode.'.format(
|
|
294
|
+
my_file.name
|
|
295
|
+
),
|
|
296
|
+
file=sys.stderr,
|
|
297
|
+
)
|
|
298
|
+
if my_file.closed is False:
|
|
299
|
+
my_file.close()
|
|
300
|
+
continue
|
|
301
|
+
|
|
302
|
+
dir_path = dirname(realpath(my_file.name))
|
|
303
|
+
file_name = basename(realpath(my_file.name))
|
|
304
|
+
|
|
305
|
+
o_: list[str] = file_name.split(".")
|
|
306
|
+
|
|
307
|
+
if args.replace is False:
|
|
308
|
+
o_.insert(-1, best_guess.encoding)
|
|
309
|
+
if my_file.closed is False:
|
|
310
|
+
my_file.close()
|
|
311
|
+
elif (
|
|
312
|
+
args.force is False
|
|
313
|
+
and query_yes_no(
|
|
314
|
+
'Are you sure to normalize "{}" by replacing it ?'.format(
|
|
315
|
+
my_file.name
|
|
316
|
+
),
|
|
317
|
+
"no",
|
|
318
|
+
)
|
|
319
|
+
is False
|
|
320
|
+
):
|
|
321
|
+
if my_file.closed is False:
|
|
322
|
+
my_file.close()
|
|
323
|
+
continue
|
|
324
|
+
|
|
325
|
+
try:
|
|
326
|
+
x_[0].unicode_path = join(dir_path, ".".join(o_))
|
|
327
|
+
|
|
328
|
+
with open(x_[0].unicode_path, "wb") as fp:
|
|
329
|
+
fp.write(best_guess.output())
|
|
330
|
+
except OSError as e:
|
|
331
|
+
print(str(e), file=sys.stderr)
|
|
332
|
+
if my_file.closed is False:
|
|
333
|
+
my_file.close()
|
|
334
|
+
return 2
|
|
335
|
+
|
|
336
|
+
if my_file.closed is False:
|
|
337
|
+
my_file.close()
|
|
338
|
+
|
|
339
|
+
if args.minimal is False:
|
|
340
|
+
print(
|
|
341
|
+
dumps(
|
|
342
|
+
[el.__dict__ for el in x_] if len(x_) > 1 else x_[0].__dict__,
|
|
343
|
+
ensure_ascii=True,
|
|
344
|
+
indent=4,
|
|
345
|
+
)
|
|
346
|
+
)
|
|
347
|
+
else:
|
|
348
|
+
for my_file in args.files:
|
|
349
|
+
print(
|
|
350
|
+
", ".join(
|
|
351
|
+
[
|
|
352
|
+
el.encoding or "undefined"
|
|
353
|
+
for el in x_
|
|
354
|
+
if el.path == abspath(my_file.name)
|
|
355
|
+
]
|
|
356
|
+
)
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
return 0
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
if __name__ == "__main__":
|
|
363
|
+
cli_detect()
|