1 # SPDX-License-Identifier: GPL-2.0-only
 
   3 # This file is part of Nominatim. (https://nominatim.org)
 
   5 # Copyright (C) 2022 by the Nominatim developer community.
 
   6 # For a full list of authors see the git log.
 
   8 Command-line interface to the Nominatim functions for import, update,
 
   9 database administration and querying.
 
  15 from pathlib import Path
 
  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 import version
 
  22 from nominatim.clicmd.args import NominatimArgs
 
  24 LOG = logging.getLogger()
 
  27 class CommandlineParser:
 
  28     """ Wraps some of the common functions for parsing the command line
 
  29         and setting up subcommands.
 
  31     def __init__(self, prog, description):
 
  32         self.parser = argparse.ArgumentParser(
 
  34             description=description,
 
  35             formatter_class=argparse.RawDescriptionHelpFormatter)
 
  37         self.subs = self.parser.add_subparsers(title='available commands',
 
  40         # Global arguments that only work if no sub-command given
 
  41         self.parser.add_argument('--version', action='store_true',
 
  42                                  help='Print Nominatim version and exit')
 
  44         # Arguments added to every sub-command
 
  45         self.default_args = argparse.ArgumentParser(add_help=False)
 
  46         group = self.default_args.add_argument_group('Default arguments')
 
  47         group.add_argument('-h', '--help', action='help',
 
  48                            help='Show this help message and exit')
 
  49         group.add_argument('-q', '--quiet', action='store_const', const=0,
 
  50                            dest='verbose', default=1,
 
  51                            help='Print only error messages')
 
  52         group.add_argument('-v', '--verbose', action='count', default=1,
 
  53                            help='Increase verboseness of output')
 
  54         group.add_argument('--project-dir', metavar='DIR', default='.',
 
  55                            help='Base directory of the Nominatim installation (default:.)')
 
  56         group.add_argument('-j', '--threads', metavar='NUM', type=int,
 
  57                            help='Number of parallel threads to use')
 
  60     def nominatim_version_text():
 
  61         """ Program name and version number as string
 
  63         text = f'Nominatim version {version.version_str()}'
 
  64         if version.GIT_COMMIT_HASH is not None:
 
  65             text += f' ({version.GIT_COMMIT_HASH})'
 
  68     def add_subcommand(self, name, cmd):
 
  69         """ Add a subcommand to the parser. The subcommand must be a class
 
  70             with a function add_args() that adds the parameters for the
 
  71             subcommand and a run() function that executes the command.
 
  73         parser = self.subs.add_parser(name, parents=[self.default_args],
 
  74                                       help=cmd.__doc__.split('\n', 1)[0],
 
  75                                       description=cmd.__doc__,
 
  76                                       formatter_class=argparse.RawDescriptionHelpFormatter,
 
  78         parser.set_defaults(command=cmd)
 
  81     def run(self, **kwargs):
 
  82         """ Parse the command line arguments of the program and execute the
 
  83             appropriate subcommand.
 
  85         args = NominatimArgs()
 
  87             self.parser.parse_args(args=kwargs.get('cli_args'), namespace=args)
 
  92             print(CommandlineParser.nominatim_version_text())
 
  95         if args.subcommand is None:
 
  96             self.parser.print_help()
 
  99         for arg in ('module_dir', 'osm2pgsql_path', 'phplib_dir', 'sqllib_dir',
 
 100                     'data_dir', 'config_dir', 'phpcgi_path'):
 
 101             setattr(args, arg, Path(kwargs[arg]))
 
 102         args.project_dir = Path(args.project_dir).resolve()
 
 104         if 'cli_args' not in kwargs:
 
 105             logging.basicConfig(stream=sys.stderr,
 
 106                                 format='%(asctime)s: %(message)s',
 
 107                                 datefmt='%Y-%m-%d %H:%M:%S',
 
 108                                 level=max(4 - args.verbose, 1) * 10)
 
 110         args.config = Configuration(args.project_dir, args.config_dir,
 
 111                                     environ=kwargs.get('environ', os.environ))
 
 112         args.config.set_libdirs(module=args.module_dir,
 
 113                                 osm2pgsql=args.osm2pgsql_path,
 
 118         log = logging.getLogger()
 
 119         log.warning('Using project directory: %s', str(args.project_dir))
 
 122             return args.command.run(args)
 
 123         except UsageError as exception:
 
 124             if log.isEnabledFor(logging.DEBUG):
 
 125                 raise # use Python's exception printing
 
 126             log.fatal('FATAL: %s', exception)
 
 128         # If we get here, then execution has failed in some way.
 
 134 # Each class needs to implement two functions: add_args() adds the CLI parameters
 
 135 # for the subfunction, run() executes the subcommand.
 
 137 # The class documentation doubles as the help text for the command. The
 
 138 # first line is also used in the summary when calling the program without
 
 141 # No need to document the functions each time.
 
 142 # pylint: disable=C0111
 
 145     Export addresses as CSV file from the database.
 
 149     def add_args(parser):
 
 150         group = parser.add_argument_group('Output arguments')
 
 151         group.add_argument('--output-type', default='street',
 
 152                            choices=('continent', 'country', 'state', 'county',
 
 153                                     'city', 'suburb', 'street', 'path'),
 
 154                            help='Type of places to output (default: street)')
 
 155         group.add_argument('--output-format',
 
 156                            default='street;suburb;city;county;state;country',
 
 157                            help=("Semicolon-separated list of address types "
 
 158                                  "(see --output-type). Multiple ranks can be "
 
 159                                  "merged into one column by simply using a "
 
 160                                  "comma-separated list."))
 
 161         group.add_argument('--output-all-postcodes', action='store_true',
 
 162                            help=("List all postcodes for address instead of "
 
 163                                  "just the most likely one"))
 
 164         group.add_argument('--language',
 
 165                            help=("Preferred language for output "
 
 166                                  "(use local name, if omitted)"))
 
 167         group = parser.add_argument_group('Filter arguments')
 
 168         group.add_argument('--restrict-to-country', metavar='COUNTRY_CODE',
 
 169                            help='Export only objects within country')
 
 170         group.add_argument('--restrict-to-osm-node', metavar='ID', type=int,
 
 171                            help='Export only children of this OSM node')
 
 172         group.add_argument('--restrict-to-osm-way', metavar='ID', type=int,
 
 173                            help='Export only children of this OSM way')
 
 174         group.add_argument('--restrict-to-osm-relation', metavar='ID', type=int,
 
 175                            help='Export only children of this OSM relation')
 
 180         params = ['export.php',
 
 181                   '--output-type', args.output_type,
 
 182                   '--output-format', args.output_format]
 
 183         if args.output_all_postcodes:
 
 184             params.append('--output-all-postcodes')
 
 186             params.extend(('--language', args.language))
 
 187         if args.restrict_to_country:
 
 188             params.extend(('--restrict-to-country', args.restrict_to_country))
 
 189         if args.restrict_to_osm_node:
 
 190             params.extend(('--restrict-to-osm-node', args.restrict_to_osm_node))
 
 191         if args.restrict_to_osm_way:
 
 192             params.extend(('--restrict-to-osm-way', args.restrict_to_osm_way))
 
 193         if args.restrict_to_osm_relation:
 
 194             params.extend(('--restrict-to-osm-relation', args.restrict_to_osm_relation))
 
 196         return run_legacy_script(*params, nominatim_env=args)
 
 201     Start a simple web server for serving the API.
 
 203     This command starts the built-in PHP webserver to serve the website
 
 204     from the current project directory. This webserver is only suitable
 
 205     for testing and development. Do not use it in production setups!
 
 207     By the default, the webserver can be accessed at: http://127.0.0.1:8088
 
 211     def add_args(parser):
 
 212         group = parser.add_argument_group('Server arguments')
 
 213         group.add_argument('--server', default='127.0.0.1:8088',
 
 214                            help='The address the server will listen to.')
 
 218         run_php_server(args.server, args.project_dir / 'website')
 
 220 def get_set_parser(**kwargs):
 
 222     Initializes the parser and adds various subcommands for
 
 225     parser = CommandlineParser('nominatim', nominatim.__doc__)
 
 227     parser.add_subcommand('import', clicmd.SetupAll)
 
 228     parser.add_subcommand('freeze', clicmd.SetupFreeze)
 
 229     parser.add_subcommand('replication', clicmd.UpdateReplication)
 
 231     parser.add_subcommand('special-phrases', clicmd.ImportSpecialPhrases)
 
 233     parser.add_subcommand('add-data', clicmd.UpdateAddData)
 
 234     parser.add_subcommand('index', clicmd.UpdateIndex)
 
 235     parser.add_subcommand('refresh', clicmd.UpdateRefresh())
 
 237     parser.add_subcommand('admin', clicmd.AdminFuncs)
 
 239     parser.add_subcommand('export', QueryExport)
 
 240     parser.add_subcommand('serve', AdminServe)
 
 242     if kwargs.get('phpcgi_path'):
 
 243         parser.add_subcommand('search', clicmd.APISearch)
 
 244         parser.add_subcommand('reverse', clicmd.APIReverse)
 
 245         parser.add_subcommand('lookup', clicmd.APILookup)
 
 246         parser.add_subcommand('details', clicmd.APIDetails)
 
 247         parser.add_subcommand('status', clicmd.APIStatus)
 
 249         parser.parser.epilog = 'php-cgi not found. Query commands not available.'
 
 254 def nominatim(**kwargs):
 
 256     Command-line tools for importing, updating, administrating and
 
 257     querying the Nominatim database.
 
 259     parser = get_set_parser(**kwargs)
 
 261     return parser.run(**kwargs)