text: # Copyright (c) 2011 Tencent Inc.
  # All rights reserved.
  #
  # Author: Chong peng <michaelpeng@tencent.com>
  # Date:   October 20, 2011
  
  
  """
  This is the CmdOptions module which parses the users'
  input and provides hint for users.
  """
  
  from __future__ import absolute_import
  from __future__ import print_function
  
  import argparse
  
  # https://pypi.org/project/argcomplete/
  try:
      # pyright: reportMissingImports=false
      import argcomplete
  except ImportError:
      argcomplete = None
  
  from blade import console
  from blade import constants
  from blade.toolchain import BuildArchitecture
  from blade.toolchain import ToolChain
  
  # The 'version.py' is generated by the dist script, then it only exists in blade.zip
  try:
      from blade.version import VERSION
  except ImportError:
      VERSION = '(developing, unversioned)'
  
  
  class CommandLineParser(object):
      """Command Line Parser.
  
      Parses user's input and provides hint.
      blade {command} [options] targets
  
      """
  
      def __init__(self):
          """Init the class."""
          self._arg_parser = self._build_arg_parser()
          if argcomplete:
              argcomplete.autocomplete(self._arg_parser)
  
      def parse(self, argv):
          """Parse command line."""
  
          options, others = self._arg_parser.parse_known_args(argv)
  
          # If '--' in arguments, use all other arguments after it as run
          # arguments
          if '--' in others:
              pos = others.index('--')
              targets = others[:pos]
              options.args = others[pos + 1:]
          else:
              targets = others
              options.args = []
  
          for t in targets:
              if t.startswith('-'):
                  console.fatal('Unrecognized option %s, use blade [action] '
                                '--help to get all the options' % t)
  
          command = options.command
          # Check the options with different sub command
          self._check_subcommand(command, options, targets)
  
          return command, options, targets
  
      def _check_subcommand(self, command, options, targets):
          """Check correctness of subcommand."""
          actions = {
              'build': self._check_build_command,
              'clean': self._check_clean_command,
              'dump': self._check_dump_command,
              'query': self._check_query_command,
              'run': self._check_run_command,
              'test': self._check_test_command,
          }
          actions[command](options, targets)
  
      def _check_run_targets(self, options, targets):
          """check that run command should have only one target."""
          if len(targets) != 1 or ':' not in targets[0] or targets[0].endswith('...'):
              console.fatal('Please specify a single target to run: '
                            'blade run //target_path:target_name (or '
                            'a_path:target_name)')
  
      def _check_test_options(self, options, targets):
          """check that test command options."""
  
      def _check_plat_and_profile_options(self, options, targets):
          """check platform and profile options."""
          compiler_arch = self._compiler_target_arch()
          arch = BuildArchitecture.get_canonical_architecture(compiler_arch)
          if arch is None:
              console.fatal('Unknown architecture: %s' % compiler_arch)
  
          m = options.m
          if not m:
              options.arch = arch
              options.bits = BuildArchitecture.get_architecture_bits(arch)
              assert options.bits
          else:
              options.bits = m
              options.arch = BuildArchitecture.get_model_architecture(arch, m)
              if options.arch is None:
                  console.fatal('"-m%s" is not supported by the architecture %s' % (m, compiler_arch))
  
      def _check_clean_options(self, options, targets):
          """check the clean options."""
          self._check_plat_and_profile_options(options, targets)
  
      def _check_query_options(self, options, targets):
          """check query action options."""
          if not options.deps and not options.dependents:
              console.fatal('Please specify --deps, --dependents or both to query target')
  
      def _check_build_options(self, options, targets):
          """check the building options."""
          self._check_plat_and_profile_options(options, targets)
  
      def _check_build_command(self, options, targets):
          """check build options."""
          self._check_build_options(options, targets)
  
      def _check_dump_command(self, options, targets):
          """check build options."""
          self._check_build_options(options, targets)
  
      def _check_run_command(self, options, targets):
          """check run options and the run targets."""
          self._check_build_options(options, targets)
          self._check_run_targets(options, targets)
  
      def _check_test_command(self, options, targets):
          """check test optios."""
          self._check_build_options(options, targets)
          self._check_test_options(options, targets)
  
      def _check_clean_command(self, options, targets):
          """check clean options."""
          self._check_clean_options(options, targets)
  
      def _check_query_command(self, options, targets):
          """check query options."""
          self._check_plat_and_profile_options(options, targets)
          self._check_query_options(options, targets)
  
      def __add_plat_profile_arguments(self, parser):
          """Add plat and profile arguments."""
          parser.add_argument('-m',
                              dest='m',
                              choices=['32', '64'],
                              default='',
                              help=('Generate code for a 32-bit(-m32) or '
                                    '64-bit(-m64) environment, '
                                    'default is autodetect'))
  
          parser.add_argument('-p',
                              '--profile',
                              dest='profile',
                              choices=['debug', 'release'],
                              default='release',
                              help=('Build profile, default is release'))
  
          parser.add_argument('--debug-info-level',
                              dest='debug_info_level',
                              choices=['no', 'low', 'mid', 'high'],
                              help='Produces how much debug information')
  
          # DEPRECATED, see above
          parser.add_argument('--no-debug-info',
                              dest='debug_info_level',
                              action='store_const',
                              const='no',
                              help=argparse.SUPPRESS)
  
      def __add_generate_arguments(self, parser):
          """Add generate related arguments."""
          parser.add_argument(
              '--generate-dynamic', dest='generate_dynamic',
              action='store_true', default=False,
              help='Generate dynamic libraries')
  
          parser.add_argument(
              '--generate-package', dest='generate_package',
              action='store_true', default=False,
              help='Generate packages for package target')
  
          parser.add_argument(
              '--generate-java', dest='generate_java',
              action='store_true', default=False,
              help='Generate java files for proto_library, thrift_library and '
                   'swig_library')
  
          parser.add_argument(
              '--generate-php', dest='generate_php',
              action='store_true', default=False,
              help='Generate php files for proto_library and swig_library')
  
          parser.add_argument(
              '--generate-python', dest='generate_python',
              action='store_true', default=False,
              help='Generate python files for proto_library and thrift_library')
  
          parser.add_argument(
              '--generate-go', dest='generate_go',
              action='store_true', default=False,
              help='Generate go files for proto_library')
  
      def __add_build_actions_arguments(self, parser):
          """Add build related action arguments."""
          parser.add_argument(
              '--backend-builder', dest='backend_builder', choices=['ninja'],
              help='Specify the underlying backend builder (currently only support ninja)')
  
          # Add extra backend builder options arguments.
          parser.add_argument(
              '--backend-builder-options', dest='backend_builder_options', metavar='OPTIONS',
              help='Specifies extra backend builder options, for debug purpose')
  
          parser.add_argument(
              '-j', '--jobs', dest='build_jobs', type=int,
              help=(constants.HELP.build_jobs))
  
          parser.add_argument(
              '-k', '--keep-going', dest='keep_going',
              action='store_true', default=False,
              help='Continue as much as possible after an error')
  
          parser.add_argument(
              '--no-test', dest='no_test', action='store_true',
              default=False, help='Do not build the test targets')
  
          parser.add_argument(
              '-n', '--dry-run', dest='dry_run', action='store_true', default=False,
              help='Dry run (don\'t run commands but act like they succeeded)')
  
          parser.add_argument(
              '--show-builds-slower-than', dest='show_builds_slower_than', metavar='SECONDS', type=float,
              help='Show build commands which are slower than specified seconds')
  
      def __add_coverage_arguments(self, parser):
          """Add coverage arguments."""
          parser.add_argument(
              '--gprof', dest='gprof',
              action='store_true', default=False,
              help='Add build options to support GNU gprof')
  
          parser.add_argument(
              '--coverage', dest='coverage',
              action='store_true', default=False,
              help='Add build options to support coverage test')
  
          # DEPRECATED, please use --coverage
          parser.add_argument(
              '--gcov', dest='coverage',
              action='store_true', default=False,
              help=argparse.SUPPRESS)
  
      def _add_query_arguments(self, parser):
          """Add query arguments for parser."""
          self.__add_plat_profile_arguments(parser)
          parser.add_argument(
              '--deps', dest='deps',
              action='store_true', default=False,
              help='Show all targets that depended by the target being queried')
          parser.add_argument(
              '--dependents', dest='dependents',
              action='store_true', default=False,
              help='Show all targets that depends on the target being queried')
          parser.add_argument(
              '--path-to', dest='query_path_to', type=str, default='',
              help='The targets to be depended on, comma separated')
          parser.add_argument(
              '--output-file', dest='output_file', type=str,
              help='The name of file to output query results, default to stdout')
          parser.add_argument(
              '--output-format', dest='output_format', type=str,
              choices=('plain', 'tree', 'dot'), default='plain',
              help='Specify the format of query results')
          parser.add_argument(
              '--depended', dest='dependents', action='store_true',
              help='DEPRECATED, please use --dependents')
  
      def _add_clean_arguments(self, parser):
          """Add clean arguments for parser."""
          self.__add_plat_profile_arguments(parser)
          self.__add_build_actions_arguments(parser)
          self.__add_generate_arguments(parser)
  
      def _add_test_arguments(self, parser):
          """Add test command arguments."""
          parser.add_argument(
              '--full-test', action='store_true',
              dest='full_test', default=False,
              help='Enable full test, default is incremental test')
  
          parser.add_argument(
              '-t', '--test-jobs', dest='test_jobs', type=int,
              help=(constants.HELP.test_jobs))
  
          parser.add_argument(
              '--show-details', action='store_true',
              dest='show_details', default=False,
              help='Shows the test result in detail and provides a file')
  
          parser.add_argument(
              '--show-tests-slower-than', type=float, metavar='SECONDS',
              dest='show_tests_slower_than',
              help='Show tests which are slower than specified seconds')
  
          parser.add_argument(
              '--no-build', action='store_true',
              dest='no_build', default=False,
              help='Run tests directly without build')
  
          parser.add_argument(
              '--exclude-tests', dest='exclude_tests', default='', metavar='TARGET_LIST',
              help='Exclude tests which matches this comma seperated target pattern list')
  
          parser.add_argument(
              '--run-unrepaired-tests', dest='run_unrepaired_tests', action='store_true',
              help=constants.HELP.run_unrepaired_tests)
  
      def _add_run_arguments(self, parser):
          """Add run command arguments."""
  
      def _add_build_arguments(self, *parsers):
          """Add building arguments for parsers."""
          for parser in parsers:
              self.__add_plat_profile_arguments(parser)
              self.__add_build_actions_arguments(parser)
              self.__add_generate_arguments(parser)
              self.__add_coverage_arguments(parser)
  
      def _add_common_arguments(self, *parsers):
          for parser in parsers:
              parser.add_argument(
                  '--profiling', dest='profiling', action='store_true',
                  help='Blade performance profiling, for blade developing')
              parser.add_argument(
                  '--stop-after', dest='stop_after', type=str,
                  choices=['load', 'analyze', 'generate', 'build', 'all'], default='all',
                  help='Stop after specified phase')
              parser.add_argument(
                  '--color', dest='color', choices=['yes', 'no', 'auto'], default='auto',
                  help='Output color mode selection')
              parser.add_argument(
                  '--load-local-config', dest='load_local_config',
                  default=True, action='store_true',
                  help='Load BLADE_ROOT.local')
              parser.add_argument(
                  '--no-load-local-config', dest='load_local_config',
                  action='store_false',
                  help='Do not load BLADE_ROOT.local')
              parser.add_argument(
                  '--verbose', dest='verbosity', action='store_const', const='verbose',
                  default='normal', help='Show all details')
              parser.add_argument(
                  '--quiet', dest='verbosity', action='store_const', const='quiet',
                  help='Only show warnings and errors')
              parser.add_argument(
                  '--exclude-targets', dest='exclude_targets', type=str, default='',
                  help='Comma separated target patterns to be excluded from loading')
              parser.add_argument(
                  '--jar-compression-level', dest='jar_compression_level', type=str, choices=['', '0'],
                  help=constants.HELP.jar_compression_level)
              parser.add_argument(
                  '--fat-jar-compression-level', dest='fat_jar_compression_level', type=str,
                  choices=([''] + [str(i) for i in range(9)]),
                  help=constants.HELP.fat_jar_compression_level)
              parser.add_argument(
                  '--tags-filter', dest='tags_filter', type=str,
                  help='Tags filter expression, see documents for details')
  
      def _add_dump_arguments(self, parser):
          """Add dump arguments for parser."""
          parser.add_argument(
              '--to-file', dest='dump_to_file', action='store', metavar='FILEPATH',
              default='/dev/stdout',
              help='Specifies the path of file to write the dump result')
          group = parser.add_mutually_exclusive_group(required=True)
          group.add_argument(
              '--compdb', dest='dump_compdb', default=False, action='store_true',
              help='Dump compilation database')
          group.add_argument(
              '--config', dest='dump_config', default=False, action='store_true',
              help='Dump blade configuration')
          group.add_argument(
              '--targets', dest='dump_targets', default=False, action='store_true',
              help='Dump attributes of targets in json format')
          group.add_argument(
              '--all-tags', dest='dump_all_tags', default=False, action='store_true',
              help='Dump all tags of targets in json format')
      def _build_arg_parser(self):
          """Add command options, add options whthin this method."""
          blade_cmd_help = 'blade <subcommand> [options...] [targets...]'
          arg_parser = argparse.ArgumentParser(prog='blade', description=blade_cmd_help)
          arg_parser.add_argument('--version', action='version', version='%(prog)s ' + VERSION)
          sub_parser = arg_parser.add_subparsers(dest='command', help='Available subcommands')
  
          sub_parser.required = True
  
          build_parser = sub_parser.add_parser(
              'build',
              help='Build specified targets')
  
          run_parser = sub_parser.add_parser(
              'run',
              help='Build and runs a single target',
              epilog='Any arguments after the empty "--" will be passed to the program')
  
          test_parser = sub_parser.add_parser(
              'test',
              help='Build the specified targets and runs tests',
              epilog='Any arguments after the empty "--" will be passed to the program')
  
          clean_parser = sub_parser.add_parser(
              'clean',
              help='Remove all blade-created output')
  
          query_parser = sub_parser.add_parser(
              'query',
              help='Execute a dependency graph query')
  
          dump_parser = sub_parser.add_parser(
              'dump',
              help='Dump specified internal information')
  
          self._add_common_arguments(build_parser, run_parser, test_parser,
                                     clean_parser, query_parser, dump_parser)
          self._add_build_arguments(build_parser, run_parser, test_parser, dump_parser)
          self._add_run_arguments(run_parser)
          self._add_test_arguments(test_parser)
          self._add_clean_arguments(clean_parser)
          self._add_query_arguments(query_parser)
          self._add_dump_arguments(dump_parser)
  
          return arg_parser
  
      def _compiler_target_arch(self):
          """Compiler(gcc) target architecture."""
          arch = ToolChain.get_cc_target_arch()
          pos = arch.find('-')
          if pos == -1:
              console.fatal('Unknown target architecture %s from gcc.' % arch)
          return arch[:pos]
  
  
  def parse(argv):
      """Parse argv into command, options and targets"""
      parser = CommandLineParser()
      return parser.parse(argv)
  