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.
 
 106 ##### Subcommand classes
 
 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
 
 117 # Using non-top-level imports to make pyosmium optional for replication only.
 
 118 # pylint: disable=E0012,C0415
 
 121     Add additional data from a file or an online source.
 
 123     Data is only imported, not indexed. You need to call `nominatim index`
 
 124     to complete the process.
 
 128     def add_args(parser):
 
 129         group_name = parser.add_argument_group('Source')
 
 130         group = group_name.add_mutually_exclusive_group(required=True)
 
 131         group.add_argument('--file', metavar='FILE',
 
 132                            help='Import data from an OSM file')
 
 133         group.add_argument('--diff', metavar='FILE',
 
 134                            help='Import data from an OSM diff file')
 
 135         group.add_argument('--node', metavar='ID', type=int,
 
 136                            help='Import a single node from the API')
 
 137         group.add_argument('--way', metavar='ID', type=int,
 
 138                            help='Import a single way from the API')
 
 139         group.add_argument('--relation', metavar='ID', type=int,
 
 140                            help='Import a single relation from the API')
 
 141         group.add_argument('--tiger-data', metavar='DIR',
 
 142                            help='Add housenumbers from the US TIGER census database.')
 
 143         group = parser.add_argument_group('Extra arguments')
 
 144         group.add_argument('--use-main-api', action='store_true',
 
 145                            help='Use OSM API instead of Overpass to download objects')
 
 149         from nominatim.tokenizer import factory as tokenizer_factory
 
 150         from nominatim.tools import tiger_data
 
 153             tokenizer = tokenizer_factory.get_tokenizer_for_db(args.config)
 
 154             return tiger_data.add_tiger_data(args.tiger_data,
 
 155                                              args.config, args.threads or 1,
 
 158         params = ['update.php']
 
 160             params.extend(('--import-file', args.file))
 
 162             params.extend(('--import-diff', args.diff))
 
 164             params.extend(('--import-node', args.node))
 
 166             params.extend(('--import-way', args.way))
 
 168             params.extend(('--import-relation', args.relation))
 
 169         if args.use_main_api:
 
 170             params.append('--use-main-api')
 
 171         return run_legacy_script(*params, nominatim_env=args)
 
 176     Export addresses as CSV file from the database.
 
 180     def add_args(parser):
 
 181         group = parser.add_argument_group('Output arguments')
 
 182         group.add_argument('--output-type', default='street',
 
 183                            choices=('continent', 'country', 'state', 'county',
 
 184                                     'city', 'suburb', 'street', 'path'),
 
 185                            help='Type of places to output (default: street)')
 
 186         group.add_argument('--output-format',
 
 187                            default='street;suburb;city;county;state;country',
 
 188                            help=("Semicolon-separated list of address types "
 
 189                                  "(see --output-type). Multiple ranks can be "
 
 190                                  "merged into one column by simply using a "
 
 191                                  "comma-separated list."))
 
 192         group.add_argument('--output-all-postcodes', action='store_true',
 
 193                            help=("List all postcodes for address instead of "
 
 194                                  "just the most likely one"))
 
 195         group.add_argument('--language',
 
 196                            help=("Preferred language for output "
 
 197                                  "(use local name, if omitted)"))
 
 198         group = parser.add_argument_group('Filter arguments')
 
 199         group.add_argument('--restrict-to-country', metavar='COUNTRY_CODE',
 
 200                            help='Export only objects within country')
 
 201         group.add_argument('--restrict-to-osm-node', metavar='ID', type=int,
 
 202                            help='Export only children of this OSM node')
 
 203         group.add_argument('--restrict-to-osm-way', metavar='ID', type=int,
 
 204                            help='Export only children of this OSM way')
 
 205         group.add_argument('--restrict-to-osm-relation', metavar='ID', type=int,
 
 206                            help='Export only children of this OSM relation')
 
 211         params = ['export.php',
 
 212                   '--output-type', args.output_type,
 
 213                   '--output-format', args.output_format]
 
 214         if args.output_all_postcodes:
 
 215             params.append('--output-all-postcodes')
 
 217             params.extend(('--language', args.language))
 
 218         if args.restrict_to_country:
 
 219             params.extend(('--restrict-to-country', args.restrict_to_country))
 
 220         if args.restrict_to_osm_node:
 
 221             params.extend(('--restrict-to-osm-node', args.restrict_to_osm_node))
 
 222         if args.restrict_to_osm_way:
 
 223             params.extend(('--restrict-to-osm-way', args.restrict_to_osm_way))
 
 224         if args.restrict_to_osm_relation:
 
 225             params.extend(('--restrict-to-osm-relation', args.restrict_to_osm_relation))
 
 227         return run_legacy_script(*params, nominatim_env=args)
 
 232     Start a simple web server for serving the API.
 
 234     This command starts the built-in PHP webserver to serve the website
 
 235     from the current project directory. This webserver is only suitable
 
 236     for testing and develop. Do not use it in production setups!
 
 238     By the default, the webserver can be accessed at: http://127.0.0.1:8088
 
 242     def add_args(parser):
 
 243         group = parser.add_argument_group('Server arguments')
 
 244         group.add_argument('--server', default='127.0.0.1:8088',
 
 245                            help='The address the server will listen to.')
 
 249         run_php_server(args.server, args.project_dir / 'website')
 
 251 def get_set_parser(**kwargs):
 
 253     Initializes the parser and adds various subcommands for
 
 256     parser = CommandlineParser('nominatim', nominatim.__doc__)
 
 258     parser.add_subcommand('import', clicmd.SetupAll)
 
 259     parser.add_subcommand('freeze', clicmd.SetupFreeze)
 
 260     parser.add_subcommand('replication', clicmd.UpdateReplication)
 
 262     parser.add_subcommand('special-phrases', clicmd.ImportSpecialPhrases)
 
 264     parser.add_subcommand('add-data', UpdateAddData)
 
 265     parser.add_subcommand('index', clicmd.UpdateIndex)
 
 266     parser.add_subcommand('refresh', clicmd.UpdateRefresh())
 
 268     parser.add_subcommand('admin', clicmd.AdminFuncs)
 
 270     parser.add_subcommand('export', QueryExport)
 
 271     parser.add_subcommand('serve', AdminServe)
 
 273     if kwargs.get('phpcgi_path'):
 
 274         parser.add_subcommand('search', clicmd.APISearch)
 
 275         parser.add_subcommand('reverse', clicmd.APIReverse)
 
 276         parser.add_subcommand('lookup', clicmd.APILookup)
 
 277         parser.add_subcommand('details', clicmd.APIDetails)
 
 278         parser.add_subcommand('status', clicmd.APIStatus)
 
 280         parser.parser.epilog = 'php-cgi not found. Query commands not available.'
 
 285 def nominatim(**kwargs):
 
 287     Command-line tools for importing, updating, administrating and
 
 288     querying the Nominatim database.
 
 290     parser = get_set_parser(**kwargs)
 
 292     return parser.run(**kwargs)