public function testUuidMethodGeneratesRandomIds() { $uuids = array(); while (count($uuids) < 1000) { $uuids[] = Toolkit::uuid(); } $this->assertEquals(count($uuids), count(array_unique($uuids))); }
public function __construct() { parent::__construct(); $app = $this; // -- global generic parameters --------------------------------------- $this['app.debug'] = false; $this['app.charset'] = 'UTF-8'; $this['app.name'] = 'easybook'; $this['app.signature'] = <<<SIGNATURE | | ,---.,---.,---., .|---.,---.,---.|__/ |---',---|`---.| || || || || \\ `---'`---^`---'`---|`---'`---'`---'` ` `---' SIGNATURE; // -- global directories location ------------------------------------- $this['app.dir.base'] = realpath(__DIR__ . '/../../../'); $this['app.dir.cache'] = $this['app.dir.base'] . '/app/Cache'; $this['app.dir.doc'] = $this['app.dir.base'] . '/doc'; $this['app.dir.resources'] = $this['app.dir.base'] . '/app/Resources'; $this['app.dir.plugins'] = $this['app.dir.base'] . '/src/Easybook/Plugins'; $this['app.dir.translations'] = $this['app.dir.resources'] . '/Translations'; $this['app.dir.skeletons'] = $this['app.dir.resources'] . '/Skeletons'; $this['app.dir.themes'] = $this['app.dir.resources'] . '/Themes'; // -- console --------------------------------------------------------- $this['console.input'] = null; $this['console.output'] = null; $this['console.dialog'] = null; // -- timer ----------------------------------------------------------- $this['app.timer.start'] = 0.0; $this['app.timer.finish'] = 0.0; // -- publishing process variables ------------------------------------ // holds the app theme dir for the current edition $this['publishing.dir.app_theme'] = ''; $this['publishing.dir.book'] = ''; $this['publishing.dir.contents'] = ''; $this['publishing.dir.resources'] = ''; $this['publishing.dir.plugins'] = ''; $this['publishing.dir.templates'] = ''; $this['publishing.dir.output'] = ''; $this['publishing.edition'] = ''; $this['publishing.items'] = array(); // the specific item currently being parsed/modified/decorated/... $this['publishing.active_item'] = array(); $this['publishing.active_item.toc'] = array(); $this['publishing.book.config'] = array('book' => array()); $this['publishing.book.slug'] = ''; $this['publishing.book.items'] = array(); // the real TOC used to generate the book (needed for html_chunked editions) $this['publishing.book.toc'] = array(); // holds all the internal links (used in html_chunked and epub editions) $this['publishing.links'] = array(); $this['publishing.list.images'] = array(); $this['publishing.list.tables'] = array(); $this['publishing.edition.id'] = function ($app) { if (null !== ($isbn = $app->edition('isbn'))) { return array('scheme' => 'isbn', 'value' => $isbn); } // for ISBN-less books, generate a unique RFC 4211 UUID v4 ID return array('scheme' => 'URN', 'value' => Toolkit::uuid()); }; // maintained for backwards compatibility $this['publishing.id'] = function () { trigger_error('The "publishing.id" option is deprecated since version 5.0 and will be removed in the future. Use "publishing.edition.id" instead.', E_USER_DEPRECATED); }; // -- event dispatcher ------------------------------------------------ $this['dispatcher'] = function () { return new EventDispatcher(); }; // -- finder ---------------------------------------------------------- $this['finder'] = $this->factory(function () { return new Finder(); }); // -- filesystem ------------------------------------------------------ $this['filesystem'] = $this->factory(function () { return new Filesystem(); }); // -- configurator ---------------------------------------------------- $this['configurator'] = function ($app) { return new BookConfigurator($app); }; // -- validator ------------------------------------------------------- $this['validator'] = function ($app) { return new Validator($app); }; $this->register(new PublisherServiceProvider()); $this->register(new ParserServiceProvider()); $this->register(new TwigServiceProvider()); $this->register(new PrinceXMLServiceProvider()); $this->register(new KindleGenServiceProvider()); $this->register(new SluggerServiceProvider()); $this->register(new CodeHighlighterServiceProvider()); // -- labels --------------------------------------------------------- $this['labels'] = function () use($app) { $labels = Yaml::parse($app['app.dir.translations'] . '/labels.' . $app->book('language') . '.yml'); // books can define their own labels files if (null !== ($customLabelsFile = $app->getCustomLabelsFile())) { $customLabels = Yaml::parse($customLabelsFile); return Toolkit::array_deep_merge_and_replace($labels, $customLabels); } return $labels; }; // -- titles ---------------------------------------------------------- $this['titles'] = function () use($app) { $titles = Yaml::parse($app['app.dir.translations'] . '/titles.' . $app->book('language') . '.yml'); // books can define their own titles files if (null !== ($customTitlesFile = $app->getCustomTitlesFile())) { $customTitles = Yaml::parse($customTitlesFile); return Toolkit::array_deep_merge_and_replace($titles, $customTitles); } return $titles; }; }
public function __construct() { $app = $this; // -- global generic parameters --------------------------------------- $this['app.debug'] = true; $this['app.charset'] = 'UTF-8'; $this['app.name'] = 'easybook'; $this['app.version'] = '4.4'; $this['app.signature'] = "\n" . " | | \n" . " ,---.,---.,---., .|---.,---.,---.|__/ \n" . " |---',---|`---.| || || || || \\ \n" . " `---'`---^`---'`---|`---'`---'`---'` `\n" . " `---'\n"; // -- global directories location ------------------------------------- $this['app.dir.base'] = realpath(__DIR__ . '/../../../'); $this['app.dir.cache'] = $this['app.dir.base'] . '/app/Cache'; $this['app.dir.doc'] = $this['app.dir.base'] . '/doc'; $this['app.dir.resources'] = $this['app.dir.base'] . '/app/Resources'; $this['app.dir.plugins'] = $this['app.dir.base'] . '/src/Easybook/Plugins'; $this['app.dir.translations'] = $this['app.dir.resources'] . '/Translations'; $this['app.dir.skeletons'] = $this['app.dir.resources'] . '/Skeletons'; $this['app.dir.themes'] = $this['app.dir.resources'] . '/Themes'; // -- default edition options ----------------------------------------- // TODO: each edition type should define different values $this['app.edition.defaults'] = array('format' => 'html', 'highlight_code' => false, 'include_styles' => true, 'isbn' => null, 'labels' => array('appendix', 'chapter', 'figure'), 'margin' => array('top' => '25mm', 'bottom' => '25mm', 'inner' => '30mm', 'outter' => '20mm'), 'page_size' => 'A4', 'theme' => 'clean', 'toc' => array('deep' => 2, 'elements' => array('appendix', 'chapter')), 'two_sided' => true); // -- timer ----------------------------------------------------------- $this['app.timer.start'] = 0.0; $this['app.timer.finish'] = 0.0; // -- publishing process variables ------------------------------------ // holds the app theme dir for the current edition $this['publishing.dir.app_theme'] = ''; $this['publishing.dir.book'] = ''; $this['publishing.dir.contents'] = ''; $this['publishing.dir.resources'] = ''; $this['publishing.dir.plugins'] = ''; $this['publishing.dir.templates'] = ''; $this['publishing.dir.output'] = ''; $this['publishing.edition'] = ''; $this['publishing.items'] = array(); // the specific item currently being parsed/modified/decorated/... $this['publishing.active_item'] = array(); $this['publishing.book.slug'] = ''; $this['publishing.book.items'] = array(); // holds all the generated slugs, to avoid repetitions $this['publishing.slugs'] = array(); // holds all the internal links (used in html_chunked and epub editions) $this['publishing.links'] = array(); $this['publishing.list.images'] = array(); $this['publishing.list.tables'] = array(); $this['publishing.id'] = $this->share(function ($app) { if (null != ($isbn = $app->edition('isbn'))) { return array('scheme' => 'isbn', 'value' => $isbn); } // if the book doesn't declare an ISBN, generate // a unique ID based on RFC 4211 UUID v4 return array('scheme' => 'URN', 'value' => Toolkit::uuid()); }); // -- event dispatcher ------------------------------------------------ $this['dispatcher'] = $this->share(function () { return new EventDispatcher(); }); // -- finder ---------------------------------------------------------- $this['finder'] = function () { return new Finder(); }; // -- filesystem ------------------------------------------------------ $this['filesystem'] = $this->share(function ($app) { return new Filesystem(); }); // -- publisher ------------------------------------------------------- $this['publisher'] = $this->share(function ($app) { $outputFormat = $app->edition('format'); switch (strtolower($outputFormat)) { case 'pdf': return new PdfPublisher($app); case 'html': return new HtmlPublisher($app); case 'html_chunked': return new HtmlChunkedPublisher($app); case 'epub': case 'epub2': return new Epub2Publisher($app); //case 'epub3': // return new Epub3Publisher($app); //case 'epub3': // return new Epub3Publisher($app); default: throw new \Exception(sprintf('Unknown "%s" format for "%s" edition (allowed: "pdf", "html", "html_chunked", "epub", "epub2")', $outputFormat, $app->get('publishing.edition'))); } }); // -- parser ---------------------------------------------------------- $this['parser'] = $this->share(function ($app) { $format = strtolower($app['publishing.active_item']['config']['format']); // TODO: extensibility -> support several format parsers (RST, Textile, ...) switch ($format) { case 'md': case 'mdown': case 'markdown': return new MdParser($app); default: throw new \Exception(sprintf('Unknown "%s" format for "%s" content (easybook only supports Markdown)', $format, $app['publishing.active_item']['config']['content'])); } }); // -- twig ------------------------------------------------------------ $this['twig.options'] = array('autoescape' => false, 'charset' => $this['app.charset'], 'debug' => $this['app.debug'], 'strict_variables' => $this['app.debug']); // the twig path used by render() function. This value is set by convenience methods // (renderCustomTemplate(), renderThemeTemplate(), ...) before template rendering $this['twig.path'] = ''; // twig path for custom templates (user defined templates for book/edition) $this['twig.path.custom'] = $this->share(function ($app) { $paths = array(); // edition custom templates // <book-dir>/Resources/Templates/<edition-name>/<template-name>.twig $dir = $app['publishing.dir.templates'] . '/' . $app['publishing.edition']; if (file_exists($dir)) { $paths[] = $dir; } // edition type custom templates (epub, pdf, html, html_chunked) // <book-dir>/Resources/Templates/<edition-type>/<template-name>.twig $dir = $app['publishing.dir.templates'] . '/' . $app->edition('format'); if (file_exists($dir)) { $paths[] = $dir; } // book custom templates (same templates for all editions) // <book-dir>/Resources/Templates/<template-name>.twig $dir = $app['publishing.dir.templates']; if (file_exists($dir)) { $paths[] = $dir; } return $paths; }); // twig path for default theme templates (easybook built-in templates) $this['twig.path.theme'] = $this->share(function ($app) { $paths = array(); $theme = ucfirst($app->edition('theme')); $format = Toolkit::camelize($app->edition('format'), true); // TODO: fix the following hack if ('Epub' == $format) { $format = 'Epub2'; } // default templates for the edition/book theme // <easybook>/app/Resources/Themes/<theme>/<edition-type>/Templates/<template-name>.twig $dir = sprintf('%s/%s/%s/Templates', $app['app.dir.themes'], $theme, $format); if (file_exists($dir)) { $paths[] = $dir; } // default common templates for all the editions of the same theme // <easybook>/app/Resources/Themes/<theme>/Common/Templates/<template-name>.twig $dir = sprintf('%s/%s/Common/Templates', $app['app.dir.themes'], $theme); if (file_exists($dir)) { $paths[] = $dir; } // default base theme for every edition and every book // <easybook>/app/Resources/Themes/Base/<edition-type>/Templates/<template-name>.twig $dir = sprintf('%s/Base/%s/Templates', $app['app.dir.themes'], $format); if (file_exists($dir)) { $paths[] = $dir; } return $paths; }); // twig path for default content templates // (easybook built-in templates for contents; e.g. `license.md.twig`) $this['twig.path.contents'] = $this->share(function ($app) { $paths = array(); $theme = ucfirst($app->edition('theme')); $format = Toolkit::camelize($app->edition('format'), true); // TODO: fix the following hack if ('Epub' == $format) { $format = 'Epub2'; } // default content templates for the edition/book theme // <easybook>/app/Resources/Themes/<theme>/<edition-type>/Contents/<template-name>.twig $dir = sprintf('%s/%s/%s/Contents', $app['app.dir.themes'], $theme, $format); if (file_exists($dir)) { $paths[] = $dir; } // default content templates for every edition and every book // <easybook>/app/Resources/Themes/Base/<edition-type>/Contents/<template-name>.twig $dir = sprintf('%s/Base/%s/Contents', $app['app.dir.themes'], $format); if (file_exists($dir)) { $paths[] = $dir; } return $paths; }); $this['twig.loader'] = function () use($app) { return new \Twig_Loader_Filesystem($app['twig.path']); }; $this['twig'] = function () use($app) { $twig = new \Twig_Environment($app['twig.loader'], $app['twig.options']); $twig->addExtension(new TwigCssExtension()); $twig->addGlobal('app', $app); if (null != $app->get('book')) { $twig->addGlobal('book', $app->get('book')); $publishingEdition = $app->get('publishing.edition'); $editions = $app->book('editions'); $twig->addGlobal('edition', $editions[$publishingEdition]); } return $twig; }; // -- princeXML ------------------------------------------------------- $this['prince.default_paths'] = array('/usr/local/bin/prince', '/usr/bin/prince', 'C:\\Program Files\\Prince\\engine\\bin\\prince.exe'); $this['prince'] = $app->share(function () use($app) { // look for the executable file of PrinceXML $princePath = null; foreach ($app['prince.default_paths'] as $path) { if (file_exists($path)) { $princePath = $path; break; } } if (null == $princePath) { echo sprintf(" In order to generate PDF files, PrinceXML library must be installed. \n\n" . " We couldn't find PrinceXML executable in any of the following directories: \n" . " -> %s \n\n" . " If you haven't installed it yet, you can download a fully-functional demo at: \n" . " %s \n\n" . " If you have installed in a custom directory, please type its full absolute path:\n > ", implode($app['prince.default_paths'], "\n -> "), 'http://www.princexml.com/download'); $input = trim(fgets(STDIN)); if (file_exists($input)) { $princePath = $input; echo "\n"; } else { throw new \Exception(sprintf("We couldn't find the PrinceXML executable in the given directory (%s)", $input)); } } $prince = new Prince($princePath); $prince->setHtml(true); return $prince; }); // -- slugger --------------------------------------------------------- $this['slugger'] = $app->share(function () use($app) { return new Slugger($app); }); // -- code syntax highlighter ----------------------------------------- $this['geshi'] = function () use($app) { require_once __DIR__ . '/../../../vendor/geshi/geshi/geshi.php'; $geshi = new \GeSHi(); $geshi->enable_classes(); // this must be the first method (see Geshi doc) $geshi->set_encoding($app['app.charset']); $geshi->enable_line_numbers(GESHI_NO_LINE_NUMBERS); $geshi->enable_keyword_links(false); return $geshi; }; // -- labels --------------------------------------------------------- $this['labels'] = $app->share(function () use($app) { $labels = Yaml::parse($app['app.dir.translations'] . '/labels.' . $app->book('language') . '.yml'); // books can define their own labels files if (null != ($customLabelsFile = $app->getCustomLabelsFile())) { $customLabels = Yaml::parse($customLabelsFile); return Toolkit::array_deep_merge($labels, $customLabels); } return $labels; }); // -- titles ---------------------------------------------------------- $this['titles'] = $app->share(function () use($app) { $titles = Yaml::parse($app['app.dir.translations'] . '/titles.' . $app->book('language') . '.yml'); // books can define their own titles files if (null != ($customTitlesFile = $app->getCustomTitlesFile())) { $customTitles = Yaml::parse($customTitlesFile); return Toolkit::array_deep_merge($titles, $customTitles); } return $titles; }); }