1# Copyright 2017-2020 Palantir Technologies, Inc.
2# Copyright 2021- Python Language Server Contributors.
3
4import argparse
5import logging
6import logging.config
7import sys
8import time
9
10try:
11    import ujson as json
12except Exception:  # pylint: disable=broad-except
13    import json
14
15from .python_lsp import (PythonLSPServer, start_io_lang_server,
16                         start_tcp_lang_server)
17from ._version import __version__
18
19LOG_FORMAT = "%(asctime)s {0} - %(levelname)s - %(name)s - %(message)s".format(
20    time.localtime().tm_zone)
21
22
23def add_arguments(parser):
24    parser.description = "Python Language Server"
25
26    parser.add_argument(
27        "--tcp", action="store_true",
28        help="Use TCP server instead of stdio"
29    )
30    parser.add_argument(
31        "--host", default="127.0.0.1",
32        help="Bind to this address"
33    )
34    parser.add_argument(
35        "--port", type=int, default=2087,
36        help="Bind to this port"
37    )
38    parser.add_argument(
39        '--check-parent-process', action="store_true",
40        help="Check whether parent process is still alive using os.kill(ppid, 0) "
41        "and auto shut down language server process when parent process is not alive."
42        "Note that this may not work on a Windows machine."
43    )
44
45    log_group = parser.add_mutually_exclusive_group()
46    log_group.add_argument(
47        "--log-config",
48        help="Path to a JSON file containing Python logging config."
49    )
50    log_group.add_argument(
51        "--log-file",
52        help="Redirect logs to the given file instead of writing to stderr."
53        "Has no effect if used with --log-config."
54    )
55
56    parser.add_argument(
57        '-v', '--verbose', action='count', default=0,
58        help="Increase verbosity of log output, overrides log config file"
59    )
60
61    parser.add_argument(
62        '-V', '--version', action='version', version='%(prog)s v' + __version__
63    )
64
65
66def main():
67    parser = argparse.ArgumentParser()
68    add_arguments(parser)
69    args = parser.parse_args()
70    _configure_logger(args.verbose, args.log_config, args.log_file)
71
72    if args.tcp:
73        start_tcp_lang_server(args.host, args.port, args.check_parent_process,
74                              PythonLSPServer)
75    else:
76        stdin, stdout = _binary_stdio()
77        start_io_lang_server(stdin, stdout, args.check_parent_process,
78                             PythonLSPServer)
79
80
81def _binary_stdio():
82    """Construct binary stdio streams (not text mode).
83
84    This seems to be different for Window/Unix Python2/3, so going by:
85        https://stackoverflow.com/questions/2850893/reading-binary-data-from-stdin
86    """
87    stdin, stdout = sys.stdin.buffer, sys.stdout.buffer
88    return stdin, stdout
89
90
91def _configure_logger(verbose=0, log_config=None, log_file=None):
92    root_logger = logging.root
93
94    if log_config:
95        with open(log_config, 'r', encoding='utf-8') as f:
96            logging.config.dictConfig(json.load(f))
97    else:
98        formatter = logging.Formatter(LOG_FORMAT)
99        if log_file:
100            log_handler = logging.handlers.RotatingFileHandler(
101                log_file, mode='a', maxBytes=50*1024*1024,
102                backupCount=10, encoding=None, delay=0
103            )
104        else:
105            log_handler = logging.StreamHandler()
106        log_handler.setFormatter(formatter)
107        root_logger.addHandler(log_handler)
108
109    if verbose == 0:
110        level = logging.WARNING
111    elif verbose == 1:
112        level = logging.INFO
113    elif verbose >= 2:
114        level = logging.DEBUG
115
116    root_logger.setLevel(level)
117
118
119if __name__ == '__main__':
120    main()
121