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 @@ -135,6 +135,8 @@ 'PhutilConsoleTable' => 'console/PhutilConsoleTable.php', 'PhutilConsoleWrapTestCase' => 'console/__tests__/PhutilConsoleWrapTestCase.php', 'PhutilContextFreeGrammar' => 'grammar/PhutilContextFreeGrammar.php', + 'PhutilCowsay' => 'utils/PhutilCowsay.php', + 'PhutilCowsayTestCase' => 'utils/__tests__/PhutilCowsayTestCase.php', 'PhutilCsprintfTestCase' => 'xsprintf/__tests__/PhutilCsprintfTestCase.php', 'PhutilCzechLocale' => 'internationalization/locales/PhutilCzechLocale.php', 'PhutilDaemon' => 'daemon/PhutilDaemon.php', @@ -643,6 +645,8 @@ 'PhutilConsoleTable' => 'Phobject', 'PhutilConsoleWrapTestCase' => 'PhutilTestCase', 'PhutilContextFreeGrammar' => 'Phobject', + 'PhutilCowsay' => 'Phobject', + 'PhutilCowsayTestCase' => 'PhutilTestCase', 'PhutilCsprintfTestCase' => 'PhutilTestCase', 'PhutilCzechLocale' => 'PhutilLocale', 'PhutilDaemon' => 'Phobject', diff --git a/src/utils/PhutilCowsay.php b/src/utils/PhutilCowsay.php new file mode 100644 --- /dev/null +++ b/src/utils/PhutilCowsay.php @@ -0,0 +1,135 @@ +template = $template; + return $this; + } + + public function setEyes($eyes) { + $this->eyes = $eyes; + return $this; + } + + public function setTongue($tongue) { + $this->tongue = $tongue; + return $this; + } + + public function setAction($action) { + $this->action = $action; + return $this; + } + + public function setText($text) { + $this->text = $text; + return $this; + } + + public function renderCow() { + $width = 40; + $template = $this->template; + + // Real ".cow" files are Perl scripts which define a variable called + // "$the_cow". We aren't going to interpret Perl, so strip all this stuff + // (and any comments in the file) away. + $template = phutil_split_lines($template, true); + $keep = array(); + foreach ($template as $key => $line) { + if (preg_match('/^#/', $line)) { + continue; + } + if (preg_match('/^\s*\\$the_cow/', $line)) { + continue; + } + if (preg_match('/^\s*EOC\s*$/', $line)) { + continue; + } + $keep[] = $line; + } + $template = implode('', $keep); + + $template = preg_replace_callback( + '/\\$([a-z]+)/', + array($this, 'replaceTemplateVariable'), + $template); + if ($template === false) { + throw new Exception( + pht( + 'Failed to replace template variables while rendering cow!')); + } + + $lines = $this->text; + + // TODO: It would be nice to use a utf8 soft wrap here instead, but we + // do not currently have one. Soft wrap first, then force to utf8. + $lines = wordwrap($lines, $width - 4, "\n", true); + $lines = phutil_split_lines($lines, false); + foreach ($lines as $key => $line) { + $lines[$key] = phutil_utf8ize($line); + } + + if ($this->action == 'think') { + $borders = '((()))'; + } else { + if (count($lines) == 1) { + $borders = '<<<>>>'; + } else { + $borders = '/|\\\\|/'; + } + } + + $size = 0; + foreach ($lines as $line) { + $size = max(strlen($line), $size); + } + + $balloon = array(); + $balloon[] = ' '.str_repeat('_', $size + 2); + $lines = array_values($lines); + $last = (count($lines) - 1); + foreach ($lines as $idx => $line) { + if ($idx == 0) { + $l = $borders[0]; + $r = $borders[3]; + } else if ($idx == $last) { + $l = $borders[2]; + $r = $borders[5]; + } else { + $l = $borders[1]; + $r = $borders[4]; + } + $balloon[] = $l.' '.str_pad($line, $size).' '.$r; + + } + $balloon[] = ' '.str_repeat('-', $size + 2); + $balloon = implode("\n", $balloon); + + return rtrim($balloon."\n".$template); + } + + public function replaceTemplateVariable($matches) { + switch ($matches[1]) { + case 'eyes': + return str_pad($this->eyes, 2); + case 'tongue': + return str_pad($this->tongue, 2); + case 'thoughts': + return ($this->action == 'say' ? '\\' : 'o'); + default: + return $matches[0]; + } + } + + +} diff --git a/src/utils/__tests__/PhutilCowsayTestCase.php b/src/utils/__tests__/PhutilCowsayTestCase.php new file mode 100644 --- /dev/null +++ b/src/utils/__tests__/PhutilCowsayTestCase.php @@ -0,0 +1,60 @@ +setTemplate($template); + + if (idx($spec, 'text') !== null) { + $cowsay->setText($spec['text']); + } + + if (idx($spec, 'eyes') !== null) { + $cowsay->setEyes($spec['eyes']); + } + + if (idx($spec, 'tongue') !== null) { + $cowsay->setTongue($spec['tongue']); + } + + if (idx($spec, 'action') !== null) { + $cowsay->setAction($spec['action']); + } + + $actual = $cowsay->renderCow(); + + $this->assertEqual( + rtrim($expect), + rtrim($actual), + pht('Cowsay Test Case "%s"', $test)); + } + } + +} diff --git a/src/utils/__tests__/cowsay/cube.expect b/src/utils/__tests__/cowsay/cube.expect new file mode 100644 --- /dev/null +++ b/src/utils/__tests__/cowsay/cube.expect @@ -0,0 +1,12 @@ + __________________ +( I made a friend! ) + ------------------ + o + o + /---\__/---\\ + | / .... \ || + \ ..--.. // + |..(<3). || + / ..--.. \\ + | \ .... / || + \---/--\---// diff --git a/src/utils/__tests__/cowsay/cube.test b/src/utils/__tests__/cowsay/cube.test new file mode 100644 --- /dev/null +++ b/src/utils/__tests__/cowsay/cube.test @@ -0,0 +1,15 @@ + $thoughts + $thoughts + /---\__/---\\ + | / .... \ || + \ ..--.. // + |..($eyes). || + / ..--.. \\ + | \ .... / || + \---/--\---// +~~~~~~~~~~ +{ + "text": "I made a friend!", + "action": "think", + "eyes": "<3" +}