Input:
from datetime import tzinfo
from typing import Optional, Union
from ._common import _to_unicode
from ._logger import logger
from ._parser import PingParser  # noqa
from ._parser import (
    AlpineLinuxPingParser,
    LinuxPingParser,
    MacOsPingParser,
    NullPingParser,
    WindowsPingParser,
)
from ._pingtransmitter import PingResult
from ._stats import PingStats
from .error import ParseError, ParseErrorReason
import pyparsing as pp
import typepy
"""
.. codeauthor:: Tsuyoshi Hombashi <tsuyoshi.hombashi@gmail.com>
"""

class PingParsing:
    """
    Parser class to parsing ping command output.

    Args:
        timezone (Optional[tzinfo]):
            Time zone for parsing timestamps.
    """

    def __init__(self, timezone: Optional[tzinfo] = None) -> None:
        self.__parser: PingParser = NullPingParser()
        self.__timezone = timezone

    @property
    def parser_name(self) -> str:
        return self.__parser._parser_name

Context:
pingparsing/_parser.py
class WindowsPingParser(PingParser):
    @property
    def _parser_name(self) -> str:
        return "Windows"

    @property
    def _icmp_reply_pattern(self) -> str:
        return (
            " from "
            + self._DEST_PATTERN
            + rf":\s*bytes=(?P<{IcmpReplyKey.BYTES}>[0-9]+)"
            + self._TIME_PATTERN
            + "ms"
            + self._TTL_PATTERN
        )

    @property
    def _stats_headline_pattern(self) -> str:
        return rf"^Ping statistics for {self._DEST_PATTERN}"

    @property
    def _is_support_packet_duplicate(self) -> bool:
        return False

    def parse(self, ping_message: Sequence[str]) -> PingStats:
        icmp_replies = self._parse_icmp_reply(ping_message)
        stats_headline, packet_info_line, body_line_list = self._preprocess_parse_stats(
            lines=ping_message
        )
        packet_pattern = (
            pp.Literal("Packets: Sent = ")
            + pp.Word(pp.nums)
            + pp.Literal(", Received = ")
            + pp.Word(pp.nums)
        )

        destination = self._parse_destination(stats_headline)
        duplicates = self._parse_duplicate(packet_info_line)

        parse_list = packet_pattern.parseString(_to_unicode(packet_info_line))
        packet_transmit = int(parse_list[1])
        packet_receive = int(parse_list[3])

        is_valid_data = True
        try:
            rtt_line = body_line_list[2].strip()
        except IndexError:
            is_valid_data = False

        if not is_valid_data or typepy.is_null_string(rtt_line):
            return PingStats(
                destination=destination,
                packet_transmit=packet_transmit,
                packet_receive=packet_receive,
                duplicates=duplicates,
                icmp_replies=icmp_replies,
            )

        rtt_pattern = (
            pp.Literal("Minimum = ")
            + pp.Word(pp.nums)
            + pp.Literal("ms, Maximum = ")
            + pp.Word(pp.nums)
            + pp.Literal("ms, Average = ")
            + pp.Word(pp.nums)
        )
        parse_list = rtt_pattern.parseString(_to_unicode(rtt_line))

        return PingStats(
            destination=destination,
            packet_transmit=packet_transmit,
            packet_receive=packet_receive,
            duplicates=duplicates,
            rtt_min=float(parse_list[1]),
            rtt_avg=float(parse_list[5]),
            rtt_max=float(parse_list[3]),
            icmp_replies=icmp_replies,
        )
pingparsing/_pingtransmitter.py
class PingResult(namedtuple("PingResult", "stdout stderr returncode")):
    """
    Data class to store ``ping`` command execution result.

    .. py:attribute:: stdout
        :type: Optional[str]

        Standard output of ``ping`` command execution result.

    .. py:attribute:: stderr
        :type: Optional[str]

        Standard error of ``ping`` command execution result.

    .. py:attribute:: returncode
        :type: int

        Return code of ``ping`` command execution result.
    """
pingparsing/_parser.py
class MacOsPingParser(PingParser):
    @property
    def _parser_name(self) -> str:
        return "macOS"

    @property
    def _icmp_reply_pattern(self) -> str:
        return (
            self._BYTES_PATTERN
            + r"\s+from "
            + self._DEST_PATTERN
            + ":"
            + self._ICMP_SEQ_PATTERN
            + self._TTL_PATTERN
            + self._TIME_PATTERN
        )

    @property
    def _stats_headline_pattern(self) -> str:
        return rf"--- {self._DEST_PATTERN} ping statistics ---"

    @property
    def _is_support_packet_duplicate(self) -> bool:
        return True

    def parse(self, ping_message: Sequence[str]) -> PingStats:
        icmp_replies = self._parse_icmp_reply(ping_message)
        stats_headline, packet_info_line, body_line_list = self._preprocess_parse_stats(
            lines=ping_message
        )
        packet_pattern = (
            pp.Word(pp.nums)
            + pp.Literal("packets transmitted,")
            + pp.Word(pp.nums)
            + pp.Literal("packets received,")
        )

        destination = self._parse_destination(stats_headline)
        duplicates = self._parse_duplicate(packet_info_line)

        parse_list = packet_pattern.parseString(_to_unicode(packet_info_line))
        packet_transmit = int(parse_list[0])
        packet_receive = int(parse_list[2])

        is_valid_data = True
        try:
            rtt_line = body_line_list[1]
        except IndexError:
            is_valid_data = False

        if not is_valid_data or typepy.is_null_string(rtt_line):
            return PingStats(
                destination=destination,
                packet_transmit=packet_transmit,
                packet_receive=packet_receive,
                duplicates=duplicates,
                icmp_replies=icmp_replies,
            )

        rtt_pattern = (
            pp.Literal("round-trip min/avg/max/stddev =")
            + pp.Word(pp.nums + ".")
            + "/"
            + pp.Word(pp.nums + ".")
            + "/"
            + pp.Word(pp.nums + ".")
            + "/"
            + pp.Word(pp.nums + ".")
            + pp.Word(pp.nums + "ms")
        )
        parse_list = rtt_pattern.parseString(_to_unicode(rtt_line))

        return PingStats(
            destination=destination,
            packet_transmit=packet_transmit,
            packet_receive=packet_receive,
            duplicates=duplicates,
            rtt_min=float(parse_list[1]),
            rtt_avg=float(parse_list[3]),
            rtt_max=float(parse_list[5]),
            rtt_mdev=float(parse_list[7]),
            icmp_replies=icmp_replies,
        )
pingparsing/_parser.py
class PingParser(PingParserInterface):

    _BYTES_PATTERN = rf"\s*(?P<{IcmpReplyKey.BYTES}>[0-9]+) bytes"
    _DEST_PATTERN = r"(?P<{key}>[a-zA-Z0-9:\-\.\(\)% ]+)".format(
        key=IcmpReplyKey.DESTINATION
    )  # host or ipv4/ipv6 addr
    _IPADDR_PATTERN = r"(\d{1,3}\.){3}\d{1,3}"
    _ICMP_SEQ_PATTERN = rf"\s*icmp_seq=(?P<{IcmpReplyKey.SEQUENCE_NO}>\d+)"
    _TTL_PATTERN = rf"\s*ttl=(?P<{IcmpReplyKey.TTL}>\d+)"
    _TIME_PATTERN = rf"\s*time[=<](?P<{IcmpReplyKey.TIME}>[0-9\.]+)"

    def __init__(self, timezone: Optional[tzinfo] = None) -> None:
        self.__timezone = timezone

    @abc.abstractproperty
    def _parser_name(self) -> str:  # pragma: no cover
        pass

    @abc.abstractproperty
    def _icmp_reply_pattern(self) -> str:  # pragma: no cover
        pass

    @property
    def _icmp_no_ans_pattern(self) -> str:
        return "(?!x)x"  # never matching anything

    @property
    def _duplicate_packet_pattern(self) -> str:
        return r".+ \(DUP!\)$"

    @abc.abstractproperty
    def _stats_headline_pattern(self) -> str:  # pragma: no cover
        pass

    @abc.abstractproperty
    def _is_support_packet_duplicate(self) -> bool:  # pragma: no cover
        pass

    def _parse_icmp_reply(self, ping_lines: Sequence[str]) -> IcmpReplies:
        icmp_reply_regexp = re.compile(self._icmp_reply_pattern, re.IGNORECASE)
        icmp_no_ans_regexp = re.compile(self._icmp_no_ans_pattern, re.IGNORECASE)
        duplicate_packet_regexp = re.compile(self._duplicate_packet_pattern)
        icmp_reply_list = []

        for line in ping_lines:
            match = icmp_reply_regexp.search(line)
            if not match:
                match = icmp_no_ans_regexp.search(line)
            if not match:
                continue

            results = match.groupdict()
            reply: Dict[str, Union[str, bool, float, int, datetime]] = {}

            if IcmpReplyKey.DESTINATION in results:
                reply[IcmpReplyKey.DESTINATION] = results[IcmpReplyKey.DESTINATION]

            if IcmpReplyKey.BYTES in results:
                reply[IcmpReplyKey.BYTES] = int(results[IcmpReplyKey.BYTES])

            if results.get(IcmpReplyKey.TIMESTAMP):
                reply[IcmpReplyKey.TIMESTAMP] = self.__timestamp_to_datetime(
                    results[IcmpReplyKey.TIMESTAMP]
                )
            elif results.get(IcmpReplyKey.TIMESTAMP_NO_ANS):
                reply[IcmpReplyKey.TIMESTAMP] = self.__timestamp_to_datetime(
                    results[IcmpReplyKey.TIMESTAMP_NO_ANS]
                )

            if IcmpReplyKey.SEQUENCE_NO in results:
                reply[IcmpReplyKey.SEQUENCE_NO] = int(results[IcmpReplyKey.SEQUENCE_NO])

            if IcmpReplyKey.TTL in results:
                reply[IcmpReplyKey.TTL] = int(results[IcmpReplyKey.TTL])

            if IcmpReplyKey.TIME in results:
                reply[IcmpReplyKey.TIME] = float(results[IcmpReplyKey.TIME])

            if duplicate_packet_regexp.search(line):
                reply[IcmpReplyKey.DUPLICATE] = True
            else:
                reply[IcmpReplyKey.DUPLICATE] = False

            icmp_reply_list.append(reply)

        return icmp_reply_list

    def _preprocess_parse_stats(self, lines: Sequence[str]) -> Tuple[str, str, Sequence[str]]:
        logger.debug(f"parsing as {self._parser_name:s} ping result format")

        stats_headline_idx = self.__find_stats_headline_idx(
            lines, re.compile(self._stats_headline_pattern)
        )
        body_line_list = lines[stats_headline_idx + 1 :]
        self.__validate_stats_body(body_line_list)

        packet_info_line = body_line_list[0]

        return (lines[stats_headline_idx], packet_info_line, body_line_list)

    def _parse_destination(self, stats_headline: str) -> str:
        match = re.search(self._stats_headline_pattern, stats_headline)
        if not match:
            return "unknown"

        return match.groupdict()[IcmpReplyKey.DESTINATION].strip(":")

    def __find_stats_headline_idx(self, lines: Sequence[str], re_stats_header: Pattern) -> int:
        for i, line in enumerate(lines):
            if re_stats_header.search(line):
                break
        else:
            raise ParseError(reason=ParseErrorReason.HEADER_NOT_FOUND)

        return i

    def __timestamp_to_datetime(self, timestamp: str) -> datetime:
        return DateTime(timestamp.lstrip("[").rstrip("]"), timezone=self.__timezone).force_convert()

    def __validate_stats_body(self, body_line_list: Sequence[str]) -> None:
        if typepy.is_empty_sequence(body_line_list):
            raise ParseError(reason=ParseErrorReason.EMPTY_STATISTICS)

    def _parse_duplicate(self, line: str) -> Optional[int]:
        if not self._is_support_packet_duplicate:
            return None

        packet_pattern = (
            pp.SkipTo(pp.Word("+" + pp.nums) + pp.Literal("duplicates,"))
            + pp.Word("+" + pp.nums)
            + pp.Literal("duplicates,")
        )

        try:
            duplicate_parse_list = packet_pattern.parseString(_to_unicode(line))
        except pp.ParseException:
            return 0

        return int(duplicate_parse_list[-2].strip("+"))
