/**
  * Gather data from annotations.js and *.md files found in source/_annotations
  *
  * @return {Array}        populates Annotations::$store
  */
 public static function gather()
 {
     // set-up default var
     $annotationsDir = Config::getOption("annotationsDir");
     // set-up the dispatcher
     $dispatcherInstance = Dispatcher::getInstance();
     // dispatch that the data gather has started
     $dispatcherInstance->dispatch("annotations.gatherStart");
     // set-up the comments store
     self::$store["comments"] = array();
     // create the annotations dir if it doesn't exist
     if (!is_dir($annotationsDir)) {
         mkdir($annotationsDir);
     }
     // find the markdown-based annotations
     $finder = new Finder();
     $finder->files()->name("*.md")->in($annotationsDir);
     $finder->sortByName();
     foreach ($finder as $name => $file) {
         $data = array();
         $data[0] = array();
         $text = file_get_contents($file->getPathname());
         $matches = strpos($text, PHP_EOL . "~*~" . PHP_EOL) !== false ? explode(PHP_EOL . "~*~" . PHP_EOL, $text) : array($text);
         foreach ($matches as $match) {
             list($yaml, $markdown) = Documentation::parse($match);
             if (isset($yaml["el"]) || isset($yaml["selector"])) {
                 $data[0]["el"] = isset($yaml["el"]) ? $yaml["el"] : $yaml["selector"];
             } else {
                 $data[0]["el"] = "#someimpossibleselector";
             }
             $data[0]["title"] = isset($yaml["title"]) ? $yaml["title"] : "";
             $data[0]["comment"] = $markdown;
             self::$store["comments"] = array_merge(self::$store["comments"], $data);
         }
     }
     // read in the old style annotations.js, modify the data and generate JSON array to merge
     $data = array();
     $oldStyleAnnotationsPath = $annotationsDir . DIRECTORY_SEPARATOR . "annotations.js";
     if (file_exists($oldStyleAnnotationsPath)) {
         $text = trim(file_get_contents($oldStyleAnnotationsPath));
         $text = str_replace("var comments = ", "", $text);
         if ($text[strlen($text) - 1] == ";") {
             $text = rtrim($text, ";");
         }
         $data = json_decode($text, true);
         if ($jsonErrorMessage = JSON::hasError()) {
             JSON::lastErrorMsg(Console::getHumanReadablePath($oldStyleAnnotationsPath), $jsonErrorMessage, $data);
         }
     }
     // merge in any data from the old file if the json decode was successful
     if (is_array($data) && isset($data["comments"])) {
         self::$store["comments"] = array_merge(self::$store["comments"], $data["comments"]);
     }
     $dispatcherInstance->dispatch("annotations.gatherEnd");
 }
 /**
  * Check that a dir from the config exists and try to build it if needed
  * @param  {String}       directory to be checked/built
  * @param  {String}       the config path
  * @param  {String}       the config option that would help them
  */
 public static function checkPathFromConfig($path, $configPath, $configOption = "")
 {
     if (!isset($path)) {
         Console::writeError("please make sure " . $configOption . " is set in <path>" . Console::getHumanReadablePath($configPath) . "</path> by adding '" . $configOption . "=some/path'. sorry, stopping pattern lab... :(");
     } else {
         if (!is_dir($path)) {
             Console::writeWarning("i can't seem to find the directory <path>" . Console::getHumanReadablePath($path) . "</path>...");
             self::makeDir($path);
             Console::writeWarning("i created <path>" . Console::getHumanReadablePath($path) . "</path> just in case. you can edit this in <path>" . Console::getHumanReadablePath($configPath) . "</path> by editing " . $configOption . "...");
         }
     }
 }
 /**
  * Set-up default vars
  */
 public static function init()
 {
     // make sure config vars exist
     if (!Config::getOption("patternExtension")) {
         Console::writeError("the pattern extension config option needs to be set...");
     }
     if (!Config::getOption("styleguideKit")) {
         Console::writeError("the styleguideKit config option needs to be set...");
     }
     // set-up config vars
     $patternExtension = Config::getOption("patternExtension");
     $metaDir = Config::getOption("metaDir");
     $styleguideKit = Config::getOption("styleguideKit");
     $styleguideKitPath = Config::getOption("styleguideKitPath");
     if (!$styleguideKitPath || !is_dir($styleguideKitPath)) {
         Console::writeError("your styleguide won't render because i can't find your styleguide files. are you sure they're at <path>" . Console::getHumanReadablePath($styleguideKitPath) . "</path>? you can fix this in <path>./config/config.yml</path> by editing styleguideKitPath...");
     }
     // load pattern-lab's resources
     $partialPath = $styleguideKitPath . DIRECTORY_SEPARATOR . "views" . DIRECTORY_SEPARATOR . "partials";
     $generalHeaderPath = $partialPath . DIRECTORY_SEPARATOR . "general-header." . $patternExtension;
     $generalFooterPath = $partialPath . DIRECTORY_SEPARATOR . "general-footer." . $patternExtension;
     self::$htmlHead = file_exists($generalHeaderPath) ? file_get_contents($generalHeaderPath) : "";
     self::$htmlFoot = file_exists($generalFooterPath) ? file_get_contents($generalFooterPath) : "";
     // gather the user-defined header and footer information
     $patternHeadPath = $metaDir . DIRECTORY_SEPARATOR . "_00-head." . $patternExtension;
     $patternFootPath = $metaDir . DIRECTORY_SEPARATOR . "_01-foot." . $patternExtension;
     self::$patternHead = file_exists($patternHeadPath) ? file_get_contents($patternHeadPath) : "";
     self::$patternFoot = file_exists($patternFootPath) ? file_get_contents($patternFootPath) : "";
     // add the filesystemLoader
     $patternEngineBasePath = PatternEngine::getInstance()->getBasePath();
     $filesystemLoaderClass = $patternEngineBasePath . "\\Loaders\\FilesystemLoader";
     $options = array();
     $options["templatePath"] = $styleguideKitPath . DIRECTORY_SEPARATOR . "views";
     $options["partialsPath"] = $options["templatePath"] . DIRECTORY_SEPARATOR . "partials";
     self::$filesystemLoader = new $filesystemLoaderClass($options);
     $stringLoaderClass = $patternEngineBasePath . "\\Loaders\\StringLoader";
     self::$stringLoader = new $stringLoaderClass();
     // i can't remember why i chose to implement the pattern loader directly in classes
     // i figure i had a good reason which is why it's not showing up here
 }
 protected function starterKitSuggestions()
 {
     Console::writeLine("");
     $composerPath = Config::getOption("baseDir") . "/composer.json";
     if (file_exists($composerPath)) {
         $json = file_get_contents($composerPath);
         $data = json_decode($json, true);
         if ($jsonErrorMessage = JSON::hasError()) {
             JSON::lastErrorMsg(Console::getHumanReadablePath($oldStyleAnnotationsPath), $jsonErrorMessage, $data);
         }
         if (isset($data["extra"]) && isset($data["extra"]["patternlab"]) && isset($data["extra"]["patternlab"]["starterKitSuggestions"])) {
             $starterKitSuggestions = $data["extra"]["patternlab"]["starterKitSuggestions"];
             Console::writeInfo("suggested starterkits that work with this edition:", false, true);
             foreach ($starterKitSuggestions as $i => $suggestion) {
                 $num = $i + 1;
                 Console::writeLine($num . ": " . $suggestion, true);
             }
             // hack around installer util feature in Console::promptInput
             InstallerUtil::$isInteractive = true;
             // prompt for input on the suggestions
             Console::writeLine("");
             $prompt = "choose an option or hit return to cancel:";
             $options = "(ex. 1)";
             $input = Console::promptInput($prompt, $options, "1");
             $result = (int) $input - 1;
             if (isset($starterKitSuggestions[$result])) {
                 Console::writeLine("");
                 $f = new Fetch();
                 $result = $f->fetchStarterKit($starterKitSuggestions[$result]);
             }
         } else {
             Console::writeWarning("this edition has no starterkits to suggested...", false, true);
         }
     } else {
         Console::writeError("can't find composer.json to get suggestions...", false, true);
     }
 }
 /**
  * Write a warning if a dir doesn't exist
  * @param  {String}         the dir that doesn't exist
  */
 protected static function dirNotExist($dir)
 {
     $dirHR = Console::getHumanReadablePath($dir);
     Console::writeWarning("the path <path>" . $dirHR . "</path> doesn't exist so filters won't be loaded...");
 }
 /**
  * Generates the data that powers the index page
  */
 protected function generateIndex()
 {
     // bomb if missing index.html
     if (!file_exists(Config::getOption("publicDir") . "/index.html")) {
         $index = Console::getHumanReadablePath(Config::getOption("publicDir")) . DIRECTORY_SEPARATOR . "index.html";
         Console::writeError("<path>" . $index . "</path> is missing. grab a copy from your StyleguideKit...");
     }
     // set-up the dispatcher
     $dispatcherInstance = Dispatcher::getInstance();
     // note the start of the operation
     $dispatcherInstance->dispatch("builder.generateIndexStart");
     // default var
     $dataDir = Config::getOption("publicDir") . "/styleguide/data";
     // double-check that the data directory exists
     if (!is_dir($dataDir)) {
         FileUtil::makeDir($dataDir);
     }
     $output = "";
     // load and write out the config options
     $config = array();
     $exposedOptions = Config::getOption("exposedOptions");
     foreach ($exposedOptions as $exposedOption) {
         $config[$exposedOption] = Config::getOption($exposedOption);
     }
     $output .= "var config = " . json_encode($config) . ";\n";
     // load the ish Controls
     $ishControls = array();
     $controlsToHide = array();
     $ishControlsHide = Config::getOption("ishControlsHide");
     if ($ishControlsHide) {
         foreach ($ishControlsHide as $controlToHide) {
             $controlsToHide[$controlToHide] = "true";
         }
     }
     $ishControls["ishControlsHide"] = $controlsToHide;
     $output .= "var ishControls = " . json_encode($ishControls) . ";\n";
     // load and write out the items for the navigation
     $niExporter = new NavItemsExporter();
     $navItems = $niExporter->run();
     $output .= "var navItems = " . json_encode($navItems) . ";\n";
     // load and write out the items for the pattern paths
     $patternPaths = array();
     $ppdExporter = new PatternPathDestsExporter();
     $patternPaths = $ppdExporter->run();
     $output .= "var patternPaths = " . json_encode($patternPaths) . ";\n";
     // load and write out the items for the view all paths
     $viewAllPaths = array();
     $vapExporter = new ViewAllPathsExporter();
     $viewAllPaths = $vapExporter->run($navItems);
     $output .= "var viewAllPaths = " . json_encode($viewAllPaths) . ";\n";
     // gather plugin package information
     $packagesInfo = array();
     $componentDir = Config::getOption("componentDir");
     if (!is_dir($componentDir)) {
         mkdir($componentDir);
     }
     $componentPackagesDir = $componentDir . "/packages";
     if (!is_dir($componentDir . "/packages")) {
         mkdir($componentDir . "/packages");
     }
     $finder = new Finder();
     $finder->files()->name("*.json")->in($componentPackagesDir);
     $finder->sortByName();
     foreach ($finder as $file) {
         $filename = $file->getFilename();
         if ($filename[0] != "_") {
             $javascriptPaths = array();
             $packageInfo = json_decode(file_get_contents($file->getPathname()), true);
             foreach ($packageInfo["templates"] as $templateKey => $templatePath) {
                 $templatePathFull = $componentDir . "/" . $packageInfo["name"] . "/" . $templatePath;
                 $packageInfo["templates"][$templateKey] = file_exists($templatePathFull) ? file_get_contents($templatePathFull) : "";
             }
             foreach ($packageInfo["javascripts"] as $key => $javascriptPath) {
                 $javascriptPaths[] = "patternlab-components/" . $packageInfo["name"] . "/" . $javascriptPath;
             }
             $packageInfo["javascripts"] = $javascriptPaths;
             $packagesInfo[] = $packageInfo;
         }
     }
     $output .= "var plugins = " . json_encode($packagesInfo) . ";";
     // write out the data
     file_put_contents($dataDir . "/patternlab-data.js", $output);
     // note the end of the operation
     $dispatcherInstance->dispatch("builder.generateIndexEnd");
 }
 /**
  * Gather all of the information related to the patterns
  */
 public static function gather($options = array())
 {
     // set default vars
     $exportClean = isset($options["exportClean"]) ? $options["exportClean"] : false;
     $exportFiles = isset($options["exportClean"]) ? $options["exportFiles"] : false;
     $dispatcherInstance = Dispatcher::getInstance();
     // cleaning the var for use below, i know this is stupid
     $options = array();
     // dispatch that the data gather has started
     $event = new PatternDataEvent($options);
     $dispatcherInstance->dispatch("patternData.gatherStart", $event);
     // load up the rules for parsing patterns and the directories
     self::loadRules($options);
     // dispatch that the rules are loaded
     $event = new PatternDataEvent($options);
     $dispatcherInstance->dispatch("patternData.rulesLoaded", $event);
     // iterate over the patterns & related data and regenerate the entire site if they've changed
     // seems a little silly to use symfony finder here. not really giving me any power
     if (!is_dir(Config::getOption("patternSourceDir"))) {
         Console::writeError("having patterns is important. please make sure you've installed a starterkit and/or that " . Console::getHumanReadablePath(Config::getOption("patternSourceDir")) . " exists...");
     }
     $patternObjects = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator(Config::getOption("patternSourceDir")), \RecursiveIteratorIterator::SELF_FIRST);
     $patternObjects->setFlags(\FilesystemIterator::SKIP_DOTS);
     // sort the returned objects
     $patternObjects = iterator_to_array($patternObjects);
     ksort($patternObjects);
     $patternSourceDir = Config::getOption("patternSourceDir");
     foreach ($patternObjects as $name => $object) {
         $ext = $object->getExtension();
         $isDir = $object->isDir();
         $isFile = $object->isFile();
         $path = str_replace($patternSourceDir . DIRECTORY_SEPARATOR, "", $object->getPath());
         $pathName = str_replace($patternSourceDir . DIRECTORY_SEPARATOR, "", $object->getPathname());
         $name = $object->getFilename();
         $depth = substr_count($pathName, DIRECTORY_SEPARATOR);
         // iterate over the rules and see if the current file matches one, if so run the rule
         foreach (self::$rules as $rule) {
             if ($rule->test($depth, $ext, $isDir, $isFile, $name)) {
                 $rule->run($depth, $ext, $path, $pathName, $name);
             }
         }
     }
     // dispatch that the data is loaded
     $event = new PatternDataEvent($options);
     $dispatcherInstance->dispatch("patternData.dataLoaded", $event);
     // make sure all of the appropriate pattern data is pumped into $this->d for rendering patterns
     $dataLinkExporter = new DataLinkExporter();
     $dataLinkExporter->run();
     // make sure all of the appropriate pattern data is pumped into $this->d for rendering patterns
     $dataMergeExporter = new DataMergeExporter();
     $dataMergeExporter->run();
     // dispatch that the raw pattern helper is about to start
     $event = new PatternDataEvent($options);
     $dispatcherInstance->dispatch("patternData.rawPatternHelperStart", $event);
     // add the lineage info to PatternData::$store
     $rawPatternHelper = new RawPatternHelper();
     $rawPatternHelper->run();
     // dispatch that the raw pattern helper is ended
     $event = new PatternDataEvent($options);
     $dispatcherInstance->dispatch("patternData.rawPatternHelperEnd", $event);
     // dispatch that the lineage helper is about to start
     $event = new PatternDataEvent($options);
     $dispatcherInstance->dispatch("patternData.lineageHelperStart", $event);
     // add the lineage info to PatternData::$store
     $lineageHelper = new LineageHelper();
     $lineageHelper->run();
     // dispatch that the lineage helper is ended
     $event = new PatternDataEvent($options);
     $dispatcherInstance->dispatch("patternData.lineageHelperEnd", $event);
     // dispatch that the pattern state helper is about to start
     $event = new PatternDataEvent($options);
     $dispatcherInstance->dispatch("patternData.patternStateHelperStart", $event);
     // using the lineage info update the pattern states on PatternData::$store
     $patternStateHelper = new PatternStateHelper();
     $patternStateHelper->run();
     // dispatch that the pattern state helper is ended
     $event = new PatternDataEvent($options);
     $dispatcherInstance->dispatch("patternData.patternStateHelperEnd", $event);
     // set-up code pattern paths
     $ppdExporter = new PatternPathSrcExporter();
     $patternPathSrc = $ppdExporter->run();
     $options = array();
     $options["patternPaths"] = $patternPathSrc;
     // dispatch that the code helper is about to start
     $event = new PatternDataEvent($options);
     $dispatcherInstance->dispatch("patternData.codeHelperStart", $event);
     // render out all of the patterns and store the generated info in PatternData::$store
     $options["exportFiles"] = $exportFiles;
     $options["exportClean"] = $exportClean;
     $patternCodeHelper = new PatternCodeHelper($options);
     $patternCodeHelper->run();
     // dispatch that the pattern code helper is ended
     $event = new PatternDataEvent($options);
     $dispatcherInstance->dispatch("patternData.patternCodeHelperEnd", $event);
     // dispatch that the gather has ended
     $event = new PatternDataEvent($options);
     $dispatcherInstance->dispatch("patternData.gatherEnd", $event);
 }
 /**
  * Get the path to the calling script. Should be core/console but let's not make that assumption
  *
  * @return {String}      the path to the calling script
  */
 public static function getPathConsole()
 {
     $console = isset($_SERVER["SCRIPT_NAME"]) ? $_SERVER["SCRIPT_NAME"] : Config::getOption("phpScriptName");
     if (!$console) {
         $configPath = Console::getHumanReadablePath(Config::getOption("configPath"));
         Console::writeError("please add the option `phpScriptName` with the path to your console option (e.g. core" . DIRECTORY_SEPARATOR . "console) to <path>" . $configPath . "</path> to run this process...");
     }
     return Config::getOption("baseDir") . $console;
 }
 /**
  * Adds the config options to a var to be accessed from the rest of the system
  * If it's an old config or no config exists this will update and generate it.
  * @param  {Boolean}       whether we should print out the status of the config being loaded
  */
 public static function init($baseDir = "", $verbose = true)
 {
     // make sure a base dir was supplied
     if (empty($baseDir)) {
         Console::writeError("need a base directory to initialize the config class...");
     }
     // normalize the baseDir
     $baseDir = FileUtil::normalizePath($baseDir);
     // double-check the default config file exists
     if (!is_dir($baseDir)) {
         Console::writeError("make sure " . $baseDir . " exists...");
     }
     // set the baseDir option
     self::$options["baseDir"] = $baseDir[strlen($baseDir) - 1] == DIRECTORY_SEPARATOR ? $baseDir : $baseDir . DIRECTORY_SEPARATOR;
     // set-up the paths
     self::$userConfigDirClean = self::$options["baseDir"] . self::$userConfigDirClean;
     self::$userConfigDirDash = self::$options["baseDir"] . self::$userConfigDirDash;
     self::$userConfigDir = is_dir(self::$userConfigDirDash) ? self::$userConfigDirDash : self::$userConfigDirClean;
     self::$userConfigPath = self::$userConfigDir . DIRECTORY_SEPARATOR . self::$userConfig;
     self::$plConfigPath = self::$options["baseDir"] . "vendor/pattern-lab/core/" . self::$plConfigPath;
     // can't add __DIR__ above so adding here
     if (!is_dir(self::$userConfigDir)) {
         mkdir(self::$userConfigDir);
     }
     // check to see if the user config exists, if not create it
     if ($verbose) {
         Console::writeLine("configuring pattern lab...");
     }
     // make sure migrate doesn't happen by default
     $migrate = false;
     $diffVersion = false;
     $defaultOptions = array();
     $userOptions = array();
     // double-check the default config file exists
     if (!file_exists(self::$plConfigPath)) {
         Console::writeError("the default options for Pattern Lab don't seem to exist at <path>" . Console::getHumanReadablePath(self::$plConfigPath) . "</path>. please check on the install location of pattern lab...");
     }
     // set the default config using the pattern lab config
     try {
         $defaultOptions = Yaml::parse(file_get_contents(self::$plConfigPath));
         self::$options = array_merge(self::$options, $defaultOptions);
     } catch (ParseException $e) {
         Console::writeError("Config parse error in <path>" . Console::getHumanReadablePath(self::$plConfigPath) . "</path>: " . $e->getMessage());
     }
     // double-check the user's config exists. if not mark that we should migrate the default one
     if (file_exists(self::$userConfigPath)) {
         try {
             $userOptions = Yaml::parse(file_get_contents(self::$userConfigPath));
             self::$options = array_merge(self::$options, $userOptions);
         } catch (ParseException $e) {
             Console::writeError("Config parse error in <path>" . Console::getHumanReadablePath(self::$userConfigPath) . "</path>: " . $e->getMessage());
         }
     } else {
         $migrate = true;
     }
     // compare version numbers
     $diffVersion = isset($userOptions["v"]) && $userOptions["v"] == $defaultOptions["v"] ? false : true;
     // run an upgrade and migrations if necessary
     if ($migrate || $diffVersion) {
         if ($verbose) {
             Console::writeInfo("upgrading your version of pattern lab...");
         }
         if ($migrate) {
             if (!@copy(self::$plConfigPath, self::$userConfigPath)) {
                 Console::writeError("make sure that Pattern Lab can write a new config to " . self::$userConfigPath . "...");
                 exit;
             }
         } else {
             self::$options = self::writeNewConfigFile(self::$options, $defaultOptions);
         }
     }
     // making sure the config isn't empty
     if (empty(self::$options) && $verbose) {
         Console::writeError("a set of configuration options is required to use Pattern Lab...");
         exit;
     }
     // set-up the various dirs
     self::$options["configDir"] = self::$userConfigDir;
     self::$options["configPath"] = self::$userConfigPath;
     self::$options["coreDir"] = is_dir(self::$options["baseDir"] . "_core") ? self::$options["baseDir"] . "_core" : self::$options["baseDir"] . "core";
     self::$options["exportDir"] = isset(self::$options["exportDir"]) ? self::$options["baseDir"] . self::cleanDir(self::$options["exportDir"]) : self::$options["baseDir"] . "exports";
     self::$options["publicDir"] = isset(self::$options["publicDir"]) ? self::$options["baseDir"] . self::cleanDir(self::$options["publicDir"]) : self::$options["baseDir"] . "public";
     self::$options["scriptsDir"] = isset(self::$options["scriptsDir"]) ? self::$options["baseDir"] . self::cleanDir(self::$options["scriptsDir"]) : self::$options["baseDir"] . "scripts";
     self::$options["sourceDir"] = isset(self::$options["sourceDir"]) ? self::$options["baseDir"] . self::cleanDir(self::$options["sourceDir"]) : self::$options["baseDir"] . "source";
     self::$options["componentDir"] = isset(self::$options["componentDir"]) ? self::$options["publicDir"] . DIRECTORY_SEPARATOR . self::cleanDir(self::$options["componentDir"]) : self::$options["publicDir"] . DIRECTORY_SEPARATOR . "patternlab-components";
     self::$options["dataDir"] = isset(self::$options["dataDir"]) ? self::$options["sourceDir"] . DIRECTORY_SEPARATOR . self::cleanDir(self::$options["dataDir"]) : self::$options["sourceDir"] . DIRECTORY_SEPARATOR . "_data";
     self::$options["patternExportDir"] = isset(self::$options["patternExportDir"]) ? self::$options["exportDir"] . DIRECTORY_SEPARATOR . self::cleanDir(self::$options["patternExportDir"]) : self::$options["exportDir"] . DIRECTORY_SEPARATOR . "patterns";
     self::$options["patternPublicDir"] = isset(self::$options["patternPublicDir"]) ? self::$options["publicDir"] . DIRECTORY_SEPARATOR . self::cleanDir(self::$options["patternPublicDir"]) : self::$options["publicDir"] . DIRECTORY_SEPARATOR . "patterns";
     self::$options["patternSourceDir"] = isset(self::$options["patternSourceDir"]) ? self::$options["sourceDir"] . DIRECTORY_SEPARATOR . self::cleanDir(self::$options["patternSourceDir"]) : self::$options["sourceDir"] . DIRECTORY_SEPARATOR . "_patterns";
     self::$options["metaDir"] = isset(self::$options["metaDir"]) ? self::$options["sourceDir"] . DIRECTORY_SEPARATOR . self::cleanDir(self::$options["metaDir"]) : self::$options["sourceDir"] . DIRECTORY_SEPARATOR . "_meta/";
     self::$options["annotationsDir"] = isset(self::$options["annotationsDir"]) ? self::$options["sourceDir"] . DIRECTORY_SEPARATOR . self::cleanDir(self::$options["annotationsDir"]) : self::$options["sourceDir"] . DIRECTORY_SEPARATOR . "_annotations/";
     // set-up outputFileSuffixes
     self::$options["outputFileSuffixes"]["rendered"] = isset(self::$options["outputFileSuffixes"]["rendered"]) ? self::$options["outputFileSuffixes"]["rendered"] : '';
     self::$options["outputFileSuffixes"]["rawTemplate"] = isset(self::$options["outputFileSuffixes"]["rawTemplate"]) ? self::$options["outputFileSuffixes"]["rawTemplate"] : '';
     self::$options["outputFileSuffixes"]["markupOnly"] = isset(self::$options["outputFileSuffixes"]["markupOnly"]) ? self::$options["outputFileSuffixes"]["markupOnly"] : '.markup-only';
     // handle a pre-2.1.0 styleguideKitPath before saving it
     if (isset(self::$options["styleguideKitPath"])) {
         self::$options["styleguideKitPath"] = self::$options["baseDir"] . self::cleanDir(self::getStyleguideKitPath(self::$options["styleguideKitPath"]));
     }
     // double-check a few directories are real and set-up
     FileUtil::checkPathFromConfig(self::$options["sourceDir"], self::$userConfigPath, "sourceDir");
     FileUtil::checkPathFromConfig(self::$options["publicDir"], self::$userConfigPath, "publicDir");
     // make sure styleguideExcludes is set to an array even if it's empty
     if (is_string(self::$options["styleGuideExcludes"])) {
         self::$options["styleGuideExcludes"] = array();
     }
     // set the cacheBuster
     self::$options["cacheBuster"] = self::$options["cacheBusterOn"] == "false" ? 0 : time();
     // provide the default for enable CSS. performance hog so it should be run infrequently
     self::$options["enableCSS"] = false;
     // which of these should be exposed in the front-end?
     self::$options["exposedOptions"] = array();
     self::setExposedOption("cacheBuster");
     self::setExposedOption("defaultPattern");
     self::setExposedOption("defaultShowPatternInfo");
     self::setExposedOption("ishFontSize");
     self::setExposedOption("ishMaximum");
     self::setExposedOption("ishMinimum");
     self::setExposedOption("outputFileSuffixes");
     self::setExposedOption("plugins");
 }