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