. */ include_once 'phing/Task.php'; include_once 'phing/BuildException.php'; include_once 'phing/lib/Capsule.php'; include_once 'phing/util/StringHelper.php'; /** * A phing task for generating output by using Capsule. * * This is based on the interface to TexenTask from Apache's Velocity engine. * * @author Hans Lellelid * @version $Revision: 1.17 $ * @package phing.tasks.ext */ class CapsuleTask extends Task { /** * Capsule "template" engine. * @var Capsule */ protected $context; /** * Any vars assigned via the build file. * @var array AssignedVar[] */ protected $assignedVars = array(); /** * This is the control template that governs the output. * It may or may not invoke the services of worker * templates. * @var string */ protected $controlTemplate; /** * This is where Velocity will look for templates * using the file template loader. * @var string */ protected $templatePath; /** * This is where texen will place all the output * that is a product of the generation process. * @var string */ protected $outputDirectory; /** * This is the file where the generated text * will be placed. * @var string */ protected $outputFile; /** *

* These are properties that are fed into the * initial context from a properties file. This * is simply a convenient way to set some values * that you wish to make available in the context. *

*

* These values are not critical, like the template path * or output path, but allow a convenient way to * set a value that may be specific to a particular * generation task. *

*

* For example, if you are generating scripts to allow * user to automatically create a database, then * you might want the $databaseName * to be placed * in the initial context so that it is available * in a script that might look something like the * following: *

     * #!bin/sh
     * 
     * echo y | mysqladmin create $databaseName
     * 
* The value of $databaseName isn't critical to * output, and you obviously don't want to change * the ant task to simply take a database name. * So initial context values can be set with * properties file. * * @var array */ protected $contextProperties; // ----------------------------------------------------------------------- // The following getters & setters are used by phing to set properties // specified in the XML for the capsule task. // ----------------------------------------------------------------------- /** * [REQUIRED] Set the control template for the * generating process. * @param string $controlTemplate * @return void */ public function setControlTemplate ($controlTemplate) { $this->controlTemplate = $controlTemplate; } /** * Get the control template for the * generating process. * @return string */ public function getControlTemplate() { return $this->controlTemplate; } /** * [REQUIRED] Set the path where Velocity will look * for templates using the file template * loader. * @return void * @throws Exception */ public function setTemplatePath($templatePath) { $resolvedPath = ""; $tok = strtok($templatePath, ","); while ( $tok ) { // resolve relative path from basedir and leave // absolute path untouched. $fullPath = $this->project->resolveFile($tok); $cpath = $fullPath->getCanonicalPath(); if ($cpath === false) { $this->log("Template directory does not exist: " . $fullPath->getAbsolutePath()); } else { $resolvedPath .= $cpath; } $tok = strtok(","); if ( $tok ) { $resolvedPath .= ","; } } $this->templatePath = $resolvedPath; } /** * Get the path where Velocity will look * for templates using the file template * loader. * @return string */ public function getTemplatePath() { return $this->templatePath; } /** * [REQUIRED] Set the output directory. It will be * created if it doesn't exist. * @param PhingFile $outputDirectory * @return void * @throws Exception */ public function setOutputDirectory(PhingFile $outputDirectory) { try { if (!$outputDirectory->exists()) { $this->log("Output directory does not exist, creating: " . $outputDirectory->getPath(),PROJECT_MSG_VERBOSE); if (!$outputDirectory->mkdirs()) { throw new IOException("Unable to create Ouptut directory: " . $outputDirectory->getAbsolutePath()); } } $this->outputDirectory = $outputDirectory->getCanonicalPath(); } catch (IOException $ioe) { throw new BuildException($ioe); } } /** * Get the output directory. * @return string */ public function getOutputDirectory() { return $this->outputDirectory; } /** * [REQUIRED] Set the output file for the * generation process. * @param string $outputFile (TODO: change this to File) * @return void */ public function setOutputFile($outputFile) { $this->outputFile = $outputFile; } /** * Get the output file for the * generation process. * @return string */ public function getOutputFile() { return $this->outputFile; } /** * Set the context properties that will be * fed into the initial context be the * generating process starts. * @param string $file * @return void */ public function setContextProperties($file) { $sources = explode(",", $file); $this->contextProperties = new Properties(); // Always try to get the context properties resource // from a file first. Templates may be taken from a JAR // file but the context properties resource may be a // resource in the filesystem. If this fails than attempt // to get the context properties resource from the // classpath. for ($i=0, $sourcesLength=count($sources); $i < $sourcesLength; $i++) { $source = new Properties(); try { // resolve relative path from basedir and leave // absolute path untouched. $fullPath = $this->project->resolveFile($sources[$i]); $this->log("Using contextProperties file: " . $fullPath->toString()); $source->load($fullPath); } catch (Exception $e) { throw new BuildException("Context properties file " . $sources[$i] . " could not be found in the file system!"); } $keys = $source->keys(); foreach ($keys as $key) { $name = $key; $value = $this->project->replaceProperties($source->getProperty($name)); $this->contextProperties->setProperty($name, $value); } } } /** * Get the context properties that will be * fed into the initial context be the * generating process starts. * @return Properties */ public function getContextProperties() { return $this->contextProperties; } /** * Creates an "AssignedVar" class. */ public function createAssign() { $a = new AssignedVar(); $this->assignedVars[] = $a; return $a; } // --------------------------------------------------------------- // End of XML setters & getters // --------------------------------------------------------------- /** * Creates a Smarty object. * * @return Smarty initialized (cleared) Smarty context. * @throws Exception the execute method will catch * and rethrow as a BuildException */ public function initControlContext() { $this->context->clear(); foreach($this->assignedVars as $var) { $this->context->put($var->getName(), $var->getValue()); } return $this->context; } /** * Execute the input script with Velocity * * @throws BuildException * BuildExceptions are thrown when required attributes are missing. * Exceptions thrown by Velocity are rethrown as BuildExceptions. */ public function main() { // Make sure the template path is set. if (empty($this->templatePath)) { throw new BuildException("The template path needs to be defined!"); } // Make sure the control template is set. if ($this->controlTemplate === null) { throw new BuildException("The control template needs to be defined!"); } // Make sure the output directory is set. if ($this->outputDirectory === null) { throw new BuildException("The output directory needs to be defined!"); } // Make sure there is an output file. if ($this->outputFile === null) { throw new BuildException("The output file needs to be defined!"); } // Setup Smarty runtime. // Smarty uses one object to store properties and to store // the context for the template (unlike Velocity). We setup this object, calling it // $this->context, and then initControlContext simply zeros out // any assigned variables. $this->context = new Capsule(); if ($this->templatePath !== null) { $this->log("Using templatePath: " . $this->templatePath); $this->context->setTemplatePath($this->templatePath); } // Make sure the output directory exists, if it doesn't // then create it. $outputDir = new PhingFile($this->outputDirectory); if (!$outputDir->exists()) { $this->log("Output directory does not exist, creating: " . $outputDir->getAbsolutePath()); $outputDir->mkdirs(); } $this->context->setOutputDirectory($outputDir->getAbsolutePath()); $path = $this->outputDirectory . DIRECTORY_SEPARATOR . $this->outputFile; $this->log("Generating to file " . $path); //$writer = new FileWriter($path); // The generator and the output path should // be placed in the init context here and // not in the generator class itself. $c = $this->initControlContext(); // Set any variables that need to always // be loaded $this->populateInitialContext($c); // Feed all the options into the initial // control context so they are available // in the control/worker templates. if ($this->contextProperties !== null) { foreach($this->contextProperties->keys() as $property) { $value = $this->contextProperties->getProperty($property); // Special exception (from Texen) // for properties ending in file.contents: // in that case we dump the contents of the file // as the "value" for the Property. if (preg_match('/file\.contents$/', $property)) { // pull in contents of file specified $property = substr($property, 0, strpos($property, "file.contents") - 1); // reset value, and then // read in teh contents of the file into that var $value = ""; $f = new PhingFile($project->resolveFile($value)->getCanonicalPath()); if ($f->exists()) { $fr = new FileReader($f); $fr->readInto($value); } } // if ends with file.contents if (StringHelper::isBoolean($value)) { $value = StringHelper::booleanValue($value); } $c->put($property, $value); } // foreach property } // if contextProperties !== null try { $this->log("Parsing control template: " . $this->controlTemplate); $c->parse($this->controlTemplate, $path); } catch (Exception $ioe) { throw new BuildException("Cannot write parsed template: ". $ioe->getMessage()); } $this->cleanup(); } /** * Place useful objects into the initial context. * * * @param Capsule $context The context to populate, as retrieved from * {@link #initControlContext()}. * @return void * @throws Exception Error while populating context. The {@link * #main()} method will catch and rethrow as a * BuildException. */ protected function populateInitialContext(Capsule $context) { $this->context->put("now", strftime("%c", time())); $this->context->put("task", $this); } /** * A hook method called at the end of {@link #execute()} which can * be overridden to perform any necessary cleanup activities (such * as the release of database connections, etc.). By default, * does nothing. * @return void * @throws Exception Problem cleaning up. */ protected function cleanup() { } } /** * An "inner" class for holding assigned var values. * May be need to expand beyond name/value in the future. */ class AssignedVar { private $name; private $value; function setName($v) { $this->name = $v; } function setValue($v) { $this->value = $v; } function getName() { return $this->name; } function getValue() { return $this->value; } }