pingparsing/_stats.py
class PingStats:
    def __init__(self, *args, **kwargs) -> None:
        self.__destination = kwargs.pop("destination", None)
        self.__packet_transmit = kwargs.pop("packet_transmit", None)
        self.__packet_receive = kwargs.pop("packet_receive", None)
        self.__rtt_min = kwargs.pop("rtt_min", None)
        self.__rtt_avg = kwargs.pop("rtt_avg", None)
        self.__rtt_max = kwargs.pop("rtt_max", None)
        self.__rtt_mdev = kwargs.pop("rtt_mdev", None)
        self.__duplicates = kwargs.pop("duplicates", None)

        self.__icmp_replies = kwargs.pop("icmp_replies", [])

    @property
    def destination(self) -> str:
        """
        The ping destination.

        Returns:
            |str|:
        """

        return self.__destination

    @property
    def packet_transmit(self) -> Optional[int]:
        """
        Number of packets transmitted.

        Returns:
            |int|:
        """

        return self.__packet_transmit

    @property
    def packet_receive(self) -> Optional[int]:
        """
        Number of packets received.

        Returns:
            |int|:
        """

        return self.__packet_receive

    @property
    def packet_loss_count(self) -> Optional[int]:
        """
        Number of packet losses.

        Returns:
            |int|: |None| if the value is not a number.
        """

        try:
            return cast(int, self.packet_transmit) - cast(int, self.packet_receive)
        except TypeError:
            return None

    @property
    def packet_loss_rate(self) -> Optional[float]:
        """
        Percentage of packet loss |percent_unit|.

        Returns:
            |float|: |None| if the value is not a number.
        """

        try:
            return (cast(int, self.packet_loss_count) / cast(int, self.packet_transmit)) * 100
        except (TypeError, ZeroDivisionError, OverflowError):
            return None

    @property
    def rtt_min(self) -> Optional[float]:
        """
        Minimum round trip time of transmitted ICMP packets |msec_unit|.

        Returns:
            |float|:
        """

        return self.__rtt_min

    @property
    def rtt_avg(self) -> Optional[float]:
        """
        Average round trip time of transmitted ICMP packets |msec_unit|.

        Returns:
            |float|:
        """

        return self.__rtt_avg

    @property
    def rtt_max(self) -> Optional[float]:
        """
        Maximum round trip time of transmitted ICMP packets |msec_unit|.

        Returns:
            |float|:
        """

        return self.__rtt_max

    @property
    def rtt_mdev(self) -> Optional[float]:
        """
        Standard deviation of transmitted ICMP packets.

        Returns:
            |float|: |None| when parsing Windows ping result.
        """

        return self.__rtt_mdev

    @property
    def packet_duplicate_count(self) -> Optional[int]:
        """
        Number of duplicated packets.

        Returns:
            |int|: |None| when parsing Windows ping result.
        """

        return self.__duplicates

    @property
    def packet_duplicate_rate(self) -> Optional[float]:
        """
        Percentage of duplicated packets |percent_unit|.

        Returns:
            |float|: |None| if the value is not a number.
        """

        try:
            return (cast(int, self.packet_duplicate_count) / cast(int, self.packet_receive)) * 100
        except (TypeError, ZeroDivisionError, OverflowError):
            return None

    @property
    def icmp_replies(self) -> IcmpReplies:
        """
        ICMP packet reply information.

            .. note:
                ``time<1ms`` considered as ``time=1``

        Returns:
            |list| of |dict|:
        """

        return self.__icmp_replies

    def is_empty(self):
        return all(
            [
                self.destination is None,
                self.packet_transmit is None,
                self.packet_receive is None,
                self.packet_loss_count is None,
                self.packet_loss_rate is None,
                self.packet_duplicate_count is None,
                self.packet_duplicate_rate is None,
                self.rtt_min is None,
                self.rtt_avg is None,
                self.rtt_max is None,
                self.rtt_mdev is None,
                not self.icmp_replies,
            ]
        )

    def as_dict(
        self, include_icmp_replies: bool = False
    ) -> Dict[str, Union[str, int, float, IcmpReplies, None]]:
        """
        ping statistics.

        Returns:
            |dict|:

        Examples:
            >>> import pingparsing
            >>> parser = pingparsing.PingParsing()
            >>> parser.parse(ping_result)
            >>> parser.as_dict()
            {
                "destination": "google.com",
                "packet_transmit": 60,
                "packet_receive": 60,
                "packet_loss_rate": 0.0,
                "packet_loss_count": 0,
                "rtt_min": 61.425,
                "rtt_avg": 99.731,
                "rtt_max": 212.597,
                "rtt_mdev": 27.566,
                "packet_duplicate_rate": 0.0,
                "packet_duplicate_count": 0
            }
        """

        d: Dict[str, Union[str, int, float, IcmpReplies, None]] = {
            "destination": self.destination,
            "packet_transmit": self.packet_transmit,
            "packet_receive": self.packet_receive,
            "packet_loss_count": self.packet_loss_count,
            "packet_loss_rate": self.packet_loss_rate,
            "rtt_min": self.rtt_min,
            "rtt_avg": self.rtt_avg,
            "rtt_max": self.rtt_max,
            "rtt_mdev": self.rtt_mdev,
            "packet_duplicate_count": self.packet_duplicate_count,
            "packet_duplicate_rate": self.packet_duplicate_rate,
        }
        if include_icmp_replies:
            d["icmp_replies"] = self.icmp_replies

        return d

    def as_tuple(self) -> Tuple:
        """
        ping statistics.

        Returns:
            |namedtuple|:

        Examples:
            >>> import pingparsing
            >>> parser = pingparsing.PingParsing()
            >>> parser.parse(ping_result)
            >>> parser.as_tuple()
            PingResult(destination='google.com', packet_transmit=60, packet_receive=60, packet_loss_rate=0.0, packet_loss_count=0, rtt_min=61.425, rtt_avg=99.731, rtt_max=212.597, rtt_mdev=27.566, packet_duplicate_rate=0.0, packet_duplicate_count=0)
        """  # noqa

        from collections import namedtuple

        ping_result = self.as_dict()

        return namedtuple("PingStatsTuple", ping_result.keys())(**ping_result)  # type: ignore
pingparsing/error.py
class ParseErrorReason(enum.Enum):
    HEADER_NOT_FOUND = "ping statistics not found"
    EMPTY_STATISTICS = "ping statistics is empty"
pingparsing/_parser.py
class AlpineLinuxPingParser(LinuxPingParser):
    @property
    def _parser_name(self) -> str:
        return "AlpineLinux"

    @property
    def _icmp_reply_pattern(self) -> str:
        return (
            self._BYTES_PATTERN
            + r"\s+from "
            + self._DEST_PATTERN
            + ": "
            + rf"seq=(?P<{IcmpReplyKey.SEQUENCE_NO}>\d+) "
            + self._TTL_PATTERN
            + self._TIME_PATTERN
        )

    @property
    def _is_support_packet_duplicate(self) -> bool:
        return True

    def parse(self, ping_message: Sequence[str]) -> PingStats:
        icmp_replies = self._parse_icmp_reply(ping_message)
        stats_headline, packet_info_line, body_line_list = self._preprocess_parse_stats(
            lines=ping_message
        )
        packet_pattern = (
            pp.Word(pp.nums)
            + pp.Literal("packets transmitted,")
            + pp.Word(pp.nums)
            + pp.Literal("packets received,")
        )

        destination = self._parse_destination(stats_headline)
        duplicates = self._parse_duplicate(packet_info_line)

        parse_list = packet_pattern.parseString(_to_unicode(packet_info_line))
        packet_transmit = int(parse_list[0])
        packet_receive = int(parse_list[2])

        is_valid_data = True
        try:
            rtt_line = body_line_list[1]
        except IndexError:
            is_valid_data = False

        if not is_valid_data or typepy.is_null_string(rtt_line):
            return PingStats(
                destination=destination,
                packet_transmit=packet_transmit,
                packet_receive=packet_receive,
                duplicates=duplicates,
                icmp_replies=icmp_replies,
            )

        rtt_pattern = (
            pp.Literal("round-trip min/avg/max =")
            + pp.Word(pp.nums + ".")
            + "/"
            + pp.Word(pp.nums + ".")
            + "/"
            + pp.Word(pp.nums + ".")
            + pp.Word(pp.nums + "ms")
        )
        parse_list = rtt_pattern.parseString(_to_unicode(rtt_line))

        return PingStats(
            destination=destination,
            packet_transmit=packet_transmit,
            packet_receive=packet_receive,
            duplicates=duplicates,
            rtt_min=float(parse_list[1]),
            rtt_avg=float(parse_list[3]),
            rtt_max=float(parse_list[5]),
            icmp_replies=icmp_replies,
        )

    def _parse_duplicate(self, line: str) -> int:
        packet_pattern = (
            pp.SkipTo(pp.Word(pp.nums) + pp.Literal("duplicates,"))
            + pp.Word(pp.nums)
            + pp.Literal("duplicates,")
        )
        try:
            duplicate_parse_list = packet_pattern.parseString(_to_unicode(line))
        except pp.ParseException:
            return 0

        return int(duplicate_parse_list[-2])
