! @brief Construct the command line parser with all subcommands and options.

def build_parser(self):
        """! @brief Construct the command line parser with all subcommands and options."""
        # Create top level argument parser.
        parser = argparse.ArgumentParser(
            description='PyOCD debug tools for Arm Cortex devices')
        subparsers = parser.add_subparsers(title="subcommands", metavar="", dest='cmd')
        
        parser.add_argument('-V', '--version', action='version', version=__version__)
        parser.add_argument('--help-options', action='store_true',
            help="Display available session options.")
        
        # Define logging related options.
        loggingOptions = argparse.ArgumentParser(description='logging', add_help=False)
        loggingOptions.add_argument('-v', '--verbose', action='count', default=0,
            help="More logging. Can be specified multiple times.")
        loggingOptions.add_argument('-q', '--quiet', action='count', default=0,
            help="Less logging. Can be specified multiple times.")
        
        # Define common options for all subcommands, excluding --verbose and --quiet.
        commonOptionsNoLogging = argparse.ArgumentParser(description='common', add_help=False)
        commonOptionsNoLogging.add_argument('-j', '--dir', metavar="PATH", dest="project_dir", default=os.getcwd(),
            help="Set the project directory. Defaults to the directory where pyocd was run.")
        commonOptionsNoLogging.add_argument('--config', metavar="PATH",
            help="Specify YAML configuration file. Default is pyocd.yaml or pyocd.yml.")
        commonOptionsNoLogging.add_argument("--no-config", action="store_true",
            help="Do not use a configuration file.")
        commonOptionsNoLogging.add_argument('--script', metavar="PATH",
            help="Use the specified user script. Defaults to pyocd_user.py.")
        commonOptionsNoLogging.add_argument('-O', action='append', dest='options', metavar="OPTION=VALUE",
            help="Set named option.")
        commonOptionsNoLogging.add_argument("-da", "--daparg", dest="daparg", nargs='+',
            help="Send setting to DAPAccess layer.")
        commonOptionsNoLogging.add_argument("--pack", metavar="PATH", action="append",
            help="Path to a CMSIS Device Family Pack.")
        
        # Define common options for all subcommands with --verbose and --quiet.
        commonOptions = argparse.ArgumentParser(description='common',
            parents=[loggingOptions, commonOptionsNoLogging], add_help=False)
        
        # Common connection related options.
        connectOptions = argparse.ArgumentParser(description='common', add_help=False)
        connectOptions.add_argument("-u", "--uid", dest="unique_id",
            help="Choose a probe by its unique ID or a substring thereof.")
        connectOptions.add_argument("-b", "--board", dest="board_override", metavar="BOARD",
            help="Set the board type (not yet implemented).")
        connectOptions.add_argument("-t", "--target", dest="target_override", metavar="TARGET",
            help="Set the target type.")
        connectOptions.add_argument("-f", "--frequency", dest="frequency", default=1000000, type=convert_frequency,
            help="SWD/JTAG clock frequency in Hz, with optional k/K or m/M suffix for kHz or MHz.")
        connectOptions.add_argument("-W", "--no-wait", action="store_true",
            help="Do not wait for a probe to be connected if none are available.")

        # Create *commander* subcommand parser.
        commandOptions = argparse.ArgumentParser(description='command', add_help=False)
        commandOptions.add_argument("-H", "--halt", action="store_true",
            help="Halt core upon connect.")
        commandOptions.add_argument("-N", "--no-init", action="store_true",
            help="Do not init debug system.")
        commandOptions.add_argument("--elf", metavar="PATH",
            help="Optionally specify ELF file being debugged.")
        commandOptions.add_argument("-c", "--command", dest="commands", metavar="CMD", action='append', nargs='+',
            help="Run commands.")
        subparsers.add_parser('commander', parents=[commonOptions, connectOptions, commandOptions],
            help="Interactive command console.")
        subparsers.add_parser('cmd', parents=[commonOptions, connectOptions, commandOptions],
            help="Alias for 'commander'.")

        # Create *erase* subcommand parser.
        eraseParser = subparsers.add_parser('erase', parents=[commonOptions, connectOptions],
            help="Erase entire device flash or specified sectors.",
            epilog="If no position arguments are listed, then no action will be taken unless the --chip or "
            "--mass-erase options are provided. Otherwise, the positional arguments should be the addresses of flash "
            "sectors or address ranges. The end address of a range is exclusive, meaning that it will not be "
            "erased. Thus, you should specify the address of the sector after the last one "
            "to be erased. If a '+' is used instead of '-' in a range, this indicates that the "
            "second value is a length rather than end address. "
            "Examples: 0x1000 (erase single sector starting at 0x1000) "
            "0x800-0x2000 (erase sectors starting at 0x800 up to but not including 0x2000) "
            "0+8192 (erase 8 kB starting at address 0)")
        eraseParser.add_argument("-c", "--chip", dest="erase_mode", action="store_const", const=loader.FlashEraser.Mode.CHIP,
            help="Perform a chip erase.")
        eraseParser.add_argument("-s", "--sector", dest="erase_mode", action="store_const", const=loader.FlashEraser.Mode.SECTOR,
            help="Erase the sectors listed as positional arguments.")
        eraseParser.add_argument("--mass-erase", dest="erase_mode", action="store_const", const=loader.FlashEraser.Mode.MASS,
            help="Perform a mass erase. On some devices this is different than a chip erase.")
        eraseParser.add_argument("addresses", metavar="<sector-address>", action='append', nargs='*',
            help="List of sector addresses or ranges to erase.")

        # Create *flash* subcommand parser.
        flashParser = subparsers.add_parser('flash', parents=[commonOptions, connectOptions],
            help="Program an image to device flash.")
        flashParser.add_argument("-e", "--erase", choices=ERASE_OPTIONS.keys(), default='sector',
            help="Choose flash erase method. Default is sector.")
        flashParser.add_argument("-a", "--base-address", metavar="ADDR", type=int_base_0,
            help="Base address used for the address where to flash a binary. Defaults to start of flash.")
        flashParser.add_argument("--trust-crc", action="store_true",
            help="Use only the CRC of each page to determine if it already has the same data.")
        flashParser.add_argument("--format", choices=("bin", "hex", "elf"),
            help="File format. Default is to use the file's extension.")
        flashParser.add_argument("--skip", metavar="BYTES", default=0, type=int_base_0,
            help="Skip programming the first N bytes. This can only be used with binary files.")
        flashParser.add_argument("file", metavar="PATH",
            help="File to program into flash.")
        
        # Create *gdbserver* subcommand parser.
        gdbserverOptions = argparse.ArgumentParser(description='gdbserver', add_help=False)
        gdbserverOptions.add_argument("-p", "--port", dest="port_number", type=int, default=3333,
            help="Set the port number that GDB server will open (default 3333).")
        gdbserverOptions.add_argument("-T", "--telnet-port", dest="telnet_port", type=int, default=4444,
            help="Specify the telnet port for semihosting (default 4444).")
        gdbserverOptions.add_argument("--allow-remote", dest="serve_local_only", default=True, action="store_false",
            help="Allow remote TCP/IP connections (default is no).")
        gdbserverOptions.add_argument("--persist", dest="persist", default=False, action="store_true",
            help="Keep GDB server running even after remote has detached.")
        gdbserverOptions.add_argument("--elf", metavar="PATH",
            help="Optionally specify ELF file being debugged.")
        gdbserverOptions.add_argument("-e", "--erase", choices=ERASE_OPTIONS.keys(), default='sector',
            help="Choose flash erase method. Default is sector.")
        gdbserverOptions.add_argument("--trust-crc", action="store_true",
            help="Use only the CRC of each page to determine if it already has the same data.")
        gdbserverOptions.add_argument("-C", "--vector-catch", default='h',
            help="Enable vector catch sources, one letter per enabled source in any order, or 'all' "
                "or 'none'. (h=hard fault, b=bus fault, m=mem fault, i=irq err, s=state err, "
                "c=check err, p=nocp, r=reset, a=all, n=none). Default is hard fault.")
        gdbserverOptions.add_argument("-S", "--semihosting", dest="enable_semihosting", action="store_true",
            help="Enable semihosting.")
        gdbserverOptions.add_argument("--step-into-interrupts", dest="step_into_interrupt", default=False, action="store_true",
            help="Allow single stepping to step into interrupts.")
        gdbserverOptions.add_argument("-c", "--command", dest="commands", metavar="CMD", action='append', nargs='+',
            help="Run command (OpenOCD compatibility).")
        subparsers.add_parser('gdbserver', parents=[commonOptions, connectOptions, gdbserverOptions],
            help="Run the gdb remote server(s).")
        subparsers.add_parser('gdb', parents=[commonOptions, connectOptions, gdbserverOptions],
            help="Alias for 'gdbserver'.")

        # Create *json* subcommand parser.
        #
        # The json subcommand does not support --verbose or --quiet since all logging is disabled.
        jsonParser = subparsers.add_parser('json', parents=[commonOptionsNoLogging],
            help="Output information as JSON.")
        group = jsonParser.add_mutually_exclusive_group()
        group.add_argument('-p', '--probes', action='store_true',
            help="List available probes.")
        group.add_argument('-t', '--targets', action='store_true',
            help="List all known targets.")
        group.add_argument('-b', '--boards', action='store_true',
            help="List all known boards.")
        jsonParser.set_defaults(verbose=0, quiet=0)

        # Create *list* subcommand parser.
        listParser = subparsers.add_parser('list', parents=[commonOptions],
            help="List information about probes, targets, or boards.")
        group = listParser.add_mutually_exclusive_group()
        group.add_argument('-p', '--probes', action='store_true',
            help="List available probes.")
        group.add_argument('-t', '--targets', action='store_true',
            help="List all known targets.")
        group.add_argument('-b', '--boards', action='store_true',
            help="List all known boards.")
        listParser.add_argument('-n', '--name',
            help="Restrict listing to items matching the given name.")

        # Create *pack* subcommand parser.
        packParser = subparsers.add_parser('pack', parents=[loggingOptions],
            help="Manage CMSIS-Packs for target support.")
        packParser.add_argument("-c", "--clean", action='store_true',
            help="Erase all stored pack information.")
        packParser.add_argument("-u", "--update", action='store_true',
            help="Update the pack index.")
        packParser.add_argument("-s", "--show", action='store_true',
            help="Show the list of installed packs.")
        packParser.add_argument("-f", "--find", dest="find_devices", metavar="GLOB", action='append',
            help="Look up a device part number in the index using a glob pattern. The pattern is "
                "suffixed with '*'. Can be specified multiple times.")
        packParser.add_argument("-i", "--install", dest="install_devices", metavar="GLOB", action='append',
            help="Download and install pack(s) to support targets matching the glob pattern. "
                "The pattern is suffixed with '*'. Can be specified multiple times.")
        packParser.add_argument("-n", "--no-download", action='store_true',
            help="Just list the pack(s) that would be downloaded, don't actually download anything.")
        
        self._parser = parser
        return parser