]> git.openstreetmap.org Git - nominatim.git/blob - nominatim/cli.py
Merge branch 'master' into country-names-yaml-configuration
[nominatim.git] / nominatim / cli.py
1 # SPDX-License-Identifier: GPL-2.0-only
2 #
3 # This file is part of Nominatim. (https://nominatim.org)
4 #
5 # Copyright (C) 2022 by the Nominatim developer community.
6 # For a full list of authors see the git log.
7 """
8 Command-line interface to the Nominatim functions for import, update,
9 database administration and querying.
10 """
11 import logging
12 import os
13 import sys
14 import argparse
15 from pathlib import Path
16
17 from nominatim.config import Configuration
18 from nominatim.tools.exec_utils import run_legacy_script, run_php_server
19 from nominatim.errors import UsageError
20 from nominatim import clicmd
21 from nominatim.clicmd.args import NominatimArgs
22
23 LOG = logging.getLogger()
24
25
26 class CommandlineParser:
27     """ Wraps some of the common functions for parsing the command line
28         and setting up subcommands.
29     """
30     def __init__(self, prog, description):
31         self.parser = argparse.ArgumentParser(
32             prog=prog,
33             description=description,
34             formatter_class=argparse.RawDescriptionHelpFormatter)
35
36         self.subs = self.parser.add_subparsers(title='available commands',
37                                                dest='subcommand')
38
39         # Arguments added to every sub-command
40         self.default_args = argparse.ArgumentParser(add_help=False)
41         group = self.default_args.add_argument_group('Default arguments')
42         group.add_argument('-h', '--help', action='help',
43                            help='Show this help message and exit')
44         group.add_argument('-q', '--quiet', action='store_const', const=0,
45                            dest='verbose', default=1,
46                            help='Print only error messages')
47         group.add_argument('-v', '--verbose', action='count', default=1,
48                            help='Increase verboseness of output')
49         group.add_argument('--project-dir', metavar='DIR', default='.',
50                            help='Base directory of the Nominatim installation (default:.)')
51         group.add_argument('-j', '--threads', metavar='NUM', type=int,
52                            help='Number of parallel threads to use')
53
54
55     def add_subcommand(self, name, cmd):
56         """ Add a subcommand to the parser. The subcommand must be a class
57             with a function add_args() that adds the parameters for the
58             subcommand and a run() function that executes the command.
59         """
60         parser = self.subs.add_parser(name, parents=[self.default_args],
61                                       help=cmd.__doc__.split('\n', 1)[0],
62                                       description=cmd.__doc__,
63                                       formatter_class=argparse.RawDescriptionHelpFormatter,
64                                       add_help=False)
65         parser.set_defaults(command=cmd)
66         cmd.add_args(parser)
67
68     def run(self, **kwargs):
69         """ Parse the command line arguments of the program and execute the
70             appropriate subcommand.
71         """
72         args = NominatimArgs()
73         self.parser.parse_args(args=kwargs.get('cli_args'), namespace=args)
74
75         if args.subcommand is None:
76             self.parser.print_help()
77             return 1
78
79         for arg in ('module_dir', 'osm2pgsql_path', 'phplib_dir', 'sqllib_dir',
80                     'data_dir', 'config_dir', 'phpcgi_path'):
81             setattr(args, arg, Path(kwargs[arg]))
82         args.project_dir = Path(args.project_dir).resolve()
83
84         if 'cli_args' not in kwargs:
85             logging.basicConfig(stream=sys.stderr,
86                                 format='%(asctime)s: %(message)s',
87                                 datefmt='%Y-%m-%d %H:%M:%S',
88                                 level=max(4 - args.verbose, 1) * 10)
89
90         args.config = Configuration(args.project_dir, args.config_dir,
91                                     environ=kwargs.get('environ', os.environ))
92         args.config.set_libdirs(module=args.module_dir,
93                                 osm2pgsql=args.osm2pgsql_path,
94                                 php=args.phplib_dir,
95                                 sql=args.sqllib_dir,
96                                 data=args.data_dir)
97
98         log = logging.getLogger()
99         log.warning('Using project directory: %s', str(args.project_dir))
100
101         try:
102             return args.command.run(args)
103         except UsageError as exception:
104             if log.isEnabledFor(logging.DEBUG):
105                 raise # use Python's exception printing
106             log.fatal('FATAL: %s', exception)
107
108         # If we get here, then execution has failed in some way.
109         return 1
110
111
112 # Subcommand classes
113 #
114 # Each class needs to implement two functions: add_args() adds the CLI parameters
115 # for the subfunction, run() executes the subcommand.
116 #
117 # The class documentation doubles as the help text for the command. The
118 # first line is also used in the summary when calling the program without
119 # a subcommand.
120 #
121 # No need to document the functions each time.
122 # pylint: disable=C0111
123 class QueryExport:
124     """\
125     Export addresses as CSV file from the database.
126     """
127
128     @staticmethod
129     def add_args(parser):
130         group = parser.add_argument_group('Output arguments')
131         group.add_argument('--output-type', default='street',
132                            choices=('continent', 'country', 'state', 'county',
133                                     'city', 'suburb', 'street', 'path'),
134                            help='Type of places to output (default: street)')
135         group.add_argument('--output-format',
136                            default='street;suburb;city;county;state;country',
137                            help=("Semicolon-separated list of address types "
138                                  "(see --output-type). Multiple ranks can be "
139                                  "merged into one column by simply using a "
140                                  "comma-separated list."))
141         group.add_argument('--output-all-postcodes', action='store_true',
142                            help=("List all postcodes for address instead of "
143                                  "just the most likely one"))
144         group.add_argument('--language',
145                            help=("Preferred language for output "
146                                  "(use local name, if omitted)"))
147         group = parser.add_argument_group('Filter arguments')
148         group.add_argument('--restrict-to-country', metavar='COUNTRY_CODE',
149                            help='Export only objects within country')
150         group.add_argument('--restrict-to-osm-node', metavar='ID', type=int,
151                            help='Export only children of this OSM node')
152         group.add_argument('--restrict-to-osm-way', metavar='ID', type=int,
153                            help='Export only children of this OSM way')
154         group.add_argument('--restrict-to-osm-relation', metavar='ID', type=int,
155                            help='Export only children of this OSM relation')
156
157
158     @staticmethod
159     def run(args):
160         params = ['export.php',
161                   '--output-type', args.output_type,
162                   '--output-format', args.output_format]
163         if args.output_all_postcodes:
164             params.append('--output-all-postcodes')
165         if args.language:
166             params.extend(('--language', args.language))
167         if args.restrict_to_country:
168             params.extend(('--restrict-to-country', args.restrict_to_country))
169         if args.restrict_to_osm_node:
170             params.extend(('--restrict-to-osm-node', args.restrict_to_osm_node))
171         if args.restrict_to_osm_way:
172             params.extend(('--restrict-to-osm-way', args.restrict_to_osm_way))
173         if args.restrict_to_osm_relation:
174             params.extend(('--restrict-to-osm-relation', args.restrict_to_osm_relation))
175
176         return run_legacy_script(*params, nominatim_env=args)
177
178
179 class AdminServe:
180     """\
181     Start a simple web server for serving the API.
182
183     This command starts the built-in PHP webserver to serve the website
184     from the current project directory. This webserver is only suitable
185     for testing and development. Do not use it in production setups!
186
187     By the default, the webserver can be accessed at: http://127.0.0.1:8088
188     """
189
190     @staticmethod
191     def add_args(parser):
192         group = parser.add_argument_group('Server arguments')
193         group.add_argument('--server', default='127.0.0.1:8088',
194                            help='The address the server will listen to.')
195
196     @staticmethod
197     def run(args):
198         run_php_server(args.server, args.project_dir / 'website')
199
200 def get_set_parser(**kwargs):
201     """\
202     Initializes the parser and adds various subcommands for
203     nominatim cli.
204     """
205     parser = CommandlineParser('nominatim', nominatim.__doc__)
206
207     parser.add_subcommand('import', clicmd.SetupAll)
208     parser.add_subcommand('freeze', clicmd.SetupFreeze)
209     parser.add_subcommand('replication', clicmd.UpdateReplication)
210
211     parser.add_subcommand('special-phrases', clicmd.ImportSpecialPhrases)
212
213     parser.add_subcommand('add-data', clicmd.UpdateAddData)
214     parser.add_subcommand('index', clicmd.UpdateIndex)
215     parser.add_subcommand('refresh', clicmd.UpdateRefresh())
216
217     parser.add_subcommand('admin', clicmd.AdminFuncs)
218
219     parser.add_subcommand('export', QueryExport)
220     parser.add_subcommand('serve', AdminServe)
221
222     if kwargs.get('phpcgi_path'):
223         parser.add_subcommand('search', clicmd.APISearch)
224         parser.add_subcommand('reverse', clicmd.APIReverse)
225         parser.add_subcommand('lookup', clicmd.APILookup)
226         parser.add_subcommand('details', clicmd.APIDetails)
227         parser.add_subcommand('status', clicmd.APIStatus)
228     else:
229         parser.parser.epilog = 'php-cgi not found. Query commands not available.'
230
231     return parser
232
233
234 def nominatim(**kwargs):
235     """\
236     Command-line tools for importing, updating, administrating and
237     querying the Nominatim database.
238     """
239     parser = get_set_parser(**kwargs)
240
241     return parser.run(**kwargs)