base keyvalue pair parsed from cli def initself key str value optionalstr sep str orig str self key key self value value self sep sep self orig orig def eqself other keyvaluearg return self dict other dict def reprself return reprself dict class sessionnamevalidator def initself errormessage str self errormessage errormessage def callself value str str session name can be a path or just a name if os path sep not in value and not validsessionnamepattern searchvalue raise argparse argumenterrornone self errormessage return value class escapedstr a keyvalue pair argument type used with argparse parses a keyvalue arg and constructs a keyvaluearg instance used for headers form data and other keyvalue pair types parse raw string arg and return self keyvalueclass instance the best of self separators is determined first found longest back slash escaped characters aren t considered as separators or parts thereof literal back slash characters have to be escaped as well r sorting by length ensures that the longest one will be chosen as it will overwrite any shorter ones starting at the same position in the found dictionary starting first longest separator found any preceding tokens are part of the key any following tokens are part of the value tokens characters iters for char in characters if char char nextcharacters if char not in self specialcharacters tokens1 char else tokens extendescapedchar else tokens1 char return tokens class promptmixin def promptpasswordself prompt str str prompttext f http prompt try return self getpassprompttext except eoferror keyboardinterrupt sys stderr write n sys exit0 staticmethod def getpassprompt to allow easy mocking return getpass getpassstrprompt class sslcredentialspromptmixin represents parsed credentials def haspasswordself bool return self value is not none def promptpasswordself host str none self value self promptpasswordf password for self keyhost class authcredentialsargtypekeyvalueargtype parse credentials from s username or username password no password provided will prompt for it later parse s and update defaults with the parsed values parseformatoptions defaults json indent 4 sortkeys true s json indent 2 json sortkeys false json indent 2 sortkeys false base key value pair parsed from cli session name can be a path or just a name represents an escaped character a key value pair argument type used with argparse parses a key value arg and constructs a keyvaluearg instance used for headers form data and other key value pair types parse raw string arg and return self key_value_class instance the best of self separators is determined first found longest back slash escaped characters aren t considered as separators or parts thereof literal back slash characters have to be escaped as well r sorting by length ensures that the longest one will be chosen as it will overwrite any shorter ones starting at the same position in the found dictionary starting first longest separator found any preceding tokens are part of the key any following tokens are part of the value tokenize the raw arg string there are only two token types strings and escaped characters keyvalueargtype tokenize r foo bar baz foo escaped bar baz to allow easy mocking represents the passphrase for the certificate s key represents parsed credentials a key value arg type that parses credentials parse credentials from s username or username password no password provided will prompt for it later parse s and update defaults with the parsed values parse_format_options defaults json indent 4 sort_keys true s json indent 2 json sort_keys false json indent 2 sort_keys false
import argparse
import getpass
import os
import sys
from copy import deepcopy
from typing import List, Optional, Union

from .constants import DEFAULT_FORMAT_OPTIONS, SEPARATOR_CREDENTIALS
from ..sessions import VALID_SESSION_NAME_PATTERN


class KeyValueArg:
    

    def __init__(self, key: str, value: Optional[str], sep: str, orig: str):
        self.key = key
        self.value = value
        self.sep = sep
        self.orig = orig

    def __eq__(self, other: 'KeyValueArg'):
        return self.__dict__ == other.__dict__

    def __repr__(self):
        return repr(self.__dict__)


class SessionNameValidator:

    def __init__(self, error_message: str):
        self.error_message = error_message

    def __call__(self, value: str) -> str:
        
        if (os.path.sep not in value
                and not VALID_SESSION_NAME_PATTERN.search(value)):
            raise argparse.ArgumentError(None, self.error_message)
        return value


class Escaped(str):
    

    def __repr__(self):
        return f"Escaped({repr(str(self))})"


