kkpy-cli 0.1.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.
Files changed (42) hide show
  1. kkpy/__init__.py +0 -0
  2. kkpy/__version__.py +1 -0
  3. kkpy/poetry/install-poetry.py +831 -0
  4. kkpy/poetry/install.sh +22 -0
  5. kkpy/template.py +227 -0
  6. kkpy/templates/.editorconfig +23 -0
  7. kkpy/templates/.gitignore +54 -0
  8. kkpy/templates/.requirements-template.txt +4 -0
  9. kkpy/templates/.requirements-test.txt +3 -0
  10. kkpy/templates/Jenkinsfile +0 -0
  11. kkpy/templates/README.md +0 -0
  12. kkpy/templates/bin/start.py +33 -0
  13. kkpy/templates/bin/start.sh +9 -0
  14. kkpy/templates/src/__init__.py +11 -0
  15. kkpy/templates/src/configs/app.ini +9 -0
  16. kkpy/templates/src/configs/db.ini +5 -0
  17. kkpy/templates/src/configs/logging_config.ini +27 -0
  18. kkpy/templates/src/main.py +27 -0
  19. kkpy/templates/src/requirements.txt +0 -0
  20. kkpy/templates/src/utils/__init__.py +0 -0
  21. kkpy/templates/src/utils/utils.py +63 -0
  22. kkpy/templates/tests/__init__.py +0 -0
  23. kkpy/templates/tests/conftest.py +9 -0
  24. kkpy/templates/tests/test_sample.py +4 -0
  25. kkpy/templates/tox.ini +27 -0
  26. kkpy_cli/__init__.py +0 -0
  27. kkpy_cli/cli.py +197 -0
  28. kkpy_cli/commands/__init__.py +0 -0
  29. kkpy_cli/commands/build.py +34 -0
  30. kkpy_cli/commands/clean.py +19 -0
  31. kkpy_cli/commands/init.py +34 -0
  32. kkpy_cli/commands/install.py +22 -0
  33. kkpy_cli/commands/run.py +23 -0
  34. kkpy_cli/commands/test.py +15 -0
  35. kkpy_cli/utils/__init__.py +1 -0
  36. kkpy_cli/utils/cli_args.py +21 -0
  37. kkpy_cli/utils/exception.py +12 -0
  38. kkpy_cli-0.1.1.dist-info/METADATA +26 -0
  39. kkpy_cli-0.1.1.dist-info/RECORD +42 -0
  40. kkpy_cli-0.1.1.dist-info/WHEEL +5 -0
  41. kkpy_cli-0.1.1.dist-info/entry_points.txt +2 -0
  42. kkpy_cli-0.1.1.dist-info/top_level.txt +2 -0
