pip-ui-tkinter 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.
- pip_ui_tkinter-0.1.0/.gitignore +70 -0
- pip_ui_tkinter-0.1.0/LICENSE +21 -0
- pip_ui_tkinter-0.1.0/PKG-INFO +77 -0
- pip_ui_tkinter-0.1.0/README.md +51 -0
- pip_ui_tkinter-0.1.0/pip_ui/__about__.py +25 -0
- pip_ui_tkinter-0.1.0/pip_ui/__init__.py +5 -0
- pip_ui_tkinter-0.1.0/pip_ui/__main__.py +6 -0
- pip_ui_tkinter-0.1.0/pip_ui/cli.py +117 -0
- pip_ui_tkinter-0.1.0/pip_ui/command_specs.py +892 -0
- pip_ui_tkinter-0.1.0/pip_ui/config_inspector.py +361 -0
- pip_ui_tkinter-0.1.0/pip_ui/encoding.py +41 -0
- pip_ui_tkinter-0.1.0/pip_ui/environment.py +131 -0
- pip_ui_tkinter-0.1.0/pip_ui/forms.py +244 -0
- pip_ui_tkinter-0.1.0/pip_ui/history.py +60 -0
- pip_ui_tkinter-0.1.0/pip_ui/models.py +79 -0
- pip_ui_tkinter-0.1.0/pip_ui/presets.py +47 -0
- pip_ui_tkinter-0.1.0/pip_ui/py.typed +0 -0
- pip_ui_tkinter-0.1.0/pip_ui/requirements_detect.py +103 -0
- pip_ui_tkinter-0.1.0/pip_ui/runner.py +123 -0
- pip_ui_tkinter-0.1.0/pip_ui/safety.py +159 -0
- pip_ui_tkinter-0.1.0/pip_ui/self_update.py +80 -0
- pip_ui_tkinter-0.1.0/pip_ui/settings.py +54 -0
- pip_ui_tkinter-0.1.0/pip_ui/ui/__init__.py +1 -0
- pip_ui_tkinter-0.1.0/pip_ui/ui/cert_tester.py +247 -0
- pip_ui_tkinter-0.1.0/pip_ui/ui/command_form.py +425 -0
- pip_ui_tkinter-0.1.0/pip_ui/ui/command_tree.py +82 -0
- pip_ui_tkinter-0.1.0/pip_ui/ui/config_view.py +216 -0
- pip_ui_tkinter-0.1.0/pip_ui/ui/dialogs.py +48 -0
- pip_ui_tkinter-0.1.0/pip_ui/ui/global_options_dialog.py +166 -0
- pip_ui_tkinter-0.1.0/pip_ui/ui/help_panel.py +272 -0
- pip_ui_tkinter-0.1.0/pip_ui/ui/index_selector.py +190 -0
- pip_ui_tkinter-0.1.0/pip_ui/ui/interpreter_picker.py +74 -0
- pip_ui_tkinter-0.1.0/pip_ui/ui/main_window.py +807 -0
- pip_ui_tkinter-0.1.0/pip_ui/ui/output_panel.py +196 -0
- pip_ui_tkinter-0.1.0/pip_ui/ui/proxy_dialog.py +220 -0
- pip_ui_tkinter-0.1.0/pip_ui/ui/proxy_utils.py +36 -0
- pip_ui_tkinter-0.1.0/pip_ui/ui/requirements_picker.py +80 -0
- pip_ui_tkinter-0.1.0/pyproject.toml +203 -0
- pip_ui_tkinter-0.1.0/tests/__init__.py +0 -0
- pip_ui_tkinter-0.1.0/tests/test_cert_tester.py +92 -0
- pip_ui_tkinter-0.1.0/tests/test_cli.py +322 -0
- pip_ui_tkinter-0.1.0/tests/test_command_specs.py +51 -0
- pip_ui_tkinter-0.1.0/tests/test_config_files.py +125 -0
- pip_ui_tkinter-0.1.0/tests/test_config_inspector.py +116 -0
- pip_ui_tkinter-0.1.0/tests/test_dialogs.py +118 -0
- pip_ui_tkinter-0.1.0/tests/test_encoding.py +64 -0
- pip_ui_tkinter-0.1.0/tests/test_environment.py +94 -0
- pip_ui_tkinter-0.1.0/tests/test_forms.py +268 -0
- pip_ui_tkinter-0.1.0/tests/test_global_options_summary.py +36 -0
- pip_ui_tkinter-0.1.0/tests/test_history.py +98 -0
- pip_ui_tkinter-0.1.0/tests/test_module_main.py +27 -0
- pip_ui_tkinter-0.1.0/tests/test_presets.py +59 -0
- pip_ui_tkinter-0.1.0/tests/test_proxy_utils.py +94 -0
- pip_ui_tkinter-0.1.0/tests/test_requirements_detect.py +50 -0
- pip_ui_tkinter-0.1.0/tests/test_runner.py +105 -0
- pip_ui_tkinter-0.1.0/tests/test_runner_cancel.py +42 -0
- pip_ui_tkinter-0.1.0/tests/test_safety.py +103 -0
- pip_ui_tkinter-0.1.0/tests/test_safety_warnings.py +98 -0
- pip_ui_tkinter-0.1.0/tests/test_self_update.py +124 -0
- pip_ui_tkinter-0.1.0/tests/test_settings.py +76 -0
- pip_ui_tkinter-0.1.0/tests/test_ui_read_only.py +216 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.pyo
|
|
5
|
+
*.pyd
|
|
6
|
+
.Python
|
|
7
|
+
*.egg
|
|
8
|
+
*.egg-info/
|
|
9
|
+
dist/
|
|
10
|
+
build/
|
|
11
|
+
eggs/
|
|
12
|
+
parts/
|
|
13
|
+
var/
|
|
14
|
+
sdist/
|
|
15
|
+
wheels/
|
|
16
|
+
pip-wheel-metadata/
|
|
17
|
+
share/python-wheels/
|
|
18
|
+
*.egg-info/
|
|
19
|
+
.installed.cfg
|
|
20
|
+
MANIFEST
|
|
21
|
+
|
|
22
|
+
# Virtual environments
|
|
23
|
+
.env
|
|
24
|
+
.venv
|
|
25
|
+
env/
|
|
26
|
+
venv/
|
|
27
|
+
ENV/
|
|
28
|
+
env.bak/
|
|
29
|
+
venv.bak/
|
|
30
|
+
|
|
31
|
+
# Testing
|
|
32
|
+
.tox/
|
|
33
|
+
.nox/
|
|
34
|
+
.coverage
|
|
35
|
+
.coverage.*
|
|
36
|
+
.cache
|
|
37
|
+
nosetests.xml
|
|
38
|
+
coverage.xml
|
|
39
|
+
*.cover
|
|
40
|
+
*.py,cover
|
|
41
|
+
.hypothesis/
|
|
42
|
+
.pytest_cache/
|
|
43
|
+
htmlcov/
|
|
44
|
+
junit.xml
|
|
45
|
+
|
|
46
|
+
# Type checking
|
|
47
|
+
.mypy_cache/
|
|
48
|
+
.dmypy.json
|
|
49
|
+
dmypy.json
|
|
50
|
+
.pyre/
|
|
51
|
+
|
|
52
|
+
# Linting
|
|
53
|
+
.ruff_cache/
|
|
54
|
+
|
|
55
|
+
# Docs
|
|
56
|
+
site/
|
|
57
|
+
docs/_build/
|
|
58
|
+
|
|
59
|
+
# Build history
|
|
60
|
+
.build_logs/
|
|
61
|
+
.build_history/
|
|
62
|
+
.uv/
|
|
63
|
+
|
|
64
|
+
# Editors
|
|
65
|
+
.idea/
|
|
66
|
+
.vscode/
|
|
67
|
+
*.swp
|
|
68
|
+
*.swo
|
|
69
|
+
*~
|
|
70
|
+
.DS_Store
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Matthew Martin
|
|
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,77 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pip-ui-tkinter
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A pip UI tool
|
|
5
|
+
Project-URL: Repository, https://github.com/matthewdeanmartin/pip_ui
|
|
6
|
+
Project-URL: Documentation, https://pip_ui.readthedocs.io/en/latest/
|
|
7
|
+
Project-URL: Changelog, https://github.com/matthewdeanmartin/pip_ui/blob/main/CHANGELOG.md
|
|
8
|
+
Project-URL: homepage, https://github.com/matthewdeanmartin/pip_ui
|
|
9
|
+
Project-URL: issues, https://github.com/matthewdeanmartin/pip_ui/issues/
|
|
10
|
+
Author-email: Matthew Martin <matthewdeanmartin@gmail.com>
|
|
11
|
+
License-Expression: MIT
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: pip,pip-ui-tkinter
|
|
14
|
+
Classifier: Development Status :: 3 - Alpha
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
24
|
+
Requires-Python: >=3.10
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
# pip-ui-tkinter
|
|
28
|
+
|
|
29
|
+
<!-- TODO (generated from template — delete this block once done)
|
|
30
|
+
- [ ] Replace this description with a real one-paragraph summary
|
|
31
|
+
- [ ] Update pyproject.toml [project] keywords with real search terms
|
|
32
|
+
- [ ] Update pyproject.toml classifiers Development Status (3-Alpha → 4-Beta → 5-Production)
|
|
33
|
+
- [ ] Update pyproject.toml description (shown on PyPI)
|
|
34
|
+
- [ ] Add a PyPI badge: https://badge.fury.io/py/pip-ui-tkinter
|
|
35
|
+
- [ ] Add a CI badge from your GitHub Actions build.yml
|
|
36
|
+
- [ ] Fill in docs/overview/README.md with a real project overview
|
|
37
|
+
- [ ] Add real subcommands to pip_ui/cli.py and update scripts/basic_checks.sh
|
|
38
|
+
- [ ] Register project on Read the Docs and point it at mkdocs.yml
|
|
39
|
+
- [ ] Set up PyPI OIDC trusted publishing (no token needed) for publish_to_pypi.yml
|
|
40
|
+
- [ ] Run `make pre-commit-install` to install git hooks
|
|
41
|
+
- [ ] Run `make gha-upgrade` after first push to pin GHA action SHAs
|
|
42
|
+
- [ ] Add project-specific words to private_dictionary.txt
|
|
43
|
+
- [ ] Update SECURITY.md scope section with what's actually in scope for this project
|
|
44
|
+
- [ ] Update CHANGELOG.md 0.1.0 entry with real release notes
|
|
45
|
+
-->
|
|
46
|
+
|
|
47
|
+
A Tkinter-based UI for pip
|
|
48
|
+
|
|
49
|
+
## Installation
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pipx install pip-ui-tkinter
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Or with pip:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
pip install pip-ui-tkinter
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Usage
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
pip-ui-tkinter --help
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Contributing
|
|
68
|
+
|
|
69
|
+
See [CONTRIBUTING.md](docs/extending/CONTRIBUTING.md).
|
|
70
|
+
|
|
71
|
+
## License
|
|
72
|
+
|
|
73
|
+
MIT — see [LICENSE](LICENSE).
|
|
74
|
+
|
|
75
|
+
## Changelog
|
|
76
|
+
|
|
77
|
+
See [CHANGELOG.md](CHANGELOG.md).
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# pip-ui-tkinter
|
|
2
|
+
|
|
3
|
+
<!-- TODO (generated from template — delete this block once done)
|
|
4
|
+
- [ ] Replace this description with a real one-paragraph summary
|
|
5
|
+
- [ ] Update pyproject.toml [project] keywords with real search terms
|
|
6
|
+
- [ ] Update pyproject.toml classifiers Development Status (3-Alpha → 4-Beta → 5-Production)
|
|
7
|
+
- [ ] Update pyproject.toml description (shown on PyPI)
|
|
8
|
+
- [ ] Add a PyPI badge: https://badge.fury.io/py/pip-ui-tkinter
|
|
9
|
+
- [ ] Add a CI badge from your GitHub Actions build.yml
|
|
10
|
+
- [ ] Fill in docs/overview/README.md with a real project overview
|
|
11
|
+
- [ ] Add real subcommands to pip_ui/cli.py and update scripts/basic_checks.sh
|
|
12
|
+
- [ ] Register project on Read the Docs and point it at mkdocs.yml
|
|
13
|
+
- [ ] Set up PyPI OIDC trusted publishing (no token needed) for publish_to_pypi.yml
|
|
14
|
+
- [ ] Run `make pre-commit-install` to install git hooks
|
|
15
|
+
- [ ] Run `make gha-upgrade` after first push to pin GHA action SHAs
|
|
16
|
+
- [ ] Add project-specific words to private_dictionary.txt
|
|
17
|
+
- [ ] Update SECURITY.md scope section with what's actually in scope for this project
|
|
18
|
+
- [ ] Update CHANGELOG.md 0.1.0 entry with real release notes
|
|
19
|
+
-->
|
|
20
|
+
|
|
21
|
+
A Tkinter-based UI for pip
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pipx install pip-ui-tkinter
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Or with pip:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install pip-ui-tkinter
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Usage
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip-ui-tkinter --help
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Contributing
|
|
42
|
+
|
|
43
|
+
See [CONTRIBUTING.md](docs/extending/CONTRIBUTING.md).
|
|
44
|
+
|
|
45
|
+
## License
|
|
46
|
+
|
|
47
|
+
MIT — see [LICENSE](LICENSE).
|
|
48
|
+
|
|
49
|
+
## Changelog
|
|
50
|
+
|
|
51
|
+
See [CHANGELOG.md](CHANGELOG.md).
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""Metadata for pip-ui-tkinter."""
|
|
2
|
+
|
|
3
|
+
__all__ = [
|
|
4
|
+
"__credits__",
|
|
5
|
+
"__dependencies__",
|
|
6
|
+
"__description__",
|
|
7
|
+
"__keywords__",
|
|
8
|
+
"__license__",
|
|
9
|
+
"__readme__",
|
|
10
|
+
"__requires_python__",
|
|
11
|
+
"__status__",
|
|
12
|
+
"__title__",
|
|
13
|
+
"__version__",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
__title__ = "pip-ui-tkinter"
|
|
17
|
+
__version__ = "0.1.0"
|
|
18
|
+
__description__ = "A pip UI tool"
|
|
19
|
+
__readme__ = "README.md"
|
|
20
|
+
__credits__ = [{"name": "Matthew Martin", "email": "matthewdeanmartin@gmail.com"}]
|
|
21
|
+
__keywords__ = ["pip-ui-tkinter", "pip"]
|
|
22
|
+
__license__ = "MIT"
|
|
23
|
+
__requires_python__ = ">=3.10"
|
|
24
|
+
__status__ = "3 - Alpha"
|
|
25
|
+
__dependencies__: list[str] = []
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""Command-line entry point for pip_ui."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import importlib.util
|
|
7
|
+
import sys
|
|
8
|
+
|
|
9
|
+
from pip_ui.__about__ import __version__
|
|
10
|
+
from pip_ui.encoding import configure_utf8_stdio
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def main() -> None:
|
|
14
|
+
"""Parse CLI arguments and launch the GUI or diagnostics mode."""
|
|
15
|
+
configure_utf8_stdio()
|
|
16
|
+
parser = argparse.ArgumentParser(
|
|
17
|
+
prog="pip-ui",
|
|
18
|
+
description="A Tkinter GUI for pip. Runs commands via: python -m pip",
|
|
19
|
+
)
|
|
20
|
+
parser.add_argument(
|
|
21
|
+
"--version",
|
|
22
|
+
action="version",
|
|
23
|
+
version=f"%(prog)s {__version__}",
|
|
24
|
+
)
|
|
25
|
+
parser.add_argument(
|
|
26
|
+
"--interpreter",
|
|
27
|
+
metavar="PATH",
|
|
28
|
+
help="Path to the Python interpreter to use.",
|
|
29
|
+
default=None,
|
|
30
|
+
)
|
|
31
|
+
parser.add_argument(
|
|
32
|
+
"--working-directory",
|
|
33
|
+
metavar="PATH",
|
|
34
|
+
help="Working directory for pip commands.",
|
|
35
|
+
default=None,
|
|
36
|
+
)
|
|
37
|
+
parser.add_argument(
|
|
38
|
+
"--diagnostics",
|
|
39
|
+
action="store_true",
|
|
40
|
+
help="Print a diagnostics report and exit without launching the GUI.",
|
|
41
|
+
)
|
|
42
|
+
parser.add_argument(
|
|
43
|
+
"--no-history",
|
|
44
|
+
action="store_true",
|
|
45
|
+
help="Do not record command history.",
|
|
46
|
+
)
|
|
47
|
+
parser.add_argument(
|
|
48
|
+
"--safe-mode",
|
|
49
|
+
action="store_true",
|
|
50
|
+
help="Prompt for confirmation before any potentially destructive command.",
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
args = parser.parse_args()
|
|
54
|
+
|
|
55
|
+
if args.diagnostics:
|
|
56
|
+
run_diagnostics(args.interpreter)
|
|
57
|
+
return
|
|
58
|
+
|
|
59
|
+
if importlib.util.find_spec("tkinter") is None:
|
|
60
|
+
print(
|
|
61
|
+
"Error: Tkinter is not available in this Python installation.\n"
|
|
62
|
+
"On Debian/Ubuntu: sudo apt-get install python3-tk\n"
|
|
63
|
+
"On Fedora: sudo dnf install python3-tkinter\n"
|
|
64
|
+
"On macOS: install Python from python.org (includes Tk)\n"
|
|
65
|
+
"On Windows: re-run the Python installer and ensure 'tcl/tk' is checked.",
|
|
66
|
+
file=sys.stderr,
|
|
67
|
+
)
|
|
68
|
+
sys.exit(1)
|
|
69
|
+
|
|
70
|
+
from pip_ui.ui.main_window import MainWindow # pylint: disable=import-outside-toplevel
|
|
71
|
+
|
|
72
|
+
app = MainWindow(
|
|
73
|
+
no_history=args.no_history,
|
|
74
|
+
safe_mode=args.safe_mode,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
if args.interpreter:
|
|
78
|
+
app.after(200, lambda: app.interpreter_picker.set_from_path(args.interpreter))
|
|
79
|
+
|
|
80
|
+
if args.working_directory:
|
|
81
|
+
app.workdir_var.set(args.working_directory)
|
|
82
|
+
app.status_workdir_var.set(args.working_directory)
|
|
83
|
+
|
|
84
|
+
try:
|
|
85
|
+
app.mainloop()
|
|
86
|
+
except KeyboardInterrupt:
|
|
87
|
+
app.on_close()
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def run_diagnostics(interpreter_path: str | None) -> None:
|
|
91
|
+
"""Print a diagnostics report for the selected or default interpreter."""
|
|
92
|
+
from pip_ui.environment import InterpreterDiscovery # pylint: disable=import-outside-toplevel
|
|
93
|
+
|
|
94
|
+
discovery = InterpreterDiscovery()
|
|
95
|
+
interpreters = discovery.discover()
|
|
96
|
+
|
|
97
|
+
if interpreter_path:
|
|
98
|
+
target = discovery.validate(interpreter_path)
|
|
99
|
+
if target is None:
|
|
100
|
+
print(f"Warning: Could not validate interpreter at: {interpreter_path}", file=sys.stderr)
|
|
101
|
+
target = interpreters[0] if interpreters else None
|
|
102
|
+
else:
|
|
103
|
+
target = interpreters[0] if interpreters else None
|
|
104
|
+
|
|
105
|
+
if target is None:
|
|
106
|
+
print("No Python interpreters found.", file=sys.stderr)
|
|
107
|
+
sys.exit(1)
|
|
108
|
+
|
|
109
|
+
from pip_ui.config_inspector import ConfigInspector # pylint: disable=import-outside-toplevel
|
|
110
|
+
|
|
111
|
+
inspector = ConfigInspector(target.path)
|
|
112
|
+
report = inspector.build_diagnostics_report("markdown")
|
|
113
|
+
print(report)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
if __name__ == "__main__":
|
|
117
|
+
main()
|