/vendor/pug-php/pug/src/Pug/Engine/PugJsEngine.php

https://github.com/welaika/wordless · PHP · 248 lines · 167 code · 38 blank · 43 comment · 14 complexity · b83b3f26aa9c2e18c8a219edd3d5341e MD5 · raw file

  1. <?php
  2. namespace Pug\Engine;
  3. use NodejsPhpFallback\NodejsPhpFallback;
  4. /**
  5. * Class Pug\PugJsEngine.
  6. */
  7. class PugJsEngine extends Keywords
  8. {
  9. /**
  10. * @var NodejsPhpFallback
  11. */
  12. protected $nodeEngine;
  13. /**
  14. * Return the NodejsPhpFallback instance used to execute pug-cli via node.
  15. *
  16. * @return NodejsPhpFallback
  17. */
  18. public function getNodeEngine()
  19. {
  20. if (!$this->nodeEngine) {
  21. $this->nodeEngine = new NodejsPhpFallback(
  22. $this->hasOption('node_path')
  23. ? $this->getDefaultOption('node_path')
  24. : 'node'
  25. );
  26. }
  27. return $this->nodeEngine;
  28. }
  29. protected function getHtml($file, array &$options)
  30. {
  31. if (empty($this->getDefaultOption('cache_dir'))) {
  32. $html = file_get_contents($file);
  33. unlink($file);
  34. return $html;
  35. }
  36. $currentDirectory = getcwd();
  37. $realPath = realpath($file);
  38. $handler = fopen($file, 'a');
  39. fwrite($handler, 'module.exports=template;');
  40. fclose($handler);
  41. $directory = dirname($file);
  42. $renderFile = './render.' . time() . mt_rand(0, 999999999) . '.js';
  43. chdir($directory);
  44. file_put_contents(
  45. $renderFile,
  46. 'console.log(require(' . json_encode($realPath) . ')' .
  47. '(require(' . json_encode($options['obj']) . ')));'
  48. );
  49. $node = $this->getNodeEngine();
  50. $html = $node->nodeExec($renderFile);
  51. unlink($renderFile);
  52. chdir($currentDirectory);
  53. return $html;
  54. }
  55. protected function parsePugJsResult($result, $path, array $options)
  56. {
  57. $result = explode('rendered ', $result);
  58. if (count($result) < 2) {
  59. throw new \RuntimeException(
  60. 'Pugjs throw an error: ' . $result[0]
  61. );
  62. }
  63. $file = trim($result[1]);
  64. $html = $this->getHtml($file, $options);
  65. if ($options['delete']) {
  66. unlink($path);
  67. }
  68. unlink($options['obj']);
  69. return $html;
  70. }
  71. protected function getPugCliArguments($options)
  72. {
  73. $args = [];
  74. if (!empty($options['pretty'])) {
  75. $args[] = '--pretty';
  76. unset($options['pretty']);
  77. }
  78. foreach ($options as $option => $value) {
  79. if (!empty($value)) {
  80. $args[] = '--' . $option . ' ' . escapeshellarg($value);
  81. }
  82. }
  83. return $args;
  84. }
  85. /**
  86. * Returns true if the rendered file is up to date against the source.
  87. *
  88. * @param string $renderFile
  89. * @param string $filename
  90. * @param PugJsEngine $pug
  91. *
  92. * @return bool
  93. */
  94. public static function pugJsCacheCheck($renderFile, $filename, self $pug)
  95. {
  96. return file_exists($renderFile) && (
  97. filemtime($renderFile) >= filemtime($filename) ||
  98. !$pug->getDefaultOption('upToDateCheck')
  99. );
  100. }
  101. protected function getCachedHtml($input, $filename, $options)
  102. {
  103. $renderFile = $options['out'] . '/' . preg_replace('/\.[^.]+$/', '', basename($filename)) . '.js';
  104. $cacheCheck = $this->getDefaultOption('pugjs_cache_check', [static::class, 'pugJsCacheCheck']);
  105. if (call_user_func($cacheCheck, $renderFile, $filename, $this)) {
  106. $mTime = filemtime($renderFile);
  107. if (!$input) {
  108. $input = file_get_contents($filename);
  109. }
  110. $html = $this->parsePugJsResult('rendered ' . $renderFile, $input, $options);
  111. touch($renderFile, $mTime);
  112. return $html;
  113. }
  114. return false;
  115. }
  116. protected function checkCachedHtml($input, $filename, $options, &$args)
  117. {
  118. if (empty($this->getDefaultOption('cache_dir'))) {
  119. return false;
  120. }
  121. $args[] = '--client';
  122. return $this->getCachedHtml($input, $filename, $options);
  123. }
  124. protected function callPugCli($input, $filename, $options, $fallback)
  125. {
  126. $args = $this->getPugCliArguments($options);
  127. $html = $this->checkCachedHtml($input, $filename, $options, $args);
  128. if ($html) {
  129. return $html;
  130. }
  131. $directory = dirname($filename);
  132. $currentDirectory = getcwd();
  133. $basename = basename($filename);
  134. chdir($directory);
  135. $node = $this->getNodeEngine();
  136. $result = $node->execModuleScript(
  137. 'pug-cli',
  138. 'index.js',
  139. implode(' ', $args) .
  140. ' ' . escapeshellarg($basename) .
  141. ' 2>&1',
  142. $fallback
  143. );
  144. chdir($currentDirectory);
  145. return $this->parsePugJsResult($result, $filename, $options);
  146. }
  147. /**
  148. * Render using the native Pug JS engine.
  149. *
  150. * @param string $input pug input or file
  151. * @param string $filename optional file path
  152. * @param array $vars to pass to the view
  153. * @param callable $fallback called if JS engine not available
  154. *
  155. * @throws \Exception
  156. *
  157. * @return string
  158. */
  159. public function renderWithJs($input, $filename, $vars = null, $fallback = null)
  160. {
  161. if (is_array($filename)) {
  162. if (!is_null($vars)) {
  163. $fallback = $vars;
  164. }
  165. $vars = $filename;
  166. $filename = null;
  167. }
  168. $vars = $this->mergeWithSharedVariables($vars);
  169. $workDirectory = empty($this->getDefaultOption('cache_dir'))
  170. ? sys_get_temp_dir()
  171. : $this->getOption('cache_dir');
  172. if ($toDelete = !$filename) {
  173. $filename = $workDirectory . '/source-' . mt_rand(0, 999999999) . '.pug';
  174. file_put_contents($filename, $input);
  175. }
  176. $options = [
  177. 'path' => realpath($filename),
  178. 'basedir' => $this->getDefaultOption('basedir'),
  179. 'pretty' => $this->getDefaultOption('prettyprint'),
  180. 'out' => $workDirectory,
  181. 'delete' => $toDelete,
  182. ];
  183. $optionsFile = $workDirectory . '/options-' . mt_rand(0, 999999999) . '.js';
  184. file_put_contents(
  185. $optionsFile,
  186. 'module.exports = require(' .
  187. json_encode(realpath(NodejsPhpFallback::getPrefixPath() . '/require.js')) .
  188. ').appendRequireMethod(' .
  189. (empty($vars) ? '{}' : json_encode($vars, JSON_UNESCAPED_SLASHES)) .
  190. ');'
  191. );
  192. $options['obj'] = $optionsFile;
  193. return $this->callPugCli($input, $filename, $options, $fallback);
  194. }
  195. /**
  196. * Render using the native Pug JS engine.
  197. *
  198. * @param string $path pug file
  199. * @param array $vars to pass to the view
  200. * @param callable $fallback called if JS engine not available
  201. *
  202. * @throws \Exception
  203. *
  204. * @return string
  205. */
  206. public function renderFileWithJs($path, array $vars, $fallback)
  207. {
  208. return $this->renderWithJs(null, $path, $vars, $fallback);
  209. }
  210. }