@@ -0,0 +1,831 @@
1
+ """
2
+ This script will install Poetry and its dependencies.
3
+
4
+ It does, in order:
5
+
6
+ - Downloads the virtualenv package to a temporary directory and add it to sys.path.
7
+ - Creates a virtual environment in the correct OS data dir which will be
8
+ - `%APPDATA%\\pypoetry` on Windows
9
+ - ~/Library/Application Support/pypoetry on MacOS
10
+ - `${XDG_DATA_HOME}/pypoetry` (or `~/.local/share/pypoetry` if it's not set) on UNIX systems
11
+ - In `${POETRY_HOME}` if it's set.
12
+ - Installs the latest or given version of Poetry inside this virtual environment.
13
+ - Installs a `poetry` script in the Python user directory (or `${POETRY_HOME/bin}` if `POETRY_HOME` is set).
14
+ """
15
+
16
+ import argparse
17
+ import json
18
+ import os
19
+ import re
20
+ import shutil
21
+ import site
22
+ import subprocess
23
+ import sys
24
+ import tempfile
25
+
26
+ from contextlib import closing
27
+ from contextlib import contextmanager
28
+ from functools import cmp_to_key
29
+ from io import UnsupportedOperation
30
+ from pathlib import Path
31
+ from typing import Optional
32
+ from urllib.request import Request
33
+ from urllib.request import urlopen
34
+
35
+
36
+ SHELL = os.getenv("SHELL", "")
37
+ WINDOWS = sys.platform.startswith("win") or (sys.platform == "cli" and os.name == "nt")
38
+ MACOS = sys.platform == "darwin"
39
+
40
+ FOREGROUND_COLORS = {
41
+ "black": 30,
42
+ "red": 31,
43
+ "green": 32,
44
+ "yellow": 33,
45
+ "blue": 34,
46
+ "magenta": 35,
47
+ "cyan": 36,
48
+ "white": 37,
49
+ }
50
+
51
+ BACKGROUND_COLORS = {
52
+ "black": 40,
53
+ "red": 41,
54
+ "green": 42,
55
+ "yellow": 43,
56
+ "blue": 44,
57
+ "magenta": 45,
58
+ "cyan": 46,
59
+ "white": 47,
60
+ }
61
+
62
+ OPTIONS = {"bold": 1, "underscore": 4, "blink": 5, "reverse": 7, "conceal": 8}
63
+
64
+
65
+ def style(fg, bg, options):
66
+ codes = []
67
+
68
+ if fg:
69
+ codes.append(FOREGROUND_COLORS[fg])
70
+
71
+ if bg:
72
+ codes.append(BACKGROUND_COLORS[bg])
73
+
74
+ if options:
75
+ if not isinstance(options, (list, tuple)):
76
+ options = [options]
77
+
78
+ for option in options:
79
+ codes.append(OPTIONS[option])
80
+
81
+ return "\033[{}m".format(";".join(map(str, codes)))
82
+
83
+
84
+ STYLES = {
85
+ "info": style("cyan", None, None),
86
+ "comment": style("yellow", None, None),
87
+ "success": style("green", None, None),
88
+ "error": style("red", None, None),
89
+ "warning": style("yellow", None, None),
90
+ "b": style(None, None, ("bold",)),
91
+ }
92
+
93
+
94
+ def is_decorated():
95
+ if WINDOWS:
96
+ return (
97
+ os.getenv("ANSICON") is not None
98
+ or "ON" == os.getenv("ConEmuANSI")
99
+ or "xterm" == os.getenv("Term")
100
+ )
101
+
102
+ if not hasattr(sys.stdout, "fileno"):
103
+ return False
104
+
105
+ try:
106
+ return os.isatty(sys.stdout.fileno())
107
+ except UnsupportedOperation:
108
+ return False
109
+
110
+
111
+ def is_interactive():
112
+ if not hasattr(sys.stdin, "fileno"):
113
+ return False
114
+
115
+ try:
116
+ return os.isatty(sys.stdin.fileno())
117
+ except UnsupportedOperation:
118
+ return False
119
+
120
+
121
+ def colorize(style, text):
122
+ if not is_decorated():
123
+ return text
124
+
125
+ return "{}{}\033[0m".format(STYLES[style], text)
126
+
127
+
128
+ def string_to_bool(value):
129
+ value = value.lower()
130
+
131
+ return value in {"true", "1", "y", "yes"}
132
+
133
+
134
+ def data_dir(version: Optional[str] = None) -> Path:
135
+ if os.getenv("POETRY_HOME"):
136
+ return Path(os.getenv("POETRY_HOME")).expanduser()
137
+
138
+ if WINDOWS:
139
+ const = "CSIDL_APPDATA"
140
+ path = os.path.normpath(_get_win_folder(const))
141
+ path = os.path.join(path, "pypoetry")
142
+ elif MACOS:
143
+ path = os.path.expanduser("~/Library/Application Support/pypoetry")
144
+ else:
145
+ path = os.getenv("XDG_DATA_HOME", os.path.expanduser("~/.local/share"))
146
+ path = os.path.join(path, "pypoetry")
147
+
148
+ if version:
149
+ path = os.path.join(path, version)
150
+
151
+ return Path(path)
152
+
153
+
154
+ def bin_dir(version: Optional[str] = None) -> Path:
155
+ if os.getenv("POETRY_HOME"):
156
+ return Path(os.getenv("POETRY_HOME"), "bin").expanduser()
157
+
158
+ user_base = site.getuserbase()
159
+
160
+ if WINDOWS:
161
+ bin_dir = os.path.join(user_base, "Scripts")
162
+ else:
163
+ bin_dir = os.path.join(user_base, "bin")
164
+
165
+ return Path(bin_dir)
166
+
167
+
168
+ def _get_win_folder_from_registry(csidl_name):
169
+ import winreg as _winreg
170
+
171
+ shell_folder_name = {
172
+ "CSIDL_APPDATA": "AppData",
173
+ "CSIDL_COMMON_APPDATA": "Common AppData",
174
+ "CSIDL_LOCAL_APPDATA": "Local AppData",
175
+ }[csidl_name]
176
+
177
+ key = _winreg.OpenKey(
178
+ _winreg.HKEY_CURRENT_USER,
179
+ r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders",
180
+ )
181
+ dir, type = _winreg.QueryValueEx(key, shell_folder_name)
182
+
183
+ return dir
184
+
185
+
186
+ def _get_win_folder_with_ctypes(csidl_name):
187
+ import ctypes
188
+
189
+ csidl_const = {
190
+ "CSIDL_APPDATA": 26,
191
+ "CSIDL_COMMON_APPDATA": 35,
192
+ "CSIDL_LOCAL_APPDATA": 28,
193
+ }[csidl_name]
194
+
195
+ buf = ctypes.create_unicode_buffer(1024)
196
+ ctypes.windll.shell32.SHGetFolderPathW(None, csidl_const, None, 0, buf)
197
+
198
+ # Downgrade to short path name if have highbit chars. See
199
+ # <http://bugs.activestate.com/show_bug.cgi?id=85099>.
200
+ has_high_char = False
201
+ for c in buf:
202
+ if ord(c) > 255:
203
+ has_high_char = True
204
+ break
205
+ if has_high_char:
206
+ buf2 = ctypes.create_unicode_buffer(1024)
207
+ if ctypes.windll.kernel32.GetShortPathNameW(buf.value, buf2, 1024):
208
+ buf = buf2
209
+
210
+ return buf.value
211
+
212
+
213
+ if WINDOWS:
214
+ try:
215
+ from ctypes import windll # noqa
216
+
217
+ _get_win_folder = _get_win_folder_with_ctypes
218
+ except ImportError:
219
+ _get_win_folder = _get_win_folder_from_registry
220
+
221
+
222
+ @contextmanager
223
+ def temporary_directory(*args, **kwargs):
224
+ try:
225
+ from tempfile import TemporaryDirectory
226
+ except ImportError:
227
+ name = tempfile.mkdtemp(*args, **kwargs)
228
+
229
+ yield name
230
+
231
+ shutil.rmtree(name)
232
+ else:
233
+ with TemporaryDirectory(*args, **kwargs) as name:
234
+ yield name
235
+
236
+
237
+ PRE_MESSAGE = """# Welcome to {poetry}!
238
+
239
+ This will download and install the latest version of {poetry},
240
+ a dependency and package manager for Python.
241
+
242
+ It will add the `poetry` command to {poetry}'s bin directory, located at:
243
+
244
+ {poetry_home_bin}
245
+
246
+ You can uninstall at any time by executing this script with the --uninstall option,
247
+ and these changes will be reverted.
248
+ """
249
+
250
+ POST_MESSAGE = """{poetry} ({version}) is installed now. Great!
251
+
252
+ You can test that everything is set up by executing:
253
+
254
+ `{test_command}`
255
+ """
256
+
257
+ POST_MESSAGE_NOT_IN_PATH = """{poetry} ({version}) is installed now. Great!
258
+
259
+ To get started you need {poetry}'s bin directory ({poetry_home_bin}) in your `PATH`
260
+ environment variable.
261
+ {configure_message}
262
+ Alternatively, you can call {poetry} explicitly with `{poetry_executable}`.
263
+
264
+ You can test that everything is set up by executing:
265
+
266
+ `{test_command}`
267
+ """
268
+
269
+ POST_MESSAGE_CONFIGURE_UNIX = """
270
+ Add `export PATH="{poetry_home_bin}:$PATH"` to your shell configuration file.
271
+ """
272
+
273
+ POST_MESSAGE_CONFIGURE_FISH = """
274
+ You can execute `set -U fish_user_paths {poetry_home_bin} $fish_user_paths`
275
+ """
276
+
277
+ POST_MESSAGE_CONFIGURE_WINDOWS = """"""
278
+
279
+
280
+ class Cursor:
281
+ def __init__(self) -> None:
282
+ self._output = sys.stdout
283
+
284
+ def move_up(self, lines: int = 1) -> "Cursor":
285
+ self._output.write("\x1b[{}A".format(lines))
286
+
287
+ return self
288
+
289
+ def move_down(self, lines: int = 1) -> "Cursor":
290
+ self._output.write("\x1b[{}B".format(lines))
291
+
292
+ return self
293
+
294
+ def move_right(self, columns: int = 1) -> "Cursor":
295
+ self._output.write("\x1b[{}C".format(columns))
296
+
297
+ return self
298
+
299
+ def move_left(self, columns: int = 1) -> "Cursor":
300
+ self._output.write("\x1b[{}D".format(columns))
301
+
302
+ return self
303
+
304
+ def move_to_column(self, column: int) -> "Cursor":
305
+ self._output.write("\x1b[{}G".format(column))
306
+
307
+ return self
308
+
309
+ def move_to_position(self, column: int, row: int) -> "Cursor":
310
+ self._output.write("\x1b[{};{}H".format(row + 1, column))
311
+
312
+ return self
313
+
314
+ def save_position(self) -> "Cursor":
315
+ self._output.write("\x1b7")
316
+
317
+ return self
318
+
319
+ def restore_position(self) -> "Cursor":
320
+ self._output.write("\x1b8")
321
+
322
+ return self
323
+
324
+ def hide(self) -> "Cursor":
325
+ self._output.write("\x1b[?25l")
326
+
327
+ return self
328
+
329
+ def show(self) -> "Cursor":
330
+ self._output.write("\x1b[?25h\x1b[?0c")
331
+
332
+ return self
333
+
334
+ def clear_line(self) -> "Cursor":
335
+ """
336
+ Clears all the output from the current line.
337
+ """
338
+ self._output.write("\x1b[2K")
339
+
340
+ return self
341
+
342
+ def clear_line_after(self) -> "Cursor":
343
+ """
344
+ Clears all the output from the current line after the current position.
345
+ """
346
+ self._output.write("\x1b[K")
347
+
348
+ return self
349
+
350
+ def clear_output(self) -> "Cursor":
351
+ """
352
+ Clears all the output from the cursors' current position
353
+ to the end of the screen.
354
+ """
355
+ self._output.write("\x1b[0J")
356
+
357
+ return self
358
+
359
+ def clear_screen(self) -> "Cursor":
360
+ """
361
+ Clears the entire screen.
362
+ """
363
+ self._output.write("\x1b[2J")
364
+
365
+ return self
366
+
367
+
368
+ class Installer:
369
+ METADATA_URL = "https://pypi.org/pypi/poetry/json"
370
+ VERSION_REGEX = re.compile(
371
+ r"v?(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:\.(\d+))?"
372
+ "("
373
+ "[._-]?"
374
+ r"(?:(stable|beta|b|rc|RC|alpha|a|patch|pl|p)((?:[.-]?\d+)*)?)?"
375
+ "([.-]?dev)?"
376
+ ")?"
377
+ r"(?:\+[^\s]+)?"
378
+ )
379
+
380
+ def __init__(
381
+ self,
382
+ version: Optional[str] = None,
383
+ preview: bool = False,
384
+ force: bool = False,
385
+ accept_all: bool = False,
386
+ git: Optional[str] = None,
387
+ path: Optional[str] = None,
388
+ ) -> None:
389
+ self._version = version
390
+ self._preview = preview
391
+ self._force = force
392
+ self._accept_all = accept_all
393
+ self._git = git
394
+ self._path = path
395
+ self._data_dir = data_dir()
396
+ self._bin_dir = bin_dir()
397
+ self._cursor = Cursor()
398
+
399
+ def allows_prereleases(self) -> bool:
400
+ return self._preview
401
+
402
+ def run(self) -> int:
403
+ if self._git:
404
+ version = self._git
405
+ elif self._path:
406
+ version = self._path
407
+ else:
408
+ version, current_version = self.get_version()
409
+
410
+ if version is None:
411
+ return 0
412
+
413
+ self.display_pre_message()
414
+ self.ensure_directories()
415
+
416
+ def _is_self_upgrade_supported(x):
417
+ mx = self.VERSION_REGEX.match(x)
418
+
419
+ if mx is None:
420
+ # the version is not semver, perhaps scm or file, we assume upgrade is supported
421
+ return True
422
+
423
+ vx = tuple(int(p) for p in mx.groups()[:3]) + (mx.group(5),)
424
+ return vx >= (1, 1, 7)
425
+
426
+ if version and not _is_self_upgrade_supported(version):
427
+ self._write(
428
+ colorize(
429
+ "warning",
430
+ f"You are installing {version}. When using the current installer, this version does not support "
431
+ f"updating using the 'self update' command. Please use 1.1.7 or later.",
432
+ )
433
+ )
434
+ if not self._accept_all:
435
+ continue_install = input("Do you want to continue? ([y]/n) ") or "y"
436
+ if continue_install.lower() in {"n", "no"}:
437
+ return 0
438
+
439
+ try:
440
+ self.install(version)
441
+ except subprocess.CalledProcessError as e:
442
+ print(colorize("error", "An error has occured: {}".format(str(e))))
443
+ print(e.output.decode())
444
+
445
+ return e.returncode
446
+
447
+ self._write("")
448
+ self.display_post_message(version)
449
+
450
+ return 0
451
+
452
+ def install(self, version, upgrade=False):
453
+ """
454
+ Installs Poetry in $POETRY_HOME.
455
+ """
456
+ self._write(
457
+ "Installing {} ({})".format(
458
+ colorize("info", "Poetry"), colorize("info", version)
459
+ )
460
+ )
461
+
462
+ env_path = self.make_env(version)
463
+ self.install_poetry(version, env_path)
464
+ self.make_bin(version)
465
+
466
+ self._overwrite(
467
+ "Installing {} ({}): {}".format(
468
+ colorize("info", "Poetry"),
469
+ colorize("b", version),
470
+ colorize("success", "Done"),
471
+ )
472
+ )
473
+
474
+ self._data_dir.joinpath("VERSION").write_text(version)
475
+
476
+ return 0
477
+
478
+ def uninstall(self) -> int:
479
+ if not self._data_dir.exists():
480
+ self._write(
481
+ "{} is not currently installed.".format(colorize("info", "Poetry"))
482
+ )
483
+
484
+ return 1
485
+
486
+ version = None
487
+ if self._data_dir.joinpath("VERSION").exists():
488
+ version = self._data_dir.joinpath("VERSION").read_text().strip()
489
+
490
+ if version:
491
+ self._write(
492
+ "Removing {} ({})".format(
493
+ colorize("info", "Poetry"), colorize("b", version)
494
+ )
495
+ )
496
+ else:
497
+ self._write("Removing {}".format(colorize("info", "Poetry")))
498
+
499
+ shutil.rmtree(str(self._data_dir))
500
+ for script in ["poetry", "poetry.bat"]:
501
+ if self._bin_dir.joinpath(script).exists():
502
+ self._bin_dir.joinpath(script).unlink()
503
+
504
+ return 0
505
+
506
+ def make_env(self, version: str) -> Path:
507
+ self._overwrite(
508
+ "Installing {} ({}): {}".format(
509
+ colorize("info", "Poetry"),
510
+ colorize("b", version),
511
+ colorize("comment", "Creating environment"),
512
+ )
513
+ )
514
+
515
+ env_path = self._data_dir.joinpath("venv")
516
+
517
+ with temporary_directory() as tmp_dir:
518
+ subprocess.call(
519
+ [sys.executable, "-m", "pip", "install", "virtualenv", "-t", tmp_dir],
520
+ stdout=subprocess.PIPE,
521
+ stderr=subprocess.STDOUT,
522
+ )
523
+
524
+ sys.path.insert(0, tmp_dir)
525
+
526
+ import virtualenv
527
+
528
+ virtualenv.cli_run([str(env_path), "--clear"])
529
+
530
+ return env_path
531
+
532
+ def make_bin(self, version: str) -> None:
533
+ self._overwrite(
534
+ "Installing {} ({}): {}".format(
535
+ colorize("info", "Poetry"),
536
+ colorize("b", version),
537
+ colorize("comment", "Creating script"),
538
+ )
539
+ )
540
+
541
+ self._bin_dir.mkdir(parents=True, exist_ok=True)
542
+
543
+ script = "poetry"
544
+ target_script = "venv/bin/poetry"
545
+ if WINDOWS:
546
+ script = "poetry.exe"
547
+ target_script = "venv/Scripts/poetry.exe"
548
+
549
+ if self._bin_dir.joinpath(script).exists():
550
+ self._bin_dir.joinpath(script).unlink()
551
+
552
+ try:
553
+ self._bin_dir.joinpath(script).symlink_to(
554
+ self._data_dir.joinpath(target_script)
555
+ )
556
+ except OSError:
557
+ # This can happen if the user
558
+ # does not have the correct permission on Windows
559
+ shutil.copy(
560
+ self._data_dir.joinpath(target_script), self._bin_dir.joinpath(script)
561
+ )
562
+
563
+ def install_poetry(self, version: str, env_path: Path) -> None:
564
+ self._overwrite(
565
+ "Installing {} ({}): {}".format(
566
+ colorize("info", "Poetry"),
567
+ colorize("b", version),
568
+ colorize("comment", "Installing Poetry"),
569
+ )
570
+ )
571
+
572
+ if WINDOWS:
573
+ python = env_path.joinpath("Scripts/python.exe")
574
+ else:
575
+ python = env_path.joinpath("bin/python")
576
+
577
+ if self._git:
578
+ specification = "git+" + version
579
+ elif self._path:
580
+ specification = version
581
+ else:
582
+ specification = f"poetry=={version}"
583
+
584
+ subprocess.run(
585
+ [str(python), "-m", "pip", "install", specification],
586
+ stdout=subprocess.PIPE,
587
+ stderr=subprocess.STDOUT,
588
+ )
589
+
590
+ def display_pre_message(self) -> None:
591
+ kwargs = {
592
+ "poetry": colorize("info", "Poetry"),
593
+ "poetry_home_bin": colorize("comment", self._bin_dir),
594
+ }
595
+ self._write(PRE_MESSAGE.format(**kwargs))
596
+
597
+ def display_post_message(self, version: str) -> None:
598
+ if WINDOWS:
599
+ return self.display_post_message_windows(version)
600
+
601
+ if SHELL == "fish":
602
+ return self.display_post_message_fish(version)
603
+
604
+ return self.display_post_message_unix(version)
605
+
606
+ def display_post_message_windows(self, version: str) -> None:
607
+ path = self.get_windows_path_var()
608
+
609
+ message = POST_MESSAGE_NOT_IN_PATH
610
+ if path and str(self._bin_dir) in path:
611
+ message = POST_MESSAGE
612
+
613
+ self._write(
614
+ message.format(
615
+ poetry=colorize("info", "Poetry"),
616
+ version=colorize("b", version),
617
+ poetry_home_bin=colorize("comment", self._bin_dir),
618
+ poetry_executable=colorize("b", self._bin_dir.joinpath("poetry")),
619
+ configure_message=POST_MESSAGE_CONFIGURE_WINDOWS.format(
620
+ poetry_home_bin=colorize("comment", self._bin_dir)
621
+ ),
622
+ test_command=colorize("b", "poetry --version"),
623
+ )
624
+ )
625
+
626
+ def get_windows_path_var(self) -> Optional[str]:
627
+ import winreg
628
+
629
+ with winreg.ConnectRegistry(None, winreg.HKEY_CURRENT_USER) as root:
630
+ with winreg.OpenKey(root, "Environment", 0, winreg.KEY_ALL_ACCESS) as key:
631
+ path, _ = winreg.QueryValueEx(key, "PATH")
632
+
633
+ return path
634
+
635
+ def display_post_message_fish(self, version: str) -> None:
636
+ fish_user_paths = subprocess.check_output(
637
+ ["fish", "-c", "echo $fish_user_paths"]
638
+ ).decode("utf-8")
639
+
640
+ message = POST_MESSAGE_NOT_IN_PATH
641
+ if fish_user_paths and str(self._bin_dir) in fish_user_paths:
642
+ message = POST_MESSAGE
643
+
644
+ self._write(
645
+ message.format(
646
+ poetry=colorize("info", "Poetry"),
647
+ version=colorize("b", version),
648
+ poetry_home_bin=colorize("comment", self._bin_dir),
649
+ poetry_executable=colorize("b", self._bin_dir.joinpath("poetry")),
650
+ configure_message=POST_MESSAGE_CONFIGURE_FISH.format(
651
+ poetry_home_bin=colorize("comment", self._bin_dir)
652
+ ),
653
+ test_command=colorize("b", "poetry --version"),
654
+ )
655
+ )
656
+
657
+ def display_post_message_unix(self, version: str) -> None:
658
+ paths = os.getenv("PATH", "").split(":")
659
+
660
+ message = POST_MESSAGE_NOT_IN_PATH
661
+ if paths and str(self._bin_dir) in paths:
662
+ message = POST_MESSAGE
663
+
664
+ self._write(
665
+ message.format(
666
+ poetry=colorize("info", "Poetry"),
667
+ version=colorize("b", version),
668
+ poetry_home_bin=colorize("comment", self._bin_dir),
669
+ poetry_executable=colorize("b", self._bin_dir.joinpath("poetry")),
670
+ configure_message=POST_MESSAGE_CONFIGURE_UNIX.format(
671
+ poetry_home_bin=colorize("comment", self._bin_dir)
672
+ ),
673
+ test_command=colorize("b", "poetry --version"),
674
+ )
675
+ )
676
+
677
+ def ensure_directories(self) -> None:
678
+ self._data_dir.mkdir(parents=True, exist_ok=True)
679
+ self._bin_dir.mkdir(parents=True, exist_ok=True)
680
+
681
+ def get_version(self):
682
+ current_version = None
683
+ if self._data_dir.joinpath("VERSION").exists():
684
+ current_version = self._data_dir.joinpath("VERSION").read_text().strip()
685
+
686
+ self._write(colorize("info", "Retrieving Poetry metadata"))
687
+
688
+ metadata = json.loads(self._get(self.METADATA_URL).decode())
689
+
690
+ def _compare_versions(x, y):
691
+ mx = self.VERSION_REGEX.match(x)
692
+ my = self.VERSION_REGEX.match(y)
693
+
694
+ vx = tuple(int(p) for p in mx.groups()[:3]) + (mx.group(5),)
695
+ vy = tuple(int(p) for p in my.groups()[:3]) + (my.group(5),)
696
+
697
+ if vx < vy:
698
+ return -1
699
+ elif vx > vy:
700
+ return 1
701
+
702
+ return 0
703
+
704
+ self._write("")
705
+ releases = sorted(
706
+ metadata["releases"].keys(), key=cmp_to_key(_compare_versions)
707
+ )
708
+
709
+ if self._version and self._version not in releases:
710
+ self._write(
711
+ colorize("error", "Version {} does not exist.".format(self._version))
712
+ )
713
+
714
+ return None, None
715
+
716
+ version = self._version
717
+ if not version:
718
+ for release in reversed(releases):
719
+ m = self.VERSION_REGEX.match(release)
720
+ if m.group(5) and not self.allows_prereleases():
721
+ continue
722
+
723
+ version = release
724
+
725
+ break
726
+
727
+ if current_version == version and not self._force:
728
+ self._write(
729
+ "The latest version ({}) is already installed.".format(
730
+ colorize("b", version)
731
+ )
732
+ )
733
+
734
+ return None, current_version
735
+
736
+ return version, current_version
737
+
738
+ def _write(self, line) -> None:
739
+ sys.stdout.write(line + "\n")
740
+
741
+ def _overwrite(self, line) -> None:
742
+ if not is_decorated():
743
+ return self._write(line)
744
+
745
+ self._cursor.move_up()
746
+ self._cursor.clear_line()
747
+ self._write(line)
748
+
749
+ def _get(self, url):
750
+ request = Request(url, headers={"User-Agent": "Python Poetry"})
751
+
752
+ with closing(urlopen(request)) as r:
753
+ return r.read()
754
+
755
+
756
+ def main():
757
+ parser = argparse.ArgumentParser(
758
+ description="Installs the latest (or given) version of poetry"
759
+ )
760
+ parser.add_argument(
761
+ "-p",
762
+ "--preview",
763
+ help="install preview version",
764
+ dest="preview",
765
+ action="store_true",
766
+ default=False,
767
+ )
768
+ parser.add_argument("--version", help="install named version", dest="version")
769
+ parser.add_argument(
770
+ "-f",
771
+ "--force",
772
+ help="install on top of existing version",
773
+ dest="force",
774
+ action="store_true",
775
+ default=False,
776
+ )
777
+ parser.add_argument(
778
+ "-y",
779
+ "--yes",
780
+ help="accept all prompts",
781
+ dest="accept_all",
782
+ action="store_true",
783
+ default=False,
784
+ )
785
+ parser.add_argument(
786
+ "--uninstall",
787
+ help="uninstall poetry",
788
+ dest="uninstall",
789
+ action="store_true",
790
+ default=False,
791
+ )
792
+ parser.add_argument(
793
+ "--path",
794
+ dest="path",
795
+ action="store",
796
+ help=(
797
+ "Install from a given path (file or directory) instead of "
798
+ "fetching the latest version of Poetry available online."
799
+ ),
800
+ )
801
+ parser.add_argument(
802
+ "--git",
803
+ dest="git",
804
+ action="store",
805
+ help=(
806
+ "Install from a git repository instead of fetching the latest version "
807
+ "of Poetry available online."
808
+ ),
809
+ )
810
+
811
+ args = parser.parse_args()
812
+
813
+ installer = Installer(
814
+ version=args.version or os.getenv("POETRY_VERSION"),
815
+ preview=args.preview or string_to_bool(os.getenv("POETRY_PREVIEW", "0")),
816
+ force=args.force,
817
+ accept_all=args.accept_all
818
+ or string_to_bool(os.getenv("POETRY_ACCEPT", "0"))
819
+ or not is_interactive(),
820
+ path=args.path,
821
+ git=args.git,
822
+ )
823
+
824
+ if args.uninstall or string_to_bool(os.getenv("POETRY_UNINSTALL", "0")):
825
+ return installer.uninstall()
826
+
827
+ return installer.run()
828
+
829
+
830
+ if __name__ == "__main__":
831
+ sys.exit(main())