implementsInterface('\Hoa\Zformat\Parameterizable')) { throw new Exception( 'Only parameterizable object can have parameter; ' . '%s does implement \Hoa\Zformat\Parameterizable.', 1, $owner ); } } $this->_owner = $owner; $this->setKeywords($keywords); $this->setDefault($parameters); return; } /** * Initialize constants. * * @return void */ public static function initializeConstants() { $c = explode('…', date('d…j…N…w…z…W…m…n…Y…y…g…G…h…H…i…s…u…O…T…U')); self::$_constants = [ 'd' => $c[0], 'j' => $c[1], 'N' => $c[2], 'w' => $c[3], 'z' => $c[4], 'W' => $c[5], 'm' => $c[6], 'n' => $c[7], 'Y' => $c[8], 'y' => $c[9], 'g' => $c[10], 'G' => $c[11], 'h' => $c[12], 'H' => $c[13], 'i' => $c[14], 's' => $c[15], 'u' => $c[16], 'O' => $c[17], 'T' => $c[18], 'U' => $c[19] ]; return; } /** * Get constants. * * @return array */ public static function getConstants() { return self::$_constants; } /** * Set default parameters to a class. * * @param array $parameters Parameters to set. * @return void * @throws \Hoa\Zformat\Exception */ private function setDefault(array $parameters) { $this->_parameters = $parameters; return; } /** * Set parameters. * * @param array $parameters Parameters. * @return void */ public function setParameters(array $parameters) { $this->resetCache(); foreach ($parameters as $key => $value) { $this->setParameter($key, $value); } return; } /** * Get parameters. * * @return array */ public function getParameters() { return $this->_parameters; } /** * Set a parameter. * * @param string $key Key. * @param mixed $value Value. * @return mixed */ public function setParameter($key, $value) { $this->resetCache(); $old = null; if (true === array_key_exists($key, $this->_parameters)) { $old = $this->_parameters[$key]; } $this->_parameters[$key] = $value; return $old; } /** * Get a parameter. * * @param string $parameter Parameter. * @return mixed */ public function getParameter($parameter) { if (array_key_exists($parameter, $this->_parameters)) { return $this->_parameters[$parameter]; } return null; } /** * Get a formatted parameter (i.e. zFormatted). * * @param string $parameter Parameter. * @return mixed */ public function getFormattedParameter($parameter) { if (null === $value = $this->getParameter($parameter)) { return null; } return $this->zFormat($value); } /** * Check a branch exists. * * @param string $branch Branch. * @return bool */ public function branchExists($branch) { $qBranch = preg_quote($branch); foreach ($this->getParameters() as $key => $value) { if (0 !== preg_match('#^' . $qBranch . '(.*)?#', $key)) { return true; } } return false; } /** * Unlinearize a branch to an array. * * @param string $branch Branch. * @return array */ public function unlinearizeBranch($branch) { $parameters = $this->getParameters(); $out = []; $lBranch = strlen($branch); foreach ($parameters as $key => $value) { if ($branch !== substr($key, 0, $lBranch)) { continue; } $handle = []; $explode = preg_split( '#((?= 0) { $explode[$i] = str_replace('\\.', '.', $explode[$i]); if ($i != $end) { $handle = [$explode[$i] => $handle]; } else { $handle = [$explode[$i] => $this->zFormat($value)]; } --$i; } $out = array_merge_recursive($out, $handle); } return $out; } /** * Set keywords. * * @param array $keywords Keywords. * @return void * @throws \Hoa\Zformat\Exception */ public function setKeywords($keywords) { $this->resetCache(); foreach ($keywords as $key => $value) { $this->setKeyword($key, $value); } return; } /** * Get keywords. * * @return array */ public function getKeywords() { return $this->_keywords; } /** * Set a keyword. * * @param string $key Key. * @param mixed $value Value. * @return mixed */ public function setKeyword($key, $value) { $this->resetCache(); $old = null; if (true === array_key_exists($key, $this->_keywords)) { $old = $this->_keywords[$key]; } $this->_keywords[$key] = $value; return $old; } /** * Get a keyword. * * @param string $keyword Keyword. * @return mixed */ public function getKeyword($keyword) { if (true === array_key_exists($keyword, $this->_keywords)) { return $this->_keywords[$keyword]; } return null; } /** * zFormat a string. * zFormat is inspired from the famous Zsh (please, take a look at * http://zsh.org), and specifically from ZStyle. * * ZFormat has the following pattern: * (:subject[:format]:) * * where subject could be a: * • keyword, i.e. a simple string: foo; * • reference to an existing parameter, i.e. a simple string prefixed by * a %: %bar; * • constant, i.e. a combination of chars, first is prefixed by a _: _Ymd * will given the current year, followed by the current month and * finally the current day. * * and where the format is a combination of chars, that apply functions on * the subject: * • h: to get the head of a path (equivalent to dirname); * • t: to get the tail of a path (equivalent to basename); * • r: to get the path without extension; * • e: to get the extension; * • l: to get the result in lowercase; * • u: to get the result in uppercase; * • U: to get the result with the first letter in uppercase (understand * classname); * • s///: to replace all matches by (the last / is * optional, only if more options are given after); * • s%%%: to replace the prefix by (the last % is * also optional); * • s###: to replace the suffix by (the last # is * also optional). * * Known constants are: * • d: day of the month, 2 digits with leading zeros; * • j: day of the month without leading zeros; * • N: ISO-8601 numeric representation of the day of the week; * • w: numeric representation of the day of the week; * • z: the day of the year (starting from 0); * • W: ISO-8601 week number of year, weeks starting on Monday; * • m: numeric representation of a month, with leading zeros; * • n: numeric representation of a month, without leading zeros; * • Y: a full numeric representation of a year, 4 digits; * • y: a two digit representation of a year; * • g: 12-hour format of an hour without leading zeros; * • G: 24-hour format of an hour without leading zeros; * • h: 12-hour format of an hour with leading zeros; * • H: 24-hour format of an hour with leading zeros; * • i: minutes with leading zeros; * • s: seconds with leading zeros; * • u: microseconds; * • O: difference to Greenwich time (GMT) in hours; * • T: timezone abbreviation; * • U: seconds since the Unix Epoch (a timestamp). * They are very useful for dynamic cache paths for example. * * Examples: * Let keywords $k and parameters $p: * $k = [ * 'foo' => 'bar', * 'car' => 'DeLoReAN', * 'power' => 2.21, * 'answerTo' => 'life_universe_everything_else', * 'answerIs' => 42, * 'hello' => 'wor.l.d' * ]; * $p = [ * 'plpl' => '(:foo:U:)', * 'foo' => 'ar(:%plpl:)', * 'favoriteCar' => 'A (:car:l:)!', * 'truth' => 'To (:answerTo:ls/_/ /U:) is (:answerIs:).', * 'file' => '/a/file/(:_Ymd:)/(:hello:trr:).(:power:e:)', * 'recursion' => 'oof(:%foo:s#ar#az:)' * ]; * Then, after applying the zFormat, we get: * • plpl: 'Bar', put the first letter in uppercase; * • foo: 'arBar', call the parameter plpl; * • favoriteCar: 'A delorean!', all is in lowercase; * • truth: 'To Life universe everything else is 42', all is in * lowercase, then replace underscores by spaces, and * finally put the first letter in uppercase; and no * transformation for 42; * • file: '/a/file/20090505/wor.21', get date constants, then * get the tail of the path and remove extension twice, * and add the extension of power; * • recursion: 'oofarBaz', get 'arbar' first, and then, replace the * suffix 'ar' by 'az'. * * @param string $value Parameter value. * @return string * @throws \Hoa\Zformat\Exception */ public function zFormat($value) { if (!is_string($value)) { return $value; } if (isset($this->_cache[$value])) { return $this->_cache[$value]; } if (null === self::$_constants) { self::initializeConstants(); } $self = $this; $keywords = $this->getKeywords(); $parameters = $this->getParameters(); return $this->_cache[$value] = preg_replace_callback( '#\(:(.*?):\)#', function ($match) use ($self, $value, &$keywords, &$parameters) { preg_match( '#([^:]+)(?::(.*))?#', $match[1], $submatch ); if (!isset($submatch[1])) { return ''; } $out = null; $key = $submatch[1]; $word = substr($key, 1); // Call a parameter. if ('%' == $key[0]) { if (false === array_key_exists($word, $parameters)) { throw new Exception( 'Parameter %s is not found in parameters.', 0, $word ); } $handle = $parameters[$word]; $out = $self->zFormat($handle); } // Call a constant. elseif ('_' == $key[0]) { $constants = Parameter::getConstants(); foreach (str_split($word) as $k => $v) { if (!isset($constants[$v])) { throw new Exception( 'Constant char %s is not supported in the ' . 'rule %s.', 1, [$v, $value] ); } $out .= $constants[$v]; } } // Call a keyword. else { if (false === array_key_exists($key, $keywords)) { throw new Exception( 'Keyword %s is not found in the rule %s.', 2, [$key, $value] ); } $out = $keywords[$key]; } if (!isset($submatch[2])) { return $out; } preg_match_all( '#(h|t|r|e|l|u|U|s(/|%|\#)(.*?)(? $flag) { switch ($flag) { case 'h': $out = dirname($out); break; case 't': $out = basename($out); break; case 'r': if (false !== $position = strrpos($out, '.', 1)) { $out = substr($out, 0, $position); } break; case 'e': if (false !== $position = strrpos($out, '.', 1)) { $out = substr($out, $position + 1); } break; case 'l': $out = strtolower($out); break; case 'u': $out = strtoupper($out); break; case 'U': $handle = null; foreach (explode('\\', $out) as $part) { if (null === $handle) { $handle = ucfirst($part); } else { $handle .= '\\' . ucfirst($part); } } $out = $handle; break; default: if (!isset($flags[3]) && !isset($flags[4])) { throw new Exception( 'Unrecognized format pattern in the rule %s.', 4, $value ); } $l = preg_quote($flags[3][$i], '#'); $r = $flags[4][$i]; switch ($flags[2][$i]) { case '%': $l = '^' . $l; break; case '#': $l .= '$'; break; } $out = preg_replace('#' . $l . '#', $r, $out); } } return $out; }, $value ); } /** * Reset zFormat cache. * * @return void */ private function resetCache() { unset($this->_cache); $this->_cache = []; return; } }