[ Index ]

PHP Cross Reference of Phabricator

title

Body

[close]

/src/applications/differential/parser/ -> DifferentialCommitMessageParser.php (source)

   1  <?php
   2  
   3  /**
   4   * Parses commit messages (containing relatively freeform text with textual
   5   * field labels) into a dictionary of fields.
   6   *
   7   *   $parser = id(new DifferentialCommitMessageParser())
   8   *     ->setLabelMap($label_map)
   9   *     ->setTitleKey($key_title)
  10   *     ->setSummaryKey($key_summary);
  11   *
  12   *   $fields = $parser->parseCorpus($corpus);
  13   *   $errors = $parser->getErrors();
  14   *
  15   * This is used by Differential to parse messages entered from the command line.
  16   *
  17   * @task config   Configuring the Parser
  18   * @task parse    Parsing Messages
  19   * @task support  Support Methods
  20   * @task internal Internals
  21   */
  22  final class DifferentialCommitMessageParser {
  23  
  24    private $labelMap;
  25    private $titleKey;
  26    private $summaryKey;
  27    private $errors;
  28  
  29  
  30  /* -(  Configuring the Parser  )--------------------------------------------- */
  31  
  32  
  33    /**
  34     * @task config
  35     */
  36    public function setLabelMap(array $label_map) {
  37      $this->labelMap = $label_map;
  38      return $this;
  39    }
  40  
  41  
  42    /**
  43     * @task config
  44     */
  45    public function setTitleKey($title_key) {
  46      $this->titleKey = $title_key;
  47      return $this;
  48    }
  49  
  50  
  51    /**
  52     * @task config
  53     */
  54    public function setSummaryKey($summary_key) {
  55      $this->summaryKey = $summary_key;
  56      return $this;
  57    }
  58  
  59  
  60  /* -(  Parsing Messages  )--------------------------------------------------- */
  61  
  62  
  63    /**
  64     * @task parse
  65     */
  66    public function parseCorpus($corpus) {
  67      $this->errors = array();
  68  
  69      $label_map = $this->labelMap;
  70      $key_title = $this->titleKey;
  71      $key_summary = $this->summaryKey;
  72  
  73      if (!$key_title || !$key_summary || ($label_map === null)) {
  74        throw new Exception(
  75          pht(
  76            'Expected labelMap, summaryKey and titleKey to be set before '.
  77            'parsing a corpus.'));
  78      }
  79  
  80      $label_regexp = $this->buildLabelRegexp($label_map);
  81  
  82      // NOTE: We're special casing things here to make the "Title:" label
  83      // optional in the message.
  84      $field = $key_title;
  85  
  86      $seen = array();
  87      $lines = explode("\n", trim($corpus));
  88      $field_map = array();
  89      foreach ($lines as $key => $line) {
  90        $match = null;
  91        if (preg_match($label_regexp, $line, $match)) {
  92          $lines[$key] = trim($match['text']);
  93          $field = $label_map[self::normalizeFieldLabel($match['field'])];
  94          if (!empty($seen[$field])) {
  95            $this->errors[] = pht(
  96              'Field "%s" occurs twice in commit message!',
  97              $field);
  98          }
  99          $seen[$field] = true;
 100        }
 101        $field_map[$key] = $field;
 102      }
 103  
 104      $fields = array();
 105      foreach ($lines as $key => $line) {
 106        $fields[$field_map[$key]][] = $line;
 107      }
 108  
 109      // This is a piece of special-cased magic which allows you to omit the
 110      // field labels for "title" and "summary". If the user enters a large block
 111      // of text at the beginning of the commit message with an empty line in it,
 112      // treat everything before the blank line as "title" and everything after
 113      // as "summary".
 114      if (isset($fields[$key_title]) && empty($fields[$key_summary])) {
 115        $lines = $fields[$key_title];
 116        for ($ii = 0; $ii < count($lines); $ii++) {
 117          if (strlen(trim($lines[$ii])) == 0) {
 118            break;
 119          }
 120        }
 121        if ($ii != count($lines)) {
 122          $fields[$key_title] = array_slice($lines, 0, $ii);
 123          $summary = array_slice($lines, $ii);
 124          if (strlen(trim(implode("\n", $summary)))) {
 125            $fields[$key_summary] = $summary;
 126          }
 127        }
 128      }
 129  
 130      // Implode all the lines back into chunks of text.
 131      foreach ($fields as $name => $lines) {
 132        $data = rtrim(implode("\n", $lines));
 133        $data = ltrim($data, "\n");
 134        $fields[$name] = $data;
 135      }
 136  
 137      // This is another piece of special-cased magic which allows you to
 138      // enter a ridiculously long title, or just type a big block of stream
 139      // of consciousness text, and have some sort of reasonable result conjured
 140      // from it.
 141      if (isset($fields[$key_title])) {
 142        $terminal = '...';
 143        $title = $fields[$key_title];
 144        $short = id(new PhutilUTF8StringTruncator())
 145          ->setMaximumGlyphs(250)
 146          ->setTerminator($terminal)
 147          ->truncateString($title);
 148  
 149        if ($short != $title) {
 150  
 151          // If we shortened the title, split the rest into the summary, so
 152          // we end up with a title like:
 153          //
 154          //    Title title tile title title...
 155          //
 156          // ...and a summary like:
 157          //
 158          //    ...title title title.
 159          //
 160          //    Summary summary summary summary.
 161  
 162          $summary = idx($fields, $key_summary, '');
 163          $offset = strlen($short) - strlen($terminal);
 164          $remainder = ltrim(substr($fields[$key_title], $offset));
 165          $summary = '...'.$remainder."\n\n".$summary;
 166          $summary = rtrim($summary, "\n");
 167  
 168          $fields[$key_title] = $short;
 169          $fields[$key_summary] = $summary;
 170        }
 171      }
 172  
 173      return $fields;
 174    }
 175  
 176  
 177    /**
 178     * @task parse
 179     */
 180    public function getErrors() {
 181      return $this->errors;
 182    }
 183  
 184  
 185  /* -(  Support Methods  )---------------------------------------------------- */
 186  
 187  
 188    /**
 189     * @task support
 190     */
 191    public static function normalizeFieldLabel($label) {
 192      return phutil_utf8_strtolower($label);
 193    }
 194  
 195  
 196  /* -(  Internals  )---------------------------------------------------------- */
 197  
 198  
 199    /**
 200     * @task internal
 201     */
 202    private function buildLabelRegexp(array $label_map) {
 203      $field_labels = array_keys($label_map);
 204      foreach ($field_labels as $key => $label) {
 205        $field_labels[$key] = preg_quote($label, '/');
 206      }
 207      $field_labels = implode('|', $field_labels);
 208  
 209      $field_pattern = '/^(?P<field>'.$field_labels.'):(?P<text>.*)$/i';
 210  
 211      return $field_pattern;
 212    }
 213  
 214  }


Generated: Sun Nov 30 09:20:46 2014 Cross-referenced by PHPXref 0.7.1