SmartyTask.php 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611
  1. <?php
  2. /*
  3. * $Id: SmartyTask.php 905 2010-10-05 16:28:03Z mrook $
  4. *
  5. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  6. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  7. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  8. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  9. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  10. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  11. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  12. * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  13. * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  14. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  15. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  16. *
  17. * This software consists of voluntary contributions made by many individuals
  18. * and is licensed under the LGPL. For more information please see
  19. * <http://phing.info>.
  20. */
  21. require_once 'phing/Task.php';
  22. include_once 'phing/BuildException.php';
  23. include_once 'phing/util/StringHelper.php';
  24. /**
  25. * A phing task for generating output by using Smarty.
  26. *
  27. * This is based on the TexenTask from Apache's Velocity engine. This class
  28. * was originally proted in order to provide a template compiling system for
  29. * Torque.
  30. *
  31. * TODO:
  32. * - Add Path / useClasspath support?
  33. *
  34. * @author Hans Lellelid <hans@xmpl.org> (SmartyTask)
  35. * @author Jason van Zyl <jvanzyl@apache.org> (TexenTask)
  36. * @author Robert Burrell Donkin <robertdonkin@mac.com>
  37. * @version $Id: SmartyTask.php 905 2010-10-05 16:28:03Z mrook $
  38. * @package phing.tasks.ext
  39. */
  40. class SmartyTask extends Task {
  41. /**
  42. * Smarty template engine.
  43. * @var Smarty
  44. */
  45. protected $context;
  46. /**
  47. * Variables that are assigned to the context on parse/compile.
  48. * @var array
  49. */
  50. protected $properties = array();
  51. /**
  52. * This is the control template that governs the output.
  53. * It may or may not invoke the services of worker
  54. * templates.
  55. * @var string
  56. */
  57. protected $controlTemplate;
  58. /**
  59. * This is where Velocity will look for templates
  60. * using the file template loader.
  61. * @var string
  62. */
  63. protected $templatePath;
  64. /**
  65. * This is where texen will place all the output
  66. * that is a product of the generation process.
  67. * @var string
  68. */
  69. protected $outputDirectory;
  70. /**
  71. * This is the file where the generated text
  72. * will be placed.
  73. * @var string
  74. */
  75. protected $outputFile;
  76. /**
  77. * <p>
  78. * These are properties that are fed into the
  79. * initial context from a properties file. This
  80. * is simply a convenient way to set some values
  81. * that you wish to make available in the context.
  82. * </p>
  83. * <p>
  84. * These values are not critical, like the template path
  85. * or output path, but allow a convenient way to
  86. * set a value that may be specific to a particular
  87. * generation task.
  88. * </p>
  89. * <p>
  90. * For example, if you are generating scripts to allow
  91. * user to automatically create a database, then
  92. * you might want the <code>$databaseName</code>
  93. * to be placed
  94. * in the initial context so that it is available
  95. * in a script that might look something like the
  96. * following:
  97. * <code><pre>
  98. * #!bin/sh
  99. *
  100. * echo y | mysqladmin create $databaseName
  101. * </pre></code>
  102. * The value of <code>$databaseName</code> isn't critical to
  103. * output, and you obviously don't want to change
  104. * the ant task to simply take a database name.
  105. * So initial context values can be set with
  106. * properties file.
  107. *
  108. * @var array
  109. */
  110. protected $contextProperties;
  111. /**
  112. * Smarty compiles templates before parsing / replacing tokens in them.
  113. * By default it will try ./templates_c, but you may wish to override this.
  114. * @var string
  115. */
  116. protected $compilePath;
  117. /**
  118. * Whether to force Smarty to recompile templates.
  119. * Smarty does check file modification time, but you can set this
  120. * to be *sure* that the template will be compiled (of course it will
  121. * be slower if you do).
  122. * @var boolean
  123. */
  124. protected $forceCompile = false;
  125. /**
  126. * Smarty can use config files.
  127. * This tells Smarty where to look for the config files.
  128. * @var string
  129. */
  130. protected $configPath;
  131. /**
  132. * Customize the left delimiter for Smarty tags.
  133. * @var string
  134. */
  135. protected $leftDelimiter;
  136. /**
  137. * Customize the right delimiter for Smarty tags.
  138. * @var string
  139. */
  140. protected $rightDelimiter;
  141. // -----------------------------------------------------------------------
  142. // The following getters & setters are used by phing to set properties
  143. // specified in the XML for the smarty task.
  144. // -----------------------------------------------------------------------
  145. public function init() {
  146. include_once 'Smarty.class.php';
  147. if (!class_exists('Smarty')) {
  148. throw new BuildException("To use SmartyTask, you must have the path to Smarty.class.php on your include_path or your \$PHP_CLASSPATH environment variable.");
  149. }
  150. }
  151. /**
  152. * [REQUIRED] Set the control template for the
  153. * generating process.
  154. * @param string $controlTemplate
  155. * @return void
  156. */
  157. public function setControlTemplate ($controlTemplate) {
  158. $this->controlTemplate = $controlTemplate;
  159. }
  160. /**
  161. * Get the control template for the
  162. * generating process.
  163. * @return string
  164. */
  165. public function getControlTemplate() {
  166. return $this->controlTemplate;
  167. }
  168. /**
  169. * [REQUIRED] Set the path where Velocity will look
  170. * for templates using the file template
  171. * loader.
  172. * @return void
  173. * @throws Exception
  174. */
  175. public function setTemplatePath($templatePath) {
  176. $resolvedPath = "";
  177. $tok = strtok($templatePath, ",");
  178. while ( $tok ) {
  179. // resolve relative path from basedir and leave
  180. // absolute path untouched.
  181. $fullPath = $this->project->resolveFile($tok);
  182. $cpath = $fullPath->getCanonicalPath();
  183. if ($cpath === false) {
  184. $this->log("Template directory does not exist: " . $fullPath->getAbsolutePath());
  185. } else {
  186. $resolvedPath .= $cpath;
  187. }
  188. $tok = strtok(",");
  189. if ( $tok ) {
  190. $resolvedPath .= ",";
  191. }
  192. }
  193. $this->templatePath = $resolvedPath;
  194. }
  195. /**
  196. * Get the path where Velocity will look
  197. * for templates using the file template
  198. * loader.
  199. * @return string
  200. */
  201. public function getTemplatePath() {
  202. return $this->templatePath;
  203. }
  204. /**
  205. * [REQUIRED] Set the output directory. It will be
  206. * created if it doesn't exist.
  207. * @param PhingFile $outputDirectory
  208. * @return void
  209. * @throws Exception
  210. */
  211. public function setOutputDirectory(PhingFile $outputDirectory) {
  212. try {
  213. if (!$outputDirectory->exists()) {
  214. $this->log("Output directory does not exist, creating: " . $outputDirectory->getPath(),Project::MSG_VERBOSE);
  215. if (!$outputDirectory->mkdirs()) {
  216. throw new IOException("Unable to create Ouptut directory: " . $outputDirectory->getAbsolutePath());
  217. }
  218. }
  219. $this->outputDirectory = $outputDirectory->getCanonicalPath();
  220. } catch (IOException $ioe) {
  221. throw new BuildException($ioe->getMessage());
  222. }
  223. }
  224. /**
  225. * Get the output directory.
  226. * @return string
  227. */
  228. public function getOutputDirectory() {
  229. return $this->outputDirectory;
  230. }
  231. /**
  232. * [REQUIRED] Set the output file for the
  233. * generation process.
  234. * @return void
  235. */
  236. public function setOutputFile($outputFile) {
  237. $this->outputFile = $outputFile;
  238. }
  239. /**
  240. * Get the output file for the
  241. * generation process.
  242. * @return string
  243. */
  244. public function getOutputFile() {
  245. return $this->outputFile;
  246. }
  247. /**
  248. * Set the path Smarty uses as a "cache" for compiled templates.
  249. * @param string $compilePath
  250. */
  251. public function setCompilePath($compilePath) {
  252. $this->compilePath = $compilePath;
  253. }
  254. /**
  255. * Get the path Smarty uses for compiling templates.
  256. * @return string
  257. */
  258. public function getCompilePath() {
  259. return $this->compilePath;
  260. }
  261. /**
  262. * Set whether Smarty should always recompile tempaltes.
  263. * @param boolean $force
  264. * @return void
  265. */
  266. public function setForceCompile($force) {
  267. $this->forceCompile = (boolean) $force;
  268. }
  269. /**
  270. * Get whether Smarty should always recompile template.
  271. * @return boolean
  272. */
  273. public function getForceCompile() {
  274. return $this->forceCompile;
  275. }
  276. /**
  277. * Set where Smarty looks for config files.
  278. * @param string $configPath
  279. * @return void
  280. */
  281. public function setConfigPath($configPath) {
  282. $this->configPath = $configPath;
  283. }
  284. /**
  285. * Get the path that Smarty uses for looking for config files.
  286. * @return string
  287. */
  288. public function getConfigPath() {
  289. return $this->configPath;
  290. }
  291. /**
  292. * Set Smarty template left delimiter.
  293. * @param string $delim
  294. * @return void
  295. */
  296. public function setLeftDelimiter($delim) {
  297. $this->leftDelimiter = $delim;
  298. }
  299. /**
  300. * Get Smarty template right delimiter
  301. * @return string
  302. */
  303. public function getLeftDelimiter() {
  304. return $this->leftDelimiter;
  305. }
  306. /**
  307. * Set Smarty template right delimiter.
  308. * @param string $delim
  309. * @return void
  310. */
  311. public function setRightDelimiter($delim) {
  312. $this->rightDelimiter = $delim;
  313. }
  314. /**
  315. * Get Smarty template right delimiter
  316. * @return string
  317. */
  318. public function getRightDelimiter() {
  319. return $this->rightDelimiter;
  320. }
  321. /**
  322. * Set the context properties that will be
  323. * fed into the initial context be the
  324. * generating process starts.
  325. * @param string $file
  326. * @return void
  327. */
  328. public function setContextProperties($file) {
  329. $sources = explode(",", $file);
  330. $this->contextProperties = new Properties();
  331. // Always try to get the context properties resource
  332. // from a file first. Templates may be taken from a JAR
  333. // file but the context properties resource may be a
  334. // resource in the filesystem. If this fails than attempt
  335. // to get the context properties resource from the
  336. // classpath.
  337. for ($i=0, $sourcesLength=count($sources); $i < $sourcesLength; $i++) {
  338. $source = new Properties();
  339. try {
  340. // resolve relative path from basedir and leave
  341. // absolute path untouched.
  342. $fullPath = $this->project->resolveFile($sources[$i]);
  343. $this->log("Using contextProperties file: " . $fullPath->__toString());
  344. $source->load($fullPath);
  345. } catch (Exception $e) {
  346. throw new BuildException("Context properties file " . $sources[$i] .
  347. " could not be found in the file system!");
  348. }
  349. $keys = $source->keys();
  350. foreach ($keys as $key) {
  351. $name = $key;
  352. $value = $this->project->replaceProperties($source->getProperty($name));
  353. $this->contextProperties->setProperty($name, $value);
  354. }
  355. }
  356. }
  357. /**
  358. * Get the context properties that will be
  359. * fed into the initial context be the
  360. * generating process starts.
  361. * @return Properties
  362. */
  363. public function getContextProperties() {
  364. return $this->contextProperties;
  365. }
  366. // ---------------------------------------------------------------
  367. // End of XML setters & getters
  368. // ---------------------------------------------------------------
  369. /**
  370. * Creates a Smarty object.
  371. *
  372. * @return Smarty initialized (cleared) Smarty context.
  373. * @throws Exception the execute method will catch
  374. * and rethrow as a <code>BuildException</code>
  375. */
  376. public function initControlContext() {
  377. $this->context->clear_all_assign();
  378. return $this->context;
  379. }
  380. /**
  381. * Execute the input script with Velocity
  382. *
  383. * @throws BuildException
  384. * BuildExceptions are thrown when required attributes are missing.
  385. * Exceptions thrown by Velocity are rethrown as BuildExceptions.
  386. */
  387. public function main() {
  388. // Make sure the template path is set.
  389. if (empty($this->templatePath)) {
  390. throw new BuildException("The template path needs to be defined!");
  391. }
  392. // Make sure the control template is set.
  393. if ($this->controlTemplate === null) {
  394. throw new BuildException("The control template needs to be defined!");
  395. }
  396. // Make sure the output directory is set.
  397. if ($this->outputDirectory === null) {
  398. throw new BuildException("The output directory needs to be defined!");
  399. }
  400. // Make sure there is an output file.
  401. if ($this->outputFile === null) {
  402. throw new BuildException("The output file needs to be defined!");
  403. }
  404. // Setup Smarty runtime.
  405. // Smarty uses one object to store properties and to store
  406. // the context for the template (unlike Velocity). We setup this object, calling it
  407. // $this->context, and then initControlContext simply zeros out
  408. // any assigned variables.
  409. $this->context = new Smarty();
  410. if ($this->compilePath !== null) {
  411. $this->log("Using compilePath: " . $this->compilePath);
  412. $this->context->compile_dir = $this->compilePath;
  413. }
  414. if ($this->configPath !== null) {
  415. $this->log("Using configPath: " . $this->configPath);
  416. $this->context->config_dir = $this->configPath;
  417. }
  418. if ($this->forceCompile !== null) {
  419. $this->context->force_compile = $this->forceCompile;
  420. }
  421. if ($this->leftDelimiter !== null) {
  422. $this->context->left_delimiter = $this->leftDelimiter;
  423. }
  424. if ($this->rightDelimiter !== null) {
  425. $this->context->right_delimiter = $this->rightDelimiter;
  426. }
  427. if ($this->templatePath !== null) {
  428. $this->log("Using templatePath: " . $this->templatePath);
  429. $this->context->template_dir = $this->templatePath;
  430. }
  431. $smartyCompilePath = new PhingFile($this->context->compile_dir);
  432. if (!$smartyCompilePath->exists()) {
  433. $this->log("Compile directory does not exist, creating: " . $smartyCompilePath->getPath(), Project::MSG_VERBOSE);
  434. if (!$smartyCompilePath->mkdirs()) {
  435. throw new BuildException("Smarty needs a place to compile templates; specify a 'compilePath' or create ".$this->context->compile_dir);
  436. }
  437. }
  438. // Make sure the output directory exists, if it doesn't
  439. // then create it.
  440. $file = new PhingFile($this->outputDirectory);
  441. if (!$file->exists()) {
  442. $this->log("Output directory does not exist, creating: " . $file->getAbsolutePath());
  443. $file->mkdirs();
  444. }
  445. $path = $this->outputDirectory . DIRECTORY_SEPARATOR . $this->outputFile;
  446. $this->log("Generating to file " . $path);
  447. $writer = new FileWriter($path);
  448. // The generator and the output path should
  449. // be placed in the init context here and
  450. // not in the generator class itself.
  451. $c = $this->initControlContext();
  452. // Set any variables that need to always
  453. // be loaded
  454. $this->populateInitialContext($c);
  455. // Feed all the options into the initial
  456. // control context so they are available
  457. // in the control/worker templates.
  458. if ($this->contextProperties !== null) {
  459. foreach($this->contextProperties->keys() as $property) {
  460. $value = $this->contextProperties->getProperty($property);
  461. // Special exception (from Texen)
  462. // for properties ending in file.contents:
  463. // in that case we dump the contents of the file
  464. // as the "value" for the Property.
  465. if (StringHelper::endsWith("file.contents", $property)) {
  466. // pull in contents of file specified
  467. $property = substr($property, 0, strpos($property, "file.contents") - 1);
  468. // reset value, and then
  469. // read in teh contents of the file into that var
  470. $value = "";
  471. $f = new PhingFile($project->resolveFile($value)->getCanonicalPath());
  472. if ($f->exists()) {
  473. try {
  474. $fr = new FileReader($f);
  475. $fr->readInto($value);
  476. } catch (Exception $e) {
  477. throw $e;
  478. }
  479. }
  480. } // if ends with file.contents
  481. if (StringHelper::isBoolean($value)) {
  482. $value = StringHelper::booleanValue($value);
  483. }
  484. $c->assign($property, $value);
  485. } // foreach property
  486. } // if contextProperties !== null
  487. try {
  488. //$c->display($this->controlTemplate);
  489. $writer->write($c->fetch($this->controlTemplate));
  490. $writer->close();
  491. } catch (IOException $ioe) {
  492. $writer->close();
  493. throw new BuildException("Cannot write parsed template.");
  494. }
  495. $this->cleanup();
  496. }
  497. /**
  498. * <p>Place useful objects into the initial context.</p>
  499. *
  500. * <p>TexenTask places <code>Date().toString()</code> into the
  501. * context as <code>$now</code>. Subclasses who want to vary the
  502. * objects in the context should override this method.</p>
  503. *
  504. * <p><code>$generator</code> is not put into the context in this
  505. * method.</p>
  506. *
  507. * @param context The context to populate, as retrieved from
  508. * {@link #initControlContext()}.
  509. * @return void
  510. * @throws Exception Error while populating context. The {@link
  511. * #execute()} method will catch and rethrow as a
  512. * <code>BuildException</code>.
  513. */
  514. protected function populateInitialContext(Smarty $context) {
  515. }
  516. /**
  517. * A hook method called at the end of {@link #execute()} which can
  518. * be overridden to perform any necessary cleanup activities (such
  519. * as the release of database connections, etc.). By default,
  520. * does nothing.
  521. * @return void
  522. * @throws Exception Problem cleaning up.
  523. */
  524. protected function cleanup() {
  525. }
  526. }