File: //proc/thread-self/root/snap/google-cloud-cli/396/lib/googlecloudsdk/command_lib/survey/question.py
# -*- coding: utf-8 -*- #
# Copyright 2018 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""This module contains all survey question types."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
import abc
from googlecloudsdk.command_lib.survey import util as survey_util
from googlecloudsdk.core import exceptions
from googlecloudsdk.core import log
import six
class Error(exceptions.Error):
  """Base error class for this module."""
  pass
class AnswerRejectedError(Error):
  """Raises when answer is rejected."""
  pass
class RetrieveAnswerOfUnansweredQuestion(Error):
  """Raises when retrieving answer from an unanswered question."""
  pass
class QuestionCreationError(Error):
  """Raises when question cannot be created with the provided data."""
  def __init__(self, required_fields):
    required_fields_in_string = ', '.join(required_fields)
    super(QuestionCreationError, self).__init__(
        'Question cannot be created because either some '
        'required field is missing or there are redundant fields. Required '
        'fields are {}.'.format(required_fields_in_string))
class Question(six.with_metaclass(abc.ABCMeta, object)):
  """Base class for survey questions.
  Attributes:
    _question: str, the question to ask.
    _instruction: str, instruction on how to answer the question.
    _instruction_on_rejection: str, instruction after the answer is rejected.
    _answer: str, the answer to the question.
  """
  def __init__(self,
               question,
               instruction,
               instruction_on_rejection=None,
               answer=None):
    self._question = question
    self._instruction = instruction
    self._instruction_on_rejection = instruction_on_rejection
    self._answer = answer
  @abc.abstractmethod
  def FromDictionary(self, content):
    pass
  @property
  def question(self):
    return self._question
  @property
  def instruction(self):
    return self._instruction
  @property
  def instruction_on_rejection(self):
    return self._instruction_on_rejection
  def PrintQuestion(self):
    self._PrintQuestion()
    log.out.flush()
  @abc.abstractmethod
  def _PrintQuestion(self):
    pass
  def PrintInstruction(self):
    if self._instruction:
      log.err.write(self._instruction)
  def PrintInstructionOnRejection(self):
    if self._instruction_on_rejection:
      log.err.write(self._instruction_on_rejection)
  @abc.abstractmethod
  def AcceptAnswer(self, answer):
    """Returns True if answer is accepted, otherwise returns False."""
    pass
  def IsAnswered(self):
    return self._answer is not None
  def AnswerQuestion(self, answer):
    if self.AcceptAnswer(answer):
      self._answer = answer
    else:
      raise AnswerRejectedError('Answer is invalid.')
  @property
  def answer(self):
    if self.IsAnswered():
      return self._answer
    else:
      raise RetrieveAnswerOfUnansweredQuestion('No answer for this question.')
  def __eq__(self, other):
    if isinstance(other, self.__class__):
      # pylint: disable=protected-access
      return (self._question == other._question and
              self._instruction == other._instruction and
              self._instruction_on_rejection == other._instruction_on_rejection)
      # pylint: enable=protected-access
    return False
  def __ne__(self, other):
    return not self == other  # pylint: disable=g-comparison-negation
  def __hash__(self):
    return hash((self._question, self._instruction,
                 self._instruction_on_rejection))
