forked from extern/httpie-cli
190 lines
5.6 KiB
Python
190 lines
5.6 KiB
Python
import argparse
|
|
import textwrap
|
|
import typing
|
|
from dataclasses import dataclass, field
|
|
from enum import Enum, auto
|
|
from typing import Any, Optional, Dict, List, Type, TypeVar
|
|
|
|
from httpie.cli.argparser import HTTPieArgumentParser
|
|
from httpie.cli.utils import LazyChoices
|
|
|
|
|
|
class Qualifiers(Enum):
|
|
OPTIONAL = auto()
|
|
ZERO_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()
|
|
}
|
|
|
|
|
|
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)
|
|
|
|
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())
|
|
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 serialize(self) -> Dict[str, 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)
|
|
if action == 'lazy_choices':
|
|
choices = LazyChoices(self.aliases, **{'dest': None, **configuration})
|
|
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)
|
|
|
|
help_msg = configuration.get('help')
|
|
if help_msg and help_msg is not Qualifiers.SUPPRESS:
|
|
result['description'] = help_msg.strip()
|
|
|
|
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
|
|
})
|
|
|
|
return result
|
|
|
|
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,
|
|
}
|
|
|
|
|
|
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.register('action', 'lazy_choices', LazyChoices)
|
|
|
|
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,
|
|
**map_qualifiers(
|
|
abstract_argument.configuration, ARGPARSE_QUALIFIER_MAP
|
|
)
|
|
)
|
|
|
|
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.SUPPRESS: {}
|
|
}
|
|
|
|
|
|
def to_data(abstract_options: ParserSpec) -> Dict[str, Any]:
|
|
return {'version': PARSER_SPEC_VERSION, 'spec': abstract_options.serialize()}
|