run a bunch of postinit hooks if there is a short help then create the longer version from it shorthelp self configuration get shorthelp if shorthelp and help not in self configuration and self configuration get action lazychoices self configuration help f nshorthelpnn def serializeself isolationmode bool false dictstr any configuration self configuration copy unpack the dynamically computed choices since we will need to store the actual values somewhere action configuration pop action none shorthelp configuration pop shorthelp none nestedoptions configuration pop nestedoptions none if action lazychoices choices lazychoices self aliases dest none configuration isolationmodeisolationmode configuration choices listchoices load configuration help choices help result if self aliases result options self aliases copy else result options configuration metavar result ispositional true qualifiers jsonqualifiertooptionsconfiguration get nargs qualifiers suppress result updatequalifiers description configuration get help if description and description is not qualifiers suppress result shortdescription shorthelp result description description if nestedoptions result nestedoptions nestedoptions pythontype configuration get type if pythontype is not none if hasattrpythontype name typename pythontype name else typename typepythontype name result pythontypename typename result update key value for key value in configuration items if key in jsondirectmirroroptions if value is not qualifiers suppress return result property def ispositionalself return lenself aliases 0 property def ishiddenself return self configuration get help is qualifiers suppress def getattrself attributename if attributename in self configuration return self configurationattributename else raise attributeerrorattributename parsertype typevar parsertype boundtypeargparse argumentparser argparsequalifiermap qualifiers optional argparse optional qualifiers suppress argparse suppress qualifiers zeroormore argparse zeroormore qualifiers oneormore argparse oneormore argparseignorekeys shorthelp nestedoptions def toargparse abstractoptions parserspec parsertype parsertype httpieargumentparser parsertype concreteparser parsertype progabstractoptions program descriptionabstractoptions description epilogabstractoptions epilog concreteparser spec abstractoptions concreteparser register action lazychoices lazychoices concreteparser register action manual manual for abstractgroup in abstractoptions groups concretegroup concreteparser addargumentgroup titleabstractgroup name descriptionabstractgroup description if abstractgroup ismutuallyexclusive concretegroup concretegroup addmutuallyexclusivegrouprequiredfalse for abstractargument in abstractgroup arguments concretegroup addargument abstractargument aliases dropkeysmapqualifiers abstractargument configuration argparsequalifiermap argparseignorekeys return concreteparser jsondirectmirroroptions choices metavar jsonqualifiertooptions qualifiers optional isoptional true qualifiers zeroormore isoptional true isvariadic true qualifiers oneormore isoptional false isvariadic true qualifiers suppress def todataabstractoptions parserspec dictstr any return version parserspecversion spec abstractoptions serialize def parsertoparserspecparser argparse argumentparser kwargs parserspec run a bunch of post init hooks if there is a short help then create the longer version from it unpack the dynamically computed choices since we will need to store the actual values somewhere take an existing argparse parser and create a spec from it
import argparse
import textwrap
import typing
from dataclasses import dataclass, field
from enum import Enum, auto
from typing import Any, Optional, Dict, List, Tuple, Type, TypeVar

from httpie.cli.argparser import HTTPieArgumentParser
from httpie.cli.utils import Manual, LazyChoices


class Qualifiers(Enum):
    OPTIONAL = auto()
    ZERO_OR_MORE = auto()
    ONE_OR_MORE = auto()
    SUPPRESS = auto()


def map_qualifiers(
    configuration: Dict[str, Any], qualifier_map: Dict[Qualifiers, Any]
) -> Dict[str, Any]:
    return {
        key: qualifier_map[value] if isinstance(value, Qualifiers) else value
        for key, value in configuration.items()
    }


def drop_keys(
    configuration: Dict[str, Any], key_blacklist: Tuple[str, ...]
):
    return {
        key: value
        for key, value in configuration.items()
        if key not in key_blacklist
    }


PARSER_SPEC_VERSION = '0.0.1a0'


@dataclass
class ParserSpec:
    program: str
    description: Optional[str] = None
    epilog: Optional[str] = None
    groups: List['Group'] = field(default_factory=list)
    man_page_hint: Optional[str] = None
    source_file: Optional[str] = None

    def finalize(self) -> 'ParserSpec':
        if self.description:
            self.description = textwrap.dedent(self.description)
        if self.epilog:
            self.epilog = textwrap.dedent(self.epilog)
        for group in self.groups:
            group.finalize()
        return self

    def add_group(self, name: str, **kwargs) -> 'Group':
        group = Group(name, **kwargs)
        self.groups.append(group)
        return group

    def serialize(self) -> Dict[str, Any]:
        return {
            'name': self.program,
            'description': self.description,
            'groups': [group.serialize() for group in self.groups],
        }


@dataclass
class Group:
    name: str
    description: str = ''
    is_mutually_exclusive: bool = False
    arguments: List['Argument'] = field(default_factory=list)

    def finalize(self) -> None:
        if self.description:
            self.description = textwrap.dedent(self.description)

    def add_argument(self, *args, **kwargs):
        argument = Argument(list(args), kwargs.copy())
        argument.post_init()
        self.arguments.append(argument)
        return argument

    def serialize(self) -> Dict[str, Any]:
        return {
            'name': self.name,
            'description': self.description or None,
            'is_mutually_exclusive': self.is_mutually_exclusive,
            'args': [argument.serialize() for argument in self.arguments],
        }


