<commit_before><?php

final class ArcanistBaseCommitParser extends Phobject {

  private $api;
  private $try;
  private $verbose = false;

  public function __construct(ArcanistRepositoryAPI $api) {
    $this->api = $api;
    return $this;
  }

  private function tokenizeBaseCommitSpecification($raw_spec) {
    if (!$raw_spec) {
      return array();
    }

    $spec = preg_split('/\s*,\s*/', $raw_spec);
    $spec = array_filter($spec);

    foreach ($spec as $rule) {
      if (strpos($rule, ':') === false) {
        throw new ArcanistUsageException(
          pht(
            "Rule '%s' is invalid, it must have a type and name like '%s'.",
            $rule,
            'arc:upstream'));
      }
    }

    return $spec;
  }

  private function log($message) {
    if ($this->verbose) {
      fwrite(STDERR, $message."\n");
    }
  }

  public function resolveBaseCommit(array $specs) {
    $specs += array(
      'runtime' => '',
      'local'   => '',
      'project' => '',
      'user'    => '',
      'system'  => '',
    );

    foreach ($specs as $source => $spec) {
      $specs[$source] = self::tokenizeBaseCommitSpecification($spec);
    }

    $this->try = array(
      'runtime',
      'local',
      'project',
      'user',
      'system',
    );

    while ($this->try) {
      $source = head($this->try);

      if (!idx($specs, $source)) {
        $this->log(pht("No rules left from source '%s'.", $source));
        array_shift($this->try);
        continue;
      }

      $this->log(pht("Trying rules from source '%s'.", $source));

      $rules = &$specs[$source];
      while ($rule = array_shift($rules)) {
        $this->log(pht("Trying rule '%s'.", $rule));

        $commit = $this->resolveRule($rule, $source);

        if ($commit === false) {
          // If a rule returns false, it means to go to the next ruleset.
          break;
        } else if ($commit !== null) {
          $this->log(pht(
            "Resolved commit '%s' from rule '%s'.",
            $commit,
            $rule));
          return $commit;
        }
      }
    }

    return null;
  }

  /**
   * Handle resolving individual rules.
   */
  private function resolveRule($rule, $source) {
    // NOTE: Returning `null` from this method means "no match".
    // Returning `false` from this method means "stop current ruleset".

    list($type, $name) = explode(':', $rule, 2);
    switch ($type) {
      case 'literal':
        return $name;
      case 'git':
      case 'hg':
        return $this->api->resolveBaseCommitRule($rule, $source);
      case 'arc':
        return $this->resolveArcRule($rule, $name, $source);
      default:
        throw new ArcanistUsageException(
          pht(
            "Base commit rule '%s' (from source '%s') ".
            "is not a recognized rule.",
            $rule,
            $source));
    }
  }


  /**
   * Handle resolving "arc:*" rules.
   */
  private function resolveArcRule($rule, $name, $source) {
    $name = $this->updateLegacyRuleName($name);

    switch ($name) {
      case 'verbose':
        $this->verbose = true;
        $this->log(pht('Enabled verbose mode.'));
        break;
      case 'prompt':
        $reason = pht('it is what you typed when prompted.');
        $this->api->setBaseCommitExplanation($reason);
        return phutil_console_prompt(pht('Against which commit?'));
      case 'local':
      case 'user':
      case 'project':
      case 'runtime':<commit_msg>Allow users to skip "arc:prompt" by entering no text<commit_after><?php

final class ArcanistBaseCommitParser extends Phobject {

  private $api;
  private $try;
  private $verbose = false;

  public function __construct(ArcanistRepositoryAPI $api) {
    $this->api = $api;
    return $this;
  }

  private function tokenizeBaseCommitSpecification($raw_spec) {
    if (!$raw_spec) {
      return array();
    }

    $spec = preg_split('/\s*,\s*/', $raw_spec);
    $spec = array_filter($spec);

    foreach ($spec as $rule) {
      if (strpos($rule, ':') === false) {
        throw new ArcanistUsageException(
          pht(
            "Rule '%s' is invalid, it must have a type and name like '%s'.",
            $rule,
            'arc:upstream'));
      }
    }

    return $spec;
  }

  private function log($message) {
    if ($this->verbose) {
      fwrite(STDERR, $message."\n");
    }
  }

  public function resolveBaseCommit(array $specs) {
    $specs += array(
      'runtime' => '',
      'local'   => '',
      'project' => '',
      'user'    => '',
      'system'  => '',
    );

    foreach ($specs as $source => $spec) {
      $specs[$source] = self::tokenizeBaseCommitSpecification($spec);
    }

    $this->try = array(
      'runtime',
      'local',
      'project',
      'user',
      'system',
    );

    while ($this->try) {
      $source = head($this->try);

      if (!idx($specs, $source)) {
        $this->log(pht("No rules left from source '%s'.", $source));
        array_shift($this->try);
        continue;
      }

      $this->log(pht("Trying rules from source '%s'.", $source));

      $rules = &$specs[$source];
      while ($rule = array_shift($rules)) {
        $this->log(pht("Trying rule '%s'.", $rule));

        $commit = $this->resolveRule($rule, $source);

        if ($commit === false) {
          // If a rule returns false, it means to go to the next ruleset.
          break;
        } else if ($commit !== null) {
          $this->log(pht(
            "Resolved commit '%s' from rule '%s'.",
            $commit,
            $rule));
          return $commit;
        }
      }
    }

    return null;
  }

  /**
   * Handle resolving individual rules.
   */
  private function resolveRule($rule, $source) {
    // NOTE: Returning `null` from this method means "no match".
    // Returning `false` from this method means "stop current ruleset".

    list($type, $name) = explode(':', $rule, 2);
    switch ($type) {
      case 'literal':
        return $name;
      case 'git':
      case 'hg':
        return $this->api->resolveBaseCommitRule($rule, $source);
      case 'arc':
        return $this->resolveArcRule($rule, $name, $source);
      default:
        throw new ArcanistUsageException(
          pht(
            "Base commit rule '%s' (from source '%s') ".
            "is not a recognized rule.",
            $rule,
            $source));
    }
  }


  /**
   * Handle resolving "arc:*" rules.
   */
  private function resolveArcRule($rule, $name, $source) {
    $name = $this->updateLegacyRuleName($name);

    switch ($name) {
      case 'verbose':
        $this->verbose = true;
        $this->log(pht('Enabled verbose mode.'));
        break;
      case 'prompt':
        $reason = pht('it is what you typed when prompted.');
        $this->api->setBaseCommitExplanation($reason);
        $result = phutil_console_prompt(pht('Against which commit?'));
        if (!strlen($result)) {
          // Allow the user to continue to the next rule by entering no
          // text.
          return null;
        }
        return $result;
      case 'local':
      case 'user':
      case 'project':
      case 'runtime':
{'fraction_non_alphanumeric': 0.16204481792717088, 'fraction_numerical': 0.00028011204481792715, 'mean_word_length': 2.088667820069204, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 53, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1774116', 'n_tokens_mistral': 2227, 'n_tokens_neox': 2174, 'n_words': 574}