1from __future__ import absolute_import, unicode_literals
2
3import logging
4from functools import partial
5
6from ..app_data import make_app_data
7from ..config.cli.parser import VirtualEnvConfigParser
8from ..report import LEVELS, setup_report
9from ..run.session import Session
10from ..seed.wheels.periodic_update import manual_upgrade
11from ..version import __version__
12from .plugin.activators import ActivationSelector
13from .plugin.creators import CreatorSelector
14from .plugin.discovery import get_discover
15from .plugin.seeders import SeederSelector
16
17
18def cli_run(args, options=None, setup_logging=True):
19    """
20    Create a virtual environment given some command line interface arguments.
21
22    :param args: the command line arguments
23    :param options: passing in a ``VirtualEnvOptions`` object allows return of the parsed options
24    :param setup_logging: ``True`` if setup logging handlers, ``False`` to use handlers already registered
25    :return: the session object of the creation (its structure for now is experimental and might change on short notice)
26    """
27    of_session = session_via_cli(args, options, setup_logging)
28    with of_session:
29        of_session.run()
30    return of_session
31
32
33def session_via_cli(args, options=None, setup_logging=True):
34    """
35    Create a virtualenv session (same as cli_run, but this does not perform the creation). Use this if you just want to
36    query what the virtual environment would look like, but not actually create it.
37
38    :param args: the command line arguments
39    :param options: passing in a ``VirtualEnvOptions`` object allows return of the parsed options
40    :param setup_logging: ``True`` if setup logging handlers, ``False`` to use handlers already registered
41    :return: the session object of the creation (its structure for now is experimental and might change on short notice)
42    """
43    parser, elements = build_parser(args, options, setup_logging)
44    options = parser.parse_args(args)
45    creator, seeder, activators = tuple(e.create(options) for e in elements)  # create types
46    of_session = Session(options.verbosity, options.app_data, parser._interpreter, creator, seeder, activators)  # noqa
47    return of_session
48
49
50def build_parser(args=None, options=None, setup_logging=True):
51    parser = VirtualEnvConfigParser(options)
52    add_version_flag(parser)
53    parser.add_argument(
54        "--with-traceback",
55        dest="with_traceback",
56        action="store_true",
57        default=False,
58        help="on failure also display the stacktrace internals of virtualenv",
59    )
60    _do_report_setup(parser, args, setup_logging)
61    options = load_app_data(args, parser, options)
62    handle_extra_commands(options)
63
64    discover = get_discover(parser, args)
65    parser._interpreter = interpreter = discover.interpreter
66    if interpreter is None:
67        raise RuntimeError("failed to find interpreter for {}".format(discover))
68    elements = [
69        CreatorSelector(interpreter, parser),
70        SeederSelector(interpreter, parser),
71        ActivationSelector(interpreter, parser),
72    ]
73    options, _ = parser.parse_known_args(args)
74    for element in elements:
75        element.handle_selected_arg_parse(options)
76    parser.enable_help()
77    return parser, elements
78
79
80def build_parser_only(args=None):
81    """Used to provide a parser for the doc generation"""
82    return build_parser(args)[0]
83
84
85def handle_extra_commands(options):
86    if options.upgrade_embed_wheels:
87        result = manual_upgrade(options.app_data)
88        raise SystemExit(result)
89
90
91def load_app_data(args, parser, options):
92    parser.add_argument(
93        "--read-only-app-data",
94        action="store_true",
95        help="use app data folder in read-only mode (write operations will fail with error)",
96    )
97    options, _ = parser.parse_known_args(args, namespace=options)
98
99    # here we need a write-able application data (e.g. the zipapp might need this for discovery cache)
100    parser.add_argument(
101        "--app-data",
102        help="a data folder used as cache by the virtualenv",
103        type=partial(make_app_data, read_only=options.read_only_app_data),
104        default=make_app_data(None, read_only=options.read_only_app_data),
105    )
106    parser.add_argument(
107        "--reset-app-data",
108        action="store_true",
109        help="start with empty app data folder",
110    )
111    parser.add_argument(
112        "--upgrade-embed-wheels",
113        action="store_true",
114        help="trigger a manual update of the embedded wheels",
115    )
116    options, _ = parser.parse_known_args(args, namespace=options)
117    if options.reset_app_data:
118        options.app_data.reset()
119    return options
120
121
122def add_version_flag(parser):
123    import virtualenv
124
125    parser.add_argument(
126        "--version",
127        action="version",
128        version="%(prog)s {} from {}".format(__version__, virtualenv.__file__),
129        help="display the version of the virtualenv package and its location, then exit",
130    )
131
132
133def _do_report_setup(parser, args, setup_logging):
134    level_map = ", ".join("{}={}".format(logging.getLevelName(l), c) for c, l in sorted(list(LEVELS.items())))
135    msg = "verbosity = verbose - quiet, default {}, mapping => {}"
136    verbosity_group = parser.add_argument_group(
137        title="verbosity",
138        description=msg.format(logging.getLevelName(LEVELS[3]), level_map),
139    )
140    verbosity = verbosity_group.add_mutually_exclusive_group()
141    verbosity.add_argument("-v", "--verbose", action="count", dest="verbose", help="increase verbosity", default=2)
142    verbosity.add_argument("-q", "--quiet", action="count", dest="quiet", help="decrease verbosity", default=0)
143    option, _ = parser.parse_known_args(args)
144    if setup_logging:
145        setup_report(option.verbosity)
146
147
148__all__ = (
149    "cli_run",
150    "session_via_cli",
151)
152