class Argument(typing.NamedTuple):
    aliases: List[str]
    configuration: Dict[str, Any]

    def post_init(self):
        
        
        short_help = self.configuration.get('short_help')
        if (
            short_help
            and 'help' not in self.configuration
            and self.configuration.get('action') != 'lazy_choices'
        ):
            self.configuration['help'] = f'\n{short_help}\n\n'

    def serialize(self, *, isolation_mode: bool = False) -> Dict[str, Any]:
        configuration = self.configuration.copy()

        
        
        action = configuration.pop('action', None)
        short_help = configuration.pop('short_help', None)
        nested_options = configuration.pop('nested_options', None)

        if action == 'lazy_choices':
            choices = LazyChoices(
                self.aliases,
                **{'dest': None, **configuration},
                isolation_mode=isolation_mode
            )
            configuration['choices'] = list(choices.load())
            configuration['help'] = choices.help

        result = {}
        if self.aliases:
            result['options'] = self.aliases.copy()
        else:
            result['options'] = [configuration['metavar']]
            result['is_positional'] = True

        qualifiers = JSON_QUALIFIER_TO_OPTIONS[configuration.get('nargs', Qualifiers.SUPPRESS)]
        result.update(qualifiers)

        description = configuration.get('help')
        if description and description is not Qualifiers.SUPPRESS:
            result['short_description'] = short_help
            result['description'] = description

        if nested_options:
            result['nested_options'] = nested_options

        python_type = configuration.get('type')
        if python_type is not None:
            if hasattr(python_type, '__name__'):
                type_name = python_type.__name__
            else:
                type_name = type(python_type).__name__

            result['python_type_name'] = type_name

        result.update({
            key: value
            for key, value in configuration.items()
            if key in JSON_DIRECT_MIRROR_OPTIONS
            if value is not Qualifiers.SUPPRESS
        })

        return result

    @property
    def is_positional(self):
        return len(self.aliases) == 0

    @property
    def is_hidden(self):
        return self.configuration.get('help') is Qualifiers.SUPPRESS

    def __getattr__(self, attribute_name):
        if attribute_name in self.configuration:
            return self.configuration[attribute_name]
        else:
            raise AttributeError(attribute_name)


ParserType = TypeVar('ParserType', bound=Type[argparse.ArgumentParser])

ARGPARSE_QUALIFIER_MAP = {
    Qualifiers.OPTIONAL: argparse.OPTIONAL,
    Qualifiers.SUPPRESS: argparse.SUPPRESS,
    Qualifiers.ZERO_OR_MORE: argparse.ZERO_OR_MORE,
    Qualifiers.ONE_OR_MORE: argparse.ONE_OR_MORE
}
ARGPARSE_IGNORE_KEYS = ('short_help', 'nested_options')


def to_argparse(
    abstract_options: ParserSpec,
    parser_type: ParserType = HTTPieArgumentParser,
) -> ParserType:
    concrete_parser = parser_type(
        prog=abstract_options.program,
        description=abstract_options.description,
        epilog=abstract_options.epilog,
    )
    concrete_parser.spec = abstract_options
    concrete_parser.register('action', 'lazy_choices', LazyChoices)
    concrete_parser.register('action', 'manual', Manual)

    for abstract_group in abstract_options.groups:
        concrete_group = concrete_parser.add_argument_group(
            title=abstract_group.name, description=abstract_group.description
        )
        if abstract_group.is_mutually_exclusive:
            concrete_group = concrete_group.add_mutually_exclusive_group(required=False)

        for abstract_argument in abstract_group.arguments:
            concrete_group.add_argument(
                *abstract_argument.aliases,
                **drop_keys(map_qualifiers(
                    abstract_argument.configuration, ARGPARSE_QUALIFIER_MAP
                ), ARGPARSE_IGNORE_KEYS)
            )

    return concrete_parser


JSON_DIRECT_MIRROR_OPTIONS = (
    'choices',
    'metavar'
)


JSON_QUALIFIER_TO_OPTIONS = {
    Qualifiers.OPTIONAL: {'is_optional': True},
    Qualifiers.ZERO_OR_MORE: {'is_optional': True, 'is_variadic': True},
    Qualifiers.ONE_OR_MORE: {'is_optional': False, 'is_variadic': True},
    Qualifiers.SUPPRESS: {}
}


def to_data(abstract_options: ParserSpec) -> Dict[str, Any]:
    return {'version': PARSER_SPEC_VERSION, 'spec': abstract_options.serialize()}


def parser_to_parser_spec(parser: argparse.ArgumentParser, **kwargs) -> ParserSpec:
    
    return ParserSpec(
        program=parser.prog,
        description=parser.description,
        epilog=parser.epilog,
        **kwargs
    )