/** * 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); } }