pingparsing/_logger.py
MODULE_NAME = "pingparsing"
class NullLogger:
    def remove(self, handler_id=None):  # pragma: no cover
    def add(self, sink, **kwargs):  # pragma: no cover
    def disable(self, name):  # pragma: no cover
    def enable(self, name):  # pragma: no cover
    def critical(self, __message, *args, **kwargs):  # pragma: no cover
    def debug(self, __message, *args, **kwargs):  # pragma: no cover
    def error(self, __message, *args, **kwargs):  # pragma: no cover
    def exception(self, __message, *args, **kwargs):  # pragma: no cover
    def info(self, __message, *args, **kwargs):  # pragma: no cover
    def log(self, __level, __message, *args, **kwargs):  # pragma: no cover
    def success(self, __message, *args, **kwargs):  # pragma: no cover
    def trace(self, __message, *args, **kwargs):  # pragma: no cover
    def warning(self, __message, *args, **kwargs):  # pragma: no cover
def set_logger(is_enable: bool, propagation_depth: int = 1) -> None:
def set_log_level(log_level):
pingparsing/_common.py
def _to_unicode(text: Union[str, bytes]) -> str:
    try:
        return text.decode("ascii")  # type: ignore
    except AttributeError:
        return cast(str, text)
pingparsing/error.py
class ParseError(Exception):
    """
    Exception raised when failed to parse ping results.
    """

    @property
    def reason(self) -> Union[str]:
        return self.__reason

    def __init__(self, *args, **kwargs):
        self.__reason = kwargs.pop("reason", None)

        super().__init__(*args, **kwargs)
pingparsing/_parser.py
class LinuxPingParser(PingParser):
    @property
    def _parser_name(self) -> str:
        return "Linux"

    _TIMESTAMP_PATTERN = rf"(?P<{IcmpReplyKey.TIMESTAMP}>\[[0-9\.]+\])"
    _NO_ANS_TIMESTAMP_PATTERN = rf"(?P<{IcmpReplyKey.TIMESTAMP_NO_ANS}>\[[0-9\.]+\])"

    @property
    def _icmp_no_ans_pattern(self) -> str:
        return self._NO_ANS_TIMESTAMP_PATTERN + " no answer yet for " + self._ICMP_SEQ_PATTERN

    @property
    def _icmp_reply_pattern(self) -> str:
        return (
            self._TIMESTAMP_PATTERN
            + "?"
            + self._BYTES_PATTERN
            + r"\s+from "
            + self._DEST_PATTERN
            + ":"
            + self._ICMP_SEQ_PATTERN
            + self._TTL_PATTERN
            + self._TIME_PATTERN
        )

    @property
    def _stats_headline_pattern(self) -> str:
        return rf"--- {self._DEST_PATTERN} ping statistics ---"

    @property
    def _is_support_packet_duplicate(self) -> bool:
        return True

    def parse(self, ping_message: Sequence[str]) -> PingStats:
        icmp_replies = self._parse_icmp_reply(ping_message)
        stats_headline, packet_info_line, body_line_list = self._preprocess_parse_stats(
            lines=ping_message
        )
        packet_pattern = (
            pp.Word(pp.nums)
            + pp.Literal("packets transmitted,")
            + pp.Word(pp.nums)
            + pp.Literal("received,")
        )

        destination = self._parse_destination(stats_headline)
        duplicates = self._parse_duplicate(packet_info_line)

        parse_list = packet_pattern.parseString(_to_unicode(packet_info_line))
        packet_transmit = int(parse_list[0])
        packet_receive = int(parse_list[2])

        is_valid_data = True
        try:
            rtt_line = body_line_list[1]
        except IndexError:
            is_valid_data = False

        if not is_valid_data or typepy.is_null_string(rtt_line):
            return PingStats(
                destination=destination,
                packet_transmit=packet_transmit,
                packet_receive=packet_receive,
                duplicates=duplicates,
                icmp_replies=icmp_replies,
            )

        rtt_pattern = (
            pp.Literal("rtt min/avg/max/mdev =")
            + pp.Word(pp.nums + ".")
            + "/"
            + pp.Word(pp.nums + ".")
            + "/"
            + pp.Word(pp.nums + ".")
            + "/"
            + pp.Word(pp.nums + ".")
            + pp.Word(pp.nums + "ms")
        )
        try:
            parse_list = rtt_pattern.parseString(_to_unicode(rtt_line))
        except pp.ParseException:
            if not re.search(r"\s*pipe \d+", rtt_line):
                raise ValueError

            return PingStats(
                destination=destination,
                packet_transmit=packet_transmit,
                packet_receive=packet_receive,
                duplicates=duplicates,
                icmp_replies=icmp_replies,
            )

        return PingStats(
            destination=destination,
            packet_transmit=packet_transmit,
            packet_receive=packet_receive,
            duplicates=duplicates,
            rtt_min=float(parse_list[1]),
            rtt_avg=float(parse_list[3]),
            rtt_max=float(parse_list[5]),
            rtt_mdev=float(parse_list[7]),
            icmp_replies=icmp_replies,
        )
pingparsing/_parser.py
class NullPingParser(PingParser):
    @property
    def _parser_name(self) -> str:
        return "null"

    @property
    def _icmp_reply_pattern(self) -> str:
        return ""

    @property
    def _stats_headline_pattern(self) -> str:
        return ""

    @property
    def _is_support_packet_duplicate(self) -> bool:  # pragma: no cover
        return False

    def parse(self, ping_message: Sequence[str]) -> PingStats:  # pragma: no cover
        return PingStats()

    def _preprocess_parse_stats(
        self, lines: Sequence[str]
    ) -> Tuple[str, str, List[str]]:  # pragma: no cover
        return ("", "", [])

Answer: def parse(self, ping_message: Union[str, PingResult]) -> PingStats:


Input:
import logging
from twisted.web import server
from twisted.internet.task import LoopingCall
from twisted.web.wsgi import WSGIResource
from flask import Flask, render_template
from flask_restful import Api
from huginn.protocols import ControlsProtocol, SimulatorDataProtocol
from huginn.http import (SimulatorDataWebSocketFactory,
                         SimulatorDataWebSocketProtocol)
from huginn.rest import (FDMResource, AircraftResource, GPSResource,
                         AccelerometerResource, GyroscopeResource,
                         ThermometerResource, PressureSensorResource,
                         PitotTubeResource, InertialNavigationSystemResource,
                         EngineResource, FlightControlsResource,
                         SimulatorControlResource, AccelerationsResource,
                         VelocitiesResource, OrientationResource,
                         AtmosphereResource, ForcesResource,
                         InitialConditionResource, PositionResource,
                         AirspeedIndicatorResource, AltimeterResource,
                         AttitudeIndicatorResource, HeadingIndicatorResource,
                         VerticalSpeedIndicatorResource, WaypointResource,
                         WaypointsResource)

    reactor.listenUDP(port, controls_protocol)

def initialize_simulator_data_server(reactor, simulator, clients):
    """Initialize the simulator data server

    Arguments:
    reactor: a Twisted reactor to use
    simulator: a Simulator object
    clients: a list of (address, port) tuples of the listening clients
    """
    for address, port, update_rate in clients:
        logger.debug("Sending fdm data to %s:%d every %f seconds",
                     address, port, update_rate)

        simulator_data_protocol = SimulatorDataProtocol(simulator,
                                                        address,
                                                        port)

        reactor.listenUDP(0, simulator_data_protocol)

        simulator_data_updater = LoopingCall(
            simulator_data_protocol.send_simulator_data)

        simulator_data_updater.start(update_rate)

def initialize_websocket_server(reactor, simulator, host, port, database):
    """Initialize the web socket server

    Arguments:
    reactor: a twisted reactor object
    simulator: an Simulator object
    host: the server host
    port: the port to listen to
    database: the database that contains the simulator data
    """
    logger.debug("The websocket interface runs on %s:%d", host, port)

    factory = SimulatorDataWebSocketFactory(
        simulator,
        database,
        "ws://%s:%d" % (host, port),
    )

    factory.protocol = SimulatorDataWebSocketProtocol

    reactor.listenTCP(port, factory)

def _add_fdm_resources(api, fdm, aircraft):
    api.add_resource(FDMResource, "/fdm",
                     resource_class_args=(fdm, aircraft))

    api.add_resource(AccelerationsResource, "/fdm/accelerations",
                     resource_class_args=(fdm.fdmexec,))

    api.add_resource(VelocitiesResource, "/fdm/velocities",
                     resource_class_args=(fdm.fdmexec,))

    api.add_resource(OrientationResource, "/fdm/orientation",
                     resource_class_args=(fdm.fdmexec,))

    api.add_resource(AtmosphereResource, "/fdm/atmosphere",
                     resource_class_args=(fdm.fdmexec,))

    api.add_resource(ForcesResource, "/fdm/forces",
                     resource_class_args=(fdm.fdmexec,))

    api.add_resource(InitialConditionResource, "/fdm/initial_condition",
                     resource_class_args=(fdm.fdmexec,))

    api.add_resource(PositionResource, "/fdm/position",
                     resource_class_args=(fdm.fdmexec,))

