<reponame>sdementen/oasapi<gh_stars>1-10
import json
from pathlib import Path
from typing import Callable, List, Dict, Tuple
from urllib.error import HTTPError, URLError
from urllib.request import urlopen

import click
import yaml
from attr import dataclass


@dataclass
class CliOasapiCommand:
    name: str
    command: Callable
    extra_options: List
    action_item: str
    description: str
    action_messages: Tuple[str, str]  # message in case of actions, no actions
    action_results: Tuple[int, int] = (0, 0)  # exit code in case of actions, no actions


def shorten_text(txt, before, after, placeholder="..."):
    """Shorten a text to max before+len(placeholder)+after chars.

    The new text will keep the before first characters and after last characters
    """
    # check if need to shorten
    if len(txt) <= before + len(placeholder) + after:
        # if not, return the original string
        return txt
    else:
        # otherwise, keep the beginning and end of sentence and insert the placeholder
        return txt[:before] + placeholder + txt[-after:]


@dataclass
class FileURL:
    url: str
    content: str

    @classmethod
    def open_url(cls, ctx, param, value):
        try:
            # try to open as if value is an URL
            fp = urlopen(value)
        except HTTPError as err:
            raise click.ClickException(
                f"Error when downloading {value} : {err.reason} ({err.code})"
            )
        except (URLError, ValueError):
            # it should be a file
            path = click.File()
            fp = path.convert(value=value, param=param, ctx=ctx)
            if value == "-":
                value = "[stdin]"

        # read the file
        content = fp.read()

        # convert to text if bytes assuming utf-8
        if isinstance(content, bytes):
            content = content.decode("utf-8")

        return FileURL(url=value, content=content)


@dataclass
class SwaggerFileURL(FileURL):
    swagger: Dict

    @classmethod
    def open_url(cls, ctx, param, value) -> "SwaggerFileURL":
        file_url = super().open_url(ctx, param, value)

        file_content = file_url.content
        if file_content.lstrip().startswith("{"):
            # this is a json file
            try:
                swagger = json.loads(file_content)
            except json.JSONDecodeError:
                swagger = None
        else:
            # this is a yaml file
            try:
                swagger = yaml.safe_load(file_content)
            except yaml.YAMLError:
                swagger = None

        if swagger is None:
            raise click.ClickException(
                f"Could not parse json/yaml swagger from '{file_url.url}' "
                f"with content {shorten_text(file_url.content, 15, 10)}"
            )

        return cls(swagger=swagger, url=file_url.url, content=file_url.content)


def validate_json_yaml_filename(ctx, param, value):
    """Validate the name of the file has the proper extension and add the extension to the file object"""
    if value is None:
        return value

    if not hasattr(value, "name") or value.name == "<stdout>":
        # stdin/stdout case
        value.extension = "yaml"
        return value

    ALLOWED_EXTENSIONS = ["json", "yaml", "yml"]
    extension = Path(value.name).suffix[1:]

    if extension not in ALLOWED_EXTENSIONS:
        raise click.BadParameter(
            f"the extension of the file is '{extension}' while it should be one of {ALLOWED_EXTENSIONS}"
        )
    value.extension = extension
    return value

import json
from pathlib import Path
from typing import Callable, List, Dict, Tuple
from urllib.error import HTTPError, URLError
from urllib.request import urlopen

import click
import yaml
from attr import dataclass


@dataclass
class CliOasapiCommand:
    name: str
    command: Callable
    extra_options: List
    action_item: str
    description: str
    action_messages: Tuple[str, str]  # message in case of actions, no actions
    action_results: Tuple[int, int] = (0, 0)  # exit code in case of actions, no actions


def shorten_text(txt, before, after, placeholder="..."):
    """Shorten a text to max before+len(placeholder)+after chars.

    The new text will keep the before first characters and after last characters
    """
    # check if need to shorten
    if len(txt) <= before + len(placeholder) + after:
        # if not, return the original string
        return txt
    else:
        # otherwise, keep the beginning and end of sentence and insert the placeholder
        return txt[:before] + placeholder + txt[-after:]


@dataclass
class FileURL:
    url: str
    content: str

    @classmethod
    def open_url(cls, ctx, param, value):
        try:
            # try to open as if value is an URL
            fp = urlopen(value)
        except HTTPError as err:
            raise click.ClickException(
                f"Error when downloading {value} : {err.reason} ({err.code})"
            )
        except (URLError, ValueError):
            # it should be a file
            path = click.File()
            fp = path.convert(value=value, param=param, ctx=ctx)
            if value == "-":
                value = "[stdin]"

        # read the file
        content = fp.read()

        # convert to text if bytes assuming utf-8
        if isinstance(content, bytes):
            content = content.decode("utf-8")

        return FileURL(url=value, content=content)


@dataclass
class SwaggerFileURL(FileURL):
    swagger: Dict

    @classmethod
    def open_url(cls, ctx, param, value) -> "SwaggerFileURL":
        file_url = super().open_url(ctx, param, value)

        file_content = file_url.content
        if file_content.lstrip().startswith("{"):
            # this is a json file
            try:
                swagger = json.loads(file_content)
            except json.JSONDecodeError:
                swagger = None
        else:
            # this is a yaml file
            try:
                swagger = yaml.safe_load(file_content)
            except yaml.YAMLError:
                swagger = None

        if swagger is None:
            raise click.ClickException(
                f"Could not parse json/yaml swagger from '{file_url.url}' "
                f"with content {shorten_text(file_url.content, 15, 10)}"
            )

        return cls(swagger=swagger, url=file_url.url, content=file_url.content)


def validate_json_yaml_filename(ctx, param, value):
    """Validate the name of the file has the proper extension and add the extension to the file object"""
    if value is None:
        return value

    if not hasattr(value, "name") or value.name == "<stdout>":
        # stdin/stdout case
        value.extension = "yaml"
        return value

    ALLOWED_EXTENSIONS = ["json", "yaml", "yml"]
    extension = Path(value.name).suffix[1:]

    if extension not in ALLOWED_EXTENSIONS:
        raise click.BadParameter(
            f"the extension of the file is '{extension}' while it should be one of {ALLOWED_EXTENSIONS}"
        )
    value.extension = extension
    return value
# message in case of actions, no actions # exit code in case of actions, no actions Shorten a text to max before+len(placeholder)+after chars.      The new text will keep the before first characters and after last characters # check if need to shorten # if not, return the original string # otherwise, keep the beginning and end of sentence and insert the placeholder # try to open as if value is an URL # it should be a file # read the file # convert to text if bytes assuming utf-8 # this is a json file # this is a yaml file Validate the name of the file has the proper extension and add the extension to the file object # stdin/stdout case