class KeyValueArgType:
    

    key_value_class = KeyValueArg

    def __init__(self, *separators: str):
        self.separators = separators
        self.special_characters = set()
        for separator in separators:
            self.special_characters.update(separator)

    def __call__(self, s: str) -> KeyValueArg:
        
        tokens = self.tokenize(s)

        
        
        
        separators = sorted(self.separators, key=len)

        for i, token in enumerate(tokens):

            if isinstance(token, Escaped):
                continue

            found = {}
            for sep in separators:
                pos = token.find(sep)
                if pos != -1:
                    found[pos] = sep

            if found:
                
                sep = found[min(found.keys())]

                key, value = token.split(sep, 1)

                
                key = ''.join(tokens[:i]) + key

                
                value += ''.join(tokens[i + 1:])

                break

        else:
            raise argparse.ArgumentTypeError(f'{s!r} is not a valid value')

        return self.key_value_class(key=key, value=value, sep=sep, orig=s)

    def tokenize(self, s: str) -> List[Union[str, Escaped]]:
        r
        tokens = ['']
        characters = iter(s)
        for char in characters:
            if char == '\\':
                char = next(characters, '')
                if char not in self.special_characters:
                    tokens[-1] += '\\' + char
                else:
                    tokens.extend([Escaped(char), ''])
            else:
                tokens[-1] += char
        return tokens


class PromptMixin:
    def _prompt_password(self, prompt: str) -> str:
        prompt_text = f'http: {prompt}: '
        try:
            return self._getpass(prompt_text)
        except (EOFError, KeyboardInterrupt):
            sys.stderr.write('\n')
            sys.exit(0)

    @staticmethod
    def _getpass(prompt):
        
        return getpass.getpass(str(prompt))


class SSLCredentials(PromptMixin):
    

    def __init__(self, value: Optional[str]) -> None:
        self.value = value

    def prompt_password(self, key_file: str) -> None:
        self.value = self._prompt_password(f'passphrase for {key_file}')


class AuthCredentials(KeyValueArg, PromptMixin):
    

    def has_password(self) -> bool:
        return self.value is not None

    def prompt_password(self, host: str) -> None:
        self.value = self._prompt_password(f'password for {self.key}@{host}:')


class AuthCredentialsArgType(KeyValueArgType):
    

    key_value_class = AuthCredentials

    def __call__(self, s):
        
        try:
            return super().__call__(s)
        except argparse.ArgumentTypeError:
            
            return self.key_value_class(
                key=s,
                value=None,
                sep=SEPARATOR_CREDENTIALS,
                orig=s
            )


parse_auth = AuthCredentialsArgType(SEPARATOR_CREDENTIALS)


def readable_file_arg(filename):
    try:
        with open(filename, 'rb'):
            return filename
    except OSError as ex:
        raise argparse.ArgumentTypeError(f'{ex.filename}: {ex.strerror}')


def parse_format_options(s: str, defaults: Optional[dict]) -> dict:
    
    value_map = {
        'true': True,
        'false': False,
    }
    options = deepcopy(defaults or {})
    for option in s.split(','):
        try:
            path, value = option.lower().split(':')
            section, key = path.split('.')
        except ValueError:
            raise argparse.ArgumentTypeError(f'invalid option {option!r}')

        if value in value_map:
            parsed_value = value_map[value]
        else:
            if value.isnumeric():
                parsed_value = int(value)
            else:
                parsed_value = value

        if defaults is None:
            options.setdefault(section, {})
        else:
            try:
                default_value = defaults[section][key]
            except KeyError:
                raise argparse.ArgumentTypeError(
                    f'invalid key {path!r}')

            default_type, parsed_type = type(default_value), type(parsed_value)
            if parsed_type is not default_type:
                raise argparse.ArgumentTypeError(
                    'invalid value'
                    f' {value!r} in {option!r}'
                    f' (expected {default_type.__name__}'
                    f' got {parsed_type.__name__})'
                )

        options[section][key] = parsed_value

    return options


PARSED_DEFAULT_FORMAT_OPTIONS = parse_format_options(
    s=','.join(DEFAULT_FORMAT_OPTIONS),
    defaults=None,
)


def response_charset_type(encoding: str) -> str:
    try:
        ''.encode(encoding)
    except LookupError:
        raise argparse.ArgumentTypeError(
            f'{encoding!r} is not a supported encoding')
    return encoding


def response_mime_type(mime_type: str) -> str:
    if mime_type.count('/') != 1:
        raise argparse.ArgumentTypeError(
            f'{mime_type!r} doesn’t look like a mime type; use type/subtype')
    return mime_type