text: #  Copyright 2008-2015 Nokia Solutions and Networks
  #
  #  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.
  
  import os
  import random
  import sys
  import time
  
  from robot.errors import DataError, FrameworkError
  from robot.output import LOGGER, loggerhelper
  from robot.result.keywordremover import KeywordRemover
  from robot.result.flattenkeywordmatcher import validate_flatten_keyword
  from robot.utils import (abspath, escape, format_time, get_link_path,
                           html_escape, is_list_like, py2to3,
                           split_args_from_name_or_path)
  
  from .gatherfailed import gather_failed_tests
  
  
  @py2to3
  class _BaseSettings(object):
      _cli_opts = {'Name'             : ('name', None),
                   'Doc'              : ('doc', None),
                   'Metadata'         : ('metadata', []),
                   'TestNames'        : ('test', []),
                   'ReRunFailed'      : ('rerunfailed', 'NONE'),
                   'SuiteNames'       : ('suite', []),
                   'SetTag'           : ('settag', []),
                   'Include'          : ('include', []),
                   'Exclude'          : ('exclude', []),
                   'Critical'         : ('critical', None),
                   'NonCritical'      : ('noncritical', None),
                   'OutputDir'        : ('outputdir', abspath('.')),
                   'Log'              : ('log', 'log.html'),
                   'Report'           : ('report', 'report.html'),
                   'XUnit'            : ('xunit', None),
                   'SplitLog'         : ('splitlog', False),
                   'TimestampOutputs' : ('timestampoutputs', False),
                   'LogTitle'         : ('logtitle', None),
                   'ReportTitle'      : ('reporttitle', None),
                   'ReportBackground' : ('reportbackground',
                                         ('#9e9', '#9e9', '#f66')),
                   'SuiteStatLevel'   : ('suitestatlevel', -1),
                   'TagStatInclude'   : ('tagstatinclude', []),
                   'TagStatExclude'   : ('tagstatexclude', []),
                   'TagStatCombine'   : ('tagstatcombine', []),
                   'TagDoc'           : ('tagdoc', []),
                   'TagStatLink'      : ('tagstatlink', []),
                   'RemoveKeywords'   : ('removekeywords', []),
                   'FlattenKeywords'  : ('flattenkeywords', []),
                   'PreRebotModifiers': ('prerebotmodifier', []),
                   'StatusRC'         : ('statusrc', True),
                   'ConsoleColors'    : ('consolecolors', 'AUTO'),
                   'StdOut'           : ('stdout', None),
                   'StdErr'           : ('stderr', None),
                   'XUnitSkipNonCritical' : ('xunitskipnoncritical', False)}
      _output_opts = ['Output', 'Log', 'Report', 'XUnit', 'DebugFile']
  
      def __init__(self, options=None, **extra_options):
          self.start_timestamp = format_time(time.time(), '', '-', '')
          self._opts = {}
          self._cli_opts = self._cli_opts.copy()
          self._cli_opts.update(self._extra_cli_opts)
          self._process_cli_opts(dict(options or {}, **extra_options))
  
      def _process_cli_opts(self, opts):
          for name, (cli_name, default) in self._cli_opts.items():
              value = opts[cli_name] if cli_name in opts else default
              if default == [] and not is_list_like(value):
                  value = [value]
              self[name] = self._process_value(name, value)
          self['TestNames'] += self['ReRunFailed']
  
      def __setitem__(self, name, value):
          if name not in self._cli_opts:
              raise KeyError("Non-existing option '%s'." % name)
          self._opts[name] = value
  
      def _process_value(self, name, value):
          if name == 'ReRunFailed':
              return gather_failed_tests(value)
          if name == 'LogLevel':
              return self._process_log_level(value)
          if value == self._get_default_value(name):
              return value
          if name in ['Name', 'Doc', 'LogTitle', 'ReportTitle']:
              if name == 'Doc':
                  value = self._escape_as_data(value)
              return value.replace('_', ' ')
          if name in ['Metadata', 'TagDoc']:
              if name == 'Metadata':
                  value = [self._escape_as_data(v) for v in value]
              return [self._process_metadata_or_tagdoc(v) for v in value]
          if name in ['Include', 'Exclude']:
              return [self._format_tag_patterns(v) for v in value]
          if name in self._output_opts and (not value or value.upper() == 'NONE'):
              return None
          if name == 'OutputDir':
              return abspath(value)
          if name in ['SuiteStatLevel', 'ConsoleWidth']:
              return self._convert_to_positive_integer_or_default(name, value)
          if name == 'VariableFiles':
              return [split_args_from_name_or_path(item) for item in value]
          if name == 'ReportBackground':
              return self._process_report_background(value)
          if name == 'TagStatCombine':
              return [self._process_tag_stat_combine(v) for v in value]
          if name == 'TagStatLink':
              return [v for v in [self._process_tag_stat_link(v) for v in value] if v]
          if name == 'Randomize':
              return self._process_randomize_value(value)
          if name == 'RemoveKeywords':
              self._validate_remove_keywords(value)
          if name == 'FlattenKeywords':
              self._validate_flatten_keywords(value)
          return value
  
      def _escape_as_data(self, value):
          return value
  
      def _process_log_level(self, level):
          level, visible_level = self._split_log_level(level.upper())
          self._opts['VisibleLogLevel'] = visible_level
          return level
  
      def _split_log_level(self, level):
          if ':' in level:
              level, visible_level = level.split(':', 1)
          else:
              visible_level = level
          self._validate_log_level_and_default(level, visible_level)
          return level, visible_level
  
      def _validate_log_level_and_default(self, log_level, default):
          if log_level not in loggerhelper.LEVELS:
              raise DataError("Invalid log level '%s'" % log_level)
          if default not in loggerhelper.LEVELS:
              raise DataError("Invalid log level '%s'" % default)
          if not loggerhelper.IsLogged(log_level)(default):
              raise DataError("Default visible log level '%s' is lower than "
                              "log level '%s'" % (default, log_level))
  
      def _process_randomize_value(self, original):
          value = original.lower()
          if ':' in value:
              value, seed = value.split(':', 1)
          else:
              seed = random.randint(0, sys.maxsize)
          if value in ('test', 'suite'):
              value += 's'
          if value not in ('tests', 'suites', 'none', 'all'):
              self._raise_invalid_option_value('--randomize', original)
          try:
              seed = int(seed)
          except ValueError:
              self._raise_invalid_option_value('--randomize', original)
          return value, seed
  
      def _raise_invalid_option_value(self, option_name, given_value):
          raise DataError("Option '%s' does not support value '%s'."
                          % (option_name, given_value))
  
      def __getitem__(self, name):
          if name not in self._opts:
              raise KeyError("Non-existing option '%s'." % name)
          if name in self._output_opts:
              return self._get_output_file(name)
          return self._opts[name]
  
      def _get_output_file(self, option):
          """Returns path of the requested output file and creates needed dirs.
  
          `option` can be 'Output', 'Log', 'Report', 'XUnit' or 'DebugFile'.
          """
          name = self._opts[option]
          if not name:
              return None
          if option == 'Log' and self._output_disabled():
              self['Log'] = None
              LOGGER.error('Log file is not created if output.xml is disabled.')
              return None
          name = self._process_output_name(option, name)
          path = abspath(os.path.join(self['OutputDir'], name))
          self._create_output_dir(os.path.dirname(path), option)
          return path
  
      def _process_output_name(self, option, name):
          base, ext = os.path.splitext(name)
          if self['TimestampOutputs']:
              base = '%s-%s' % (base, self.start_timestamp)
          ext = self._get_output_extension(ext, option)
          return base + ext
  
      def _get_output_extension(self, ext, type_):
          if ext != '':
              return ext
          if type_ in ['Output', 'XUnit']:
              return '.xml'
          if type_ in ['Log', 'Report']:
              return '.html'
          if type_ == 'DebugFile':
              return '.txt'
          raise FrameworkError("Invalid output file type: %s" % type_)
  
      def _create_output_dir(self, path, type_):
          try:
              if not os.path.exists(path):
                  os.makedirs(path)
          except EnvironmentError as err:
              raise DataError("Creating %s file directory '%s' failed: %s"
                              % (type_.lower(), path, err.strerror))
  
      def _process_metadata_or_tagdoc(self, value):
          value = value.replace('_', ' ')
          if ':' in value:
              return value.split(':', 1)
          return value, ''
  
      def _process_report_background(self, colors):
          if colors.count(':') not in [1, 2]:
              raise DataError("Invalid report background colors '%s'." % colors)
          colors = colors.split(':')
          if len(colors) == 2:
              return colors[0], colors[0], colors[1]
          return tuple(colors)
  
      def _process_tag_stat_combine(self, pattern):
          if ':' in pattern:
              pattern, title = pattern.rsplit(':', 1)
          else:
              title = ''
          return self._format_tag_patterns(pattern), title.replace('_', ' ')
  
      def _format_tag_patterns(self, pattern):
          for search, replace in [('&', 'AND'), ('AND', ' AND '), ('OR', ' OR '),
                                  ('NOT', ' NOT '), ('_', ' ')]:
              if search in pattern:
                  pattern = pattern.replace(search, replace)
          while '  ' in pattern:
              pattern = pattern.replace('  ', ' ')
          if pattern.startswith(' NOT'):
              pattern = pattern[1:]
          return pattern
  
      def _process_tag_stat_link(self, value):
          tokens = value.split(':')
          if len(tokens) >= 3:
              return tokens[0], ':'.join(tokens[1:-1]), tokens[-1]
          raise DataError("Invalid format for option '--tagstatlink'. "
                          "Expected 'tag:link:title' but got '%s'." % value)
  
      def _convert_to_positive_integer_or_default(self, name, value):
          value = self._convert_to_integer(name, value)
          return value if value > 0 else self._get_default_value(name)
  
      def _convert_to_integer(self, name, value):
          try:
              return int(value)
          except ValueError:
              raise DataError("Option '--%s' expected integer value but got '%s'."
                              % (name.lower(), value))
  
      def _get_default_value(self, name):
          return self._cli_opts[name][1]
  
      def _validate_remove_keywords(self, values):
          for value in values:
              try:
                  KeywordRemover(value)
              except DataError as err:
                  raise DataError("Invalid value for option '--removekeywords'. %s" % err)
  
      def _validate_flatten_keywords(self, values):
          try:
              validate_flatten_keyword(values)
          except DataError as err:
              raise DataError("Invalid value for option '--flattenkeywords'. %s" % err)
  
      def __contains__(self, setting):
          return setting in self._cli_opts
  
      def __unicode__(self):
          return '\n'.join('%s: %s' % (name, self._opts[name])
                           for name in sorted(self._opts))
  
      @property
      def output_directory(self):
          return self['OutputDir']
  
      @property
      def output(self):
          return self['Output']
  
      @property
      def log(self):
          return self['Log']
  
      @property
      def report(self):
          return self['Report']
  
      @property
      def xunit(self):
          return self['XUnit']
  
      @property
      def log_level(self):
          return self['LogLevel']
  
      @property
      def split_log(self):
          return self['SplitLog']
  
      @property
      def status_rc(self):
          return self['StatusRC']
  
      @property
      def xunit_skip_noncritical(self):
          return self['XUnitSkipNonCritical']
  
      @property
      def statistics_config(self):
          return {
              'suite_stat_level': self['SuiteStatLevel'],
              'tag_stat_include': self['TagStatInclude'],
              'tag_stat_exclude': self['TagStatExclude'],
              'tag_stat_combine': self['TagStatCombine'],
              'tag_stat_link': self['TagStatLink'],
              'tag_doc': self['TagDoc'],
          }
  
      @property
      def critical_tags(self):
          return self['Critical']
  
      @property
      def non_critical_tags(self):
          return self['NonCritical']
  
      @property
      def remove_keywords(self):
          return self['RemoveKeywords']
  
      @property
      def flatten_keywords(self):
          return self['FlattenKeywords']
  
      @property
      def pre_rebot_modifiers(self):
          return self['PreRebotModifiers']
  
      @property
      def console_colors(self):
          return self['ConsoleColors']
  
  
  class RobotSettings(_BaseSettings):
      _extra_cli_opts = {'Output'             : ('output', 'output.xml'),
                         'LogLevel'           : ('loglevel', 'INFO'),
                         'DryRun'             : ('dryrun', False),
                         'ExitOnFailure'      : ('exitonfailure', False),
                         'ExitOnError'        : ('exitonerror', False),
                         'SkipTeardownOnExit' : ('skipteardownonexit', False),
                         'Randomize'          : ('randomize', 'NONE'),
                         'RunEmptySuite'      : ('runemptysuite', False),
                         'WarnOnSkipped'      : ('warnonskippedfiles', False),
                         'Variables'          : ('variable', []),
                         'VariableFiles'      : ('variablefile', []),
                         'PreRunModifiers'    : ('prerunmodifier', []),
                         'Listeners'          : ('listener', []),
                         'ConsoleType'        : ('console', 'verbose'),
                         'ConsoleTypeDotted'  : ('dotted', False),
                         'ConsoleTypeQuiet'   : ('quiet', False),
                         'ConsoleWidth'       : ('consolewidth', 78),
                         'ConsoleMarkers'     : ('consolemarkers', 'AUTO'),
                         'DebugFile'          : ('debugfile', None)}
  
      def get_rebot_settings(self):
          settings = RebotSettings()
          settings._opts.update(self._opts)
          for name in ['Variables', 'VariableFiles', 'Listeners']:
              del(settings._opts[name])
          for name in ['Include', 'Exclude', 'TestNames', 'SuiteNames', 'Metadata']:
              settings._opts[name] = []
          for name in ['Name', 'Doc']:
              settings._opts[name] = None
          settings._opts['Output'] = None
          settings._opts['LogLevel'] = 'TRACE'
          settings._opts['ProcessEmptySuite'] = self['RunEmptySuite']
          return settings
  
      def _output_disabled(self):
          return self.output is None
  
      def _escape_as_data(self, value):
          return escape(value)
  
      @property
      def debug_file(self):
          return self['DebugFile']
  
      @property
      def suite_config(self):
          return {
              'name': self['Name'],
              'doc': self['Doc'],
              'metadata': dict(self['Metadata']),
              'set_tags': self['SetTag'],
              'include_tags': self['Include'],
              'exclude_tags': self['Exclude'],
              'include_suites': self['SuiteNames'],
              'include_tests': self['TestNames'],
              'empty_suite_ok': self.run_empty_suite,
              'randomize_suites': self.randomize_suites,
              'randomize_tests': self.randomize_tests,
              'randomize_seed': self.randomize_seed,
          }
  
      @property
      def randomize_seed(self):
          return self['Randomize'][1]
  
      @property
      def randomize_suites(self):
          return self['Randomize'][0] in ('suites', 'all')
  
      @property
      def randomize_tests(self):
          return self['Randomize'][0] in ('tests', 'all')
  
      @property
      def dry_run(self):
          return self['DryRun']
      @property
      def exit_on_failure(self):
          return self['ExitOnFailure']
  
      @property
      def exit_on_error(self):
          return self['ExitOnError']
  
      @property
      def skip_teardown_on_exit(self):
          return self['SkipTeardownOnExit']
  
      @property
      def console_output_config(self):
          return {
              'type':    self.console_type,
              'width':   self.console_width,
              'colors':  self.console_colors,
              'markers': self.console_markers,
              'stdout':  self['StdOut'],
              'stderr':  self['StdErr']
          }
  
      @property
      def console_type(self):
          if self['ConsoleTypeQuiet']:
              return 'quiet'
          if self['ConsoleTypeDotted']:
              return 'dotted'
          return self['ConsoleType']
  
      @property
      def console_width(self):
          return self['ConsoleWidth']
  
      @property
      def console_markers(self):
          return self['ConsoleMarkers']
  
      @property
      def pre_run_modifiers(self):
          return self['PreRunModifiers']
  
      @property
      def run_empty_suite(self):
          return self['RunEmptySuite']
  
      @property
      def variables(self):
          return self['Variables']
  
      @property
      def variable_files(self):
          return self['VariableFiles']
  
  
  class RebotSettings(_BaseSettings):
      _extra_cli_opts = {'Output'            : ('output', None),
                         'LogLevel'          : ('loglevel', 'TRACE'),
                         'ProcessEmptySuite' : ('processemptysuite', False),
                         'StartTime'         : ('starttime', None),
                         'EndTime'           : ('endtime', None),
                         'Merge'             : ('merge', False)}
  
      def _output_disabled(self):
          return False
  
      @property
      def suite_config(self):
          return {
              'name': self['Name'],
              'doc': self['Doc'],
              'metadata': dict(self['Metadata']),
              'set_tags': self['SetTag'],
              'include_tags': self['Include'],
              'exclude_tags': self['Exclude'],
              'include_suites': self['SuiteNames'],
              'include_tests': self['TestNames'],
              'empty_suite_ok': self.process_empty_suite,
              'remove_keywords': self.remove_keywords,
              'log_level': self['LogLevel'],
              'critical_tags': self.critical_tags,
              'non_critical_tags': self.non_critical_tags,
              'start_time': self['StartTime'],
              'end_time': self['EndTime']
          }
  
      @property
      def log_config(self):
          if not self.log:
              return {}
          return {
              'title': html_escape(self['LogTitle'] or ''),
              'reportURL': self._url_from_path(self.log, self.report),
              'splitLogBase': os.path.basename(os.path.splitext(self.log)[0]),
              'defaultLevel': self['VisibleLogLevel']
          }
  
      @property
      def report_config(self):
          if not self.report:
              return {}
          return {
              'title': html_escape(self['ReportTitle'] or ''),
              'logURL': self._url_from_path(self.report, self.log),
              'background' : self._resolve_background_colors(),
          }
  
      def _url_from_path(self, source, destination):
          if not destination:
              return None
          return get_link_path(destination, os.path.dirname(source))
  
      def _resolve_background_colors(self):
          colors = self['ReportBackground']
          return {'pass': colors[0], 'nonCriticalFail': colors[1], 'fail': colors[2]}
  
      @property
      def merge(self):
          return self['Merge']
  
      @property
      def console_output_config(self):
          return {
              'colors':  self.console_colors,
              'stdout':  self['StdOut'],
              'stderr':  self['StdErr']
          }
  
      @property
      def process_empty_suite(self):
          return self['ProcessEmptySuite']
  