PageRenderTime 46ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/src/parser/PhutilSimpleOptions.php

http://github.com/facebook/libphutil
PHP | 195 lines | 113 code | 25 blank | 57 comment | 28 complexity | 78afb7addb5f141eb03a0a936378e738 MD5 | raw file
Possible License(s): Apache-2.0
  1. <?php
  2. /**
  3. * Utilities for parsing simple option lists used in Remarkup, like codeblocks:
  4. *
  5. * lang=text
  6. * lang=php, name=example.php, lines=30, counterexample
  7. *
  8. * @task parse Parsing Simple Options
  9. * @task unparse Unparsing Simple Options
  10. * @task config Parser Configuration
  11. * @task internal Internals
  12. */
  13. final class PhutilSimpleOptions extends Phobject {
  14. private $caseSensitive;
  15. /* -( Parsing Simple Options )--------------------------------------------- */
  16. /**
  17. * Convert a simple option list into a dict. For example:
  18. *
  19. * legs=4, eyes=2
  20. *
  21. * ...becomes:
  22. *
  23. * array(
  24. * 'legs' => '4',
  25. * 'eyes' => '2',
  26. * );
  27. *
  28. * @param string Input option list.
  29. * @return dict Parsed dictionary.
  30. * @task parse
  31. */
  32. public function parse($input) {
  33. $result = array();
  34. $lexer = new PhutilSimpleOptionsLexer();
  35. $tokens = $lexer->getNiceTokens($input);
  36. $state = 'key';
  37. $pairs = array();
  38. foreach ($tokens as $token) {
  39. list($type, $value) = $token;
  40. switch ($state) {
  41. case 'key':
  42. if ($type != 'word') {
  43. return array();
  44. }
  45. if (!strlen($value)) {
  46. return array();
  47. }
  48. $key = $this->normalizeKey($value);
  49. $state = '=';
  50. break;
  51. case '=':
  52. if ($type == '=') {
  53. $state = 'value';
  54. break;
  55. }
  56. if ($type == ',') {
  57. $pairs[] = array($key, true);
  58. $state = 'key';
  59. break;
  60. }
  61. return array();
  62. case 'value':
  63. if ($type == ',') {
  64. $pairs[] = array($key, null);
  65. $state = 'key';
  66. break;
  67. }
  68. if ($type != 'word') {
  69. return array();
  70. }
  71. $pairs[] = array($key, $value);
  72. $state = ',';
  73. break;
  74. case ',':
  75. if ($type == 'word') {
  76. $pair = array_pop($pairs);
  77. $pair[1] .= $value;
  78. $pairs[] = $pair;
  79. break;
  80. }
  81. if ($type != ',') {
  82. return array();
  83. }
  84. $state = 'key';
  85. break;
  86. }
  87. }
  88. if ($state == '=') {
  89. $pairs[] = array($key, true);
  90. }
  91. if ($state == 'value') {
  92. $pairs[] = array($key, null);
  93. }
  94. $result = array();
  95. foreach ($pairs as $pair) {
  96. list($key, $value) = $pair;
  97. if ($value === null) {
  98. unset($result[$key]);
  99. } else {
  100. $result[$key] = $value;
  101. }
  102. }
  103. return $result;
  104. }
  105. /* -( Unparsing Simple Options )------------------------------------------- */
  106. /**
  107. * Convert a dictionary into a simple option list. For example:
  108. *
  109. * array(
  110. * 'legs' => '4',
  111. * 'eyes' => '2',
  112. * );
  113. *
  114. * ...becomes:
  115. *
  116. * legs=4, eyes=2
  117. *
  118. * @param dict Input dictionary.
  119. * @param string Additional characters to escape.
  120. * @return string Unparsed option list.
  121. */
  122. public function unparse(array $options, $escape = '') {
  123. $result = array();
  124. foreach ($options as $name => $value) {
  125. $name = $this->normalizeKey($name);
  126. if (!strlen($value)) {
  127. continue;
  128. }
  129. if ($value === true) {
  130. $result[] = $this->quoteString($name, $escape);
  131. } else {
  132. $qn = $this->quoteString($name, $escape);
  133. $qv = $this->quoteString($value, $escape);
  134. $result[] = $qn.'='.$qv;
  135. }
  136. }
  137. return implode(', ', $result);
  138. }
  139. /* -( Parser Configuration )----------------------------------------------- */
  140. /**
  141. * Configure case sensitivity of the parser. By default, the parser is
  142. * case insensitive, so "legs=4" has the same meaning as "LEGS=4". If you
  143. * set it to be case sensitive, the keys have different meanings.
  144. *
  145. * @param bool True to make the parser case sensitive, false (default) to
  146. * make it case-insensitive.
  147. * @return this
  148. * @task config
  149. */
  150. public function setCaseSensitive($case_sensitive) {
  151. $this->caseSensitive = $case_sensitive;
  152. return $this;
  153. }
  154. /* -( Internals )---------------------------------------------------------- */
  155. private function normalizeKey($key) {
  156. if (!strlen($key)) {
  157. throw new Exception(pht('Empty key is invalid!'));
  158. }
  159. if (!$this->caseSensitive) {
  160. $key = strtolower($key);
  161. }
  162. return $key;
  163. }
  164. private function quoteString($string, $escape) {
  165. if (preg_match('/[^a-zA-Z0-9]/', $string)) {
  166. $string = '"'.addcslashes($string, '\\\'"'.$escape).'"';
  167. }
  168. return $string;
  169. }
  170. }