diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -260,6 +260,7 @@ 'PhutilPersonaAuthAdapter' => 'auth/PhutilPersonaAuthAdapter.php', 'PhutilPhabricatorAuthAdapter' => 'auth/PhutilPhabricatorAuthAdapter.php', 'PhutilPhtTestCase' => 'internationalization/__tests__/PhutilPhtTestCase.php', + 'PhutilPregsprintfTestCase' => 'xsprintf/__tests__/PhutilPregsprintfTestCase.php', 'PhutilProcessGroupDaemon' => 'daemon/torture/PhutilProcessGroupDaemon.php', 'PhutilProtocolChannel' => 'channel/PhutilProtocolChannel.php', 'PhutilProxyException' => 'error/PhutilProxyException.php', @@ -463,6 +464,7 @@ 'phutil_utf8v_combined' => 'utils/utf8.php', 'phutil_var_export' => 'utils/utils.php', 'ppull' => 'utils/utils.php', + 'pregsprintf' => 'xsprintf/pregsprintf.php', 'qsprintf' => 'xsprintf/qsprintf.php', 'qsprintf_check_scalar_type' => 'xsprintf/qsprintf.php', 'qsprintf_check_type' => 'xsprintf/qsprintf.php', @@ -484,6 +486,7 @@ 'xsprintf_ldap' => 'xsprintf/ldapsprintf.php', 'xsprintf_mercurial' => 'xsprintf/hgsprintf.php', 'xsprintf_query' => 'xsprintf/qsprintf.php', + 'xsprintf_regex' => 'xsprintf/pregsprintf.php', 'xsprintf_terminal' => 'xsprintf/tsprintf.php', 'xsprintf_uri' => 'xsprintf/urisprintf.php', ), @@ -764,6 +767,7 @@ 'PhutilPersonaAuthAdapter' => 'PhutilAuthAdapter', 'PhutilPhabricatorAuthAdapter' => 'PhutilOAuthAuthAdapter', 'PhutilPhtTestCase' => 'PhutilTestCase', + 'PhutilPregsprintfTestCase' => 'PhutilTestCase', 'PhutilProcessGroupDaemon' => 'PhutilTortureTestDaemon', 'PhutilProtocolChannel' => 'PhutilChannelChannel', 'PhutilProxyException' => 'Exception', diff --git a/src/xsprintf/__tests__/PhutilPregsprintfTestCase.php b/src/xsprintf/__tests__/PhutilPregsprintfTestCase.php new file mode 100644 --- /dev/null +++ b/src/xsprintf/__tests__/PhutilPregsprintfTestCase.php @@ -0,0 +1,23 @@ +<?php + +final class PhutilPregsprintfTestCase extends PhutilTestCase { + + public function testPregsprintf() { + $this->assertEqual( + chr(7).'foobar'.chr(7), + pregsprintf('%s', '', 'foobar')); + + $this->assertEqual( + chr(7).'\.\*\[a\-z\]'.chr(7), + pregsprintf('%s', '', '.*[a-z]')); + + $this->assertEqual( + chr(7).'.*[a-z]'.chr(7), + pregsprintf('%R', '', '.*[a-z]')); + + $this->assertEqual( + chr(7).'^abc\.\*xyz.*$'.chr(7).'siU', + pregsprintf('^abc%sxyz%R$', 'siU', '.*', '.*')); + } + +} diff --git a/src/xsprintf/pregsprintf.php b/src/xsprintf/pregsprintf.php new file mode 100644 --- /dev/null +++ b/src/xsprintf/pregsprintf.php @@ -0,0 +1,45 @@ +<?php + +/** + * Format a regular expression. Supports the following conversions: + * + * %s String + * Escapes a string using `preg_quote`. + * + * %R Raw + * Inserts a raw regular expression. + * + * @param string sprintf()-style format string. + * @param string Flags to use with the regular expression. + * @param ... Zero or more arguments. + * @return string Formatted string. + */ +function pregsprintf($pattern /* , ... */) { + $args = func_get_args(); + $flags = head(array_splice($args, 1, 1)); + + $delim = chr(7); + $userdata = array('delimiter' => $delim); + + $pattern = xsprintf('xsprintf_regex', $userdata, $args); + return $delim.$pattern.$delim.$flags; +} + +/** + * @{function:xsprintf} callback for regular expressions. + */ +function xsprintf_regex($userdata, &$pattern, &$pos, &$value, &$length) { + $delim = idx($userdata, 'delimiter'); + $type = $pattern[$pos]; + + switch ($type) { + case 's': + $value = preg_quote($value, $delim); + break; + case 'R': + $type = 's'; + break; + } + + $pattern[$pos] = $type; +}