options = array(); } /** * Watch the source/ directory for any changes to existing files. Will run forever if given the chance. * @param {Boolean} decide if the reload server should be turned on * @param {Boolean} decide if static files like CSS and JS should be moved */ public function watch($options = array()) { // double-checks options was properly set if (empty($options)) { Console::writeError("need to pass options to generate..."); } // set default attributes $moveStatic = (isset($options["moveStatic"])) ? $options["moveStatic"] : true; $noCacheBuster = (isset($options["noCacheBuster"])) ? $options["noCacheBuster"] : false; // make sure a copy of the given options are saved for using when running generate $this->options = $options; // set-up the Dispatcher $dispatcherInstance = Dispatcher::getInstance(); $dispatcherInstance->dispatch("watcher.start"); if ($noCacheBuster) { Config::setOption("cacheBuster",0); } $c = false; // track that one loop through the pattern file listing has completed $o = new \stdClass(); // create an object to hold the properties $cp = new \stdClass(); // create an object to hold a clone of $o $o->patterns = new \stdClass(); Console::writeLine("watching your site for changes..."); // default vars $publicDir = Config::getOption("publicDir"); $sourceDir = Config::getOption("sourceDir"); $patternSourceDir = Config::getOption("patternSourceDir"); $ignoreExts = Config::getOption("ie"); $ignoreDirs = Config::getOption("id"); $patternExt = Config::getOption("patternExtension"); // build the file extensions based on the rules $fileExtensions = array(); $patternRules = PatternData::getRules(); foreach ($patternRules as $patternRule) { $extensions = $patternRule->getProp("extProp"); if (strpos($extensions,"&&") !== false) { $extensions = explode("&&",$extensions); } else if (strpos($extensions,"||") !== false) { $extensions = explode("||",$extensions); } else { $extensions = array($extensions); } foreach ($extensions as $extension) { if (!in_array($extension, $fileExtensions)) { $fileExtensions[] = $extension; } } } // run forever while (true) { // clone the patterns so they can be checked in case something gets deleted $cp = clone $o->patterns; // iterate over the patterns & related data and regenerate the entire site if they've changed $patternObjects = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($patternSourceDir), \RecursiveIteratorIterator::SELF_FIRST); // make sure dots are skipped $patternObjects->setFlags(\FilesystemIterator::SKIP_DOTS); foreach($patternObjects as $name => $object) { // clean-up the file name and make sure it's not one of the pattern lab files or to be ignored $fileName = str_replace($patternSourceDir.DIRECTORY_SEPARATOR,"",$name); $fileNameClean = str_replace(DIRECTORY_SEPARATOR."_",DIRECTORY_SEPARATOR,$fileName); if ($object->isFile() && in_array($object->getExtension(), $fileExtensions)) { // make sure this isn't a hidden pattern $patternParts = explode(DIRECTORY_SEPARATOR,$fileName); $pattern = isset($patternParts[2]) ? $patternParts[2] : $patternParts[1]; // make sure the pattern still exists in source just in case it's been deleted during the iteration if (file_exists($name)) { $mt = $object->getMTime(); if (isset($o->patterns->$fileName) && ($o->patterns->$fileName != $mt)) { $o->patterns->$fileName = $mt; $this->updateSite($fileName,"changed"); } else if (!isset($o->patterns->$fileName) && $c) { $o->patterns->$fileName = $mt; $this->updateSite($fileName,"added"); if ($object->getExtension() == $patternExt) { $patternSrcPath = str_replace(".".$patternExt,"",$fileName); $patternDestPath = str_replace("/","-",$patternSrcPath); $render = ($pattern[0] != "_") ? true : false; $this->patternPaths[$patternParts[0]][$pattern] = array("patternSrcPath" => $patternSrcPath, "patternDestPath" => $patternDestPath, "render" => $render); } } else if (!isset($o->patterns->$fileName)) { $o->patterns->$fileName = $mt; } if ($c && isset($o->patterns->$fileName)) { unset($cp->$fileName); } } else { // the file was removed during the iteration so remove references to the item unset($o->patterns->$fileName); unset($cp->$fileName); unset($this->patternPaths[$patternParts[0]][$pattern]); $this->updateSite($fileName,"removed"); } } } // make sure old entries are deleted // will throw "pattern not found" errors if an entire directory is removed at once but that shouldn't be a big deal if ($c) { foreach($cp as $fileName => $mt) { unset($o->patterns->$fileName); $patternParts = explode(DIRECTORY_SEPARATOR,$fileName); $pattern = isset($patternParts[2]) ? $patternParts[2] : $patternParts[1]; unset($this->patternPaths[$patternParts[0]][$pattern]); $this->updateSite($fileName,"removed"); } } // iterate over annotations, data, meta and any other _ dirs $watchDirs = glob($sourceDir.DIRECTORY_SEPARATOR."_*",GLOB_ONLYDIR); foreach ($watchDirs as $watchDir) { if ($watchDir != $patternSourceDir) { // iterate over the data files and regenerate the entire site if they've changed $objects = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($watchDir), \RecursiveIteratorIterator::SELF_FIRST); // make sure dots are skipped $objects->setFlags(\FilesystemIterator::SKIP_DOTS); foreach($objects as $name => $object) { $fileName = str_replace($sourceDir.DIRECTORY_SEPARATOR,"",$name); $mt = $object->getMTime(); if (!isset($o->$fileName)) { $o->$fileName = $mt; if ($c) { $this->updateSite($fileName,"added"); } } else if ($o->$fileName != $mt) { $o->$fileName = $mt; if ($c) { $this->updateSite($fileName,"changed"); } } } } } // iterate over all of the other files in the source directory and move them if their modified time has changed if ($moveStatic) { $objects = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($sourceDir.DIRECTORY_SEPARATOR), \RecursiveIteratorIterator::SELF_FIRST); // make sure dots are skipped $objects->setFlags(\FilesystemIterator::SKIP_DOTS); foreach($objects as $name => $object) { // clean-up the file name and make sure it's not one of the pattern lab files or to be ignored $fileName = str_replace($sourceDir.DIRECTORY_SEPARATOR,"",$name); if (($fileName[0] != "_") && (!in_array($object->getExtension(),$ignoreExts)) && (!in_array($object->getFilename(),$ignoreDirs))) { // catch directories that have the ignored dir in their path $ignoreDir = FileUtil::ignoreDir($fileName); // check to see if it's a new directory if (!$ignoreDir && $object->isDir() && !isset($o->$fileName) && !is_dir($publicDir."/".$fileName)) { mkdir($publicDir."/".$fileName); $o->$fileName = "dir created"; // placeholder Console::writeLine($fileName."/ directory was created..."); } // check to see if it's a new file or a file that has changed if (file_exists($name)) { $mt = $object->getMTime(); if (!$ignoreDir && $object->isFile() && !isset($o->$fileName) && !file_exists($publicDir."/".$fileName)) { $o->$fileName = $mt; FileUtil::moveStaticFile($fileName,"added"); if ($object->getExtension() == "css") { $this->updateSite($fileName,"changed",0); // make sure the site is updated for MQ reasons } } else if (!$ignoreDir && $object->isFile() && isset($o->$fileName) && ($o->$fileName != $mt)) { $o->$fileName = $mt; FileUtil::moveStaticFile($fileName,"changed"); if ($object->getExtension() == "css") { $this->updateSite($fileName,"changed",0); // make sure the site is updated for MQ reasons } } else if (!isset($o->fileName)) { $o->$fileName = $mt; } } else { unset($o->$fileName); } } } } $c = true; // taking out the garbage. basically killing mustache after each run. if (gc_enabled()) gc_collect_cycles(); // pause for .05 seconds to give the CPU a rest usleep(50000); } } /** * Updates the Pattern Lab Website and prints the appropriate message * @param {String} file name to included in the message * @param {String} a switch for decided which message isn't printed * * @return {String} the final message */ private function updateSite($fileName,$message,$verbose = true) { $watchMessage = ""; if ($verbose) { if ($message == "added") { $watchMessage = "".$fileName." was added to Pattern Lab. reload the website to see this change in the navigation..."; } elseif ($message == "removed") { $watchMessage = "".$fileName." was removed from Pattern Lab. reload the website to see this change reflected in the navigation..."; } elseif ($message == "hidden") { $watchMessage = "".$fileName." was hidden from Pattern Lab. reload the website to see this change reflected in the navigation..."; } else { $watchMessage = "".$fileName." changed..."; } } $options = $this->options; $options["watchVerbose"] = $verbose; $options["watchMessage"] = $watchMessage; $options["moveStatic"] = false; // clear the various data stores for re-population Data::clear(); PatternData::clear(); Annotations::clear(); $g = new Generator(); $g->generate($options); } protected function starterKitPathPrompt() { // need to figure this out long-term InstallerUtil::$isInteractive = true; $input = Console::promptInput("Tell me the path to the starterkit you want to watch.","e.g. vendor/pattern-lab/starterkit-mustache-demo/dist","baz",false); // set-up the full starterkit path $starterKitPath = Config::getOption("baseDir").$input; if (!is_dir($starterKitPath)) { Console::writeWarning("that doesn't seem to be a real directory. let's try again..."); $starterKitPath = $this->starterKitPathPrompt(); } return $starterKitPath; } public function watchStarterKit() { // default vars $starterKitPath = $this->starterKitPathPrompt(); $sourceDir = Config::getOption("sourceDir"); $fs = new Filesystem(); $c = false; // track that one loop through the pattern file listing has completed $o = new \stdClass(); // create an object to hold the properties $cp = new \stdClass(); // create an object to hold a clone of $o $o->patterns = new \stdClass(); Console::writeLine("watching your starterkit for changes..."); // run forever while (true) { // clone the patterns so they can be checked in case something gets deleted $cp = clone $o->patterns; $objects = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($starterKitPath), \RecursiveIteratorIterator::SELF_FIRST); // make sure dots are skipped $objects->setFlags(\FilesystemIterator::SKIP_DOTS); foreach ($objects as $name => $object) { // clean-up the file name and make sure it's not one of the pattern lab files or to be ignored $fileName = str_replace($starterKitPath.DIRECTORY_SEPARATOR,"",$name); // check to see if it's a new directory if ($object->isDir() && !isset($o->$fileName) && !is_dir($starterKitPath."/".$fileName)) { mkdir($sourceDir."/".$fileName); $o->$fileName = "dir created"; // placeholder Console::writeLine($fileName."/ directory was created..."); } // check to see if it's a new file or a file that has changed if (file_exists($name)) { $mt = $object->getMTime(); if ($object->isFile() && !isset($o->$fileName) && !file_exists($sourceDir.DIRECTORY_SEPARATOR.$fileName)) { $o->$fileName = $mt; $fs->copy($starterKitPath.DIRECTORY_SEPARATOR.$fileName,$sourceDir.DIRECTORY_SEPARATOR.$fileName); Console::writeInfo($fileName." added..."); } else if ($object->isFile() && isset($o->$fileName) && ($o->$fileName != $mt)) { $o->$fileName = $mt; $fs->copy($starterKitPath.DIRECTORY_SEPARATOR.$fileName,$sourceDir.DIRECTORY_SEPARATOR.$fileName); Console::writeInfo($fileName." changed..."); } else if (!isset($o->fileName)) { $o->$fileName = $mt; } } else { unset($o->$fileName); } } $c = true; // taking out the garbage. basically killing mustache after each run. if (gc_enabled()) gc_collect_cycles(); // pause for .05 seconds to give the CPU a rest usleep(50000); } } }