text: # Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
  # Use of this source code is governed by a BSD-style license that can be
  # found in the LICENSE file.
  """The experiment file module. It manages the input file of crosperf."""
  
  from __future__ import print_function
  import os.path
  import re
  from settings_factory import SettingsFactory
  
  
  class ExperimentFile(object):
    """Class for parsing the experiment file format.
  
    The grammar for this format is:
  
    experiment = { _FIELD_VALUE_RE | settings }
    settings = _OPEN_SETTINGS_RE
               { _FIELD_VALUE_RE }
               _CLOSE_SETTINGS_RE
  
    Where the regexes are terminals defined below. This results in an format
    which looks something like:
  
    field_name: value
    settings_type: settings_name {
      field_name: value
      field_name: value
    }
    """
  
    # Field regex, e.g. "iterations: 3"
    _FIELD_VALUE_RE = re.compile(r'(\+)?\s*(\w+?)(?:\.(\S+))?\s*:\s*(.*)')
    # Open settings regex, e.g. "label {"
    _OPEN_SETTINGS_RE = re.compile(r'(?:([\w.-]+):)?\s*([\w.-]+)\s*{')
    # Close settings regex.
    _CLOSE_SETTINGS_RE = re.compile(r'}')
  
    def __init__(self, experiment_file, overrides=None):
      """Construct object from file-like experiment_file.
  
      Args:
        experiment_file: file-like object with text description of experiment.
        overrides: A settings object that will override fields in other settings.
  
      Raises:
        Exception: if invalid build type or description is invalid.
      """
      self.all_settings = []
      self.global_settings = SettingsFactory().GetSettings('global', 'global')
      self.all_settings.append(self.global_settings)
  
      self._Parse(experiment_file)
  
      for settings in self.all_settings:
        settings.Inherit()
        settings.Validate()
        if overrides:
          settings.Override(overrides)
  
    def GetSettings(self, settings_type):
      """Return nested fields from the experiment file."""
      res = []
      for settings in self.all_settings:
        if settings.settings_type == settings_type:
          res.append(settings)
      return res
  
    def GetGlobalSettings(self):
      """Return the global fields from the experiment file."""
      return self.global_settings
  
    def _ParseField(self, reader):
      """Parse a key/value field."""
      line = reader.CurrentLine().strip()
      match = ExperimentFile._FIELD_VALUE_RE.match(line)
      append, name, _, text_value = match.groups()
      return (name, text_value, append)
  
    def _ParseSettings(self, reader):
      """Parse a settings block."""
      line = reader.CurrentLine().strip()
      match = ExperimentFile._OPEN_SETTINGS_RE.match(line)
      settings_type = match.group(1)
      if settings_type is None:
        settings_type = ''
      settings_name = match.group(2)
      settings = SettingsFactory().GetSettings(settings_name, settings_type)
      settings.SetParentSettings(self.global_settings)
  
      while reader.NextLine():
        line = reader.CurrentLine().strip()
  
        if not line:
          continue
        elif ExperimentFile._FIELD_VALUE_RE.match(line):
          field = self._ParseField(reader)
          settings.SetField(field[0], field[1], field[2])
        elif ExperimentFile._CLOSE_SETTINGS_RE.match(line):
          return settings
  
      raise EOFError('Unexpected EOF while parsing settings block.')
  
    def _Parse(self, experiment_file):
      """Parse experiment file and create settings."""
      reader = ExperimentFileReader(experiment_file)
      settings_names = {}
      try:
        while reader.NextLine():
          line = reader.CurrentLine().strip()
  
          if not line:
            continue
          elif ExperimentFile._OPEN_SETTINGS_RE.match(line):
            new_settings = self._ParseSettings(reader)
            if new_settings.name in settings_names:
              raise SyntaxError(
                  "Duplicate settings name: '%s'." % new_settings.name)
            settings_names[new_settings.name] = True
            self.all_settings.append(new_settings)
          elif ExperimentFile._FIELD_VALUE_RE.match(line):
            field = self._ParseField(reader)
            self.global_settings.SetField(field[0], field[1], field[2])
          else:
            raise IOError('Unexpected line.')
      except Exception, err:
        raise RuntimeError('Line %d: %s\n==> %s' % (reader.LineNo(), str(err),
                                                    reader.CurrentLine(False)))
  
    def Canonicalize(self):
      """Convert parsed experiment file back into an experiment file."""
      res = ''
      board = ''
      for field_name in self.global_settings.fields:
        field = self.global_settings.fields[field_name]
        if field.assigned:
          res += '%s: %s\n' % (field.name, field.GetString())
        if field.name == 'board':
          board = field.GetString()
      res += '\n'
  
      for settings in self.all_settings:
        if settings.settings_type != 'global':
          res += '%s: %s {\n' % (settings.settings_type, settings.name)
          for field_name in settings.fields:
            field = settings.fields[field_name]
            if field.assigned:
              res += '\t%s: %s\n' % (field.name, field.GetString())
              if field.name == 'chromeos_image':
                real_file = (
                    os.path.realpath(os.path.expanduser(field.GetString())))
                if real_file != field.GetString():
                  res += '\t#actual_image: %s\n' % real_file
              if field.name == 'build':
                chromeos_root_field = settings.fields['chromeos_root']
                if chromeos_root_field:
                  chromeos_root = chromeos_root_field.GetString()
                value = field.GetString()
                autotest_field = settings.fields['autotest_path']
                autotest_path = ''
                if autotest_field.assigned:
                  autotest_path = autotest_field.GetString()
                image_path, autotest_path = settings.GetXbuddyPath(
                    value, autotest_path, board, chromeos_root, 'quiet')
                res += '\t#actual_image: %s\n' % image_path
                if not autotest_field.assigned:
                  res += '\t#actual_autotest_path: %s\n' % autotest_path
  
          res += '}\n\n'
  
      return res
  
  
  class ExperimentFileReader(object):
    """Handle reading lines from an experiment file."""
  
    def __init__(self, file_object):
      self.file_object = file_object
      self.current_line = None
      self.current_line_no = 0
  
    def CurrentLine(self, strip_comment=True):
      """Return the next line from the file, without advancing the iterator."""
      if strip_comment:
        return self._StripComment(self.current_line)
      return self.current_line
  
    def NextLine(self, strip_comment=True):
      """Advance the iterator and return the next line of the file."""
      self.current_line_no += 1
      self.current_line = self.file_object.readline()
      return self.CurrentLine(strip_comment)
  
    def _StripComment(self, line):
      """Strip comments starting with # from a line."""
      if '#' in line:
        line = line[:line.find('#')] + line[-1]
      return line
  
    def LineNo(self):
      """Return the current line number."""
      return self.current_line_no
  