class MultiChoiceQuestion(Question):
  """Multi-choice question.
  Attributes:
    _choices: [str], list of choices.
  """
  def __init__(self,
               question,
               instruction,
               instruction_on_rejection,
               choices,
               answer=None):
    super(MultiChoiceQuestion, self).__init__(
        question, instruction, instruction_on_rejection, answer=answer)
    self._choices = choices
  @classmethod
  def FromDictionary(cls, content):
    try:
      return cls(**content)
    except TypeError:
      raise QuestionCreationError(required_fields=[
          'question', 'instruction', 'instruction_on_rejection', 'choices'
      ])
  def _PrintQuestion(self):
    """Prints question and lists all choices."""
    # index choices from 1
    question_repr = self._FormatQuestion(
        indexes=range(1,
                      len(self._choices) + 1))
    log.Print(question_repr)
  def _FormatQuestion(self, indexes):
    """Formats question to present to users."""
    choices_repr = [
        '[{}] {}'.format(index, msg)
        for index, msg in zip(indexes, self._choices)
    ]
    choices_repr = [survey_util.Indent(content, 2) for content in choices_repr]
    choices_repr = '\n'.join(choices_repr)
    question_repr = survey_util.Indent(self._question, 1)
    return '\n'.join([question_repr, choices_repr])
  def AcceptAnswer(self, answer):
    """Returns True if answer is accepted, otherwise returns False."""
    try:
      answer_int = int(answer)
    except ValueError:
      return False
    else:
      return 1 <= answer_int <= len(self._choices)
  def Choice(self, index):
    """Gets the choice at the given index."""
    # choices are indexed from 1
    return self._choices[index - 1]
  def __eq__(self, other):
    if isinstance(other, self.__class__):
      # pylint: disable=protected-access
      return (self._question == other._question and
              self._instruction == other._instruction and
              self._instruction_on_rejection == other._instruction_on_rejection
              and self._choices == other._choices)
      # pylint: enable=protected-access
    return False
  def __hash__(self):
    return hash((self._question, self._instruction,
                 self._instruction_on_rejection, tuple(self._choices)))
  def __len__(self):
    return len(self._choices)
class SatisfactionQuestion(MultiChoiceQuestion):
  """Customer satisfaction question."""
  def IsSatisfied(self):
    """Returns true is user answers "Very satisfied" or "Somewhat satisfied"."""
    if self.IsAnswered():
      return int(self.answer) > 3
    else:
      return None
  def _PrintQuestion(self):
    # index choices in the reverse order, e.g. 5 is "Very satisfied" and 1 is
    # "Very dissatisfied".
    choice_indexes = range(len(self._choices), 0, -1)
    question_repr = self._FormatQuestion(indexes=choice_indexes)
    log.Print(question_repr)
  def Choice(self, index):
    """Gets the choice at the given index."""
    # choices are indexed in the reverse order
    return self._choices[len(self._choices) - index]
class RatingQuestion(Question):
  """"Rating question.
  Attributes:
     min_answer: int, minimum acceptable value for answer.
     max_answer: int, maximum acceptable value for answer.
  """
  @classmethod
  def FromDictionary(cls, content):
    try:
      return cls(**content)
    except TypeError:
      raise QuestionCreationError(required_fields=[
          'question', 'instruction', 'instruction_on_rejection', 'min_answer',
          'max_answer'
      ])
  def __init__(self,
               question,
               instruction,
               instruction_on_rejection,
               min_answer,
               max_answer,
               answer=None):
    super(RatingQuestion, self).__init__(
        question=question,
        instruction=instruction,
        instruction_on_rejection=instruction_on_rejection,
        answer=answer)
    self._min = min_answer
    self._max = max_answer
  def _PrintQuestion(self):
    question = survey_util.Indent(self._question, 1)
    log.Print(question)
  def AcceptAnswer(self, answer):
    try:
      answer_int = int(answer)
      return self._min <= answer_int <= self._max
    except ValueError:
      return False
  def __eq__(self, other):
    if isinstance(other, self.__class__):
      # pylint: disable=protected-access
      return (self._question == other._question and
              self._instruction == other._instruction and
              self._instruction_on_rejection == other._instruction_on_rejection
              and self._min == other._min and self._max == other._max)
      # pylint: enable=protected-access
    return False
  def __ne__(self, other):
    return not self == other  # pylint: disable=g-comparison-negation
  def __hash__(self):
    return hash((self._question, self._instruction,
                 self._instruction_on_rejection, self._min, self._max))
class NPSQuestion(RatingQuestion):
  """Net promoter score question."""
class FreeTextQuestion(Question):
  """Free text question."""
  def _PrintQuestion(self):
    question = survey_util.Indent(self._question, 1)
    log.Print(question)
  def AcceptAnswer(self, answer):
    """Returns True if answer is accepted, otherwise returns False.
    Accepts any answer for free text question.
    Args:
      answer: str, the answer to check.
    Returns:
       True
    """
    return True
  @classmethod
  def FromDictionary(cls, content):
    try:
      return cls(**content)
    except TypeError:
      raise QuestionCreationError(required_fields=['question', 'instruction'])