def _add_instrument_resources(api, instruments):
    api.add_resource(GPSResource, "/aircraft/instruments/gps",
                     resource_class_args=(instruments.gps,))

    api.add_resource(
        AirspeedIndicatorResource,
        "/aircraft/instruments/airspeed_indicator",
        resource_class_args=(instruments.airspeed_indicator,)
    )

    api.add_resource(
        AltimeterResource,
        "/aircraft/instruments/altimeter",
        resource_class_args=(instruments.altimeter,)
    )

    api.add_resource(
        AttitudeIndicatorResource,
        "/aircraft/instruments/attitude_indicator",
        resource_class_args=(instruments.attitude_indicator,)
    )

    api.add_resource(
        HeadingIndicatorResource,
        "/aircraft/instruments/heading_indicator",
        resource_class_args=(instruments.heading_indicator,)
    )

    api.add_resource(
        VerticalSpeedIndicatorResource,
        "/aircraft/instruments/vertical_speed_indicator",
        resource_class_args=(instruments.vertical_speed_indicator,)
    )

def _add_sensor_resources(api, sensors):
    api.add_resource(
        AccelerometerResource,
        "/aircraft/sensors/accelerometer",
        resource_class_args=(sensors.accelerometer,)
    )

    api.add_resource(

Context:
huginn/rest.py
class InitialConditionResource(ObjectResource):
    """The InitialConditionResource object contain the simulator initial
    conditions"""

    def __init__(self, fdmexec):
        """Create a new InitialConditionResource object

        Arguments:
        fdmexec: a jsbsim FGFDMExec object
        """
        self.fdmexec = fdmexec
        self.initial_condition = InitialCondition(fdmexec)
        self.initial_condition_schema = InitialConditionSchema()

        super(InitialConditionResource, self).__init__(
            self.initial_condition,
            self.initial_condition_schema
        )

    def update_initial_conditions(self, initial_condition):
        """Update the simulator initial conditions

        Arguments:
        initial_conditions: a dictionary with the initial conditions of the
            simulator
        """
        if "latitude" in initial_condition:
            self.initial_condition.latitude = initial_condition["latitude"]

        if "longitude" in initial_condition:
            self.initial_condition.longitude = initial_condition["longitude"]

        if "airspeed" in initial_condition:
            self.initial_condition.airspeed = initial_condition["airspeed"]

        if "altitude" in initial_condition:
            self.initial_condition.altitude = initial_condition["altitude"]

        if "heading" in initial_condition:
            self.initial_condition.heading = initial_condition["heading"]

    def post(self):
        """Update the simulator condition when a POST request is executed"""
        data = request.json

        ic_schema = InitialConditionSchema()

        initial_condition = ic_schema.load(data)

        self.update_initial_conditions(initial_condition.data)

        return {"result": "ok"}
huginn/rest.py
class ThermometerResource(Resource):
    """The ThermometerResource class contains the measurements from the
    temperature sensor"""

    def __init__(self, thermometer):
        """Create a new ThermometerResource object

        Arguments:
        thermometer: a Thermometer object
        """
        self.thermometer = thermometer

    def get(self):
        """Returns the thermometer measurements"""
        thermometer_data = {
            "temperature": self.thermometer.temperature,
        }

        return thermometer_data
huginn/rest.py
class AccelerationsResource(ObjectResource):
    """The AccelerationsResource object returns the fdm accelerations"""

    def __init__(self, fdmexec):
        """Create a new AccelerationsResource object

        Arguments:
        fdmexec: a jsbsim FGFDMExec object
        """
        self.fdmexec = fdmexec
        self.accelerations = Accelerations(self.fdmexec)
        self.acceleration_schema = AccelerationsSchema()

        super(AccelerationsResource, self).__init__(self.accelerations,
                                                    self.acceleration_schema)
huginn/rest.py
class GPSResource(Resource):
    """The GPSResource returns the gps data"""

    def __init__(self, gps):
        """Create a new GPSResource object

        Arguments:
        gps: A GPS object
        """
        self.gps = gps

    def get(self):
        """Return the gps data"""
        gps_data = {
            "latitude": self.gps.latitude,
            "longitude": self.gps.longitude,
            "altitude": self.gps.altitude,
            "airspeed": self.gps.airspeed,
            "heading": self.gps.heading
        }

        return gps_data
huginn/rest.py
class HeadingIndicatorResource(ObjectResource):
    """The HeadingIndicatorResource object returns the aircraft's heading
    indicator data"""

    def __init__(self, heading_indicator):
        """Create a new HeadingIndicatorResource object

        Arguments:
        heading_indicator: a HeadingIndicator object
        """
        self.heading_indicator = heading_indicator
        self.heading_indicator_schema = HeadingIndicatorSchema()

        super(HeadingIndicatorResource, self).__init__(
            self.heading_indicator,
            self.heading_indicator_schema
        )
huginn/rest.py
class PitotTubeResource(Resource):
    """The PitotTubeResource returns the measurements from the pitot tube"""

    def __init__(self, pitot_tube):
        """Create a new PitotTubeResource object

        Arguments:
        pitot_tube: A PitotTube object
        """
        self.pitot_tube = pitot_tube

    def get(self):
        """Returns the pitot tube measurements"""
        pitot_tube_data = {
            "total_pressure": self.pitot_tube.pressure,
        }

        return pitot_tube_data
huginn/rest.py
class VelocitiesResource(ObjectResource):
    """The VelocitiesResource object returns the fdm velocities"""

    def __init__(self, fdmexec):
        """Create a new VelocitiesResource object

        Arguments:
        fdmexec: a jsbsim FGFDMExec object
        """
        self.fdmexec = fdmexec
        self.velocities = Velocities(self.fdmexec)
        self.velocities_schema = VelocitiesSchema()

        super(VelocitiesResource, self).__init__(self.velocities,
                                                 self.velocities_schema)
huginn/rest.py
class AirspeedIndicatorResource(ObjectResource):
    """The AirspeedIndicatorResource object returns the aircraft's true
    airspeed"""

    def __init__(self, airspeed_indicator):
        """Create a new AirspeedindicatorResource object

        Arguments:
        airspeed_indicator: an AirspeedIndicator object
        """
        self.airspeed_indicator = airspeed_indicator
        self.airspeed_indicator_schema = AirspeedIndicatorSchema()

        super(AirspeedIndicatorResource, self).__init__(
            self.airspeed_indicator,
            self.airspeed_indicator_schema
        )
huginn/protocols.py
class SimulatorDataProtocol(DatagramProtocol):
    """The FDMDataProtocol class is used to transmit the flight dynamics model
    data to the client"""
    def __init__(self, simulator, remote_host, port):
        self.simulator = simulator
        self.fdmexec = simulator.fdmexec
        self.aircraft = simulator.aircraft
        self.remote_host = remote_host
        self.port = port

    def _fill_gps_data(self, simulator_data):
        """Fill the gps data in the SimulatorData object

        Arguments:
        simulator_data: the protocol buffer SimulatorData object
        """
        simulator_data.gps.latitude = self.aircraft.instruments.gps.latitude
        simulator_data.gps.longitude = self.aircraft.instruments.gps.longitude
        simulator_data.gps.altitude = self.aircraft.instruments.gps.altitude
        simulator_data.gps.airspeed = self.aircraft.instruments.gps.airspeed
        simulator_data.gps.heading = self.aircraft.instruments.gps.heading

    def _fill_accelerometer_data(self, simulator_data):
        """Fill the accelerometer data in the SimulatorData object

        Arguments:
        simulator_data: the protocol buffer SimulatorData object
        """
        sensors = self.aircraft.sensors

        simulator_data.accelerometer.x = sensors.accelerometer.x
        simulator_data.accelerometer.y = sensors.accelerometer.y
        simulator_data.accelerometer.z = sensors.accelerometer.z

    def _fill_gyroscope_data(self, simulator_data):
        """Fill the gyroscope data in the SimulatorData object

        Arguments:
        simulator_data: the protocol buffer SimulatorData object
        """
        sensors = self.aircraft.sensors

        simulator_data.gyroscope.roll_rate = sensors.gyroscope.roll_rate
        simulator_data.gyroscope.pitch_rate = sensors.gyroscope.pitch_rate
        simulator_data.gyroscope.yaw_rate = sensors.gyroscope.yaw_rate

    def _fill_thermometer_data(self, simulator_data):
        """Fill the thermometer data in the SimulatorData object

        Arguments:
        simulator_data: the protocol buffer SimulatorData object
        """
        thermometer = self.aircraft.sensors.thermometer

        simulator_data.thermometer.temperature = thermometer.temperature

    def _fill_pressure_sensor_data(self, simulator_data):
        """Fill the pressure sensor data in the SimulatorData object

        Arguments:
        simulator_data: the protocol buffer SimulatorData object
        """
        pressure_sensor = self.aircraft.sensors.pressure_sensor
        pitot_tube = self.aircraft.sensors.pitot_tube

        simulator_data.pressure_sensor.pressure = pressure_sensor.pressure
        simulator_data.pitot_tube.pressure = pitot_tube.pressure

    def _fill_engine_data(self, simulator_data):
        """Fill the engine data in the SimulatorData object

        Arguments:
        simulator_data: the protocol buffer SimulatorData object
        """
        simulator_data.engine.thrust = self.aircraft.engine.thrust
        simulator_data.engine.throttle = self.aircraft.engine.throttle

    def _fill_aircraft_controls_data(self, simulator_data):
        """Fill the aircraft controls data in the SimulatorData object

        Arguments:
        simulator_data: the protocol buffer SimulatorData object
        """
        simulator_data.controls.aileron = self.aircraft.controls.aileron
        simulator_data.controls.elevator = self.aircraft.controls.elevator
        simulator_data.controls.rudder = self.aircraft.controls.rudder
        simulator_data.controls.throttle = self.aircraft.controls.throttle

    def _fill_ins_data(self, simulator_data):
        """Fill the inertial navigation system data in the SimulatorData
        object

        Arguments:
        simulator_data: the protocol buffer SimulatorData object
        """
        ins = self.aircraft.sensors.inertial_navigation_system

        simulator_data.ins.roll = ins.roll
        simulator_data.ins.pitch = ins.pitch
        simulator_data.ins.latitude = ins.latitude
        simulator_data.ins.longitude = ins.longitude
        simulator_data.ins.altitude = ins.altitude
        simulator_data.ins.airspeed = ins.airspeed
        simulator_data.ins.heading = ins.heading

    def _fill_accelerations(self, simulator_data):
        """Fill the fdm accelerations data in the SimulatorData object

        Arguments:
        simulator_data: the protocol buffer SimulatorData object
        """
        accelerations = self.simulator.fdm.accelerations

        simulator_data.accelerations.x = accelerations.x
        simulator_data.accelerations.y = accelerations.y
        simulator_data.accelerations.z = accelerations.z
        simulator_data.accelerations.p_dot = accelerations.p_dot
        simulator_data.accelerations.q_dot = accelerations.q_dot
        simulator_data.accelerations.r_dot = accelerations.r_dot
        simulator_data.accelerations.u_dot = accelerations.u_dot
        simulator_data.accelerations.v_dot = accelerations.v_dot
        simulator_data.accelerations.w_dot = accelerations.w_dot
        simulator_data.accelerations.gravity = accelerations.gravity

    def _fill_velocities(self, simulator_data):
        """Fill the fdm velocities data in the SimulatorData object

        Arguments:
        simulator_data: the protocol buffer SimulatorData object
        """
        velocities = self.simulator.fdm.velocities

        simulator_data.velocities.p = velocities.p
        simulator_data.velocities.q = velocities.q
        simulator_data.velocities.r = velocities.r
        simulator_data.velocities.u = velocities.u
        simulator_data.velocities.v = velocities.v
        simulator_data.velocities.w = velocities.w
        simulator_data.velocities.true_airspeed = velocities.true_airspeed

        calibrated_airspeed = velocities.calibrated_airspeed
        simulator_data.velocities.calibrated_airspeed = calibrated_airspeed

        equivalent_airspeed = velocities.equivalent_airspeed
        simulator_data.velocities.equivalent_airspeed = equivalent_airspeed

        simulator_data.velocities.climb_rate = velocities.climb_rate
        simulator_data.velocities.ground_speed = velocities.ground_speed

    def _fill_position(self, simulator_data):
        """Fill the fdm position data in the SimulatorData object

        Arguments:
        simulator_data: the protocol buffer SimulatorData object
        """
        position = self.simulator.fdm.position

        simulator_data.position.latitude = position.latitude
        simulator_data.position.longitude = position.longitude
        simulator_data.position.altitude = position.altitude
        simulator_data.position.heading = position.heading

    def _fill_orientation(self, simulator_data):
        """Fill the fdm orientation data in the SimulatorData object

        Arguments:
        simulator_data: the protocol buffer SimulatorData object
        """
        orientation = self.simulator.fdm.orientation

        simulator_data.orientation.phi = orientation.phi
        simulator_data.orientation.theta = orientation.theta
        simulator_data.orientation.psi = orientation.psi

    def _fill_atmosphere(self, simulator_data):
        """Fill the fdm atmospheric data in the SimulatorData object

        Arguments:
        simulator_data: the protocol buffer SimulatorData object
        """
        atmosphere = self.simulator.fdm.atmosphere

        simulator_data.atmosphere.pressure = atmosphere.pressure

        sea_level_pressure = atmosphere.sea_level_pressure
        simulator_data.atmosphere.sea_level_pressure = sea_level_pressure

        simulator_data.atmosphere.temperature = atmosphere.temperature

        sea_level_temperature = atmosphere.sea_level_temperature
        simulator_data.atmosphere.sea_level_temperature = sea_level_temperature

        simulator_data.atmosphere.density = atmosphere.density

        sea_level_density = atmosphere.sea_level_density
        simulator_data.atmosphere.sea_level_density = sea_level_density

    def _fill_forces(self, simulator_data):
        """Fill the fdm atmospheric data in the SimulatorData object

        Arguments:
        simulator_data: the protocol buffer SimulatorData object
        """
        forces = self.simulator.fdm.forces

        simulator_data.forces.x_body = forces.x_body
        simulator_data.forces.y_body = forces.y_body
        simulator_data.forces.z_body = forces.z_body
        simulator_data.forces.x_wind = forces.x_wind
        simulator_data.forces.y_wind = forces.y_wind
        simulator_data.forces.z_wind = forces.z_wind
        simulator_data.forces.x_total = forces.x_total
        simulator_data.forces.y_total = forces.y_total
        simulator_data.forces.z_total = forces.z_total

    def get_simulator_data(self):
        """Return the simulator data"""
        simulator_data = fdm_pb2.SimulatorData()

        simulator_data.time = self.fdmexec.GetSimTime()

        self._fill_gps_data(simulator_data)
        self._fill_accelerometer_data(simulator_data)
        self._fill_gyroscope_data(simulator_data)
        self._fill_thermometer_data(simulator_data)
        self._fill_pressure_sensor_data(simulator_data)
        self._fill_engine_data(simulator_data)
        self._fill_aircraft_controls_data(simulator_data)
        self._fill_ins_data(simulator_data)
        self._fill_accelerations(simulator_data)
        self._fill_velocities(simulator_data)
        self._fill_position(simulator_data)
        self._fill_orientation(simulator_data)
        self._fill_atmosphere(simulator_data)
        self._fill_forces(simulator_data)

        return simulator_data

    def send_simulator_data(self):
        """Transmit the simulator data"""
        simulator_data = self.get_simulator_data()

        datagram = simulator_data.SerializeToString()

        self.transport.write(datagram, (self.remote_host, self.port))
huginn/rest.py
class PressureSensorResource(Resource):
    """The PressureSensorResource class returns the pressure sensor
    measurements"""

    def __init__(self, pressure_sensor):
        """Create a new PressureSensorResource object

        Arguments:
        pressure_sensor: a PressureSensor object
        """
        self.pressure_sensor = pressure_sensor

    def get(self):
        """Returns the pressure sensor data"""
        pressure_sensor_data = {
            "static_pressure": self.pressure_sensor.pressure,
        }

        return pressure_sensor_data
huginn/http.py
class SimulatorDataWebSocketFactory(WebSocketServerFactory):
    """The SimulatorDataWebSocketFactory class is a factory that creates the
    protocol objects for the simulator data transmission through web sockets"""
    def __init__(self, simulator, database, *args, **kwargs):
        WebSocketServerFactory.__init__(self, *args, **kwargs)

        self.simulator = simulator
        self.database = database
huginn/rest.py
class InertialNavigationSystemResource(Resource):
    """The InertialNavigationSystemResource returns the measurements from the
    inertial navigation system"""

    def __init__(self, inertial_navigation_system):
        """Create a new InertialNavigationSystemResource object

        Arguments:
        inertial_navigation_system: An InertialNavigationSystem object
        """
        self.inertial_navigation_system = inertial_navigation_system

    def get(self):
        """Returns the ins measurements"""
        inertial_navigation_system_data = {
            "latitude": self.inertial_navigation_system.latitude,
            "longitude": self.inertial_navigation_system.longitude,
            "altitude": self.inertial_navigation_system.altitude,
            "airspeed": self.inertial_navigation_system.airspeed,
            "heading": self.inertial_navigation_system.heading,
            "roll": self.inertial_navigation_system.roll,
            "pitch": self.inertial_navigation_system.pitch,
        }

        return inertial_navigation_system_data
huginn/rest.py
class FlightControlsResource(Resource):
    """The FlightControlsResource return the flight controls values"""

    def __init__(self, controls):
        """Create a new FlightControlsResource object

        Arguments:
        controls: A Controls object
        """
        self.controls = controls

    def get(self):
        """returns the flight controls values"""
        flight_controls_data = {
            "aileron": self.controls.aileron,
            "elevator": self.controls.elevator,
            "rudder": self.controls.rudder,
            "throttle": self.controls.throttle,
        }

        return flight_controls_data
huginn/rest.py
class PositionResource(ObjectResource):
    """The PositionResource object contain the aircraft position data"""

    def __init__(self, fdmexec):
        """Create a new PositionResource object

        Arguments:
        fdmexec: a jsbsim FGFDMExec object
        """
        self.fdmexec = fdmexec
        self.position = Position(fdmexec)
        self.position_schema = PositionSchema()

        super(PositionResource, self).__init__(
            self.position,
            self.position_schema
        )
huginn/rest.py
class WaypointResource(Resource):
    """The WaypointResource is used to ad, delete and update waypoints"""
    def __init__(self, db):
        self.db = db

    @marshal_with(request_models.waypoint)
    def get(self, name):
        """Get the waypoint's data"""
        Document = Query()

        waypoints = self.db.search(Document.type == "waypoints")[0]

        waypoint = waypoints["waypoints"].get(name)

        if waypoint is not None:
            return waypoint
        else:
            abort(404, error="waypoint doesn't exist", name=name)

    @marshal_with(request_models.waypoint)
    def post(self, name):
        """Add a new waypoint or update an existing waypoint's data"""
        waypoint = request_parsers.waypoint.parse_args()
        waypoint["name"] = name

        Document = Query()

        waypoints = self.db.search(Document.type == "waypoints")[0]

        waypoints["waypoints"][name] = waypoint

        self.db.update(waypoints, Document.type == "waypoints")

        return waypoint

    @marshal_with(request_models.waypoint)
    def delete(self, name):
        """Delete a waypoint"""
        Document = Query()

        waypoints = self.db.search(Document.type == "waypoints")[0]

        waypoint = waypoints["waypoints"].get(name)

        if waypoint is not None:
            del waypoints["waypoints"][name]
            return waypoint
        else:
            abort(404, error="waypoint doesn't exist", name=name)
huginn/rest.py
class AccelerometerResource(Resource):
    """The AccelerometerResource returns the accelerometer measurements"""

    def __init__(self, accelerometer):
        """Create a new AccelerometerResource object

        Arguments:
        accelerometer: an Accelerometer object
        """
        self.accelerometer = accelerometer

    def get(self):
        """Returns the accelerometer measurements"""
        accelerometer_data = {
            "x": self.accelerometer.x,
            "y": self.accelerometer.y,
            "z": self.accelerometer.z,
        }

        return accelerometer_data
huginn/protocols.py
class ControlsProtocol(DatagramProtocol):
    """The ControlsProtocol is used to receive and update tha aircraft's
    controls"""
    def __init__(self, fdmexec):
        self.fdmexec = fdmexec

    def update_aircraft_controls(self, aileron, elevator, rudder, throttle):
        """Set the new aircraft controls values"""
        if aileron > 1.0:
            aileron = 1.0
        elif aileron < -1.0:
            aileron = -1.0

        self.fdmexec.GetFCS().SetDaCmd(aileron)

        if elevator > 1.0:
            elevator = 1.0
        elif elevator < -1.0:
            elevator = -1.0

        self.fdmexec.GetFCS().SetDeCmd(elevator)

        if rudder > 1.0:
            rudder = 1.0
        elif rudder < -1.0:
            rudder = -1.0

        self.fdmexec.GetFCS().SetDrCmd(rudder)

        if throttle > 1.0:
            throttle = 1.0
        elif throttle < 0.0:
            throttle = 0.0

        for i in range(self.fdmexec.GetPropulsion().GetNumEngines()):
            self.fdmexec.GetFCS().SetThrottleCmd(i, throttle)

    def datagramReceived(self, datagram, addr):
        controls = fdm_pb2.Controls()

        try:
            controls.ParseFromString(datagram)
        except DecodeError:
            logger.exception("Failed to parse control data")
            print("Failed to parse control data")
            return

        self.update_aircraft_controls(controls.aileron,
                                      controls.elevator,
                                      controls.rudder,
                                      controls.throttle)
huginn/rest.py
class AtmosphereResource(ObjectResource):
    """The AtmosphereResource object return the fdm atmospheric data"""

    def __init__(self, fdmexec):
        """Create a new AtmosphereResource object

        Arguments:
        fdmexec: a jsbsim FGFDMExec object
        """
        self.fdmexec = fdmexec
        self.atmosphere = Atmosphere(fdmexec)
        self.atmosphere_schema = AtmosphereShema()

        super(AtmosphereResource, self).__init__(self.atmosphere,
                                                 self.atmosphere_schema)
huginn/rest.py
class AircraftResource(Resource):
    """The AircraftResource returns infomration about the aircraft"""

    def __init__(self, aircraft):
        """Create a new AircraftResource object

        Arguments:
        aircraft: an Aircraft object
        """
        self.aircraft = aircraft

    def get(self):
        """Get the aircraft information"""
        return {"type": self.aircraft.type}
huginn/rest.py
class SimulatorControlResource(Resource):
    """The SimulatorControlResource provides the endpoint that is used to
    control the simulator"""

    def __init__(self, simulator):
        """Create a new SimulatorControlResource object

        Arguments:
        simulator: a Simulator object
        """
        self.simulator = simulator

    def get(self):
        """Returns the simulator state"""
        simulator_state = {
            "time": self.simulator.simulation_time,
            "dt": self.simulator.dt,
            "running": not self.simulator.is_paused(),
            "crashed": self.simulator.crashed
        }

        return simulator_state

    def execute_command(self, command, params=None):
        """Execute the simulator command

        Arguments:
        command: the command to execute
        params: the parameters of the command
        """
        if command == "reset":
            self.simulator.reset()

            response = {"result": "ok",
                        "command": command}
        elif command == "pause":
            self.simulator.pause()

            response = {"result": "ok",
                        "command": command}
        elif command == "resume":
            self.simulator.resume()

            response = {"result": "ok",
                        "command": command}
        elif command == "step":
            self.simulator.step()

            response = {"result": "ok",
                        "command": command}
        elif command == "run_for":
            if params:
                time_to_run = params["time_to_run"]
            else:
                time_to_run = None

            if not time_to_run:
                response = {"error": "no time to run provided",
                            "command": command}
            else:
                self.simulator.run_for(time_to_run)

                response = {"result": "ok",
                            "command": "run_for"}
        elif command == "start_paused":
            self.simulator.start_paused = True

            response = {"result": "ok",
                        "command": "start_paused"}
        elif command == "start_running":
            self.simulator.start_paused = False

            response = {"result": "ok",
                        "command": "start_running"}
        else:
            response = {"error": "unknown command",
                        "command": command}

        return response

    def post(self):
        """Execute a command on the simulator"""
        parser = reqparse.RequestParser()

        parser.add_argument("command", type=str, required=True)
        parser.add_argument("time_to_run", type=float)
        parser.add_argument("paused", type=bool)

        args = parser.parse_args()

        command = args.command

        params = {}

        if args.time_to_run:
            params["time_to_run"] = args.time_to_run

        if args.paused:
            params["paused"] = args.paused

        return self.execute_command(command, params)
huginn/rest.py
class AttitudeIndicatorResource(ObjectResource):
    """The AttitudeIndicatorResource object returns the aircraft's attitude
    indicator data"""

    def __init__(self, attitude_indicator):
        """Create a new AttitudeIndicatorResource object

        Arguments:
        attitude_indicator: an AttitudeIndicator object
        """
        self.attitude_indicator = attitude_indicator
        self.attitude_indicator_schema = AttitudeIndicatorSchema()

        super(AttitudeIndicatorResource, self).__init__(
            self.attitude_indicator,
            self.attitude_indicator_schema
        )
huginn/rest.py
class AltimeterResource(ObjectResource):
    """The AltimeterResource object returns the aircraft's altimeter data"""

    def __init__(self, altimeter):
        """Create a new AltimeterResource object

        Arguments:
        altimeter: an Altimeter object
        """
        self.altimeter = altimeter
        self.altimeter_schema = AltimeterSchema()

        super(AltimeterResource, self).__init__(
            self.altimeter,
            self.altimeter_schema
        )
huginn/rest.py
class GyroscopeResource(Resource):
    """The GyroscopeResource returns the data from the gyroscope sensor"""

    def __init__(self, gyroscope):
        """Create a new GyroscopeResource object

        Arguments:
        gyroscope: A Gyroscope object
        """
        self.gyroscope = gyroscope

    def get(self):
        """Returns the gyroscope measurements"""
        gyroscope_data = {
            "roll_rate": self.gyroscope.roll_rate,
            "pitch_rate": self.gyroscope.pitch_rate,
            "yaw_rate": self.gyroscope.yaw_rate
        }

        return gyroscope_data
huginn/http.py
class SimulatorDataWebSocketProtocol(WebSocketServerProtocol):
    """The SimulatorDataWebSocketProtocol class if the protocol class that
    transmits the simulator data using a web socket"""
    def onMessage(self, payload, isBinary):
        try:
            request = json.loads(payload)
        except:
            logger.exception("failed to parse websocket command")
            return

        if "command" not in request:
            logger.debug("The request doesn't contain a command field")
            return

        command = request["command"]

        if command == "flight_data":
            self.send_flight_data()
        elif command == "waypoints":
            self.send_waypoint_data()
        else:
            logger.warning("Unknown web socket command %s", command)

    def send_flight_data(self):
        """Send the fdm data"""
        fdm = self.factory.simulator.fdm

        flight_data = {
            "command": "flight_data",
            "data": {
                "roll": fdm.orientation.phi,
                "pitch": fdm.orientation.theta,
                "airspeed": fdm.velocities.true_airspeed,
                "latitude": fdm.position.latitude,
                "longitude": fdm.position.longitude,
                "altitude": fdm.position.altitude,
                "heading": fdm.position.heading,
                "climb_rate": fdm.velocities.climb_rate
            },
        }

        payload = json.dumps(flight_data).encode("utf8")

        self.sendMessage(payload, False)

    def send_waypoint_data(self):
        """Send the waypoint information"""
        Document = Query()

        waypoints = self.factory.database.search(
            Document.type == "waypoints")[0]

        response = {
            "command": "waypoints",
            "data": waypoints["waypoints"].values()
        }

        payload = json.dumps(response).encode("utf8")

        self.sendMessage(payload, False)
huginn/rest.py
class OrientationResource(ObjectResource):
    """The OrientationResource object returns the orientations of the
    aircraft"""

    def __init__(self, fdmexec):
        """Create a new OrientationResource object

        Arguments:
        fdmexec: a jsbsim FGFDMExec object
        """
        self.fdmexec = fdmexec
        self.orientation = Orientation(fdmexec)
        self.orientation_schema = OrientationSchema()

        super(OrientationResource, self).__init__(self.orientation,
                                                  self.orientation_schema)
huginn/rest.py
class FDMResource(Resource):
    """The FDMResource will return the data from the flight dynamics model"""

    def __init__(self, fdm, aircraft):
        """Create a new FDMResource object

        Arguments:
        fdm: an FDM object
        aircraft: an aircraft object
        """
        self.fdm = fdm
        self.aircraft = aircraft

    def get(self):
        """Get the fdm data"""
        sensors = self.aircraft.sensors

        flight_data = {
            "time": self.fdm.fdmexec.GetSimTime(),
            "dt": self.fdm.fdmexec.GetDeltaT(),
            "latitude": self.fdm.position.latitude,
            "longitude": self.fdm.position.longitude,
            "altitude": self.fdm.position.altitude,
            "airspeed": self.fdm.velocities.true_airspeed,
            "heading": self.fdm.orientation.psi,
            "x_acceleration": self.fdm.accelerations.x,
            "y_acceleration": self.fdm.accelerations.y,
            "z_acceleration": self.fdm.accelerations.z,
            "roll_rate": self.fdm.velocities.p,
            "pitch_rate": self.fdm.velocities.q,
            "yaw_rate": self.fdm.velocities.r,
            "temperature": self.fdm.atmosphere.temperature,
            "static_pressure": self.fdm.atmosphere.pressure,
            "total_pressure": sensors.pitot_tube.true_pressure,
            "roll": self.fdm.orientation.phi,
            "pitch": self.fdm.orientation.theta,
            "climb_rate": self.fdm.velocities.climb_rate
        }

        return flight_data
huginn/rest.py
class VerticalSpeedIndicatorResource(ObjectResource):
    """The VerticalSpeedIndicatorResource object returns the aircraft's heading
    indicator data"""

    def __init__(self, vertical_speed_indicator):
        """Create a new VerticalSpeedIndicatorResource object

        Arguments:
        vertical_speed_indicator: a VerticalSpeedIndicator object
        """
        self.vertical_speed_indicator = vertical_speed_indicator
        self.vertical_speed_indicator_schema = VerticalSpeedIndicatorSchema()

        super(VerticalSpeedIndicatorResource, self).__init__(
            self.vertical_speed_indicator,
            self.vertical_speed_indicator_schema
        )
huginn/rest.py
class WaypointsResource(Resource):
    """The WaypointsResource is used to retrieve all the available waypoints"""
    def __init__(self, db):
        self.db = db

    @marshal_with(request_models.waypoint)
    def get(self):
        """Get a list of the available waypoints"""
        Document = Query()

        waypoints = self.db.search(Document.type == "waypoints")[0]

        return waypoints["waypoints"].values()
huginn/rest.py
class ForcesResource(ObjectResource):
    """The ForcesResource object contains the forces that act on the
    aircraft"""

    def __init__(self, fdmexec):
        """Create a new ForcesResource object

        Arguments:
        fdmexec: a jsbsim FGFDMExec object
        """
        self.fdmexec = fdmexec
        self.forces = Forces(fdmexec)
        self.forces_schema = ForcesSchema()

        super(ForcesResource, self).__init__(self.forces,
                                             self.forces_schema)
huginn/rest.py
class EngineResource(Resource):
    """The EngineResource class returns the engine data"""
    def __init__(self, engine):
        """Create a new EngineResource object

        Arguments:
        engine: An Engine object
        """
        self.engine = engine

    def get(self):
        """Returns the engine data"""
        engine_data = {
            "thrust": self.engine.thrust,
            "throttle": self.engine.throttle,
        }

        return engine_data

Answer: GyroscopeResource,


Input:
import getpass
import netrc
import optparse
import os
import shlex
import sys
import textwrap
import time
import urllib.request
  import kerberos
import event_log
import gitc_utils
      import trace
from color import SetDefaultColoring
from repo_trace import SetTrace
from git_command import user_agent
from git_config import RepoConfig
from git_trace2_event_log import EventLog
from command import InteractiveCommand
from command import MirrorSafeCommand
from command import GitcAvailableCommand, GitcClientCommand
from subcmds.version import Version
from editor import Editor
from error import DownloadError
from error import InvalidProjectGroupsError
from error import ManifestInvalidRevisionError
from error import ManifestParseError
from error import NoManifestException
from error import NoSuchProjectError
from error import RepoChangedException
from manifest_xml import GitcClient, RepoClient
from pager import RunPager, TerminatePager
from wrapper import WrapperPath, Wrapper
from subcmds import all_commands
  def __init__(self, repodir):
    self.repodir = repodir
    self.commands = all_commands

  def _PrintHelp(self, short: bool = False, all_commands: bool = False):
    """Show --help screen."""
    global_options.print_help()
    print()
    if short:
      commands = ' '.join(sorted(self.commands))
      wrapped_commands = textwrap.wrap(commands, width=77)
      print('Available commands:\n  %s' % ('\n  '.join(wrapped_commands),))
      print('\nRun `repo help <command>` for command-specific details.')
      print('Bug reports:', Wrapper().BUG_URL)
    else:
      cmd = self.commands['help']()
      if all_commands:
        cmd.PrintAllCommandsBody()
      else:
        cmd.PrintCommonCommandsBody()

  def _ParseArgs(self, argv):
    """Parse the main `repo` command line options."""
    for i, arg in enumerate(argv):
      if not arg.startswith('-'):
        name = arg
        glob = argv[:i]
        argv = argv[i + 1:]
        break
    else:
      name = None
      glob = argv
      argv = []
    gopts, _gargs = global_options.parse_args(glob)

    if name:
      name, alias_args = self._ExpandAlias(name)
      argv = alias_args + argv

    return (name, gopts, argv)

  def _ExpandAlias(self, name):
    """Look up user registered aliases."""
    # We don't resolve aliases for existing subcommands.  This matches git.
    if name in self.commands:
      return name, []

    key = 'alias.%s' % (name,)
    alias = RepoConfig.ForRepository(self.repodir).GetString(key)
    if alias is None:
      alias = RepoConfig.ForUser().GetString(key)
    if alias is None:
      return name, []

    args = alias.strip().split(' ', 1)
    name = args[0]
    if len(args) == 2:
      args = shlex.split(args[1])
    else:
      args = []
    return name, args

  def _Run(self, name, gopts, argv):
    """Execute the requested subcommand."""
    result = 0

    if gopts.trace:
      SetTrace()

    # Handle options that terminate quickly first.
    if gopts.help or gopts.help_all:
      self._PrintHelp(short=False, all_commands=gopts.help_all)
      return 0
    elif gopts.show_version:
      # Always allow global --version regardless of subcommand validity.
      name = 'version'
    elif gopts.show_toplevel:
      print(os.path.dirname(self.repodir))
      return 0
    elif not name:
      # No subcommand specified, so show the help/subcommand.
      self._PrintHelp(short=True)
      return 1

    SetDefaultColoring(gopts.color)

    git_trace2_event_log = EventLog()
    outer_client = RepoClient(self.repodir)
    repo_client = outer_client
    if gopts.submanifest_path:
      repo_client = RepoClient(self.repodir,
                               submanifest_path=gopts.submanifest_path,
                               outer_client=outer_client)
    gitc_manifest = None
    gitc_client_name = gitc_utils.parse_clientdir(os.getcwd())
    if gitc_client_name:
      gitc_manifest = GitcClient(self.repodir, gitc_client_name)
      repo_client.isGitcClient = True

    try:
      cmd = self.commands[name](
          repodir=self.repodir,
          client=repo_client,
          manifest=repo_client.manifest,
          outer_client=outer_client,
          outer_manifest=outer_client.manifest,
          gitc_manifest=gitc_manifest,
          git_event_log=git_trace2_event_log)
    except KeyError:
      print("repo: '%s' is not a repo command.  See 'repo help'." % name,
            file=sys.stderr)
      return 1

    Editor.globalConfig = cmd.client.globalConfig

    if not isinstance(cmd, MirrorSafeCommand) and cmd.manifest.IsMirror:
      print("fatal: '%s' requires a working directory" % name,
            file=sys.stderr)
      return 1

Context:
command.py
class GitcAvailableCommand(object):
  """Command that requires GITC to be available, but does
     not require the local client to be a GITC client.
  """
error.py
class NoManifestException(Exception):
  """The required manifest does not exist.
  """

  def __init__(self, path, reason):
    super().__init__(path, reason)
    self.path = path
    self.reason = reason

  def __str__(self):
    return self.reason
manifest_xml.py
class RepoClient(XmlManifest):
  """Manages a repo client checkout."""

  def __init__(self, repodir, manifest_file=None, submanifest_path='', **kwargs):
    self.isGitcClient = False
    submanifest_path = submanifest_path or ''
    if submanifest_path:
      self._CheckLocalPath(submanifest_path)
      prefix = os.path.join(repodir, SUBMANIFEST_DIR, submanifest_path)
    else:
      prefix = repodir

    if os.path.exists(os.path.join(prefix, LOCAL_MANIFEST_NAME)):
      print('error: %s is not supported; put local manifests in `%s` instead' %
            (LOCAL_MANIFEST_NAME, os.path.join(prefix, LOCAL_MANIFESTS_DIR_NAME)),
            file=sys.stderr)
      sys.exit(1)

    if manifest_file is None:
        manifest_file = os.path.join(prefix, MANIFEST_FILE_NAME)
    local_manifests = os.path.abspath(os.path.join(prefix, LOCAL_MANIFESTS_DIR_NAME))
    super().__init__(repodir, manifest_file, local_manifests,
                     submanifest_path=submanifest_path, **kwargs)

    # TODO: Completely separate manifest logic out of the client.
    self.manifest = self
error.py
class RepoChangedException(Exception):
  """Thrown if 'repo sync' results in repo updating its internal
     repo or manifest repositories.  In this special case we must
     use exec to re-execute repo with the new code and manifest.
  """

  def __init__(self, extra_args=None):
    super().__init__(extra_args)
    self.extra_args = extra_args or []
subcmds/version.py
class Version(Command, MirrorSafeCommand):
  wrapper_version = None
  wrapper_path = None

  COMMON = False
  helpSummary = "Display the version of repo"
  helpUsage = """
%prog
"""

  def Execute(self, opt, args):
    rp = self.manifest.repoProject
    rem = rp.GetRemote(rp.remote.name)
    branch = rp.GetBranch('default')

    # These might not be the same.  Report them both.
    src_ver = RepoSourceVersion()
    rp_ver = rp.bare_git.describe(HEAD)
    print('repo version %s' % rp_ver)
    print('       (from %s)' % rem.url)
    print('       (tracking %s)' % branch.merge)
    print('       (%s)' % rp.bare_git.log('-1', '--format=%cD', HEAD))

    if self.wrapper_path is not None:
      print('repo launcher version %s' % self.wrapper_version)
      print('       (from %s)' % self.wrapper_path)

      if src_ver != rp_ver:
        print('       (currently at %s)' % src_ver)

    print('repo User-Agent %s' % user_agent.repo)
    print('git %s' % git.version_tuple().full)
    print('git User-Agent %s' % user_agent.git)
    print('Python %s' % sys.version)
    uname = platform.uname()
    if sys.version_info.major < 3:
      # Python 3 returns a named tuple, but Python 2 is simpler.
      print(uname)
    else:
      print('OS %s %s (%s)' % (uname.system, uname.release, uname.version))
      print('CPU %s (%s)' %
            (uname.machine, uname.processor if uname.processor else 'unknown'))
    print('Bug reports:', Wrapper().BUG_URL)
git_command.py
GIT = 'git'
MIN_GIT_VERSION_SOFT = (1, 9, 1)
MIN_GIT_VERSION_HARD = (1, 7, 2)
GIT_DIR = 'GIT_DIR'
LAST_GITDIR = None
LAST_CWD = None
        LAST_CWD = cwd
        LAST_GITDIR = env[GIT_DIR]
class _GitCall(object):
class UserAgent(object):
class GitCommand(object):
  def version_tuple(self):
  def __getattr__(self, name):
    def fun(*cmdv):
def RepoSourceVersion():
  def os(self):
  def repo(self):
  def git(self):
def git_require(min_version, fail=False, msg=''):
  def __init__(self,
               project,
               cmdv,
               bare=False,
               input=None,
               capture_stdout=False,
               capture_stderr=False,
               merge_output=False,
               disable_editor=False,
               ssh_proxy=None,
               cwd=None,
               gitdir=None,
               objdir=None):
  def _GetBasicEnv():
  def Wait(self):
manifest_xml.py
class GitcClient(RepoClient, GitcManifest):
  """Manages a GitC client checkout."""

  def __init__(self, repodir, gitc_client_name):
    """Initialize the GitcManifest object."""
    self.gitc_client_name = gitc_client_name
    self.gitc_client_dir = os.path.join(gitc_utils.get_gitc_manifest_dir(),
                                        gitc_client_name)

    super().__init__(repodir, os.path.join(self.gitc_client_dir, '.manifest'))
    self.isGitcClient = True
command.py
class InteractiveCommand(Command):
  """Command which requires user interaction on the tty and
     must not run within a pager, even if the user asks to.
  """

  def WantPager(self, _opt):
    return False
command.py
class MirrorSafeCommand(object):
  """Command permits itself to run within a mirror,
     and does not require a working directory.
  """
command.py
class GitcClientCommand(object):
  """Command that requires the local client to be a GITC
     client.
  """
error.py
class DownloadError(Exception):
  """Cannot download a repository.
  """

  def __init__(self, reason):
    super().__init__(reason)
    self.reason = reason

  def __str__(self):
    return self.reason
error.py
class ManifestInvalidRevisionError(ManifestParseError):
  """The revision value in a project is incorrect.
  """
git_config.py
class RepoConfig(GitConfig):
  """User settings for repo itself."""

  _USER_CONFIG = '~/.repoconfig/config'
git_trace2_event_log.py
class EventLog(object):
  """Event log that records events that occurred during a repo invocation.

  Events are written to the log as a consecutive JSON entries, one per line.
  Entries follow the git trace2 EVENT format.

  Each entry contains the following common keys:
  - event: The event name
  - sid: session-id - Unique string to allow process instance to be identified.
  - thread: The thread name.
  - time: is the UTC time of the event.

  Valid 'event' names and event specific fields are documented here:
  https://git-scm.com/docs/api-trace2#_event_format
  """

  def __init__(self, env=None):
    """Initializes the event log."""
    self._log = []
    # Try to get session-id (sid) from environment (setup in repo launcher).
    KEY = 'GIT_TRACE2_PARENT_SID'
    if env is None:
      env = os.environ

    now = datetime.datetime.utcnow()

    # Save both our sid component and the complete sid.
    # We use our sid component (self._sid) as the unique filename prefix and
    # the full sid (self._full_sid) in the log itself.
    self._sid = 'repo-%s-P%08x' % (now.strftime('%Y%m%dT%H%M%SZ'), os.getpid())
    parent_sid = env.get(KEY)
    # Append our sid component to the parent sid (if it exists).
    if parent_sid is not None:
      self._full_sid = parent_sid + '/' + self._sid
    else:
      self._full_sid = self._sid

    # Set/update the environment variable.
    # Environment handling across systems is messy.
    try:
      env[KEY] = self._full_sid
    except UnicodeEncodeError:
      env[KEY] = self._full_sid.encode()

    # Add a version event to front of the log.
    self._AddVersionEvent()

  @property
  def full_sid(self):
    return self._full_sid

  def _AddVersionEvent(self):
    """Adds a 'version' event at the beginning of current log."""
    version_event = self._CreateEventDict('version')
    version_event['evt'] = "2"
    version_event['exe'] = RepoSourceVersion()
    self._log.insert(0, version_event)

  def _CreateEventDict(self, event_name):
    """Returns a dictionary with the common keys/values for git trace2 events.

    Args:
      event_name: The event name.

    Returns:
      Dictionary with the common event fields populated.
    """
    return {
        'event': event_name,
        'sid': self._full_sid,
        'thread': threading.currentThread().getName(),
        'time': datetime.datetime.utcnow().isoformat() + 'Z',
    }

  def StartEvent(self):
    """Append a 'start' event to the current log."""
    start_event = self._CreateEventDict('start')
    start_event['argv'] = sys.argv
    self._log.append(start_event)

  def ExitEvent(self, result):
    """Append an 'exit' event to the current log.

    Args:
      result: Exit code of the event
    """
    exit_event = self._CreateEventDict('exit')

    # Consider 'None' success (consistent with event_log result handling).
    if result is None:
      result = 0
    exit_event['code'] = result
    self._log.append(exit_event)

  def CommandEvent(self, name, subcommands):
    """Append a 'command' event to the current log.

    Args:
      name: Name of the primary command (ex: repo, git)
      subcommands: List of the sub-commands (ex: version, init, sync)
    """
    command_event = self._CreateEventDict('command')
    command_event['name'] = name
    command_event['subcommands'] = subcommands
    self._log.append(command_event)

  def LogConfigEvents(self, config, event_dict_name):
    """Append a |event_dict_name| event for each config key in |config|.

    Args:
      config: Configuration dictionary.
      event_dict_name: Name of the event dictionary for items to be logged under.
    """
    for param, value in config.items():
      event = self._CreateEventDict(event_dict_name)
      event['param'] = param
      event['value'] = value
      self._log.append(event)

  def DefParamRepoEvents(self, config):
    """Append a 'def_param' event for each repo.* config key to the current log.

    Args:
      config: Repo configuration dictionary
    """
    # Only output the repo.* config parameters.
    repo_config = {k: v for k, v in config.items() if k.startswith('repo.')}
    self.LogConfigEvents(repo_config, 'def_param')

  def GetDataEventName(self, value):
    """Returns 'data-json' if the value is an array else returns 'data'."""
    return 'data-json' if value[0] == '[' and value[-1] == ']' else 'data'

  def LogDataConfigEvents(self, config, prefix):
    """Append a 'data' event for each config key/value in |config| to the current log.

    For each keyX and valueX of the config, "key" field of the event is '|prefix|/keyX'
    and the "value" of the "key" field is valueX.

    Args:
      config: Configuration dictionary.
      prefix: Prefix for each key that is logged.
    """
    for key, value in config.items():
      event = self._CreateEventDict(self.GetDataEventName(value))
      event['key'] = f'{prefix}/{key}'
      event['value'] = value
      self._log.append(event)

  def ErrorEvent(self, msg, fmt):
    """Append a 'error' event to the current log."""
    error_event = self._CreateEventDict('error')
    error_event['msg'] = msg
    error_event['fmt'] = fmt
    self._log.append(error_event)

  def _GetEventTargetPath(self):
    """Get the 'trace2.eventtarget' path from git configuration.

    Returns:
      path: git config's 'trace2.eventtarget' path if it exists, or None
    """
    path = None
    cmd = ['config', '--get', 'trace2.eventtarget']
    # TODO(https://crbug.com/gerrit/13706): Use GitConfig when it supports
    # system git config variables.
    p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
                   bare=True)
    retval = p.Wait()
    if retval == 0:
      # Strip trailing carriage-return in path.
      path = p.stdout.rstrip('\n')
    elif retval != 1:
      # `git config --get` is documented to produce an exit status of `1` if
      # the requested variable is not present in the configuration. Report any
      # other return value as an error.
      print("repo: error: 'git config --get' call failed with return code: %r, stderr: %r" % (
          retval, p.stderr), file=sys.stderr)
    return path

  def Write(self, path=None):
    """Writes the log out to a file.

    Log is only written if 'path' or 'git config --get trace2.eventtarget'
    provide a valid path to write logs to.

    Logging filename format follows the git trace2 style of being a unique
    (exclusive writable) file.

    Args:
      path: Path to where logs should be written.

    Returns:
      log_path: Path to the log file if log is written, otherwise None
    """
    log_path = None
    # If no logging path is specified, get the path from 'trace2.eventtarget'.
    if path is None:
      path = self._GetEventTargetPath()

    # If no logging path is specified, exit.
    if path is None:
      return None

    if isinstance(path, str):
      # Get absolute path.
      path = os.path.abspath(os.path.expanduser(path))
    else:
      raise TypeError('path: str required but got %s.' % type(path))

    # Git trace2 requires a directory to write log to.

    # TODO(https://crbug.com/gerrit/13706): Support file (append) mode also.
    if not os.path.isdir(path):
      return None
    # Use NamedTemporaryFile to generate a unique filename as required by git trace2.
    try:
      with tempfile.NamedTemporaryFile(mode='x', prefix=self._sid, dir=path,
                                       delete=False) as f:
        # TODO(https://crbug.com/gerrit/13706): Support writing events as they
        # occur.
        for e in self._log:
          # Dump in compact encoding mode.
          # See 'Compact encoding' in Python docs:
          # https://docs.python.org/3/library/json.html#module-json
          json.dump(e, f, indent=None, separators=(',', ':'))
          f.write('\n')
        log_path = f.name
    except FileExistsError as err:
      print('repo: warning: git trace2 logging failed: %r' % err,
            file=sys.stderr)
      return None
    return log_path
error.py
class NoSuchProjectError(Exception):
  """A specified project does not exist in the work tree.
  """

  def __init__(self, name=None):
    super().__init__(name)
    self.name = name

  def __str__(self):
    if self.name is None:
      return 'in current directory'
    return self.name
error.py
class InvalidProjectGroupsError(Exception):
  """A specified project is not suitable for the specified groups
  """

  def __init__(self, name=None):
    super().__init__(name)
    self.name = name

  def __str__(self):
    if self.name is None:
      return 'in current directory'
    return self.name
error.py
class ManifestParseError(Exception):
  """Failed to parse the manifest file.
  """

Answer: if isinstance(cmd, GitcAvailableCommand) and not gitc_utils.get_gitc_manifest_dir():