]> git.openstreetmap.org Git - nominatim.git/blob - nominatim/cli.py
ICU: better letter identification in normalization
[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         try:
74             self.parser.parse_args(args=kwargs.get('cli_args'), namespace=args)
75         except SystemExit:
76             return 1
77
78         if args.subcommand is None:
79             self.parser.print_help()
80             return 1
81
82         for arg in ('module_dir', 'osm2pgsql_path', 'phplib_dir', 'sqllib_dir',
83                     'data_dir', 'config_dir', 'phpcgi_path'):
84             setattr(args, arg, Path(kwargs[arg]))
85         args.project_dir = Path(args.project_dir).resolve()
86
87         if 'cli_args' not in kwargs:
88             logging.basicConfig(stream=sys.stderr,
89                                 format='%(asctime)s: %(message)s',
90                                 datefmt='%Y-%m-%d %H:%M:%S',
91                                 level=max(4 - args.verbose, 1) * 10)
92
93         args.config = Configuration(args.project_dir, args.config_dir,
94                                     environ=kwargs.get('environ', os.environ))
95         args.config.set_libdirs(module=args.module_dir,
96                                 osm2pgsql=args.osm2pgsql_path,
97                                 php=args.phplib_dir,
98                                 sql=args.sqllib_dir,
99                                 data=args.data_dir)
100
101         log = logging.getLogger()
102         log.warning('Using project directory: %s', str(args.project_dir))
103
104         try:
105             return args.command.run(args)
106         except UsageError as exception:
107             if log.isEnabledFor(logging.DEBUG):
108                 raise # use Python's exception printing
109             log.fatal('FATAL: %s', exception)
110
111         # If we get here, then execution has failed in some way.
112         return 1
113
114
115 # Subcommand classes
116 #
117 # Each class needs to implement two functions: add_args() adds the CLI parameters
118 # for the subfunction, run() executes the subcommand.
119 #
120 # The class documentation doubles as the help text for the command. The
121 # first line is also used in the summary when calling the program without
122 # a subcommand.
123 #
124 # No need to document the functions each time.
125 # pylint: disable=C0111
126 class QueryExport:
127     """\
128     Export addresses as CSV file from the database.
129     """
130
131     @staticmethod
132     def add_args(parser):
133         group = parser.add_argument_group('Output arguments')
134         group.add_argument('--output-type', default='street',
135                            choices=('continent', 'country', 'state', 'county',
136                                     'city', 'suburb', 'street', 'path'),
137                            help='Type of places to output (default: street)')
138         group.add_argument('--output-format',
139                            default='street;suburb;city;county;state;country',
140                            help=("Semicolon-separated list of address types "
141                                  "(see --output-type). Multiple ranks can be "
142                                  "merged into one column by simply using a "
143                                  "comma-separated list."))
144         group.add_argument('--output-all-postcodes', action='store_true',
145                            help=("List all postcodes for address instead of "
146                                  "just the most likely one"))
147         group.add_argument('--language',
148                            help=("Preferred language for output "
149                                  "(use local name, if omitted)"))
150         group = parser.add_argument_group('Filter arguments')
151         group.add_argument('--restrict-to-country', metavar='COUNTRY_CODE',
152                            help='Export only objects within country')
153         group.add_argument('--restrict-to-osm-node', metavar='ID', type=int,
154                            help='Export only children of this OSM node')
155         group.add_argument('--restrict-to-osm-way', metavar='ID', type=int,
156                            help='Export only children of this OSM way')
157         group.add_argument('--restrict-to-osm-relation', metavar='ID', type=int,
158                            help='Export only children of this OSM relation')
159
160
161     @staticmethod
162     def run(args):
163         params = ['export.php',
164                   '--output-type', args.output_type,
165                   '--output-format', args.output_format]
166         if args.output_all_postcodes:
167             params.append('--output-all-postcodes')
168         if args.language:
169             params.extend(('--language', args.language))
170         if args.restrict_to_country:
171             params.extend(('--restrict-to-country', args.restrict_to_country))
172         if args.restrict_to_osm_node:
173             params.extend(('--restrict-to-osm-node', args.restrict_to_osm_node))
174         if args.restrict_to_osm_way:
175             params.extend(('--restrict-to-osm-way', args.restrict_to_osm_way))
176         if args.restrict_to_osm_relation:
177             params.extend(('--restrict-to-osm-relation', args.restrict_to_osm_relation))
178
179         return run_legacy_script(*params, nominatim_env=args)
180
181
182 class AdminServe:
183     """\
184     Start a simple web server for serving the API.
185
186     This command starts the built-in PHP webserver to serve the website
187     from the current project directory. This webserver is only suitable
188     for testing and development. Do not use it in production setups!
189
190     By the default, the webserver can be accessed at: http://127.0.0.1:8088
191     """
192
193     @staticmethod
194     def add_args(parser):
195         group = parser.add_argument_group('Server arguments')
196         group.add_argument('--server', default='127.0.0.1:8088',
197                            help='The address the server will listen to.')
198
199     @staticmethod
200     def run(args):
201         run_php_server(args.server, args.project_dir / 'website')
202
203 def get_set_parser(**kwargs):
204     """\
205     Initializes the parser and adds various subcommands for
206     nominatim cli.
207     """
208     parser = CommandlineParser('nominatim', nominatim.__doc__)
209
210     parser.add_subcommand('import', clicmd.SetupAll)
211     parser.add_subcommand('freeze', clicmd.SetupFreeze)
212     parser.add_subcommand('replication', clicmd.UpdateReplication)
213
214     parser.add_subcommand('special-phrases', clicmd.ImportSpecialPhrases)
215
216     parser.add_subcommand('add-data', clicmd.UpdateAddData)
217     parser.add_subcommand('index', clicmd.UpdateIndex)
218     parser.add_subcommand('refresh', clicmd.UpdateRefresh())
219
220     parser.add_subcommand('admin', clicmd.AdminFuncs)
221
222     parser.add_subcommand('export', QueryExport)
223     parser.add_subcommand('serve', AdminServe)
224
225     if kwargs.get('phpcgi_path'):
226         parser.add_subcommand('search', clicmd.APISearch)
227         parser.add_subcommand('reverse', clicmd.APIReverse)
228         parser.add_subcommand('lookup', clicmd.APILookup)
229         parser.add_subcommand('details', clicmd.APIDetails)
230         parser.add_subcommand('status', clicmd.APIStatus)
231     else:
232         parser.parser.epilog = 'php-cgi not found. Query commands not available.'
233
234     return parser
235
236
237 def nominatim(**kwargs):
238     """\
239     Command-line tools for importing, updating, administrating and
240     querying the Nominatim database.
241     """
242     parser = get_set_parser(**kwargs)
243
244     return parser.run(**kwargs)