nanoc-4.8.0/0000755000004100000410000000000013134141051012642 5ustar www-datawww-datananoc-4.8.0/Rakefile0000644000004100000410000000074713134141051014317 0ustar www-datawww-data# frozen_string_literal: true require 'rubocop/rake_task' require 'rspec/core/rake_task' require 'rake/testtask' require 'coveralls/rake/task' RuboCop::RakeTask.new(:rubocop) Coveralls::RakeTask.new Rake::TestTask.new(:test_all) do |t| t.test_files = Dir['test/**/test_*.rb'] t.libs << 'test' t.verbose = false end RSpec::Core::RakeTask.new(:spec) do |t| t.verbose = false end task test: %i[spec test_all rubocop] task test_ci: %i[test coveralls:push] task default: :test nanoc-4.8.0/NEWS.md0000644000004100000410000014451213134141051013747 0ustar www-datawww-data# Nanoc news ## 4.8.0 (2017-07-17) Featured: * Added `asciidoctor` filter (#1199) ## 4.7.14 (2017-07-16) Enhancements: * Various speed improvements (#1195, #1198) ## 4.7.13 (2017-07-02) Enhancements: * Made adding an item or a layout only recompile items that likely depend on it (#1191, #1193) ## 4.7.12 (2017-06-23) Fixes: * Fixed an issue in which the fog deployer could run out of open file descriptors (#1189) Enhancements: * Made changes to `@config` only cause outdatedness of items that depend on `@config` (#1188) ## 4.7.11 (2017-06-17) Fixes: * Made the `external_links` check always use GET, not HEAD (#1178, #1179) * Fixed an issue where changing identifiers during preprocessing could cause a crash (#1184, #1185) Enhancements: * Added `--preprocess` to `shell` command, to load preprocessed site data (#1180) ## 4.7.10 (2017-05-14) Fixes: * Fixed behavior of `parent_config_file` so that it merges, rather than overwrites (#1176) [Gregory Pakosz] Enhancements: * Made `slim` filter show the filename in error messages (#1175) [Alexander Groß] * Added `#use_helper` as an alternative to `#include` for enabling helpers (#1170) ## 4.7.9 (2017-05-01) Fixes: * Fixed character set handling of code snippets (#1171, #1174) Enhancements: * Added `xml:base` support (#1172, #1173) [Chris Burkhardt] This release drops support for Ruby 2.2, as it is no longer maintained. ## 4.7.8 (2017-04-22) Fixes: * Fixed a crash when printing dependency cycle error messages (#1164, #1166) * Fixed incorrect ordering in dependency cycle error messages (#1167, #1168) * Fixed a crash when attempting to print malformed UTF-8 strings (#1163, #1165) Enhancements: * Added various speed improvements (#1158, #1159, #1160) ## 4.7.7 (2017-04-16) Enhancements: * Added `--diff` option to `compile` command as a one-time alternative to `enable_output_diff` (#1155) * Sped up incremental compilation (#1156, #1157) ## 4.7.6 (2017-04-15) Enhancements: * Added support for `:html5` in `relativize_paths` and `colorize_syntax` filters (#1153) ## 4.7.5 (2017-04-01) Fixes: * Made `--verbose` be recognised when calling `nanoc` without subcommand (#1145, #1146) Enhancements: * Made `show-data` print all outdatedness reasons, not just the first (#1142) * Sped up `@items.find_all` (#1147) ## 4.7.4 (2017-03-29) Enhancements: * Made attribute dependencies cause outdatedness less often (#1136, #1137, #1138) * Improved speed of Rules file handling (#1140) ## 4.7.3 (2017-03-26) Fixes: * Fixed an issue which could cause a missing file for a snapshot that is not `:last` not to be regenerated when compiling (#1134, #1135) ## 4.7.2 (2017-03-21) Fixes: * Fixed crash when calling `#raw_path` in the Checks file (#1130, #1131) ## 4.7.1 (2017-03-19) Fixes: * Fixed issue with `:xsl` filter not recompiling when it should (#924, #1127) Enhancements: * Made `compile --verbose` print percentiles rather than averages (#1122) * Improved dependency cycle error messages (#1123) ## 4.7 (2017-03-15) Features: * Added `:erubi` filter (#1103) [Jan M. Faber] * Added `write ext: 'something'` shortcut (#1079) ## 4.6.4 (2017-03-10) Fixes: * Fixed issue where `compile --verbose` would show misleading timing information (#1113) ## 4.6.3 (2017-03-05) Fixes: * Fixed `Errno::ENOENT` crash (#1094, #1109) * Fixed undefined method `#reverse_each` crash (#1107, #1108) * Fixed compilation speed issue introduced in 4.6.2 (#1106) ## 4.6.2 (2017-03-04) Fixes: * Fixed crash in `#binary?` (#1082, #1083, #1084) * Fixed issue which would cause the file referenced by `#raw_path` not to exist (#1097, #1099) Enhancements: * Allowed calling `#write` multiple times in the same rule (#1037, #1085) * Changed the `html` check to use validator.nu (#1104) ## 4.6.1 (2017-01-29) Fixes: * Fixed table formatting in `compile --verbose` (#1074) [Hugo Peixoto] Enhancements: * Reduced memory usage (#1075, #1076, #1077, #1078) ## 4.6 (2017-01-22) Features: * Made `#content_for` accept a string as well as a block, to allow setting captured content outside of ERB (#1066, #1073) * Added `#raw_content=` to items and layouts during preprocessing (#1057) * Added `#snapshot?` to item rep views (#1056) Enhancements: * Prevented useless recompilations when switching Nanoc environments (#1070, #1071) ## 4.5.4 (2017-01-16) Fixes: * Fixed issue with site not being recompiled fully when switching between environments (#1067, #1069) ## 4.5.3 (2017-01-15) Fixes: * Fixed “Fixnum is deprecated” message (#1061, #1062) * Fixed `:pre` snapshot not being created for items that are not laid out (#1064, #1065) ## 4.5.2 (2017-01-11) Fixes: * Fixed handling of periods in full identifiers (#1022, #1059) * Fixed “cannot layout binary items” error message (#1058) * Fixed escaping of URLs in sitemaps (#1052, #1054) ## 4.5.1 (2017-01-09) Fixes: * Fixed crash when Nokogiri is not installed (#1053) ## 4.5 (2017-01-09) Features: * Added Git deployer (#997) ## 4.4.7 (2017-01-05) Fixes: * Fixed issue that caused an item not to be considered outdated when only the mtime has changed (#1046) * Removed stray `require 'parallel'` which could break the `external_links` check (#1051) [Cédric Boutillier] Enhancements: * Made Nanoc not recompile compiled items after an exception occurs (#1044) ## 4.4.6 (2016-12-28) Fixes: * Fixed issue where `#compiled_content` would not return the correct content (#1040, #1041) ## 4.4.5 (2016-12-24) Fixes: * Prevented stale data from making it into the checksum store and thereby blowing up in memory (#1004, #1027) * Fixed slow recompile after adding many items to a site (#1028) * Fixed wrong capturing helper output when the output field separator (`$,`) is set * Fixed issue that could cause items with multiple reps not to be recompiled when needed (#1031, #1032) * Fixed error when fetching textual content of item whose `:last` snapshot is binary (#1035, #1036) ## 4.4.4 (2016-12-19) Enhancements: * Improved speed of incremental compilations (#1017, #1019, #1024) ## 4.4.3 (2016-12-17) Fixes: * Prevented stale data from making it into the compiled content cache and thereby blowing up in memory (#1004, #1013) * Fixed “about” and “IRC channel” links in default site * Fixed accuracy of `` in Atom feed (use most recent `updated_at` or `created_at`) (#1007, #1014) Enhancements: * Added support for non-legacy identifiers in `#breadcrumbs_trail` (#1010, #1011) * Defined checksum for `Nanoc::Int::Context` to make outdatedness checker more precise (#1008, #1012) * Made Nanoc raise an error when item reps are routed to a path that does not start with a slash (#1015, #1016) ## 4.4.2 (2016-11-27) Fixes: * Fixed “Maximum call stack size exceeded” issue in the `less` filter (#1001) * Fixed issue that could cause the `less` filter to not generate all necessary dependencies (#1003) Enhancements: * Improved the way that the crash log displays the item rep that is being compiled (#1000) ## 4.4.1 (2016-11-21) Fixes: * Fixed an issue where the `xsl` filter would not generate a correct dependency on the layout (#996) Enhancements: * Made `view` command use index filenames specified in the `index_filenames` site configuration attribute (#998) ## 4.4 (2016-11-19) Features: * Added support for Nanoc environments (#859) ## 4.3.8 (2016-11-18) Enhancements: * Improved support for Rouge 1.x and 2.x (#880) [Rémi Barraquand] * Added `#include` to the `nanoc shell` command (#973) * Improved speed of full and incremental compilations (#977, #985) Fixes: * Made routing rules and `#write` calls accept an identifier, and not just a string (#976) * Removed GC speed-up hacks, which became counterproductive in Ruby 2.2 (#975) * Fixed issue which caused items to be always recompiled if `rep`/`item_rep` or `self` are used in those items’ rules (#982) ## 4.3.7 (2016-10-29) Fixes: * Fixed issue with `show-data` and `show-rules` commands not showing all data (#970) [Chris Chapman] Enhancements: * Improved speed of `compile` command (#968) * Improved speed of `prune` command (#969) * Made kramdown warnings include affected item rep (#967) [Gregory Pakosz] * Made kramdown warnings configurable (#967) [Gregory Pakosz] ## 4.3.6 (2016-10-23) Fixes: * Made legacy patterns properly support full identifiers (#957) * Fixed timezone issues in `#to_iso8601_date` (#961) * Fixed error when accessing item (rep) paths in shell command (#963) * Fixed issue that caused `#path` to be nil inside compilation rules (#964) * Made `__FILE__` in Checks file be a absolute path (#966) Enhancements: * Made the command line write status information to stderr, not stdout (#958) ## 4.3.5 (2016-10-14) Fixes: * Handle `form/@action` in `relativize_paths` filter (#950) [Lorin Werthen] Experimental features: * `profiler`: adds `--profile` option to the `compile` command to profile compilation (#903) * `environments`: adds support for Nanoc environments (#859) ## 4.3.4 (2016-10-02) Fixes: * Fixed compilation crash when output directory does not exist and auto-pruning is enabled (#948, #949) ## 4.3.3 (2016-09-26) Fixes: * Fixed issue causing `Bundler::GemfileNotFound` to be thrown (#936) [Lorin Werthen] * Fixed issue when replacing a directory with a file or vice versa (#942, #946) Enhancements: * Modified the `compile` command to allow specifying the deploy target as argument (#945) ## 4.3.2 (2016-08-23) Identical to 4.3.1, but with corrected release notes. ## 4.3.1 (2016-08-23) Fixes: * Fixed “outdatedness of LayoutView” error (#927, #931) * Fixed bug causing some checks not to appear in `nanoc check --list` (#928, #930) * Fixed `@item`, … not being accessible in filters defined with `Nanoc::Filter.define` (#932, #934) ## 4.3 (2016-08-21) Features: * Added `Nanoc::Filter.define`, to easily define filters (#895) * Made the `nanoc` Gemfile group be auto-required when Nanoc starts (#910) [whitequark] ## 4.2.4 (2016-07-24) Fixes: * Fixed `UnmetDependency` errors in postprocessor (#913, #917) Enhancements: * Sped up Nanoc by not releasing cache memory as quickly (#902) * Let `internal_links` check also verify resource paths, such as scripts and images (#912) [Lorin Werthen] * Improved error reporting for errors in the Rules file (#908, #914, #915, #916) * Removed `win32console` support, as it’s deprecated and causing problems (#900, #918) ## 4.2.3 (2016-07-03) Fixes: * Fixed issue with `#inspect` raising a `WeakRef::RefError` (#877, #897) Enhancements: * Sped up compiler (#894) * Improved `#inspect` output of some classes (#896) * Deprecated `Item#modified` and replaced it with `Item#modified_reps` (#898) ## 4.2.2 (2016-07-02) Fixes: * Fixed confusing “invalid prefix” error message (#873, #879) * Ensured filter arguments are frozen, to prevent outdatedness checker errors (#881, #886) * Fixed issue with dependencies of items generated in the preprocessor not being tracked (#885, #891, #893) Enhancements: * Added specific handling for `Sass::Importers::Filesystem` in the checksummer, which should reduce unnecessary recompiles in sites using Compass (#866, #884) * Improved speed of checksummer (#864, #887) ## 4.2.1 (2016-06-19) Fixes: * Fixed an occasional `WeakRef::RefError` (#863, #865) * Fixed `show-data` command not running preprocessor (#867, #870) ## 4.2 (2016-06-04) Enhancements: * Dropped Ruby 2.1 support (#856) This release also includes the changes from 4.2.0b1. ## 4.1.6 (2016-04-17) Fixes: * Strip index.html only if it is a full component (#850, #851) * Force UTF-8 for item rep paths (#837, #852) ## 4.2.0b1 (2016-04-17) Features: * Allow creating items and layouts with a pre-calculated checksum (#793) [Ruben Verborgh] * Allow lazy-loading item/layout content and attributes (#794) [Ruben Verborgh] * Added `exclude_origins` configuration option to internal links checker (#847) * Added `ChildParent` helper, providing `#children_of` and `#parent_of` (#849) Enhancements: * Made `#html_escape` raise an appropriate error when the given argument is not a String (#840) [Micha Rosenbaum] * Improved memory usage of memoized values by using weak refs (#846) ## 4.1.5 (2016-03-24) Fixes: * Fixed crash in `show-data` command (#833, #835) * Fixed preprocessor not being invoked before running checks (#841, #842) ## 4.1.4 (2016-02-13) Fixes: * Added missing `Configuration#key?` method (#815, #820) * Made output diff use correct snapshot rather than `:last` (#813, #814) Enhancements: * Sped up item resolution in Sass filter (#821) * Made `#link_to` more resilient to unsupported argument types (#816, #819) ## 4.1.3 (2016-01-30) Fixes: * Fixed crash in `check` command when the subject of an issue is nil (#804, #811) * Made stale check not ignore non-final snapshot paths (#809, #810) ## 4.1.2 (2016-01-16) Fixes: * Made @-variables (e.g. `@items`) report their frozenness properly, so that optimisations based on frozenness work once again (#795, #797) * Removed environment from `crash.log` to prevent leaking sensitive information (#798, #800) Enhancements: * Removed redundant checksum calculation (#789) [Ruben Verborgh] ## 4.1.1 (2015-12-30) Fixes: * Fixed preprocessor not being run before check/deploy/prune commands (#763, #784, #787, #788) Enhancements: * Made `#breadcrumbs_trail` explicitly fail when using full identifiers (#781, #783) ## 4.1 (2015-12-18) Fixes: * Fixed crash when attempting to `#puts` an object that’s not a string (#778) * Made pruner not prune away files from routes defined for custom snapshots (#779) * Wrapped `@layout` in a layout view (#773) Enhancements: * Added a base path to the Checks file, so that it supports `#require_relative` (#774) This release also includes the changes from 4.1.0a1 to 4.1.0rc2. ## 4.1.0rc2 (2015-12-13) Fixes: * Fixed children of the root item not having a parent (#769, #770) Enhancements: * Made `#path`, `#compiled_content` and `#reps` unavailable during pre-processing, compilation and routing, because they do not make sense in these contexts (#571, #767, #768) ## 4.1.0rc1 (2015-12-12) Fixes: * Fixed `@item.compiled_content` in a layout raising an exception (#761, #766) ## 4.1.0b1 (2015-12-11) Fixes: * Fixed issue with `:pre` snapshot not being generated properly (#764) Enhancements: * Updated default site to use `#write` (#759) ## 4.1.0a1 (2015-12-05) Features: * Added `postprocess` block (#726) [Garen Torikian] * Added `#write` compilation instruction and `path` option to `#snapshot` (#753) Fixes: * Fixed crash when printing non-string object (#712) [Garen Torikian] * Removed English text from `#link_to` helper (#736) [Lucas Vuotto] Enhancements: * Allowed excluding URLs from external links check (#686) [Yannick Ihmels] * Added `atom` to list of text extensions (#657) [Yannick Ihmels] * Added `#each` to `Nanoc::ConfigView` (#705) [Garen Torikian] * Made `#attribute_to_time` handle `DateTime` (#717) [Micha Rosenbaum] * Added `Identifier#components` (#677) * Added `:existing` option to `#content_for` (can be `:error`, `:overwrite` and `:append`) (#744) ## 4.0.2 (2015-11-30) Fixes: * Properly set required Ruby version to 2.1 in the gem specification (#747) * Fixed issue with CLI commands not being loaded as UTF-8 (#742) * Added missing `#identifier=` method to items and layouts during preprocessing (#750) Enhancements: * Let attempts to fetch an item rep by number, rather than symbol, fail with a meaningful error (#749) ## 4.0.1 (2015-11-28) Fixes: * Fixed params documentation for :rdiscount filter (#722) * Fixed crash when comparing item rep views (#735, #738) Enhancements: * Lowered minimum required Ruby version from 2.2 to 2.1 (#732) ## 4.0 (2015-11-07) Enhancements: * `#parent` and `#children` now raise an exception when used on items with a non-legacy identifier (#710) This release also includes the changes from 4.0.0a1 to 4.0.0rc3. ## 4.0.0rc3 (2015-09-20) Features: * Added `Identifier#without_exts` and `Identifier#exts` (#644, #696) [Rémi Barraquand] * Added `DocumentView#attributes` (#699, #702) Fixes: * Fixed issue when comparing document views (#680, #693) Enhancements: * Made `#base_url` argument in `#tags_for` optional (#687) [Croath Liu] * Allowed `IdentifiableCollection#[]` to be passed an identifier (#681, #695) * Improved `Pattern.from` error message (#683, #692) * Let default site use a direct link to the stylesheet (#685, #701) Changes: * Removed `Identifier#with_ext` because its behavior was confusing (#697, #700) * Disallowed storing document (views) in attributes (#682, #694) ## 4.0.0rc2 (2015-07-11) Fixes: * Fixed broken `shell` command (#672) [Jim Mendenhall] * Fixed absolute path check on Windows (#656) Enhancements: * Made Nanoc error when multiple items have the same output path (#665, #669) * Improved error message for non-hash frontmatter (#670, #673) Changes: * nanoc is now called Nanoc ## 4.0.0rc1 (2015-06-21) Fixes: * Fixed double-wrapping of `@layout` in rendering helper (#631) * Fixed `show-rules` command (#633) ## 4.0.0b4 (2015-06-10) Fixes: * Added missing `#ext` method to identifiers (#612) * Fixed issue where identifiers would have the wrong extension (#611) * Fixed rule context exposing entities rather than views (#614, #615) * Fixed `#key?` and `#fetch` not being available on layout views (#618) * Fixed `#update_attributes` not being available on mutable layout views (#619) ## 4.0.0b3 (2015-05-31) Changes: * Removed `filesystem_verbose` data source (#599) * Set minimum required Ruby version to 2.2 Enhancements: * Made `@config`, `@items` and `@layouts` available in checks (#598) * Made `filesystem` an alias for `filesystem_unified` (#599) * Made specific reps for an item accessible using `@item.reps[:name]` (#586, #607) * Removed `allow_periods_in_identifiers` documentation (#605) * Made fog deployer not upload files with identical ETags to AWS (#480, #536, #552) [Paul Boone] Fixes: * Made `ItemView#parent` return nil if parent is nil (#600, #602) * Added missing `identifier_type` documentation (#604) ## 4.0.0b2 (2015-05-23) Changes: * Removed `ItemCollectionView#at` (#582) * Removed support for calling `ItemCollectionView#[]` with an integer (#582) * Renamed `identifier_style` to `identifier_type`, and made its values be `"full"` or `"legacy"` (#593) * Renamed `pattern_syntax` to `string_pattern_type`, and made its values be `"glob"` or `"legacy"` (#593) * Made `"full"` the default for `identifier_type` (#592, #594) * Made `"glob"` the default for `string_pattern_type` (#592) * Enabled auto-pruning by default for new sites (#590) Enhancements: * Added `--force` to `create-site` command (#580) [David Alexander] * Made default Rules file more future-proof (#591) Fixes: * Fixed `LayoutCollectionView#[]` documentation (it mentioned items) * Fixed `ItemCollectionView#[]` returning an array when passed a regex * Fixed an issue with mutable collection views’ `#delete_if` not yielding mutable views * Fixed an issue with collection views’ `#find_all` returning entities instead of views ## 4.0.0b1 (2015-05-14) Changes: * Removed tasks * Removed several private methods in the view API * Removed default `base_url` in tagging helper Enhancements: * Removed unused options from CLI * Added `Nanoc::Identifier#without_ext` * Made `Nanoc::Identifier#=~` work with a glob * Added `Nanoc::LayoutCollectionView#[]` * Allowed creation of site in current directory (#549) [David Alexander] Fixes: * Fixed `#passthrough` for identifiers with extensions * Fixed rendering helper for identifiers with extensions * Fixed filtering helper ## 4.0.0a2 (2015-05-12) Features: * Glob patterns (opt-in by setting `pattern_syntax` to `"glob"` in the site configuration) * Identifiers with extensions (opt-in by setting `identifier_style` to `"full"` in the data source configuration) Enhancements: * Added several convenience methods to view classes (#570, #572) See the [nanoc 4 upgrade guide](http://nanoc.ws/docs/nanoc-4-upgrade-guide/) for details. ## 4.0.0a1 (2015-05-09) This is a major upgrade. For details on upgrading, see the [nanoc 4 upgrade guide](http://nanoc.ws/docs/nanoc-4-upgrade-guide/). This release provides no new features, but streamlines the API and functionality, in order to easen future development, both for features and for optimisations. ## 3.8 (2015-05-04) Features: * Added `mixed_content` check (#542, #543) [Mike Pennisi] * Added `commands_dirs` configuration option for specifying directories to read commands from (#475) [Gregory Pakosz] * Added `:cdn_id` option to fog deployer for invalidating CDN objects (#451) [Vlatko Kosturjak] * Add access to regular expressions group matches in rules (#478) [Michal Papis] * Allow filtering the items array by regex (#458) [Mike Pennisi] Enhancements: * Added `:preserve_order` option to preserve order in Atom feed (#533, #534) * Allowed accessing `:pre` snapshot from within item itself (#537, #538, #548) Fixes: * Allowed passing generic Pandoc options with :args (#526, #535) * Fix crash when compiling extensionless binary items (#524, #525) * Fix double snapshot creation error (#547) ## 3.7.5 (2015-01-12) Enhancements: * Allowed extra patterns to be specified in the data source configuration, so that dotfiles are no longer necessary ignored (e.g. `extra_files: ['.htaccess']`) (#492, #498) [Andy Drop, Michal Papis] * Removed Ruby 1.8.x support ([details](https://groups.google.com/forum/#!topic/nanoc/pSL1i15EFz8)) (#517) * Improved CSS and HTML error messages (#484, #504) * Let kramdown filter print warnings (#459, #519) Fixes: * Fixed HTML class names for recent Rouge versions (#502) * Fixed crash when using items or layouts in attributes (#469, #518) ## 3.7.4 (2014-11-23) Enhancements: * Made `check` command fail when output directory is missing (#472) [Mike Pennisi] * Made external links check timeouts start small and grow (#483) [Michal Papis] * Made code and API adhere much more closely to the Ruby style guide (#476) Fixes: * Fixed potential “parent directory is world writable” error (#465, #474) * Fixed retrying requests in the external link checker (#483) [Michal Papis] * Fixed issue with data sources not being unloaded (#491) [Michal Papis] ## 3.7.3 (2014-08-31) Fixes: * Fixed issue which caused metadata sections not be recognised in files that use CRLF line endings (#470, #471) [Gregory Pakosz] ## 3.7.2 (2014-08-17) Fixes: * Fixed broken links to the now defunct RubyForge (#454, #467) * Fixed crash when Gemfile is missing but Bundler is installed (#464) * Made filesystem data source not strip any whitespace (#463) [Gregory Pakosz] Enhancements: * Fixed issue which could cause items to be unnecessarily marked as outdated (#461) [Gregory Pakosz] * Prevented binary layouts from being generated (#468) [Gregory Pakosz] ## 3.7.1 (2014-06-16) Fixes: * Fixed bug which would cause nanoc to crash if no Gemfile is present (#447, #449) ## 3.7 (2014-06-08) New features: * Allowed excluding links from the internal links check (`@config[:checks][:internal_links][:exclude]`) (#242) [Remko Tronçon] * Added Rouge syntax coloring filter (#398) [Guilherme Garnier] * Backported `after_setup` from nanoc 4 to make it easier to create CLI plugins (#407) [Rémi Barraquand] * Make lib dirs configurable using `lib_dirs` config attribute (#424) [Gregory Pakosz] * Added support for setting parent config dir using `parent_config_file` config attribute (#419) [Gregory Pakosz] Enhancements: * Added `:with_toc` support to RedCarpet (#222, #232) * Added `slim` to the list of text extensions (#316) * Made `content/` and `layouts/` dirs configurable (#412) [Gregory Pakosz] * Allowed included rules files to have their own preprocess block (#420) [Gregory Pakosz] Fixes: * Fixed bug which caused temporary directories not to be removed (#440, #444) ## 3.6.11 (2014-05-09) Identical to 3.6.10 but published with corrected release notes. This release was previously known as 3.6.10.1, but was renamed due to incompatibilities with the Semantic Versioning specification. ## 3.6.10 (2014-05-09) Fixes: * Fixed occasional "no such file" error on JRuby (#422) * Prevented multiple items and layouts from having the same identifier (#434, #435) Enhancements: * Set default encoding to UTF-8 (#428) * Improved checksummer to reduce number of unnecessary recompiles (#310, #431) * Disabled USR1 on JRuby in order to suppress warning (#425, #426) * Made pandoc filter argument passing more generic (#210, #433) ## 3.6.9 (2014-04-15) Fixes: * Fixed path to default stylesheet (#410, #411) * Improved reliability of piping from/to external processes in JRuby (#417) * Added workaround for “cannot modify” errors when using Nokogiri on JRuby (#416) * Made corrupted cached data auto-repair itself if possible (#409, #418) ## 3.6.8 (2014-03-22) Fixes: * Fixed issue with missing compilation durations (#374, #379) * Made XSL filter transform item rather than layout (#399, #401) [Simon South] * Made XSL filter honor omit-xml-declaration (#403, #404) [Simon South] * Removed "see full crash log" line from crash log (#397, #402) Enhancements: * Added warning when multiple preprocessors are defined (#389) * Improve stylesheet handling in default site (#339, #395) ## 3.6.7 (2013-12-09) Fixes: * Made Handlebars filter usable outside layouts (#346, #348) * Fixed ANSI color support on Windows (#352, #356) * Made fog deployer handle prefixes properly (#351) [Oliver Byford] * Fixed crash in watcher (#358) * Fixed huge durations when showing skipped items after compilation (#360, #364) * Fixed output of `--verbose` compilation statistics (#359, #365) * Fixed issue with Sass files not recompiling (#350, #370) Enhancements: * Fixed Windows compatibility issues in test suite (#353) [Raphael von der Grün] * Hid deprecated `autocompile` and `watch` commands in help * Made CLI swallow broken pipe errors when piping to a process that terminates prematurely (#318, #369) ## 3.6.6 (2013-11-08) Enhancements: * Reduced number of dependencies generated by Sass filter (#306) [Gregory Pakosz] * Recognised lowercase `utf` in language value (e.g. `en_US.utf8`) as being UTF-8 (#335, #338) * Set [Thin](http://code.macournoyer.com/thin/) as the default server for `nanoc view` (#342, #345) * Removed watcher section from the default configuration file (#343, #344) Fixes: * Prevented capturing helper from erroneously compiling items twice (#331, #337) ## 3.6.5 (2013-09-29) Fixes: * Fixed bug which could cause incorrect dependencies to be generated in some cases (#329) * Fixed handling of index filenames when allowing periods in identifiers (#330) ## 3.6.4 (2013-05-29) Enhancements: * Deprecated `watch` and `autocompile` commands in favour of [`guard-nanoc`](https://github.com/nanoc/guard-nanoc) Fixes: * Fixed bug which could cause the `tmp/` dir to blow up in size * Unescaped URLs when checking internal links ## 3.6.3 (2013-04-24) Fixes: * Added support for growlnotify on Windows (#253, #267) * Fixed bug which caused the external links checker to ignore the query string (#279, #297) * Removed weird treatment of `DOCTYPE`s in the `relativize_paths` filter (#296) * Fixed CodeRay syntax coloring on Ruby 2.0 * Silenced "Could not find files for the given pattern(s)" message on Windows (#298) * Fixed issue which could cause `output.diff` not to be generated correctly (#255, #301) * Let filesystem and static data sources follow symlinks (#299, #302) * Added compatibility with Listen 1.0 (#309) * Let `#passthrough` in Rules work well with the static data source (#251) [Gregory Pakosz] * Made timing information be more accurate (#303) ## 3.6.2 (2013-03-23) Fixes: * Removed the list of available deployers from the `deploy` help text and moved them into a new `--list-deployers` option [Damien Pollet] * Fixed warning about `__send__ `and `object_id` being redefined on Ruby 1.8.x [Justin Hileman] Enhancements: * Added possible alternative names for the `Checks` file for consistency with the `Rules` file: `Checks.rb`, `checks`, `checks.rb` [Damien Pollet] * Made sure unchanged files never have their mtime updated [Justin Hileman] * Made link checker retry 405 Method Not Allowed results with GET instead of HEAD [Daniel Hofstetter] ## 3.6.1 (2013-02-25) Fixes: * Fixed bug which could cause the Sass filter to raise a load error [Damien Pollet] * Fixed warnings about `__send__` and `object_id` being redefined [Justin Hileman] * Made `files_to_watch` contain `nanoc.yaml`, not `config.yaml` by default ## 3.6 (2013-02-24) Features: * Added `sync` command, allowing data sources to update local caches of external data [Justin Hileman] * Added `#ignore` compiler DSL method * Allowed accessing items by identifier using e.g. `@items['/about/']` * Added `shell` command Enhancements: * Renamed the nanoc configuration file from `config.yaml` to `nanoc.yaml` Fixes: * Updated references to old web site and old repository * Made `require` errors mention Bundler if appropriate * Fixed bug which caused pruner not to delete directories in some cases [Matthias Reitinger] * Made `check` command exit with the proper exit status * Added support for the `HTML_TOC` Redcarpet renderer * Made `stale` check honor files excluded by the pruner ## 3.5 (2013-01-27) Major changes: * Added checks Minor changes: * Added `#include_rules` for modularising Rules files [Justin Hileman] * Replaced FSSM with Listen [Takashi Uchibe] * Made USR1 print stack trace (not on Windows) * Added ability to configure autocompiler host/port in config.yaml [Stuart Montgomery] * Added static data source * Added `:rep_select` parameter to XML sitemap to allow filtering reps * Removed use of bright/bold colors for compatibility with Solarized Exensions: * Added support for parameters in Less filter [Ruben Verborgh] * Added support for icon and logo in Atom feed [Ruben Verborgh] Fixes: * Made syntax colorizer only use the first non-empty line when extracting the language comment * Fixed XSL filter ## 3.4.3 (2012-12-09) Improvements: * Item reps are now accessible in a consistent way: in Rules and during compilation, they can be accessed using both `@rep` and `@item_rep` Fixes: * Made cleaning streams (stdout/stderr as used by nanoc) compatible with Ruby’s built-in Logger * Made prune work when the output directory is a symlink * Made Handlebars filter compatible with the latest version * Made `show-data` command show more accurate dependencies [Stefan Bühler] * Restored compatibility with Sass 3.2.2 ## 3.4.2 (2012-11-01) Fixes: * Made passthrough rules be inserted in the right place [Gregory Pakosz] * Fixed crashes in the progress indicator when compiling * Made auto-pruning honor excluded files [Grégory Karékinian] * Made lack of which/where not crash watch command Improvements: * Fixed constant reinitialization warnings [Damien Pollet] * Made UTF-8 not be decomposed when outputting to a file from a non-UTF-8 terminal * Made syntax colorizer wrap CodeRay output in required CodeRay divs * Made fog delete after upload, not before [Go Maeda] * Made requesting compiled content of binary item impossible ## 3.4.1 (2012-09-22) Fixes: * Fixed auto-pruning * Made slim filter work with the capturing helper [Bil Bas] Improvements: * Made several speed improvements * Added prune configuration to config.yaml * Made nanoc check for presence of nanoc in Gemfile * Made compile command not show identicals (use `--verbose` if you want them) * Made `relativize_paths` filter recognise more paths to relativize [Arnau Siches] * Fixed #passthrough for items without extensions [Justin Hileman] * Added more IO/File proxy methods to cleaning streams ## 3.4 (2012-06-09) * Improved error output and added crash log * Renamed `debug` and `info` commands to `show-data` and `show-plugins`, respectively * Added `show-rules` command (aka `explain`) Extensions: * Added `:yield` key for Mustache filter * Added Handebars filter * Added Pandoc filter * Made the deployer use the `default` target if no target is specified * Converted HTML/CSS/link validation tasks to commands * Made link validator follow relative redirects ## 3.3.7 (2012-05-28) * Added filename to YAML parser errors * Fixed issue which caused extra dependencies to be generated * Made timing information take filtering helper into account ## 3.3.6 (2012-04-27) * Fixed issue with relative_link_to stripping HTML boilerplate ## 3.3.5 (2012-04-23) * Fixed issue with relative_link_to not working properly ## 3.3.4 (2012-04-23) * Fixed bug which caused the compilation stack to be empty * Restored Ruby 1.8 compatibility ## 3.3.3 (2012-04-11) * Fixed directed graph implementation on Rubinius * Made capturing helper not remember content between runs * Fixed Date#freeze issue on Ruby 1.8.x * Made it possible to have any kind of object as parameters in the Rules file * Fixed bug which caused changed routes not to cause a recompile ## 3.3.2 (2012-03-16) * Removed bin/nanoc3 (use nanoc3 gem if you want it) * Fixed wrong “no such snapshot” errors * Made deployer default to rsync for backwards compatibility * Fixed missing Nanoc::CLI in deployment tasks * Fixed “unrecognised kind” deployer error ## 3.3.1 (2012-02-18) * Fixed issue with long paths on Windows * Fixed a few deployer crashes * Added nanoc3.rb, nanoc3/tasks.rb, … for compatibility with older versions * Made nanoc setup Bundler at startup [John Nishinaga] ## 3.3 (2012-02-12) Base: * Dropped the “3” suffix on nanoc3/Nanoc3 * Turned Rake tasks into proper nanoc commands * Improved dependency tracking * Added support for locals in filters and layouts Extensions: * Added support for deployment using Fog [Jack Chu] * Added CoffeeScript filter [Riley Goodside] * Added XSL filter [Arnau Siches] * Added YUICompress filter [Matt Keveney] * Added pygments.rb to supported syntax colorizers * Allowed syntax colorizer to colorize outside `pre` elements [Kevin Lynagh] * Added support for HTTPS link validation [Fabian Buch] * Added support for (automatically) pruning stray output files [Justin Hileman] * Added deploy command ## 3.2.4 (2012-01-09) * Fixed bug which would cause some reps not to be compiled when invoking nanoc programmatically * Made data source configuration location a bit more obvious * Fixed watch command under Windows * Made filesystem data source ignore UTF-8 BOM * Improved compatibility of `colorize_syntax` filter with older libxml versions ## 3.2.3 (2011-10-31) * Made syntax colorizer only strip trailing blank lines instead of all blanks * Improved Ruby 1.9.x compatibility * Made default rakefile require rubygems if necessary * Made filename/content argument of `Nanoc3::Item#initialize` mandatory ## 3.2.2 (2011-09-04) * Fixed command usage printing * Made `relativize_paths` filter handle Windows network paths [Ruben Verborgh] * Made watcher use correct configuration * Allowed code blocks to start with a non-language shebang line ## 3.2.1 (2011-07-27) * Made `@config` available in rules file * Fixed `#readpartial` issue on JRuby [Matt Keveney] * Fixed possible `@cache` name clash in memoization module * Fixed options with required arguments (such as `--port` and `--host`) * Fixed broken `#check_availability` * Fixed error handling in watch command ## 3.2 (2011-07-24) Base: * Sped up nanoc quite a bit * Added progress indicator for long-running filters * Made all source data, such as item attributes, frozen during compilation * Added --color option to force color on * Cleaned up internals, deprecating several parts and/or marking them as private in the progress * Allowed custom commands in commands/ Extensions: * Added AsciiDoc filter * Added Redcarpet filter [Peter Aronoff] * Added Slim filter [Zaiste de Grengolada] * Added Typogruby filter * Added UglifyJS filter [Justin Hileman] * Added `:items` parameter for the XML site map [Justin Hileman] * Added support for params to ERB * Added `:default_colorizer` parameter to the `:colorize_syntax` filter * Allowed for passing arbitrary options to pygmentize [Matthias Vallentin] * Exposed RedCloth parameters in the filter [Vincent Driessen] ## 3.1.9 (2011-06-30) * Really fixed dependency generation between Sass partials this time * Updated Less filter to 2.0 * Made `colorize_syntax` filter throw an error if pygmentize is not available ## 3.1.8 (2011-06-25) * Made link validator accept https: URLs * Fixed erroneous handling of layouts with names ending in index * Fixed dependency generation between Sass partials * Fixed errors related to thread requires * Fixed crash while handling load errors * Improved encoding handling while reading files ## 3.1.7 (2011-05-03) * Restored compatibility with Sass 3.1 ## 3.1.6 (2010-11-21) * Fixed issues with incompatible encodings ## 3.1.5 (2010-08-24) * Improved `#render` documentation * Improved metadata section check so that e.g. raw diffs are handled properly * Deprecated using `Nanoc3::Site#initialize` with a non-`"."` argument * Added Ruby engine to version string * Allowed the `created_at` and `updated_at` attributes used in the `Blogging` helper to be `Date` instances ## 3.1.4 (2010-07-25) * Made INT and TERM signals always quit the CLI * Allowed relative imports in LESS * Made sure modification times are unchanged for identical recompiled items * Improved link validator error handling * Made pygmentize not output extra divs and pres * Allowed colorizers to be specified using symbols instead of strings * Added scss to the default list of text extensions ## 3.1.3 (2010-04-25) * Removed annoying win32console warning [Eric Sunshine] * Removed color codes when not writing to a terminal, or when writing to Windows’ console when win32console is not installed [Eric Sunshine] * Added .xhtml and .xml to list of text extensions * Improved support for relative Sass @imports [Chris Eppstein] ## 3.1.2 (2010-04-07) * Fixed bug which could cause incorrect output when compilation of an item is delayed due to an unmet dependency ## 3.1.1 (2010-04-05) * Sass `@import`s now work for files not managed by nanoc * Rake tasks now have their Unicode description decomposed if necessary ## 3.1 (2010-04-03) New: * An `Item#rep_named(name)` function for quickly getting a certain rep * An `Item#compiled_content` function for quickly getting compiled content * An `Item#path` function for quickly getting the path of an item rep * A new “+” wildcard in rule patterns that matches one or more characters * A `view` command that starts a web server in the output directory * A `debug` command that shows information about the items, reps and layouts * A `kramdown` filter ([kramdown site](http://kramdown.gettalong.org/)) * A diff between the previously compiled content and the last compiled content is now written to `output.diff` if the `enable_output_diff` site configuration attribute is true * Assigns, such as `@items`, `@layouts`, `@item`, … are accessible without `@` * Support for binary items Changed: * New sites now come with a stylesheet item instead of a `style.css` file in the output directory * The `deploy:rsync` task now use sensible default options * The `deploy:rsync` task now accepts a config environment variable * The `deploy:rsync` task now uses a lowercase `dry_run` environment variable * The `maruku` filter now accepts parameters * The `rainpress` filter now accepts parameters * The `filesystem` data source is now known as `filesystem_verbose` * Meta files and content files are now optional * The `filesystem_compact` and `filesystem_combined` data sources have been merged into a new `filesystem_unified` data source * The metadata section in `filesystem_unified` is now optional [Chris Eppstein] * The `--server` autocompile option is now known as `--handler` * Assigns in filters are now available as instance variables and methods * The `#breadcrumbs_trail` function now allows missing parents * The `sass` filter now properly handles `@import` dependencies Deprecated: * `Nanoc3::FileProxy`; use one of the filename attributes instead * `ItemRep#content_at_snapshot`; use `#compiled_content` instead * The `last_fm`, `delicious` and `twitter` data sources; fetch online content into a cache by a rake task and load data from this cache instead ## 3.0.9 (2010-02-24) * Fixed 1.8.x parsing bug due to lack of parens which could cause “undefined method `to_iso8601_time` for #” errors ## 3.0.8 (2010-02-24) * `#atom_tag_for` now works with `base_url`s that contain a path [Eric Sunshine] * Generated root URLs in `#atom_feed` now end with a slash [Eric Sunshine] * Autocompiler now recognises requests to index files * `Blogging` helper now allows `created_at` to be a Time instance ## 3.0.7 (2010-01-29) * Fixed bug which could cause layout rules not be matched in order ## 3.0.6 (2010-01-17) * Error checking in `filesystem_combined` has been improved [Brian Candler] * Generated HTML files now have a default encoding of UTF-8 * Periods in identifiers for layouts now behave correctly * The `relativize_paths` filter now correctly handles “/” [Eric Sunshine] ## 3.0.5 (2010-01-12) * Restored pre-3.0.3 behaviour of periods in identifiers. By default, a file can have multiple extensions (e.g. `content/foo.html.erb` will have the identifier `/foo/`), but if `allow_periods_in_identifiers` in the site configuration is true, a file can have only one extension (e.g. `content/blog/stuff.entry.html` will have the identifier `/blog/stuff.entry/`). ## 3.0.4 (2010-01-07) * Fixed a bug which would cause the `filesystem_compact` data source to incorrectly determine the content filename, leading to weird “Expected 1 content file but found 3” errors [Eric Sunshine] ## 3.0.3 (2010-01-06) * The `Blogging` helper now properly handles item reps without paths * The `relativize_paths` filter now only operates inside tags * The autocompiler now handles escaped paths * The `LinkTo` and `Tagging` helpers now output escaped HTML * Fixed `played_at` attribute assignment in the `LastFM` data source for tracks playing now, and added a `now_playing` attribute [Nicky Peeters] * The `filesystem_*` data sources can now handle dots in identifiers * Required enumerator to make sure `#enum_with_index` always works * `Array#stringify_keys` now properly recurses ## 3.0.2 (2009-11-07) * Children-only identifier patterns no longer erroneously also match parent (e.g.` /foo/*/` no longer matches `/foo/`) * The `create_site` command no longer uses those ugly HTML entities * Install message now mentions the IRC channel ## 3.0.1 (2009-10-05) * The proper exception is now raised when no matching compilation rules can be found * The autocompile command no longer has a duplicate `--port` option * The `#url_for` and `#feed_url` methods now check the presence of the `base_url` site configuration attribute * Several outdated URLs are now up-to-date * Error handling has been improved in general ## 3.0 (2009-08-14) New: * Multiple data sources * Dependency tracking between items * Filters can now optionally take arguments * `#create_page` and `#create_layout` methods in data sources * A new way to specify compilation/routing rules using a Rules file * A `coderay` filter ([CodeRay site](http://coderay.rubychan.de/)) * A `filesystem_compact` data source which uses less directories Changed: * Pages and textual assets are now known as “items” Removed: * Support for drafts * Support for binary assets * Support for templates * Everything that was deprecated in nanoc 2.x * `save_*`, `move_*` and `delete_*` methods in data sources * Processing instructions in metadata ## 2.2.2 (2009-05-18) * Removed `relativize_paths` filter; use `relativize_paths_in_html` or `relativize_paths_in_css` instead * Fixed bug which could cause nanoc to eat massive amounts of memory when an exception occurs * Fixed bug which would cause nanoc to complain about the open file limit being reached when using a large amount of assets ## 2.2.1 (2009-04-08) * Fixed bug which prevented `relative_path_to` from working * Split `relativize_paths` filter into two filter: `relativize_paths_in_html` and `relativize_paths_in_css` * Removed bundled mime-types library ## 2.2 (2009-04-06) New: * `--pages` and `--assets` compiler options * `--no-color` command-line option * `Filtering` helper * `#relative_path_to` function in `LinkTo` helper * `rainpress` filter ([Rainpress site](http://code.google.com/p/rainpress/)) * `relativize_paths` filter * The current layout is now accessible through the `@layout` variable * Much more informative stack traces when something goes wrong Changed: * The command-line option parser is now a lot more reliable * `#atom_feed` now takes optional `:content_proc`, `:excerpt_proc` and `:articles` parameters * The compile command show non-written items (those with `skip_output: true`) * The compile command compiles everything by default * Added `--only-outdated` option to compile only outdated pages Removed: * deprecated extension-based code ## 2.1.6 (2009-02-28) * The `filesystem_combined` data source now supports empty metadata sections * The `rdoc` filter now works for both RDoc 1.x and 2.x * The autocompiler now serves a 500 when an exception occurs outside compilation * The autocompiler no longer serves index files when the request path does not end with a slash * The autocompiler now always serves asset content correctly ## 2.1.5 (2009-02-01) * Added Ruby 1.9 compatibility * The `filesystem` and `filesystem_combined` data sources now preserve custom extensions ## 2.1.4 (2008-11-15) * Fixed an issue where the autocompiler in Windows would serve broken assets ## 2.1.3 (2008-09-27) * The `haml` and `sass` filters now correctly take their options from assets * The autocompiler now serves index files instead of 404s * Layouts named “index” are now handled correctly * The `filesystem_combined` data source now properly handles assets ## 2.1.2 (2008-09-08) * The utocompiler now compiles assets as well * The `sass` filter now takes options (just like the `haml` filter) * Haml/Sass options are now taken from the page *rep* instead of the page ## 2.1.1 (2008-08-18) * Fixed issue which would cause files not to be required in the right order ## 2.1 (2008-08-17) This is only a short summary of all changes in 2.1. For details, see the [nanoc web site](http://nanoc.stoneship.org/). Especially the blog and the updated manual will be useful. New: * New `rdiscount` filter ([RDiscount site](http://github.com/rtomayko/rdiscount)) * New `maruku` filter ([Maruku site](https://github.com/bhollis/maruku/)) * New `erubis` filter ([Erubis site](http://www.kuwata-lab.com/erubis/)) * A better command-line frontend * A new filesystem data source named `filesystem_combined` * Routers, which decide where compiled pages should be written to * Page/layout mtimes can now be retrieved through `page.mtime`/`layout.mtime` Changed: * Already compiled pages will no longer be re-compiled unless outdated * Layout processors and filters have been merged * Layouts no longer rely on file extensions to determine the layout processor * Greatly improved source code documentation * Greatly improved unit test suite Removed: * Several filters have been removed and replaced by newer filters: * `eruby`: use `erb` or `erubis` instead * `markdown`: use `bluecloth`, `rdiscount` or `maruku` instead * `textile`: use `redcloth` instead ## 2.0.4 (2008-05-04) * Fixed `default.rb`’s `#html_escape` * Updated Haml filter and layout processor so that @page, @pages and @config are now available as instance variables instead of local variables ## 2.0.3 (2008-03-25) * The autocompiler now honors custom paths * The autocompiler now attempts to serve pages with the most appropriate MIME type, instead of always serving everything as `text/html` ## 2.0.2 (2008-01-26) * nanoc now requires Ruby 1.8.5 instead of 1.8.6 ## 2.0.1 (2008-01-21) * Fixed a “too many open files” error that could appear during (auto)compiling ## 2.0 (2007-12-25) New: * Support for custom layout processors * Support for custom data sources * Database data source * An auto-compiler * Pages have `parent` and `children` Changed: * The source has been restructured and cleaned up a great deal * Filters are defined in a different way now * The `eruby` filter now uses ERB instead of Erubis Removed: * The `filters` property; use `filters_pre` instead * Support for Liquid ## 1.6.2 (2007-10-23) * Fixed an issue which prevented the content capturing plugin from working ## 1.6.1 (2007-10-14) * Removed a stray debug message ## 1.6 (2007-10-13) * Added support for post-layout filters * Added support for getting a File object for the page, so you can now e.g. easily get the modification time for a given page (`@page.file.mtime`) * Cleaned up the source code a lot * Removed deprecated asset-copying functionality ## 1.5 (2007-09-10) * Added support for custom filters * Improved Liquid support -- Liquid is now a first-class nanoc citizen * Deprecated assets -- use something like rsync instead * Added `eruby_engine` option, which can be `erb` or `erubis` ## 1.4 (2007-07-06) * nanoc now supports ERB (as well as Erubis); Erubis no longer is a dependency * `meta.yaml` can now have `haml_options` property, which is passed to Haml * Pages can now have a `filename` property, which defaults to `index` [Dennis Sutch] * Pages now know in what order they should be compiled, eliminating the need for custom page ordering [Dennis Sutch] ## 1.3.1 (2007-06-30) * The contents of the `assets` directory are now copied into the output directory specified in `config.yaml` ## 1.3 (2007-06-24) * The `@pages` array now also contains uncompiled pages * Pages with `skip_output` set to true will not be outputted * Added new filters * Textile/RedCloth * Sass * nanoc now warns before overwriting in `create_site`, `create_page` and `create_template` (but not in compile) ## 1.2 (2007-06-05) * Sites now have an `assets` directory, whose contents are copied to the `output` directory when compiling [Stanley Rost] * Added support for non-eRuby layouts (Markaby, Haml, Liquid, …) * Added more filters (Markaby, Haml, Liquid, RDoc [Dmitry Bilunov]) * Improved error reporting * Accessing page attributes using instance variables, and not through `@page`, is no longer possible * Page attributes can now be accessed using dot notation, i.e. `@page.title` as well as `@page[:title]` ## 1.1.3 (2007-05-18) * Fixed bug which would cause layoutless pages to be outputted incorrectly ## 1.1.2 (2007-05-17) * Backup files (files ending with a “~”) are now ignored * Fixed bug which would cause subpages not to be generated correctly ## 1.1 (2007-05-08) * Added support for nested layouts * Added coloured logging * `@page` now hold the page that is currently being processed * Index files are now called “content” files and are now named after the directory they are in [Colin Barrett] * It is now possible to access `@page` in the page’s content file ## 1.0.1 (2007-05-05) * Fixed a bug which would cause a “no such template” error to be displayed when the template existed but compiling it would raise an exception * Fixed bug which would cause pages not to be sorted by order before compiling ## 1.0 (2007-05-03) * Initial release nanoc-4.8.0/nanoc.gemspec0000644000004100000410000000235413134141051015311 0ustar www-datawww-data# frozen_string_literal: true require_relative 'lib/nanoc/version' Gem::Specification.new do |s| s.name = 'nanoc' s.version = Nanoc::VERSION s.homepage = 'http://nanoc.ws/' s.summary = 'A static-site generator with a focus on flexibility.' s.description = 'Nanoc is a static-site generator focused on flexibility. It transforms content from a format such as Markdown or AsciiDoc into another format, usually HTML, and lays out pages consistently to retain the site’s look and feel throughout. Static sites built with Nanoc can be deployed to any web server.' s.author = 'Denis Defreyne' s.email = 'denis@stoneship.org' s.license = 'MIT' s.files = `git ls-files -z`.split("\x0") s.executables = ['nanoc'] s.require_paths = ['lib'] s.rdoc_options = ['--main', 'README.md'] s.extra_rdoc_files = ['LICENSE', 'README.md', 'NEWS.md'] s.required_ruby_version = '>= 2.3.0' s.add_runtime_dependency('cri', '~> 2.8') s.add_runtime_dependency('hamster', '~> 3.0') s.add_runtime_dependency('ref', '~> 2.0') s.add_runtime_dependency('ddplugin', '~> 1.0') s.add_development_dependency('bundler', '>= 1.7.10', '< 2.0') s.add_development_dependency('appraisal', '~> 2.1') end nanoc-4.8.0/bin/0000755000004100000410000000000013134141051013412 5ustar www-datawww-datananoc-4.8.0/bin/nanoc0000755000004100000410000000057713134141051014447 0ustar www-datawww-data#!/usr/bin/env ruby # frozen_string_literal: true require 'nanoc' begin require 'nanoc-rust' NanocRust.activate! rescue LoadError end require 'nanoc/cli' if File.file?('Gemfile') && !defined?(Bundler) warn 'A Gemfile was detected, but Bundler is not loaded. This is probably not what you want. To run Nanoc with Bundler, use `bundle exec nanoc`.' end Nanoc::CLI.run(ARGV) nanoc-4.8.0/Gemfile0000644000004100000410000000300613134141051014134 0ustar www-datawww-data# frozen_string_literal: true source 'https://rubygems.org' gemspec group :devel do gem 'codecov', require: false gem 'contracts', '~> 0.14' gem 'coveralls', require: false gem 'fuubar' gem 'guard-rake' gem 'json', '~> 2.0' gem 'm', '~> 1.5' gem 'minitest', '~> 5.0' gem 'mocha' gem 'pry' gem 'rainbow', '~> 2.1' gem 'rake' gem 'rdoc', '~> 5.0' gem 'rspec' gem 'rspec-its', '~> 1.2' gem 'rspec-mocks' gem 'rubocop', '~> 0.49' gem 'simplecov', require: false gem 'timecop' gem 'vcr' gem 'webmock' gem 'yard' gem 'yard-contracts' end group :plugins do gem 'adsf' gem 'asciidoctor' gem 'bluecloth', platforms: :ruby gem 'builder' gem 'coderay' gem 'coffee-script' gem 'compass' gem 'erubi' gem 'erubis' gem 'fog' gem 'haml' gem 'handlebars', platforms: :ruby gem 'kramdown' gem 'less', '~> 2.0', platforms: :ruby gem 'libv8', platforms: :ruby gem 'listen' gem 'markaby' gem 'maruku' gem 'mime-types' gem 'mustache', '~> 1.0' gem 'nokogiri', '~> 1.6' gem 'nokogumbo', '~> 1.4', platforms: :ruby gem 'pandoc-ruby' gem 'pygments.rb', '~> 1.1', '>= 1.1.1', platforms: %i[ruby mswin] gem 'rack' gem 'rainpress' gem 'rdiscount', '~> 2.2', platforms: %i[ruby mswin] gem 'redcarpet', platforms: %i[ruby mswin] gem 'RedCloth', platforms: :ruby gem 'rouge' gem 'rubypants' gem 'sass' gem 'slim', '~> 3.0' gem 'therubyracer', '~> 0.12', platforms: :ruby gem 'typogruby' gem 'uglifier' gem 'w3c_validators' gem 'yuicompressor' end nanoc-4.8.0/.rspec0000644000004100000410000000006113134141051013754 0ustar www-datawww-data-r ./spec/spec_helper.rb --format Fuubar --color nanoc-4.8.0/CODE_OF_CONDUCT.md0000644000004100000410000000623213134141051015444 0ustar www-datawww-data# Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at denis+nanoc-coc@stoneship.org. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] [homepage]: http://contributor-covenant.org [version]: http://contributor-covenant.org/version/1/4/ nanoc-4.8.0/spec/0000755000004100000410000000000013134141051013574 5ustar www-datawww-datananoc-4.8.0/spec/spec_helper.rb0000644000004100000410000001736213134141051016423 0ustar www-datawww-data# frozen_string_literal: true require 'simplecov' SimpleCov.start require 'codecov' SimpleCov.formatter = SimpleCov::Formatter::Codecov require 'nanoc' require 'nanoc/cli' require 'nanoc/spec' require 'timecop' require 'rspec/its' require 'fuubar' Nanoc::CLI.setup RSpec.configure do |c| c.fuubar_progress_bar_options = { format: '%c/%C |<%b>%i| %p%%', } c.around(:each) do |example| Nanoc::CLI::ErrorHandler.disable example.run Nanoc::CLI::ErrorHandler.enable end c.around(:each) do |example| Dir.mktmpdir('nanoc-test') do |dir| FileUtils.cd(dir) do example.run end end end c.around(:each, chdir: false) do |example| FileUtils.cd(File.dirname(__FILE__) + '/..') do example.run end end c.before(:each) do Nanoc::Int::NotificationCenter.reset end c.around(:each, stdio: true) do |example| orig_stdout = $stdout orig_stderr = $stderr unless ENV['QUIET'] == 'false' $stdout = StringIO.new $stderr = StringIO.new end example.run $stdout = orig_stdout $stderr = orig_stderr end c.before(:each, site: true) do FileUtils.mkdir_p('content') FileUtils.mkdir_p('layouts') FileUtils.mkdir_p('lib') FileUtils.mkdir_p('output') File.write('nanoc.yaml', '{}') File.write('Rules', 'passthrough "/**/*"') end c.include(Nanoc::Spec::HelperHelper, helper: true) # Set focus if any if ENV.fetch('FOCUS', false) $stdout.puts "Focusing spec on '#{ENV['FOCUS']}'" c.filter_run_including ENV['FOCUS'].to_sym => true end end RSpec::Matchers.define_negated_matcher :not_match, :match RSpec::Matchers.define :raise_frozen_error do |_expected| match do |actual| begin actual.call false rescue => e if e.is_a?(RuntimeError) || e.is_a?(TypeError) e.message =~ /(^can't modify frozen |^unable to modify frozen object$)/ else false end end end supports_block_expectations failure_message do |_actual| 'expected that proc would raise a frozen error' end failure_message_when_negated do |_actual| 'expected that proc would not raise a frozen error' end end RSpec::Matchers.define :be_humanly_sorted do match do |actual| actual == sort(actual) end description do 'be humanly sorted' end failure_message do |actual| expected_order = [] actual.zip(sort(actual)).each do |a, b| if a != b expected_order << b end end "expected collection to be sorted (incorrect order: #{expected_order.join(' < ')})" end def sort(x) x.sort_by { |n| n.dup.unicode_normalize(:nfd).encode('ASCII', fallback: ->(_) { '' }).downcase } end end RSpec::Matchers.define :finish_in_under do |expected| supports_block_expectations match do |actual| before = Time.now actual.call after = Time.now @actual_duration = after - before @actual_duration < expected end chain :seconds do end failure_message do |_actual| "expected that proc would finish in under #{expected}s, but took #{format '%0.1fs', @actual_duration}" end failure_message_when_negated do |_actual| "expected that proc would not finish in under #{expected}s, but took #{format '%0.1fs', @actual_duration}" end end RSpec::Matchers.define :yield_from_fiber do |expected| supports_block_expectations include RSpec::Matchers::Composable match do |actual| @res = Fiber.new { actual.call }.resume values_match?(expected, @res) end description do "yield #{expected.inspect} from fiber" end failure_message do |_actual| "expected that proc would yield #{expected.inspect} from fiber, but was #{@res.inspect}" end failure_message_when_negated do |_actual| "expected that proc would not yield #{expected.inspect} from fiber, but was #{@res.inspect}" end end RSpec::Matchers.define :raise_wrapped_error do |expected| supports_block_expectations include RSpec::Matchers::Composable match do |actual| begin actual.call rescue Nanoc::Int::Errors::CompilationError => e values_match?(expected, e.unwrap) end end description do "raise wrapped error #{expected.inspect}" end failure_message do |_actual| "expected that proc would raise wrapped error #{expected.inspect}" end failure_message_when_negated do |_actual| "expected that proc would not raise wrapped error #{expected.inspect}" end end RSpec::Matchers.define :be_some_textual_content do |expected| include RSpec::Matchers::Composable match do |actual| actual.is_a?(Nanoc::Int::TextualContent) && values_match?(expected, actual.string) end description do "textual content matching #{expected.inspect}" end failure_message do |actual| "expected #{actual.inspect} to be textual content matching #{expected.inspect}" end failure_message_when_negated do |actual| "expected #{actual.inspect} not to be textual content matching #{expected.inspect}" end end RSpec::Matchers.define :be_some_binary_content do |expected| include RSpec::Matchers::Composable match do |actual| actual.is_a?(Nanoc::Int::BinaryContent) && values_match?(expected, File.read(actual.filename)) end description do "binary content matching #{expected.inspect}" end failure_message do |actual| "expected #{actual.inspect} to be binary content matching #{expected.inspect}" end failure_message_when_negated do |actual| "expected #{actual.inspect} not to be binary content matching #{expected.inspect}" end end RSpec::Matchers.alias_matcher :some_textual_content, :be_some_textual_content RSpec::Matchers.alias_matcher :some_binary_content, :be_some_binary_content RSpec::Matchers.define :send_notification do |name, *expected_args| supports_block_expectations include RSpec::Matchers::Composable match do |actual| @actual_notifications = [] Nanoc::Int::NotificationCenter.on(name, self) do |*actual_args| @actual_notifications << actual_args end actual.call @actual_notifications.any? { |c| c == expected_args } end description do "send notification #{name.inspect} with args #{expected_args.inspect}" end failure_message do |_actual| s = "expected that proc would send notification #{name.inspect} with args #{expected_args.inspect}" if @actual_notifications.any? s << " (received #{@actual_notifications.size} times with other arguments: #{@actual_notifications.map(&:inspect).join(', ')})" end s end failure_message_when_negated do |_actual| "expected that proc would not send notification #{name.inspect} with args #{expected_args.inspect}" end end RSpec::Matchers.define :leak_open_files do |_name, *_expected_args| # Some remarks: # # • This matcher relies on global state (list of file descriptors and their # state), which means that a test case that uses this matcher can fail if # tests are being run in parallel. # # • This matcher assumes that file descriptors are closed explicitly, rather # than implicitly through garbage collection. This means that a test case # that uses this matcher can fail if the code exercised by the test case # relies on file descriptors to be closed when they are garbage collected. supports_block_expectations match do |actual| open_files_before = [] ObjectSpace.each_object(File) { |f| open_files_before << f.fileno unless f.closed? } actual.call open_files_after = [] ObjectSpace.each_object(File) { |f| open_files_after << f.fileno unless f.closed? } open_files_before.sort != open_files_after.sort end description do 'leak open files' end failure_message do |_actual| 'expected that proc would leak open files' end failure_message_when_negated do |_actual| 'expected that proc would not leak open files' end end nanoc-4.8.0/spec/regression_filenames_spec.rb0000644000004100000410000000104613134141051021337 0ustar www-datawww-data# frozen_string_literal: true describe 'regression tests', chdir: false do let(:regression_test_filenames) do Dir['spec/nanoc/regressions/*'] end let(:regression_test_numbers) do regression_test_filenames .map { |fn| File.readlines(fn).find { |l| l =~ /^describe/ }.match(/GH-(\d+)/)[1] } end it 'should have the proper filenames' do regression_test_filenames.zip(regression_test_numbers) do |fn, num| expect(fn).to match(/gh_#{num}[a-z]*_spec/), "#{fn} has the wrong name in its #define block" end end end nanoc-4.8.0/spec/nanoc/0000755000004100000410000000000013134141051014672 5ustar www-datawww-datananoc-4.8.0/spec/nanoc/base/0000755000004100000410000000000013134141051015604 5ustar www-datawww-datananoc-4.8.0/spec/nanoc/base/views/0000755000004100000410000000000013134141051016741 5ustar www-datawww-datananoc-4.8.0/spec/nanoc/base/views/document_view_spec.rb0000644000004100000410000002306613134141051023157 0ustar www-datawww-data# frozen_string_literal: true shared_examples 'a document view' do let(:view) { described_class.new(document, view_context) } let(:view_context) do Nanoc::ViewContext.new( reps: double(:reps), items: double(:items), dependency_tracker: dependency_tracker, compilation_context: double(:compilation_context), snapshot_repo: double(:snapshot_repo), ) end let(:dependency_tracker) { Nanoc::Int::DependencyTracker.new(dependency_store) } let(:dependency_store) { Nanoc::Int::DependencyStore.new(empty_items, empty_layouts, config) } let(:base_item) { Nanoc::Int::Item.new('base', {}, '/base.md') } let(:empty_items) { Nanoc::Int::ItemCollection.new(config) } let(:empty_layouts) { Nanoc::Int::LayoutCollection.new(config) } let(:config) { Nanoc::Int::Configuration.new.with_defaults } before do dependency_tracker.enter(base_item) end describe '#frozen?' do let(:document) { entity_class.new('content', {}, '/asdf/') } subject { view.frozen? } context 'non-frozen document' do it { is_expected.to be(false) } end context 'frozen document' do before { document.freeze } it { is_expected.to be(true) } end end describe '#== and #eql?' do let(:document) { entity_class.new('content', {}, '/asdf/') } context 'comparing with document with same identifier' do let(:other) { entity_class.new('content', {}, '/asdf/') } it 'is ==' do expect(view).to eq(other) end it 'is not eql?' do expect(view).not_to eql(other) end end context 'comparing with document with different identifier' do let(:other) { entity_class.new('content', {}, '/fdsa/') } it 'is not ==' do expect(view).not_to eq(other) end it 'is not eql?' do expect(view).not_to eql(other) end end context 'comparing with document view with same identifier' do let(:other) { other_view_class.new(entity_class.new('content', {}, '/asdf/'), nil) } it 'is ==' do expect(view).to eq(other) end it 'is not eql?' do expect(view).not_to eql(other) end end context 'comparing with document view with different identifier' do let(:other) { other_view_class.new(entity_class.new('content', {}, '/fdsa/'), nil) } it 'is not ==' do expect(view).not_to eq(other) end it 'is not eql?' do expect(view).not_to eql(other) end end context 'comparing with other object' do let(:other) { nil } it 'is not ==' do expect(view).not_to eq(other) end it 'is not eql?' do expect(view).not_to eql(other) end end end describe '#[]' do let(:document) { entity_class.new('stuff', { animal: 'donkey' }, '/foo/') } subject { view[key] } context 'with existant key' do let(:key) { :animal } it { is_expected.to eql('donkey') } it 'creates a dependency' do expect { subject }.to change { dependency_store.objects_causing_outdatedness_of(base_item) }.from([]).to([document]) end it 'creates a dependency with the right props' do subject dep = dependency_store.dependencies_causing_outdatedness_of(base_item)[0] expect(dep.props.attributes?).to eq(true) expect(dep.props.raw_content?).to eq(false) expect(dep.props.compiled_content?).to eq(false) expect(dep.props.path?).to eq(false) end end context 'with non-existant key' do let(:key) { :weapon } it { is_expected.to eql(nil) } it 'creates a dependency' do expect { subject }.to change { dependency_store.objects_causing_outdatedness_of(base_item) }.from([]).to([document]) end it 'creates a dependency with the right props' do subject dep = dependency_store.dependencies_causing_outdatedness_of(base_item)[0] expect(dep.props.attributes?).to eq(true) expect(dep.props.raw_content?).to eq(false) expect(dep.props.compiled_content?).to eq(false) expect(dep.props.path?).to eq(false) end end end describe '#attributes' do let(:document) { entity_class.new('stuff', { animal: 'donkey' }, '/foo/') } subject { view.attributes } it 'creates a dependency' do expect { subject }.to change { dependency_store.objects_causing_outdatedness_of(base_item) }.from([]).to([document]) end it 'creates a dependency with the right props' do subject dep = dependency_store.dependencies_causing_outdatedness_of(base_item)[0] expect(dep.props.attributes?).to eq(true) expect(dep.props.raw_content?).to eq(false) expect(dep.props.compiled_content?).to eq(false) expect(dep.props.path?).to eq(false) end it 'returns attributes' do expect(subject).to eql(animal: 'donkey') end end describe '#fetch' do let(:document) { entity_class.new('stuff', { animal: 'donkey' }, '/foo/') } context 'with existant key' do let(:key) { :animal } subject { view.fetch(key) } it { is_expected.to eql('donkey') } it 'creates a dependency' do expect { subject }.to change { dependency_store.objects_causing_outdatedness_of(base_item) }.from([]).to([document]) end it 'creates a dependency with the right props' do subject dep = dependency_store.dependencies_causing_outdatedness_of(base_item)[0] expect(dep.props.attributes?).to eq(true) expect(dep.props.raw_content?).to eq(false) expect(dep.props.compiled_content?).to eq(false) expect(dep.props.path?).to eq(false) end end context 'with non-existant key' do let(:key) { :weapon } context 'with fallback' do subject { view.fetch(key, 'nothing sorry') } it { is_expected.to eql('nothing sorry') } it 'creates a dependency' do expect { subject }.to change { dependency_store.objects_causing_outdatedness_of(base_item) }.from([]).to([document]) end it 'creates a dependency with the right props' do subject dep = dependency_store.dependencies_causing_outdatedness_of(base_item)[0] expect(dep.props.attributes?).to eq(true) expect(dep.props.raw_content?).to eq(false) expect(dep.props.compiled_content?).to eq(false) expect(dep.props.path?).to eq(false) end end context 'with block' do subject { view.fetch(key) { 'nothing sorry' } } it { is_expected.to eql('nothing sorry') } it 'creates a dependency' do expect { subject }.to change { dependency_store.objects_causing_outdatedness_of(base_item) }.from([]).to([document]) end it 'creates a dependency with the right props' do subject dep = dependency_store.dependencies_causing_outdatedness_of(base_item)[0] expect(dep.props.attributes?).to eq(true) expect(dep.props.raw_content?).to eq(false) expect(dep.props.compiled_content?).to eq(false) expect(dep.props.path?).to eq(false) end end context 'with no fallback and no block' do subject { view.fetch(key) } it 'raises' do expect { subject }.to raise_error(KeyError) end end end end describe '#key?' do let(:document) { entity_class.new('stuff', { animal: 'donkey' }, '/foo/') } subject { view.key?(key) } context 'with existant key' do let(:key) { :animal } it { is_expected.to eql(true) } it 'creates a dependency' do expect { subject }.to change { dependency_store.objects_causing_outdatedness_of(base_item) }.from([]).to([document]) end it 'creates a dependency with the right props' do subject dep = dependency_store.dependencies_causing_outdatedness_of(base_item)[0] expect(dep.props.attributes?).to eq(true) expect(dep.props.raw_content?).to eq(false) expect(dep.props.compiled_content?).to eq(false) expect(dep.props.path?).to eq(false) end end context 'with non-existant key' do let(:key) { :weapon } it { is_expected.to eql(false) } it 'creates a dependency' do expect { subject }.to change { dependency_store.objects_causing_outdatedness_of(base_item) }.from([]).to([document]) end it 'creates a dependency with the right props' do subject dep = dependency_store.dependencies_causing_outdatedness_of(base_item)[0] expect(dep.props.attributes?).to eq(true) expect(dep.props.raw_content?).to eq(false) expect(dep.props.compiled_content?).to eq(false) expect(dep.props.path?).to eq(false) end end end describe '#hash' do let(:document) { double(:document, identifier: '/foo/') } subject { view.hash } it { should == described_class.hash ^ '/foo/'.hash } end describe '#raw_content' do let(:document) { entity_class.new('stuff', { animal: 'donkey' }, '/foo/') } subject { view.raw_content } it { is_expected.to eql('stuff') } it 'creates a dependency' do expect { subject }.to change { dependency_store.objects_causing_outdatedness_of(base_item) }.from([]).to([document]) end it 'creates a dependency with the right props' do subject dep = dependency_store.dependencies_causing_outdatedness_of(base_item)[0] expect(dep.props.raw_content?).to eq(true) expect(dep.props.attributes?).to eq(false) expect(dep.props.compiled_content?).to eq(false) expect(dep.props.path?).to eq(false) end end end nanoc-4.8.0/spec/nanoc/base/views/mutable_layout_collection_view_spec.rb0000644000004100000410000000275713134141051026606 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::MutableLayoutCollectionView do let(:view_class) { Nanoc::MutableLayoutView } let(:collection_class) { Nanoc::Int::LayoutCollection } it_behaves_like 'an identifiable collection' it_behaves_like 'a mutable identifiable collection' let(:config) do { string_pattern_type: 'glob' } end describe '#create' do let(:layout) do Nanoc::Int::Layout.new('content', {}, '/asdf/') end let(:wrapped) do Nanoc::Int::LayoutCollection.new(config, [layout]) end let(:view) { described_class.new(wrapped, nil) } it 'creates an object' do view.create('new content', { title: 'New Page' }, '/new/') expect(view.unwrap.size).to eq(2) expect(view.unwrap['/new/'].content.string).to eq('new content') end it 'does not update wrapped' do view.create('new content', { title: 'New Page' }, '/new/') expect(wrapped.size).to eq(1) expect(wrapped['/new']).to be_nil end it 'returns self' do ret = view.create('new content', { title: 'New Page' }, '/new/') expect(ret).to equal(view) end end describe '#inspect' do let(:wrapped) do Nanoc::Int::LayoutCollection.new(config) end let(:view) { described_class.new(wrapped, view_context) } let(:view_context) { double(:view_context) } let(:config) { { string_pattern_type: 'glob' } } subject { view.inspect } it { is_expected.to eql('') } end end nanoc-4.8.0/spec/nanoc/base/views/item_collection_with_reps_view_spec.rb0000644000004100000410000000113213134141051026564 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::ItemCollectionWithRepsView do let(:view_class) { Nanoc::ItemWithRepsView } let(:collection_class) { Nanoc::Int::ItemCollection } it_behaves_like 'an identifiable collection' describe '#inspect' do let(:wrapped) do Nanoc::Int::ItemCollection.new(config) end let(:view) { described_class.new(wrapped, view_context) } let(:view_context) { double(:view_context) } let(:config) { { string_pattern_type: 'glob' } } subject { view.inspect } it { is_expected.to eql('') } end end nanoc-4.8.0/spec/nanoc/base/views/mutable_document_view_spec.rb0000644000004100000410000000603013134141051024660 0ustar www-datawww-data# frozen_string_literal: true shared_examples 'a mutable document view' do let(:view) { described_class.new(document, view_context) } let(:view_context) do Nanoc::ViewContext.new( reps: double(:reps), items: double(:items), dependency_tracker: dependency_tracker, compilation_context: double(:compilation_context), snapshot_repo: snapshot_repo, ) end let(:dependency_tracker) { Nanoc::Int::DependencyTracker.new(double(:dependency_store)) } let(:snapshot_repo) { double(:snapshot_repo) } describe '#raw_content=' do let(:document) { entity_class.new('content', {}, '/asdf/') } it 'sets raw content' do expect { view.raw_content = 'donkey' } .to change { document.content.string } .from('content') .to('donkey') end end describe '#[]=' do let(:document) { entity_class.new('content', {}, '/asdf/') } it 'sets attributes' do view[:title] = 'Donkey' expect(view[:title]).to eq('Donkey') end it 'disallows items' do item = Nanoc::Int::Item.new('content', {}, '/foo.md') expect { view[:item] = item }.to raise_error(Nanoc::MutableDocumentViewMixin::DisallowedAttributeValueError) end it 'disallows layouts' do layout = Nanoc::Int::Layout.new('content', {}, '/foo.md') expect { view[:layout] = layout }.to raise_error(Nanoc::MutableDocumentViewMixin::DisallowedAttributeValueError) end it 'disallows item views' do item = Nanoc::ItemWithRepsView.new(Nanoc::Int::Item.new('content', {}, '/foo.md'), nil) expect { view[:item] = item }.to raise_error(Nanoc::MutableDocumentViewMixin::DisallowedAttributeValueError) end it 'disallows layout views' do layout = Nanoc::LayoutView.new(Nanoc::Int::Layout.new('content', {}, '/foo.md'), nil) expect { view[:layout] = layout }.to raise_error(Nanoc::MutableDocumentViewMixin::DisallowedAttributeValueError) end end describe '#identifier=' do let(:document) { entity_class.new('content', {}, '/about.md') } subject { view.identifier = arg } context 'given a string' do let(:arg) { '/about.adoc' } it 'changes the identifier' do subject expect(view.identifier).to eq('/about.adoc') end end context 'given an identifier' do let(:arg) { Nanoc::Identifier.new('/about.adoc') } it 'changes the identifier' do subject expect(view.identifier).to eq('/about.adoc') end end context 'given anything else' do let(:arg) { :donkey } it 'raises' do expect { subject }.to raise_error(Nanoc::Identifier::NonCoercibleObjectError) end end end describe '#update_attributes' do let(:document) { entity_class.new('content', {}, '/asdf/') } let(:update) { { friend: 'Giraffe' } } subject { view.update_attributes(update) } it 'sets attributes' do expect { subject }.to change { view[:friend] }.from(nil).to('Giraffe') end it 'returns self' do expect(subject).to equal(view) end end end nanoc-4.8.0/spec/nanoc/base/views/config_view_spec.rb0000644000004100000410000000544113134141051022603 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::ConfigView do let(:config) do Nanoc::Int::Configuration.new(hash: hash) end let(:hash) { { amount: 9000, animal: 'donkey' } } let(:view) { described_class.new(config, view_context) } let(:view_context) do Nanoc::ViewContext.new( reps: double(:reps), items: double(:items), dependency_tracker: dependency_tracker, compilation_context: double(:compilation_context), snapshot_repo: double(:snapshot_repo), ) end let(:dependency_tracker) { double(:dependency_tracker) } describe '#frozen?' do subject { view.frozen? } context 'non-frozen config' do it { is_expected.to be(false) } end context 'frozen config' do before { config.freeze } it { is_expected.to be(true) } end end describe '#[]' do subject { view[key] } before do expect(dependency_tracker).to receive(:bounce).with(config, attributes: [key]) end context 'with existant key' do let(:key) { :animal } it { is_expected.to eql('donkey') } end context 'with non-existant key' do let(:key) { :weapon } it { is_expected.to eql(nil) } end end describe '#fetch' do before do expect(dependency_tracker).to receive(:bounce).with(config, attributes: [key]) end context 'with existant key' do let(:key) { :animal } subject { view.fetch(key) } it { is_expected.to eql('donkey') } end context 'with non-existant key' do let(:key) { :weapon } context 'with fallback' do subject { view.fetch(key, 'nothing sorry') } it { is_expected.to eql('nothing sorry') } end context 'with block' do subject { view.fetch(key) { 'nothing sorry' } } it { is_expected.to eql('nothing sorry') } end context 'with no fallback and no block' do subject { view.fetch(key) } it 'raises' do expect { subject }.to raise_error(KeyError) end end end end describe '#key?' do subject { view.key?(key) } before do expect(dependency_tracker).to receive(:bounce).with(config, attributes: [key]) end context 'with existant key' do let(:key) { :animal } it { is_expected.to eql(true) } end context 'with non-existant key' do let(:key) { :weapon } it { is_expected.to eql(false) } end end describe '#each' do before do expect(dependency_tracker).to receive(:bounce).with(config, attributes: true) end example do res = [] view.each { |k, v| res << [k, v] } expect(res).to eql([[:amount, 9000], [:animal, 'donkey']]) end end describe '#inspect' do subject { view.inspect } it { is_expected.to eql('') } end end nanoc-4.8.0/spec/nanoc/base/views/mutable_item_collection_view_spec.rb0000644000004100000410000000273713134141051026225 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::MutableItemCollectionView do let(:view_class) { Nanoc::MutableItemView } let(:collection_class) { Nanoc::Int::ItemCollection } it_behaves_like 'an identifiable collection' it_behaves_like 'a mutable identifiable collection' let(:config) do { string_pattern_type: 'glob' } end describe '#create' do let(:item) do Nanoc::Int::Layout.new('content', {}, '/asdf/') end let(:wrapped) do Nanoc::Int::ItemCollection.new(config, [item]) end let(:view) { described_class.new(wrapped, nil) } it 'creates an object' do view.create('new content', { title: 'New Page' }, '/new/') expect(view.unwrap.size).to eq(2) expect(view.unwrap['/new/'].content.string).to eq('new content') end it 'does not update wrapped' do view.create('new content', { title: 'New Page' }, '/new/') expect(wrapped.size).to eq(1) expect(wrapped['/new']).to be_nil end it 'returns self' do ret = view.create('new content', { title: 'New Page' }, '/new/') expect(ret).to equal(view) end end describe '#inspect' do let(:wrapped) do Nanoc::Int::ItemCollection.new(config) end let(:view) { described_class.new(wrapped, view_context) } let(:view_context) { double(:view_context) } let(:config) { { string_pattern_type: 'glob' } } subject { view.inspect } it { is_expected.to eql('') } end end nanoc-4.8.0/spec/nanoc/base/views/mutable_item_view_spec.rb0000644000004100000410000000126613134141051024006 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::MutableItemView do let(:entity_class) { Nanoc::Int::Item } it_behaves_like 'a mutable document view' let(:item) { entity_class.new('content', {}, '/asdf/') } let(:view) { described_class.new(item, nil) } it 'does have rep access' do expect(view).not_to respond_to(:compiled_content) expect(view).not_to respond_to(:path) expect(view).not_to respond_to(:reps) end describe '#inspect' do let(:item) { Nanoc::Int::Item.new('content', {}, '/asdf/') } let(:view) { described_class.new(item, nil) } subject { view.inspect } it { is_expected.to eql('') } end end nanoc-4.8.0/spec/nanoc/base/views/post_compile_item_rep_view_spec.rb0000644000004100000410000001304413134141051025715 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::PostCompileItemRepView do let(:item_rep) { Nanoc::Int::ItemRep.new(item, :jacques) } let(:item) { Nanoc::Int::Item.new('asdf', {}, '/foo/') } let(:view) { described_class.new(item_rep, view_context) } let(:view_context) do Nanoc::ViewContext.new( reps: reps, items: items, dependency_tracker: dependency_tracker, compilation_context: compilation_context, snapshot_repo: snapshot_repo, ) end let(:reps) { double(:reps) } let(:items) { Nanoc::Int::ItemCollection.new(config) } let(:config) { Nanoc::Int::Configuration.new } let(:dependency_tracker) { Nanoc::Int::DependencyTracker.new(double(:dependency_store)) } let(:compilation_context) { double(:compilation_context, compiled_content_cache: compiled_content_cache) } let(:snapshot_repo) { double(:snapshot_repo) } let(:snapshot_contents) do { last: Nanoc::Int::TextualContent.new('content-last'), pre: Nanoc::Int::TextualContent.new('content-pre'), donkey: Nanoc::Int::TextualContent.new('content-donkey'), } end let(:compiled_content_cache) do Nanoc::Int::CompiledContentCache.new(items: items).tap do |ccc| ccc[item_rep] = snapshot_contents end end describe '#raw_path' do context 'no args' do subject { view.raw_path } it 'does not raise' do subject end context 'no path specified' do it { is_expected.to be_nil } end context 'path for default snapshot specified' do before do item_rep.raw_paths = { last: ['output/about/index.html'] } end it { is_expected.to eql('output/about/index.html') } end context 'path specified, but not for default snapshot' do before do item_rep.raw_paths = { pre: ['output/about/index.html'] } end it { is_expected.to be_nil } end end context 'snapshot arg' do subject { view.raw_path(snapshot: :special) } it 'does not raise' do subject end context 'no path specified' do it { is_expected.to be_nil } end context 'path for default snapshot specified' do before do item_rep.raw_paths = { special: ['output/about/index.html'] } end it { is_expected.to eql('output/about/index.html') } end context 'path specified, but not for default snapshot' do before do item_rep.raw_paths = { pre: ['output/about/index.html'] } end it { is_expected.to be_nil } end end end describe '#compiled_content' do subject { view.compiled_content } context 'binary' do let(:snapshot_contents) do { last: Nanoc::Int::TextualContent.new('content-last'), pre: Nanoc::Int::BinaryContent.new('/content/pre'), donkey: Nanoc::Int::TextualContent.new('content-donkey'), } end it 'raises error' do err = Nanoc::Int::Errors::CannotGetCompiledContentOfBinaryItem expect { subject }.to raise_error(err) end end shared_examples 'returns pre content' do example { expect(subject).to eq('content-pre') } end shared_examples 'returns last content' do example { expect(subject).to eq('content-last') } end shared_examples 'returns donkey content' do example { expect(subject).to eq('content-donkey') } end shared_examples 'raises no-such-snapshot error' do it 'raises error' do err = Nanoc::Int::Errors::NoSuchSnapshot expect { subject }.to raise_error(err) end end context 'textual' do context 'snapshot provided' do subject { view.compiled_content(snapshot: :donkey) } let(:expected_snapshot) { :donkey } context 'snapshot exists' do include_examples 'returns donkey content' end context 'snapshot does not exist' do let(:snapshot_contents) do { last: Nanoc::Int::TextualContent.new('content-last'), pre: Nanoc::Int::TextualContent.new('content-pre'), } end include_examples 'raises no-such-snapshot error' end end context 'no snapshot provided' do context 'pre and last snapshots exist' do let(:snapshot_contents) do { last: Nanoc::Int::TextualContent.new('content-last'), pre: Nanoc::Int::TextualContent.new('content-pre'), donkey: Nanoc::Int::TextualContent.new('content-donkey'), } end include_examples 'returns pre content' end context 'pre snapshot exists' do let(:snapshot_contents) do { pre: Nanoc::Int::TextualContent.new('content-pre'), donkey: Nanoc::Int::TextualContent.new('content-donkey'), } end include_examples 'returns pre content' end context 'last snapshot exists' do let(:snapshot_contents) do { last: Nanoc::Int::TextualContent.new('content-last'), donkey: Nanoc::Int::TextualContent.new('content-donkey'), } end include_examples 'returns last content' end context 'neither pre nor last snapshot exists' do let(:snapshot_contents) do { donkey: Nanoc::Int::TextualContent.new('content-donkey'), } end include_examples 'raises no-such-snapshot error' end end end end end nanoc-4.8.0/spec/nanoc/base/views/item_rep_view_spec.rb0000644000004100000410000002701013134141051023136 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::ItemRepView do let(:view_context) do Nanoc::ViewContext.new( reps: reps, items: items, dependency_tracker: dependency_tracker, compilation_context: compilation_context, snapshot_repo: snapshot_repo, ) end let(:reps) { double(:reps) } let(:items) { double(:items) } let(:compilation_context) { double(:compilation_context) } let(:snapshot_repo) { Nanoc::Int::SnapshotRepo.new } let(:dependency_tracker) { Nanoc::Int::DependencyTracker.new(dependency_store) } let(:dependency_store) { Nanoc::Int::DependencyStore.new(empty_items, empty_layouts, config) } let(:base_item) { Nanoc::Int::Item.new('base', {}, '/base.md') } let(:empty_items) { Nanoc::Int::ItemCollection.new(config) } let(:empty_layouts) { Nanoc::Int::LayoutCollection.new(config) } let(:config) { Nanoc::Int::Configuration.new.with_defaults } before do dependency_tracker.enter(base_item) end describe '#frozen?' do let(:item_rep) { Nanoc::Int::ItemRep.new(item, :jacques) } let(:item) { Nanoc::Int::Item.new('asdf', {}, '/foo/') } let(:view) { described_class.new(item_rep, view_context) } subject { view.frozen? } context 'non-frozen item rep' do it { is_expected.to be(false) } end context 'frozen item rep' do before { item_rep.freeze } it { is_expected.to be(true) } end end describe '#== and #eql?' do let(:item_rep) { Nanoc::Int::ItemRep.new(item, :jacques) } let(:item) { Nanoc::Int::Item.new('asdf', {}, '/foo/') } let(:view) { described_class.new(item_rep, view_context) } context 'comparing with item rep with same identifier' do let(:other_item) { double(:other_item, identifier: '/foo/') } let(:other) { double(:other_item_rep, item: other_item, name: :jacques) } it 'is ==' do expect(view).to eq(other) end it 'is eql?' do expect(view).not_to eql(other) end end context 'comparing with item rep with different identifier' do let(:other_item) { double(:other_item, identifier: '/bar/') } let(:other) { double(:other_item_rep, item: other_item, name: :jacques) } it 'is not ==' do expect(view).not_to eq(other) end it 'is not eql?' do expect(view).not_to eql(other) end end context 'comparing with item rep with different name' do let(:other_item) { double(:other_item, identifier: '/foo/') } let(:other) { double(:other_item_rep, item: other_item, name: :marvin) } it 'is not ==' do expect(view).not_to eq(other) end it 'is not eql?' do expect(view).not_to eql(other) end end context 'comparing with item rep with same identifier' do let(:other_item) { double(:other_item, identifier: '/foo/') } let(:other) { described_class.new(double(:other_item_rep, item: other_item, name: :jacques), view_context) } it 'is ==' do expect(view).to eq(other) end it 'is eql?' do expect(view).not_to eql(other) end end context 'comparing with item rep with different identifier' do let(:other_item) { double(:other_item, identifier: '/bar/') } let(:other) { described_class.new(double(:other_item_rep, item: other_item, name: :jacques), view_context) } it 'is not equal' do expect(view).not_to eq(other) expect(view).not_to eql(other) end end context 'comparing with item rep with different name' do let(:other_item) { double(:other_item, identifier: '/foo/') } let(:other) { described_class.new(double(:other_item_rep, item: other_item, name: :marvin), view_context) } it 'is not equal' do expect(view).not_to eq(other) expect(view).not_to eql(other) end end context 'comparing with something that is not an item rep' do let(:other_item) { double(:other_item, identifier: '/foo/') } let(:other) { :donkey } it 'is not equal' do expect(view).not_to eq(other) expect(view).not_to eql(other) end end end describe '#hash' do let(:item_rep) { Nanoc::Int::ItemRep.new(item, :jacques) } let(:item) { Nanoc::Int::Item.new('asdf', {}, '/foo/') } let(:view) { described_class.new(item_rep, view_context) } subject { view.hash } it { should == described_class.hash ^ Nanoc::Identifier.new('/foo/').hash ^ :jacques.hash } end describe '#snapshot?' do subject { view.snapshot?(snapshot_name) } let(:view) { described_class.new(rep, view_context) } let(:rep) do Nanoc::Int::ItemRep.new(item, :default).tap do |ir| ir.compiled = true ir.snapshot_defs = [ Nanoc::Int::SnapshotDef.new(:last, binary: false), ] end end let(:item) do Nanoc::Int::Item.new('content', {}, '/asdf.md') end let(:snapshot_name) { raise 'override me' } before do snapshot_repo.set(rep, :last, Nanoc::Int::TextualContent.new('Hallo')) end context 'snapshot exists' do let(:snapshot_name) { :last } it 'creates a dependency' do expect { subject }.to change { dependency_store.objects_causing_outdatedness_of(base_item) }.from([]).to([item]) end it 'creates a dependency with the right props' do subject dep = dependency_store.dependencies_causing_outdatedness_of(base_item)[0] expect(dep.props.compiled_content?).to eq(true) expect(dep.props.raw_content?).to eq(false) expect(dep.props.attributes?).to eq(false) expect(dep.props.path?).to eq(false) end it { is_expected.to be } end context 'snapshot does not exist' do let(:snapshot_name) { :donkey } it 'creates a dependency' do expect { subject }.to change { dependency_store.objects_causing_outdatedness_of(base_item) }.from([]).to([item]) end it 'creates a dependency with the right props' do subject dep = dependency_store.dependencies_causing_outdatedness_of(base_item)[0] expect(dep.props.compiled_content?).to eq(true) expect(dep.props.raw_content?).to eq(false) expect(dep.props.attributes?).to eq(false) expect(dep.props.path?).to eq(false) end it { is_expected.not_to be } end end describe '#compiled_content' do subject { view.compiled_content } let(:view) { described_class.new(rep, view_context) } let(:rep) do Nanoc::Int::ItemRep.new(item, :default).tap do |ir| ir.compiled = true ir.snapshot_defs = [ Nanoc::Int::SnapshotDef.new(:last, binary: false), ] end end let(:item) do Nanoc::Int::Item.new('content', {}, '/asdf.md') end before do snapshot_repo.set(rep, :last, Nanoc::Int::TextualContent.new('Hallo')) end it 'creates a dependency' do expect { subject } .to change { dependency_store.objects_causing_outdatedness_of(base_item) } .from([]) .to([item]) end it 'creates a dependency with the right props' do subject dep = dependency_store.dependencies_causing_outdatedness_of(base_item)[0] expect(dep.props.compiled_content?).to eq(true) expect(dep.props.raw_content?).to eq(false) expect(dep.props.attributes?).to eq(false) expect(dep.props.path?).to eq(false) end it { should eq('Hallo') } end describe '#path' do subject { view.path } let(:view) { described_class.new(rep, view_context) } let(:rep) do Nanoc::Int::ItemRep.new(item, :default).tap do |ir| ir.paths = { last: ['/about/'], } end end let(:item) do Nanoc::Int::Item.new('content', {}, '/asdf.md') end it 'creates a dependency' do expect { subject }.to change { dependency_store.objects_causing_outdatedness_of(base_item) }.from([]).to([item]) end it 'creates a dependency with the right props' do subject dep = dependency_store.dependencies_causing_outdatedness_of(base_item)[0] expect(dep.props.path?).to eq(true) expect(dep.props.raw_content?).to eq(false) expect(dep.props.attributes?).to eq(false) expect(dep.props.compiled_content?).to eq(false) end it { should eq('/about/') } end describe '#raw_path' do subject { Fiber.new { view.raw_path }.resume } let(:view) { described_class.new(rep, view_context) } let(:rep) do Nanoc::Int::ItemRep.new(item, :default).tap do |ir| ir.raw_paths = { last: ['output/about/index.html'], } end end let(:item) do Nanoc::Int::Item.new('content', {}, '/asdf.md') end context 'rep is not compiled' do it 'creates a dependency' do expect { subject }.to change { dependency_store.objects_causing_outdatedness_of(base_item) }.from([]).to([item]) end it 'creates a dependency with the right props' do subject dep = dependency_store.dependencies_causing_outdatedness_of(base_item)[0] expect(dep.props.compiled_content?).to eq(true) expect(dep.props.raw_content?).to eq(false) expect(dep.props.attributes?).to eq(false) expect(dep.props.path?).to eq(false) end it { should be_a(Nanoc::Int::Errors::UnmetDependency) } end context 'rep is compiled' do before { rep.compiled = true } it 'creates a dependency' do expect { subject }.to change { dependency_store.objects_causing_outdatedness_of(base_item) }.from([]).to([item]) end it 'creates a dependency with the right props' do subject dep = dependency_store.dependencies_causing_outdatedness_of(base_item)[0] expect(dep.props.compiled_content?).to eq(true) expect(dep.props.raw_content?).to eq(false) expect(dep.props.attributes?).to eq(false) expect(dep.props.path?).to eq(false) end it { should eq('output/about/index.html') } end end describe '#binary?' do let(:item_rep) { Nanoc::Int::ItemRep.new(item, :jacques) } let(:item) { Nanoc::Int::Item.new('asdf', {}, '/foo/') } let(:view) { described_class.new(item_rep, view_context) } subject { view.binary? } context 'no :last snapshot' do before do item_rep.snapshot_defs = [] end it 'raises' do expect { subject }.to raise_error(Nanoc::Int::Errors::NoSuchSnapshot) end end context ':last snapshot is textual' do before do item_rep.snapshot_defs = [Nanoc::Int::SnapshotDef.new(:last, binary: false)] end it { is_expected.not_to be } end context ':last snapshot is binary' do before do item_rep.snapshot_defs = [Nanoc::Int::SnapshotDef.new(:last, binary: true)] end it { is_expected.to be } end end describe '#item' do let(:item_rep) { Nanoc::Int::ItemRep.new(item, :jacques) } let(:item) { Nanoc::Int::Item.new('asdf', {}, '/foo/') } let(:view) { described_class.new(item_rep, view_context) } subject { view.item } it 'returns an item view' do expect(subject).to be_a(Nanoc::ItemWithRepsView) end it 'returns an item view with the right context' do expect(subject._context).to equal(view_context) end end describe '#inspect' do let(:item_rep) { Nanoc::Int::ItemRep.new(item, :jacques) } let(:item) { Nanoc::Int::Item.new('asdf', {}, '/foo/') } let(:view) { described_class.new(item_rep, view_context) } subject { view.inspect } it { is_expected.to eql('') } end end nanoc-4.8.0/spec/nanoc/base/views/item_view_spec.rb0000644000004100000410000002267213134141051022301 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::ItemWithRepsView do let(:entity_class) { Nanoc::Int::Item } let(:other_view_class) { Nanoc::LayoutView } it_behaves_like 'a document view' let(:view_context) do Nanoc::ViewContext.new( reps: reps, items: items, dependency_tracker: dependency_tracker, compilation_context: compilation_context, snapshot_repo: snapshot_repo, ) end let(:reps) { [] } let(:items) { [] } let(:dependency_tracker) { Nanoc::Int::DependencyTracker.new(dependency_store) } let(:dependency_store) { Nanoc::Int::DependencyStore.new(empty_items, empty_layouts, config) } let(:compilation_context) { double(:compilation_context) } let(:snapshot_repo) { Nanoc::Int::SnapshotRepo.new } let(:base_item) { Nanoc::Int::Item.new('base', {}, '/base.md') } let(:empty_items) { Nanoc::Int::ItemCollection.new(config) } let(:empty_layouts) { Nanoc::Int::LayoutCollection.new(config) } let(:config) { Nanoc::Int::Configuration.new.with_defaults } before do dependency_tracker.enter(base_item) end describe '#parent' do let(:item) do Nanoc::Int::Item.new('me', {}, identifier) end let(:view) { described_class.new(item, view_context) } let(:items) do Nanoc::Int::ItemCollection.new( {}, [ item, parent_item, ].compact, ) end subject { view.parent } context 'with parent' do let(:parent_item) do Nanoc::Int::Item.new('parent', {}, '/parent/') end context 'full identifier' do let(:identifier) do Nanoc::Identifier.new('/parent/me.md') end it 'raises' do expect { subject }.to raise_error(Nanoc::Int::Errors::CannotGetParentOrChildrenOfNonLegacyItem) end end context 'legacy identifier' do let(:identifier) do Nanoc::Identifier.new('/parent/me/', type: :legacy) end it 'returns a view for the parent' do expect(subject.class).to eql(Nanoc::ItemWithRepsView) expect(subject.unwrap).to eql(parent_item) end it 'returns a view with the right context' do expect(subject._context).to equal(view_context) end context 'frozen parent' do before { parent_item.freeze } it { is_expected.to be_frozen } end context 'non-frozen parent' do it { is_expected.not_to be_frozen } end context 'with root parent' do let(:parent_item) { Nanoc::Int::Item.new('parent', {}, '/') } let(:identifier) { Nanoc::Identifier.new('/me/', type: :legacy) } it 'returns a view for the parent' do expect(subject.class).to eql(Nanoc::ItemWithRepsView) expect(subject.unwrap).to eql(parent_item) end end end end context 'without parent' do let(:parent_item) do nil end context 'full identifier' do let(:identifier) do Nanoc::Identifier.new('/me.md') end it 'raises' do expect { subject }.to raise_error(Nanoc::Int::Errors::CannotGetParentOrChildrenOfNonLegacyItem) end end context 'legacy identifier' do let(:identifier) do Nanoc::Identifier.new('/me/', type: :legacy) end it { is_expected.to be_nil } it { is_expected.to be_frozen } end end end describe '#children' do let(:item) do Nanoc::Int::Item.new('me', {}, identifier) end let(:children) do [Nanoc::Int::Item.new('child', {}, '/me/child/')] end let(:view) { described_class.new(item, view_context) } let(:items) do Nanoc::Int::ItemCollection.new( {}, [ item, *children, ], ) end subject { view.children } context 'full identifier' do let(:identifier) do Nanoc::Identifier.new('/me.md') end it 'raises' do expect { subject }.to raise_error(Nanoc::Int::Errors::CannotGetParentOrChildrenOfNonLegacyItem) end end context 'legacy identifier' do let(:identifier) do Nanoc::Identifier.new('/me/', type: :legacy) end it 'returns views for the children' do expect(subject.size).to eql(1) expect(subject[0].class).to eql(Nanoc::ItemWithRepsView) expect(subject[0].unwrap).to eql(children[0]) end it { is_expected.to be_frozen } end end describe '#reps' do let(:item) { Nanoc::Int::Item.new('blah', {}, '/foo.md') } let(:rep_a) { Nanoc::Int::ItemRep.new(item, :a) } let(:rep_b) { Nanoc::Int::ItemRep.new(item, :b) } let(:reps) do Nanoc::Int::ItemRepRepo.new.tap do |reps| reps << rep_a reps << rep_b end end let(:view) { described_class.new(item, view_context) } subject { view.reps } it 'returns a proper item rep collection' do expect(subject.size).to eq(2) expect(subject.class).to eql(Nanoc::ItemRepCollectionView) end it 'returns a view with the right context' do expect(subject._context).to eq(view_context) end end describe '#compiled_content' do subject { view.compiled_content(params) } let(:view) { described_class.new(item, view_context) } let(:item) do Nanoc::Int::Item.new('content', {}, '/asdf/') end let(:reps) do Nanoc::Int::ItemRepRepo.new.tap do |reps| reps << rep end end let(:rep) do Nanoc::Int::ItemRep.new(item, :default).tap do |ir| ir.compiled = true ir.snapshot_defs = [ Nanoc::Int::SnapshotDef.new(:last, binary: false), Nanoc::Int::SnapshotDef.new(:pre, binary: false), Nanoc::Int::SnapshotDef.new(:post, binary: false), Nanoc::Int::SnapshotDef.new(:specific, binary: false), ] end end before do snapshot_repo.set(rep, :last, Nanoc::Int::TextualContent.new('Last Hallo')) snapshot_repo.set(rep, :pre, Nanoc::Int::TextualContent.new('Pre Hallo')) snapshot_repo.set(rep, :post, Nanoc::Int::TextualContent.new('Post Hallo')) snapshot_repo.set(rep, :specific, Nanoc::Int::TextualContent.new('Specific Hallo')) end context 'requesting implicit default rep' do let(:params) { {} } it { is_expected.to eq('Pre Hallo') } it 'creates a dependency' do expect { subject }.to change { dependency_store.objects_causing_outdatedness_of(base_item) }.from([]).to([item]) end context 'requesting explicit snapshot' do let(:params) { { snapshot: :specific } } it { is_expected.to eq('Specific Hallo') } it 'creates a dependency' do expect { subject }.to change { dependency_store.objects_causing_outdatedness_of(base_item) }.from([]).to([item]) end end end context 'requesting explicit default rep' do let(:params) { { rep: :default } } it 'creates a dependency' do expect { subject }.to change { dependency_store.objects_causing_outdatedness_of(base_item) }.from([]).to([item]) end it { is_expected.to eq('Pre Hallo') } context 'requesting explicit snapshot' do let(:params) { { snapshot: :specific } } it { is_expected.to eq('Specific Hallo') } end end context 'requesting other rep' do let(:params) { { rep: :other } } it 'raises an error' do expect { subject }.to raise_error(Nanoc::ItemRepCollectionView::NoSuchItemRepError) end end end describe '#path' do subject { view.path(params) } let(:view) { described_class.new(item, view_context) } let(:item) do Nanoc::Int::Item.new('content', {}, '/asdf.md') end let(:reps) do Nanoc::Int::ItemRepRepo.new.tap do |reps| reps << rep end end let(:rep) do Nanoc::Int::ItemRep.new(item, :default).tap do |ir| ir.paths = { last: ['/about/'], specific: ['/about.txt'], } end end context 'requesting implicit default rep' do let(:params) { {} } it 'creates a dependency' do expect { subject }.to change { dependency_store.objects_causing_outdatedness_of(base_item) }.from([]).to([item]) end it { is_expected.to eq('/about/') } context 'requesting explicit snapshot' do let(:params) { { snapshot: :specific } } it { is_expected.to eq('/about.txt') } end end context 'requesting explicit default rep' do let(:params) { { rep: :default } } it 'creates a dependency' do expect { subject }.to change { dependency_store.objects_causing_outdatedness_of(base_item) }.from([]).to([item]) end it { is_expected.to eq('/about/') } context 'requesting explicit snapshot' do let(:params) { { snapshot: :specific } } it { is_expected.to eq('/about.txt') } end end context 'requesting other rep' do let(:params) { { rep: :other } } it 'raises an error' do expect { subject }.to raise_error(Nanoc::ItemRepCollectionView::NoSuchItemRepError) end end end describe '#binary?' do # TODO: implement end describe '#raw_filename' do # TODO: implement end describe '#inspect' do let(:item) { Nanoc::Int::Item.new('content', {}, '/asdf/') } let(:view) { described_class.new(item, nil) } subject { view.inspect } it { is_expected.to eql('') } end end nanoc-4.8.0/spec/nanoc/base/views/post_compile_item_view_spec.rb0000644000004100000410000000316513134141051025052 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::PostCompileItemView do let(:item) { Nanoc::Int::Item.new('blah', {}, '/foo.md') } let(:rep_a) { Nanoc::Int::ItemRep.new(item, :no_mod) } let(:rep_b) { Nanoc::Int::ItemRep.new(item, :modded).tap { |r| r.modified = true } } let(:reps) do Nanoc::Int::ItemRepRepo.new.tap do |reps| reps << rep_a reps << rep_b end end let(:view_context) { double(:view_context, reps: reps) } let(:view) { described_class.new(item, view_context) } shared_examples 'a method that returns modified reps only' do it 'returns only modified items' do expect(subject.size).to eq(1) expect(subject.map(&:name)).to eq(%i[modded]) end it 'returns an array' do expect(subject.class).to eql(Array) end end shared_examples 'a method that returns PostCompileItemRepViews' do it 'returns PostCompileItemRepViews' do expect(subject).to all(be_a(Nanoc::PostCompileItemRepView)) end end describe '#modified_reps' do subject { view.modified_reps } it_behaves_like 'a method that returns modified reps only' it_behaves_like 'a method that returns PostCompileItemRepViews' end describe '#modified' do subject { view.modified } it_behaves_like 'a method that returns modified reps only' it_behaves_like 'a method that returns PostCompileItemRepViews' end describe '#reps' do subject { view.reps } it_behaves_like 'a method that returns PostCompileItemRepViews' it 'returns a PostCompileItemRepCollectionView' do expect(subject).to be_a(Nanoc::PostCompileItemRepCollectionView) end end end nanoc-4.8.0/spec/nanoc/base/views/mutable_config_view_spec.rb0000644000004100000410000000065213134141051024313 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::MutableConfigView do let(:config) { {} } let(:view) { described_class.new(config, nil) } describe '#[]=' do it 'sets attributes' do view[:awesomeness] = 'rather high' expect(config[:awesomeness]).to eq('rather high') end end describe '#inspect' do subject { view.inspect } it { is_expected.to eql('') } end end nanoc-4.8.0/spec/nanoc/base/views/item_rep_collection_view_spec.rb0000644000004100000410000000645513134141051025363 0ustar www-datawww-data# frozen_string_literal: true shared_examples 'an item rep collection view' do let(:view) { described_class.new(wrapped, view_context) } let(:view_context) { double(:view_context) } let(:wrapped) do [ double(:item_rep, name: :foo), double(:item_rep, name: :bar), double(:item_rep, name: :baz), ] end describe '#unwrap' do subject { view.unwrap } it { should equal(wrapped) } end describe '#frozen?' do subject { view.frozen? } context 'non-frozen collection' do it { is_expected.to be(false) } end context 'frozen collection' do before { wrapped.freeze } it { is_expected.to be(true) } end end describe '#each' do it 'yields' do actual = [].tap { |res| view.each { |v| res << v } } expect(actual.size).to eq(3) end it 'returns self' do expect(view.each { |_i| }).to equal(view) end it 'yields elements with the right context' do view.each { |v| expect(v._context).to equal(view_context) } end end describe '#size' do subject { view.size } it { should == 3 } end describe '#to_ary' do subject { view.to_ary } it 'returns an array of item rep views' do expect(subject.class).to eq(Array) expect(subject.size).to eq(3) expect(subject[0].class).to eql(expected_view_class) expect(subject[0].name).to eql(:foo) end it 'returns an array with correct contexts' do expect(subject[0]._context).to equal(view_context) end end describe '#[]' do subject { view[name] } context 'when not found' do let(:name) { :donkey } it { should be_nil } end context 'when found' do let(:name) { :foo } it 'returns a view' do expect(subject.class).to eq(expected_view_class) expect(subject.name).to eq(:foo) end it 'returns a view with the correct context' do expect(subject._context).to equal(view_context) end end context 'when given a string' do let(:name) { 'foo' } it 'raises' do expect { subject }.to raise_error(ArgumentError, 'expected ItemRepCollectionView#[] to be called with a symbol') end end context 'when given a number' do let(:name) { 0 } it 'raises' do expect { subject }.to raise_error(ArgumentError, 'expected ItemRepCollectionView#[] to be called with a symbol (you likely want `.reps[:default]` rather than `.reps[0]`)') end end end describe '#fetch' do subject { view.fetch(name) } context 'when not found' do let(:name) { :donkey } it 'raises' do expect { subject }.to raise_error(Nanoc::ItemRepCollectionView::NoSuchItemRepError) end end context 'when found' do let(:name) { :foo } it 'returns a view' do expect(subject.class).to eq(expected_view_class) expect(subject.name).to eq(:foo) end it 'returns a view with the correct context' do expect(subject._context).to equal(view_context) end end end describe '#inspect' do subject { view.inspect } it { is_expected.to eql('<' + described_class.name + '>') } end end describe Nanoc::ItemRepCollectionView do it_behaves_like 'an item rep collection view' let(:expected_view_class) { Nanoc::ItemRepView } end nanoc-4.8.0/spec/nanoc/base/views/mutable_layout_view_spec.rb0000644000004100000410000000064013134141051024360 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::MutableLayoutView do let(:entity_class) { Nanoc::Int::Layout } it_behaves_like 'a mutable document view' describe '#inspect' do let(:item) { Nanoc::Int::Item.new('content', {}, '/asdf/') } let(:view) { described_class.new(item, nil) } subject { view.inspect } it { is_expected.to eql('') } end end nanoc-4.8.0/spec/nanoc/base/views/mutable_identifiable_collection_view_spec.rb0000644000004100000410000000204113134141051027672 0ustar www-datawww-data# frozen_string_literal: true shared_examples 'a mutable identifiable collection' do let(:view) { described_class.new(wrapped, view_context) } let(:view_context) { double(:view_context) } let(:config) do {} end describe '#delete_if' do let(:wrapped) do collection_class.new( config, [double(:identifiable, identifier: Nanoc::Identifier.new('/asdf/'))], ) end it 'deletes matching' do view.delete_if { |i| i.identifier == '/asdf/' } expect(view.unwrap).to be_empty end it 'does not mutate' do view.delete_if { |i| i.identifier == '/asdf/' } expect(wrapped).not_to be_empty end it 'deletes no non-matching' do view.delete_if { |i| i.identifier == '/blah/' } expect(wrapped).not_to be_empty end it 'returns self' do ret = view.delete_if { |_i| false } expect(ret).to equal(view) end it 'yields items with the proper context' do view.delete_if { |i| expect(i._context).to equal(view_context) } end end end nanoc-4.8.0/spec/nanoc/base/views/item_collection_without_reps_view_spec.rb0000644000004100000410000000114313134141051027316 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::ItemCollectionWithoutRepsView do let(:view_class) { Nanoc::ItemWithoutRepsView } let(:collection_class) { Nanoc::Int::ItemCollection } it_behaves_like 'an identifiable collection' describe '#inspect' do let(:wrapped) do Nanoc::Int::ItemCollection.new(config) end let(:view) { described_class.new(wrapped, view_context) } let(:view_context) { double(:view_context) } let(:config) { { string_pattern_type: 'glob' } } subject { view.inspect } it { is_expected.to eql('') } end end nanoc-4.8.0/spec/nanoc/base/views/post_compile_item_rep_collection_view_spec.rb0000644000004100000410000000030513134141051030124 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::PostCompileItemRepCollectionView do it_behaves_like 'an item rep collection view' let(:expected_view_class) { Nanoc::PostCompileItemRepView } end nanoc-4.8.0/spec/nanoc/base/views/layout_view_spec.rb0000644000004100000410000000070113134141051022645 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::LayoutView do let(:entity_class) { Nanoc::Int::Layout } let(:other_view_class) { Nanoc::ItemWithRepsView } it_behaves_like 'a document view' describe '#inspect' do let(:item) { Nanoc::Int::Layout.new('content', {}, '/asdf/') } let(:view) { described_class.new(item, nil) } subject { view.inspect } it { is_expected.to eql('') } end end nanoc-4.8.0/spec/nanoc/base/views/identifiable_collection_view_spec.rb0000644000004100000410000001615113134141051026170 0ustar www-datawww-data# frozen_string_literal: true # Needs :view_class shared_examples 'an identifiable collection' do let(:view) { described_class.new(wrapped, view_context) } let(:view_context) do Nanoc::ViewContext.new( reps: double(:__reps), items: double(:__items), dependency_tracker: dependency_tracker, compilation_context: double(:__compilation_context), snapshot_repo: double(:__snapshot_repo), ) end let(:dependency_tracker) do Nanoc::Int::DependencyTracker::Null.new end let(:config) do { string_pattern_type: 'glob' } end describe '#frozen?' do let(:wrapped) do collection_class.new( config, [ double(:identifiable, identifier: Nanoc::Identifier.new('/foo')), double(:identifiable, identifier: Nanoc::Identifier.new('/bar')), ], ) end subject { view.frozen? } context 'non-frozen collection' do it { is_expected.to be(false) } end context 'frozen collection' do before do wrapped.each { |o| expect(o).to receive(:freeze) } wrapped.freeze end it { is_expected.to be(true) } end end describe '#unwrap' do let(:wrapped) do collection_class.new( config, [ double(:identifiable, identifier: Nanoc::Identifier.new('/foo')), double(:identifiable, identifier: Nanoc::Identifier.new('/bar')), double(:identifiable, identifier: Nanoc::Identifier.new('/baz')), ], ) end subject { view.unwrap } it { should equal(wrapped) } it 'does not create dependency' do expect(dependency_tracker).not_to receive(:bounce) subject end end describe '#each' do let(:wrapped) do collection_class.new( config, [ double(:identifiable, identifier: Nanoc::Identifier.new('/foo')), double(:identifiable, identifier: Nanoc::Identifier.new('/bar')), double(:identifiable, identifier: Nanoc::Identifier.new('/baz')), ], ) end it 'creates dependency' do expect(dependency_tracker).to receive(:bounce).with(wrapped, raw_content: true) view.each { |_i| } end it 'returns self' do expect(view.each { |_i| }).to equal(view) end it 'yields elements with the right context' do view.each { |v| expect(v._context).to equal(view_context) } end end describe '#size' do let(:wrapped) do collection_class.new( config, [ double(:identifiable, identifier: Nanoc::Identifier.new('/foo')), double(:identifiable, identifier: Nanoc::Identifier.new('/bar')), double(:identifiable, identifier: Nanoc::Identifier.new('/baz')), ], ) end subject { view.size } it 'creates dependency' do expect(dependency_tracker).to receive(:bounce).with(wrapped, raw_content: true) subject end it { should == 3 } end describe '#[]' do let(:page_object) do double(:identifiable, identifier: Nanoc::Identifier.new('/page.erb')) end let(:home_object) do double(:identifiable, identifier: Nanoc::Identifier.new('/home.erb')) end let(:wrapped) do collection_class.new( config, [ page_object, home_object, ], ) end subject { view[arg] } context 'no objects found' do let(:arg) { '/donkey.*' } it { is_expected.to equal(nil) } it 'creates dependency' do expect(dependency_tracker).to receive(:bounce).with(wrapped, raw_content: ['/donkey.*']) subject end end context 'string' do let(:arg) { '/home.erb' } it 'creates dependency' do expect(dependency_tracker).to receive(:bounce).with(wrapped, raw_content: ['/home.erb']) subject end it 'returns wrapped object' do expect(subject.class).to equal(view_class) expect(subject.unwrap).to equal(home_object) end it 'returns objects with right context' do expect(subject._context).to equal(view_context) end end context 'identifier' do let(:arg) { Nanoc::Identifier.new('/home.erb') } it 'creates dependency' do expect(dependency_tracker).to receive(:bounce).with(wrapped, raw_content: ['/home.erb']) subject end it 'returns wrapped object' do expect(subject.class).to equal(view_class) expect(subject.unwrap).to equal(home_object) end end context 'glob' do let(:arg) { '/home.*' } context 'globs not enabled' do let(:config) { { string_pattern_type: 'legacy' } } it 'creates dependency' do expect(dependency_tracker).to receive(:bounce).with(wrapped, raw_content: ['/home.*']) subject end it 'returns nil' do expect(subject).to be_nil end end context 'globs enabled' do it 'creates dependency' do expect(dependency_tracker).to receive(:bounce).with(wrapped, raw_content: ['/home.*']) subject end it 'returns wrapped object' do expect(subject.class).to equal(view_class) expect(subject.unwrap).to equal(home_object) end end end context 'regex' do let(:arg) { %r{\A/home} } it 'creates dependency' do expect(dependency_tracker).to receive(:bounce).with(wrapped, raw_content: [%r{\A/home}]) subject end it 'returns wrapped object' do expect(subject.class).to equal(view_class) expect(subject.unwrap).to equal(home_object) end end end describe '#find_all' do let(:wrapped) do collection_class.new( config, [ double(:identifiable, identifier: Nanoc::Identifier.new('/about.css')), double(:identifiable, identifier: Nanoc::Identifier.new('/about.md')), double(:identifiable, identifier: Nanoc::Identifier.new('/style.css')), ], ) end subject { view.find_all(arg) } context 'with string' do let(:arg) { '/*.css' } it 'creates dependency' do expect(dependency_tracker).to receive(:bounce).with(wrapped, raw_content: ['/*.css']) subject end it 'contains views' do expect(subject.size).to eql(2) about_css = subject.find { |iv| iv.identifier == '/about.css' } style_css = subject.find { |iv| iv.identifier == '/style.css' } expect(about_css.class).to equal(view_class) expect(style_css.class).to equal(view_class) end end context 'with regex' do let(:arg) { %r{\.css\z} } it 'creates dependency' do expect(dependency_tracker).to receive(:bounce).with(wrapped, raw_content: [%r{\.css\z}]) subject end it 'contains views' do expect(subject.size).to eql(2) about_css = subject.find { |iv| iv.identifier == '/about.css' } style_css = subject.find { |iv| iv.identifier == '/style.css' } expect(about_css.class).to equal(view_class) expect(style_css.class).to equal(view_class) end end end end nanoc-4.8.0/spec/nanoc/base/views/layout_collection_view_spec.rb0000644000004100000410000000111413134141051025057 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::LayoutCollectionView do let(:view_class) { Nanoc::LayoutView } let(:collection_class) { Nanoc::Int::LayoutCollection } it_behaves_like 'an identifiable collection' describe '#inspect' do let(:wrapped) do Nanoc::Int::LayoutCollection.new(config) end let(:view) { described_class.new(wrapped, view_context) } let(:view_context) { double(:view_context) } let(:config) { { string_pattern_type: 'glob' } } subject { view.inspect } it { is_expected.to eql('') } end end nanoc-4.8.0/spec/nanoc/base/core_ext/0000755000004100000410000000000013134141051017414 5ustar www-datawww-datananoc-4.8.0/spec/nanoc/base/core_ext/pathname_spec.rb0000644000004100000410000000000013134141051022536 0ustar www-datawww-datananoc-4.8.0/spec/nanoc/base/core_ext/hash_spec.rb0000644000004100000410000000203513134141051021676 0ustar www-datawww-data# frozen_string_literal: true describe 'Hash#__nanoc_symbolize_keys_recursively' do it 'should convert keys to symbols' do hash_old = { 'foo' => 'bar' } hash_new = { foo: 'bar' } expect(hash_old.__nanoc_symbolize_keys_recursively).to eql(hash_new) end it 'should not require string keys' do hash_old = { Time.now => 'abc' } hash_new = hash_old expect(hash_old.__nanoc_symbolize_keys_recursively).to eql(hash_new) end end describe 'Hash#__nanoc_freeze_recursively' do it 'should prevent first-level elements from being modified' do hash = { a: { b: :c } } hash.__nanoc_freeze_recursively expect { hash[:a] = 123 }.to raise_frozen_error end it 'should prevent second-level elements from being modified' do hash = { a: { b: :c } } hash.__nanoc_freeze_recursively expect { hash[:a][:b] = 123 }.to raise_frozen_error end it 'should not freeze infinitely' do a = {} a[:x] = a a.__nanoc_freeze_recursively expect(a).to be_frozen expect(a[0]).to be_frozen end end nanoc-4.8.0/spec/nanoc/base/core_ext/string_spec.rb0000644000004100000410000000127613134141051022267 0ustar www-datawww-data# frozen_string_literal: true describe 'String#__nanoc_cleaned_identifier' do it 'should not convert already clean paths' do expect('/foo/bar/'.__nanoc_cleaned_identifier).to eql('/foo/bar/') end it 'should prepend slash if necessary' do expect('foo/bar/'.__nanoc_cleaned_identifier).to eql('/foo/bar/') end it 'should append slash if necessary' do expect('/foo/bar'.__nanoc_cleaned_identifier).to eql('/foo/bar/') end it 'should remove double slashes at start' do expect('//foo/bar/'.__nanoc_cleaned_identifier).to eql('/foo/bar/') end it 'should remove double slashes at end' do expect('/foo/bar//'.__nanoc_cleaned_identifier).to eql('/foo/bar/') end end nanoc-4.8.0/spec/nanoc/base/core_ext/array_spec.rb0000644000004100000410000000164613134141051022100 0ustar www-datawww-data# frozen_string_literal: true describe 'Array#__nanoc_symbolize_keys_recursively' do it 'should convert keys to symbols' do array_old = [:abc, 'xyz', { 'foo' => 'bar', :baz => :qux }] array_new = [:abc, 'xyz', { foo: 'bar', baz: :qux }] expect(array_old.__nanoc_symbolize_keys_recursively).to eql(array_new) end end describe 'Array#__nanoc_freeze_recursively' do it 'should prevent first-level elements from being modified' do array = [:a, %i[b c], :d] array.__nanoc_freeze_recursively expect { array[0] = 123 }.to raise_frozen_error end it 'should prevent second-level elements from being modified' do array = [:a, %i[b c], :d] array.__nanoc_freeze_recursively expect { array[1][0] = 123 }.to raise_frozen_error end it 'should not freeze infinitely' do a = [] a << a a.__nanoc_freeze_recursively expect(a).to be_frozen expect(a[0]).to be_frozen end end nanoc-4.8.0/spec/nanoc/base/entities/0000755000004100000410000000000013134141051017430 5ustar www-datawww-datananoc-4.8.0/spec/nanoc/base/entities/layout_spec.rb0000644000004100000410000000044713134141051022311 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::Int::Layout do it_behaves_like 'a document' describe '#reference' do let(:layout) { described_class.new('hi', {}, '/foo.md') } it 'has the proper reference' do expect(layout.reference).to eql('layout:/foo.md') end end end nanoc-4.8.0/spec/nanoc/base/entities/site_spec.rb0000644000004100000410000000336313134141051021740 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::Int::Site do describe '#freeze' do let(:site) do described_class.new( config: config, code_snippets: code_snippets, data_source: Nanoc::Int::InMemDataSource.new(items, layouts), ) end let(:config) do Nanoc::Int::Configuration.new.with_defaults end let(:code_snippets) do [ Nanoc::Int::CodeSnippet.new('FOO = 123', 'hello.rb'), Nanoc::Int::CodeSnippet.new('BAR = 123', 'hi.rb'), ] end let(:items) do Nanoc::Int::ItemCollection.new( config, [ Nanoc::Int::Item.new('foo', {}, '/foo.md'), Nanoc::Int::Item.new('bar', {}, '/bar.md'), ], ) end let(:layouts) do Nanoc::Int::LayoutCollection.new( config, [ Nanoc::Int::Layout.new('foo', {}, '/foo.md'), Nanoc::Int::Layout.new('bar', {}, '/bar.md'), ], ) end before do site.freeze end it 'freezes the configuration' do expect(site.config).to be_frozen end it 'freezes the configuration contents' do expect(site.config[:output_dir]).to be_frozen end it 'freezes items collection' do expect(site.items).to be_frozen end it 'freezes individual items' do expect(site.items).to all(be_frozen) end it 'freezes layouts collection' do expect(site.layouts).to be_frozen end it 'freezes individual layouts' do expect(site.layouts).to all(be_frozen) end it 'freezes code snippets collection' do expect(site.code_snippets).to be_frozen end it 'freezes individual code snippets' do expect(site.code_snippets).to all(be_frozen) end end end nanoc-4.8.0/spec/nanoc/base/entities/item_rep_spec.rb0000644000004100000410000000121213134141051022567 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::Int::ItemRep do let(:item) { Nanoc::Int::Item.new('asdf', {}, '/foo.md') } let(:rep) { Nanoc::Int::ItemRep.new(item, :giraffe) } describe '#snapshot?' do subject { rep.snapshot?(snapshot_name) } let(:snapshot_name) { raise 'override me' } before do rep.snapshot_defs = [Nanoc::Int::SnapshotDef.new(:donkey, binary: false)] end context 'snapshot does not exist' do let(:snapshot_name) { :giraffe } it { is_expected.not_to be } end context 'snapshot exists' do let(:snapshot_name) { :donkey } it { is_expected.to be } end end end nanoc-4.8.0/spec/nanoc/base/entities/action_sequence_spec.rb0000644000004100000410000002172713134141051024145 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::Int::ActionSequence do let(:action_sequence) { raise 'override me' } let(:item) { Nanoc::Int::Item.new('foo', {}, '/foo.md') } let(:rep) { Nanoc::Int::ItemRep.new(item, :default) } describe '#size' do subject { action_sequence.size } context 'no actions' do let(:action_sequence) do described_class.build(rep) do |b| end end it { is_expected.to eql(0) } end context 'some actions' do let(:action_sequence) do described_class.build(rep) do |b| b.add_filter(:foo, {}) end end it { is_expected.to eql(1) } end end describe '#[]' do subject { action_sequence[index] } let(:index) { 0 } context 'no actions' do let(:action_sequence) do described_class.build(rep) do |b| end end it { is_expected.to be_nil } end context 'some actions' do let(:action_sequence) do described_class.build(rep) do |b| b.add_filter(:foo, {}) end end it { is_expected.to be_a(Nanoc::Int::ProcessingActions::Filter) } end end describe '#add_filter' do let(:action_sequence) do described_class.build(rep) do |b| b.add_filter(:foo, donkey: 123) end end example do expect(action_sequence.size).to eql(1) expect(action_sequence[0]).to be_a(Nanoc::Int::ProcessingActions::Filter) expect(action_sequence[0].filter_name).to eql(:foo) expect(action_sequence[0].params).to eql(donkey: 123) end end describe '#add_layout' do let(:action_sequence) do described_class.build(rep) do |b| b.add_layout('/foo.*', donkey: 123) end end example do expect(action_sequence.size).to eql(1) expect(action_sequence[0]).to be_a(Nanoc::Int::ProcessingActions::Layout) expect(action_sequence[0].layout_identifier).to eql('/foo.*') expect(action_sequence[0].params).to eql(donkey: 123) end end describe '#add_snapshot' do context 'snapshot does not yet exist' do let(:action_sequence) do described_class.build(rep) do |b| b.add_snapshot(:before_layout, '/foo.md') end end example do expect(action_sequence.size).to eql(1) expect(action_sequence[0]).to be_a(Nanoc::Int::ProcessingActions::Snapshot) expect(action_sequence[0].snapshot_names).to eql([:before_layout]) expect(action_sequence[0].paths).to eql(['/foo.md']) end end context 'snapshot already exist' do it 'raises' do described_class.build(rep) do |b| b.add_snapshot(:before_layout, '/bar.md') expect { b.add_snapshot(:before_layout, '/foo.md') } .to raise_error(Nanoc::Int::Errors::CannotCreateMultipleSnapshotsWithSameName) end end end end describe '#each' do let(:action_sequence) do described_class.build(rep) do |b| b.add_filter(:erb, awesomeness: 'high') b.add_snapshot(:bar, '/foo.md') b.add_layout('/default.erb', somelayoutparam: 'yes') end end example do actions = [] action_sequence.each { |a| actions << a } expect(actions.size).to eq(3) end end describe '#map' do let(:action_sequence) do described_class.build(rep) do |b| b.add_filter(:erb, awesomeness: 'high') b.add_snapshot(:bar, '/foo.md') b.add_layout('/default.erb', somelayoutparam: 'yes') end end example do res = action_sequence.map { Nanoc::Int::ProcessingActions::Filter.new(:donkey, {}) } expect(res.to_a.size).to eq(3) expect(res.to_a).to all(be_a(Nanoc::Int::ProcessingActions::Filter)) end end describe '#serialize' do subject { action_sequence.serialize } let(:action_sequence) do described_class.build(rep) do |b| b.add_filter(:erb, awesomeness: 'high') b.add_snapshot(:bar, '/foo.md') b.add_layout('/default.erb', somelayoutparam: 'yes') end end example do expect(subject).to eql( [ [:filter, :erb, 'PeWUm2PtXYtqeHJdTqnY7kkwAow='], [:snapshot, [:bar], true, ['/foo.md']], [:layout, '/default.erb', '97LAe1pYTLKczxBsu+x4MmvqdkU='], ], ) end end describe '#snapshots_defs' do let(:item) { Nanoc::Int::Item.new('asdf', {}, '/foo.md') } let(:rep) { Nanoc::Int::ItemRep.new(item, :default) } Class.new(Nanoc::Filter) do identifier :RuleMemSpec_filter_b2b type :binary => :binary # rubocop:disable Style/HashSyntax def run(content, params = {}); end end Class.new(Nanoc::Filter) do identifier :RuleMemSpec_filter_b2t type :binary => :text # rubocop:disable Style/HashSyntax def run(content, params = {}); end end Class.new(Nanoc::Filter) do identifier :RuleMemSpec_filter_t2t type :text => :text # rubocop:disable Style/HashSyntax def run(content, params = {}); end end Class.new(Nanoc::Filter) do identifier :RuleMemSpec_filter_t2b type :text => :binary # rubocop:disable Style/HashSyntax def run(content, params = {}); end end it 'has no snapshot defs by default' do action_sequence = described_class.build(rep) do |b| end expect(action_sequence.snapshots_defs).to be_empty end context 'textual item' do let(:item) { Nanoc::Int::Item.new('asdf', {}, '/foo.md') } it 'generates initial textual snapshot def' do action_sequence = described_class.build(rep) do |b| b.add_snapshot(:giraffe, nil) end expect(action_sequence.snapshots_defs.size).to eq(1) expect(action_sequence.snapshots_defs[0].name).to eq(:giraffe) expect(action_sequence.snapshots_defs[0]).not_to be_binary end it 'generated follow-up textual snapshot def if previous filter is textual' do action_sequence = described_class.build(rep) do |b| b.add_snapshot(:giraffe, nil) b.add_filter(:RuleMemSpec_filter_t2t, arguments: 'irrelevant') b.add_snapshot(:zebra, nil) end expect(action_sequence.snapshots_defs.size).to eq(2) expect(action_sequence.snapshots_defs[0].name).to eq(:giraffe) expect(action_sequence.snapshots_defs[0]).not_to be_binary expect(action_sequence.snapshots_defs[1].name).to eq(:zebra) expect(action_sequence.snapshots_defs[1]).not_to be_binary end it 'generated follow-up binary snapshot def if previous filter is text-to-bianry' do action_sequence = described_class.build(rep) do |b| b.add_snapshot(:giraffe, nil) b.add_filter(:RuleMemSpec_filter_t2b, arguments: 'irrelevant') b.add_snapshot(:zebra, nil) end expect(action_sequence.snapshots_defs.size).to eq(2) expect(action_sequence.snapshots_defs[0].name).to eq(:giraffe) expect(action_sequence.snapshots_defs[0]).not_to be_binary expect(action_sequence.snapshots_defs[1].name).to eq(:zebra) expect(action_sequence.snapshots_defs[1]).to be_binary end end context 'binary item' do let(:item) { Nanoc::Int::Item.new(Nanoc::Int::BinaryContent.new('/asdf.dat'), {}, '/foo.md') } it 'generates initial binary snapshot def' do action_sequence = described_class.build(rep) do |b| b.add_snapshot(:giraffe, nil) end expect(action_sequence.snapshots_defs.size).to eq(1) expect(action_sequence.snapshots_defs[0].name).to eq(:giraffe) expect(action_sequence.snapshots_defs[0]).to be_binary end it 'generated follow-up binary snapshot def if previous filter is binary' do action_sequence = described_class.build(rep) do |b| b.add_snapshot(:giraffe, nil) b.add_filter(:RuleMemSpec_filter_b2b, arguments: 'irrelevant') b.add_snapshot(:zebra, nil) end expect(action_sequence.snapshots_defs.size).to eq(2) expect(action_sequence.snapshots_defs[0].name).to eq(:giraffe) expect(action_sequence.snapshots_defs[0]).to be_binary expect(action_sequence.snapshots_defs[1].name).to eq(:zebra) expect(action_sequence.snapshots_defs[1]).to be_binary end it 'generated follow-up textual snapshot def if previous filter is binary-to-text' do action_sequence = described_class.build(rep) do |b| b.add_snapshot(:giraffe, nil) b.add_filter(:RuleMemSpec_filter_b2t, arguments: 'irrelevant') b.add_snapshot(:zebra, nil) end expect(action_sequence.snapshots_defs.size).to eq(2) expect(action_sequence.snapshots_defs[0].name).to eq(:giraffe) expect(action_sequence.snapshots_defs[0]).to be_binary expect(action_sequence.snapshots_defs[1].name).to eq(:zebra) expect(action_sequence.snapshots_defs[1]).not_to be_binary end end end end nanoc-4.8.0/spec/nanoc/base/entities/document_spec.rb0000644000004100000410000001500413134141051022605 0ustar www-datawww-data# frozen_string_literal: true shared_examples 'a document' do describe '#initialize' do let(:content_arg) { 'Hello world' } let(:attributes_arg) { { 'title' => 'Home' } } let(:identifier_arg) { '/home.md' } let(:checksum_data_arg) { 'abcdef' } let(:content_checksum_data_arg) { 'con-cs' } let(:attributes_checksum_data_arg) { 'attr-cs' } subject do described_class.new( content_arg, attributes_arg, identifier_arg, checksum_data: checksum_data_arg, content_checksum_data: content_checksum_data_arg, attributes_checksum_data: attributes_checksum_data_arg, ) end describe 'content arg' do context 'string' do it 'converts content' do expect(subject.content).to be_a(Nanoc::Int::TextualContent) expect(subject.content.string).to eql('Hello world') end end context 'content' do let(:content_arg) { Nanoc::Int::TextualContent.new('foo') } it 'reuses content' do expect(subject.content).to equal(content_arg) end end end describe 'attributes arg' do context 'hash' do it 'symbolizes attributes' do expect(subject.attributes).to eq(title: 'Home') end end context 'proc' do call_count = 0 let(:attributes_arg) do proc do call_count += 1 { 'title' => 'Home' } end end before do call_count = 0 end it 'does not call the proc immediately' do expect(call_count).to eql(0) end it 'symbolizes attributes' do expect(subject.attributes).to eq(title: 'Home') end it 'only calls the proc once' do subject.attributes subject.attributes expect(call_count).to eql(1) end end end describe 'identifier arg' do context 'string' do it 'converts identifier' do expect(subject.identifier).to be_a(Nanoc::Identifier) expect(subject.identifier.to_s).to eql('/home.md') end end context 'identifier' do let(:identifier_arg) { Nanoc::Identifier.new('/foo.md') } it 'retains identifier' do expect(subject.identifier).to equal(identifier_arg) end end end describe 'checksum_data arg' do it 'reuses checksum_data' do expect(subject.checksum_data).to eql(checksum_data_arg) end end describe 'content_checksum_data arg' do it 'reuses content_checksum_data' do expect(subject.content_checksum_data).to eql(content_checksum_data_arg) end end describe 'attributes_checksum_data arg' do it 'reuses attributes_checksum_data' do expect(subject.attributes_checksum_data).to eql(attributes_checksum_data_arg) end end end describe '#freeze' do let(:content_arg) { 'Hallo' } let(:attributes_arg) { { foo: { bar: 'asdf' } } } let(:document) { described_class.new(content_arg, attributes_arg, '/foo.md') } before do document.freeze end it 'refuses changes to content' do expect { document.instance_variable_set(:@content, 'hah') }.to raise_frozen_error expect { document.content.string << 'hah' }.to raise_frozen_error end it 'refuses to change attributes' do expect { document.instance_variable_set(:@attributes, a: 'Hi') }.to raise_frozen_error expect { document.attributes[:title] = 'Bye' }.to raise_frozen_error expect { document.attributes[:foo][:bar] = 'fdsa' }.to raise_frozen_error end it 'refuses to change identifier' do expect { document.identifier = '/asdf' }.to raise_frozen_error expect { document.identifier.to_s << '/omg' }.to raise_frozen_error end context 'binary content' do let(:content_arg) { Nanoc::Int::BinaryContent.new(File.expand_path('foo.dat')) } it 'refuses changes to content' do expect { document.instance_variable_set(:@content, 'hah') }.to raise_frozen_error expect { document.content.filename << 'hah' }.to raise_frozen_error end end context 'attributes block' do let(:attributes_arg) { proc { { foo: { bar: 'asdf' } } } } it 'gives access to the attributes' do expect(document.attributes).to eql(foo: { bar: 'asdf' }) end it 'refuses to change attributes' do expect { document.instance_variable_set(:@attributes, a: 'Hi') }.to raise_frozen_error expect { document.attributes[:title] = 'Bye' }.to raise_frozen_error expect { document.attributes[:foo][:bar] = 'fdsa' }.to raise_frozen_error end end end describe 'equality' do let(:content_arg_a) { 'Hello world' } let(:content_arg_b) { 'Bye world' } let(:attributes_arg_a) { { 'title' => 'Home' } } let(:attributes_arg_b) { { 'title' => 'About' } } let(:identifier_arg_a) { '/home.md' } let(:identifier_arg_b) { '/home.md' } let(:document_a) { described_class.new(content_arg_a, attributes_arg_a, identifier_arg_a) } let(:document_b) { described_class.new(content_arg_b, attributes_arg_b, identifier_arg_b) } subject { document_a == document_b } context 'same identifier' do let(:identifier_arg_a) { '/home.md' } let(:identifier_arg_b) { '/home.md' } it { is_expected.to eql(true) } it 'has same hashes' do expect(document_a.hash).to eql(document_b.hash) end end context 'different identifier' do let(:identifier_arg_a) { '/home.md' } let(:identifier_arg_b) { '/about.md' } it { is_expected.to eql(false) } it 'has different hashes' do expect(document_a.hash).not_to eql(document_b.hash) end end context 'comparing with non-document' do let(:document_b) { nil } it { is_expected.to eql(false) } it 'has different hashes' do expect(document_a.hash).not_to eql(document_b.hash) end end end describe '#with_identifier_prefix' do let(:document) { described_class.new('kontent', { at: 'ribut' }, '/donkey.md') } subject { document.with_identifier_prefix('/animals') } it 'does not mutate the original' do document.freeze subject end it 'returns a new document with a prefixed identifier' do expect(subject.identifier).to eq('/animals/donkey.md') end it 'does not change other data' do expect(subject.content).to be_some_textual_content('kontent') expect(subject.attributes).to eq(at: 'ribut') end end end nanoc-4.8.0/spec/nanoc/base/entities/outdatedness_status_spec.rb0000644000004100000410000000551113134141051025076 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::Int::OutdatednessStatus do let(:status) { described_class.new } describe '#reasons' do subject { status.reasons } context 'default' do it { is_expected.to eql([]) } end context 'one passed in' do let(:reasons) do [ Nanoc::Int::OutdatednessReasons::CodeSnippetsModified, ] end let(:status) { described_class.new(reasons: reasons) } it { is_expected.to eql(reasons) } end context 'two passed in' do let(:reasons) do [ Nanoc::Int::OutdatednessReasons::CodeSnippetsModified, Nanoc::Int::OutdatednessReasons::ContentModified, ] end let(:status) { described_class.new(reasons: reasons) } it { is_expected.to eql(reasons) } end end describe '#props' do subject { status.props.active } context 'default' do it { is_expected.to eql(Set.new) } end context 'specific one passed in' do let(:props) do Nanoc::Int::Props.new(attributes: true) end let(:status) { described_class.new(props: props) } it { is_expected.to eql(Set.new([:attributes])) } end end describe '#useful_to_apply' do subject { status.useful_to_apply?(rule) } let(:status) { described_class.new(props: props) } let(:props) { Nanoc::Int::Props.new } let(:rule) { Nanoc::Int::OutdatednessRules::RulesModified } context 'no props' do it { is_expected.to be } end context 'some props' do context 'same props' do let(:props) { Nanoc::Int::Props.new(compiled_content: true, path: true) } it { is_expected.not_to be } end context 'different props' do let(:props) { Nanoc::Int::Props.new(attributes: true) } it { is_expected.to be } end end context 'all props' do let(:props) { Nanoc::Int::Props.new(raw_content: true, attributes: true, compiled_content: true, path: true) } it { is_expected.not_to be } end end describe '#update' do subject { status.update(reason) } let(:reason) { Nanoc::Int::OutdatednessReasons::ContentModified } context 'no existing reason or props' do it 'adds a reason' do expect(subject.reasons).to eql([reason]) end end context 'existing reason' do let(:status) { described_class.new(reasons: [old_reason]) } let(:old_reason) { Nanoc::Int::OutdatednessReasons::NotWritten } it 'adds a reason' do expect(subject.reasons).to eql([old_reason, reason]) end end context 'existing props' do let(:status) { described_class.new(props: Nanoc::Int::Props.new(attributes: true)) } it 'updates props' do expect(subject.props.active).to eql(Set.new(%i[raw_content attributes compiled_content])) end end end end nanoc-4.8.0/spec/nanoc/base/entities/content_spec.rb0000644000004100000410000001160213134141051022441 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::Int::Content do describe '.create' do subject { described_class.create(arg, params) } let(:params) { {} } context 'nil arg' do let(:arg) { nil } it 'raises' do expect { subject }.to raise_error(ArgumentError) end end context 'content arg' do let(:arg) { Nanoc::Int::TextualContent.new('foo') } it { is_expected.to eql(arg) } end context 'with binary: true param' do let(:arg) { '/foo.dat' } let(:params) { { binary: true } } it 'returns binary content' do expect(subject).to be_a(Nanoc::Int::BinaryContent) expect(subject.filename).to eql('/foo.dat') end end context 'with binary: false param' do context 'with filename param' do let(:arg) { 'foo' } let(:params) { { binary: false, filename: '/foo.md' } } it 'returns textual content' do expect(subject).to be_a(Nanoc::Int::TextualContent) expect(subject.string).to eql('foo') expect(subject.filename).to eql('/foo.md') end end context 'without filename param' do let(:arg) { 'foo' } let(:params) { { binary: false } } it 'returns textual content' do expect(subject).to be_a(Nanoc::Int::TextualContent) expect(subject.string).to eql('foo') expect(subject.filename).to be_nil end end end end end describe Nanoc::Int::TextualContent do describe '#initialize' do context 'without filename' do let(:content) { described_class.new('foo') } it 'sets string and filename' do expect(content.string).to eq('foo') expect(content.filename).to be_nil end end context 'with absolute filename' do let(:content) { described_class.new('foo', filename: '/foo.md') } it 'sets string and filename' do expect(content.string).to eq('foo') expect(content.filename).to eq('/foo.md') end end context 'with relative filename' do let(:content) { described_class.new('foo', filename: 'foo.md') } it 'errors' do expect { content }.to raise_error(ArgumentError) end end context 'with proc' do let(:content_proc) { -> { 'foo' } } let(:content) { described_class.new(content_proc) } it 'does not call the proc immediately' do expect(content_proc).not_to receive(:call) content end it 'sets string' do expect(content_proc).to receive(:call).once.and_return('dataz') expect(content.string).to eq('dataz') end it 'only calls the proc once' do expect(content_proc).to receive(:call).once.and_return('dataz') expect(content.string).to eq('dataz') expect(content.string).to eq('dataz') end end end describe '#binary?' do subject { content.binary? } let(:content) { described_class.new('foo') } it { is_expected.to eql(false) } end describe '#freeze' do let(:content) { described_class.new('foo', filename: '/asdf.md') } before do content.freeze end it 'prevents changes to string' do expect(content.string).to be_frozen expect { content.string << 'asdf' }.to raise_frozen_error end it 'prevents changes to filename' do expect(content.filename).to be_frozen expect { content.filename << 'asdf' }.to raise_frozen_error end context 'with proc' do let(:content) { described_class.new(proc { 'foo' }) } it 'prevents changes to string' do expect(content.string).to be_frozen expect { content.string << 'asdf' }.to raise_frozen_error end end end describe 'marshalling' do let(:content) { described_class.new('foo', filename: '/foo.md') } it 'dumps as an array' do expect(content.marshal_dump).to eq(['/foo.md', 'foo']) end it 'restores a dumped object' do restored = Marshal.load(Marshal.dump(content)) expect(restored.string).to eq('foo') expect(restored.filename).to eq('/foo.md') end end end describe Nanoc::Int::BinaryContent do describe '#initialize' do let(:content) { described_class.new('/foo.dat') } it 'sets filename' do expect(content.filename).to eql('/foo.dat') end context 'with relative filename' do let(:content) { described_class.new('foo.dat') } it 'errors' do expect { content }.to raise_error(ArgumentError) end end end describe '#binary?' do subject { content.binary? } let(:content) { described_class.new('/foo.dat') } it { is_expected.to eql(true) } end describe '#freeze' do let(:content) { described_class.new('/foo.dat') } before do content.freeze end it 'prevents changes' do expect(content.filename).to be_frozen expect { content.filename << 'asdf' }.to raise_frozen_error end end end nanoc-4.8.0/spec/nanoc/base/entities/processing_action_spec.rb0000644000004100000410000000052513134141051024502 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::Int::ProcessingAction do let(:action) { described_class.new } it 'is abstract' do expect { action.serialize }.to raise_error(NotImplementedError) expect { action.to_s }.to raise_error(NotImplementedError) expect { action.inspect }.to raise_error(NotImplementedError) end end nanoc-4.8.0/spec/nanoc/base/entities/identifiable_collection_spec.rb0000644000004100000410000001123213134141051025620 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::Int::IdentifiableCollection do shared_examples 'a generic identifiable collection' do subject(:identifiable_collection) { described_class.new(config, objects) } let(:config) { Nanoc::Int::Configuration.new } let(:objects) { [] } describe '#reject' do subject { identifiable_collection.reject { |_| false } } it { is_expected.to be_a(described_class) } end describe '#[]' do let(:objects) do [ Nanoc::Int::Item.new('asdf', {}, Nanoc::Identifier.new('/one')), Nanoc::Int::Item.new('asdf', {}, Nanoc::Identifier.new('/two')), ] end context 'string pattern style is glob' do let(:config) { Nanoc::Int::Configuration.new.with_defaults } it 'handles glob' do expect(identifiable_collection['/on*']).to equal(objects[0]) expect(identifiable_collection['/*wo']).to equal(objects[1]) end end context 'string pattern style is glob' do let(:config) { Nanoc::Int::Configuration.new } it 'does not handle glob' do expect(identifiable_collection['/on*']).to be_nil expect(identifiable_collection['/*wo']).to be_nil end end it 'handles identifier' do expect(identifiable_collection['/one']).to equal(objects[0]) expect(identifiable_collection['/two']).to equal(objects[1]) end it 'handles malformed identifier' do expect(identifiable_collection['one/']).to be_nil expect(identifiable_collection['/one/']).to be_nil expect(identifiable_collection['one']).to be_nil expect(identifiable_collection['//one']).to be_nil expect(identifiable_collection['/one//']).to be_nil end it 'handles regex' do expect(identifiable_collection[/one/]).to equal(objects[0]) expect(identifiable_collection[/on/]).to equal(objects[0]) expect(identifiable_collection[/\/o/]).to equal(objects[0]) expect(identifiable_collection[/e$/]).to equal(objects[0]) end context 'frozen' do before { identifiable_collection.freeze } example do expect(identifiable_collection['/one']).to equal(objects[0]) expect(identifiable_collection['/fifty']).to be_nil end end end describe '#find_all' do let(:objects) do [ double(:identifiable, identifier: Nanoc::Identifier.new('/about.css')), double(:identifiable, identifier: Nanoc::Identifier.new('/about.md')), double(:identifiable, identifier: Nanoc::Identifier.new('/style.css')), ] end let(:arg) { raise 'override me' } subject { identifiable_collection.find_all(arg) } context 'with string' do let(:arg) { '/*.css' } it 'contains objects' do expect(subject.size).to eql(2) expect(subject.find { |iv| iv.identifier == '/about.css' }).to eq(objects[0]) expect(subject.find { |iv| iv.identifier == '/style.css' }).to eq(objects[2]) end end context 'with regex' do let(:arg) { %r{\.css\z} } it 'contains objects' do expect(subject.size).to eql(2) expect(subject.find { |iv| iv.identifier == '/about.css' }).to eq(objects[0]) expect(subject.find { |iv| iv.identifier == '/style.css' }).to eq(objects[2]) end end end describe '#object_with_identifier' do let(:objects) do [ Nanoc::Int::Item.new('stuff', {}, Nanoc::Identifier.new('/about.css')), Nanoc::Int::Item.new('stuff', {}, Nanoc::Identifier.new('/about.md')), Nanoc::Int::Item.new('stuff', {}, Nanoc::Identifier.new('/style.css')), ] end let(:arg) { raise 'override me' } subject { identifiable_collection.object_with_identifier(arg) } context 'with string' do let(:arg) { '/about.css' } it { is_expected.to eq(objects[0]) } end context 'with identifier' do let(:arg) { Nanoc::Identifier.new('/about.css') } it { is_expected.to eq(objects[0]) } end context 'with glob string' do let(:arg) { '/about.*' } it { is_expected.to be_nil } end end describe '#reference' do subject { identifiable_collection.reference } it { is_expected.to eql(expected_reference) } end end describe Nanoc::Int::ItemCollection do let(:expected_reference) { 'items' } it_behaves_like 'a generic identifiable collection' end describe Nanoc::Int::LayoutCollection do let(:expected_reference) { 'layouts' } it_behaves_like 'a generic identifiable collection' end end nanoc-4.8.0/spec/nanoc/base/entities/identifier_spec.rb0000644000004100000410000003024013134141051023110 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::Identifier do describe '.from' do subject { described_class.from(arg) } context 'given an identifier' do let(:arg) { Nanoc::Identifier.new('/foo.md') } it 'returns an identifier' do expect(subject).to be_a(Nanoc::Identifier) expect(subject.to_s).to eq('/foo.md') expect(subject).to be_full end end context 'given a string' do let(:arg) { '/foo.md' } it 'returns an identifier' do expect(subject).to be_a(Nanoc::Identifier) expect(subject.to_s).to eq('/foo.md') expect(subject).to be_full end end context 'given something else' do let(:arg) { 12_345 } it 'raises' do expect { subject }.to raise_error(Nanoc::Identifier::NonCoercibleObjectError) end end end describe '#initialize' do context 'legacy type' do it 'does not convert already clean paths' do expect(described_class.new('/foo/bar/', type: :legacy).to_s).to eql('/foo/bar/') end it 'prepends slash if necessary' do expect(described_class.new('foo/bar/', type: :legacy).to_s).to eql('/foo/bar/') end it 'appends slash if necessary' do expect(described_class.new('/foo/bar', type: :legacy).to_s).to eql('/foo/bar/') end it 'removes double slashes at start' do expect(described_class.new('//foo/bar/', type: :legacy).to_s).to eql('/foo/bar/') end it 'removes double slashes at end' do expect(described_class.new('/foo/bar//', type: :legacy).to_s).to eql('/foo/bar/') end it 'freezes' do identifier = described_class.new('/foo/bar/', type: :legacy) expect { identifier.to_s << 'bbq' }.to raise_frozen_error end end context 'full type' do it 'refuses string not starting with a slash' do expect { described_class.new('foo') } .to raise_error(Nanoc::Identifier::InvalidIdentifierError) end it 'has proper string representation' do expect(described_class.new('/foo').to_s).to eql('/foo') end it 'freezes' do identifier = described_class.new('/foo/bar') expect { identifier.to_s << 'bbq' }.to raise_frozen_error end end context 'other type' do it 'errors' do expect { described_class.new('foo', type: :donkey) } .to raise_error(Nanoc::Identifier::InvalidTypeError) end end context 'default type' do it 'is full' do expect(described_class.new('/foo')).to be_full end end context 'other args specified' do it 'errors' do expect { described_class.new('?', animal: :donkey) } .to raise_error(ArgumentError) end end end describe '#to_s' do it 'returns immutable string' do expect { described_class.new('foo/', type: :legacy).to_s << 'lols' }.to raise_frozen_error expect { described_class.new('/foo').to_s << 'lols' }.to raise_frozen_error end end describe '#to_str' do it 'returns immutable string' do expect { described_class.new('/foo/bar').to_str << 'lols' }.to raise_frozen_error end end describe 'Comparable' do it 'can be compared' do expect(described_class.new('/foo/bar') <= '/qux').to eql(true) end end describe '#inspect' do let(:identifier) { described_class.new('/foo/bar') } subject { identifier.inspect } it { should == '' } end describe '#== and #eql?' do context 'comparing with equal identifier' do let(:identifier_a) { described_class.new('//foo/bar/', type: :legacy) } let(:identifier_b) { described_class.new('/foo/bar//', type: :legacy) } it 'is ==' do expect(identifier_a).to eq(identifier_b) end it 'is eql?' do expect(identifier_a).to eql(identifier_b) end end context 'comparing with equal string' do let(:identifier_a) { described_class.new('//foo/bar/', type: :legacy) } let(:identifier_b) { '/foo/bar/' } it 'is ==' do expect(identifier_a).to eq(identifier_b.to_s) end it 'is not eql?' do expect(identifier_a).not_to eql(identifier_b.to_s) end end context 'comparing with different identifier' do let(:identifier_a) { described_class.new('//foo/bar/', type: :legacy) } let(:identifier_b) { described_class.new('/baz/qux//', type: :legacy) } it 'is not ==' do expect(identifier_a).not_to eq(identifier_b) end it 'is not eql?' do expect(identifier_a).not_to eql(identifier_b) end end context 'comparing with different string' do let(:identifier_a) { described_class.new('//foo/bar/', type: :legacy) } let(:identifier_b) { '/baz/qux/' } it 'is not equal' do expect(identifier_a).not_to eq(identifier_b) end it 'is not eql?' do expect(identifier_a).not_to eql(identifier_b) end end context 'comparing with something that is not an identifier' do let(:identifier_a) { described_class.new('//foo/bar/', type: :legacy) } let(:identifier_b) { :donkey } it 'is not equal' do expect(identifier_a).not_to eq(identifier_b) expect(identifier_a).not_to eql(identifier_b) end end end describe '#hash' do context 'equal identifiers' do let(:identifier_a) { described_class.new('//foo/bar/', type: :legacy) } let(:identifier_b) { described_class.new('/foo/bar//', type: :legacy) } it 'is the same' do expect(identifier_a.hash == identifier_b.hash).to eql(true) end end context 'different identifiers' do let(:identifier_a) { described_class.new('//foo/bar/', type: :legacy) } let(:identifier_b) { described_class.new('/monkey/', type: :legacy) } it 'is different' do expect(identifier_a.hash == identifier_b.hash).to eql(false) end end end describe '#=~' do let(:identifier) { described_class.new('/foo/bar') } subject { identifier =~ pat } context 'given a regex' do context 'matching regex' do let(:pat) { %r{\A/foo/bar} } it { is_expected.to eql(0) } end context 'non-matching regex' do let(:pat) { %r{\A/qux/monkey} } it { is_expected.to eql(nil) } end end context 'given a string' do context 'matching string' do let(:pat) { '/foo/*' } it { is_expected.to eql(0) } end context 'non-matching string' do let(:pat) { '/qux/*' } it { is_expected.to eql(nil) } end end end describe '#<=>' do let(:identifier) { described_class.new('/foo/bar') } it 'compares by string' do expect(identifier <=> '/foo/aarghh').to eql(1) expect(identifier <=> '/foo/bar').to eql(0) expect(identifier <=> '/foo/qux').to eql(-1) end end describe '#prefix' do let(:identifier) { described_class.new('/foo') } subject { identifier.prefix(prefix) } context 'prefix not ending nor starting with a slash' do let(:prefix) { 'asdf' } it 'raises an error' do expect { subject }.to raise_error( Nanoc::Identifier::InvalidPrefixError, 'Invalid prefix (does not start with a slash): "asdf"', ) end end context 'prefix ending with a slash' do let(:prefix) { 'asdf/' } it 'raises an error' do expect { subject }.to raise_error( Nanoc::Identifier::InvalidPrefixError, 'Invalid prefix (does not start with a slash): "asdf/"', ) end end context 'prefix ending and starting with a slash' do let(:prefix) { '/asdf/' } it 'returns a proper new identifier' do expect(subject).to be_a(Nanoc::Identifier) expect(subject.to_s).to eql('/asdf/foo') end end context 'prefix nstarting with a slash' do let(:prefix) { '/asdf' } it 'returns a proper new identifier' do expect(subject).to be_a(Nanoc::Identifier) expect(subject.to_s).to eql('/asdf/foo') end end end describe '#without_ext' do subject { identifier.without_ext } context 'legacy type' do let(:identifier) { described_class.new('/foo/', type: :legacy) } it 'raises an error' do expect { subject }.to raise_error(Nanoc::Identifier::UnsupportedLegacyOperationError) end end context 'identifier with no extension' do let(:identifier) { described_class.new('/foo') } it 'does nothing' do expect(subject).to eql('/foo') end end context 'identifier with extension' do let(:identifier) { described_class.new('/foo.md') } it 'removes the extension' do expect(subject).to eql('/foo') end end end describe '#ext' do subject { identifier.ext } context 'legacy type' do let(:identifier) { described_class.new('/foo/', type: :legacy) } it 'raises an error' do expect { subject }.to raise_error(Nanoc::Identifier::UnsupportedLegacyOperationError) end end context 'identifier with no extension' do let(:identifier) { described_class.new('/foo') } it { is_expected.to be_nil } end context 'identifier with extension' do let(:identifier) { described_class.new('/foo.md') } it { is_expected.to eql('md') } end end describe '#without_exts' do subject { identifier.without_exts } context 'legacy type' do let(:identifier) { described_class.new('/foo/', type: :legacy) } it 'raises an error' do expect { subject }.to raise_error(Nanoc::Identifier::UnsupportedLegacyOperationError) end end context 'identifier with no extension' do let(:identifier) { described_class.new('/foo') } it 'does nothing' do expect(subject).to eql('/foo') end end context 'identifier with one extension' do let(:identifier) { described_class.new('/foo.md') } it 'removes the extension' do expect(subject).to eql('/foo') end end context 'identifier with multiple extensions' do let(:identifier) { described_class.new('/foo.html.md') } it 'removes the extension' do expect(subject).to eql('/foo') end end end describe '#exts' do subject { identifier.exts } context 'legacy type' do let(:identifier) { described_class.new('/foo/', type: :legacy) } it 'raises an error' do expect { subject }.to raise_error(Nanoc::Identifier::UnsupportedLegacyOperationError) end end context 'identifier with no extension' do let(:identifier) { described_class.new('/foo') } it { is_expected.to be_empty } end context 'identifier with one extension' do let(:identifier) { described_class.new('/foo.md') } it { is_expected.to eql(['md']) } end context 'identifier with multiple extensions' do let(:identifier) { described_class.new('/foo.html.md') } it { is_expected.to eql(%w[html md]) } end end describe '#legacy?' do subject { identifier.legacy? } context 'legacy type' do let(:identifier) { described_class.new('/foo/', type: :legacy) } it { is_expected.to eql(true) } end context 'full type' do let(:identifier) { described_class.new('/foo/', type: :full) } it { is_expected.to eql(false) } end end describe '#full?' do subject { identifier.full? } context 'legacy type' do let(:identifier) { described_class.new('/foo/', type: :legacy) } it { is_expected.to eql(false) } end context 'full type' do let(:identifier) { described_class.new('/foo/', type: :full) } it { is_expected.to eql(true) } end end describe '#components' do subject { identifier.components } context 'no components' do let(:identifier) { described_class.new('/') } it { is_expected.to eql([]) } end context 'one component' do let(:identifier) { described_class.new('/foo.md') } it { is_expected.to eql(['foo.md']) } end context 'two components' do let(:identifier) { described_class.new('/foo/bar.md') } it { is_expected.to eql(['foo', 'bar.md']) } end end end nanoc-4.8.0/spec/nanoc/base/entities/pattern_spec.rb0000644000004100000410000000701013134141051022442 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::Int::Pattern do describe '.from' do it 'converts from string' do pattern = described_class.from('/foo/x[ab]z/bar.*') expect(pattern.match?('/foo/xaz/bar.html')).to eql(true) expect(pattern.match?('/foo/xyz/bar.html')).to eql(false) end it 'converts from regex' do pattern = described_class.from(%r{\A/foo/x[ab]z/bar\..*\z}) expect(pattern.match?('/foo/xaz/bar.html')).to eql(true) expect(pattern.match?('/foo/xyz/bar.html')).to eql(false) end it 'converts from pattern' do pattern = described_class.from('/foo/x[ab]z/bar.*') pattern = described_class.from(pattern) expect(pattern.match?('/foo/xaz/bar.html')).to eql(true) expect(pattern.match?('/foo/xyz/bar.html')).to eql(false) end it 'errors on other inputs' do expect { described_class.from(123) }.to raise_error(ArgumentError) end it 'errors with a proper error message on other inputs' do expect { described_class.from(nil) } .to raise_error(ArgumentError, 'Do not know how to convert `nil` into a Nanoc::Pattern') end end describe '#initialize' do it 'errors' do expect { described_class.new('/stuff') } .to raise_error(NotImplementedError) end end describe '#match?' do it 'errors' do expect { described_class.allocate.match?('/foo.md') } .to raise_error(NotImplementedError) end end describe '#captures' do it 'errors' do expect { described_class.allocate.captures('/foo.md') } .to raise_error(NotImplementedError) end end end describe Nanoc::Int::RegexpPattern do let(:pattern) { described_class.new(/the answer is (\d+)/) } describe '#match?' do it 'matches' do expect(pattern.match?('the answer is 42')).to eql(true) expect(pattern.match?('the answer is donkey')).to eql(false) end end describe '#captures' do it 'returns nil if it does not match' do expect(pattern.captures('the answer is donkey')).to be_nil end it 'returns array if it matches' do expect(pattern.captures('the answer is 42')).to eql(['42']) end end describe '#to_s' do subject { pattern.to_s } it 'returns the regex' do expect(subject).to eq('(?-mix:the answer is (\d+))') end end end describe Nanoc::Int::StringPattern do describe '#match?' do it 'matches simple strings' do pattern = described_class.new('d*key') expect(pattern.match?('donkey')).to eql(true) expect(pattern.match?('giraffe')).to eql(false) end it 'matches with pathname option' do pattern = described_class.new('/foo/*/bar/**/*.animal') expect(pattern.match?('/foo/x/bar/a/b/donkey.animal')).to eql(true) expect(pattern.match?('/foo/x/bar/donkey.animal')).to eql(true) expect(pattern.match?('/foo/x/railroad/donkey.animal')).to eql(false) end it 'matches with extglob option' do pattern = described_class.new('{b,gl}oat') expect(pattern.match?('boat')).to eql(true) expect(pattern.match?('gloat')).to eql(true) expect(pattern.match?('stoat')).to eql(false) end end describe '#captures' do it 'returns nil' do pattern = described_class.new('d*key') expect(pattern.captures('donkey')).to be_nil end end describe '#to_s' do let(:pattern) { described_class.new('/foo/*/bar/**/*.animal') } subject { pattern.to_s } it 'returns the regex' do expect(subject).to eq('/foo/*/bar/**/*.animal') end end end nanoc-4.8.0/spec/nanoc/base/entities/configuration_spec.rb0000644000004100000410000000645513134141051023650 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::Int::Configuration do let(:hash) { { foo: 'bar' } } let(:config) { described_class.new(hash: hash) } describe '#key?' do subject { config.key?(key) } context 'non-existent key' do let(:key) { :donkey } it { is_expected.not_to be } end context 'existent key' do let(:key) { :foo } it { is_expected.to be } end end describe '#with_defaults' do subject { config.with_defaults } context 'no env' do it 'has a default output_dir' do expect(subject[:output_dir]).to eql('output') end end context 'env' do let(:config) { described_class.new(hash: hash, env_name: 'giraffes') } it 'retains the env name' do expect(subject.env_name).to eql('giraffes') end end end describe '#output_dir' do subject { config.with_defaults.output_dir } context 'not explicitly defined' do let(:hash) { { foo: 'bar' } } it { is_expected.to eql('output') } end context 'explicitly defined, top-level' do let(:hash) { { foo: 'bar', output_dir: 'build' } } it { is_expected.to eql('build') } end end describe '#output_dirs' do subject { config.with_defaults.output_dirs } let(:hash) do { output_dir: 'output_toplevel', environments: { default: { output_dir: 'output_default', }, production: { output_dir: 'output_prod', }, staging: { output_dir: 'output_staging', }, other: {}, }, } end it 'contains both top-level and default output dir' do expect(subject).to include('output_toplevel') expect(subject).to include('output_default') end it 'does not contain nil' do expect(subject).not_to include(nil) end it 'contains all other output dirs' do expect(subject).to include('output_staging') expect(subject).to include('output_prod') end end describe '#merge' do let(:hash1) { { foo: { bar: 'baz', baz: ['biz'] } } } let(:hash2) { { foo: { bar: :boz, biz: 'buz' } } } let(:config1) { described_class.new(hash: hash1) } let(:config2) { described_class.new(hash: hash2) } subject { config1.merge(config2).to_h } it 'contains the recursive merge of both configurations' do expect(subject).to include(foo: { bar: :boz, baz: ['biz'], biz: 'buz' }) end end context 'with environments defined' do let(:hash) { { foo: 'bar', environments: { test: { foo: 'test-bar' }, default: { foo: 'default-bar' } } } } let(:config) { described_class.new(hash: hash, env_name: env_name).with_environment } subject { config } context 'with existing environment' do let(:env_name) { 'test' } it 'inherits options from given environment' do expect(subject[:foo]).to eq('test-bar') end end context 'with unknown environment' do let(:env_name) { 'wtf' } it 'does not inherits options from any environment' do expect(subject[:foo]).to eq('bar') end end context 'without given environment' do let(:env_name) { nil } it 'inherits options from default environment' do expect(subject[:foo]).to eq('default-bar') end end end end nanoc-4.8.0/spec/nanoc/base/entities/props_spec.rb0000644000004100000410000002632213134141051022137 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::Int::Props do let(:props) { described_class.new } let(:props_all) do described_class.new(raw_content: true, attributes: true, compiled_content: true, path: true) end describe '#inspect' do subject { props.inspect } context 'nothing active' do it { is_expected.to eql('Props(____)') } end context 'attributes active' do let(:props) { described_class.new(attributes: true) } it { is_expected.to eql('Props(_a__)') } end context 'attributes and compiled_content active' do let(:props) { described_class.new(attributes: true, compiled_content: true) } it { is_expected.to eql('Props(_ac_)') } end context 'compiled_content active' do let(:props) { described_class.new(compiled_content: true) } it { is_expected.to eql('Props(__c_)') } end end describe '#raw_content?' do subject { props.raw_content? } context 'nothing active' do it { is_expected.not_to be } end context 'raw_content active' do let(:props) { described_class.new(raw_content: true) } it { is_expected.to be } end context 'raw_content and compiled_content active' do let(:props) { described_class.new(raw_content: true, compiled_content: true) } it { is_expected.to be } end context 'compiled_content active' do let(:props) { described_class.new(compiled_content: true) } it { is_expected.not_to be } end context 'all active' do let(:props) { described_class.new(raw_content: true, attributes: true, compiled_content: true, path: true) } it { is_expected.to be } end context 'raw_content is empty list' do let(:props) { described_class.new(raw_content: []) } it { is_expected.not_to be } end context 'raw_content is non-empty list' do let(:props) { described_class.new(raw_content: ['/asdf.*']) } it { is_expected.to be } end end describe '#attributes?' do subject { props.attributes? } context 'nothing active' do it { is_expected.not_to be } end context 'attributes active' do let(:props) { described_class.new(attributes: true) } it { is_expected.to be } end context 'attributes and compiled_content active' do let(:props) { described_class.new(attributes: true, compiled_content: true) } it { is_expected.to be } end context 'compiled_content active' do let(:props) { described_class.new(compiled_content: true) } it { is_expected.not_to be } end context 'all active' do let(:props) { described_class.new(raw_content: true, attributes: true, compiled_content: true, path: true) } it { is_expected.to be } end context 'attributes is empty list' do let(:props) { described_class.new(attributes: []) } it { is_expected.not_to be } end context 'attributes is non-empty list' do let(:props) { described_class.new(attributes: [:donkey]) } it { is_expected.to be } end end describe '#compiled_content?' do # … end describe '#path?' do # … end describe '#merge' do subject { props.merge(other_props).active } context 'nothing + nothing' do let(:props) { described_class.new } let(:other_props) { described_class.new } it { is_expected.to eql(Set.new) } end context 'nothing + some' do let(:props) { described_class.new } let(:other_props) { described_class.new(raw_content: true) } it { is_expected.to eql(Set.new([:raw_content])) } end context 'nothing + all' do let(:props) { described_class.new } let(:other_props) { props_all } it { is_expected.to eql(Set.new(%i[raw_content attributes compiled_content path])) } end context 'some + nothing' do let(:props) { described_class.new(compiled_content: true) } let(:other_props) { described_class.new } it { is_expected.to eql(Set.new([:compiled_content])) } end context 'some + others' do let(:props) { described_class.new(compiled_content: true) } let(:other_props) { described_class.new(raw_content: true) } it { is_expected.to eql(Set.new(%i[raw_content compiled_content])) } end context 'some + all' do let(:props) { described_class.new(compiled_content: true) } let(:other_props) { props_all } it { is_expected.to eql(Set.new(%i[raw_content attributes compiled_content path])) } end context 'all + nothing' do let(:props) { props_all } let(:other_props) { described_class.new } it { is_expected.to eql(Set.new(%i[raw_content attributes compiled_content path])) } end context 'some + all' do let(:props) { props_all } let(:other_props) { described_class.new(compiled_content: true) } it { is_expected.to eql(Set.new(%i[raw_content attributes compiled_content path])) } end context 'all + all' do let(:props) { props_all } let(:other_props) { props_all } it { is_expected.to eql(Set.new(%i[raw_content attributes compiled_content path])) } end end describe '#merge_attributes' do let(:props_attrs_true) do described_class.new(attributes: true) end let(:props_attrs_false) do described_class.new(attributes: false) end let(:props_attrs_list_a) do described_class.new(attributes: %i[donkey giraffe]) end let(:props_attrs_list_b) do described_class.new(attributes: %i[giraffe zebra]) end subject { props.merge(other_props).attributes } context 'false + false' do let(:props) { props_attrs_false } let(:other_props) { props_attrs_false } it { is_expected.to be(false) } end context 'false + true' do let(:props) { props_attrs_false } let(:other_props) { props_attrs_true } it { is_expected.to be(true) } end context 'false + list' do let(:props) { props_attrs_false } let(:other_props) { props_attrs_list_a } it { is_expected.to be_a(Set) } it { is_expected.to match_array(%i[donkey giraffe]) } end context 'true + false' do let(:props) { props_attrs_true } let(:other_props) { props_attrs_false } it { is_expected.to be(true) } end context 'true + true' do let(:props) { props_attrs_true } let(:other_props) { props_attrs_true } it { is_expected.to be(true) } end context 'true + list' do let(:props) { props_attrs_true } let(:other_props) { props_attrs_list_a } it { is_expected.to be(true) } end context 'list + false' do let(:props) { props_attrs_list_a } let(:other_props) { props_attrs_false } it { is_expected.to be_a(Set) } it { is_expected.to match_array(%i[donkey giraffe]) } end context 'list + true' do let(:props) { props_attrs_list_a } let(:other_props) { props_attrs_true } it { is_expected.to be(true) } end context 'list + list' do let(:props) { props_attrs_list_a } let(:other_props) { props_attrs_list_b } it { is_expected.to be_a(Set) } it { is_expected.to match_array(%i[donkey giraffe zebra]) } end end describe '#merge_raw_content' do let(:props_raw_content_true) do described_class.new(raw_content: true) end let(:props_raw_content_false) do described_class.new(raw_content: false) end let(:props_raw_content_list_a) do described_class.new(raw_content: %w[donkey giraffe]) end let(:props_raw_content_list_b) do described_class.new(raw_content: %w[giraffe zebra]) end subject { props.merge(other_props).raw_content } context 'false + false' do let(:props) { props_raw_content_false } let(:other_props) { props_raw_content_false } it { is_expected.to be(false) } end context 'false + true' do let(:props) { props_raw_content_false } let(:other_props) { props_raw_content_true } it { is_expected.to be(true) } end context 'false + list' do let(:props) { props_raw_content_false } let(:other_props) { props_raw_content_list_a } it { is_expected.to be_a(Set) } it { is_expected.to match_array(%w[donkey giraffe]) } end context 'true + false' do let(:props) { props_raw_content_true } let(:other_props) { props_raw_content_false } it { is_expected.to be(true) } end context 'true + true' do let(:props) { props_raw_content_true } let(:other_props) { props_raw_content_true } it { is_expected.to be(true) } end context 'true + list' do let(:props) { props_raw_content_true } let(:other_props) { props_raw_content_list_a } it { is_expected.to be(true) } end context 'list + false' do let(:props) { props_raw_content_list_a } let(:other_props) { props_raw_content_false } it { is_expected.to be_a(Set) } it { is_expected.to match_array(%w[donkey giraffe]) } end context 'list + true' do let(:props) { props_raw_content_list_a } let(:other_props) { props_raw_content_true } it { is_expected.to be(true) } end context 'list + list' do let(:props) { props_raw_content_list_a } let(:other_props) { props_raw_content_list_b } it { is_expected.to be_a(Set) } it { is_expected.to match_array(%w[donkey giraffe zebra]) } end end describe '#active' do subject { props.active } context 'nothing active' do let(:props) { described_class.new } it { is_expected.to eql(Set.new) } end context 'raw_content active' do let(:props) { described_class.new(raw_content: true) } it { is_expected.to eql(Set.new([:raw_content])) } end context 'attributes active' do let(:props) { described_class.new(attributes: true) } it { is_expected.to eql(Set.new([:attributes])) } end context 'compiled_content active' do let(:props) { described_class.new(compiled_content: true) } it { is_expected.to eql(Set.new([:compiled_content])) } end context 'path active' do let(:props) { described_class.new(path: true) } it { is_expected.to eql(Set.new([:path])) } end context 'attributes and compiled_content active' do let(:props) { described_class.new(attributes: true, compiled_content: true) } it { is_expected.to eql(Set.new(%i[attributes compiled_content])) } end context 'all active' do let(:props) { described_class.new(raw_content: true, attributes: true, compiled_content: true, path: true) } it { is_expected.to eql(Set.new(%i[raw_content attributes compiled_content path])) } end end describe '#to_h' do subject { props.to_h } context 'nothing' do let(:props) { described_class.new } it { is_expected.to eql(raw_content: false, attributes: false, compiled_content: false, path: false) } end context 'some' do let(:props) { described_class.new(attributes: true, compiled_content: true) } it { is_expected.to eql(raw_content: false, attributes: true, compiled_content: true, path: false) } end context 'all' do let(:props) { props_all } it { is_expected.to eql(raw_content: true, attributes: true, compiled_content: true, path: true) } end end end nanoc-4.8.0/spec/nanoc/base/entities/processing_actions/0000755000004100000410000000000013134141051023324 5ustar www-datawww-datananoc-4.8.0/spec/nanoc/base/entities/processing_actions/layout_spec.rb0000644000004100000410000000114013134141051026174 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::Int::ProcessingActions::Layout do let(:action) { described_class.new('/foo.erb', awesome: true) } describe '#serialize' do subject { action.serialize } it { is_expected.to eql([:layout, '/foo.erb', 'sJYzLjHGo1e4ytuDfnOLkqrt9QE=']) } end describe '#to_s' do subject { action.to_s } it { is_expected.to eql('layout "/foo.erb", {:awesome=>true}') } end describe '#inspect' do subject { action.inspect } it { is_expected.to eql('') } end describe '#update' do context 'with nothing' do subject { action.update } its(:snapshot_names) { is_expected.to eql([:before_layout]) } its(:paths) { is_expected.to eql(['/foo.md']) } end context 'with snapshot name' do subject { action.update(snapshot_names: [:zebra]) } its(:snapshot_names) { is_expected.to eql(%i[before_layout zebra]) } its(:paths) { is_expected.to eql(['/foo.md']) } end context 'with paths' do subject { action.update(paths: ['/donkey.md', '/giraffe.md']) } its(:snapshot_names) { is_expected.to eql([:before_layout]) } its(:paths) { is_expected.to eql(['/foo.md', '/donkey.md', '/giraffe.md']) } end end end nanoc-4.8.0/spec/nanoc/base/entities/code_snippet_spec.rb0000644000004100000410000000240513134141051023444 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::Int::CodeSnippet do subject(:code_snippet) { described_class.new(data, 'lib/foo.rb') } describe '#load' do subject { code_snippet.load } describe 'calling #include' do let(:data) do <<~EOS module CodeSnippetSpecHelper1 def fe345b48e4 "fe345b48e4" end end include CodeSnippetSpecHelper1 EOS end it 'makes helper functions available in contexts' do expect { subject } .to change { [Nanoc::Int::Context.new({}).respond_to?(:fe345b48e4), Complex.respond_to?(:fe345b48e4)] } .from([false, false]) .to([true, true]) end end describe 'calling #use_helper' do let(:data) do <<~EOS module CodeSnippetSpecHelper2 def e0f0c30b5e "e0f0c30b5e" end end use_helper CodeSnippetSpecHelper2 EOS end it 'makes helper functions available everywhere' do expect { subject } .to change { [Nanoc::Int::Context.new({}).respond_to?(:e0f0c30b5e), Complex.respond_to?(:e0f0c30b5e)] } .from([false, false]) .to([true, false]) end end end end nanoc-4.8.0/spec/nanoc/base/entities/lazy_value_spec.rb0000644000004100000410000000455513134141051023153 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::Int::LazyValue do describe '#value' do let(:value_arg) { 'Hello world'.dup } let(:lazy_value) { described_class.new(value_arg) } subject { lazy_value.value } context 'object' do it { is_expected.to equal(value_arg) } end context 'proc' do it 'does not call the proc immediately' do expect(value_arg).not_to receive(:call) lazy_value end it 'returns proc return value' do expect(value_arg).to receive(:call).once.and_return('Hello proc') expect(subject).to eql('Hello proc') end it 'only calls the proc once' do expect(value_arg).to receive(:call).once.and_return('Hello proc') expect(subject).to eql('Hello proc') expect(subject).to eql('Hello proc') end end end describe '#map' do let(:value_arg) { -> { 'Hello world' } } let(:lazy_value) { described_class.new(value_arg) } subject { lazy_value.map(&:upcase) } it 'does not call the proc immediately' do expect(value_arg).not_to receive(:call) subject end it 'returns proc return value' do expect(value_arg).to receive(:call).once.and_return('Hello proc') expect(subject.value).to eql('HELLO PROC') end it 'only calls the proc once' do expect(value_arg).to receive(:call).once.and_return('Hello proc') expect(subject.value).to eql('HELLO PROC') expect(subject.value).to eql('HELLO PROC') end end describe '#freeze' do let(:value_arg) { 'Hello world' } subject { described_class.new(value_arg) } before do subject.freeze end context 'object' do it 'returns value' do expect(subject.value).to equal(value_arg) end it 'freezes value' do expect(subject.value).to be_frozen end end context 'proc' do call_count = 0 let(:value_arg) do proc do call_count += 1 'Hello proc' end end before do call_count = 0 subject.freeze end it 'does not call the proc immediately' do expect(call_count).to eql(0) end it 'returns proc return value' do expect(subject.value).to eq('Hello proc') end it 'freezes upon access' do expect(subject.value).to be_frozen end end end end nanoc-4.8.0/spec/nanoc/base/entities/item_spec.rb0000644000004100000410000000043713134141051021731 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::Int::Item do it_behaves_like 'a document' describe '#reference' do let(:item) { described_class.new('hi', {}, '/foo.md') } it 'has the proper reference' do expect(item.reference).to eql('item:/foo.md') end end end nanoc-4.8.0/spec/nanoc/base/feature_spec.rb0000644000004100000410000000552313134141051020603 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::Feature do describe '.enabled?' do subject { described_class.enabled?(feature_name) } let(:feature_name) { 'magic' } before do Nanoc::Feature.reset_caches ENV['NANOC_FEATURES'] = String.new end context 'not set' do it { is_expected.not_to be } end context 'set to list not including feature' do before { ENV['NANOC_FEATURES'] = 'foo,bar' } it { is_expected.not_to be } end context 'set to all' do before { ENV['NANOC_FEATURES'] = 'all' } it { is_expected.to be } end context 'set to list including feature' do before { ENV['NANOC_FEATURES'] = 'foo,magic,bar' } it { is_expected.to be } end end describe '.enable' do subject do described_class.enable(feature_name) do Nanoc::Feature.enabled?(feature_name) end end let(:feature_name) { 'magic' } before do Nanoc::Feature.reset_caches ENV['NANOC_FEATURES'] = String.new end context 'not set' do it { is_expected.to be } it 'unsets afterwards' do expect(Nanoc::Feature.enabled?(feature_name)).not_to be end end context 'set to list not including feature' do before { ENV['NANOC_FEATURES'] = 'foo,bar' } it { is_expected.to be } it 'unsets afterwards' do expect(Nanoc::Feature.enabled?(feature_name)).not_to be end end context 'set to all' do before { ENV['NANOC_FEATURES'] = 'all' } it { is_expected.to be } end context 'set to list including feature' do before { ENV['NANOC_FEATURES'] = 'foo,magic,bar' } it { is_expected.to be } end end describe '.all_outdated' do it 'refuses outdated features' do # If this spec fails, there are features marked as experimental in the previous minor or major # release, but not in the current one. Either remove the feature, or mark it as experimental # in the current release. expect(Nanoc::Feature.all_outdated).to be_empty end describe 'fake outdated features' do before { Nanoc::Feature.define('abc', version: '4.2.x') } after { Nanoc::Feature.undefine('abc') } it 'detects outdated features' do expect(Nanoc::Feature.all_outdated).to eq(['abc']) end end end describe '.define and .undefine' do let(:feature_name) { 'testing123' } after { Nanoc::Feature.undefine(feature_name) if defined?(Nanoc::Feature::TESTING123) } it 'can define' do Nanoc::Feature.define(feature_name, version: '4.3.x') expect(Nanoc::Feature::TESTING123).not_to be_nil end it 'can undefine' do Nanoc::Feature.define(feature_name, version: '4.3.x') Nanoc::Feature.undefine(feature_name) expect { Nanoc::Feature::TESTING123 }.to raise_error(NameError) end end end nanoc-4.8.0/spec/nanoc/base/checksummer_spec.rb0000644000004100000410000003401413134141051021453 0ustar www-datawww-data# frozen_string_literal: true require 'tempfile' describe Nanoc::Int::Checksummer::VerboseDigest do let(:digest) { described_class.new } it 'concatenates' do digest.update('foo') digest.update('bar') expect(digest.to_s).to eql('foobar') end end describe Nanoc::Int::Checksummer::CompactDigest do let(:digest) { described_class.new } it 'uses SHA1 and Base64' do digest.update('foo') digest.update('bar') expect(digest.to_s).to eql(Digest::SHA1.base64digest('foobar')) end end describe Nanoc::Int::Checksummer do subject { described_class.calc(obj, Nanoc::Int::Checksummer::VerboseDigest) } describe '.calc_for_each_attribute_of' do let(:obj) { Nanoc::Int::Item.new('asdf', { 'foo' => 'bar' }, '/foo.md') } context 'compact' do subject do described_class.calc_for_each_attribute_of(obj) end it { is_expected.to have_key(:foo) } end context 'verbose' do subject do described_class.calc_for_each_attribute_of(obj, Nanoc::Int::Checksummer::VerboseDigest) end it { is_expected.to eq(foo: 'String') } end end context 'String' do let(:obj) { 'hello' } it { is_expected.to eql('String') } end context 'Symbol' do let(:obj) { :hello } it { is_expected.to eql('Symbol') } end context 'nil' do let(:obj) { nil } it { is_expected.to eql('NilClass<>') } end context 'true' do let(:obj) { true } it { is_expected.to eql('TrueClass<>') } end context 'false' do let(:obj) { false } it { is_expected.to eql('FalseClass<>') } end context 'Array' do let(:obj) { %w[hello goodbye] } it { is_expected.to eql('Array,String,>') } context 'different order' do let(:obj) { %w[goodbye hello] } it { is_expected.to eql('Array,String,>') } end context 'recursive' do let(:obj) { [].tap { |arr| arr << ['hello', arr] } } it { is_expected.to eql('Array,Array,>,>') } end context 'non-serializable' do let(:obj) { [-> {}] } it { is_expected.to match(/\AArray>,>\z/) } end end context 'Hash' do let(:obj) { { 'a' => 'foo', 'b' => 'bar' } } it { is_expected.to eql('Hash=String,String=String,>') } context 'different order' do let(:obj) { { 'b' => 'bar', 'a' => 'foo' } } it { is_expected.to eql('Hash=String,String=String,>') } end context 'non-serializable' do let(:obj) { { 'a' => -> {} } } it { is_expected.to match(/\AHash=Proc<#>,>\z/) } end context 'recursive values' do let(:obj) { {}.tap { |hash| hash['a'] = hash } } it { is_expected.to eql('Hash=Hash,>') } end context 'recursive keys' do let(:obj) { {}.tap { |hash| hash[hash] = 'hello' } } it { is_expected.to eql('Hash=String,>') } end end context 'Pathname' do let(:obj) { ::Pathname.new(filename) } let(:filename) { '/tmp/whatever' } let(:mtime) { 200 } let(:data) { 'stuffs' } before do FileUtils.mkdir_p(File.dirname(filename)) File.write(filename, data) File.utime(mtime, mtime, filename) end it { is_expected.to eql('Pathname<6-200>') } context 'does not exist' do before do FileUtils.rm_rf(filename) end it { is_expected.to eql('Pathname') } end context 'different data' do let(:data) { 'other stuffs :o' } it { is_expected.to eql('Pathname<15-200>') } end end context 'Time' do let(:obj) { Time.at(111_223) } it { is_expected.to eql('Time<111223>') } end context 'Float' do let(:obj) { 3.14 } it { is_expected.to eql('Float<3.14>') } end context 'Fixnum/Integer' do let(:obj) { 3 } it { is_expected.to match(/\A(Integer|Fixnum)<3>\z/) } end context 'Nanoc::Identifier' do let(:obj) { Nanoc::Identifier.new('/foo.md') } it { is_expected.to eql('Nanoc::Identifier>') } end context 'Nanoc::RuleDSL::RulesCollection' do let(:obj) do Nanoc::RuleDSL::RulesCollection.new.tap { |rc| rc.data = data } end let(:data) { 'STUFF!' } it { is_expected.to eql('Nanoc::RuleDSL::RulesCollection>') } end context 'Nanoc::Int::CodeSnippet' do let(:obj) { Nanoc::Int::CodeSnippet.new('asdf', '/bob.rb') } it { is_expected.to eql('Nanoc::Int::CodeSnippet>') } end context 'Nanoc::Int::Configuration' do let(:obj) { Nanoc::Int::Configuration.new(hash: { 'foo' => 'bar' }) } it { is_expected.to eql('Nanoc::Int::Configuration=String,>') } end context 'Nanoc::Int::Item' do let(:obj) { Nanoc::Int::Item.new('asdf', { 'foo' => 'bar' }, '/foo.md') } it { is_expected.to eql('Nanoc::Int::Item>,attributes=Hash=String,>,identifier=Nanoc::Identifier>>') } context 'binary' do let(:filename) { File.expand_path('foo.dat') } let(:content) { Nanoc::Int::BinaryContent.new(filename) } let(:obj) { Nanoc::Int::Item.new(content, { 'foo' => 'bar' }, '/foo.md') } let(:mtime) { 200 } let(:data) { 'stuffs' } before do File.write(content.filename, data) File.utime(mtime, mtime, content.filename) end it { is_expected.to eql('Nanoc::Int::Item>,attributes=Hash=String,>,identifier=Nanoc::Identifier>>') } end context 'recursive attributes' do before do obj.attributes[:foo] = obj end it { is_expected.to eql('Nanoc::Int::Item>,attributes=Hash=Nanoc::Int::Item,>,identifier=Nanoc::Identifier>>') } end context 'with checksum' do let(:obj) { Nanoc::Int::Item.new('asdf', { 'foo' => 'bar' }, '/foo.md', checksum_data: 'abcdef') } it { is_expected.to eql('Nanoc::Int::Item') } end context 'with content checksum' do let(:obj) { Nanoc::Int::Item.new('asdf', { 'foo' => 'bar' }, '/foo.md', content_checksum_data: 'con-cs') } it { is_expected.to eql('Nanoc::Int::Item=String,>,identifier=Nanoc::Identifier>>') } end context 'with attributes checksum' do let(:obj) { Nanoc::Int::Item.new('asdf', { 'foo' => 'bar' }, '/foo.md', attributes_checksum_data: 'attr-cs') } it { is_expected.to eql('Nanoc::Int::Item>,attributes_checksum_data=attr-cs,identifier=Nanoc::Identifier>>') } end end context 'Nanoc::Int::Layout' do let(:obj) { Nanoc::Int::Layout.new('asdf', { 'foo' => 'bar' }, '/foo.md') } it { is_expected.to eql('Nanoc::Int::Layout>,attributes=Hash=String,>,identifier=Nanoc::Identifier>>') } context 'recursive attributes' do before do obj.attributes[:foo] = obj end it { is_expected.to eql('Nanoc::Int::Layout>,attributes=Hash=Nanoc::Int::Layout,>,identifier=Nanoc::Identifier>>') } end context 'with checksum' do let(:obj) { Nanoc::Int::Layout.new('asdf', { 'foo' => 'bar' }, '/foo.md', checksum_data: 'abcdef') } it { is_expected.to eql('Nanoc::Int::Layout') } end end context 'Nanoc::ItemWithRepsView' do let(:obj) { Nanoc::ItemWithRepsView.new(item, nil) } let(:item) { Nanoc::Int::Item.new('asdf', {}, '/foo.md') } it { is_expected.to eql('Nanoc::ItemWithRepsView>,attributes=Hash<>,identifier=Nanoc::Identifier>>>') } end context 'Nanoc::Int::ItemRep' do let(:obj) { Nanoc::Int::ItemRep.new(item, :pdf) } let(:item) { Nanoc::Int::Item.new('asdf', {}, '/foo.md') } it { is_expected.to eql('Nanoc::Int::ItemRep>,attributes=Hash<>,identifier=Nanoc::Identifier>>,name=Symbol>') } end context 'Nanoc::ItemRepView' do let(:obj) { Nanoc::ItemRepView.new(rep, :_unused_context) } let(:rep) { Nanoc::Int::ItemRep.new(item, :pdf) } let(:item) { Nanoc::Int::Item.new('asdf', {}, '/foo.md') } it { is_expected.to eql('Nanoc::ItemRepView>,attributes=Hash<>,identifier=Nanoc::Identifier>>,name=Symbol>>') } end context 'Nanoc::ItemWithoutRepsView' do let(:obj) { Nanoc::ItemWithoutRepsView.new(item, nil) } let(:item) { Nanoc::Int::Item.new('asdf', {}, '/foo.md') } it { is_expected.to eql('Nanoc::ItemWithoutRepsView>,attributes=Hash<>,identifier=Nanoc::Identifier>>>') } end context 'Nanoc::LayoutView' do let(:obj) { Nanoc::LayoutView.new(layout, nil) } let(:layout) { Nanoc::Int::Layout.new('asdf', {}, '/foo.md') } it { is_expected.to eql('Nanoc::LayoutView>,attributes=Hash<>,identifier=Nanoc::Identifier>>>') } end context 'Nanoc::ConfigView' do let(:obj) { Nanoc::ConfigView.new(config, nil) } let(:config) { Nanoc::Int::Configuration.new(hash: { 'foo' => 'bar' }) } it { is_expected.to eql('Nanoc::ConfigView=String,>>') } end context 'Nanoc::ItemCollectionWithRepsView' do let(:obj) { Nanoc::ItemCollectionWithRepsView.new(wrapped, nil) } let(:config) { Nanoc::Int::Configuration.new(hash: { 'foo' => 'bar' }) } let(:wrapped) do Nanoc::Int::ItemCollection.new( config, [ Nanoc::Int::Item.new('foo', {}, '/foo.md'), Nanoc::Int::Item.new('bar', {}, '/foo.md'), ], ) end it { is_expected.to eql('Nanoc::ItemCollectionWithRepsView>,attributes=Hash<>,identifier=Nanoc::Identifier>>,Nanoc::Int::Item>,attributes=Hash<>,identifier=Nanoc::Identifier>>,>>') } end context 'Nanoc::ItemCollectionWithoutRepsView' do let(:obj) { Nanoc::ItemCollectionWithoutRepsView.new(wrapped, nil) } let(:config) { Nanoc::Int::Configuration.new(hash: { 'foo' => 'bar' }) } let(:wrapped) do Nanoc::Int::ItemCollection.new( config, [ Nanoc::Int::Item.new('foo', {}, '/foo.md'), Nanoc::Int::Item.new('bar', {}, '/foo.md'), ], ) end it { is_expected.to eql('Nanoc::ItemCollectionWithoutRepsView>,attributes=Hash<>,identifier=Nanoc::Identifier>>,Nanoc::Int::Item>,attributes=Hash<>,identifier=Nanoc::Identifier>>,>>') } end context 'Nanoc::RuleDSL::RuleContext' do let(:obj) { Nanoc::RuleDSL::RuleContext.new(rep: rep, site: site, executor: executor, view_context: view_context) } let(:rep) { Nanoc::Int::ItemRep.new(item, :pdf) } let(:item) { Nanoc::Int::Item.new('stuff', {}, '/stuff.md') } let(:site) do Nanoc::Int::Site.new( config: config, code_snippets: code_snippets, data_source: Nanoc::Int::InMemDataSource.new(items, layouts), ) end let(:config) { Nanoc::Int::Configuration.new(hash: { 'foo' => 'bar' }) } let(:code_snippets) { [Nanoc::Int::CodeSnippet.new('asdf', '/bob.rb')] } let(:items) { [item] } let(:layouts) { [Nanoc::Int::Layout.new('asdf', {}, '/foo.md')] } let(:executor) { :_unused_ } let(:view_context) { :_unused_ } let(:expected_item_checksum) { 'Nanoc::Int::Item>,attributes=Hash<>,identifier=Nanoc::Identifier>>' } let(:expected_item_rep_checksum) { 'Nanoc::Int::ItemRep>' } let(:expected_layout_checksum) { 'Nanoc::Int::Layout>,attributes=Hash<>,identifier=Nanoc::Identifier>>' } let(:expected_config_checksum) { 'Nanoc::Int::Configuration=String,>' } let(:expected_checksum) do [ 'Nanoc::RuleDSL::RuleContext<', 'item=', 'Nanoc::ItemWithoutRepsView<' + expected_item_checksum + '>', ',rep=', 'Nanoc::ItemRepView<' + expected_item_rep_checksum + '>', ',items=', 'Nanoc::ItemCollectionWithoutRepsView>', ',layouts=', 'Nanoc::LayoutCollectionView>', ',config=', 'Nanoc::ConfigView<' + expected_config_checksum + '>', '>', ].join('') end it { is_expected.to eql(expected_checksum) } end context 'Nanoc::Int::Context' do let(:obj) { Nanoc::Int::Context.new(foo: 123) } it { is_expected.to match(/\ANanoc::Int::Context<@foo=(Fixnum|Integer)<123>,>\z/) } end context 'Sass::Importers::Filesystem' do let(:obj) { Sass::Importers::Filesystem.new('/foo') } before { require 'sass' } it { is_expected.to eql('Sass::Importers::Filesystem') } end context 'other marshal-able classes' do let(:obj) { klass.new('hello') } let(:klass) do Class.new do def initialize(a) @a = a end end end it { is_expected.to match(/\A#<.*>\z/) } end context 'other non-marshal-able classes' do let(:obj) { proc {} } it { is_expected.to match(/\AProc<#>\z/) } end end nanoc-4.8.0/spec/nanoc/base/filter_spec.rb0000644000004100000410000001020013134141051020421 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::Filter do describe '.define' do context 'simple filter' do let(:filter_name) { :b5355bbb4d772b9853d21be57da614dba521dbbb } let(:filter_class) { Nanoc::Filter.named(filter_name) } before do Nanoc::Filter.define(filter_name) do |content, _params| content.upcase end end it 'defines a filter' do expect(filter_class).not_to be_nil end it 'defines a callable filter' do expect(filter_class.new.run('foo', {})).to eql('FOO') end end context 'filter that accesses assigns' do let(:filter_name) { :d7ed105d460e99a3d38f46af023d9490c140fdd9 } let(:filter_class) { Nanoc::Filter.named(filter_name) } let(:filter) { filter_class.new(assigns) } let(:assigns) { { animal: 'Giraffe' } } before do Nanoc::Filter.define(filter_name) do |_content, _params| @animal end end it 'can access assigns' do expect(filter.setup_and_run(:__irrelevant__, {})).to eq('Giraffe') end end end describe '.always_outdated? + .always_outdated' do context 'not always outdated' do let(:filter_class) do Class.new(Nanoc::Filter) do identifier :bea22a356b6b031cea1e615087179803818c6a53 def run(content, _params) content.upcase end end end it 'is not always outdated' do expect(filter_class).not_to be_always_outdated end end context 'always outdated' do let(:filter_class) do Class.new(Nanoc::Filter) do identifier :d7413fa71223e5e69b03a0abfa25806e07e14f3a always_outdated def run(content, _params) content.upcase end end end it 'is always outdated' do expect(filter_class).to be_always_outdated end end end describe '#depend_on' do subject { filter.depend_on(item_views) } let(:filter) { Nanoc::Filters::ERB.new(assigns) } let(:item_views) { [item_view] } let(:item) { Nanoc::Int::Item.new('foo', {}, '/stuff.md') } let(:item_view) { Nanoc::ItemWithRepsView.new(item, view_context) } let(:rep) { Nanoc::Int::ItemRep.new(item, :default) } let(:view_context) do Nanoc::ViewContext.new( reps: reps, items: double(:items), dependency_tracker: dependency_tracker, compilation_context: double(:compilation_context), snapshot_repo: double(:snapshot_repo), ) end let(:dependency_tracker) { double(:dependency_tracker) } let(:reps) { Nanoc::Int::ItemRepRepo.new } let(:assigns) do { item: item_view, } end before do reps << rep expect(dependency_tracker).to receive(:bounce).with(item, compiled_content: true).at_least(:once) end context 'rep is compiled' do before do rep.compiled = true end example do expect { subject }.not_to yield_from_fiber(an_instance_of(Nanoc::Int::Errors::UnmetDependency)) end end context 'rep is not compiled' do example do fiber = Fiber.new { subject } # resume 1 res = fiber.resume expect(res).to be_a(Nanoc::Int::Errors::UnmetDependency) expect(res.rep).to eql(rep) # resume 2 expect(fiber.resume).not_to be_a(Nanoc::Int::Errors::UnmetDependency) end end context 'multiple reps exist' do let(:other_rep) { Nanoc::Int::ItemRep.new(item, :default) } before do reps << other_rep rep.compiled = false other_rep.compiled = false end it 'yields an unmet dependency error twice' do fiber = Fiber.new { subject } # resume 1 res = fiber.resume expect(res).to be_a(Nanoc::Int::Errors::UnmetDependency) expect(res.rep).to eql(rep) # resume 2 res = fiber.resume expect(res).to be_a(Nanoc::Int::Errors::UnmetDependency) expect(res.rep).to eql(other_rep) # resume 3 expect(fiber.resume).not_to be_a(Nanoc::Int::Errors::UnmetDependency) end end end end nanoc-4.8.0/spec/nanoc/base/item_rep_writer_spec.rb0000644000004100000410000000767613134141051022363 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::Int::ItemRepWriter do describe '#write' do let(:raw_path) { 'output/blah.dat' } let(:item) { Nanoc::Int::Item.new(orig_content, {}, '/') } let(:item_rep) do Nanoc::Int::ItemRep.new(item, :default).tap do |ir| ir.raw_paths = raw_paths end end let(:snapshot_contents) do { last: Nanoc::Int::TextualContent.new('last content'), donkey: Nanoc::Int::TextualContent.new('donkey content'), } end let(:snapshot_name) { :donkey } let(:raw_paths) do { snapshot_name => [raw_path] } end let(:snapshot_repo) { Nanoc::Int::SnapshotRepo.new } let(:written_paths) { [] } subject { described_class.new.write(item_rep, snapshot_repo, snapshot_name, written_paths) } before do expect(File.directory?('output')).to be_falsy snapshot_contents.each_pair do |key, value| snapshot_repo.set(item_rep, key, value) end end context 'binary item rep' do let(:orig_content) { Nanoc::Int::BinaryContent.new(File.expand_path('foo.dat')) } let(:snapshot_contents) do { last: Nanoc::Int::BinaryContent.new(File.expand_path('input-last.dat')), donkey: Nanoc::Int::BinaryContent.new(File.expand_path('input-donkey.dat')), } end before do File.write(snapshot_contents[:last].filename, 'binary last stuff') File.write(snapshot_contents[:donkey].filename, 'binary donkey stuff') end it 'copies' do expect(Nanoc::Int::NotificationCenter).to receive(:post) .with(:will_write_rep, item_rep, 'output/blah.dat') expect(Nanoc::Int::NotificationCenter).to receive(:post) .with(:rep_written, item_rep, true, 'output/blah.dat', true, true) subject expect(File.read('output/blah.dat')).to eql('binary donkey stuff') end context 'output file already exists' do let(:old_mtime) { Time.at((Time.now - 600).to_i) } before do FileUtils.mkdir_p('output') File.write('output/blah.dat', old_content) FileUtils.touch('output/blah.dat', mtime: old_mtime) end context 'file is identical' do let(:old_content) { 'binary donkey stuff' } it 'keeps mtime' do subject expect(File.mtime('output/blah.dat')).to eql(old_mtime) end end context 'file is not identical' do let(:old_content) { 'other binary donkey stuff' } it 'updates mtime' do subject expect(File.mtime('output/blah.dat')).to be > (Time.now - 1) end end end end context 'textual item rep' do let(:orig_content) { Nanoc::Int::TextualContent.new('Hallo Welt') } it 'writes' do expect(Nanoc::Int::NotificationCenter).to receive(:post) .with(:will_write_rep, item_rep, 'output/blah.dat') expect(Nanoc::Int::NotificationCenter).to receive(:post) .with(:rep_written, item_rep, false, 'output/blah.dat', true, true) subject expect(File.read('output/blah.dat')).to eql('donkey content') end context 'output file already exists' do let(:old_mtime) { Time.at((Time.now - 600).to_i) } before do FileUtils.mkdir_p('output') File.write('output/blah.dat', old_content) FileUtils.touch('output/blah.dat', mtime: old_mtime) end context 'file is identical' do let(:old_content) { 'donkey content' } it 'keeps mtime' do subject expect(File.mtime('output/blah.dat')).to eql(old_mtime) end end context 'file is not identical' do let(:old_content) { 'other donkey content' } it 'updates mtime' do subject expect(File.mtime('output/blah.dat')).to be > (Time.now - 1) end end end end end end nanoc-4.8.0/spec/nanoc/base/services/0000755000004100000410000000000013134141051017427 5ustar www-datawww-datananoc-4.8.0/spec/nanoc/base/services/compiler/0000755000004100000410000000000013134141051021241 5ustar www-datawww-datananoc-4.8.0/spec/nanoc/base/services/compiler/phases/0000755000004100000410000000000013134141051022524 5ustar www-datawww-datananoc-4.8.0/spec/nanoc/base/services/compiler/phases/abstract_spec.rb0000644000004100000410000000362413134141051025673 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::Int::Compiler::Phases::Abstract do subject(:phase) do described_class.new(wrapped: wrapped) end let(:item) { Nanoc::Int::Item.new('foo', {}, '/stuff.md') } let(:rep) { Nanoc::Int::ItemRep.new(item, :default) } let(:wrapped) { nil } describe '#run' do subject { phase.run(rep, is_outdated: false) {} } it 'raises' do expect { subject }.to raise_error(NotImplementedError) end end describe '#call' do subject { phase.call(rep, is_outdated: false) } let(:phase_class) do Class.new(described_class) do def self.to_s 'AbstractSpec::MyTestingPhaseClass' end def run(_rep, is_outdated:) # rubocop:disable Lint/UnusedMethodArgument yield end end end let(:phase) { phase_class.new(wrapped: wrapped) } let(:wrapped_class) do Class.new(described_class) do def self.to_s 'AbstractSpec::MyTestingWrappedPhaseClass' end def run(_rep, is_outdated:); end end end let(:wrapped) { wrapped_class.new(wrapped: nil) } it 'sends the proper notifications' do expect(Nanoc::Int::NotificationCenter).to receive(:post).with(:phase_started, 'MyTestingPhaseClass', rep).ordered expect(Nanoc::Int::NotificationCenter).to receive(:post).with(:phase_yielded, 'MyTestingPhaseClass', rep).ordered expect(Nanoc::Int::NotificationCenter).to receive(:post).with(:phase_started, 'MyTestingWrappedPhaseClass', rep).ordered expect(Nanoc::Int::NotificationCenter).to receive(:post).with(:phase_ended, 'MyTestingWrappedPhaseClass', rep).ordered expect(Nanoc::Int::NotificationCenter).to receive(:post).with(:phase_resumed, 'MyTestingPhaseClass', rep).ordered expect(Nanoc::Int::NotificationCenter).to receive(:post).with(:phase_ended, 'MyTestingPhaseClass', rep).ordered subject end end end nanoc-4.8.0/spec/nanoc/base/services/compiler/phases/cache_spec.rb0000644000004100000410000001101513134141051025124 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::Int::Compiler::Phases::Cache do subject(:phase) do described_class.new( compiled_content_cache: compiled_content_cache, snapshot_repo: snapshot_repo, wrapped: wrapped, ) end let(:compiled_content_cache) do Nanoc::Int::CompiledContentCache.new(items: [item]) end let(:snapshot_repo) { Nanoc::Int::SnapshotRepo.new } let(:wrapped_class) do Class.new(Nanoc::Int::Compiler::Phases::Abstract) do def initialize(snapshot_repo) @snapshot_repo = snapshot_repo end def run(rep, is_outdated:) # rubocop:disable Lint/UnusedMethodArgument @snapshot_repo.set(rep, :last, Nanoc::Int::TextualContent.new('wrapped content')) end end end let(:wrapped) { wrapped_class.new(snapshot_repo) } let(:item) { Nanoc::Int::Item.new('item content', {}, '/donkey.md') } let(:rep) { Nanoc::Int::ItemRep.new(item, :latex) } describe '#run' do subject { phase.call(rep, is_outdated: is_outdated) } let(:is_outdated) { raise 'override me' } before do allow(Nanoc::Int::NotificationCenter).to receive(:post).with(:phase_started, anything, anything) allow(Nanoc::Int::NotificationCenter).to receive(:post).with(:phase_yielded, anything, anything) allow(Nanoc::Int::NotificationCenter).to receive(:post).with(:phase_resumed, anything, anything) allow(Nanoc::Int::NotificationCenter).to receive(:post).with(:phase_ended, anything, anything) end shared_examples 'calls wrapped' do it 'delegates to wrapped' do expect(wrapped).to receive(:run).with(rep, is_outdated: is_outdated) subject end it 'marks rep as compiled' do expect { subject } .to change { rep.compiled? } .from(false) .to(true) end it 'sends no other notifications' do subject end it 'updates compiled content cache' do expect { subject } .to change { compiled_content_cache[rep] } .from(nil) .to(last: some_textual_content('wrapped content')) end end context 'outdated' do let(:is_outdated) { true } include_examples 'calls wrapped' end context 'not outdated' do let(:is_outdated) { false } context 'textual cached compiled content available' do before do compiled_content_cache[rep] = { last: Nanoc::Int::TextualContent.new('cached') } end it 'writes content to cache' do expect(Nanoc::Int::NotificationCenter).to receive(:post).with(:cached_content_used, rep) expect { subject } .to change { snapshot_repo.get(rep, :last) } .from(nil) .to(some_textual_content('cached')) end it 'marks rep as compiled' do expect(Nanoc::Int::NotificationCenter).to receive(:post).with(:cached_content_used, rep) expect { subject } .to change { rep.compiled? } .from(false) .to(true) end it 'does not change compiled content cache' do expect(Nanoc::Int::NotificationCenter).to receive(:post).with(:cached_content_used, rep) expect { subject } .not_to change { compiled_content_cache[rep] } end end context 'binary cached compiled content available' do let(:binary_content) { 'b1n4ry' } let(:binary_filename) { Tempfile.open('test') { |fn| fn << binary_content }.path } before do compiled_content_cache[rep] = { last: Nanoc::Int::BinaryContent.new(binary_filename) } end it 'writes content to cache' do expect { subject } .to change { snapshot_repo.get(rep, :last) } .from(nil) .to(some_textual_content('wrapped content')) end it 'marks rep as compiled' do expect { subject } .to change { rep.compiled? } .from(false) .to(true) end it 'changes compiled content cache' do expect { subject } .to change { compiled_content_cache[rep] } .from(last: some_binary_content(binary_content)) .to(last: some_textual_content('wrapped content')) end it 'does not send notification' do expect(Nanoc::Int::NotificationCenter).not_to receive(:post).with(:cached_content_used, rep) subject end end context 'no cached compiled content available' do include_examples 'calls wrapped' end end end end nanoc-4.8.0/spec/nanoc/base/services/compiler/stages/0000755000004100000410000000000013134141051022527 5ustar www-datawww-datananoc-4.8.0/spec/nanoc/base/services/compiler/stages/cleanup_spec.rb0000644000004100000410000000456413134141051025526 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::Int::Compiler::Stages::Cleanup do let(:stage) { described_class.new(config) } let(:config) do Nanoc::Int::Configuration.new.with_defaults end describe '#run' do subject { stage.run } it 'removes temporary binary items' do a = Nanoc::Int::TempFilenameFactory.instance.create(Nanoc::Filter::TMP_BINARY_ITEMS_DIR) File.write(a, 'hello there') expect { subject } .to change { File.file?(a) } .from(true).to(false) end it 'removes temporary textual items' do a = Nanoc::Int::TempFilenameFactory.instance.create(Nanoc::Int::ItemRepWriter::TMP_TEXT_ITEMS_DIR) File.write(a, 'hello there') expect { subject } .to change { File.file?(a) } .from(true).to(false) end shared_examples 'an old store' do it 'removes the old store' do FileUtils.mkdir_p('tmp') File.write('tmp/' + store_name, 'stuff') expect { subject } .to change { File.file?('tmp/' + store_name) } .from(true).to(false) end end context 'tmp/checksums' do let(:store_name) { 'checksums' } it_behaves_like 'an old store' end context 'tmp/compiled_content' do let(:store_name) { 'compiled_content' } it_behaves_like 'an old store' end context 'tmp/dependencies' do let(:store_name) { 'dependencies' } it_behaves_like 'an old store' end context 'tmp/outdatedness' do let(:store_name) { 'outdatedness' } it_behaves_like 'an old store' end context 'tmp/action_sequence' do let(:store_name) { 'action_sequence' } it_behaves_like 'an old store' end context 'tmp/somethingelse' do it 'does not removes the store' do FileUtils.mkdir_p('tmp') File.write('tmp/somethingelse', 'stuff') expect { subject } .not_to change { File.file?('tmp/somethingelse') } end end it 'removes stores for unused output paths' do FileUtils.mkdir_p('tmp/nanoc/2f0692fb1a1d') FileUtils.mkdir_p('tmp/nanoc/1a2195bfef6c') FileUtils.mkdir_p('tmp/nanoc/1029d67644815') expect { subject } .to change { Dir.glob('tmp/nanoc/*').sort } .from(['tmp/nanoc/1029d67644815', 'tmp/nanoc/1a2195bfef6c', 'tmp/nanoc/2f0692fb1a1d']) .to(['tmp/nanoc/1029d67644815']) end end end nanoc-4.8.0/spec/nanoc/base/services/compiler/stages/compile_reps_spec.rb0000644000004100000410000001025313134141051026550 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::Int::Compiler::Stages::CompileReps do let(:stage) do described_class.new( outdatedness_store: outdatedness_store, dependency_store: dependency_store, action_sequences: action_sequences, compilation_context: compilation_context, compiled_content_cache: compiled_content_cache, ) end let(:compilation_context) do Nanoc::Int::CompilationContext.new( action_provider: action_provider, reps: reps, site: site, compiled_content_cache: compiled_content_cache, snapshot_repo: snapshot_repo, ) end let(:action_provider) { double(:action_provider) } let(:action_sequences) { double(:action_sequences) } let(:reps) { Nanoc::Int::ItemRepRepo.new } let(:compiled_content_cache) { Nanoc::Int::CompiledContentCache.new(items: items) } let(:snapshot_repo) { Nanoc::Int::SnapshotRepo.new } let(:outdatedness_store) { Nanoc::Int::OutdatednessStore.new(site: site, reps: reps) } let(:dependency_store) { Nanoc::Int::DependencyStore.new(items, layouts, config) } let(:rep) { Nanoc::Int::ItemRep.new(item, :default) } let(:item) { Nanoc::Int::Item.new('<%= 1 + 2 %>', {}, '/hi.md') } let(:other_rep) { Nanoc::Int::ItemRep.new(other_item, :default) } let(:other_item) { Nanoc::Int::Item.new('other content', {}, '/other.md') } let(:site) do Nanoc::Int::Site.new( config: config, code_snippets: code_snippets, data_source: Nanoc::Int::InMemDataSource.new(items, layouts), ) end let(:config) { Nanoc::Int::Configuration.new.with_defaults } let(:code_snippets) { [] } let(:layouts) do Nanoc::Int::LayoutCollection.new(config) end let(:items) do Nanoc::Int::ItemCollection.new( config, [item, other_item], ) end let(:memory) do actions = [ Nanoc::Int::ProcessingActions::Filter.new(:erb, {}), Nanoc::Int::ProcessingActions::Snapshot.new([:last], []), ] Nanoc::Int::ActionSequence.new(nil, actions: actions) end before do reps << rep reps << other_rep reps.each do |rep| rep.snapshot_defs << Nanoc::Int::SnapshotDef.new(:last, binary: false) end allow(action_sequences).to receive(:[]).with(rep).and_return(memory) allow(action_sequences).to receive(:[]).with(other_rep).and_return(memory) end describe '#compile_reps' do subject { stage.run } let(:snapshot_defs_for_rep) do [Nanoc::Int::SnapshotDef.new(:last, binary: false)] end let(:snapshot_defs_for_other_rep) do [Nanoc::Int::SnapshotDef.new(:last, binary: false)] end context 'rep not in outdatedness store' do it 'keeps the item rep out of the outdatedness store' do expect(outdatedness_store.include?(rep)).not_to be expect { subject }.not_to change { outdatedness_store.include?(rep) } end end context 'rep in outdatedness store' do before { outdatedness_store.add(rep) } it 'compiles individual reps' do expect { subject }.to change { snapshot_repo.get(rep, :last) } .from(nil) .to(some_textual_content('3')) end it 'removes the item rep from the outdatedness store' do expect { subject }.to change { outdatedness_store.include?(rep) }.from(true).to(false) end context 'exception' do let(:item) { Nanoc::Int::Item.new('<%= raise "lol" %>', {}, '/hi.md') } it 'wraps exception' do expect { subject }.to raise_error(Nanoc::Int::Errors::CompilationError) end it 'contains the right item rep in the wrapped exception' do expect { subject }.to raise_error do |err| expect(err.item_rep).to eql(rep) end end it 'contains the right wrapped exception' do expect { subject }.to raise_error do |err| expect(err.unwrap).to be_a(RuntimeError) expect(err.unwrap.message).to eq('lol') end end it 'keeps the item rep in the outdatedness store' do expect(outdatedness_store.include?(rep)).to be expect { subject rescue nil }.not_to change { outdatedness_store.include?(rep) } end end end end end nanoc-4.8.0/spec/nanoc/base/services/compiler/stages/calculate_checksums_spec.rb0000644000004100000410000000366013134141051030075 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::Int::Compiler::Stages::CalculateChecksums do let(:stage) do described_class.new(items: items, layouts: layouts, code_snippets: code_snippets, config: config) end let(:config) do Nanoc::Int::Configuration.new.with_defaults end let(:code_snippets) do [code_snippet] end let(:items) do Nanoc::Int::ItemCollection.new(config, [item]) end let(:layouts) do Nanoc::Int::LayoutCollection.new(config, [layout]) end let(:code_snippet) do Nanoc::Int::CodeSnippet.new('woof!', 'dog.rb') end let(:item) do Nanoc::Int::Item.new('hello there', {}, '/hi.md') end let(:layout) do Nanoc::Int::Layout.new('t3mpl4t3', {}, '/page.erb') end describe '#run' do subject { stage.run } it 'checksums items' do expect(subject.checksum_for(item)) .to eq(Nanoc::Int::Checksummer.calc(item)) expect(subject.content_checksum_for(item)) .to eq(Nanoc::Int::Checksummer.calc_for_content_of(item)) expect(subject.attributes_checksum_for(item)) .to eq(Nanoc::Int::Checksummer.calc_for_each_attribute_of(item)) end it 'checksums layouts' do expect(subject.checksum_for(layout)) .to eq(Nanoc::Int::Checksummer.calc(layout)) expect(subject.content_checksum_for(layout)) .to eq(Nanoc::Int::Checksummer.calc_for_content_of(layout)) expect(subject.attributes_checksum_for(layout)) .to eq(Nanoc::Int::Checksummer.calc_for_each_attribute_of(layout)) end it 'checksums config' do expect(subject.checksum_for(config)) .to eq(Nanoc::Int::Checksummer.calc(config)) expect(subject.attributes_checksum_for(config)) .to eq(Nanoc::Int::Checksummer.calc_for_each_attribute_of(config)) end it 'checksums code snippets' do expect(subject.checksum_for(code_snippet)) .to eq(Nanoc::Int::Checksummer.calc(code_snippet)) end end end nanoc-4.8.0/spec/nanoc/base/services/outdatedness_checker_spec.rb0000644000004100000410000005665013134141051025170 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::Int::OutdatednessChecker do let(:outdatedness_checker) do described_class.new( site: site, checksum_store: checksum_store, checksums: checksums, dependency_store: dependency_store, action_sequence_store: action_sequence_store, action_sequences: action_sequences, reps: reps, ) end let(:checksum_store) { double(:checksum_store) } let(:checksums) do Nanoc::Int::Compiler::Stages::CalculateChecksums.new( items: items, layouts: layouts, code_snippets: code_snippets, config: config, ).run end let(:dependency_store) do Nanoc::Int::DependencyStore.new(items, layouts, config) end let(:items) { Nanoc::Int::ItemCollection.new(config, [item]) } let(:layouts) { Nanoc::Int::LayoutCollection.new(config) } let(:code_snippets) { [] } let(:site) do Nanoc::Int::Site.new( config: config, code_snippets: code_snippets, data_source: Nanoc::Int::InMemDataSource.new([], []), ) end let(:action_sequence_store) do Nanoc::Int::ActionSequenceStore.new end let(:old_action_sequence_for_item_rep) do Nanoc::Int::ActionSequence.build(item_rep) do |b| b.add_filter(:erb, {}) end end let(:new_action_sequence_for_item_rep) { old_action_sequence_for_item_rep } let(:action_sequences) do { item_rep => new_action_sequence_for_item_rep } end let(:reps) do Nanoc::Int::ItemRepRepo.new end let(:item_rep) { Nanoc::Int::ItemRep.new(item, :default) } let(:item) { Nanoc::Int::Item.new('stuff', {}, '/foo.md') } before do reps << item_rep action_sequence_store[item_rep] = old_action_sequence_for_item_rep.serialize end describe 'basic outdatedness reasons' do subject { outdatedness_checker.send(:basic).outdatedness_status_for(obj).reasons.first } let(:checksum_store) { Nanoc::Int::ChecksumStore.new(objects: items.to_a + layouts.to_a) } let(:config) { Nanoc::Int::Configuration.new } before do checksum_store.add(item) allow(site).to receive(:code_snippets).and_return([]) allow(site).to receive(:config).and_return(config) end context 'with item' do let(:obj) { item } context 'action sequence differs' do let(:new_action_sequence_for_item_rep) do Nanoc::Int::ActionSequence.build(item_rep) do |b| b.add_filter(:super_erb, {}) end end it 'is outdated due to rule differences' do expect(subject).to eql(Nanoc::Int::OutdatednessReasons::RulesModified) end end # … end context 'with item rep' do let(:obj) { item_rep } context 'action sequence differs' do let(:new_action_sequence_for_item_rep) do Nanoc::Int::ActionSequence.build(item_rep) do |b| b.add_filter(:super_erb, {}) end end it 'is outdated due to rule differences' do expect(subject).to eql(Nanoc::Int::OutdatednessReasons::RulesModified) end end # … end context 'with layout' do # … end context 'with item collection' do let(:obj) { items } context 'no new items' do it { is_expected.to be_nil } end context 'new items' do before do dependency_store.store new_item = Nanoc::Int::Item.new('stuff', {}, '/newblahz.md') dependency_store.items = Nanoc::Int::ItemCollection.new(config, [item, new_item]) dependency_store.load end it { is_expected.to be_a(Nanoc::Int::OutdatednessReasons::ItemCollectionExtended) } it 'includes proper raw_content props' do expect(subject.objects.map(&:identifier).map(&:to_s)).to eq(['/newblahz.md']) end end end context 'with layout collection' do let(:obj) { layouts } context 'no new layouts' do it { is_expected.to be_nil } end context 'new layouts' do before do dependency_store.store new_layout = Nanoc::Int::Layout.new('stuff', {}, '/newblahz.md') dependency_store.layouts = Nanoc::Int::LayoutCollection.new(config, layouts.to_a + [new_layout]) dependency_store.load end it { is_expected.to be_a(Nanoc::Int::OutdatednessReasons::LayoutCollectionExtended) } it 'includes proper raw_content props' do expect(subject.objects.map(&:identifier).map(&:to_s)).to eq(['/newblahz.md']) end end end end describe '#outdated_due_to_dependencies?' do subject { outdatedness_checker.send(:outdated_due_to_dependencies?, item) } let(:checksum_store) { Nanoc::Int::ChecksumStore.new(objects: items.to_a + layouts.to_a) } let(:other_item) { Nanoc::Int::Item.new('other stuff', {}, '/other.md') } let(:other_item_rep) { Nanoc::Int::ItemRep.new(other_item, :default) } let(:config) { Nanoc::Int::Configuration.new } let(:items) { Nanoc::Int::ItemCollection.new(config, [item, other_item]) } let(:old_action_sequence_for_other_item_rep) do Nanoc::Int::ActionSequence.build(other_item_rep) do |b| b.add_filter(:erb, {}) end end let(:new_action_sequence_for_other_item_rep) { old_action_sequence_for_other_item_rep } let(:action_sequences) do { item_rep => new_action_sequence_for_item_rep, other_item_rep => new_action_sequence_for_other_item_rep, } end before do reps << other_item_rep action_sequence_store[other_item_rep] = old_action_sequence_for_other_item_rep.serialize checksum_store.add(item) checksum_store.add(other_item) checksum_store.add(config) allow(site).to receive(:code_snippets).and_return([]) allow(site).to receive(:config).and_return(config) end context 'transitive dependency' do let(:distant_item) { Nanoc::Int::Item.new('distant stuff', {}, '/distant.md') } let(:distant_item_rep) { Nanoc::Int::ItemRep.new(distant_item, :default) } let(:items) do Nanoc::Int::ItemCollection.new(config, [item, other_item, distant_item]) end let(:action_sequences) do { item_rep => new_action_sequence_for_item_rep, other_item_rep => new_action_sequence_for_other_item_rep, distant_item_rep => new_action_sequence_for_other_item_rep, } end before do reps << distant_item_rep checksum_store.add(distant_item) action_sequence_store[distant_item_rep] = old_action_sequence_for_other_item_rep.serialize end context 'on attribute + attribute' do before do dependency_store.record_dependency(item, other_item, attributes: true) dependency_store.record_dependency(other_item, distant_item, attributes: true) end context 'distant attribute changed' do before { distant_item.attributes[:title] = 'omg new title' } it 'has correct outdatedness of item' do expect(outdatedness_checker.send(:outdated_due_to_dependencies?, item)).not_to be end it 'has correct outdatedness of other item' do expect(outdatedness_checker.send(:outdated_due_to_dependencies?, other_item)).to be end end context 'distant raw content changed' do before { distant_item.content = Nanoc::Int::TextualContent.new('omg new content') } it 'has correct outdatedness of item' do expect(outdatedness_checker.send(:outdated_due_to_dependencies?, item)).not_to be end it 'has correct outdatedness of other item' do expect(outdatedness_checker.send(:outdated_due_to_dependencies?, other_item)).not_to be end end end context 'on compiled content + attribute' do before do dependency_store.record_dependency(item, other_item, compiled_content: true) dependency_store.record_dependency(other_item, distant_item, attributes: true) end context 'distant attribute changed' do before { distant_item.attributes[:title] = 'omg new title' } it 'has correct outdatedness of item' do expect(outdatedness_checker.send(:outdated_due_to_dependencies?, item)).to be end it 'has correct outdatedness of other item' do expect(outdatedness_checker.send(:outdated_due_to_dependencies?, other_item)).to be end end context 'distant raw content changed' do before { distant_item.content = Nanoc::Int::TextualContent.new('omg new content') } it 'has correct outdatedness of item' do expect(outdatedness_checker.send(:outdated_due_to_dependencies?, item)).not_to be end it 'has correct outdatedness of other item' do expect(outdatedness_checker.send(:outdated_due_to_dependencies?, other_item)).not_to be end end end end context 'only generic attribute dependency' do before do dependency_store.record_dependency(item, other_item, attributes: true) end context 'attribute changed' do before { other_item.attributes[:title] = 'omg new title' } it { is_expected.to be } end context 'raw content changed' do before { other_item.content = Nanoc::Int::TextualContent.new('omg new content') } it { is_expected.not_to be } end context 'attribute + raw content changed' do before { other_item.attributes[:title] = 'omg new title' } before { other_item.content = Nanoc::Int::TextualContent.new('omg new content') } it { is_expected.to be } end context 'path changed' do let(:new_action_sequence_for_other_item_rep) do Nanoc::Int::ActionSequence.build(other_item_rep) do |b| b.add_filter(:erb, {}) b.add_snapshot(:donkey, '/giraffe.txt') end end it { is_expected.not_to be } end end context 'only specific attribute dependency' do before do dependency_store.record_dependency(item, other_item, attributes: [:title]) end context 'attribute changed' do before { other_item.attributes[:title] = 'omg new title' } it { is_expected.to be } end context 'other attribute changed' do before { other_item.attributes[:subtitle] = 'tagline here' } it { is_expected.not_to be } end context 'raw content changed' do before { other_item.content = Nanoc::Int::TextualContent.new('omg new content') } it { is_expected.not_to be } end context 'attribute + raw content changed' do before { other_item.attributes[:title] = 'omg new title' } before { other_item.content = Nanoc::Int::TextualContent.new('omg new content') } it { is_expected.to be } end context 'other attribute + raw content changed' do before { other_item.attributes[:subtitle] = 'tagline here' } before { other_item.content = Nanoc::Int::TextualContent.new('omg new content') } it { is_expected.not_to be } end context 'path changed' do let(:new_action_sequence_for_other_item_rep) do Nanoc::Int::ActionSequence.build(other_item_rep) do |b| b.add_filter(:erb, {}) b.add_snapshot(:donkey, '/giraffe.txt') end end it { is_expected.not_to be } end end context 'generic dependency on config' do before do dependency_store.record_dependency(item, config, attributes: true) end context 'nothing changed' do it { is_expected.not_to be } end context 'attribute changed' do before { config[:title] = 'omg new title' } it { is_expected.to be } end context 'other attribute changed' do before { config[:subtitle] = 'tagline here' } it { is_expected.to be } end end context 'specific dependency on config' do before do dependency_store.record_dependency(item, config, attributes: [:title]) end context 'nothing changed' do it { is_expected.not_to be } end context 'attribute changed' do before { config[:title] = 'omg new title' } it { is_expected.to be } end context 'other attribute changed' do before { config[:subtitle] = 'tagline here' } it { is_expected.not_to be } end end context 'only raw content dependency' do before do dependency_store.record_dependency(item, other_item, raw_content: true) end context 'attribute changed' do before { other_item.attributes[:title] = 'omg new title' } it { is_expected.not_to be } end context 'raw content changed' do before { other_item.content = Nanoc::Int::TextualContent.new('omg new content') } it { is_expected.to be } end context 'attribute + raw content changed' do before { other_item.attributes[:title] = 'omg new title' } before { other_item.content = Nanoc::Int::TextualContent.new('omg new content') } it { is_expected.to be } end context 'path changed' do let(:new_action_sequence_for_other_item_rep) do Nanoc::Int::ActionSequence.build(other_item_rep) do |b| b.add_filter(:erb, {}) b.add_snapshot(:donkey, '/giraffe.txt') end end it { is_expected.not_to be } end end context 'only path dependency' do before do dependency_store.record_dependency(item, other_item, raw_content: true) end context 'attribute changed' do before { other_item.attributes[:title] = 'omg new title' } it { is_expected.not_to be } end context 'raw content changed' do before { other_item.content = Nanoc::Int::TextualContent.new('omg new content') } it { is_expected.to be } end context 'path changed' do let(:new_action_sequence_for_other_item_rep) do Nanoc::Int::ActionSequence.build(other_item_rep) do |b| b.add_filter(:erb, {}) b.add_snapshot(:donkey, '/giraffe.txt') end end it { is_expected.not_to be } end end context 'attribute + raw content dependency' do before do dependency_store.record_dependency(item, other_item, attributes: true, raw_content: true) end context 'attribute changed' do before { other_item.attributes[:title] = 'omg new title' } it { is_expected.to be } end context 'raw content changed' do before { other_item.content = Nanoc::Int::TextualContent.new('omg new content') } it { is_expected.to be } end context 'attribute + raw content changed' do before { other_item.attributes[:title] = 'omg new title' } before { other_item.content = Nanoc::Int::TextualContent.new('omg new content') } it { is_expected.to be } end context 'rules changed' do let(:new_action_sequence_for_other_item_rep) do Nanoc::Int::ActionSequence.build(other_item_rep) do |b| b.add_filter(:erb, {}) b.add_filter(:donkey, {}) end end it { is_expected.not_to be } end end context 'attribute + other dependency' do before do dependency_store.record_dependency(item, other_item, attributes: true, path: true) end context 'attribute changed' do before { other_item.attributes[:title] = 'omg new title' } it { is_expected.to be } end context 'raw content changed' do before { other_item.content = Nanoc::Int::TextualContent.new('omg new content') } it { is_expected.not_to be } end end context 'other dependency' do before do dependency_store.record_dependency(item, other_item, path: true) end context 'attribute changed' do before { other_item.attributes[:title] = 'omg new title' } it { is_expected.not_to be } end context 'raw content changed' do before { other_item.content = Nanoc::Int::TextualContent.new('omg new content') } it { is_expected.not_to be } end end context 'only item collection dependency' do context 'dependency on any new item' do before do dependency_tracker = Nanoc::Int::DependencyTracker.new(dependency_store) dependency_tracker.enter(item) dependency_tracker.bounce(items, raw_content: true) dependency_store.store end context 'nothing changed' do it { is_expected.not_to be } end context 'item added' do before do new_item = Nanoc::Int::Item.new('stuff', {}, '/newblahz.md') dependency_store.items = Nanoc::Int::ItemCollection.new(config, items.to_a + [new_item]) dependency_store.load end it { is_expected.to be } end context 'item removed' do before do dependency_store.items = Nanoc::Int::ItemCollection.new(config, []) dependency_store.load end it { is_expected.not_to be } end end context 'dependency on specific new items (string)' do before do dependency_tracker = Nanoc::Int::DependencyTracker.new(dependency_store) dependency_tracker.enter(item) dependency_tracker.bounce(items, raw_content: ['/new*']) dependency_store.store end context 'nothing changed' do it { is_expected.not_to be } end context 'matching item added' do before do new_item = Nanoc::Int::Item.new('stuff', {}, '/newblahz.md') dependency_store.items = Nanoc::Int::ItemCollection.new(config, items.to_a + [new_item]) dependency_store.load end it { is_expected.to be } end context 'non-matching item added' do before do new_item = Nanoc::Int::Item.new('stuff', {}, '/nublahz.md') dependency_store.items = Nanoc::Int::ItemCollection.new(config, items.to_a + [new_item]) dependency_store.load end it { is_expected.not_to be } end context 'item removed' do before do dependency_store.items = Nanoc::Int::ItemCollection.new(config, []) dependency_store.load end it { is_expected.not_to be } end end context 'dependency on specific new items (regex)' do before do dependency_tracker = Nanoc::Int::DependencyTracker.new(dependency_store) dependency_tracker.enter(item) dependency_tracker.bounce(items, raw_content: [%r{^/new.*}]) dependency_store.store end context 'nothing changed' do it { is_expected.not_to be } end context 'matching item added' do before do new_item = Nanoc::Int::Item.new('stuff', {}, '/newblahz.md') dependency_store.items = Nanoc::Int::ItemCollection.new(config, items.to_a + [new_item]) dependency_store.load end it { is_expected.to be } end context 'non-matching item added' do before do new_item = Nanoc::Int::Item.new('stuff', {}, '/nublahz.md') dependency_store.items = Nanoc::Int::ItemCollection.new(config, items.to_a + [new_item]) dependency_store.load end it { is_expected.not_to be } end context 'item removed' do before do dependency_store.items = Nanoc::Int::ItemCollection.new(config, []) dependency_store.load end it { is_expected.not_to be } end end end context 'only layout collection dependency' do context 'dependency on any new layout' do before do dependency_tracker = Nanoc::Int::DependencyTracker.new(dependency_store) dependency_tracker.enter(item) dependency_tracker.bounce(layouts, raw_content: true) dependency_store.store end context 'nothing changed' do it { is_expected.not_to be } end context 'layout added' do before do new_layout = Nanoc::Int::Layout.new('stuff', {}, '/newblahz.md') dependency_store.layouts = Nanoc::Int::LayoutCollection.new(config, layouts.to_a + [new_layout]) dependency_store.load end it { is_expected.to be } end context 'layout removed' do before do dependency_store.layouts = Nanoc::Int::LayoutCollection.new(config, []) dependency_store.load end it { is_expected.not_to be } end end context 'dependency on specific new layouts (string)' do before do dependency_tracker = Nanoc::Int::DependencyTracker.new(dependency_store) dependency_tracker.enter(item) dependency_tracker.bounce(layouts, raw_content: ['/new*']) dependency_store.store end context 'nothing changed' do it { is_expected.not_to be } end context 'matching layout added' do before do new_layout = Nanoc::Int::Layout.new('stuff', {}, '/newblahz.md') dependency_store.layouts = Nanoc::Int::LayoutCollection.new(config, layouts.to_a + [new_layout]) dependency_store.load end it { is_expected.to be } end context 'non-matching layout added' do before do new_layout = Nanoc::Int::Layout.new('stuff', {}, '/nublahz.md') dependency_store.layouts = Nanoc::Int::LayoutCollection.new(config, layouts.to_a + [new_layout]) dependency_store.load end it { is_expected.not_to be } end context 'layout removed' do before do dependency_store.layouts = Nanoc::Int::LayoutCollection.new(config, []) dependency_store.load end it { is_expected.not_to be } end end context 'dependency on specific new layouts (regex)' do before do dependency_tracker = Nanoc::Int::DependencyTracker.new(dependency_store) dependency_tracker.enter(item) dependency_tracker.bounce(layouts, raw_content: [%r{^/new.*}]) dependency_store.store end context 'nothing changed' do it { is_expected.not_to be } end context 'matching layout added' do before do new_layout = Nanoc::Int::Layout.new('stuff', {}, '/newblahz.md') dependency_store.layouts = Nanoc::Int::LayoutCollection.new(config, layouts.to_a + [new_layout]) dependency_store.load end it { is_expected.to be } end context 'non-matching layout added' do before do new_layout = Nanoc::Int::Layout.new('stuff', {}, '/nublahz.md') dependency_store.layouts = Nanoc::Int::LayoutCollection.new(config, layouts.to_a + [new_layout]) dependency_store.load end it { is_expected.not_to be } end context 'layout removed' do before do dependency_store.layouts = Nanoc::Int::LayoutCollection.new(config, []) dependency_store.load end it { is_expected.not_to be } end end end end end nanoc-4.8.0/spec/nanoc/base/services/item_rep_selector_spec.rb0000644000004100000410000001266013134141051024477 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::Int::ItemRepSelector do let(:selector) { described_class.new(reps_for_selector) } let(:item) do Nanoc::Int::Item.new('stuff', {}, '/foo.md') end let(:reps_array) do [ Nanoc::Int::ItemRep.new(item, :a), Nanoc::Int::ItemRep.new(item, :b), Nanoc::Int::ItemRep.new(item, :c), Nanoc::Int::ItemRep.new(item, :d), Nanoc::Int::ItemRep.new(item, :e), ] end let(:reps_for_selector) { reps_array } let(:names_to_reps) do reps_array.each_with_object({}) do |rep, acc| acc[rep.name] = rep end end let(:dependencies) { {} } let(:result) do tentatively_yielded = [] successfully_yielded = [] selector.each do |rep| tentatively_yielded << rep.name dependencies.fetch(rep.name, []).each do |name| unless successfully_yielded.include?(name) raise Nanoc::Int::Errors::UnmetDependency.new(names_to_reps[name]) end end successfully_yielded << rep.name end [tentatively_yielded, successfully_yielded] end let(:tentatively_yielded) { result[0] } let(:successfully_yielded) { result[1] } describe 'error' do context 'plain error' do subject { selector.each { |_rep| raise 'heh' } } it 'raises' do expect { subject }.to raise_error(RuntimeError, 'heh') end end context 'plain dependency error' do subject do idx = 0 selector.each do |_rep| idx += 1 raise Nanoc::Int::Errors::UnmetDependency.new(reps_array[2]) if idx == 1 end end it 'does not raise' do expect { subject }.not_to raise_error end end context 'wrapped error' do subject do selector.each do |rep| begin raise 'heh' rescue => e raise Nanoc::Int::Errors::CompilationError.new(e, rep) end end end it 'raises original error' do expect { subject }.to raise_error(Nanoc::Int::Errors::CompilationError) do |err| expect(err.unwrap).to be_a(RuntimeError) expect(err.unwrap.message).to eq('heh') end end end context 'wrapped dependency error' do subject do idx = 0 selector.each do |rep| idx += 1 begin raise Nanoc::Int::Errors::UnmetDependency.new(reps_array[2]) if idx == 1 rescue => e raise Nanoc::Int::Errors::CompilationError.new(e, rep) end end end it 'does not raise' do expect { subject }.not_to raise_error end end end describe 'cycle' do context 'dependency on self' do subject do selector.each { |r| raise Nanoc::Int::Errors::UnmetDependency.new(r) } end example do expect { subject }.to raise_error(Nanoc::Int::Errors::DependencyCycle, <<~EOS) The site cannot be compiled because there is a dependency cycle: (1) item /foo.md, rep :a, uses compiled content of (1) EOS end end context 'cycle with three dependencies' do subject do selector.each do |r| case r when reps_array[0] raise Nanoc::Int::Errors::UnmetDependency.new(reps_array[1]) when reps_array[1] raise Nanoc::Int::Errors::UnmetDependency.new(reps_array[2]) when reps_array[2] raise Nanoc::Int::Errors::UnmetDependency.new(reps_array[0]) end end end example do expect { subject }.to raise_error(Nanoc::Int::Errors::DependencyCycle, <<~EOS) The site cannot be compiled because there is a dependency cycle: (1) item /foo.md, rep :a, uses compiled content of (2) item /foo.md, rep :b, uses compiled content of (3) item /foo.md, rep :c, uses compiled content of (1) EOS end end end describe 'yield order' do context 'linear dependencies' do let(:dependencies) do { a: [:b], b: [:c], c: [:d], d: [:e], e: [], } end example do expect(successfully_yielded).to eq %i[e d c b a] expect(tentatively_yielded).to eq %i[a b c d e d c b a] end end context 'no dependencies' do let(:dependencies) do {} end example do expect(successfully_yielded).to eq %i[a b c d e] expect(tentatively_yielded).to eq %i[a b c d e] end end context 'star dependencies' do let(:dependencies) do { a: %i[b c d e], } end example do expect(successfully_yielded).to eq %i[b c d e a] expect(tentatively_yielded).to eq %i[a b a c a d a e a] end end context 'star dependencies; selectively recompiling' do let(:reps_for_selector) { reps_array.first(1) } let(:dependencies) do { a: %i[b c d e], } end example do expect(successfully_yielded).to eq %i[b c d e a] expect(tentatively_yielded).to eq %i[a b a c a d a e a] end end context 'unrelated roots' do let(:dependencies) do { a: [:d], b: [:e], c: [], } end it 'picks prioritised roots' do expect(successfully_yielded).to eq %i[d a e b c] expect(tentatively_yielded).to eq %i[a d a b e b c] end end end end nanoc-4.8.0/spec/nanoc/base/services/executor_spec.rb0000644000004100000410000005132113134141051022626 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::Int::Executor do let(:executor) { described_class.new(rep, compilation_context, dependency_tracker) } let(:compilation_context) do Nanoc::Int::CompilationContext.new( action_provider: action_provider, reps: reps, site: site, compiled_content_cache: compiled_content_cache, snapshot_repo: snapshot_repo, ) end let(:item) { Nanoc::Int::Item.new(content, {}, '/index.md') } let(:rep) { Nanoc::Int::ItemRep.new(item, :donkey) } let(:content) { Nanoc::Int::TextualContent.new('Donkey Power').tap(&:freeze) } let(:action_provider) { double(:action_provider) } let(:reps) { double(:reps) } let(:site) { double(:site) } let(:compiled_content_cache) { double(:compiled_content_cache) } let(:snapshot_repo) { Nanoc::Int::SnapshotRepo.new } let(:dependency_tracker) { Nanoc::Int::DependencyTracker.new(double(:dependency_store)) } describe '#filter' do let(:assigns) { {} } let(:content) { Nanoc::Int::TextualContent.new('<%= "Donkey" %> Power') } before do allow(compilation_context).to receive(:assigns_for) { assigns } end context 'normal flow with textual rep' do subject { executor.filter(:erb) } before do expect(Nanoc::Int::NotificationCenter) .to receive(:post).with(:filtering_started, rep, :erb) expect(Nanoc::Int::NotificationCenter) .to receive(:post).with(:filtering_ended, rep, :erb) snapshot_repo.set(rep, :last, content) end it 'does not set :pre on rep' do expect(snapshot_repo.get(rep, :pre)).to be_nil expect { subject }.not_to change { snapshot_repo.get(rep, :pre) } end it 'does not set :post on rep' do expect(snapshot_repo.get(rep, :post)).to be_nil expect { subject }.not_to change { snapshot_repo.get(rep, :post) } end it 'updates :last on rep' do expect { subject } .to change { snapshot_repo.get(rep, :last).string } .from('<%= "Donkey" %> Power') .to('Donkey Power') end it 'does not set :pre in repo' do expect(snapshot_repo.get(rep, :pre)).to be_nil expect { subject }.not_to change { snapshot_repo.get(rep, :pre) } end it 'does not set :post in repo' do expect(snapshot_repo.get(rep, :post)).to be_nil expect { subject }.not_to change { snapshot_repo.get(rep, :post) } end it 'updates :last in repo' do expect { subject } .to change { snapshot_repo.get(rep, :last).string } .from('<%= "Donkey" %> Power') .to('Donkey Power') end it 'returns frozen data' do executor.filter(:erb) expect(snapshot_repo.get(rep, :last)).to be_frozen end end context 'normal flow with binary rep' do subject { executor.filter(:whatever) } let(:content) { Nanoc::Int::BinaryContent.new(File.expand_path('foo.dat')) } before do expect(Nanoc::Int::NotificationCenter) .to receive(:post).with(:filtering_started, rep, :whatever) expect(Nanoc::Int::NotificationCenter) .to receive(:post).with(:filtering_ended, rep, :whatever) File.write(content.filename, 'Foo Data') filter_class = Class.new(::Nanoc::Filter) do type :binary def run(filename, _params = {}) File.write(output_filename, "Compiled data for #{filename}") end end expect(Nanoc::Filter).to receive(:named).with(:whatever) { filter_class } snapshot_repo.set(rep, :last, content) end it 'does not set :pre on rep' do expect(snapshot_repo.get(rep, :pre)).to be_nil expect { subject }.not_to change { snapshot_repo.get(rep, :pre) } end it 'does not set :post on rep' do expect(snapshot_repo.get(rep, :post)).to be_nil expect { subject }.not_to change { snapshot_repo.get(rep, :post) } end it 'updates :last on rep' do expect { subject } .to change { snapshot_repo.get(rep, :last) } .from(some_binary_content('Foo Data')) .to(some_binary_content(/\ACompiled data for \/.*\/foo.dat\z/)) end it 'does not set :pre in repo' do expect(snapshot_repo.get(rep, :pre)).to be_nil expect { subject }.not_to change { snapshot_repo.get(rep, :pre) } end it 'does not set :post in repo' do expect(snapshot_repo.get(rep, :post)).to be_nil expect { subject }.not_to change { snapshot_repo.get(rep, :post) } end it 'updates :last in repo' do expect { subject } .to change { File.read(snapshot_repo.get(rep, :last).filename) } .from('Foo Data') .to(/\ACompiled data for \/.*\/foo.dat\z/) end it 'returns frozen data' do executor.filter(:whatever) expect(snapshot_repo.get(rep, :last)).to be_frozen end end context 'normal flow with binary rep and binary-to-text filter' do subject { executor.filter(:whatever) } let(:content) { Nanoc::Int::BinaryContent.new(File.expand_path('foo.dat')) } before do expect(Nanoc::Int::NotificationCenter) .to receive(:post).with(:filtering_started, rep, :whatever) expect(Nanoc::Int::NotificationCenter) .to receive(:post).with(:filtering_ended, rep, :whatever) File.write(content.filename, 'Foo Data') filter_class = Class.new(::Nanoc::Filter) do type binary: :text def run(filename, _params = {}) "Compiled data for #{filename}" end end expect(Nanoc::Filter).to receive(:named).with(:whatever) { filter_class } snapshot_repo.set(rep, :last, content) end it 'does not set :pre on rep' do expect(snapshot_repo.get(rep, :pre)).to be_nil expect { subject }.not_to change { snapshot_repo.get(rep, :pre) } end it 'does not set :post on rep' do expect(snapshot_repo.get(rep, :post)).to be_nil expect { subject }.not_to change { snapshot_repo.get(rep, :post) } end it 'updates :last on rep' do expect { subject } .to change { snapshot_repo.get(rep, :last) } .from(some_binary_content('Foo Data')) .to(some_textual_content(/\ACompiled data for \/.*\/foo.dat\z/)) end it 'does not set :pre in repo' do expect(snapshot_repo.get(rep, :pre)).to be_nil expect { subject }.not_to change { snapshot_repo.get(rep, :pre) } end it 'does not set :post in repo' do expect(snapshot_repo.get(rep, :post)).to be_nil expect { subject }.not_to change { snapshot_repo.get(rep, :post) } end it 'updates :last in repo' do expect { subject } .to change { snapshot_repo.get(rep, :last) } .from(some_binary_content('Foo Data')) .to(some_textual_content(/\ACompiled data for \/.*\/foo.dat\z/)) end end context 'normal flow with textual rep and text-to-binary filter' do subject { executor.filter(:whatever) } before do expect(Nanoc::Int::NotificationCenter) .to receive(:post).with(:filtering_started, rep, :whatever) expect(Nanoc::Int::NotificationCenter) .to receive(:post).with(:filtering_ended, rep, :whatever) filter_class = Class.new(::Nanoc::Filter) do type text: :binary def run(content, _params = {}) File.write(output_filename, "Binary #{content}") end end expect(Nanoc::Filter).to receive(:named).with(:whatever) { filter_class } snapshot_repo.set(rep, :last, content) end it 'does not set :pre on rep' do expect(snapshot_repo.get(rep, :pre)).to be_nil expect { subject }.not_to change { snapshot_repo.get(rep, :pre) } end it 'does not set :post on rep' do expect(snapshot_repo.get(rep, :post)).to be_nil expect { subject }.not_to change { snapshot_repo.get(rep, :post) } end it 'updates :last on rep' do expect { subject } .to change { snapshot_repo.get(rep, :last) } .from(some_textual_content('<%= "Donkey" %> Power')) .to(some_binary_content('Binary <%= "Donkey" %> Power')) end it 'does not set :pre in repo' do expect(snapshot_repo.get(rep, :pre)).to be_nil expect { subject }.not_to change { snapshot_repo.get(rep, :pre) } end it 'does not set :post in repo' do expect(snapshot_repo.get(rep, :post)).to be_nil expect { subject }.not_to change { snapshot_repo.get(rep, :post) } end it 'updates :last in repo' do expect { subject } .to change { snapshot_repo.get(rep, :last) } .from(some_textual_content('<%= "Donkey" %> Power')) .to(some_binary_content('Binary <%= "Donkey" %> Power')) end end context 'non-existant filter' do it 'raises' do expect { executor.filter(:ajlsdfjklaskldfj) } .to raise_error(Nanoc::Int::Errors::UnknownFilter) end end context 'non-binary rep, binary-to-something filter' do before do filter_class = Class.new(::Nanoc::Filter) do type :binary def run(_content, _params = {}); end end expect(Nanoc::Filter).to receive(:named).with(:whatever) { filter_class } snapshot_repo.set(rep, :last, content) end it 'raises' do expect { executor.filter(:whatever) } .to raise_error(Nanoc::Int::Errors::CannotUseBinaryFilter) end end context 'binary rep, text-to-something filter' do let(:content) { Nanoc::Int::BinaryContent.new(File.expand_path('foo.md')) } before do snapshot_repo.set(rep, :last, content) end it 'raises' do expect { executor.filter(:erb) } .to raise_error(Nanoc::Int::Errors::CannotUseTextualFilter) end end context 'binary filter that does not write anything' do let(:content) { Nanoc::Int::BinaryContent.new(File.expand_path('foo.dat')) } before do expect(Nanoc::Int::NotificationCenter) .to receive(:post).with(:filtering_started, rep, :whatever) expect(Nanoc::Int::NotificationCenter) .to receive(:post).with(:filtering_ended, rep, :whatever) File.write(content.filename, 'Foo Data') filter_class = Class.new(::Nanoc::Filter) do type :binary def run(_filename, _params = {}); end end snapshot_repo.set(rep, :last, content) expect(Nanoc::Filter).to receive(:named).with(:whatever) { filter_class } end example do expect { executor.filter(:whatever) } .to raise_error(Nanoc::Int::Executor::OutputNotWrittenError) end end context 'content is frozen' do before do snapshot_repo.set(rep, :last, item.content) end let(:item) do Nanoc::Int::Item.new('foo bar', {}, '/foo.md').tap(&:freeze) end let(:filter_that_modifies_content) do Class.new(::Nanoc::Filter) do def run(content, _params = {}) content.gsub!('foo', 'moo') content end end end let(:filter_that_modifies_params) do Class.new(::Nanoc::Filter) do def run(_content, params = {}) params[:foo] = 'bar' 'asdf' end end end it 'errors when attempting to modify content' do expect(Nanoc::Filter).to receive(:named).with(:whatever).and_return(filter_that_modifies_content) expect { executor.filter(:whatever) }.to raise_frozen_error end it 'receives frozen filter args' do expect(Nanoc::Filter).to receive(:named).with(:whatever).and_return(filter_that_modifies_params) expect { executor.filter(:whatever) }.to raise_frozen_error end end end describe '#layout' do let(:site) { double(:site, config: config, layouts: layouts) } let(:config) do { string_pattern_type: 'glob', } end let(:layout) do Nanoc::Int::Layout.new(layout_content, { bug: 'Gum Emperor' }, '/default.erb') end let(:layouts) { [layout] } let(:layout_content) { 'head <%= @foo %> foot' } let(:assigns) do { foo: 'hallo' } end let(:view_context) do Nanoc::ViewContext.new( reps: double(:reps), items: double(:items), dependency_tracker: dependency_tracker, compilation_context: double(:compilation_context), snapshot_repo: snapshot_repo, ) end let(:action_sequence) do Nanoc::Int::ActionSequence.build(rep) do |b| b.add_filter(:erb, {}) end end before do rep.snapshot_defs = [Nanoc::Int::SnapshotDef.new(:pre, binary: false)] snapshot_repo.set(rep, :last, content) allow(compilation_context).to receive(:site) { site } allow(compilation_context).to receive(:assigns_for).with(rep, dependency_tracker) { assigns } allow(compilation_context).to receive(:create_view_context).with(dependency_tracker).and_return(view_context) allow(action_provider).to receive(:action_sequence_for).with(layout).and_return(action_sequence) end subject { executor.layout('/default.*') } context 'accessing layout attributes' do let(:layout_content) { 'head <%= @layout[:bug] %> foot' } it 'exposes @layout as view' do allow(dependency_tracker).to receive(:enter) .with(layout, raw_content: true, attributes: false, compiled_content: false, path: false) allow(dependency_tracker).to receive(:enter) .with(layout, raw_content: false, attributes: [:bug], compiled_content: false, path: false) allow(dependency_tracker).to receive(:exit) subject expect(snapshot_repo.get(rep, :last).string).to eq('head Gum Emperor foot') end end context 'normal flow' do it 'updates :last on rep' do expect { subject } .to change { snapshot_repo.get(rep, :last) } .from(some_textual_content('Donkey Power')) .to(some_textual_content('head hallo foot')) end it 'updates :last in repo' do expect { subject } .to change { snapshot_repo.get(rep, :last) } .from(some_textual_content('Donkey Power')) .to(some_textual_content('head hallo foot')) end it 'sets frozen content' do subject expect(snapshot_repo.get(rep, :last)).to be_frozen expect(snapshot_repo.get(rep, :pre)).to be_frozen end it 'does not create pre snapshot' do # a #layout is followed by a #snapshot(:pre, …) expect(snapshot_repo.get(rep, :pre)).to be_nil subject expect(snapshot_repo.get(rep, :pre)).to be_nil end it 'sends notifications' do expect(Nanoc::Int::NotificationCenter).to receive(:post).with(:filtering_started, rep, :erb).ordered expect(Nanoc::Int::NotificationCenter).to receive(:post).with(:filtering_ended, rep, :erb).ordered subject end context 'compiled_content reference in layout' do let(:layout_content) { 'head <%= @item_rep.compiled_content(snapshot: :pre) %> foot' } let(:assigns) do { item_rep: Nanoc::ItemRepView.new(rep, view_context) } end before do executor.snapshot(:pre) end it 'updates :last on rep' do expect { subject } .to change { snapshot_repo.get(rep, :last) } .from(some_textual_content('Donkey Power')) .to(some_textual_content('head Donkey Power foot')) end it 'updates :last in repo' do expect { subject } .to change { snapshot_repo.get(rep, :last) } .from(some_textual_content('Donkey Power')) .to(some_textual_content('head Donkey Power foot')) end end context 'content with layout reference' do let(:layout_content) { 'head <%= @layout.identifier %> foot' } it 'updates :last on rep' do expect { subject } .to change { snapshot_repo.get(rep, :last) } .from(some_textual_content('Donkey Power')) .to(some_textual_content('head /default.erb foot')) end it 'updates :last in repo' do expect { subject } .to change { snapshot_repo.get(rep, :last) } .from(some_textual_content('Donkey Power')) .to(some_textual_content('head /default.erb foot')) end end end context 'no layout found' do let(:layouts) do [Nanoc::Int::Layout.new('head <%= @foo %> foot', {}, '/other.erb')] end it 'raises' do expect { subject }.to raise_error(Nanoc::Int::Errors::UnknownLayout) end end context 'no filter specified' do let(:action_sequence) do Nanoc::Int::ActionSequence.new(rep) end it 'raises' do expect { subject }.to raise_error(Nanoc::Int::Errors::UndefinedFilterForLayout) end end context 'binary item' do let(:content) { Nanoc::Int::BinaryContent.new(File.expand_path('donkey.md')) } it 'raises' do expect { subject }.to raise_error( Nanoc::Int::Errors::CannotLayoutBinaryItem, 'The “/index.md” item (rep “donkey”) cannot be laid out because it is a binary item. If you are getting this error for an item that should be textual instead of binary, make sure that its extension is included in the text_extensions array in the site configuration.', ) end end it 'receives frozen filter args' do filter_class = Class.new(::Nanoc::Filter) do def run(_content, params = {}) params[:foo] = 'bar' 'asdf' end end expect(Nanoc::Filter).to receive(:named).with(:erb) { filter_class } expect { subject }.to raise_frozen_error end end describe '#snapshot' do subject { executor.snapshot(:something) } before do snapshot_repo.set(rep, :last, content) File.write('donkey.dat', 'binary donkey') end context 'binary content' do let(:content) { Nanoc::Int::BinaryContent.new(File.expand_path('donkey.dat')) } it 'creates snapshots on rep' do expect { subject } .to change { snapshot_repo.get(rep, :something) } .from(nil) .to(some_binary_content('binary donkey')) end it 'creates snapshots in repo' do expect { subject } .to change { snapshot_repo.get(rep, :something) } .from(nil) .to(some_binary_content('binary donkey')) end end context 'textual content' do let(:content) { Nanoc::Int::TextualContent.new('Donkey Power') } it 'creates snapshots on rep' do expect { subject } .to change { snapshot_repo.get(rep, :something) } .from(nil) .to(some_textual_content('Donkey Power')) end it 'creates snapshots in repo' do expect { subject } .to change { snapshot_repo.get(rep, :something) } .from(nil) .to(some_textual_content('Donkey Power')) end end context 'final snapshot' do let(:content) { Nanoc::Int::TextualContent.new('Donkey Power') } context 'raw path' do before do rep.raw_paths = { something: ['output/donkey.md'] } end it 'does not write' do executor.snapshot(:something) expect(File.file?('output/donkey.md')).not_to be end end context 'no raw path' do it 'does not write' do executor.snapshot(:something) expect(File.file?('output/donkey.md')).to eq(false) end end end end describe '#find_layout' do let(:site) { double(:site, config: config, layouts: layouts) } let(:config) { {} } before do allow(compilation_context).to receive(:site) { site } end subject { executor.find_layout(arg) } context 'layout with cleaned identifier exists' do let(:arg) { '/default' } let(:layouts) do [Nanoc::Int::Layout.new('head <%= @foo %> foot', {}, '/default/')] end it { is_expected.to eq(layouts[0]) } end context 'no layout with cleaned identifier exists' do let(:layouts) do [Nanoc::Int::Layout.new('head <%= @foo %> foot', {}, '/default.erb')] end context 'globs' do let(:config) { { string_pattern_type: 'glob' } } let(:arg) { '/default.*' } it { is_expected.to eq(layouts[0]) } end context 'no globs' do let(:config) { { string_pattern_type: 'legacy' } } let(:arg) { '/default.*' } it 'raises' do expect { subject }.to raise_error(Nanoc::Int::Errors::UnknownLayout) end end end end end nanoc-4.8.0/spec/nanoc/base/services/pruner_spec.rb0000644000004100000410000000562713134141051022313 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::Pruner do subject(:pruner) { described_class.new(config, reps, dry_run: dry_run, exclude: exclude) } let(:config) { Nanoc::Int::Configuration.new({}) } let(:dry_run) { false } let(:exclude) { [] } let(:reps) do Nanoc::Int::ItemRepRepo.new.tap do |reps| reps << Nanoc::Int::ItemRep.new(item, :default).tap do |rep| rep.raw_paths = { last: ['output/asdf.html'] } end reps << Nanoc::Int::ItemRep.new(item, :text).tap do |rep| rep.raw_paths = { last: ['output/asdf.txt'] } end end end let(:item) { Nanoc::Int::Item.new('asdf', {}, '/a.md') } it 'is accessible through Nanoc::Extra::Pruner' do expect(Nanoc::Extra::Pruner).to equal(Nanoc::Pruner) end describe '#pathname_components' do subject { pruner.pathname_components(pathname) } context 'regular path' do let(:pathname) { Pathname.new('/a/bb/ccc/dd/e') } it { is_expected.to eql(%w[/ a bb ccc dd e]) } end end describe '#files_and_dirs_in' do subject { pruner.files_and_dirs_in('output/') } before do FileUtils.mkdir_p('output/projects') FileUtils.mkdir_p('output/.git') File.write('output/asdf.html', '

text

') File.write('output/.htaccess', 'secret stuff here') File.write('output/projects/nanoc.html', '

Nanoc is v cool!!

') File.write('output/.git/HEAD', 'some content here') end context 'nothing excluded' do let(:exclude) { [] } it 'returns all files' do files = [ 'output/asdf.html', 'output/.htaccess', 'output/projects/nanoc.html', 'output/.git/HEAD', ] expect(subject[0]).to match_array(files) end it 'returns all directories' do dirs = [ 'output/', 'output/projects', 'output/.git', ] expect(subject[1]).to match_array(dirs) end end context 'directory (.git) excluded' do let(:exclude) { ['.git'] } it 'returns all files' do files = [ 'output/asdf.html', 'output/.htaccess', 'output/projects/nanoc.html', ] expect(subject[0]).to match_array(files) end it 'returns all directories' do dirs = [ 'output/', 'output/projects', ] expect(subject[1]).to match_array(dirs) end end context 'file (.htaccess) excluded' do let(:exclude) { ['.htaccess'] } it 'returns all files' do files = [ 'output/asdf.html', 'output/projects/nanoc.html', 'output/.git/HEAD', ] expect(subject[0]).to match_array(files) end it 'returns all directories' do dirs = [ 'output/', 'output/projects', 'output/.git', ] expect(subject[1]).to match_array(dirs) end end end end nanoc-4.8.0/spec/nanoc/base/services/dependency_tracker_spec.rb0000644000004100000410000001624613134141051024630 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::Int::DependencyTracker do let(:tracker) { described_class.new(store) } let(:store) { Nanoc::Int::DependencyStore.new(empty_items, empty_layouts, config) } let(:item_a) { Nanoc::Int::Item.new('a', {}, '/a.md') } let(:item_b) { Nanoc::Int::Item.new('b', {}, '/b.md') } let(:item_c) { Nanoc::Int::Item.new('c', {}, '/c.md') } let(:empty_items) { Nanoc::Int::ItemCollection.new(config) } let(:empty_layouts) { Nanoc::Int::LayoutCollection.new(config) } let(:config) { Nanoc::Int::Configuration.new.with_defaults } shared_examples 'a null dependency tracker' do let(:tracker) { Nanoc::Int::DependencyTracker::Null.new } example do expect { subject }.not_to change { store.objects_causing_outdatedness_of(item_a) } end example do expect { subject }.not_to change { store.objects_causing_outdatedness_of(item_b) } end example do expect { subject }.not_to change { store.objects_causing_outdatedness_of(item_c) } end example do expect { subject }.not_to change { store.dependencies_causing_outdatedness_of(item_a) } end example do expect { subject }.not_to change { store.dependencies_causing_outdatedness_of(item_b) } end example do expect { subject }.not_to change { store.dependencies_causing_outdatedness_of(item_c) } end end describe '#enter and #exit' do context 'enter' do subject do tracker.enter(item_a) end it_behaves_like 'a null dependency tracker' example do expect { subject }.not_to change { store.objects_causing_outdatedness_of(item_a) } end example do expect { subject }.not_to change { store.objects_causing_outdatedness_of(item_b) } end example do expect { subject }.not_to change { store.objects_causing_outdatedness_of(item_c) } end end context 'enter + enter' do subject do tracker.enter(item_a) tracker.enter(item_b) end it_behaves_like 'a null dependency tracker' it 'changes predecessors of item A' do expect { subject }.to change { store.objects_causing_outdatedness_of(item_a) } .from([]).to([item_b]) end example do expect { subject }.not_to change { store.objects_causing_outdatedness_of(item_b) } end example do expect { subject }.not_to change { store.objects_causing_outdatedness_of(item_c) } end end context 'enter + enter with props' do subject do tracker.enter(item_a) tracker.enter(item_b, compiled_content: true) end it_behaves_like 'a null dependency tracker' it 'changes predecessors of item A' do expect { subject }.to change { store.objects_causing_outdatedness_of(item_a) } .from([]).to([item_b]) end it 'changes dependencies causing outdatedness of item A' do expect { subject }.to change { store.dependencies_causing_outdatedness_of(item_a).size } .from(0).to(1) end it 'creates correct new dependency causing outdatedness of item A' do subject dep = store.dependencies_causing_outdatedness_of(item_a)[0] expect(dep.from).to eql(item_b) expect(dep.to).to eql(item_a) end it 'creates dependency with correct props causing outdatedness of item A' do subject dep = store.dependencies_causing_outdatedness_of(item_a)[0] expect(dep.props.compiled_content?).to eq(true) expect(dep.props.raw_content?).to eq(false) expect(dep.props.attributes?).to eq(false) expect(dep.props.path?).to eq(false) end example do expect { subject }.not_to change { store.objects_causing_outdatedness_of(item_b) } end example do expect { subject }.not_to change { store.objects_causing_outdatedness_of(item_c) } end example do expect { subject }.not_to change { store.dependencies_causing_outdatedness_of(item_b) } end example do expect { subject }.not_to change { store.dependencies_causing_outdatedness_of(item_c) } end end context 'enter + enter with prop + exit + enter with prop' do subject do tracker.enter(item_a) tracker.enter(item_b, compiled_content: true) tracker.exit tracker.enter(item_b, attributes: true) end it_behaves_like 'a null dependency tracker' it 'changes predecessors of item A' do expect { subject }.to change { store.objects_causing_outdatedness_of(item_a) } .from([]).to([item_b]) end it 'changes dependencies causing outdatedness of item A' do expect { subject }.to change { store.dependencies_causing_outdatedness_of(item_a).size } .from(0).to(1) end it 'creates correct new dependency causing outdatedness of item A' do subject dep = store.dependencies_causing_outdatedness_of(item_a)[0] expect(dep.from).to eql(item_b) expect(dep.to).to eql(item_a) end it 'creates dependency with correct props causing outdatedness of item A' do subject dep = store.dependencies_causing_outdatedness_of(item_a)[0] expect(dep.props.compiled_content?).to eq(true) expect(dep.props.attributes?).to eq(true) expect(dep.props.raw_content?).to eq(false) expect(dep.props.path?).to eq(false) end example do expect { subject }.not_to change { store.objects_causing_outdatedness_of(item_b) } end example do expect { subject }.not_to change { store.objects_causing_outdatedness_of(item_c) } end example do expect { subject }.not_to change { store.dependencies_causing_outdatedness_of(item_b) } end example do expect { subject }.not_to change { store.dependencies_causing_outdatedness_of(item_c) } end end context 'enter + enter + exit + enter' do subject do tracker.enter(item_a) tracker.enter(item_b) tracker.exit tracker.enter(item_c) end it_behaves_like 'a null dependency tracker' it 'changes predecessors of item A' do expect { subject }.to change { store.objects_causing_outdatedness_of(item_a) } .from([]).to([item_b, item_c]) end example do expect { subject }.not_to change { store.objects_causing_outdatedness_of(item_b) } end example do expect { subject }.not_to change { store.objects_causing_outdatedness_of(item_c) } end end context 'enter + bounce + enter' do subject do tracker.enter(item_a) tracker.bounce(item_b) tracker.enter(item_c) end it_behaves_like 'a null dependency tracker' it 'changes predecessors of item A' do expect { subject }.to change { store.objects_causing_outdatedness_of(item_a) } .from([]).to([item_b, item_c]) end example do expect { subject }.not_to change { store.objects_causing_outdatedness_of(item_b) } end example do expect { subject }.not_to change { store.objects_causing_outdatedness_of(item_c) } end end end end nanoc-4.8.0/spec/nanoc/base/services/temp_filename_factory_spec.rb0000644000004100000410000000415513134141051025327 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::Int::TempFilenameFactory do subject(:factory) { described_class.new } let(:prefix) { 'foo' } describe '#create' do it 'creates unique paths' do path_a = subject.create(prefix) path_b = subject.create(prefix) expect(path_a).not_to eq(path_b) end it 'returns absolute paths' do path = subject.create(prefix) expect(path).to match(/\A\//) end it 'creates the containing directory' do expect(Dir[subject.root_dir + '/**/*']).to be_empty path = subject.create(prefix) expect(File.directory?(File.dirname(path))).to be(true) end it 'reuses the same path after cleanup' do path_a = subject.create(prefix) subject.cleanup(prefix) path_b = subject.create(prefix) expect(path_a).to eq(path_b) end it 'does not create the file' do path = subject.create(prefix) expect(File.file?(path)).not_to be(true) end end describe '#cleanup' do subject { factory.cleanup(prefix) } let!(:path) { factory.create(prefix) } before { File.write(path, 'hello') } def files Dir[factory.root_dir + '/**/*'].select { |fn| File.file?(fn) } end it 'removes generated files' do expect { subject }.to change { files }.from([path]).to([]) end context 'files with other prefixes exist' do before do factory.create('donkey') end it 'does not delete root dir' do expect(File.directory?(factory.root_dir)).to be(true) expect { subject }.not_to change { File.directory?(factory.root_dir) } end end context 'no files with other prefixes exist' do it 'deletes root dir' do expect { subject }.to change { File.directory?(factory.root_dir) }.from(true).to(false) end end end describe 'other instance' do let(:other_instance) do Nanoc::Int::TempFilenameFactory.new end it 'creates unique paths across instances' do path_a = subject.create(prefix) path_b = other_instance.create(prefix) expect(path_a).not_to eq(path_b) end end end nanoc-4.8.0/spec/nanoc/base/services/item_rep_router_spec.rb0000644000004100000410000001376513134141051024206 0ustar www-datawww-data# frozen_string_literal: true describe(Nanoc::Int::ItemRepRouter) do subject(:item_rep_router) { described_class.new(reps, action_provider, site) } let(:reps) { double(:reps) } let(:action_provider) { double(:action_provider) } let(:site) { double(:site, config: config) } let(:config) { Nanoc::Int::Configuration.new.with_defaults } describe '#run' do subject { item_rep_router.run } let(:item) { Nanoc::Int::Item.new('content', {}, '/foo.md') } let(:reps) do [ Nanoc::Int::ItemRep.new(item, :default), Nanoc::Int::ItemRep.new(item, :csv), ] end let(:memory_without_paths) do actions = [ Nanoc::Int::ProcessingActions::Filter.new(:erb, {}), Nanoc::Int::ProcessingActions::Snapshot.new([], []), ] Nanoc::Int::ActionSequence.new(nil, actions: actions) end let(:action_sequence_for_default) do actions = [ Nanoc::Int::ProcessingActions::Filter.new(:erb, {}), Nanoc::Int::ProcessingActions::Snapshot.new([:last], ['/foo/index.html']), ] Nanoc::Int::ActionSequence.new(nil, actions: actions) end let(:action_sequence_for_csv) do actions = [ Nanoc::Int::ProcessingActions::Filter.new(:erb, {}), Nanoc::Int::ProcessingActions::Snapshot.new([:last], ['/foo.csv']), ] Nanoc::Int::ActionSequence.new(nil, actions: actions) end example do allow(action_provider).to receive(:action_sequence_for).with(reps[0]).and_return(action_sequence_for_default) allow(action_provider).to receive(:action_sequence_for).with(reps[1]).and_return(action_sequence_for_csv) subject expect(reps[0].raw_paths).to eql(last: ['output/foo/index.html']) expect(reps[0].paths).to eql(last: ['/foo/']) expect(reps[1].raw_paths).to eql(last: ['output/foo.csv']) expect(reps[1].paths).to eql(last: ['/foo.csv']) end it 'picks the paths last returned' do allow(action_provider).to receive(:action_sequence_for) .with(reps[0]).and_return(memory_without_paths, action_sequence_for_default) allow(action_provider).to receive(:action_sequence_for) .with(reps[1]).and_return(memory_without_paths, action_sequence_for_csv) subject expect(reps[0].raw_paths).to eql(last: ['output/foo/index.html']) expect(reps[0].paths).to eql(last: ['/foo/']) expect(reps[1].raw_paths).to eql(last: ['output/foo.csv']) expect(reps[1].paths).to eql(last: ['/foo.csv']) end end describe '#route_rep' do subject { item_rep_router.route_rep(rep, paths, snapshot_names, paths_to_reps) } let(:snapshot_names) { [:foo] } let(:rep) { Nanoc::Int::ItemRep.new(item, :default) } let(:item) { Nanoc::Int::Item.new('content', {}, '/foo.md') } let(:paths_to_reps) { {} } context 'basic path is nil' do let(:paths) { [] } it 'assigns no paths' do subject expect(rep.raw_paths[:foo]).to be_empty end end context 'basic path is not nil' do let(:paths) { ['/foo/index.html'] } context 'other snapshot with this path already exists' do let(:paths_to_reps) { { '/foo/index.html' => Nanoc::Int::ItemRep.new(item, :other) } } it 'errors' do expect { subject }.to raise_error(Nanoc::Int::ItemRepRouter::IdenticalRoutesError) end end context 'path is unique' do context 'single path' do it 'sets the raw path' do subject expect(rep.raw_paths).to eql(foo: ['output/foo/index.html']) end it 'sets the path' do subject expect(rep.paths).to eql(foo: ['/foo/']) end it 'adds to paths_to_reps' do subject expect(paths_to_reps).to have_key('/foo/index.html') end context 'path does not start with a slash' do let(:paths) { ['foo/index.html'] } it 'errors' do expect { subject }.to raise_error(Nanoc::Int::ItemRepRouter::RouteWithoutSlashError) end end context 'path is not UTF-8' do let(:paths) { ['/foo/index.html'.encode('ISO-8859-1')] } it 'sets the path as UTF-8' do subject expect(rep.paths).to eql(foo: ['/foo/']) expect(rep.paths[:foo].first.encoding.to_s).to eql('UTF-8') end it 'sets the raw path as UTF-8' do subject expect(rep.raw_paths).to eql(foo: ['output/foo/index.html']) expect(rep.raw_paths[:foo].first.encoding.to_s).to eql('UTF-8') end end end context 'multiple paths' do let(:paths) { ['/foo/index.html', '/bar/index.html'] } it 'sets the raw paths' do subject expect(rep.raw_paths).to eql(foo: ['output/foo/index.html', 'output/bar/index.html']) end it 'sets the paths' do subject expect(rep.paths).to eql(foo: ['/foo/', '/bar/']) end it 'adds to paths_to_reps' do subject expect(paths_to_reps).to have_key('/foo/index.html') expect(paths_to_reps).to have_key('/bar/index.html') end end end end end describe '#strip_index_filename' do subject { item_rep_router.strip_index_filename(basic_path) } context 'basic path ends with /index.html' do let(:basic_path) { '/bar/index.html' } it { is_expected.to eql('/bar/') } end context 'basic path contains /index.html' do let(:basic_path) { '/bar/index.html/foo' } it { is_expected.to eql('/bar/index.html/foo') } end context 'basic path ends with xindex.html' do let(:basic_path) { '/bar/xindex.html' } it { is_expected.to eql('/bar/xindex.html') } end context 'basic path does not contain /index.html' do let(:basic_path) { '/bar/foo.html' } it { is_expected.to eql('/bar/foo.html') } end end end nanoc-4.8.0/spec/nanoc/base/services/outdatedness_rules_spec.rb0000644000004100000410000003476713134141051024723 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::Int::OutdatednessRules do describe '#apply' do subject { rule_class.instance.apply(obj, outdatedness_checker) } let(:obj) { item_rep } let(:outdatedness_checker) do Nanoc::Int::OutdatednessChecker.new( site: site, checksum_store: checksum_store, checksums: checksums, dependency_store: dependency_store, action_sequence_store: action_sequence_store, action_sequences: action_sequences, reps: reps, ) end let(:item_rep) { Nanoc::Int::ItemRep.new(item, :default) } let(:item) { Nanoc::Int::Item.new('stuff', {}, '/foo.md') } let(:config) { Nanoc::Int::Configuration.new } let(:code_snippets) { [] } let(:objects) { [config] + code_snippets + [item] } let(:site) do Nanoc::Int::Site.new( config: config, code_snippets: code_snippets, data_source: Nanoc::Int::InMemDataSource.new([], []), ) end let(:action_sequences) { {} } let(:reps) { Nanoc::Int::ItemRepRepo.new } let(:dependency_store) { Nanoc::Int::DependencyStore.new(items, layouts, config) } let(:action_sequence_store) { Nanoc::Int::ActionSequenceStore.new } let(:checksum_store) { Nanoc::Int::ChecksumStore.new(objects: objects) } let(:checksums) do Nanoc::Int::Compiler::Stages::CalculateChecksums.new( items: items, layouts: layouts, code_snippets: code_snippets, config: config, ).run end let(:items) { Nanoc::Int::ItemCollection.new(config, [item]) } let(:layouts) { Nanoc::Int::LayoutCollection.new(config) } before do allow(site).to receive(:code_snippets).and_return(code_snippets) allow(site).to receive(:config).and_return(config) end context 'CodeSnippetsModified' do let(:rule_class) { Nanoc::Int::OutdatednessRules::CodeSnippetsModified } context 'no snippets' do let(:code_snippets) { [] } it { is_expected.not_to be } end context 'only non-outdated snippets' do let(:code_snippet) { Nanoc::Int::CodeSnippet.new('asdf', 'lib/foo.md') } let(:code_snippets) { [code_snippet] } before { checksum_store.add(code_snippet) } it { is_expected.not_to be } end context 'only outdated snippets' do let(:code_snippet) { Nanoc::Int::CodeSnippet.new('asdf', 'lib/foo.md') } let(:code_snippet_old) { Nanoc::Int::CodeSnippet.new('aaaaaaaa', 'lib/foo.md') } let(:code_snippets) { [code_snippet] } before { checksum_store.add(code_snippet_old) } it { is_expected.to be } end end context 'NotWritten' do let(:rule_class) { Nanoc::Int::OutdatednessRules::NotWritten } context 'no path' do before { item_rep.paths = {} } it { is_expected.not_to be } end context 'path for last snapshot' do let(:path) { 'foo.txt' } before { item_rep.raw_paths = { last: [path] } } context 'not written' do it { is_expected.to be } end context 'written' do before { File.write(path, 'hello') } it { is_expected.not_to be } end end context 'path for other snapshot' do let(:path) { 'foo.txt' } before { item_rep.raw_paths = { donkey: [path] } } context 'not written' do it { is_expected.to be } end context 'written' do before { File.write(path, 'hello') } it { is_expected.not_to be } end end end context 'ContentModified' do let(:rule_class) { Nanoc::Int::OutdatednessRules::ContentModified } context 'item' do let(:obj) { item } before { reps << item_rep } context 'no checksum available' do it { is_expected.to be } end context 'checksum available and same' do before { checksum_store.add(item) } it { is_expected.not_to be } end context 'checksum available, but content different' do let(:old_item) { Nanoc::Int::Item.new('other stuff!!!!', {}, '/foo.md') } before { checksum_store.add(old_item) } it { is_expected.to be } end context 'checksum available, but attributes different' do let(:old_item) { Nanoc::Int::Item.new('stuff', { greeting: 'hi' }, '/foo.md') } before { checksum_store.add(old_item) } it { is_expected.not_to be } end end context 'item rep' do let(:obj) { item_rep } context 'no checksum available' do it { is_expected.to be } end context 'checksum available and same' do before { checksum_store.add(item) } it { is_expected.not_to be } end context 'checksum available, but content different' do let(:old_item) { Nanoc::Int::Item.new('other stuff!!!!', {}, '/foo.md') } before { checksum_store.add(old_item) } it { is_expected.to be } end context 'checksum available, but attributes different' do let(:old_item) { Nanoc::Int::Item.new('stuff', { greeting: 'hi' }, '/foo.md') } before { checksum_store.add(old_item) } it { is_expected.not_to be } end end end context 'AttributesModified' do let(:rule_class) { Nanoc::Int::OutdatednessRules::AttributesModified } context 'item' do let(:obj) { item } before { reps << item_rep } context 'no checksum available' do it { is_expected.to be } end context 'checksum available and same' do before { checksum_store.add(item) } it { is_expected.not_to be } end context 'checksum available, but content different' do let(:old_item) { Nanoc::Int::Item.new('other stuff!!!!', {}, '/foo.md') } before { checksum_store.add(old_item) } it { is_expected.not_to be } end context 'checksum available, but attributes different' do let(:old_item) { Nanoc::Int::Item.new('stuff', { greeting: 'hi' }, '/foo.md') } before { checksum_store.add(old_item) } it { is_expected.to be } it 'has the one changed attribute' do expect(subject.attributes).to contain_exactly(:greeting) end end context 'attribute kept identical' do let(:item) { Nanoc::Int::Item.new('stuff', { greeting: 'hi' }, '/foo.md') } let(:old_item) { Nanoc::Int::Item.new('stuff', { greeting: 'hi' }, '/foo.md') } before { checksum_store.add(old_item) } it 'has the one changed attribute' do expect(subject).to be_nil end end context 'attribute changed' do let(:item) { Nanoc::Int::Item.new('stuff', { greeting: 'hi' }, '/foo.md') } let(:old_item) { Nanoc::Int::Item.new('stuff', { greeting: 'ho' }, '/foo.md') } before { checksum_store.add(old_item) } it 'has the one changed attribute' do expect(subject.attributes).to contain_exactly(:greeting) end end context 'attribute deleted' do let(:item) { Nanoc::Int::Item.new('stuff', { greeting: 'hi' }, '/foo.md') } let(:old_item) { Nanoc::Int::Item.new('stuff', {}, '/foo.md') } before { checksum_store.add(old_item) } it 'has the one changed attribute' do expect(subject.attributes).to contain_exactly(:greeting) end end context 'attribute added' do let(:item) { Nanoc::Int::Item.new('stuff', {}, '/foo.md') } let(:old_item) { Nanoc::Int::Item.new('stuff', { greeting: 'hi' }, '/foo.md') } before { checksum_store.add(old_item) } it 'has the one changed attribute' do expect(subject.attributes).to contain_exactly(:greeting) end end end context 'item rep' do let(:obj) { item_rep } context 'no checksum available' do it { is_expected.to be } end context 'checksum available and same' do before { checksum_store.add(item) } it { is_expected.not_to be } end context 'checksum available, but content different' do let(:old_item) { Nanoc::Int::Item.new('other stuff!!!!', {}, '/foo.md') } before { checksum_store.add(old_item) } it { is_expected.not_to be } end context 'checksum available, but attributes different' do let(:old_item) { Nanoc::Int::Item.new('stuff', { greeting: 'hi' }, '/foo.md') } before { checksum_store.add(old_item) } it { is_expected.to be } it 'has the one changed attribute' do expect(subject.attributes).to contain_exactly(:greeting) end end end context 'config' do # TODO end end context 'RulesModified' do let(:rule_class) { Nanoc::Int::OutdatednessRules::RulesModified } let(:old_mem) do Nanoc::Int::ActionSequence.build(item_rep) do |b| b.add_filter(:erb, {}) end end let(:action_sequences) { { item_rep => new_mem } } before do action_sequence_store[item_rep] = old_mem.serialize end context 'memory is the same' do let(:new_mem) { old_mem } it { is_expected.not_to be } end context 'memory is different' do let(:new_mem) do Nanoc::Int::ActionSequence.build(item_rep) do |b| b.add_filter(:erb, {}) b.add_filter(:donkey, {}) end end it { is_expected.to be } end end describe '#{Content,Attributes}Modified' do subject do [ Nanoc::Int::OutdatednessRules::ContentModified, Nanoc::Int::OutdatednessRules::AttributesModified, ].map { |c| !!c.instance.apply(new_obj, outdatedness_checker) } # rubocop:disable Style/DoubleNegation end let(:stored_obj) { raise 'override me' } let(:new_obj) { raise 'override me' } let(:items) { Nanoc::Int::ItemCollection.new(config, [new_obj]) } shared_examples 'a document' do let(:stored_obj) { klass.new('a', {}, '/foo.md') } let(:new_obj) { stored_obj } context 'no checksum data' do context 'not stored' do it { is_expected.to eql([true, true]) } end context 'stored' do before { checksum_store.add(stored_obj) } context 'but content changed afterwards' do let(:new_obj) { klass.new('aaaaaaaa', {}, '/foo.md') } it { is_expected.to eql([true, false]) } end context 'but attributes changed afterwards' do let(:new_obj) { klass.new('a', { animal: 'donkey' }, '/foo.md') } it { is_expected.to eql([false, true]) } end context 'and unchanged' do it { is_expected.to eql([false, false]) } end end end context 'checksum_data' do let(:stored_obj) { klass.new('a', {}, '/foo.md', checksum_data: 'cs-data') } let(:new_obj) { stored_obj } context 'not stored' do it { is_expected.to eql([true, true]) } end context 'stored' do before { checksum_store.add(stored_obj) } context 'but checksum data afterwards' do # NOTE: ignored for attributes! let(:new_obj) { klass.new('a', {}, '/foo.md', checksum_data: 'cs-data-new') } it { is_expected.to eql([true, false]) } end context 'and unchanged' do it { is_expected.to eql([false, false]) } end end end context 'content_checksum_data' do let(:stored_obj) { klass.new('a', {}, '/foo.md', content_checksum_data: 'cs-data') } let(:new_obj) { stored_obj } context 'not stored' do it { is_expected.to eql([true, true]) } end context 'stored' do before { checksum_store.add(stored_obj) } context 'but checksum data afterwards' do let(:new_obj) { klass.new('a', {}, '/foo.md', content_checksum_data: 'cs-data-new') } it { is_expected.to eql([true, false]) } end context 'and unchanged' do it { is_expected.to eql([false, false]) } end end end context 'attributes_checksum_data' do # NOTE: attributes_checksum_data is ignored! let(:stored_obj) { klass.new('a', {}, '/foo.md', attributes_checksum_data: 'cs-data') } let(:new_obj) { stored_obj } context 'not stored' do it { is_expected.to eql([true, true]) } end context 'stored' do before { checksum_store.add(stored_obj) } context 'but checksum data afterwards' do let(:new_obj) { klass.new('a', {}, '/foo.md', attributes_checksum_data: 'cs-data-new') } it { is_expected.to eql([false, false]) } end context 'and unchanged' do it { is_expected.to eql([false, false]) } end end end end context 'item' do let(:klass) { Nanoc::Int::Item } it_behaves_like 'a document' end context 'layout' do let(:klass) { Nanoc::Int::Layout } it_behaves_like 'a document' end # … end describe 'UsesAlwaysOutdatedFilter' do let(:rule_class) { Nanoc::Int::OutdatednessRules::UsesAlwaysOutdatedFilter } let(:action_sequences) { { item_rep => mem } } context 'unknown filter' do let(:mem) do Nanoc::Int::ActionSequence.build(item_rep) do |b| b.add_snapshot(:donkey, '/foo.md') b.add_filter(:asdf, {}) end end it { is_expected.not_to be } end context 'known filter, not always outdated' do let(:mem) do Nanoc::Int::ActionSequence.build(item_rep) do |b| b.add_snapshot(:donkey, '/foo.md') b.add_filter(:erb, {}) end end it { is_expected.not_to be } end context 'known filter, always outdated' do let(:mem) do Nanoc::Int::ActionSequence.build(item_rep) do |b| b.add_snapshot(:donkey, '/foo.md') b.add_filter(:xsl, {}) end end it { is_expected.to be } end end end end nanoc-4.8.0/spec/nanoc/base/directed_graph_spec.rb0000644000004100000410000001230213134141051022105 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::Int::DirectedGraph do subject(:graph) { described_class.new(%w[1 2 3]) } describe '#edges' do subject { graph.edges } context 'empty graph' do it { is_expected.to be_empty } end context 'graph with vertices, but no edges' do before do graph.add_vertex('1') graph.add_vertex('2') end it { is_expected.to be_empty } end context 'graph with edges from previously added vertices' do before do graph.add_vertex('1') graph.add_vertex('2') graph.add_vertex('3') graph.add_edge('1', '2') graph.add_edge('1', '3') end it { is_expected.to match_array([[0, 1, nil], [0, 2, nil]]) } end context 'graph with edges from new vertices' do before do graph.add_edge('1', '2') graph.add_edge('1', '3') end it { is_expected.to match_array([[0, 1, nil], [0, 2, nil]]) } end context 'graph with edge props' do before do graph.add_edge('1', '2', props: { name: 'Mr. C' }) graph.add_edge('1', '3', props: { name: 'Cooper' }) end it { is_expected.to match_array([[0, 1, { name: 'Mr. C' }], [0, 2, { name: 'Cooper' }]]) } end end describe '#props_for' do subject { graph.props_for('1', '2') } context 'no edge' do it { is_expected.to be_nil } end context 'edge, but no props' do before { graph.add_edge('1', '2') } it { is_expected.to be_nil } end context 'edge with props' do before { graph.add_edge('1', '2', props: { name: 'Mr. C' }) } it { is_expected.to eq(name: 'Mr. C') } context 'deleted edge (#delete_edges_to)' do before { graph.delete_edges_to('2') } it { is_expected.to be_nil } end end end describe '#direct_predecessors_of' do subject { graph.direct_predecessors_of('2') } context 'no edges' do it { is_expected.to be_empty } end context 'one edge to' do before { graph.add_edge('1', '2') } it { is_expected.to eq(['1']) } end context 'two edges to' do before do graph.add_edge('1', '2') graph.add_edge('3', '2') end it { is_expected.to match_array(%w[1 3]) } end context 'edge from' do before { graph.add_edge('2', '3') } it { is_expected.to be_empty } end end describe '#direct_successors_of' do subject { graph.direct_successors_of('2') } context 'no edges' do it { is_expected.to be_empty } end context 'one edge to' do before { graph.add_edge('1', '2') } it { is_expected.to be_empty } end context 'one edge from' do before { graph.add_edge('2', '3') } it { is_expected.to eq(['3']) } end context 'two edges from' do before do graph.add_edge('2', '1') graph.add_edge('2', '3') end it { is_expected.to match_array(%w[1 3]) } end end describe '#predecessors_of' do subject { graph.predecessors_of('2') } context 'no predecessors' do before do graph.add_edge('2', '3') end it { is_expected.to be_empty } end context 'direct predecessor' do before do graph.add_edge('2', '3') graph.add_edge('1', '2') end context 'no indirect predecessors' do it { is_expected.to match_array(['1']) } end context 'indirect predecessors' do before { graph.add_edge('3', '1') } it { is_expected.to match_array(%w[1 2 3]) } end end end describe '#successors_of' do subject { graph.successors_of('2') } context 'no successors' do before do graph.add_edge('1', '2') end it { is_expected.to be_empty } end context 'direct predecessor' do before do graph.add_edge('1', '2') graph.add_edge('2', '3') end context 'no indirect successors' do it { is_expected.to match_array(['3']) } end context 'indirect successors' do before { graph.add_edge('3', '1') } it { is_expected.to match_array(%w[1 2 3]) } end end end describe '#inspect' do subject { graph.inspect } context 'empty graph' do it { is_expected.to eq('Nanoc::Int::DirectedGraph()') } end context 'one edge, no props' do before do graph.add_edge('1', '2') end it { is_expected.to eq('Nanoc::Int::DirectedGraph("1" -> "2" props=nil)') } end context 'two edges, no props' do before do graph.add_edge('1', '2') graph.add_edge('2', '3') end it { is_expected.to eq('Nanoc::Int::DirectedGraph("1" -> "2" props=nil, "2" -> "3" props=nil)') } end context 'one edge, props' do before do graph.add_edge('1', '2', props: 'giraffe') end it { is_expected.to eq('Nanoc::Int::DirectedGraph("1" -> "2" props="giraffe")') } end context 'two edges, props' do before do graph.add_edge('1', '2', props: 'donkey') graph.add_edge('2', '3', props: 'zebra') end it { is_expected.to eq('Nanoc::Int::DirectedGraph("1" -> "2" props="donkey", "2" -> "3" props="zebra")') } end end end nanoc-4.8.0/spec/nanoc/base/errors/0000755000004100000410000000000013134141051017120 5ustar www-datawww-datananoc-4.8.0/spec/nanoc/base/errors/dependency_cycle_spec.rb0000644000004100000410000000221313134141051023752 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::Int::Errors::DependencyCycle do subject(:error) { described_class.new(stack) } let(:stack) do [ rep_a, rep_b, rep_c, rep_d, rep_e, rep_b, ] end let(:rep_a) { Nanoc::Int::ItemRep.new(Nanoc::Int::Item.new('a', {}, '/a.md'), :default) } let(:rep_b) { Nanoc::Int::ItemRep.new(Nanoc::Int::Item.new('b', {}, '/b.md'), :default) } let(:rep_c) { Nanoc::Int::ItemRep.new(Nanoc::Int::Item.new('c', {}, '/c.md'), :default) } let(:rep_d) { Nanoc::Int::ItemRep.new(Nanoc::Int::Item.new('d', {}, '/d.md'), :default) } let(:rep_e) { Nanoc::Int::ItemRep.new(Nanoc::Int::Item.new('e', {}, '/e.md'), :default) } it 'has an informative error message' do expected = <<~EOS The site cannot be compiled because there is a dependency cycle: (1) item /b.md, rep :default, uses compiled content of (2) item /c.md, rep :default, uses compiled content of (3) item /d.md, rep :default, uses compiled content of (4) item /e.md, rep :default, uses compiled content of (1) EOS expect(error.message).to eql(expected) end end nanoc-4.8.0/spec/nanoc/base/memoization_spec.rb0000644000004100000410000000442213134141051021500 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::Int::Memoization do class MemoizationSpecSample1 extend Nanoc::Int::Memoization def initialize(value) @value = value end def run(n) @value * 10 + n end memoize :run end class MemoizationSpecSample2 extend Nanoc::Int::Memoization def initialize(value) @value = value end def run(n) @value * 100 + n end memoize :run end class MemoizationSpecUpcaser extend Nanoc::Int::Memoization def run(value) value.upcase end memoize :run end class MemoizationSpecUpcaserInlineSyntax extend Nanoc::Int::Memoization memoized def run(value) value.upcase end end class MemoizationSpecInlineSyntaxReturn extend Nanoc::Int::Memoization class << self attr_reader :sym end def self.record(sym) @sym = sym end record memoized def run; end end example do sample1a = MemoizationSpecSample1.new(10) sample1b = MemoizationSpecSample1.new(15) sample2a = MemoizationSpecSample2.new(20) sample2b = MemoizationSpecSample2.new(25) 3.times do expect(sample1a.run(5)).to eq(10 * 10 + 5) expect(sample1b.run(7)).to eq(10 * 15 + 7) expect(sample2a.run(5)).to eq(100 * 20 + 5) expect(sample2b.run(7)).to eq(100 * 25 + 7) end end it 'supports frozen objects' do sample = MemoizationSpecSample1.new(10) sample.freeze sample.run(5) end it 'supports memoized def … syntax' do upcaser = MemoizationSpecUpcaserInlineSyntax.new expect(upcaser.run('hi')).to eq('HI') end it 'does not crash on #inspect' do upcaser = MemoizationSpecUpcaser.new 10_000.times do |i| upcaser.run("hello world #{i}") end GC.start GC.start upcaser.inspect end it 'returns method name' do expect(MemoizationSpecInlineSyntaxReturn.sym).to eq(:run) end it 'sends notifications' do sample = MemoizationSpecSample1.new(10) expect { sample.run(5) }.to send_notification(:memoization_miss, 'MemoizationSpecSample1#run') expect { sample.run(5) }.to send_notification(:memoization_hit, 'MemoizationSpecSample1#run') expect { sample.run(5) }.to send_notification(:memoization_hit, 'MemoizationSpecSample1#run') end end nanoc-4.8.0/spec/nanoc/base/repos/0000755000004100000410000000000013134141051016734 5ustar www-datawww-datananoc-4.8.0/spec/nanoc/base/repos/compiled_content_cache_spec.rb0000644000004100000410000000254613134141051024753 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::Int::CompiledContentCache do let(:cache) { described_class.new(items: items) } let(:items) { [item] } let(:item) { Nanoc::Int::Item.new('asdf', {}, '/foo.md') } let(:item_rep) { Nanoc::Int::ItemRep.new(item, :default) } let(:other_item) { Nanoc::Int::Item.new('asdf', {}, '/sneaky.md') } let(:other_item_rep) { Nanoc::Int::ItemRep.new(other_item, :default) } let(:content) { Nanoc::Int::Content.create('omg') } it 'has no content by default' do expect(cache[item_rep]).to be_nil end context 'setting content on known item' do before { cache[item_rep] = { last: content } } it 'has content' do expect(cache[item_rep][:last].string).to eql('omg') end context 'after storing and loading' do before do cache.store cache.load end it 'has content' do expect(cache[item_rep][:last].string).to eql('omg') end end end context 'setting content on unknown item' do before { cache[other_item_rep] = { last: content } } it 'has content' do expect(cache[other_item_rep][:last].string).to eql('omg') end context 'after storing and loading' do before do cache.store cache.load end it 'has no content' do expect(cache[other_item_rep]).to be_nil end end end end nanoc-4.8.0/spec/nanoc/base/repos/store_spec.rb0000644000004100000410000000424713134141051021436 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::Int::Store do describe '#tmp_path_for' do context 'passing site' do subject { described_class.tmp_path_for(site: site, store_name: 'giraffes') } let(:site) do Nanoc::Int::Site.new( config: config, code_snippets: code_snippets, data_source: Nanoc::Int::InMemDataSource.new(items, layouts), ) end let(:code_snippets) { [] } let(:items) { [] } let(:layouts) { [] } context 'no env specified' do let(:config) { Nanoc::Int::Configuration.new(hash: config_hash).with_defaults.with_environment } context 'output dir at root is specified' do let(:config_hash) { { output_dir: 'output-default' } } it { is_expected.to eql('tmp/nanoc/b592240c777c6/giraffes') } end context 'output dir in default env is specified' do let(:config_hash) { { environments: { default: { output_dir: 'output-default' } } } } it { is_expected.to eql('tmp/nanoc/b592240c777c6/giraffes') } end context 'output dir in other env is specified' do let(:config_hash) { { environments: { production: { output_dir: 'output-production' } } } } it { is_expected.to eql('tmp/nanoc/1029d67644815/giraffes') } end end context 'env specified' do let(:config) { Nanoc::Int::Configuration.new(env_name: 'staging', hash: config_hash).with_defaults.with_environment } context 'output dir at root is specified' do let(:config_hash) { { output_dir: 'output-default' } } it { is_expected.to eql('tmp/nanoc/b592240c777c6/giraffes') } end context 'output dir in given env is specified' do let(:config_hash) { { environments: { staging: { output_dir: 'output-staging' } } } } it { is_expected.to eql('tmp/nanoc/9d274da4d73ba/giraffes') } end context 'output dir in other env is specified' do let(:config_hash) { { environments: { production: { output_dir: 'output-production' } } } } it { is_expected.to eql('tmp/nanoc/1029d67644815/giraffes') } end end end end end nanoc-4.8.0/spec/nanoc/base/repos/checksum_store_spec.rb0000644000004100000410000000745413134141051023323 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::Int::ChecksumStore do let(:store) { described_class.new(objects: objects) } let(:objects) { [item, code_snippet] } let(:item) { Nanoc::Int::Item.new('asdf', item_attributes, '/foo.md') } let(:other_item) { Nanoc::Int::Item.new('asdf', other_item_attributes, '/sneaky.md') } let(:item_attributes) { {} } let(:other_item_attributes) { {} } let(:code_snippet) { Nanoc::Int::CodeSnippet.new('def hi ; end', 'lib/foo.rb') } let(:other_code_snippet) { Nanoc::Int::CodeSnippet.new('def ho ; end', 'lib/bar.rb') } context 'nothing added' do it 'has no checksum' do expect(store[item]).to be_nil end it 'has no content checksum' do expect(store.content_checksum_for(item)).to be_nil end it 'has no attributes checksum' do expect(store.attributes_checksum_for(item)).to be_nil end end context 'setting content on known non-document' do before { store.add(code_snippet) } it 'has checksum' do expect(store[code_snippet]).not_to be_nil end it 'has no content checksum' do expect(store.content_checksum_for(code_snippet)).to be_nil end it 'has no attributes checksum' do expect(store.attributes_checksum_for(code_snippet)).to be_nil end context 'after storing and loading' do before do store.store store.load end it 'has checksum' do expect(store[code_snippet]).not_to be_nil end end end context 'setting content on unknown non-document' do before { store.add(other_code_snippet) } it 'has checksum' do expect(store[other_code_snippet]).not_to be_nil end it 'has no content checksum' do expect(store.content_checksum_for(other_code_snippet)).to be_nil end it 'has no attributes checksum' do expect(store.attributes_checksum_for(other_code_snippet)).to be_nil end context 'after storing and loading' do before do store.store store.load end it 'has no checksum' do expect(store[other_code_snippet]).to be_nil end end end context 'setting content on known item' do before { store.add(item) } it 'has checksum' do expect(store[item]).not_to be_nil end it 'has content checksum' do expect(store.content_checksum_for(item)).not_to be_nil end it 'has attributes checksum' do expect(store.attributes_checksum_for(item)).not_to be_nil expect(store.attributes_checksum_for(item)).to eq({}) end context 'item has attributes' do let(:item_attributes) { { animal: 'donkey' } } it 'has attribute checksum for specified attribute' do expect(store.attributes_checksum_for(item)).to have_key(:animal) end end context 'after storing and loading' do before do store.store store.load end it 'has checksum' do expect(store[item]).not_to be_nil end end end context 'setting content on unknown item' do before { store.add(other_item) } it 'has checksum' do expect(store[other_item]).not_to be_nil end it 'has content checksum' do expect(store.content_checksum_for(other_item)).not_to be_nil end it 'has attributes checksum' do expect(store.attributes_checksum_for(other_item)).not_to be_nil end context 'item has attributes' do let(:other_item_attributes) { { location: 'Bernauer Str.' } } it 'has attribute checksum for specified attribute' do expect(store.attributes_checksum_for(other_item)).to have_key(:location) end end context 'after storing and loading' do before do store.store store.load end it 'has no checksum' do expect(store[other_item]).to be_nil end end end end nanoc-4.8.0/spec/nanoc/base/repos/dependency_store_spec.rb0000644000004100000410000003260213134141051023630 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::Int::DependencyStore do let(:store) { described_class.new(items, layouts, config) } let(:item_a) { Nanoc::Int::Item.new('a', {}, '/a.md') } let(:item_b) { Nanoc::Int::Item.new('b', {}, '/b.md') } let(:item_c) { Nanoc::Int::Item.new('c', {}, '/c.md') } let(:layout_a) { Nanoc::Int::Layout.new('la', {}, '/la.md') } let(:layout_b) { Nanoc::Int::Layout.new('lb', {}, '/lb.md') } let(:items) { Nanoc::Int::ItemCollection.new(config, [item_a, item_b, item_c]) } let(:layouts) { Nanoc::Int::LayoutCollection.new(config, [layout_a, layout_b]) } let(:config) { Nanoc::Int::Configuration.new } describe '#dependencies_causing_outdatedness_of' do context 'no dependencies' do it 'returns nothing for each' do expect(store.dependencies_causing_outdatedness_of(item_a)).to be_empty expect(store.dependencies_causing_outdatedness_of(item_b)).to be_empty expect(store.dependencies_causing_outdatedness_of(item_c)).to be_empty end end context 'one dependency' do context 'dependency on config, no props' do before do store.record_dependency(item_a, config) end it 'returns one dependency' do deps = store.dependencies_causing_outdatedness_of(item_a) expect(deps.size).to eql(1) end it 'returns dependency from a onto config' do deps = store.dependencies_causing_outdatedness_of(item_a) expect(deps[0].from).to eql(config) expect(deps[0].to).to eql(item_a) end it 'returns true for all props by default' do deps = store.dependencies_causing_outdatedness_of(item_a) expect(deps[0].props.raw_content?).to eq(true) expect(deps[0].props.attributes?).to eq(true) expect(deps[0].props.compiled_content?).to eq(true) expect(deps[0].props.path?).to eq(true) end it 'returns nothing for the others' do expect(store.dependencies_causing_outdatedness_of(item_b)).to be_empty expect(store.dependencies_causing_outdatedness_of(item_c)).to be_empty end end context 'dependency on config, generic attributes prop' do before do store.record_dependency(item_a, config, attributes: true) end it 'returns false for all unspecified props' do deps = store.dependencies_causing_outdatedness_of(item_a) expect(deps[0].props.raw_content?).to eq(false) expect(deps[0].props.compiled_content?).to eq(false) expect(deps[0].props.path?).to eq(false) end it 'returns the specified props' do deps = store.dependencies_causing_outdatedness_of(item_a) expect(deps[0].props.attributes?).to eq(true) end end context 'dependency on config, specific attributes prop' do before do store.record_dependency(item_a, config, attributes: [:donkey]) end it 'returns false for all unspecified props' do deps = store.dependencies_causing_outdatedness_of(item_a) expect(deps[0].props.raw_content?).to eq(false) expect(deps[0].props.compiled_content?).to eq(false) expect(deps[0].props.path?).to eq(false) end it 'returns the specified props' do deps = store.dependencies_causing_outdatedness_of(item_a) expect(deps[0].props.attributes?).to eq(true) expect(deps[0].props.attributes).to contain_exactly(:donkey) end end context 'dependency on items, generic prop' do before do store.record_dependency(item_a, items) end it 'creates one dependency' do deps = store.dependencies_causing_outdatedness_of(item_a) expect(deps.size).to eql(1) end it 'returns true for all props' do deps = store.dependencies_causing_outdatedness_of(item_a) expect(deps[0].props.raw_content?).to be expect(deps[0].props.compiled_content?).to be expect(deps[0].props.path?).to be expect(deps[0].props.attributes?).to be end end context 'no props' do before do store.record_dependency(item_a, item_b) end it 'returns one dependency' do deps = store.dependencies_causing_outdatedness_of(item_a) expect(deps.size).to eql(1) end it 'returns dependency from b to a' do deps = store.dependencies_causing_outdatedness_of(item_a) expect(deps[0].from).to eql(item_b) expect(deps[0].to).to eql(item_a) end it 'returns true for all props by default' do deps = store.dependencies_causing_outdatedness_of(item_a) expect(deps[0].props.raw_content?).to eq(true) expect(deps[0].props.attributes?).to eq(true) expect(deps[0].props.compiled_content?).to eq(true) expect(deps[0].props.path?).to eq(true) end it 'returns nothing for the others' do expect(store.dependencies_causing_outdatedness_of(item_b)).to be_empty expect(store.dependencies_causing_outdatedness_of(item_c)).to be_empty end end context 'one prop' do before do store.record_dependency(item_a, item_b, compiled_content: true) end it 'returns false for all unspecified props' do deps = store.dependencies_causing_outdatedness_of(item_a) expect(deps[0].props.raw_content?).to eq(false) expect(deps[0].props.attributes?).to eq(false) expect(deps[0].props.path?).to eq(false) end it 'returns the specified props' do deps = store.dependencies_causing_outdatedness_of(item_a) expect(deps[0].props.compiled_content?).to eq(true) end end context 'two props' do before do store.record_dependency(item_a, item_b, compiled_content: true) store.record_dependency(item_a, item_b, attributes: true) end it 'returns false for all unspecified props' do deps = store.dependencies_causing_outdatedness_of(item_a) expect(deps[0].props.raw_content?).to eq(false) expect(deps[0].props.path?).to eq(false) end it 'returns the specified props' do deps = store.dependencies_causing_outdatedness_of(item_a) expect(deps[0].props.attributes?).to eq(true) expect(deps[0].props.compiled_content?).to eq(true) end end end context 'two dependency in a chain' do before do store.record_dependency(item_a, item_b) store.record_dependency(item_b, item_c) end it 'returns one dependency for object A' do deps = store.dependencies_causing_outdatedness_of(item_a) expect(deps.size).to eql(1) expect(deps[0].from).to eql(item_b) end it 'returns one dependency for object B' do deps = store.dependencies_causing_outdatedness_of(item_b) expect(deps.size).to eql(1) expect(deps[0].from).to eql(item_c) end it 'returns nothing for the others' do expect(store.dependencies_causing_outdatedness_of(item_c)).to be_empty end end end describe 'reloading - item a -> b' do before do store.record_dependency(item_a, item_b, compiled_content: true) store.record_dependency(item_a, item_b, attributes: true) store.store store.items = items_after store.load end context 'no new items' do let(:items_after) { items } it 'has the right dependencies for item A' do deps = store.dependencies_causing_outdatedness_of(item_a) expect(deps.size).to eql(1) expect(deps[0].from).to eql(item_b) expect(deps[0].to).to eql(item_a) expect(deps[0].props.raw_content?).to eq(false) expect(deps[0].props.attributes?).to eq(true) expect(deps[0].props.compiled_content?).to eq(true) expect(deps[0].props.path?).to eq(false) end it 'has the right dependencies for item B' do deps = store.dependencies_causing_outdatedness_of(item_b) expect(deps).to be_empty end it 'has the right dependencies for item C' do deps = store.dependencies_causing_outdatedness_of(item_c) expect(deps).to be_empty end end context 'one new item' do let(:items_after) do Nanoc::Int::ItemCollection.new(config, [item_a, item_b, item_c, item_d]) end let(:item_d) { Nanoc::Int::Item.new('d', {}, '/d.md') } it 'does not mark items as outdated' do expect(store.objects_causing_outdatedness_of(item_a)).not_to include(item_d) expect(store.objects_causing_outdatedness_of(item_b)).not_to include(item_d) expect(store.objects_causing_outdatedness_of(item_c)).not_to include(item_d) expect(store.objects_causing_outdatedness_of(item_d)).not_to include(item_d) end end end describe 'reloading - item a -> config' do before do store.record_dependency(item_a, config, attributes: [:donkey]) store.store store.load end it 'has the right dependencies for item A' do deps = store.dependencies_causing_outdatedness_of(item_a) expect(deps.size).to eql(1) expect(deps[0].from).to eql(config) expect(deps[0].to).to eql(item_a) expect(deps[0].props.raw_content?).to eq(false) expect(deps[0].props.attributes?).to eq(true) expect(deps[0].props.attributes).to contain_exactly(:donkey) expect(deps[0].props.compiled_content?).to eq(false) expect(deps[0].props.path?).to eq(false) end it 'has the right dependencies for item B' do deps = store.dependencies_causing_outdatedness_of(item_b) expect(deps).to be_empty end it 'has the right dependencies for item C' do deps = store.dependencies_causing_outdatedness_of(item_c) expect(deps).to be_empty end end shared_examples 'records dependencies' do context 'no props' do subject { store.record_dependency(source_obj, item_b) } it 'records a dependency' do expect { subject } .to change { store.objects_causing_outdatedness_of(source_obj) } .from([]) .to([item_b]) end it 'ignores all other objects' do subject expect(other_items).to all(satisfy { |o| store.dependencies_causing_outdatedness_of(o).empty? }) end end context 'compiled content prop' do subject { store.record_dependency(source_obj, target_obj, compiled_content: true) } it 'records a dependency' do expect { subject } .to change { store.objects_causing_outdatedness_of(source_obj) } .from([]) .to([target_obj]) end it 'records a dependency with the right props' do subject deps = store.dependencies_causing_outdatedness_of(source_obj) expect(deps.first.props.attributes?).not_to be expect(deps.first.props.compiled_content?).to be end it 'ignores all other objects' do subject expect(other_items).to all(satisfy { |o| store.dependencies_causing_outdatedness_of(o).empty? }) end end context 'attribute prop (true)' do subject { store.record_dependency(source_obj, target_obj, attributes: true) } it 'records a dependency' do expect { subject } .to change { store.objects_causing_outdatedness_of(source_obj) } .from([]) .to([target_obj]) end it 'records a dependency with the right props' do subject deps = store.dependencies_causing_outdatedness_of(source_obj) expect(deps.first.props.attributes?).to be expect(deps.first.props.attributes).to be expect(deps.first.props.compiled_content?).not_to be end it 'ignores all other objects' do subject expect(other_items).to all(satisfy { |o| store.dependencies_causing_outdatedness_of(o).empty? }) end end context 'attribute prop (true)' do subject { store.record_dependency(source_obj, target_obj, attributes: [:giraffe]) } it 'records a dependency' do expect { subject } .to change { store.objects_causing_outdatedness_of(source_obj) } .from([]) .to([target_obj]) end it 'records a dependency with the right props' do subject deps = store.dependencies_causing_outdatedness_of(source_obj) expect(deps.first.props.attributes?).to be expect(deps.first.props.attributes).to match_array([:giraffe]) expect(deps.first.props.compiled_content?).not_to be end it 'ignores all other objects' do subject expect(other_items).to all(satisfy { |o| store.dependencies_causing_outdatedness_of(o).empty? }) end end end describe '#record_dependency' do context 'item on item' do let(:source_obj) { item_a } let(:target_obj) { item_b } let(:other_items) { [item_c] } include_examples 'records dependencies' end context 'item on layout' do let(:source_obj) { item_a } let(:target_obj) { layout_a } let(:other_items) { [item_b, item_c] } include_examples 'records dependencies' end context 'item on config' do let(:source_obj) { item_a } let(:target_obj) { config } let(:other_items) { [item_b, item_c] } include_examples 'records dependencies' end end end nanoc-4.8.0/spec/nanoc/base/repos/site_loader_spec.rb0000644000004100000410000002003113134141051022561 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::Int::SiteLoader do let(:loader) { described_class.new } describe '#new_empty' do subject { loader.new_empty } it 'has the default configuration' do expect(subject.config).to be_a(Nanoc::Int::Configuration) expect(subject.config[:index_filenames]).to eq(['index.html']) end it 'has no code snippets' do expect(subject.code_snippets).to be_empty end it 'has no items' do expect(subject.items).to be_empty end it 'has no layouts' do expect(subject.layouts).to be_empty end end describe '#new_with_config' do subject { loader.new_with_config(arg) } let(:arg) { { foo: 'bar' } } it 'has a slightly modified configuration' do expect(subject.config).to be_a(Nanoc::Int::Configuration) expect(subject.config[:index_filenames]).to eq(['index.html']) expect(subject.config[:foo]).to eq('bar') end it 'has no code snippets' do expect(subject.code_snippets).to be_empty end it 'has no items' do expect(subject.items).to be_empty end it 'has no layouts' do expect(subject.layouts).to be_empty end end describe '#new_from_cwd' do subject { loader.new_from_cwd } context 'no config file' do it 'errors' do expect { subject }.to raise_error( Nanoc::Int::ConfigLoader::NoConfigFileFoundError, ) end end shared_examples 'a directory with a config file' do it 'has the default configuration' do expect(subject.config).to be_a(Nanoc::Int::Configuration) expect(subject.config[:index_filenames]).to eq(['index.html']) expect(subject.config[:foo]).to eq('bar') end it 'has no code snippets' do expect(subject.code_snippets).to be_empty end it 'has no items' do expect(subject.items).to be_empty end it 'has no layouts' do expect(subject.layouts).to be_empty end context 'some items, layouts, and code snippets' do before do FileUtils.mkdir_p('lib') File.write('lib/foo.rb', '$spirit_animal = :donkey') FileUtils.mkdir_p('content') File.write('content/about.md', 'I am Denis!') FileUtils.mkdir_p('layouts') File.write('layouts/page.erb', '<%= yield %>') end it 'has a code snippet' do expect(subject.code_snippets.size).to eq(1) expect(subject.code_snippets[0].data).to eq('$spirit_animal = :donkey') end it 'has an item' do expect(subject.items.size).to eq(1) expect(subject.items['/about.md'].content).to be_a(Nanoc::Int::TextualContent) expect(subject.items['/about.md'].content.string).to eq('I am Denis!') expect(subject.items['/about.md'].attributes[:content_filename]) .to eq('content/about.md') expect(subject.items['/about.md'].attributes[:extension]) .to eq('md') expect(subject.items['/about.md'].attributes[:filename]) .to eq('content/about.md') expect(subject.items['/about.md'].attributes[:meta_filename]) .to be_nil expect(subject.items['/about.md'].attributes[:mtime]) .to be > Time.now - 5 expect(subject.items['/about.md'].identifier.to_s).to eq('/about.md') end it 'has a layout' do expect(subject.layouts.size).to eq(1) expect(subject.layouts['/page.erb'].content).to be_a(Nanoc::Int::TextualContent) expect(subject.layouts['/page.erb'].content.string).to eq('<%= yield %>') expect(subject.layouts['/page.erb'].attributes[:content_filename]) .to eq('layouts/page.erb') expect(subject.layouts['/page.erb'].attributes[:extension]) .to eq('erb') expect(subject.layouts['/page.erb'].attributes[:filename]) .to eq('layouts/page.erb') expect(subject.layouts['/page.erb'].attributes[:meta_filename]) .to be_nil expect(subject.layouts['/page.erb'].attributes[:mtime]) .to be > Time.now - 5 expect(subject.layouts['/page.erb'].identifier.to_s).to eq('/page.erb') end end end context 'nanoc.yaml config file' do before do File.write('nanoc.yaml', "---\nfoo: bar\n") end it_behaves_like 'a directory with a config file' end context 'config.yaml config file' do before do File.write('config.yaml', "---\nfoo: bar\n") end it_behaves_like 'a directory with a config file' end context 'configuration has non-existant data source' do before do File.write('nanoc.yaml', <<-EOS.gsub(/^ {10}/, '')) data_sources: - type: eenvaleed EOS end it 'raises an error' do expect { subject }.to raise_error(Nanoc::Int::Errors::UnknownDataSource) end end context 'environments defined' do before do File.write('nanoc.yaml', <<-EOS.gsub(/^ {10}/, '')) animal: donkey environments: staging: animal: giraffe EOS end before do expect(ENV).to receive(:fetch).with('NANOC_ENV', 'default').and_return('staging') end it 'does not load environment' do expect(subject.config[:animal]).to eq('giraffe') end end context 'code snippet with data source implementation' do before do FileUtils.mkdir_p('lib') File.write('lib/foo_data_source.rb', <<-EOS.gsub(/^ {10}/, '')) class FooDataSource < Nanoc::DataSource identifier :site_loader_spec_sample def items [ Nanoc::Int::Item.new( 'Generated content!', { generated: true }, '/generated.txt', ) ] end end EOS File.write('nanoc.yaml', <<-EOS.gsub(/^ {10}/, '')) data_sources: - type: site_loader_spec_sample EOS end it 'loads code snippets before items/layouts' do expect(subject.items.size).to eq(1) expect(subject.items['/generated.txt'].content).to be_a(Nanoc::Int::TextualContent) expect(subject.items['/generated.txt'].content.string).to eq('Generated content!') expect(subject.items['/generated.txt'].attributes).to eq(generated: true) expect(subject.items['/generated.txt'].identifier.to_s).to eq('/generated.txt') end end end describe '#code_snippets_from_config' do subject { loader.send(:code_snippets_from_config, config) } let(:config) { Nanoc::Int::Configuration.new.with_defaults } before { FileUtils.mkdir_p('lib') } context 'no explicit encoding specified' do example do File.write('lib/asdf.rb', 'hi 🔥', encoding: 'utf-8') expect(subject.size).to eq(1) expect(subject.first.data).to eq('hi 🔥') end end context '# encoding: x specified' do example do File.write('lib/asdf.rb', "# encoding: iso-8859-1\n\nBRØKEN", encoding: 'iso-8859-1') expect(subject.size).to eq(1) expect(subject.first.data).to eq('BRØKEN') end end context '# coding: x specified' do example do File.write('lib/asdf.rb', "# coding: iso-8859-1\n\nBRØKEN", encoding: 'iso-8859-1') expect(subject.size).to eq(1) expect(subject.first.data).to eq('BRØKEN') end end context '# -*- encoding: x -*- specified' do example do File.write('lib/asdf.rb', "# -*- encoding: iso-8859-1 -*-\n\nBRØKEN", encoding: 'iso-8859-1') expect(subject.size).to eq(1) expect(subject.first.data).to eq('BRØKEN') end end context '# -*- coding: x -*- specified' do example do File.write('lib/asdf.rb', "# -*- coding: iso-8859-1 -*-\n\nBRØKEN", encoding: 'iso-8859-1') expect(subject.size).to eq(1) expect(subject.first.data).to eq('BRØKEN') end end end end nanoc-4.8.0/spec/nanoc/base/repos/outdatedness_store_spec.rb0000644000004100000410000000413513134141051024214 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::Int::OutdatednessStore do subject(:store) { described_class.new(site: site, reps: reps) } let(:site) { double(:site) } let(:reps) { Nanoc::Int::ItemRepRepo.new } let(:item) { Nanoc::Int::Item.new('foo', {}, '/foo.md') } let(:rep) { Nanoc::Int::ItemRep.new(item, :foo) } let(:site) do Nanoc::Int::Site.new( config: config, code_snippets: code_snippets, data_source: Nanoc::Int::InMemDataSource.new(items, layouts), ) end let(:config) { Nanoc::Int::Configuration.new.with_defaults } let(:items) { [] } let(:layouts) { [] } let(:code_snippets) { [] } describe '#include?, #add and #remove' do subject { store.include?(rep) } context 'nothing added' do it { is_expected.not_to be } end context 'rep added' do before { store.add(rep) } it { is_expected.to be } end context 'rep added and removed' do before do store.add(rep) store.remove(rep) end it { is_expected.not_to be } end context 'rep added, removed, and added again' do before do store.add(rep) store.remove(rep) store.add(rep) end it { is_expected.to be } end end describe '#to_a' do subject { store.to_a } context 'nothing added' do it { is_expected.to be_empty } end context 'one rep added' do before { store.add(rep) } it { is_expected.to eql([rep]) } end end describe 'reloading' do subject do store.store store.load store.include?(rep) end context 'not added' do context 'rep part of new reps' do before { reps << rep } it { is_expected.not_to be } end context 'rep not part of new reps' do it { is_expected.not_to be } end end context 'added' do before { store.add(rep) } context 'rep part of new reps' do before { reps << rep } it { is_expected.to be } end context 'rep not part of new reps' do it { is_expected.not_to be } end end end end nanoc-4.8.0/spec/nanoc/base/repos/config_loader_spec.rb0000644000004100000410000001513613134141051023074 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::Int::ConfigLoader do let(:loader) { described_class.new } describe '#new_from_cwd' do subject { loader.new_from_cwd } context 'no config file present' do it 'errors' do expect { subject }.to raise_error( Nanoc::Int::ConfigLoader::NoConfigFileFoundError, ) end end context 'config file present' do before do File.write('nanoc.yaml', YAML.dump(foo: 'bar')) end it 'returns a configuration' do expect(subject).to be_a(Nanoc::Int::Configuration) end it 'has the defaults' do expect(subject[:output_dir]).to eq('output') end it 'has the custom option' do expect(subject[:foo]).to eq('bar') end end context 'config file and parent present' do before do File.write('nanoc.yaml', YAML.dump(parent_config_file: 'parent.yaml')) File.write('parent.yaml', YAML.dump(foo: 'bar')) end it 'returns the configuration' do expect(subject).to be_a(Nanoc::Int::Configuration) end it 'has the defaults' do expect(subject[:output_dir]).to eq('output') end it 'has the custom option' do expect(subject[:foo]).to eq('bar') end it 'does not include parent config option' do expect(subject[:parent_config_file]).to be_nil end end context 'config file present, environment defined' do let(:active_env_name) { 'default' } let(:config) do { foo: 'bar', tofoo: 'bar', environments: { test: { foo: 'test-bar' }, default: { foo: 'default-bar' }, }, } end before do File.write('nanoc.yaml', YAML.dump(config)) end before do expect(ENV).to receive(:fetch).with('NANOC_ENV', 'default').and_return(active_env_name) end it 'returns the configuration' do expect(subject).to be_a(Nanoc::Int::Configuration) end it 'has option defined not within environments' do expect(subject[:tofoo]).to eq('bar') end context 'current env is test' do let(:active_env_name) { 'test' } it 'has the test environment custom option' do expect(subject[:foo]).to eq('test-bar') end end it 'has the default environment custom option' do expect(subject[:foo]).to eq('default-bar') end end end describe '.cwd_is_nanoc_site? + .config_filename_for_cwd' do context 'no config files' do it 'is not considered a nanoc site dir' do expect(described_class.cwd_is_nanoc_site?).to eq(false) expect(described_class.config_filename_for_cwd).to be_nil end end context 'nanoc.yaml config file' do before do File.write('nanoc.yaml', 'stuff') end it 'is considered a nanoc site dir' do expect(described_class.cwd_is_nanoc_site?).to eq(true) expect(described_class.config_filename_for_cwd).to eq(File.expand_path('nanoc.yaml')) end end context 'config.yaml config file' do before do File.write('config.yaml', 'stuff') end it 'is considered a nanoc site dir' do expect(described_class.cwd_is_nanoc_site?).to eq(true) expect(described_class.config_filename_for_cwd).to eq(File.expand_path('config.yaml')) end end end describe '#apply_parent_config' do subject { loader.apply_parent_config(config, processed_paths) } let(:config) { Nanoc::Int::Configuration.new(hash: { foo: 'bar' }) } let(:processed_paths) { ['nanoc.yaml'] } context 'no parent_config_file' do it 'returns self' do expect(subject).to eq(config) end end context 'parent config file is set' do let(:config) do Nanoc::Int::Configuration.new(hash: { parent_config_file: 'foo.yaml', foo: 'bar' }) end context 'parent config file is not present' do it 'errors' do expect { subject }.to raise_error( Nanoc::Int::ConfigLoader::NoParentConfigFileFoundError, ) end end context 'parent config file is present' do context 'parent-child cycle' do before do File.write('foo.yaml', 'parent_config_file: bar.yaml') File.write('bar.yaml', 'parent_config_file: foo.yaml') end it 'errors' do expect { subject }.to raise_error( Nanoc::Int::ConfigLoader::CyclicalConfigFileError, ) end end context 'self parent-child cycle' do before do File.write('foo.yaml', 'parent_config_file: foo.yaml') end it 'errors' do expect { subject }.to raise_error( Nanoc::Int::ConfigLoader::CyclicalConfigFileError, ) end end context 'no parent-child cycle' do before do File.write('foo.yaml', 'animal: giraffe') end it 'returns a configuration' do expect(subject).to be_a(Nanoc::Int::Configuration) end it 'has no defaults (added in #new_from_cwd only)' do expect(subject[:output_dir]).to be_nil end it 'inherits options from parent' do expect(subject[:animal]).to eq('giraffe') end it 'takes options from child' do expect(subject[:foo]).to eq('bar') end it 'does not include parent config option' do expect(subject[:parent_config_file]).to be_nil end end context 'long parent chain' do before do File.write('foo.yaml', "parrots: 43\nparent_config_file: bar.yaml\n") File.write('bar.yaml', "day_one: lasers\nslugs: false\n") end it 'returns a configuration' do expect(subject).to be_a(Nanoc::Int::Configuration) end it 'has no defaults (added in #new_from_cwd only)' do expect(subject[:output_dir]).to be_nil end it 'inherits options from grandparent' do expect(subject[:day_one]).to eq('lasers') expect(subject[:slugs]).to eq(false) end it 'inherits options from parent' do expect(subject[:parrots]).to eq(43) end it 'takes options from child' do expect(subject[:foo]).to eq('bar') end it 'does not include parent config option' do expect(subject[:parent_config_file]).to be_nil end end end end end end nanoc-4.8.0/spec/nanoc/base/repos/snapshot_repo_spec.rb0000644000004100000410000002303013134141051023155 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::Int::SnapshotRepo do subject(:repo) { described_class.new } describe '#get' do subject { repo.get(rep, snapshot_name) } let(:item) { Nanoc::Int::Item.new('contentz', {}, '/foo.md') } let(:rep) { Nanoc::Int::ItemRep.new(item, :foo) } let(:snapshot_name) { :donkey } context 'rep does not exist in repo' do it { is_expected.to be_nil } end context 'rep exists in repo' do before { repo.set(rep, :foobar, Nanoc::Int::TextualContent.new('other content')) } context 'snapshot does not exist in repo' do it { is_expected.to be_nil } end context 'snapshot exists in repo' do before { repo.set(rep, :donkey, Nanoc::Int::TextualContent.new('donkey')) } it { is_expected.to be_some_textual_content('donkey') } end end end describe '#get_all' do subject { repo.get_all(rep) } let(:item) { Nanoc::Int::Item.new('contentz', {}, '/foo.md') } let(:rep) { Nanoc::Int::ItemRep.new(item, :foo) } context 'rep does not exist in repo' do it { is_expected.to eq({}) } end context 'rep exists in repo' do before { repo.set(rep, :foobar, Nanoc::Int::TextualContent.new('donkey')) } it { is_expected.to match(foobar: some_textual_content('donkey')) } end end describe '#set' do subject { repo.set(rep, snapshot_name, contents) } let(:item) { Nanoc::Int::Item.new('contentz', {}, '/foo.md') } let(:rep) { Nanoc::Int::ItemRep.new(item, :foo) } let(:snapshot_name) { :donkey } let(:contents) { Nanoc::Int::TextualContent.new('donkey') } it 'changes the given rep+snapshot' do expect { subject } .to change { repo.get(rep, snapshot_name) } .from(nil) .to(some_textual_content('donkey')) end end describe '#set_all' do subject { repo.set_all(rep, contents_by_snapshot) } let(:other_item) { Nanoc::Int::Item.new('contentz', {}, '/foo.md') } let(:other_rep) { Nanoc::Int::ItemRep.new(other_item, :foo) } let(:item) { Nanoc::Int::Item.new('contentz', {}, '/foo.md') } let(:rep) { Nanoc::Int::ItemRep.new(item, :foo) } let(:contents_by_snapshot) { { donkey: Nanoc::Int::TextualContent.new('donkey') } } it 'changes the given rep+snapshot' do expect { subject } .to change { repo.get(rep, :donkey) } .from(nil) .to(some_textual_content('donkey')) end it 'leaves other reps intact' do expect { subject } .not_to change { repo.get(other_rep, :donkey) } end it 'leaves other snapshots intact' do expect { subject } .not_to change { repo.get(rep, :giraffe) } end end describe '#compiled_content' do subject { repo.compiled_content(rep: rep, snapshot: snapshot_name) } let(:snapshot_name) { raise 'override me' } let(:item) { Nanoc::Int::Item.new('contentz', {}, '/foo.md') } let(:rep) { Nanoc::Int::ItemRep.new(item, :foo) } shared_examples 'a non-moving snapshot with content' do context 'no snapshot def' do it 'raises' do expect { subject }.to raise_error(Nanoc::Int::Errors::NoSuchSnapshot) end end context 'snapshot def exists' do before do rep.snapshot_defs = [Nanoc::Int::SnapshotDef.new(snapshot_name, binary: false)] repo.set_all(rep, snapshot_name => content) end context 'content is textual' do let(:content) { Nanoc::Int::TextualContent.new('hellos') } it { is_expected.to eql('hellos') } end context 'content is binary' do before { File.write('donkey.dat', 'binary data') } let(:content) { Nanoc::Int::BinaryContent.new(File.expand_path('donkey.dat')) } it 'raises' do expect { subject }.to raise_error(Nanoc::Int::Errors::CannotGetCompiledContentOfBinaryItem) end end end end shared_examples 'a non-moving snapshot' do include_examples 'a non-moving snapshot with content' context 'snapshot def exists, but not content' do before do rep.snapshot_defs = [Nanoc::Int::SnapshotDef.new(snapshot_name, binary: false)] repo.set_all(rep, {}) end it 'errors' do expect { subject }.to yield_from_fiber(an_instance_of(Nanoc::Int::Errors::UnmetDependency)) end end end shared_examples 'snapshot :last' do context 'no snapshot def' do it 'errors' do expect { subject }.to raise_error(Nanoc::Int::Errors::NoSuchSnapshot) end end context 'snapshot exists' do context 'snapshot is not final' do before do rep.snapshot_defs = [Nanoc::Int::SnapshotDef.new(snapshot_name, binary: false)] end context 'snapshot content does not exist' do before do repo.set_all(rep, {}) end it 'errors' do expect { subject }.to yield_from_fiber(an_instance_of(Nanoc::Int::Errors::UnmetDependency)) end end context 'snapshot content exists' do context 'content is textual' do before do repo.set(rep, snapshot_name, Nanoc::Int::TextualContent.new('hellos')) end context 'not compiled' do before { rep.compiled = false } it 'raises' do expect { subject }.to yield_from_fiber(an_instance_of(Nanoc::Int::Errors::UnmetDependency)) end end context 'compiled' do before { rep.compiled = true } it { is_expected.to eql('hellos') } end end context 'content is binary' do before do File.write('donkey.dat', 'binary data') repo.set(rep, snapshot_name, Nanoc::Int::BinaryContent.new(File.expand_path('donkey.dat'))) end context 'not compiled' do before { rep.compiled = false } it 'raises' do expect { subject }.to yield_from_fiber(an_instance_of(Nanoc::Int::Errors::UnmetDependency)) end end context 'compiled' do before { rep.compiled = true } it 'raises' do expect { subject }.to raise_error(Nanoc::Int::Errors::CannotGetCompiledContentOfBinaryItem) end end end end end context 'snapshot is final' do before do rep.snapshot_defs = [Nanoc::Int::SnapshotDef.new(snapshot_name, binary: false)] end context 'snapshot content does not exist' do before do repo.set_all(rep, {}) end it 'errors' do expect { subject }.to yield_from_fiber(an_instance_of(Nanoc::Int::Errors::UnmetDependency)) end end context 'snapshot content exists' do context 'content is textual' do before do repo.set(rep, snapshot_name, Nanoc::Int::TextualContent.new('hellos')) end context 'not compiled' do before { rep.compiled = false } it 'errors' do expect { subject }.to yield_from_fiber(an_instance_of(Nanoc::Int::Errors::UnmetDependency)) end end context 'compiled' do before { rep.compiled = true } it { is_expected.to eql('hellos') } end end context 'content is binary' do before do File.write('donkey.dat', 'binary data') repo.set(rep, snapshot_name, Nanoc::Int::BinaryContent.new(File.expand_path('donkey.dat'))) end context 'not compiled' do before { rep.compiled = false } it 'raises' do expect { subject }.to yield_from_fiber(an_instance_of(Nanoc::Int::Errors::UnmetDependency)) end end context 'compiled' do before { rep.compiled = true } it 'raises' do expect { subject }.to raise_error(Nanoc::Int::Errors::CannotGetCompiledContentOfBinaryItem) end end end end end end end context 'snapshot nil' do let(:snapshot_name) { :last } subject { repo.compiled_content(rep: rep, snapshot: nil) } include_examples 'snapshot :last' end context 'snapshot not specified' do subject { repo.compiled_content(rep: rep) } context 'pre exists' do before { repo.set(rep, :pre, Nanoc::Int::TextualContent.new('omg')) } let(:snapshot_name) { :pre } include_examples 'a non-moving snapshot with content' end context 'pre does not exist' do let(:snapshot_name) { :last } include_examples 'snapshot :last' end end context 'snapshot :pre specified' do let(:snapshot_name) { :pre } include_examples 'a non-moving snapshot' end context 'snapshot :post specified' do let(:snapshot_name) { :post } include_examples 'a non-moving snapshot' end context 'snapshot :last specified' do let(:snapshot_name) { :last } include_examples 'snapshot :last' end context 'snapshot :donkey specified' do let(:snapshot_name) { :donkey } include_examples 'a non-moving snapshot' end end end nanoc-4.8.0/spec/nanoc/base/compiler_spec.rb0000644000004100000410000001225513134141051020762 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::Int::Compiler do let(:compiler) do described_class.new( site, compiled_content_cache: compiled_content_cache, checksum_store: checksum_store, action_sequence_store: action_sequence_store, action_provider: action_provider, dependency_store: dependency_store, reps: reps, outdatedness_store: outdatedness_store, ) end let(:checksum_store) { Nanoc::Int::ChecksumStore.new(objects: items) } let(:action_sequence_store) { Nanoc::Int::ActionSequenceStore.new } let(:dependency_store) { Nanoc::Int::DependencyStore.new(items, layouts, config) } let(:reps) { Nanoc::Int::ItemRepRepo.new } let(:outdatedness_store) { Nanoc::Int::OutdatednessStore.new(site: site, reps: reps) } let(:action_provider) { double(:action_provider) } let(:compiled_content_cache) { Nanoc::Int::CompiledContentCache.new(items: items) } let(:rep) { Nanoc::Int::ItemRep.new(item, :default) } let(:item) { Nanoc::Int::Item.new('<%= 1 + 2 %>', {}, '/hi.md') } let(:other_rep) { Nanoc::Int::ItemRep.new(other_item, :default) } let(:other_item) { Nanoc::Int::Item.new('other content', {}, '/other.md') } let(:site) do Nanoc::Int::Site.new( config: config, code_snippets: code_snippets, data_source: Nanoc::Int::InMemDataSource.new(items, layouts), ) end let(:config) { Nanoc::Int::Configuration.new.with_defaults } let(:code_snippets) { [] } let(:items) do Nanoc::Int::ItemCollection.new(config, [item, other_item]) end let(:layouts) do Nanoc::Int::LayoutCollection.new(config) end let(:memory) do actions = [ Nanoc::Int::ProcessingActions::Filter.new(:erb, {}), Nanoc::Int::ProcessingActions::Snapshot.new([:last], []), ] Nanoc::Int::ActionSequence.new(nil, actions: actions) end let(:action_sequences) do { rep => memory, other_rep => memory } end before do reps << rep reps << other_rep reps.each do |rep| rep.snapshot_defs << Nanoc::Int::SnapshotDef.new(:last, binary: false) end allow(Nanoc::Int::NotificationCenter).to receive(:post) end describe '#compile_rep' do let(:stage) { compiler.send(:compile_reps_stage, action_sequences) } subject { stage.send(:compile_rep, rep, is_outdated: is_outdated) } let(:is_outdated) { true } it 'generates expected output' do expect { subject } .to change { compiler.snapshot_repo.get(rep, :last) } .from(nil) .to(some_textual_content('3')) end it 'generates notifications in the proper order' do expect(Nanoc::Int::NotificationCenter).to receive(:post).with(:compilation_started, rep).ordered expect(Nanoc::Int::NotificationCenter).to receive(:post).with(:filtering_started, rep, :erb).ordered expect(Nanoc::Int::NotificationCenter).to receive(:post).with(:filtering_ended, rep, :erb).ordered expect(Nanoc::Int::NotificationCenter).to receive(:post).with(:compilation_ended, rep).ordered subject end context 'interrupted compilation' do let(:item) { Nanoc::Int::Item.new('other=<%= @items["/other.*"].compiled_content %>', {}, '/hi.md') } it 'generates expected output' do expect(compiler.snapshot_repo.get(rep, :last)).to be_nil expect { stage.send(:compile_rep, rep, is_outdated: true) } .to raise_error(Nanoc::Int::Errors::UnmetDependency) stage.send(:compile_rep, other_rep, is_outdated: true) stage.send(:compile_rep, rep, is_outdated: true) expect(compiler.snapshot_repo.get(rep, :last).string).to eql('other=other content') end it 'generates notifications in the proper order' do # rep 1 expect(Nanoc::Int::NotificationCenter).to receive(:post).with(:compilation_started, rep).ordered expect(Nanoc::Int::NotificationCenter).to receive(:post).with(:filtering_started, rep, :erb).ordered expect(Nanoc::Int::NotificationCenter).to receive(:post).with(:dependency_created, item, other_item).ordered expect(Nanoc::Int::NotificationCenter).to receive(:post).with(:compilation_suspended, rep, anything).ordered # rep 2 expect(Nanoc::Int::NotificationCenter).to receive(:post).with(:compilation_started, other_rep).ordered expect(Nanoc::Int::NotificationCenter).to receive(:post).with(:filtering_started, other_rep, :erb).ordered expect(Nanoc::Int::NotificationCenter).to receive(:post).with(:filtering_ended, other_rep, :erb).ordered expect(Nanoc::Int::NotificationCenter).to receive(:post).with(:compilation_ended, other_rep).ordered # rep 1 (again) expect(Nanoc::Int::NotificationCenter).to receive(:post).with(:compilation_started, rep).ordered expect(Nanoc::Int::NotificationCenter).to receive(:post).with(:filtering_ended, rep, :erb).ordered expect(Nanoc::Int::NotificationCenter).to receive(:post).with(:compilation_ended, rep).ordered expect { stage.send(:compile_rep, rep, is_outdated: true) } .to raise_error(Nanoc::Int::Errors::UnmetDependency) stage.send(:compile_rep, other_rep, is_outdated: true) stage.send(:compile_rep, rep, is_outdated: true) end end end end nanoc-4.8.0/spec/nanoc/filters/0000755000004100000410000000000013134141051016342 5ustar www-datawww-datananoc-4.8.0/spec/nanoc/filters/asciidoctor_spec.rb0000644000004100000410000000044413134141051022206 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::Filters::Asciidoctor do subject { filter.setup_and_run(input, params) } let(:filter) { described_class.new } let(:input) { '== Blah blah' } let(:params) { {} } it { is_expected.to match(%r{

Blah blah

}) } end nanoc-4.8.0/spec/nanoc/filters/less_spec.rb0000644000004100000410000000677113134141051020662 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::Filters::Less, site: true, stdio: true do # These tests are high-level in order to interact well with the compiler. This is important for # this :less filter, because of the way it handles fibers. before do File.open('Rules', 'w') do |io| io.write "compile '/**/*.less' do\n" io.write " filter :less\n" io.write " write item.identifier.without_ext + '.css'\n" io.write "end\n" end end context 'one file' do let(:content_a) { 'p { color: red; }' } before do File.write('content/a.less', content_a) end it 'compiles a.less' do Nanoc::CLI.run(%w[compile]) expect(File.read('output/a.css')).to match(/^p\s*\{\s*color:\s*red;?\s*\}/) end context 'with compression' do let(:content_a) { '.foo { bar: a; } .bar { foo: b; }' } before do File.open('Rules', 'w') do |io| io.write "compile '/*.less' do\n" io.write " filter :less, compress: true\n" io.write " write item.identifier.without_ext + '.css'\n" io.write "end\n" end end it 'compiles and compresses a.less' do Nanoc::CLI.run(%w[compile]) expect(File.read('output/a.css')).to match(/^\.foo\{bar:a\}\n?\.bar\{foo:b\}/) end end end context 'two files' do let(:content_a) { '@import "b.less";' } let(:content_b) { 'p { color: red; }' } before do File.write('content/a.less', content_a) File.write('content/b.less', content_b) end it 'compiles a.less' do Nanoc::CLI.run(%w[compile]) expect(File.read('output/a.css')).to match(/^p\s*\{\s*color:\s*red;?\s*\}/) end it 'recompiles a.less if b.less has changed' do Nanoc::CLI.run(%w[compile]) File.write('content/b.less', 'p { color: blue; }') Nanoc::CLI.run(%w[compile]) expect(File.read('output/a.css')).to match(/^p\s*\{\s*color:\s*blue;?\s*\}/) end end context 'paths relative to site directory' do let(:content_a) { '@import "content/foo/bar/imported_file.less";' } let(:content_b) { 'p { color: red; }' } before do FileUtils.mkdir_p('content/foo/bar') File.write('content/a.less', content_a) File.write('content/foo/bar/imported_file.less', content_b) end it 'compiles a.less' do Nanoc::CLI.run(%w[compile]) expect(File.read('output/a.css')).to match(/^p\s*\{\s*color:\s*red;?\s*\}/) end it 'recompiles a.less if b.less has changed' do Nanoc::CLI.run(%w[compile]) File.write('content/foo/bar/imported_file.less', 'p { color: blue; }') Nanoc::CLI.run(%w[compile]) expect(File.read('output/a.css')).to match(/^p\s*\{\s*color:\s*blue;?\s*\}/) end end context 'paths relative to current file' do let(:content_a) { '@import "bar/imported_file.less";' } let(:content_b) { 'p { color: red; }' } before do FileUtils.mkdir_p('content/foo/bar') File.write('content/foo/a.less', content_a) File.write('content/foo/bar/imported_file.less', content_b) end it 'compiles a.less' do Nanoc::CLI.run(%w[compile]) expect(File.read('output/foo/a.css')).to match(/^p\s*\{\s*color:\s*red;?\s*\}/) end it 'recompiles a.less if b.less has changed' do Nanoc::CLI.run(%w[compile]) File.write('content/foo/bar/imported_file.less', 'p { color: blue; }') Nanoc::CLI.run(%w[compile]) expect(File.read('output/foo/a.css')).to match(/^p\s*\{\s*color:\s*blue;?\s*\}/) end end end nanoc-4.8.0/spec/nanoc/filters/colorize_syntax/0000755000004100000410000000000013134141051021576 5ustar www-datawww-datananoc-4.8.0/spec/nanoc/filters/colorize_syntax/rouge_spec.rb0000644000004100000410000001412413134141051024260 0ustar www-datawww-data# frozen_string_literal: true require 'rouge' describe Nanoc::Filters::ColorizeSyntax, filter: true, rouge: true do subject { filter.setup_and_run(input, default_colorizer: :rouge, rouge: params) } let(:filter) { ::Nanoc::Filters::ColorizeSyntax.new } let(:params) { {} } let(:wrap) { false } let(:css_class) { 'highlight' } let(:input) do <<~EOS before

        def foo
        end
      
after EOS end let(:output) do <<~EOS before
  def foo
        end
after EOS end context 'with Rouge' do context 'with 1.x', if: Rouge.version < '2' do context 'with default options' do it { is_expected.to eql output } end context 'with pygments wrapper' do let(:wrap) { true } let(:params) { super().merge(wrap: wrap) } it { is_expected.to eql output } context 'with css_class' do let(:css_class) { 'nanoc' } let(:params) { super().merge(css_class: css_class) } it { is_expected.to eql output } end end context 'with line number' do let(:line_numbers) { true } let(:params) { super().merge(line_numbers: line_numbers) } let(:output) do <<~EOS before
1
            2
  def foo
              end
            
after EOS end it { is_expected.to eql output } end end context 'with 2.x', if: Rouge.version >= '2' do context 'with default options' do it { is_expected.to eql output } end context 'with legacy' do let(:legacy) { true } let(:params) { super().merge(legacy: legacy) } it { is_expected.to eql output } context 'with pygments wrapper' do let(:wrap) { true } let(:params) { super().merge(wrap: wrap) } it { is_expected.to eql output } context 'with css_class' do let(:css_class) { 'nanoc' } let(:params) { super().merge(css_class: css_class) } it { is_expected.to eql output } end end context 'with line number' do let(:line_numbers) { true } let(:params) { super().merge(line_numbers: line_numbers) } let(:output) do <<~EOS before
1
              2
              
  def foo
                end
after EOS end it { is_expected.to eql output } end end context 'with formater' do let(:params) { super().merge(formatter: formatter) } context 'with inline' do let(:formatter) { Rouge::Formatters::HTMLInline.new(theme) } context 'with github theme' do let(:theme) { Rouge::Themes::Github.new } let(:output) do <<~EOS before
  def foo
                  end
after EOS end it { is_expected.to eql output } end context 'with colorful theme' do let(:theme) { Rouge::Themes::Colorful.new } let(:output) do <<~EOS before
  def foo
                  end
after EOS end it { is_expected.to eql output } end end context 'with linewise' do let(:formatter) { Rouge::Formatters::HTMLLinewise.new(Rouge::Formatters::HTML.new) } let(:output) do <<~EOS before
def foo
end
after EOS end it { is_expected.to eql output } end context 'with pygments' do let(:wrap) { true } let(:css_class) { 'codehilite' } let(:formatter) { Rouge::Formatters::HTMLPygments.new(Rouge::Formatters::HTML.new) } it { is_expected.to eql output } end context 'with table' do let(:formatter) { Rouge::Formatters::HTMLTable.new(Rouge::Formatters::HTML.new) } let(:output) do <<~EOS before
1
              2
              
  def foo
                end
after EOS end it { is_expected.to eql output } end end end end end nanoc-4.8.0/spec/nanoc/telemetry/0000755000004100000410000000000013134141051016704 5ustar www-datawww-datananoc-4.8.0/spec/nanoc/telemetry/summary_spec.rb0000644000004100000410000000406613134141051021746 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::Telemetry::Summary do subject(:summary) { described_class.new } context 'no observations' do it 'errors on #min' do expect { subject.min } .to raise_error(Nanoc::Telemetry::Summary::EmptySummaryError) end it 'errors on #max' do expect { subject.max } .to raise_error(Nanoc::Telemetry::Summary::EmptySummaryError) end it 'errors on #avg' do expect { subject.avg } .to raise_error(Nanoc::Telemetry::Summary::EmptySummaryError) end it 'errors on #sum' do expect { subject.sum } .to raise_error(Nanoc::Telemetry::Summary::EmptySummaryError) end its(:count) { is_expected.to eq(0) } end context 'one observation' do before { subject.observe(2.1) } its(:count) { is_expected.to eq(1) } its(:sum) { is_expected.to eq(2.1) } its(:avg) { is_expected.to eq(2.1) } its(:min) { is_expected.to eq(2.1) } its(:max) { is_expected.to eq(2.1) } it 'has proper quantiles' do expect(subject.quantile(0.00)).to eq(2.1) expect(subject.quantile(0.25)).to eq(2.1) expect(subject.quantile(0.50)).to eq(2.1) expect(subject.quantile(0.90)).to eq(2.1) expect(subject.quantile(0.99)).to eq(2.1) end end context 'two observations' do before do subject.observe(2.1) subject.observe(4.1) end its(:count) { is_expected.to be_within(0.000001).of(2) } its(:sum) { is_expected.to be_within(0.000001).of(6.2) } its(:avg) { is_expected.to be_within(0.000001).of(3.1) } its(:min) { is_expected.to be_within(0.000001).of(2.1) } its(:max) { is_expected.to be_within(0.000001).of(4.1) } it 'has proper quantiles' do expect(subject.quantile(0.00)).to be_within(0.000001).of(2.1) expect(subject.quantile(0.25)).to be_within(0.000001).of(2.6) expect(subject.quantile(0.50)).to be_within(0.000001).of(3.1) expect(subject.quantile(0.90)).to be_within(0.000001).of(3.9) expect(subject.quantile(0.99)).to be_within(0.000001).of(4.08) end end end nanoc-4.8.0/spec/nanoc/telemetry/table_spec.rb0000644000004100000410000000071613134141051021336 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::Telemetry::Table do let(:table) { described_class.new(rows) } let(:rows) do [ %w[name awesomeness], %w[denis high], %w[REDACTED low], ] end example do expect(table.to_s).to eq(<<~EOS.rstrip) name │ awesomeness ─────────┼──────────── denis │ high REDACTED │ low EOS end end nanoc-4.8.0/spec/nanoc/telemetry/labelled_counter_spec.rb0000644000004100000410000000405113134141051023546 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::Telemetry::LabelledCounter do subject(:counter) { described_class.new } describe 'new counter' do it 'starts at 0' do expect(subject.value(:erb)).to eq(0) expect(subject.value(:haml)).to eq(0) end it 'has no values' do expect(subject.values).to eq({}) end end describe '#increment' do subject { counter.increment(:erb) } it 'increments the matching value' do expect { subject } .to change { counter.value(:erb) } .from(0) .to(1) end it 'does not increment any other value' do expect(counter.value(:haml)).to eq(0) expect { subject } .not_to change { counter.value(:haml) } end it 'correctly changes #values' do expect { subject } .to change { counter.values } .from({}) .to(erb: 1) end end describe '#get' do subject { counter.get(:erb) } context 'not incremented' do its(:value) { is_expected.to eq(0) } end context 'incremented' do before { counter.increment(:erb) } its(:value) { is_expected.to eq(1) } end context 'other incremented' do before { counter.increment(:haml) } its(:value) { is_expected.to eq(0) } end end describe '#empty?' do subject { counter.empty? } context 'not incremented' do it { is_expected.to be } end context 'incremented' do before { counter.increment(:erb) } it { is_expected.not_to be } end end describe '#map' do subject { counter.map { |label, counter| [label, counter.value] } } context 'not incremented' do it { is_expected.to be_empty } end context 'incremented once' do before { counter.increment(:erb) } it { is_expected.to eq [[:erb, 1]] } end context 'both incremental multiple times' do before do counter.increment(:erb) counter.increment(:erb) counter.increment(:haml) end it { is_expected.to eq [[:erb, 2], [:haml, 1]] } end end end nanoc-4.8.0/spec/nanoc/telemetry/counter_spec.rb0000644000004100000410000000056713134141051021732 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::Telemetry::Counter do subject(:counter) { described_class.new } it 'starts at 0' do expect(counter.value).to eq(0) end describe '#increment' do subject { counter.increment } it 'increments' do expect { subject } .to change { counter.value } .from(0) .to(1) end end end nanoc-4.8.0/spec/nanoc/telemetry/stopwatch_spec.rb0000644000004100000410000000305413134141051022261 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::Telemetry::Stopwatch do subject(:stopwatch) { described_class.new } after { Timecop.return } it 'is zero by default' do expect(stopwatch.duration).to eq(0.0) end # TODO: if running, raise error when asking for #duration it 'records correct duration after start+stop' do Timecop.freeze(Time.local(2008, 9, 1, 10, 5, 0)) stopwatch.start Timecop.freeze(Time.local(2008, 9, 1, 10, 5, 1)) stopwatch.stop expect(stopwatch.duration).to eq(1.0) end it 'records correct duration after start+stop+start+stop' do Timecop.freeze(Time.local(2008, 9, 1, 10, 5, 0)) stopwatch.start Timecop.freeze(Time.local(2008, 9, 1, 10, 5, 1)) stopwatch.stop Timecop.freeze(Time.local(2008, 9, 1, 10, 5, 3)) stopwatch.start Timecop.freeze(Time.local(2008, 9, 1, 10, 5, 6)) stopwatch.stop expect(stopwatch.duration).to eq(1.0 + 3.0) end it 'errors when stopping when not started' do expect { stopwatch.stop }.to raise_error(Nanoc::Telemetry::Stopwatch::NotRunningError) end it 'errors when starting when already started' do stopwatch.start expect { stopwatch.start }.to raise_error(Nanoc::Telemetry::Stopwatch::AlreadyRunningError) end it 'reports running status' do expect(stopwatch).not_to be_running expect(stopwatch).to be_stopped stopwatch.start expect(stopwatch).to be_running expect(stopwatch).not_to be_stopped stopwatch.stop expect(stopwatch).not_to be_running expect(stopwatch).to be_stopped end end nanoc-4.8.0/spec/nanoc/telemetry/labelled_summary_spec.rb0000644000004100000410000000430113134141051023562 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::Telemetry::LabelledSummary do subject(:summary) { described_class.new } describe '#empty?' do subject { summary.empty? } context 'empty summary' do it { is_expected.to be } end context 'some observations' do before do summary.observe(7.2, :erb) summary.observe(5.3, :erb) summary.observe(3.0, :haml) end it { is_expected.not_to be } end end describe '#get' do subject { summary.get(:erb) } context 'empty summary' do its(:count) { is_expected.to eq(0) } end context 'one observation with that label' do before { summary.observe(0.1, :erb) } its(:count) { is_expected.to eq(1) } end context 'one observation with a different label' do before { summary.observe(0.1, :haml) } its(:count) { is_expected.to eq(0) } end end describe '#map' do before do subject.observe(2.1, :erb) subject.observe(4.1, :erb) subject.observe(5.3, :haml) end it 'yields label and summary' do res = subject.map { |label, summary| [label, summary.avg.round(3)] } expect(res).to eql([[:erb, 3.1], [:haml, 5.3]]) end end describe '#quantile' do before do subject.observe(2.1, :erb) subject.observe(4.1, :erb) subject.observe(5.3, :haml) end it 'has proper quantiles for :erb' do expect(subject.quantile(0.00, :erb)).to be_within(0.000001).of(2.1) expect(subject.quantile(0.25, :erb)).to be_within(0.000001).of(2.6) expect(subject.quantile(0.50, :erb)).to be_within(0.000001).of(3.1) expect(subject.quantile(0.90, :erb)).to be_within(0.000001).of(3.9) expect(subject.quantile(0.99, :erb)).to be_within(0.000001).of(4.08) end it 'has proper quantiles for :erb' do expect(subject.quantile(0.00, :haml)).to be_within(0.000001).of(5.3) expect(subject.quantile(0.25, :haml)).to be_within(0.000001).of(5.3) expect(subject.quantile(0.50, :haml)).to be_within(0.000001).of(5.3) expect(subject.quantile(0.90, :haml)).to be_within(0.000001).of(5.3) expect(subject.quantile(0.99, :haml)).to be_within(0.000001).of(5.3) end end end nanoc-4.8.0/spec/nanoc/extra/0000755000004100000410000000000013134141051016015 5ustar www-datawww-datananoc-4.8.0/spec/nanoc/extra/parallel_collection_spec.rb0000644000004100000410000000431613134141051023367 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::Extra::ParallelCollection do subject(:col) { described_class.new(wrapped, parallelism: parallelism) } let(:wrapped) { [1, 2, 3, 4, 5] } let(:parallelism) { 5 } describe '#each' do subject do col.each do |e| sleep 0.1 out << e end end let!(:out) { [] } it 'is fast' do expect { subject }.to finish_in_under(0.25).seconds end it 'is correct' do expect { subject }.to change { out.sort }.from([]).to([1, 2, 3, 4, 5]) end it 'does not leave threads lingering' do expect { subject }.not_to change { Thread.list.size } end context 'errors' do subject do col.each do |e| if e == 1 sleep 0.02 raise 'ugh' else sleep 0.1 out << e end end end let(:parallelism) { 3 } it 'raises' do expect { subject }.to raise_error(RuntimeError, 'ugh') end it 'aborts early' do expect { subject rescue nil }.to change { out.sort }.from([]).to([2, 3]) end end context 'low parallelism' do let(:parallelism) { 1 } it 'is not fast' do expect { subject }.not_to finish_in_under(0.5).seconds end end end describe '#map' do subject do col.map do |e| sleep 0.1 e * 10 end end it 'is fast' do expect { subject }.to finish_in_under(0.25).seconds end it 'does not leave threads lingering' do expect { subject }.not_to change { Thread.list.size } end it 'is correct' do expect(subject.sort).to eq([10, 20, 30, 40, 50]) end context 'errors' do subject do col.each do |e| if e == 1 sleep 0.02 raise 'ugh' else sleep 0.1 e * 10 end end end let(:parallelism) { 3 } it 'raises' do expect { subject }.to raise_error(RuntimeError, 'ugh') end end context 'low parallelism' do let(:parallelism) { 1 } it 'is not fast' do expect { subject }.not_to finish_in_under(0.5).seconds end end end end nanoc-4.8.0/spec/nanoc/regressions/0000755000004100000410000000000013134141051017235 5ustar www-datawww-datananoc-4.8.0/spec/nanoc/regressions/gh_767_spec.rb0000644000004100000410000000100413134141051021570 0ustar www-datawww-data# frozen_string_literal: true describe 'GH-767', site: true do before do File.write('content/donkey.md', 'Compiled content donkey!') File.write('Rules', <') File.write('content/z.dat', 'asdf') File.write('Rules', <') File.write('content/z.dat', 'quux') File.write('Rules', <') File.write('nanoc.yaml', <<~EOS) base_url: 'http://example.com' EOS File.write('lib/default.rb', <<~EOS) include Nanoc::Helpers::XMLSitemap EOS File.write('Rules', <<~EOS) compile '/*.txt' do write item.identifier.without_ext + '/index.html' end compile '/sitemap.erb' do filter :erb write item.identifier.without_ext + '.xml' end EOS end it 'creates the sitemap' do Nanoc::CLI.run(%w[compile]) expect(File.file?('output/sitemap.xml')).to be contents = File.read('output/sitemap.xml') expect(contents).to match(%r{http://example.com/foo/}) expect(contents).to match(%r{2015-03-02}) end it 'updates the sitemap' do Nanoc::CLI.run(%w[compile]) File.write('content/foo.txt', 'foo 2') FileUtils.touch('content/foo.txt', mtime: Time.parse('2016-04-03 10:00:00Z')) Nanoc::CLI.run(%w[compile]) expect(File.file?('output/sitemap.xml')).to be contents = File.read('output/sitemap.xml') expect(contents).to match(%r{http://example.com/foo/}) expect(contents).to match(%r{2016-04-03}) end end nanoc-4.8.0/spec/nanoc/regressions/gh_970b_spec.rb0000644000004100000410000000303713134141051021736 0ustar www-datawww-data# frozen_string_literal: true describe 'GH-970 (show-data)', site: true, stdio: true do before do File.write('content/foo.md', 'foo') File.write('content/bar.md', '<%= @items["/foo.*"].compiled_content %>') File.write('Rules', <<~EOS) compile '/foo.*' do write '/foo.html' end compile '/bar.*' do filter :erb write '/bar.html' end EOS end before { Nanoc::CLI.run(%w[compile]) } it 'shows default rep outdatedness' do expect { Nanoc::CLI.run(%w[show-data --no-color]) }.to( output(/^item \/foo\.md, rep default:\n is not outdated/).to_stdout, ) expect { Nanoc::CLI.run(%w[show-data --no-color]) }.to( output(/^item \/bar\.md, rep default:\n is not outdated/).to_stdout, ) end it 'shows file as outdated after modification' do File.write('content/bar.md', 'JUST BAR!') expect { Nanoc::CLI.run(%w[show-data --no-color]) }.to( output(/^item \/foo\.md, rep default:\n is not outdated/).to_stdout, ) expect { Nanoc::CLI.run(%w[show-data --no-color]) }.to( output(/^item \/bar\.md, rep default:\n is outdated:/).to_stdout, ) end it 'shows file and dependencies as outdated after modification' do File.write('content/foo.md', 'FOO!') expect { Nanoc::CLI.run(%w[show-data --no-color]) }.to( output(/^item \/foo\.md, rep default:\n is outdated:/).to_stdout, ) expect { Nanoc::CLI.run(%w[show-data --no-color]) }.to( output(/^item \/bar\.md, rep default:\n is outdated:/).to_stdout, ) end end nanoc-4.8.0/spec/nanoc/regressions/gh_1040_spec.rb0000644000004100000410000000111713134141051021636 0ustar www-datawww-data# frozen_string_literal: true describe 'GH-1040', site: true, stdio: true do before do File.write('content/foo.txt', 'bar=<%= @items["/bar.*"].compiled_content %>') File.write('content/bar.txt', 'foo=<%= @items["/foo.*"].compiled_content %>') File.write('layouts/default.erb', '*<%= yield %>*') File.write('Rules', <!') File.write('content/items-view.md', 'Frozen? <%= @items.frozen? %>!') File.write('Rules', <', encoding: 'utf-8') File.write('lib/asdf.rb', 'EMOJI_🔥 = "hot"', encoding: 'utf-8') File.write('Rules', <<~EOS) compile '/**/*' do filter :erb write '/last.html' end EOS end around do |ex| orig_encoding = Encoding.default_external Encoding.default_external = 'ASCII' ex.run Encoding.default_external = orig_encoding end it 'does not crash' do Nanoc::CLI.run(%w[compile]) expect(File.read('output/last.html')).to eql('hot') end end context 'ISO 8859-1 code UTF-8 env' do before do File.write('content/hi.md', '<%= ::BRØKEN %>') File.write('lib/asdf.rb', "# encoding: iso-8859-1\n\nBRØKEN = 1", encoding: 'ISO-8859-1') File.write('Rules', <<~EOS) compile '/**/*' do filter :erb write '/last.html' end EOS end it 'detects manually specified encodings' do Nanoc::CLI.run(%w[compile]) expect(File.read('output/last.html')).to eql('1') end end end nanoc-4.8.0/spec/nanoc/regressions/gh_1047_spec.rb0000644000004100000410000000201113134141051021637 0ustar www-datawww-data# frozen_string_literal: true describe 'GH-1047', site: true, stdio: true do before do File.write('Rules', <<%= raise "boom" %>') expect { Nanoc::CLI.run(%w[compile]) }.to raise_error(Nanoc::Int::Errors::CompilationError) expect(File.read('output/foo.md')).to eql('I am foo!') File.write('content/bar.md', '[<%= @items["/foo.*"].compiled_content %>]') Nanoc::CLI.run(%w[compile]) expect(File.read('output/foo.md')).to eql('I am foo!') expect(File.read('output/bar.md')).to eql('[I am foo!]') end end nanoc-4.8.0/spec/nanoc/regressions/gh_970a_spec.rb0000644000004100000410000000064413134141051021736 0ustar www-datawww-data# frozen_string_literal: true describe 'GH-970 (show-rules)', site: true, stdio: true do before do File.write('content/foo.md', 'foo') File.write('Rules', <<~EOS) compile '/foo.*' do write '/donkey.html' end EOS end it 'shows reps' do expect { Nanoc::CLI.run(%w[show-rules --no-color]) }.to( output(/^Item \/foo\.md:\n Rep default: \/foo\.\*$/).to_stdout, ) end end nanoc-4.8.0/spec/nanoc/regressions/gh_809_spec.rb0000644000004100000410000000077613134141051021604 0ustar www-datawww-data# frozen_string_literal: true describe 'GH-809', site: true, stdio: true do before do File.write('content/greeting.md', 'Hallöchen!') File.write('Rules', <') File.write('Rules', <]') File.write('Rules', <') File.write('Rules', <') File.write('Rules', < / <%= yield %>') File.write('Rules', <]') File.open('nanoc.yaml', 'w') do |io| io << 'string_pattern_type: legacy' << "\n" io << 'data_sources:' << "\n" io << ' -' << "\n" io << ' type: filesystem' << "\n" io << ' identifier_type: legacy' << "\n" end File.write('Rules', <') File.write('Rules', <') File.write('Rules', < - <%= Time.now.to_f %>", ) File.write('Rules', <') File.write('Rules', <') Nanoc::CLI.run(%w[compile]) expect(File.read('output/hello.html')).to include('donkeys?') end end nanoc-4.8.0/spec/nanoc/regressions/gh_1082c_spec.rb0000644000004100000410000000110613134141051022005 0ustar www-datawww-data# frozen_string_literal: true describe 'GH-1082', site: true, stdio: true do before do File.write('content/a.erb', '<%= @items["/b.*"].reps[:default].binary? %>') File.write('content/b.erb', '<%= @items["/a.*"].reps[:default].binary? %>') File.write('Rules', <') File.write('content/b.dat', 'stuff') File.write('Rules', <]') File.write('content/bar.md', 'I am bar!') File.write('lib/stuff.rb', <<~EOS) Class.new(Nanoc::Filter) do identifier :gh_1031_text2bin type :text => :binary def run(content, params = {}) File.write(output_filename, content) end end EOS File.write('Rules', <root
') File.write('content/bar.md', 'bar root') File.write('content/bar-copy.md', '<%= @items["/bar.*"].compiled_content(snapshot: :last) %>') File.write('Rules', <<~EOS) compile '/foo.*' do filter :relativize_paths, type: :html unless rep.path.nil? write item.identifier.without_ext + '.html' end compile '/bar.*' do filter :relativize_paths, type: :html unless rep.path.nil? end compile '/bar-copy.*' do filter :erb write item.identifier.without_ext + '.html' end EOS end it 'properly filters foo.md' do Nanoc::CLI.run(%w[compile]) # Path is relativized expect(File.read('output/foo.html')).to eq('foo root') # Path is not relativized expect(File.read('output/bar-copy.html')).to eq('bar root') end end nanoc-4.8.0/spec/nanoc/regressions/gh_937_spec.rb0000644000004100000410000000137313134141051021600 0ustar www-datawww-data# frozen_string_literal: true describe 'GH-937', site: true, stdio: true do before do File.write('content/style.sass', ".test\n color: red") File.write( 'nanoc.yaml', "sass_style: compact\nenvironments:\n staging:\n sass_style: expanded", ) File.write('Rules', <<~EOS) compile '/*.sass' do filter :sass, style: @config[:sass_style].to_sym write item.identifier.without_ext + '.css' end EOS end it 'does not use cache when switching environments' do Nanoc::CLI.run(%w[compile]) expect(File.read('output/style.css')).to eq(".test { color: red; }\n") Nanoc::CLI.run(%w[compile --env=staging]) expect(File.read('output/style.css')).to eq(".test {\n color: red;\n}\n") end end nanoc-4.8.0/spec/nanoc/regressions/gh_1082a_spec.rb0000644000004100000410000000072113134141051022005 0ustar www-datawww-data# frozen_string_literal: true describe 'GH-1082', site: true, stdio: true do before do File.write('content/a.erb', '<%= @items["/b.*"].binary? %>') File.write('content/b.erb', 'stuff') File.write('Rules', <*') File.write('content/bar.erb', 'Bar!') File.write('Rules', <') File.write('Rules', <]') File.write('content/bar.md', 'I am bar!') File.write('Rules', <]') File.write('Rules', <') File.write('layouts/default.xsl', <<~EOS) EOS File.write('layouts/snippet.xsl', <<~EOS) Original Title

Test Body

EOS File.write('Rules', <<~EOS) compile '/index.xml' do layout '/default.xsl' write '/index.xhtml' end layout '/**/*.xsl', :xsl EOS end before do Nanoc::CLI.run(%w[compile]) end example do File.write('layouts/snippet.xsl', <<~EOS) Changed Title

Test Body

EOS expect { Nanoc::CLI.run(%w[compile]) } .to change { File.read('output/index.xhtml') } .from(/Original Title/) .to(/<title>Changed Title/) end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nanoc-4.8.0/spec/nanoc/integration/�����������������������������������������������������������������0000755�0000041�0000041�00000000000�13134141051�017215� 5����������������������������������������������������������������������������������������������������ustar �www-data������������������������www-data���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nanoc-4.8.0/spec/nanoc/integration/partial_recompilation_spec.rb������������������������������������0000644�0000041�0000041�00000003401�13134141051�025133� 0����������������������������������������������������������������������������������������������������ustar �www-data������������������������www-data���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# frozen_string_literal: true describe 'Partial recompilation', site: true, stdio: true do before do File.write('content/foo.md', "---\ntitle: hello\n---\n\nfoo") File.write('content/bar.md', '<%= @items["/foo.*"].compiled_content %><% raise "boom" %>') File.write('Rules', <<~EOS) compile '/foo.*' do write '/foo.html' end compile '/bar.*' do filter :erb write '/bar.html' end EOS end example do expect(File.file?('output/foo.html')).not_to be expect(File.file?('output/bar.html')).not_to be expect { Nanoc::CLI.run(%w[show-data --no-color]) } .to(output(/^item \/foo\.md, rep default:\n is outdated:/).to_stdout) expect { Nanoc::CLI.run(%w[show-data --no-color]) } .to(output(/^item \/bar\.md, rep default:\n is outdated:/).to_stdout) expect { Nanoc::CLI.run(%w[compile --verbose]) rescue nil } .to output(/create.*output\/foo\.html/).to_stdout expect { Nanoc::CLI.run(%w[show-data --no-color]) } .to(output(/^item \/foo\.md, rep default:\n is not outdated/).to_stdout) expect { Nanoc::CLI.run(%w[show-data --no-color]) } .to(output(/^item \/bar\.md, rep default:\n is outdated:/).to_stdout) expect(File.file?('output/foo.html')).to be expect(File.file?('output/bar.html')).not_to be File.write('content/bar.md', '<% raise "boom" %>') expect { Nanoc::CLI.run(%w[compile --verbose --debug]) rescue nil } .to output(/skip.*output\/foo\.html/).to_stdout expect { Nanoc::CLI.run(%w[show-data --no-color]) } .to(output(/^item \/foo\.md, rep default:\n is not outdated/).to_stdout) expect { Nanoc::CLI.run(%w[show-data --no-color]) } .to(output(/^item \/bar\.md, rep default:\n is outdated:/).to_stdout) end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nanoc-4.8.0/spec/nanoc/integration/outdatedness_integration_spec.rb���������������������������������0000644�0000041�0000041�00000021142�13134141051�025661� 0����������������������������������������������������������������������������������������������������ustar �www-data������������������������www-data���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# frozen_string_literal: true describe 'Outdatedness integration', site: true, stdio: true do context 'only attribute dependency' do let(:time) { Time.now } before do File.write('content/foo.md', "---\ntitle: hello\n---\n\nfoo") File.write('content/bar.md', '<%= @items["/foo.*"][:title] %>') FileUtils.touch('content/foo.md', mtime: time) FileUtils.touch('content/bar.md', mtime: time) File.write('Rules', <<~EOS) compile '/foo.*' do write '/foo.html' end compile '/bar.*' do filter :erb write '/bar.html' end EOS end before { Nanoc::CLI.run(%w[compile]) } it 'shows default rep outdatedness' do expect { Nanoc::CLI.run(%w[show-data --no-color]) }.to( output(/^item \/foo\.md, rep default:\n is not outdated/).to_stdout, ) expect { Nanoc::CLI.run(%w[show-data --no-color]) }.to( output(/^item \/bar\.md, rep default:\n is not outdated/).to_stdout, ) end it 'shows file as outdated after modification' do File.write('content/bar.md', 'JUST BAR!') FileUtils.touch('content/bar.md', mtime: time) expect { Nanoc::CLI.run(%w[show-data --no-color]) }.to( output(/^item \/foo\.md, rep default:\n is not outdated/).to_stdout, ) expect { Nanoc::CLI.run(%w[show-data --no-color]) }.to( output(/^item \/bar\.md, rep default:\n is outdated:/).to_stdout, ) end it 'shows file and dependencies as not outdated after content modification' do File.write('content/foo.md', "---\ntitle: hello\n---\n\nfoooOoooOOoooOooo") FileUtils.touch('content/foo.md', mtime: time) expect { Nanoc::CLI.run(%w[show-data --no-color]) }.to( output(/^item \/foo\.md, rep default:\n is outdated:/).to_stdout, ) expect { Nanoc::CLI.run(%w[show-data --no-color]) }.to( output(/^item \/bar\.md, rep default:\n is not outdated/).to_stdout, ) end it 'shows file and dependencies as outdated after title modification' do File.write('content/foo.md', "---\ntitle: bye\n---\n\nfoo") FileUtils.touch('content/foo.md', mtime: time) expect { Nanoc::CLI.run(%w[show-data --no-color]) }.to( output(/^item \/foo\.md, rep default:\n is outdated:/).to_stdout, ) expect { Nanoc::CLI.run(%w[show-data --no-color]) }.to( output(/^item \/bar\.md, rep default:\n is outdated:/).to_stdout, ) end end context 'only attribute dependency on config' do let(:time) { Time.now } before do File.write('content/bar.md', '<%= @config[:title] %>') FileUtils.touch('content/bar.md', mtime: time) File.write('nanoc.yaml', <<~EOS) title: The Original EOS File.write('Rules', <<~EOS) compile '/foo.*' do write '/foo.html' end compile '/bar.*' do filter :erb write '/bar.html' end EOS end before { Nanoc::CLI.run(%w[compile]) } it 'shows default rep outdatedness' do expect { Nanoc::CLI.run(%w[show-data --no-color]) }.to( output(/^item \/bar\.md, rep default:\n is not outdated/).to_stdout, ) end it 'shows file as outdated after modification' do File.write('content/bar.md', 'JUST BAR!') FileUtils.touch('content/bar.md', mtime: time) expect { Nanoc::CLI.run(%w[show-data --no-color]) }.to( output(/^item \/bar\.md, rep default:\n is outdated:/).to_stdout, ) end it 'shows file and dependencies as outdated after title modification' do File.write('nanoc.yaml', 'title: Totes Newz') FileUtils.touch('nanoc.yaml', mtime: time) expect { Nanoc::CLI.run(%w[show-data --no-color]) }.to( output(/^item \/bar\.md, rep default:\n is outdated:/).to_stdout, ) end end context 'only raw content dependency' do before do File.write('content/foo.md', "---\ntitle: hello\n---\n\nfoo") File.write('content/bar.md', '<%= @items["/foo.*"].raw_content %>') File.write('Rules', <<~EOS) compile '/foo.*' do write '/foo.html' end compile '/bar.*' do filter :erb write '/bar.html' end EOS end before { Nanoc::CLI.run(%w[compile]) } it 'shows default rep outdatedness' do expect { Nanoc::CLI.run(%w[show-data --no-color]) }.to( output(/^item \/foo\.md, rep default:\n is not outdated/).to_stdout, ) expect { Nanoc::CLI.run(%w[show-data --no-color]) }.to( output(/^item \/bar\.md, rep default:\n is not outdated/).to_stdout, ) end it 'shows file as outdated after modification' do File.write('content/bar.md', 'JUST BAR!') expect { Nanoc::CLI.run(%w[show-data --no-color]) }.to( output(/^item \/foo\.md, rep default:\n is not outdated/).to_stdout, ) expect { Nanoc::CLI.run(%w[show-data --no-color]) }.to( output(/^item \/bar\.md, rep default:\n is outdated:/).to_stdout, ) end it 'shows file and dependencies as outdated after content modification' do File.write('content/foo.md', "---\ntitle: hello\n---\n\nfoooOoooOOoooOooo") expect { Nanoc::CLI.run(%w[show-data --no-color]) }.to( output(/^item \/foo\.md, rep default:\n is outdated:/).to_stdout, ) expect { Nanoc::CLI.run(%w[show-data --no-color]) }.to( output(/^item \/bar\.md, rep default:\n is outdated:/).to_stdout, ) end it 'shows file and dependencies as not outdated after title modification' do File.write('content/foo.md', "---\ntitle: bye\n---\n\nfoo") expect { Nanoc::CLI.run(%w[show-data --no-color]) }.to( output(/^item \/foo\.md, rep default:\n is outdated:/).to_stdout, ) expect { Nanoc::CLI.run(%w[show-data --no-color]) }.to( output(/^item \/bar\.md, rep default:\n is not outdated/).to_stdout, ) end end context 'attribute and raw content dependency' do before do File.write('content/foo.md', "---\ntitle: hello\n---\n\nfoo") File.write('content/bar.md', '<%= @items["/foo.*"].raw_content %> / <%= @items["/foo.*"][:title] %>') File.write('Rules', <<~EOS) compile '/foo.*' do write '/foo.html' end compile '/bar.*' do filter :erb write '/bar.html' end EOS end before { Nanoc::CLI.run(%w[compile]) } it 'shows default rep outdatedness' do expect { Nanoc::CLI.run(%w[show-data --no-color]) }.to( output(/^item \/foo\.md, rep default:\n is not outdated/).to_stdout, ) expect { Nanoc::CLI.run(%w[show-data --no-color]) }.to( output(/^item \/bar\.md, rep default:\n is not outdated/).to_stdout, ) end it 'shows file as outdated after modification' do File.write('content/bar.md', 'JUST BAR!') expect { Nanoc::CLI.run(%w[show-data --no-color]) }.to( output(/^item \/foo\.md, rep default:\n is not outdated/).to_stdout, ) expect { Nanoc::CLI.run(%w[show-data --no-color]) }.to( output(/^item \/bar\.md, rep default:\n is outdated:/).to_stdout, ) end it 'shows file and dependencies as outdated after content modification' do File.write('content/foo.md', "---\ntitle: hello\n---\n\nfoooOoooOOoooOooo") expect { Nanoc::CLI.run(%w[show-data --no-color]) }.to( output(/^item \/foo\.md, rep default:\n is outdated:/).to_stdout, ) expect { Nanoc::CLI.run(%w[show-data --no-color]) }.to( output(/^item \/bar\.md, rep default:\n is outdated:/).to_stdout, ) end it 'shows file and dependencies as outdated after title modification' do File.write('content/foo.md', "---\ntitle: bye\n---\n\nfoo") expect { Nanoc::CLI.run(%w[show-data --no-color]) }.to( output(/^item \/foo\.md, rep default:\n is outdated:/).to_stdout, ) expect { Nanoc::CLI.run(%w[show-data --no-color]) }.to( output(/^item \/bar\.md, rep default:\n is outdated:/).to_stdout, ) end it 'shows file and dependencies as not outdated after rule modification' do File.write('Rules', <<~EOS) compile '/foo.*' do filter :erb write '/foo.html' end compile '/bar.*' do filter :erb write '/bar.html' end EOS expect { Nanoc::CLI.run(%w[show-data --no-color]) }.to( output(/^item \/foo\.md, rep default:\n is outdated:/).to_stdout, ) expect { Nanoc::CLI.run(%w[show-data --no-color]) }.to( output(/^item \/bar\.md, rep default:\n is not outdated/).to_stdout, ) end end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nanoc-4.8.0/spec/nanoc/integration/compile_command_spec.rb������������������������������������������0000644�0000041�0000041�00000001356�13134141051�023707� 0����������������������������������������������������������������������������������������������������ustar �www-data������������������������www-data���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# frozen_string_literal: true describe 'Compile command', site: true, stdio: true do describe 'diff generation' do before do File.write('content/foo.md', "I am foo!\n") File.write('Rules', <<~EOS) compile '/foo.*' do write '/foo.html' end EOS end it 'does not generate diff by default' do FileUtils.mkdir_p('output') File.write('output/foo.html', "I am old foo!\n") Nanoc::CLI.run(%w[compile]) expect(File.file?('output.diff')).not_to be end it 'honors --diff' do FileUtils.mkdir_p('output') File.write('output/foo.html', "I am old foo!\n") Nanoc::CLI.run(%w[compile --diff]) expect(File.file?('output.diff')).to be end end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nanoc-4.8.0/spec/nanoc/deploying/�������������������������������������������������������������������0000755�0000041�0000041�00000000000�13134141051�016664� 5����������������������������������������������������������������������������������������������������ustar �www-data������������������������www-data���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nanoc-4.8.0/spec/nanoc/deploying/git_spec.rb��������������������������������������������������������0000644�0000041�0000041�00000020752�13134141051�021014� 0����������������������������������������������������������������������������������������������������ustar �www-data������������������������www-data���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# frozen_string_literal: true describe Nanoc::Deploying::Deployers::Git, stdio: true do let(:deployer) { described_class.new(output_dir, options, dry_run: dry_run) } subject { deployer.run } let(:output_dir) { 'output/' } let(:options) { remote_options.merge(branch_options).merge(forced_options) } let(:dry_run) { false } let(:remote_options) { {} } let(:branch_options) { {} } let(:forced_options) { {} } def run_and_get_stdout(*args) stdout = String.new stderr = String.new piper = Nanoc::Extra::Piper.new(stdout: stdout, stderr: stderr) piper.run(args, '') stdout end def add_changes_to_remote system('git', 'init', '--quiet', 'rere_tmp') Dir.chdir('rere_tmp') do system('git', 'config', 'user.name', 'Zebra Platypus') system('git', 'config', 'user.email', 'zebra@platypus.example.com') system('git', 'remote', 'add', 'origin', '../rere') File.write('evil.txt', 'muaha') system('git', 'add', 'evil.txt') system('git', 'commit', '--quiet', '-m', 'muaha') system('git', 'checkout', '--quiet', '-b', 'giraffe') system('git', 'push', '--quiet', 'origin', 'master') system('git', 'push', '--quiet', 'origin', 'giraffe') end end def rev_list run_and_get_stdout('git', 'rev-list', '--objects', '--all') end shared_examples 'branch configured properly' do context 'clean working copy' do it 'does not commit or push' do subject end end context 'non-clean working copy' do before do Dir.chdir(output_dir) { File.write('hello.txt', 'Hi there') } end shared_examples 'successful push' do context 'no dry run' do it 'outputs status' do expect { subject } .to output(/Deploying via Git to branch “#{branch}” on remote “#{remote}”…/) .to_stdout end it 'makes a change in the local repo' do expect { subject } .to change { Dir.chdir(output_dir) { rev_list } } .from(not_match(/^[a-f0-9]{40} hello\.txt$/)) .to(match(/^[a-f0-9]{40} hello\.txt$/)) expect(Dir.chdir(output_dir) { run_and_get_stdout('git', 'show', branch) }) .to match(/^Author: Nanoc <>$/) end it 'makes a change in the remote repo' do expect { subject } .to change { Dir.chdir('rere') { rev_list } } .from(not_match(/^[a-f0-9]{40} hello\.txt$/)) .to(match(/^[a-f0-9]{40} hello\.txt$/)) end end context 'dry run' do let(:dry_run) { true } it 'makes a change in the local repo' do expect { subject } .not_to change { Dir.chdir(output_dir) { rev_list } } end it 'makes a change in the remote repo' do expect { subject } .not_to change { Dir.chdir('rere') { rev_list } } end end end context 'forced' do let(:forced_options) { { forced: true } } context 'remote has no other changes' do include_examples 'successful push' end context 'remote has other changes' do before { add_changes_to_remote } include_examples 'successful push' end end context 'not forced (implicit)' do let(:forced_options) { {} } context 'remote has no other changes' do include_examples 'successful push' end context 'remote has other changes' do before { add_changes_to_remote } it 'raises' do expect { subject }.to raise_error(Nanoc::Extra::Piper::Error) end end end context 'not forced (explicit)' do let(:forced_options) { { forced: false } } context 'remote has no other changes' do include_examples 'successful push' end context 'remote has other changes' do before { add_changes_to_remote } it 'raises' do expect { subject }.to raise_error(Nanoc::Extra::Piper::Error) end end end end end shared_examples 'remote configured properly' do before do system('git', 'init', '--bare', '--quiet', 'rere') end context 'default branch' do context 'branch does not exist' do it 'raises' do expect { subject }.to raise_error( Nanoc::Deploying::Deployers::Git::Errors::BranchDoesNotExist, 'The branch to deploy, master, does not exist.', ) end end context 'branch exists' do before do Dir.chdir(output_dir) do system('git', 'commit', '--quiet', '-m', 'init', '--allow-empty') end end let(:branch) { 'master' } include_examples 'branch configured properly' end end context 'custom branch' do let(:branch) { 'giraffe' } let(:branch_options) { { branch: branch } } context 'branch does not exist' do it 'raises' do expect { subject }.to raise_error( Nanoc::Deploying::Deployers::Git::Errors::BranchDoesNotExist, 'The branch to deploy, giraffe, does not exist.', ) end end context 'branch exists' do before do Dir.chdir(output_dir) do system('git', 'commit', '--quiet', '-m', 'init', '--allow-empty') system('git', 'branch', 'giraffe') end end include_examples 'branch configured properly' end end end context 'output dir does not exist' do it 'raises' do expect { subject }.to raise_error( Nanoc::Deploying::Deployers::Git::Errors::OutputDirDoesNotExist, 'The directory to deploy, output/, does not exist.', ) end end context 'output dir exists' do before do FileUtils.mkdir_p(output_dir) end context 'output dir is not a Git repo' do it 'raises' do expect { subject }.to raise_error( Nanoc::Deploying::Deployers::Git::Errors::OutputDirIsNotAGitRepo, 'The directory to deploy, output/, is not a Git repository.', ) end end context 'output dir is a Git repo' do before do Dir.chdir(output_dir) do system('git', 'init', '--quiet') system('git', 'config', 'user.name', 'Donkey Giraffe') system('git', 'config', 'user.email', 'donkey@giraffe.example.com') end end context 'default remote' do context 'remote does not exist' do it 'raises' do expect { subject }.to raise_error( Nanoc::Deploying::Deployers::Git::Errors::RemoteDoesNotExist, 'The remote to deploy to, origin, does not exist.', ) end end context 'remote exists' do before do Dir.chdir(output_dir) do system('git', 'remote', 'add', 'origin', '../rere') end end let(:remote) { 'origin' } include_examples 'remote configured properly' end end context 'custom remote (name)' do let(:remote_options) { { remote: 'donkey' } } context 'remote does not exist' do it 'raises' do expect { subject }.to raise_error( Nanoc::Deploying::Deployers::Git::Errors::RemoteDoesNotExist, 'The remote to deploy to, donkey, does not exist.', ) end end context 'remote exists' do before do Dir.chdir(output_dir) do system('git', 'remote', 'add', 'donkey', '../rere') end end let(:remote) { 'donkey' } include_examples 'remote configured properly' end end context 'custom remote (file:// URL)' do let(:remote_options) { { remote: remote } } let(:remote) { "file://#{Dir.getwd}/rere" } include_examples 'remote configured properly' end end end describe '#remote_is_name?' do def val(remote) deployer.send(:remote_is_name?, remote) end it 'recognises names' do expect(val('denis')).to be end it 'recognises URLs' do expect(val('git@github.com:/foo')).not_to be expect(val('http://example.com/donkey.git')).not_to be expect(val('https://example.com/donkey.git')).not_to be expect(val('ssh://example.com/donkey.git')).not_to be expect(val('file:///example.com/donkey.git')).not_to be end end end ����������������������nanoc-4.8.0/spec/nanoc/deploying/fog_spec.rb��������������������������������������������������������0000644�0000041�0000041�00000013057�13134141051�021004� 0����������������������������������������������������������������������������������������������������ustar �www-data������������������������www-data���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# frozen_string_literal: true describe Nanoc::Deploying::Deployers::Fog, stdio: true do let(:deployer) do Nanoc::Deploying::Deployers::Fog.new( 'output/', config, dry_run: is_dry_run, ) end let(:is_dry_run) { false } let(:config) do { bucket: 'bucky', provider: 'local', local_root: 'remote', } end before do # create output FileUtils.mkdir_p('output') FileUtils.mkdir_p('output/etc') File.open('output/woof', 'w') { |io| io.write 'I am a dog!' } File.open('output/etc/meow', 'w') { |io| io.write 'I am a cat!' } # create local cloud FileUtils.mkdir_p('remote') end subject { deployer.run } shared_examples 'no effective deploy' do it 'does not modify remote' do expect { subject }.not_to change { Dir['remote/**/*'].sort } end end shared_examples 'effective deploy' do it 'modifies remote' do expect { subject }.to change { Dir['remote/**/*'].sort } .to([ 'remote/bucky', 'remote/bucky/etc', 'remote/bucky/etc/meow', 'remote/bucky/woof', ]) end it 'does not leave lingering open files' do expect { subject }.not_to leak_open_files end end context 'dry run' do let(:is_dry_run) { true } before do FileUtils.mkdir_p('remote/bucky') FileUtils.mkdir_p('remote/bucky/tiny') File.write('remote/bucky/pig', 'oink?') File.write('remote/bucky/tiny/piglet', 'little oink?') end include_examples 'no effective deploy' context 'with CDN ID' do let(:config) { super().merge(cdn_id: 'donkey-cdn') } let(:cdn) { Object.new } let(:distribution) { Object.new } it 'does not actually invalidate' do expect(::Fog::CDN).to receive(:new).with(provider: 'local', local_root: 'remote').and_return(cdn) expect(cdn).to receive(:get_distribution).with('donkey-cdn').and_return(distribution) subject end end end context 'effective run' do include_examples 'effective deploy' context 'custom path' do context 'custom path ends with /' do let(:config) do super().merge(path: 'foo/') end it 'raises' do expect { subject }.to raise_error('The path `foo/` is not supposed to have a trailing slash') end end context 'custom path does not end with /' do let(:config) do super().merge(path: 'foo') end it 'modifies remote' do expect { subject }.to change { Dir['remote/**/*'].sort } .to([ 'remote/bucky', 'remote/bucky/foo', 'remote/bucky/foo/etc', 'remote/bucky/foo/etc/meow', 'remote/bucky/foo/woof', ]) end end end context 'bucket already exists' do before do FileUtils.mkdir_p('remote/bucky') end include_examples 'effective deploy' end context 'remote contains stale file at root' do before do FileUtils.mkdir_p('remote/bucky') File.write('remote/bucky/pig', 'oink?') end include_examples 'effective deploy' it 'does not contain stale files' do subject expect(Dir['remote/**/*'].sort).not_to include('remote/bucky/pig') end end context 'remote contains stale file in subdirectory' do before do FileUtils.mkdir_p('remote/bucky/secret') File.write('remote/bucky/secret/pig', 'oink?') end include_examples 'effective deploy' it 'does not contain stale files' do subject expect(Dir['remote/**/*'].sort).not_to include('remote/bucky/secret/pig') end end context 'with CDN ID' do let(:config) { super().merge(cdn_id: 'donkey-cdn') } let(:cdn) { Object.new } let(:distribution) { Object.new } it 'invalidates' do expect(::Fog::CDN).to receive(:new).with(provider: 'local', local_root: 'remote').and_return(cdn) expect(cdn).to receive(:get_distribution).with('donkey-cdn').and_return(distribution) expect(cdn).to receive(:post_invalidation).with(distribution, contain_exactly('etc/meow', 'woof')) subject end end context 'remote list consists of truncated sets' do before do expect(::Fog::Storage).to receive(:new).and_return(fog_storage) expect(fog_storage).to receive(:directories).and_return(directories) expect(directories).to receive(:get).and_return(directory) allow(directory).to receive(:files).and_return(files) expect(files).to receive(:get).with('stray').and_return(file_stray).ordered expect(files).to receive(:each) .and_yield(double(:woof, key: 'woof')) .and_yield(double(:meow, key: 'etc/meow')) .and_yield(double(:stray, key: 'stray')) expect(file_stray).to receive(:destroy) expect(files).to receive(:create).with(key: 'woof', body: anything, public: true) do FileUtils.mkdir_p('remote/bucky') File.write('remote/bucky/woof', 'hi') end expect(files).to receive(:create).with(key: 'etc/meow', body: anything, public: true) do FileUtils.mkdir_p('remote/bucky/etc') File.write('remote/bucky/etc/meow', 'hi') end end let(:fog_storage) { double(:fog_storage) } let(:directories) { double(:directories) } let(:directory) { double(:directory) } let(:files) { double(:files) } let(:file_stray) { double(:file_stray) } include_examples 'effective deploy' end end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nanoc-4.8.0/spec/nanoc/telemetry_spec.rb������������������������������������������������������������0000644�0000041�0000041�00000002222�13134141051�020241� 0����������������������������������������������������������������������������������������������������ustar �www-data������������������������www-data���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# frozen_string_literal: true describe Nanoc::Telemetry do subject { described_class.new } example do expect(subject.counter(:filters).values).to eq({}) expect(subject.counter(:filters).get(:erb).value).to eq(0) expect(subject.counter(:filters).value(:erb)).to eq(0) subject.counter(:filters).increment(:erb) expect(subject.counter(:filters).values).to eq(erb: 1) expect(subject.counter(:filters).get(:erb).value).to eq(1) expect(subject.counter(:filters).value(:erb)).to eq(1) end example do subject.summary(:filters).observe(0.1, :erb) expect(subject.summary(:filters).quantile(0.0, :erb)).to be_within(0.00001).of(0.1) expect(subject.summary(:filters).quantile(0.5, :erb)).to be_within(0.00001).of(0.1) expect(subject.summary(:filters).quantile(1.0, :erb)).to be_within(0.00001).of(0.1) subject.summary(:filters).observe(1.1, :erb) expect(subject.summary(:filters).quantile(0.0, :erb)).to be_within(0.00001).of(0.1) expect(subject.summary(:filters).quantile(0.5, :erb)).to be_within(0.00001).of(0.6) expect(subject.summary(:filters).quantile(1.0, :erb)).to be_within(0.00001).of(1.1) end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nanoc-4.8.0/spec/nanoc/rule_dsl/��������������������������������������������������������������������0000755�0000041�0000041�00000000000�13134141051�016503� 5����������������������������������������������������������������������������������������������������ustar �www-data������������������������www-data���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nanoc-4.8.0/spec/nanoc/rule_dsl/rules_collection_spec.rb��������������������������������������������0000644�0000041�0000041�00000017017�13134141051�023415� 0����������������������������������������������������������������������������������������������������ustar �www-data������������������������www-data���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# frozen_string_literal: true describe Nanoc::RuleDSL::RulesCollection do let(:rules_collection) { described_class.new } describe '#data' do subject { rules_collection.data } it 'is nil by default' do expect(subject).to be_nil end it 'can be set' do rules_collection.data = 'asdf' expect(subject).to eq('asdf') end end describe '#compilation_rule_for' do let(:item) { Nanoc::Int::Item.new('content', {}, '/foo.md') } let(:rep) { Nanoc::Int::ItemRep.new(item, rep_name) } let(:rep_name) { :default } subject { rules_collection.compilation_rule_for(rep) } context 'no rules' do it 'is nil' do expect(subject).to be_nil end end context 'some rules, none matching' do before do rules_collection.add_item_compilation_rule(rule) end let(:rule) do Nanoc::RuleDSL::Rule.new(Nanoc::Int::Pattern.from('/bar.*'), :default, proc {}) end it 'is nil' do expect(subject).to be_nil end end context 'some rules, one matching' do before do rules_collection.add_item_compilation_rule(rule_a) rules_collection.add_item_compilation_rule(rule_b) end let(:rule_a) do Nanoc::RuleDSL::Rule.new(Nanoc::Int::Pattern.from('/foo.*'), :default, proc {}) end let(:rule_b) do Nanoc::RuleDSL::Rule.new(Nanoc::Int::Pattern.from('/bar.*'), :default, proc {}) end context 'rep name does not match' do let(:rep_name) { :platypus } it 'is nil' do expect(subject).to be_nil end end context 'rep name matches' do it 'is the rule' do expect(subject).to equal(rule_a) end end end context 'some rules, multiple matching' do before do rules_collection.add_item_compilation_rule(rule_a) rules_collection.add_item_compilation_rule(rule_b) rules_collection.add_item_compilation_rule(rule_c) end let(:rule_a) do Nanoc::RuleDSL::Rule.new(Nanoc::Int::Pattern.from('/foo.*'), :default, proc {}) end let(:rule_b) do Nanoc::RuleDSL::Rule.new(Nanoc::Int::Pattern.from('/*.*'), :default, proc {}) end let(:rule_c) do Nanoc::RuleDSL::Rule.new(Nanoc::Int::Pattern.from('/*.*'), :foo, proc {}) end context 'no rep name matches' do let(:rep_name) { :platypus } it 'is the first matching rule' do expect(subject).to be_nil end end context 'one rep name matches' do let(:rep_name) { :foo } it 'is the first matching rule' do expect(subject).to equal(rule_c) end end context 'multiple rep names match' do it 'is the first matching rule' do expect(subject).to equal(rule_a) end end end end describe '#item_compilation_rules_for' do let(:item) { Nanoc::Int::Item.new('content', {}, '/foo.md') } subject { rules_collection.item_compilation_rules_for(item) } context 'no rules' do it 'is none' do expect(subject).to be_empty end end context 'some rules, none matching' do before do rules_collection.add_item_compilation_rule(rule) end let(:rule) do Nanoc::RuleDSL::Rule.new(Nanoc::Int::Pattern.from('/bar.*'), :default, proc {}) end it 'is none' do expect(subject).to be_empty end end context 'some rules, one matching' do before do rules_collection.add_item_compilation_rule(rule_a) rules_collection.add_item_compilation_rule(rule_b) end let(:rule_a) do Nanoc::RuleDSL::Rule.new(Nanoc::Int::Pattern.from('/foo.*'), :default, proc {}) end let(:rule_b) do Nanoc::RuleDSL::Rule.new(Nanoc::Int::Pattern.from('/bar.*'), :default, proc {}) end it 'is the single rule' do expect(subject).to contain_exactly(rule_a) end end context 'some rules, multiple matching' do before do rules_collection.add_item_compilation_rule(rule_a) rules_collection.add_item_compilation_rule(rule_b) end let(:rule_a) do Nanoc::RuleDSL::Rule.new(Nanoc::Int::Pattern.from('/foo.*'), :default, proc {}) end let(:rule_b) do Nanoc::RuleDSL::Rule.new(Nanoc::Int::Pattern.from('/*.*'), :default, proc {}) end it 'is all matching rule' do expect(subject).to contain_exactly(rule_a, rule_b) end end end describe '#routing_rules_for' do let(:item) { Nanoc::Int::Item.new('content', {}, '/foo.md') } let(:rep) { Nanoc::Int::ItemRep.new(item, :default) } subject { rules_collection.routing_rules_for(rep) } let(:rules) do [ # Matching item, matching rep Nanoc::RuleDSL::Rule.new( Nanoc::Int::Pattern.from('/foo.*'), :default, proc {}, snapshot_name: :a ), Nanoc::RuleDSL::Rule.new( Nanoc::Int::Pattern.from('/foo.*'), :default, proc {}, snapshot_name: :b ), # Matching item, non-matching rep Nanoc::RuleDSL::Rule.new( Nanoc::Int::Pattern.from('/foo.*'), :raw, proc {}, snapshot_name: :a ), Nanoc::RuleDSL::Rule.new( Nanoc::Int::Pattern.from('/foo.*'), :raw, proc {}, snapshot_name: :b ), # Non-matching item, matching rep Nanoc::RuleDSL::Rule.new( Nanoc::Int::Pattern.from('/bar.*'), :default, proc {}, snapshot_name: :a ), Nanoc::RuleDSL::Rule.new( Nanoc::Int::Pattern.from('/bar.*'), :default, proc {}, snapshot_name: :b ), # Non-matching item, non-matching rep Nanoc::RuleDSL::Rule.new( Nanoc::Int::Pattern.from('/bar.*'), :raw, proc {}, snapshot_name: :a ), Nanoc::RuleDSL::Rule.new( Nanoc::Int::Pattern.from('/bar.*'), :raw, proc {}, snapshot_name: :b ), # Matching item, matching rep, but not the first Nanoc::RuleDSL::Rule.new( Nanoc::Int::Pattern.from('/*.*'), :default, proc {}, snapshot_name: :a ), Nanoc::RuleDSL::Rule.new( Nanoc::Int::Pattern.from('/*.*'), :default, proc {}, snapshot_name: :b ), ] end before do rules.each do |rule| rules_collection.add_item_routing_rule(rule) end end it 'returns the first matching rule for every snapshot' do expect(subject).to eq( a: rules[0], b: rules[1], ) end end describe '#filter_for_layout' do let(:layout) { Nanoc::Int::Layout.new('Some content', {}, '/foo.md') } subject { rules_collection.filter_for_layout(layout) } let(:mapping) { {} } before do mapping.each_pair do |key, value| rules_collection.layout_filter_mapping[Nanoc::Int::Pattern.from(key)] = value end end context 'no rules' do it { is_expected.to be_nil } end context 'one non-matching rule' do let(:mapping) do { '/default.*' => [:erb, {}], } end it { is_expected.to be_nil } end context 'one matching rule' do let(:mapping) do { '/foo.*' => [:erb, {}], } end it 'is the single one' do expect(subject).to eq([:erb, {}]) end end context 'multiple matching rules' do let(:mapping) do { '/foo.*' => [:erb, {}], '/*' => [:haml, {}], } end it 'is the first one' do expect(subject).to eq([:erb, {}]) end end end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nanoc-4.8.0/spec/nanoc/rule_dsl/rule_context_spec.rb������������������������������������������������0000644�0000041�0000041�00000017750�13134141051�022567� 0����������������������������������������������������������������������������������������������������ustar �www-data������������������������www-data���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# frozen_string_literal: true describe(Nanoc::RuleDSL::RuleContext) do subject(:rule_context) do described_class.new(rep: rep, site: site, executor: executor, view_context: view_context) end let(:item_identifier) { Nanoc::Identifier.new('/foo.md') } let(:item) { Nanoc::Int::Item.new('content', {}, item_identifier) } let(:config) { Nanoc::Int::Configuration.new } let(:items) { Nanoc::Int::ItemCollection.new(config) } let(:layouts) { Nanoc::Int::LayoutCollection.new(config) } let(:rep) { double(:rep, item: item) } let(:site) { double(:site, items: items, layouts: layouts, config: config) } let(:executor) { double(:executor) } let(:reps) { double(:reps) } let(:compilation_context) { double(:compilation_context) } let(:view_context) { Nanoc::ViewContext.new(reps: reps, items: items, dependency_tracker: dependency_tracker, compilation_context: compilation_context, snapshot_repo: snapshot_repo) } let(:dependency_tracker) { double(:dependency_tracker) } let(:snapshot_repo) { double(:snapshot_repo) } describe '#initialize' do it 'wraps objects in view classes' do expect(subject.rep.class).to eql(Nanoc::ItemRepView) expect(subject.item.class).to eql(Nanoc::ItemWithoutRepsView) expect(subject.config.class).to eql(Nanoc::ConfigView) expect(subject.layouts.class).to eql(Nanoc::LayoutCollectionView) expect(subject.items.class).to eql(Nanoc::ItemCollectionWithoutRepsView) end it 'contains the right objects' do expect(rule_context.rep.unwrap).to eql(rep) expect(rule_context.item.unwrap).to eql(item) expect(rule_context.config.unwrap).to eql(config) expect(rule_context.layouts.unwrap).to eql(layouts) expect(rule_context.items.unwrap).to eql(items) end end describe '#item' do subject { rule_context.item } it 'is a view without reps access' do expect(subject.class).to eql(Nanoc::ItemWithoutRepsView) end it 'contains the right item' do expect(subject.unwrap).to eql(item) end context 'with legacy identifier and children/parent' do let(:item_identifier) { Nanoc::Identifier.new('/foo/', type: :legacy) } let(:parent_identifier) { Nanoc::Identifier.new('/', type: :legacy) } let(:parent) { Nanoc::Int::Item.new('parent', {}, parent_identifier) } let(:child_identifier) { Nanoc::Identifier.new('/foo/bar/', type: :legacy) } let(:child) { Nanoc::Int::Item.new('child', {}, child_identifier) } let(:items) do Nanoc::Int::ItemCollection.new(config, [item, parent, child]) end it 'has a parent' do expect(subject.parent.unwrap).to eql(parent) end it 'wraps the parent in a view without reps access' do expect(subject.parent.class).to eql(Nanoc::ItemWithoutRepsView) expect(subject.parent).not_to respond_to(:compiled_content) expect(subject.parent).not_to respond_to(:path) expect(subject.parent).not_to respond_to(:reps) end it 'has children' do expect(subject.children.map(&:unwrap)).to eql([child]) end it 'wraps the children in a view without reps access' do expect(subject.children.map(&:class)).to eql([Nanoc::ItemWithoutRepsView]) expect(subject.children[0]).not_to respond_to(:compiled_content) expect(subject.children[0]).not_to respond_to(:path) expect(subject.children[0]).not_to respond_to(:reps) end end end describe '#items' do subject { rule_context.items } let(:item_identifier) { Nanoc::Identifier.new('/foo/', type: :legacy) } let(:parent_identifier) { Nanoc::Identifier.new('/', type: :legacy) } let(:parent) { Nanoc::Int::Item.new('parent', {}, parent_identifier) } let(:child_identifier) { Nanoc::Identifier.new('/foo/bar/', type: :legacy) } let(:child) { Nanoc::Int::Item.new('child', {}, child_identifier) } let(:items) do Nanoc::Int::ItemCollection.new(config, [item, parent, child]) end it 'is a view without reps access' do expect(subject.class).to eql(Nanoc::ItemCollectionWithoutRepsView) end it 'contains all items' do expect(subject.unwrap).to match_array([item, parent, child]) end it 'provides no rep access' do allow(dependency_tracker).to receive(:bounce).and_return(nil) expect(subject['/']).not_to be_nil expect(subject['/']).not_to respond_to(:compiled_content) expect(subject['/']).not_to respond_to(:path) expect(subject['/']).not_to respond_to(:reps) expect(subject['/foo/']).not_to be_nil expect(subject['/foo/']).not_to respond_to(:compiled_content) expect(subject['/foo/']).not_to respond_to(:path) expect(subject['/foo/']).not_to respond_to(:reps) expect(subject['/foo/bar/']).not_to be_nil expect(subject['/foo/bar/']).not_to respond_to(:compiled_content) expect(subject['/foo/bar/']).not_to respond_to(:path) expect(subject['/foo/bar/']).not_to respond_to(:reps) end end describe '#filter' do subject { rule_context.filter(filter_name, filter_args) } let(:filter_name) { :donkey } let(:filter_args) { { color: 'grey' } } it 'makes a request to the executor' do expect(executor).to receive(:filter).with(filter_name, filter_args) subject end end describe '#layout' do subject { rule_context.layout(layout_identifier, extra_filter_args) } let(:layout_identifier) { '/default.*' } let(:extra_filter_args) { { color: 'grey' } } it 'makes a request to the executor' do expect(executor).to receive(:layout).with(layout_identifier, extra_filter_args) subject end end describe '#snapshot' do subject { rule_context.snapshot(snapshot_name, path: path) } let(:snapshot_name) { :for_snippet } let(:path) { '/foo.html' } it 'makes a request to the executor' do expect(executor).to receive(:snapshot).with(:for_snippet, path: '/foo.html') subject end end describe '#write' do context 'with path' do context 'calling once' do subject { rule_context.write('/foo.html') } it 'makes a request to the executor' do expect(executor).to receive(:snapshot).with(:_0, path: '/foo.html') subject end end context 'calling twice' do subject do rule_context.write('/foo.html') rule_context.write('/bar.html') end it 'makes two requests to the executor with unique snapshot names' do expect(executor).to receive(:snapshot).with(:_0, path: '/foo.html') expect(executor).to receive(:snapshot).with(:_1, path: '/bar.html') subject end end end context 'with :ext, without period' do context 'calling once' do subject { rule_context.write(ext: 'html') } it 'makes a request to the executor' do expect(executor).to receive(:snapshot).with(:_0, path: '/foo.html') subject end end context 'calling twice' do subject do rule_context.write(ext: 'html') rule_context.write(ext: 'htm') end it 'makes a request to the executor' do expect(executor).to receive(:snapshot).with(:_0, path: '/foo.html') expect(executor).to receive(:snapshot).with(:_1, path: '/foo.htm') subject end end end context 'with :ext, without period' do context 'calling once' do subject { rule_context.write(ext: '.html') } it 'makes a request to the executor' do expect(executor).to receive(:snapshot).with(:_0, path: '/foo.html') subject end end context 'calling twice' do subject do rule_context.write(ext: '.html') rule_context.write(ext: '.htm') end it 'makes a request to the executor' do expect(executor).to receive(:snapshot).with(:_0, path: '/foo.html') expect(executor).to receive(:snapshot).with(:_1, path: '/foo.htm') subject end end end end end ������������������������nanoc-4.8.0/spec/nanoc/rule_dsl/recording_executor_spec.rb������������������������������������������0000644�0000041�0000041�00000011721�13134141051�023736� 0����������������������������������������������������������������������������������������������������ustar �www-data������������������������www-data���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# frozen_string_literal: true describe Nanoc::RuleDSL::RecordingExecutor do let(:executor) { described_class.new(rep) } let(:action_sequence) { executor.action_sequence } let(:item) { Nanoc::Int::Item.new('stuff', {}, '/foo.md') } let(:rep) { Nanoc::Int::ItemRep.new(item, :default) } describe '#filter' do it 'records filter call without arguments' do executor.filter(:erb) expect(action_sequence.size).to eql(1) expect(action_sequence[0]).to be_a(Nanoc::Int::ProcessingActions::Filter) expect(action_sequence[0].filter_name).to eql(:erb) expect(action_sequence[0].params).to eql({}) end it 'records filter call with arguments' do executor.filter(:erb, x: 123) expect(action_sequence.size).to eql(1) expect(action_sequence[0]).to be_a(Nanoc::Int::ProcessingActions::Filter) expect(action_sequence[0].filter_name).to eql(:erb) expect(action_sequence[0].params).to eql(x: 123) end end describe '#layout' do it 'records layout call without arguments' do executor.layout('/default.*') expect(action_sequence.size).to eql(2) expect(action_sequence[0]).to be_a(Nanoc::Int::ProcessingActions::Snapshot) expect(action_sequence[0].snapshot_names).to eql([:pre]) expect(action_sequence[0].paths).to be_empty expect(action_sequence[1]).to be_a(Nanoc::Int::ProcessingActions::Layout) expect(action_sequence[1].layout_identifier).to eql('/default.*') expect(action_sequence[1].params).to eql({}) end it 'records layout call with arguments' do executor.layout('/default.*', donkey: 123) expect(action_sequence.size).to eql(2) expect(action_sequence[0]).to be_a(Nanoc::Int::ProcessingActions::Snapshot) expect(action_sequence[0].snapshot_names).to eql([:pre]) expect(action_sequence[0].paths).to be_empty expect(action_sequence[1]).to be_a(Nanoc::Int::ProcessingActions::Layout) expect(action_sequence[1].layout_identifier).to eql('/default.*') expect(action_sequence[1].params).to eql(donkey: 123) end it 'fails when passed a symbol' do expect { executor.layout(:default, donkey: 123) }.to raise_error(ArgumentError) end end describe '#snapshot' do context 'snapshot already exists' do before do executor.snapshot(:foo) end it 'raises when creating same snapshot' do expect { executor.snapshot(:foo) } .to raise_error(Nanoc::Int::Errors::CannotCreateMultipleSnapshotsWithSameName) end end context 'no arguments' do subject { executor.snapshot(:foo) } it 'records' do subject expect(action_sequence.size).to eql(1) expect(action_sequence[0]).to be_a(Nanoc::Int::ProcessingActions::Snapshot) expect(action_sequence[0].snapshot_names).to eql([:foo]) expect(action_sequence[0].paths).to be_empty end end context 'final argument' do subject { executor.snapshot(:foo, path: path) } let(:path) { nil } context 'routing rule does not exist' do context 'no explicit path given' do it 'records' do subject expect(action_sequence.size).to eql(1) expect(action_sequence[0]).to be_a(Nanoc::Int::ProcessingActions::Snapshot) expect(action_sequence[0].snapshot_names).to eql([:foo]) expect(action_sequence[0].paths).to be_empty end end context 'explicit path given as string' do let(:path) { '/routed-foo.html' } it 'records' do subject expect(action_sequence.size).to eql(1) expect(action_sequence[0]).to be_a(Nanoc::Int::ProcessingActions::Snapshot) expect(action_sequence[0].snapshot_names).to eql([:foo]) expect(action_sequence[0].paths).to eql(['/routed-foo.html']) end end context 'explicit path given as identifier' do let(:path) { Nanoc::Identifier.from('/routed-foo.html') } it 'records' do subject expect(action_sequence.size).to eql(1) expect(action_sequence[0]).to be_a(Nanoc::Int::ProcessingActions::Snapshot) expect(action_sequence[0].snapshot_names).to eql([:foo]) expect(action_sequence[0].paths).to eql(['/routed-foo.html']) end end end end it 'raises when given unknown arguments' do expect { executor.snapshot(:foo, animal: 'giraffe') } .to raise_error(ArgumentError) end it 'can create multiple snapshots with different names' do executor.snapshot(:foo) executor.snapshot(:bar) expect(action_sequence.size).to eql(2) expect(action_sequence[0]).to be_a(Nanoc::Int::ProcessingActions::Snapshot) expect(action_sequence[0].snapshot_names).to eql([:foo]) expect(action_sequence[1]).to be_a(Nanoc::Int::ProcessingActions::Snapshot) expect(action_sequence[1].snapshot_names).to eql([:bar]) end end end �����������������������������������������������nanoc-4.8.0/spec/nanoc/rule_dsl/action_sequence_calculator_spec.rb����������������������������������0000644�0000041�0000041�00000017073�13134141051�025430� 0����������������������������������������������������������������������������������������������������ustar �www-data������������������������www-data���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# frozen_string_literal: true describe(Nanoc::RuleDSL::ActionSequenceCalculator) do subject(:action_sequence_calculator) do described_class.new(site: site, rules_collection: rules_collection) end let(:rules_collection) { Nanoc::RuleDSL::RulesCollection.new } let(:site) { double(:site) } describe '#[]' do subject { action_sequence_calculator[obj] } context 'with item rep' do let(:obj) { Nanoc::Int::ItemRep.new(item, :csv) } let(:item) { Nanoc::Int::Item.new('content', {}, Nanoc::Identifier.from('/list.md')) } let(:config) { Nanoc::Int::Configuration.new.with_defaults } let(:items) { Nanoc::Int::ItemCollection.new(config) } let(:layouts) { Nanoc::Int::LayoutCollection.new(config) } let(:site) { double(:site, items: items, layouts: layouts, config: config, compiler: compiler) } let(:compiler) { double(:compiler, compilation_context: compilation_context) } let(:compilation_context) { double(:compilation_context) } let(:view_context) { double(:view_context) } before do expect(compilation_context).to receive(:create_view_context).and_return(view_context) end context 'no rules exist' do it 'raises error' do error = Nanoc::RuleDSL::ActionSequenceCalculator::NoActionSequenceForItemRepException expect { subject }.to raise_error(error) end end context 'rules exist' do before do rules_proc = proc do filter :erb, speed: :over_9000 layout '/default.*' filter :typohero end rule = Nanoc::RuleDSL::Rule.new(Nanoc::Int::Pattern.from('/list.*'), :csv, rules_proc) rules_collection.add_item_compilation_rule(rule) end example do subject expect(subject[0]).to be_a(Nanoc::Int::ProcessingActions::Snapshot) expect(subject[0].snapshot_names).to eql([:raw]) expect(subject[0].paths).to be_empty expect(subject[1]).to be_a(Nanoc::Int::ProcessingActions::Filter) expect(subject[1].filter_name).to eql(:erb) expect(subject[1].params).to eql(speed: :over_9000) expect(subject[2]).to be_a(Nanoc::Int::ProcessingActions::Snapshot) expect(subject[2].snapshot_names).to eql([:pre]) expect(subject[2].paths).to be_empty expect(subject[3]).to be_a(Nanoc::Int::ProcessingActions::Layout) expect(subject[3].layout_identifier).to eql('/default.*') expect(subject[3].params).to be_nil expect(subject[4]).to be_a(Nanoc::Int::ProcessingActions::Filter) expect(subject[4].filter_name).to eql(:typohero) expect(subject[4].params).to eql({}) expect(subject[5]).to be_a(Nanoc::Int::ProcessingActions::Snapshot) expect(subject[5].snapshot_names).to eql(%i[post last]) expect(subject[5].paths).to be_empty expect(subject.size).to eql(6) end end context 'no routing rule exists' do before do # Add compilation rule compilation_rule = Nanoc::RuleDSL::Rule.new(Nanoc::Int::Pattern.from('/list.*'), :csv, proc {}) rules_collection.add_item_compilation_rule(compilation_rule) end example do subject expect(subject[0]).to be_a(Nanoc::Int::ProcessingActions::Snapshot) expect(subject[0].snapshot_names).to eql(%i[raw last pre]) expect(subject[0].paths).to be_empty expect(subject.size).to eql(1) end end context 'routing rule exists' do before do # Add compilation rule compilation_rule = Nanoc::RuleDSL::Rule.new(Nanoc::Int::Pattern.from('/list.*'), :csv, proc {}) rules_collection.add_item_compilation_rule(compilation_rule) # Add routing rule routing_rule = Nanoc::RuleDSL::Rule.new(Nanoc::Int::Pattern.from('/list.*'), :csv, proc { '/foo.md' }, snapshot_name: :last) rules_collection.add_item_routing_rule(routing_rule) end example do subject expect(subject[0]).to be_a(Nanoc::Int::ProcessingActions::Snapshot) expect(subject[0].snapshot_names).to eql(%i[raw last pre]) expect(subject[0].paths).to eq(['/foo.md']) expect(subject.size).to eql(1) end end context 'routing rule for other rep exists' do before do # Add compilation rule compilation_rule = Nanoc::RuleDSL::Rule.new(Nanoc::Int::Pattern.from('/list.*'), :csv, proc {}) rules_collection.add_item_compilation_rule(compilation_rule) # Add routing rule routing_rule = Nanoc::RuleDSL::Rule.new(Nanoc::Int::Pattern.from('/list.*'), :abc, proc { '/foo.md' }, snapshot_name: :last) rules_collection.add_item_routing_rule(routing_rule) end example do subject expect(subject[0]).to be_a(Nanoc::Int::ProcessingActions::Snapshot) expect(subject[0].snapshot_names).to eql(%i[raw last pre]) expect(subject[0].paths).to be_empty expect(subject.size).to eql(1) end end end context 'with layout' do let(:obj) { Nanoc::Int::Layout.new('content', {}, '/default.erb') } context 'no rules exist' do it 'raises error' do error = Nanoc::RuleDSL::ActionSequenceCalculator::NoActionSequenceForLayoutException expect { subject }.to raise_error(error) end end context 'rule exists' do before do pat = Nanoc::Int::Pattern.from('/*.erb') rules_collection.layout_filter_mapping[pat] = [:erb, { x: 123 }] end it 'contains memory for the rule' do expect(subject.size).to eql(1) expect(subject[0]).to be_a(Nanoc::Int::ProcessingActions::Filter) expect(subject[0].filter_name).to eql(:erb) expect(subject[0].params).to eql(x: 123) end end end context 'with something else' do let(:obj) { :donkey } it 'errors' do error = Nanoc::RuleDSL::ActionSequenceCalculator::UnsupportedObjectTypeException expect { subject }.to raise_error(error) end end end describe '#compact_snapshots' do subject { action_sequence_calculator.compact_snapshots(action_sequence) } let(:action_sequence) do Nanoc::Int::ActionSequence.build(rep) do |b| b.add_snapshot(:a1, nil) b.add_snapshot(:a2, '/a2.md') b.add_snapshot(:a3, nil) b.add_filter(:erb, awesomeness: 'high') b.add_snapshot(:b1, '/b1.md') b.add_snapshot(:b2, nil) b.add_snapshot(:b3, '/b3.md') b.add_filter(:erb, awesomeness: 'high') b.add_snapshot(:c, nil) end end let(:item) { Nanoc::Int::Item.new('asdf', {}, '/foo.md') } let(:rep) { Nanoc::Int::ItemRep.new(item, :default) } example do expect(subject[0]).to be_a(Nanoc::Int::ProcessingActions::Snapshot) expect(subject[0].snapshot_names).to eql(%i[a1 a2 a3]) expect(subject[0].paths).to eql(['/a2.md']) expect(subject[1]).to be_a(Nanoc::Int::ProcessingActions::Filter) expect(subject[2]).to be_a(Nanoc::Int::ProcessingActions::Snapshot) expect(subject[2].snapshot_names).to eql(%i[b1 b2 b3]) expect(subject[2].paths).to eql(['/b1.md', '/b3.md']) expect(subject[3]).to be_a(Nanoc::Int::ProcessingActions::Filter) expect(subject[4]).to be_a(Nanoc::Int::ProcessingActions::Snapshot) expect(subject[4].snapshot_names).to eql([:c]) expect(subject[4].paths).to be_empty expect(subject.size).to eql(5) end end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nanoc-4.8.0/spec/nanoc/cli/�������������������������������������������������������������������������0000755�0000041�0000041�00000000000�13134141051�015441� 5����������������������������������������������������������������������������������������������������ustar �www-data������������������������www-data���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nanoc-4.8.0/spec/nanoc/cli/commands/����������������������������������������������������������������0000755�0000041�0000041�00000000000�13134141051�017242� 5����������������������������������������������������������������������������������������������������ustar �www-data������������������������www-data���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nanoc-4.8.0/spec/nanoc/cli/commands/compile/��������������������������������������������������������0000755�0000041�0000041�00000000000�13134141051�020672� 5����������������������������������������������������������������������������������������������������ustar �www-data������������������������www-data���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nanoc-4.8.0/spec/nanoc/cli/commands/compile/file_action_printer_spec.rb�����������������������������0000644�0000041�0000041�00000004624�13134141051�026256� 0����������������������������������������������������������������������������������������������������ustar �www-data������������������������www-data���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# frozen_string_literal: true describe Nanoc::CLI::Commands::CompileListeners::FileActionPrinter, stdio: true do let(:listener) { described_class.new(reps: reps) } before { Timecop.freeze(Time.local(2008, 1, 2, 14, 5, 0)) } after { Timecop.return } let(:reps) do Nanoc::Int::ItemRepRepo.new.tap do |reps| reps << rep end end let(:item) { Nanoc::Int::Item.new('<%= 1 + 2 %>', {}, '/hi.md') } let(:rep) do Nanoc::Int::ItemRep.new(item, :default).tap do |rep| rep.raw_paths = { default: ['/hi.html'] } end end it 'records from compilation_started to rep_written' do listener.start Timecop.freeze(Time.local(2008, 9, 1, 10, 5, 0)) Nanoc::Int::NotificationCenter.post(:compilation_started, rep) Timecop.freeze(Time.local(2008, 9, 1, 10, 5, 1)) expect { Nanoc::Int::NotificationCenter.post(:rep_written, rep, false, '/foo.html', true, true) } .to output(/create.*\[1\.00s\]/).to_stdout end it 'stops listening after #stop' do listener.start listener.stop Nanoc::Int::NotificationCenter.post(:compilation_started, rep) expect { Nanoc::Int::NotificationCenter.post(:rep_written, rep, false, '/foo.html', true, true) } .not_to output(/create/).to_stdout end it 'records from compilation_started over compilation_suspended to rep_written' do listener.start Timecop.freeze(Time.local(2008, 9, 1, 10, 5, 0)) Nanoc::Int::NotificationCenter.post(:compilation_started, rep) Timecop.freeze(Time.local(2008, 9, 1, 10, 5, 1)) Nanoc::Int::NotificationCenter.post(:compilation_suspended, rep, :__irrelevant__) Timecop.freeze(Time.local(2008, 9, 1, 10, 5, 3)) Nanoc::Int::NotificationCenter.post(:compilation_started, rep) Timecop.freeze(Time.local(2008, 9, 1, 10, 5, 6)) expect { Nanoc::Int::NotificationCenter.post(:rep_written, rep, false, '/foo.html', true, true) } .to output(/create.*\[4\.00s\]/).to_stdout end context 'log level = high' do before { listener.start } before { Nanoc::CLI::Logger.instance.level = :high } it 'prints skipped (uncompiled) reps' do expect { listener.stop } .not_to output(/skip/).to_stdout end end context 'log level = low' do before { listener.start } before { Nanoc::CLI::Logger.instance.level = :low } it 'prints nothing' do expect { listener.stop } .to output(/skip.*\/hi\.html/).to_stdout end end end ������������������������������������������������������������������������������������������������������������nanoc-4.8.0/spec/nanoc/cli/commands/compile/timing_recorder_spec.rb���������������������������������0000644�0000041�0000041�00000035461�13134141051�025416� 0����������������������������������������������������������������������������������������������������ustar �www-data������������������������www-data���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# frozen_string_literal: true describe Nanoc::CLI::Commands::CompileListeners::TimingRecorder, stdio: true do let(:listener) { described_class.new(reps: reps) } before { Timecop.freeze(Time.local(2008, 1, 2, 14, 5, 0)) } after { Timecop.return } before { Nanoc::CLI.verbosity = 2 } before { listener.start } after { listener.stop_safely } let(:reps) do Nanoc::Int::ItemRepRepo.new.tap do |reps| reps << rep end end let(:item) { Nanoc::Int::Item.new('<%= 1 + 2 %>', {}, '/hi.md') } let(:rep) do Nanoc::Int::ItemRep.new(item, :default).tap do |rep| rep.raw_paths = { default: ['/hi.html'] } end end let(:other_rep) do Nanoc::Int::ItemRep.new(item, :other).tap do |rep| rep.raw_paths = { default: ['/bye.html'] } end end it 'prints filters table' do Timecop.freeze(Time.local(2008, 9, 1, 10, 5, 0)) Nanoc::Int::NotificationCenter.post(:filtering_started, rep, :erb) Timecop.freeze(Time.local(2008, 9, 1, 10, 5, 1)) Nanoc::Int::NotificationCenter.post(:filtering_ended, rep, :erb) Timecop.freeze(Time.local(2008, 9, 1, 10, 14, 1)) Nanoc::Int::NotificationCenter.post(:filtering_started, rep, :erb) Timecop.freeze(Time.local(2008, 9, 1, 10, 14, 3)) Nanoc::Int::NotificationCenter.post(:filtering_ended, rep, :erb) expect { listener.stop } .to output(/^\s*erb │ 2 1\.00s 1\.50s 1\.90s 1\.95s 2\.00s 3\.00s$/).to_stdout end it 'records single from filtering_started to filtering_ended' do Timecop.freeze(Time.local(2008, 9, 1, 10, 5, 0)) Nanoc::Int::NotificationCenter.post(:filtering_started, rep, :erb) Timecop.freeze(Time.local(2008, 9, 1, 10, 5, 1)) Nanoc::Int::NotificationCenter.post(:filtering_ended, rep, :erb) expect(listener.telemetry.summary(:filters).get('erb').min).to eq(1.00) expect(listener.telemetry.summary(:filters).get('erb').avg).to eq(1.00) expect(listener.telemetry.summary(:filters).get('erb').max).to eq(1.00) expect(listener.telemetry.summary(:filters).get('erb').sum).to eq(1.00) expect(listener.telemetry.summary(:filters).get('erb').count).to eq(1.00) end it 'records multiple from filtering_started to filtering_ended' do Timecop.freeze(Time.local(2008, 9, 1, 10, 5, 0)) Nanoc::Int::NotificationCenter.post(:filtering_started, rep, :erb) Timecop.freeze(Time.local(2008, 9, 1, 10, 5, 1)) Nanoc::Int::NotificationCenter.post(:filtering_ended, rep, :erb) Timecop.freeze(Time.local(2008, 9, 1, 10, 14, 1)) Nanoc::Int::NotificationCenter.post(:filtering_started, rep, :erb) Timecop.freeze(Time.local(2008, 9, 1, 10, 14, 3)) Nanoc::Int::NotificationCenter.post(:filtering_ended, rep, :erb) expect(listener.telemetry.summary(:filters).get('erb').min).to eq(1.00) expect(listener.telemetry.summary(:filters).get('erb').avg).to eq(1.50) expect(listener.telemetry.summary(:filters).get('erb').max).to eq(2.00) expect(listener.telemetry.summary(:filters).get('erb').sum).to eq(3.00) expect(listener.telemetry.summary(:filters).get('erb').count).to eq(2.00) end it 'records filters in nested filtering_started/filtering_ended' do Timecop.freeze(Time.local(2008, 9, 1, 10, 5, 0)) Nanoc::Int::NotificationCenter.post(:filtering_started, rep, :outer) Timecop.freeze(Time.local(2008, 9, 1, 10, 5, 1)) Nanoc::Int::NotificationCenter.post(:filtering_started, rep, :inner) Timecop.freeze(Time.local(2008, 9, 1, 10, 5, 3)) Nanoc::Int::NotificationCenter.post(:filtering_ended, rep, :inner) Timecop.freeze(Time.local(2008, 9, 1, 10, 5, 6)) Nanoc::Int::NotificationCenter.post(:filtering_ended, rep, :outer) expect(listener.telemetry.summary(:filters).get('inner').min).to eq(2.00) expect(listener.telemetry.summary(:filters).get('inner').avg).to eq(2.00) expect(listener.telemetry.summary(:filters).get('inner').max).to eq(2.00) expect(listener.telemetry.summary(:filters).get('inner').sum).to eq(2.00) expect(listener.telemetry.summary(:filters).get('inner').count).to eq(1.00) expect(listener.telemetry.summary(:filters).get('outer').min).to eq(6.00) expect(listener.telemetry.summary(:filters).get('outer').avg).to eq(6.00) expect(listener.telemetry.summary(:filters).get('outer').max).to eq(6.00) expect(listener.telemetry.summary(:filters).get('outer').sum).to eq(6.00) expect(listener.telemetry.summary(:filters).get('outer').count).to eq(1.00) end it 'pauses outer stopwatch when suspended' do Timecop.freeze(Time.local(2008, 9, 1, 10, 5, 0)) Nanoc::Int::NotificationCenter.post(:compilation_started, rep) Nanoc::Int::NotificationCenter.post(:filtering_started, rep, :outer) Timecop.freeze(Time.local(2008, 9, 1, 10, 5, 1)) Nanoc::Int::NotificationCenter.post(:filtering_started, rep, :inner) Timecop.freeze(Time.local(2008, 9, 1, 10, 5, 3)) Nanoc::Int::NotificationCenter.post(:compilation_suspended, rep, :__anything__) Timecop.freeze(Time.local(2008, 9, 1, 10, 5, 6)) Nanoc::Int::NotificationCenter.post(:compilation_started, rep) Timecop.freeze(Time.local(2008, 9, 1, 10, 5, 10)) Nanoc::Int::NotificationCenter.post(:filtering_ended, rep, :inner) Nanoc::Int::NotificationCenter.post(:filtering_ended, rep, :outer) expect(listener.telemetry.summary(:filters).get('outer').min).to eq(7.00) expect(listener.telemetry.summary(:filters).get('outer').avg).to eq(7.00) expect(listener.telemetry.summary(:filters).get('outer').max).to eq(7.00) expect(listener.telemetry.summary(:filters).get('outer').sum).to eq(7.00) expect(listener.telemetry.summary(:filters).get('outer').count).to eq(1.00) end it 'records single from filtering_started over compilation_{suspended,started} to filtering_ended' do Nanoc::Int::NotificationCenter.post(:compilation_started, rep) Timecop.freeze(Time.local(2008, 9, 1, 10, 5, 0)) Nanoc::Int::NotificationCenter.post(:filtering_started, rep, :erb) Timecop.freeze(Time.local(2008, 9, 1, 10, 5, 1)) Nanoc::Int::NotificationCenter.post(:compilation_suspended, rep, :__anything__) Timecop.freeze(Time.local(2008, 9, 1, 10, 5, 3)) Nanoc::Int::NotificationCenter.post(:compilation_started, rep) Timecop.freeze(Time.local(2008, 9, 1, 10, 5, 7)) Nanoc::Int::NotificationCenter.post(:filtering_ended, rep, :erb) expect(listener.telemetry.summary(:filters).get('erb').min).to eq(5.00) expect(listener.telemetry.summary(:filters).get('erb').avg).to eq(5.00) expect(listener.telemetry.summary(:filters).get('erb').max).to eq(5.00) expect(listener.telemetry.summary(:filters).get('erb').sum).to eq(5.00) expect(listener.telemetry.summary(:filters).get('erb').count).to eq(1.00) end it 'records single phase start+stop' do Timecop.freeze(Time.local(2008, 9, 1, 10, 5, 0)) Nanoc::Int::NotificationCenter.post(:phase_started, 'donkey', rep) Timecop.freeze(Time.local(2008, 9, 1, 10, 5, 1)) Nanoc::Int::NotificationCenter.post(:phase_ended, 'donkey', rep) expect(listener.telemetry.summary(:phases).get('donkey').min).to eq(1.00) expect(listener.telemetry.summary(:phases).get('donkey').avg).to eq(1.00) expect(listener.telemetry.summary(:phases).get('donkey').max).to eq(1.00) expect(listener.telemetry.summary(:phases).get('donkey').sum).to eq(1.00) expect(listener.telemetry.summary(:phases).get('donkey').count).to eq(1.00) end it 'records multiple phase start+stop' do Timecop.freeze(Time.local(2008, 9, 1, 10, 5, 0)) Nanoc::Int::NotificationCenter.post(:phase_started, 'donkey', rep) Timecop.freeze(Time.local(2008, 9, 1, 10, 5, 1)) Nanoc::Int::NotificationCenter.post(:phase_ended, 'donkey', rep) Timecop.freeze(Time.local(2008, 9, 1, 11, 6, 0)) Nanoc::Int::NotificationCenter.post(:phase_started, 'donkey', rep) Timecop.freeze(Time.local(2008, 9, 1, 11, 6, 2)) Nanoc::Int::NotificationCenter.post(:phase_ended, 'donkey', rep) expect(listener.telemetry.summary(:phases).get('donkey').min).to eq(1.00) expect(listener.telemetry.summary(:phases).get('donkey').avg).to eq(1.50) expect(listener.telemetry.summary(:phases).get('donkey').max).to eq(2.00) expect(listener.telemetry.summary(:phases).get('donkey').sum).to eq(3.00) expect(listener.telemetry.summary(:phases).get('donkey').count).to eq(2.00) end it 'records single phase start+yield+resume+stop' do Timecop.freeze(Time.local(2008, 9, 1, 10, 5, 0)) Nanoc::Int::NotificationCenter.post(:phase_started, 'donkey', rep) Timecop.freeze(Time.local(2008, 9, 1, 10, 5, 1)) Nanoc::Int::NotificationCenter.post(:phase_yielded, 'donkey', rep) Timecop.freeze(Time.local(2008, 9, 1, 11, 6, 0)) Nanoc::Int::NotificationCenter.post(:phase_resumed, 'donkey', rep) Timecop.freeze(Time.local(2008, 9, 1, 11, 6, 2)) Nanoc::Int::NotificationCenter.post(:phase_ended, 'donkey', rep) expect(listener.telemetry.summary(:phases).get('donkey').min).to eq(3.00) expect(listener.telemetry.summary(:phases).get('donkey').avg).to eq(3.00) expect(listener.telemetry.summary(:phases).get('donkey').max).to eq(3.00) expect(listener.telemetry.summary(:phases).get('donkey').sum).to eq(3.00) expect(listener.telemetry.summary(:phases).get('donkey').count).to eq(1.00) end it 'records single phase start+yield+abort+start+stop' do Timecop.freeze(Time.local(2008, 9, 1, 10, 5, 0)) Nanoc::Int::NotificationCenter.post(:phase_started, 'donkey', rep) Timecop.freeze(Time.local(2008, 9, 1, 10, 5, 1)) Nanoc::Int::NotificationCenter.post(:phase_yielded, 'donkey', rep) Timecop.freeze(Time.local(2008, 9, 1, 11, 6, 0)) Nanoc::Int::NotificationCenter.post(:phase_aborted, 'donkey', rep) Timecop.freeze(Time.local(2008, 9, 1, 12, 7, 2)) Nanoc::Int::NotificationCenter.post(:phase_started, 'donkey', rep) Timecop.freeze(Time.local(2008, 9, 1, 12, 7, 5)) Nanoc::Int::NotificationCenter.post(:phase_ended, 'donkey', rep) expect(listener.telemetry.summary(:phases).get('donkey').min).to eq(1.00) expect(listener.telemetry.summary(:phases).get('donkey').avg).to eq(2.00) expect(listener.telemetry.summary(:phases).get('donkey').max).to eq(3.00) expect(listener.telemetry.summary(:phases).get('donkey').sum).to eq(4.00) expect(listener.telemetry.summary(:phases).get('donkey').count).to eq(2.00) end it 'records stage duration' do Timecop.freeze(Time.local(2008, 9, 1, 10, 5, 0)) Nanoc::Int::NotificationCenter.post(:stage_started, 'donkey', rep) Timecop.freeze(Time.local(2008, 9, 1, 10, 5, 1)) Nanoc::Int::NotificationCenter.post(:stage_ended, 'donkey', rep) expect(listener.telemetry.summary(:stages).get('donkey').sum).to eq(1.00) expect(listener.telemetry.summary(:stages).get('donkey').count).to eq(1.00) end it 'prints stage durations' do Timecop.freeze(Time.local(2008, 9, 1, 10, 5, 0)) Nanoc::Int::NotificationCenter.post(:stage_started, 'donkey', rep) Timecop.freeze(Time.local(2008, 9, 1, 10, 5, 1)) Nanoc::Int::NotificationCenter.post(:stage_ended, 'donkey', rep) expect { listener.stop } .to output(/^\s*donkey │ 1\.00s$/).to_stdout end it 'prints out outdatedness rule durations' do Timecop.freeze(Time.local(2008, 9, 1, 10, 5, 0)) Nanoc::Int::NotificationCenter.post(:outdatedness_rule_started, Nanoc::Int::OutdatednessRules::CodeSnippetsModified, rep) Timecop.freeze(Time.local(2008, 9, 1, 10, 5, 1)) Nanoc::Int::NotificationCenter.post(:outdatedness_rule_ended, Nanoc::Int::OutdatednessRules::CodeSnippetsModified, rep) expect { listener.stop } .to output(/^\s*CodeSnippetsModified │ 1 1\.00s 1\.00s 1\.00s 1\.00s 1\.00s 1\.00s$/).to_stdout end it 'records single outdatedness rule duration' do Timecop.freeze(Time.local(2008, 9, 1, 10, 5, 0)) Nanoc::Int::NotificationCenter.post(:outdatedness_rule_started, Nanoc::Int::OutdatednessRules::CodeSnippetsModified, rep) Timecop.freeze(Time.local(2008, 9, 1, 10, 5, 1)) Nanoc::Int::NotificationCenter.post(:outdatedness_rule_ended, Nanoc::Int::OutdatednessRules::CodeSnippetsModified, rep) expect(listener.telemetry.summary(:outdatedness_rules).get('CodeSnippetsModified').min).to eq(1.00) expect(listener.telemetry.summary(:outdatedness_rules).get('CodeSnippetsModified').avg).to eq(1.00) expect(listener.telemetry.summary(:outdatedness_rules).get('CodeSnippetsModified').max).to eq(1.00) expect(listener.telemetry.summary(:outdatedness_rules).get('CodeSnippetsModified').sum).to eq(1.00) expect(listener.telemetry.summary(:outdatedness_rules).get('CodeSnippetsModified').count).to eq(1.00) end it 'records multiple outdatedness rule duration' do Timecop.freeze(Time.local(2008, 9, 1, 10, 5, 0)) Nanoc::Int::NotificationCenter.post(:outdatedness_rule_started, Nanoc::Int::OutdatednessRules::CodeSnippetsModified, rep) Timecop.freeze(Time.local(2008, 9, 1, 10, 5, 1)) Nanoc::Int::NotificationCenter.post(:outdatedness_rule_ended, Nanoc::Int::OutdatednessRules::CodeSnippetsModified, rep) Timecop.freeze(Time.local(2008, 9, 1, 10, 6, 0)) Nanoc::Int::NotificationCenter.post(:outdatedness_rule_started, Nanoc::Int::OutdatednessRules::CodeSnippetsModified, other_rep) Timecop.freeze(Time.local(2008, 9, 1, 10, 6, 3)) Nanoc::Int::NotificationCenter.post(:outdatedness_rule_ended, Nanoc::Int::OutdatednessRules::CodeSnippetsModified, other_rep) expect(listener.telemetry.summary(:outdatedness_rules).get('CodeSnippetsModified').min).to eq(1.00) expect(listener.telemetry.summary(:outdatedness_rules).get('CodeSnippetsModified').avg).to eq(2.00) expect(listener.telemetry.summary(:outdatedness_rules).get('CodeSnippetsModified').max).to eq(3.00) expect(listener.telemetry.summary(:outdatedness_rules).get('CodeSnippetsModified').sum).to eq(4.00) expect(listener.telemetry.summary(:outdatedness_rules).get('CodeSnippetsModified').count).to eq(2.00) end it 'records memoization usage' do Nanoc::Int::NotificationCenter.post(:memoization_hit, 'Foo#bar', rep) Nanoc::Int::NotificationCenter.post(:memoization_miss, 'Foo#bar', rep) Nanoc::Int::NotificationCenter.post(:memoization_miss, 'Foo#bar', rep) Nanoc::Int::NotificationCenter.post(:memoization_miss, 'Foo#bar', rep) Nanoc::Int::NotificationCenter.post(:memoization_miss, 'Foo#bar', rep) expect(listener.telemetry.counter(:memoization).get(['Foo#bar', :hit]).value).to eq(1) expect(listener.telemetry.counter(:memoization).get(['Foo#bar', :miss]).value).to eq(4) end it 'prints memoization table' do Nanoc::Int::NotificationCenter.post(:memoization_hit, 'Foo#bar', rep) Nanoc::Int::NotificationCenter.post(:memoization_miss, 'Foo#bar', rep) Nanoc::Int::NotificationCenter.post(:memoization_miss, 'Foo#bar', rep) Nanoc::Int::NotificationCenter.post(:memoization_miss, 'Foo#bar', rep) Nanoc::Int::NotificationCenter.post(:memoization_miss, 'Foo#bar', rep) expect { listener.stop } .to output(/^\s*Foo#bar │ 1 4 20\.0%$/).to_stdout end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nanoc-4.8.0/spec/nanoc/cli/commands/compile/diff_generator_spec.rb����������������������������������0000644�0000041�0000041�00000002215�13134141051�025207� 0����������������������������������������������������������������������������������������������������ustar �www-data������������������������www-data���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# frozen_string_literal: true describe Nanoc::CLI::Commands::CompileListeners::DiffGenerator do describe '.enable_for?' do subject { described_class.enable_for?(command_runner) } let(:options) { {} } let(:config_hash) { {} } let(:arguments) { double(:arguments) } let(:command) { double(:command) } let(:site) do Nanoc::Int::Site.new( config: config, code_snippets: code_snippets, data_source: Nanoc::Int::InMemDataSource.new(items, layouts), ) end let(:config) { Nanoc::Int::Configuration.new(hash: config_hash).with_defaults } let(:items) { [] } let(:layouts) { [] } let(:code_snippets) { [] } let(:command_runner) do Nanoc::CLI::Commands::Compile.new(options, arguments, command).tap do |cr| cr.site = site end end context 'default' do it { is_expected.not_to be } end context 'enabled in config' do let(:config_hash) { { enable_output_diff: true } } it { is_expected.to be } end context 'enabled on command line' do let(:options) { { diff: true } } it { is_expected.to be } end end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nanoc-4.8.0/spec/nanoc/cli/commands/show_rules_spec.rb����������������������������������������������0000644�0000041�0000041�00000006372�13134141051�023003� 0����������������������������������������������������������������������������������������������������ustar �www-data������������������������www-data���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# frozen_string_literal: true describe Nanoc::CLI::Commands::ShowRules, stdio: true do describe '#run' do subject { runner.run } let(:runner) do described_class.new(options, arguments, command).tap do |runner| runner.site = site end end let(:options) { {} } let(:arguments) { [] } let(:command) { double(:command) } let(:site) do double( :site, items: items, layouts: layouts, compiler: compiler, ) end let(:items) do Nanoc::Int::ItemCollection.new( config, [ Nanoc::Int::Item.new('About Me', {}, '/about.md'), Nanoc::Int::Item.new('About My Dog', {}, '/dog.md'), Nanoc::Int::Item.new('Raw Data', {}, '/other.dat'), ], ) end let(:reps) do Nanoc::Int::ItemRepRepo.new.tap do |reps| reps << Nanoc::Int::ItemRep.new(items['/about.md'], :default) reps << Nanoc::Int::ItemRep.new(items['/about.md'], :text) reps << Nanoc::Int::ItemRep.new(items['/dog.md'], :default) reps << Nanoc::Int::ItemRep.new(items['/dog.md'], :text) reps << Nanoc::Int::ItemRep.new(items['/other.dat'], :default) end end let(:layouts) do Nanoc::Int::LayoutCollection.new( config, [ Nanoc::Int::Layout.new('Default', {}, '/default.erb'), Nanoc::Int::Layout.new('Article', {}, '/article.haml'), Nanoc::Int::Layout.new('Other', {}, '/other.xyzzy'), ], ) end let(:config) { Nanoc::Int::Configuration.new } let(:action_provider) { double(:action_provider, rules_collection: rules_collection) } let(:compiler) { double(:compiler, action_provider: action_provider, reps: reps) } let(:rules_collection) do Nanoc::RuleDSL::RulesCollection.new.tap do |rc| rc.add_item_compilation_rule( Nanoc::RuleDSL::Rule.new(Nanoc::Int::Pattern.from('/dog.*'), :default, proc {}), ) rc.add_item_compilation_rule( Nanoc::RuleDSL::Rule.new(Nanoc::Int::Pattern.from('/*.md'), :default, proc {}), ) rc.add_item_compilation_rule( Nanoc::RuleDSL::Rule.new(Nanoc::Int::Pattern.from('/**/*'), :text, proc {}), ) rc.layout_filter_mapping[Nanoc::Int::Pattern.from('/*.haml')] = [:haml, {}] rc.layout_filter_mapping[Nanoc::Int::Pattern.from('/*.erb')] = [:erb, {}] end end let(:expected_out) do <<-EOS \e[1m\e[33mItem /about.md\e[0m: Rep default: /*.md Rep text: /**/* \e[1m\e[33mItem /dog.md\e[0m: Rep default: /dog.* Rep text: /**/* \e[1m\e[33mItem /other.dat\e[0m: Rep default: (none) \e[1m\e[33mLayout /article.haml\e[0m: /*.haml \e[1m\e[33mLayout /default.erb\e[0m: /*.erb \e[1m\e[33mLayout /other.xyzzy\e[0m: (none) EOS .gsub(/^ {8}/, '') end before do expect(compiler).to receive(:build_reps).once end it 'writes item and layout rules to stdout' do expect { subject }.to output(expected_out).to_stdout end it 'writes status informaion to stderr' do expect { subject }.to output("Loading site… done\n").to_stderr end end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nanoc-4.8.0/spec/nanoc/cli/commands/compile_spec.rb�������������������������������������������������0000644�0000041�0000041�00000002277�13134141051�022241� 0����������������������������������������������������������������������������������������������������ustar �www-data������������������������www-data���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# frozen_string_literal: true describe Nanoc::CLI::Commands::CompileListeners::Abstract do let(:klass) do Class.new(described_class) do attr_reader :started attr_reader :stopped def initialize @started = false @stopped = false end def start @started = true end def stop @stopped = true end end end subject { klass.new } it 'starts' do subject.start expect(subject.started).to be end it 'stops' do subject.start subject.stop expect(subject.stopped).to be end it 'starts safely' do subject.start_safely expect(subject.started).to be end it 'stops safely' do subject.start_safely subject.stop_safely expect(subject.stopped).to be end context 'listener that does not start or stop properly' do let(:klass) do Class.new(described_class) do def start raise 'boom' end def stop raise 'boom' end end end it 'raises on start, but not stop' do expect { subject.start_safely }.to raise_error(RuntimeError) expect { subject.stop_safely }.not_to raise_error end end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nanoc-4.8.0/spec/nanoc/cli/commands/show_plugins_spec.rb��������������������������������������������0000644�0000041�0000041�00000001042�13134141051�023317� 0����������������������������������������������������������������������������������������������������ustar �www-data������������������������www-data���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# frozen_string_literal: true describe Nanoc::CLI::Commands::ShowPlugins, site: true, stdio: true do describe '#run' do it 'can be invoked' do Nanoc::CLI.run(['show-plugins']) end context 'site with plugins' do before do File.write('lib/default.rb', 'Nanoc::Filter.define(:show_plugins_x) {}') end it 'outputs show_plugins_x under the right section' do expect { Nanoc::CLI.run(['show-plugins']) } .to output(/ custom:\n show_plugins_x/).to_stdout end end end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nanoc-4.8.0/spec/nanoc/cli/commands/deploy_spec.rb��������������������������������������������������0000644�0000041�0000041�00000021663�13134141051�022105� 0����������������������������������������������������������������������������������������������������ustar �www-data������������������������www-data���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# frozen_string_literal: true describe Nanoc::CLI::Commands::Deploy, site: true, stdio: true do describe '#run' do let(:config) { {} } before do # Prevent double-loading expect(Nanoc::CLI).to receive(:setup) File.write('nanoc.yaml', YAML.dump(config)) end shared_examples 'no effective deploy' do it 'does not write any files' do expect { run rescue nil }.not_to change { Dir['remote/*'] } expect(Dir['remote/*']).to be_empty end end shared_examples 'effective deploy' do it 'writes files' do expect { run }.to change { Dir['remote/*'] }.from([]).to(['remote/success.txt']) expect(File.read('remote/success.txt')).to eql('hurrah') end end shared_examples 'attempted/effective deploy' do context 'no checks' do include_examples 'effective deploy' end context 'checks fail' do before do File.write( 'Checks', "check :donkey do\n" \ " add_issue('things are broken', subject: 'success.txt')\n" \ "end\n" \ "\n" \ "deploy_check :donkey\n", ) end include_examples 'no effective deploy' context 'checks disabled' do context '--no-check' do let(:command) { super() + ['--no-check'] } include_examples 'effective deploy' end context '--Ck' do let(:command) { super() + ['-C'] } include_examples 'effective deploy' end end end context 'checks pass' do before do File.write( 'Checks', "check :donkey do\n" \ "end\n" \ "\n" \ "deploy_check :donkey\n", ) end include_examples 'effective deploy' end end describe 'listing deployers' do shared_examples 'lists all deployers' do let(:run) { Nanoc::CLI.run(command) } it 'lists all deployers' do expect { run }.to output(/Available deployers:\n fog\n git\n rsync/).to_stdout end include_examples 'no effective deploy' end context '--list-deployers' do let(:command) { %w[deploy --list-deployers] } include_examples 'lists all deployers' end context '-D' do let(:command) { %w[deploy -D] } include_examples 'lists all deployers' end end describe 'listing deployment configurations' do shared_examples 'lists all deployment configurations' do let(:run) { Nanoc::CLI.run(command) } context 'no deployment configurations' do let(:config) { { donkeys: 'lots' } } it 'says nothing is found' do expect { run }.to output(/No deployment configurations./).to_stdout end include_examples 'no effective deploy' end context 'some deployment configurations' do let(:config) do { deploy: { production: { kind: 'rsync', dst: 'remote', }, staging: { kind: 'rsync', dst: 'remote', }, }, } end it 'says some targets are found' do expect { run }.to output(/Available deployment configurations:\n production\n staging/).to_stdout end include_examples 'no effective deploy' end end context '--list' do let(:command) { %w[deploy --list] } include_examples 'lists all deployment configurations' end context '-L' do let(:command) { %w[deploy -L] } include_examples 'lists all deployment configurations' end end describe 'deploying' do let(:run) { Nanoc::CLI.run(command) } let(:command) { %w[deploy] } before do FileUtils.mkdir_p('output') FileUtils.mkdir_p('remote') File.write('output/success.txt', 'hurrah') end shared_examples 'missing kind warning' do it 'warns about missing kind' do expect { run }.to output(/Warning: The specified deploy target does not have a kind attribute. Assuming rsync./).to_stderr end end context 'no deploy configs' do it 'errors' do expect { run }.to raise_error( Nanoc::Int::Errors::GenericTrivial, 'The site has no deployment configurations.', ) end include_examples 'no effective deploy' context 'configuration created in preprocessor' do before do File.write( 'Rules', "preprocess do\n" \ " @config[:deploy] = {\n" \ " default: { dst: 'remote' },\n" \ " }\n" \ "end\n\n" + File.read('Rules'), ) end include_examples 'attempted/effective deploy' end end context 'some deploy configs' do let(:config) do { deploy: { irrelevant: { kind: 'rsync', dst: 'remote', }, }, } end context 'default target' do context 'requested deploy config does not exist' do it 'errors' do expect { run }.to raise_error( Nanoc::Int::Errors::GenericTrivial, 'The site has no deployment configuration named `default`.', ) end include_examples 'no effective deploy' end context 'requested deploy config exists' do let(:config) do { deploy: { default: { kind: 'rsync', dst: 'remote', }, }, } end include_examples 'attempted/effective deploy' context 'dry run' do let(:command) { super() + ['--dry-run'] } include_examples 'no effective deploy' end end context 'requested deploy config exists, but has no kind' do let(:config) do { deploy: { default: { dst: 'remote', }, }, } end include_examples 'attempted/effective deploy' include_examples 'missing kind warning' context 'dry run' do let(:command) { super() + ['--dry-run'] } include_examples 'no effective deploy' end end end shared_examples 'deploy with non-default target' do context 'requested deploy config does not exist' do it 'errors' do expect { run }.to raise_error( Nanoc::Int::Errors::GenericTrivial, 'The site has no deployment configuration named `production`.', ) end include_examples 'no effective deploy' end context 'requested deploy config exists' do let(:config) do { deploy: { production: { kind: 'rsync', dst: 'remote', }, }, } end include_examples 'attempted/effective deploy' context 'dry run' do let(:command) { (super() + ['--dry-run']) } include_examples 'no effective deploy' end end context 'requested deploy config exists, but has no kind' do let(:config) do { deploy: { production: { dst: 'remote', }, }, } end include_examples 'attempted/effective deploy' include_examples 'missing kind warning' context 'dry run' do let(:command) { (super() + ['--dry-run']) } include_examples 'no effective deploy' end end end context 'non-default target, specified as argument' do let(:command) { %w[deploy production] } include_examples 'deploy with non-default target' end context 'non-default target, specified as option (--target)' do let(:command) { %w[deploy --target production] } include_examples 'deploy with non-default target' end context 'multiple targets specified' do let(:command) { %w[deploy --target staging production] } it 'errors' do expect { run }.to raise_error( Nanoc::Int::Errors::GenericTrivial, 'Only one deployment target can be specified on the command line.', ) end end end end end end �����������������������������������������������������������������������������nanoc-4.8.0/spec/nanoc/cli/commands/shell_spec.rb���������������������������������������������������0000644�0000041�0000041�00000004204�13134141051�021710� 0����������������������������������������������������������������������������������������������������ustar �www-data������������������������www-data���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# frozen_string_literal: true describe Nanoc::CLI::Commands::Shell, site: true, stdio: true do describe '#run' do before do # Prevent double-loading expect(Nanoc::CLI).to receive(:setup) File.write('content/hello.md', 'Hello!') File.write('Rules', <<~EOS) preprocess do @items['/hello.*'].raw_content = 'Better hello!' end compile '/**/*' do end EOS end it 'can be invoked' do expect_any_instance_of(Nanoc::Int::Context).to receive(:pry) do |ctx| expect(ctx.items.size).to eq(1) expect(ctx.items.to_a[0].unwrap.content.string).to eq('Hello!') end Nanoc::CLI.run(['shell']) end it 'will preprocess if requested' do expect_any_instance_of(Nanoc::Int::Context).to receive(:pry) do |ctx| expect(ctx.items.size).to eq(1) expect(ctx.items.to_a[0].unwrap.content.string).to eq('Better hello!') end Nanoc::CLI.run(['shell', '--preprocess']) end end describe '#env_for_site' do subject { described_class.env_for_site(site) } before do File.write('content/hello.md', 'Hello!') File.write('layouts/default.erb', '<title>MY SITE!<%= yield %>') end let(:site) do Nanoc::Int::SiteLoader.new.new_from_cwd end it 'returns views' do expect(subject[:items]).to be_a(Nanoc::ItemCollectionWithRepsView) expect(subject[:layouts]).to be_a(Nanoc::LayoutCollectionView) expect(subject[:config]).to be_a(Nanoc::ConfigView) end it 'returns correct items' do expect(subject[:items].size).to eq(1) expect(subject[:items].first.identifier.to_s).to eq('/hello.md') end it 'returns correct layouts' do expect(subject[:layouts].size).to eq(1) expect(subject[:layouts].first.identifier.to_s).to eq('/default.erb') end it 'returns items with reps' do expect(subject[:items].first.reps).not_to be_nil expect(subject[:items].first.reps.first.name).to eq(:default) end it 'returns items with rep paths' do expect(subject[:items].first.reps.first.path).to eq('/hello.md') end end end nanoc-4.8.0/spec/nanoc/cli/commands/view_spec.rb0000644000004100000410000000354613134141051021563 0ustar www-datawww-data# frozen_string_literal: true require 'net/http' describe Nanoc::CLI::Commands::View, site: true, stdio: true do describe '#run' do def run_nanoc_cmd(cmd) pid = fork { Nanoc::CLI.run(cmd) } # Wait for server to start up 20.times do |i| begin Net::HTTP.get('127.0.0.1', '/', 50_385) rescue Errno::ECONNREFUSED, Errno::ECONNRESET sleep(0.1 * 1.2**i) retry end break end yield ensure Process.kill('TERM', pid) end context 'default configuration' do it 'serves /index.html as /' do File.write('output/index.html', 'Hello there! Nanoc loves you! <3') run_nanoc_cmd(['view', '--port', '50385']) do expect(Net::HTTP.get('127.0.0.1', '/', 50_385)).to eql('Hello there! Nanoc loves you! <3') end end it 'does not serve /index.xhtml as /' do File.write('output/index.xhtml', 'Hello there! Nanoc loves you! <3') run_nanoc_cmd(['view', '--port', '50385']) do expect(Net::HTTP.get('127.0.0.1', '/', 50_385)).to eql("File not found: /\n") end end end context 'index_filenames including index.xhtml' do before do File.write('nanoc.yaml', 'index_filenames: [index.xhtml]') end it 'serves /index.xhtml as /' do File.write('output/index.xhtml', 'Hello there! Nanoc loves you! <3') run_nanoc_cmd(['view', '--port', '50385']) do expect(Net::HTTP.get('127.0.0.1', '/', 50_385)).to eql('Hello there! Nanoc loves you! <3') end end end it 'does not serve other files as /' do File.write('output/index.html666', 'Hello there! Nanoc loves you! <3') run_nanoc_cmd(['view', '--port', '50385']) do expect(Net::HTTP.get('127.0.0.1', '/', 50_385)).to eql("File not found: /\n") end end end end nanoc-4.8.0/spec/nanoc/cli/commands/show_data_spec.rb0000644000004100000410000002361113134141051022555 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::CLI::Commands::ShowData, stdio: true do describe '#print_item_dependencies' do subject { runner.send(:print_item_dependencies, items, dependency_store) } let(:runner) do described_class.new(options, arguments, command) end let(:options) { {} } let(:arguments) { [] } let(:command) { double(:command) } let(:items) do Nanoc::Int::ItemCollection.new( config, [ item_about, item_dog, item_other, ], ) end let(:item_about) { Nanoc::Int::Item.new('About Me', {}, '/about.md') } let(:item_dog) { Nanoc::Int::Item.new('About My Dog', {}, '/dog.md') } let(:item_other) { Nanoc::Int::Item.new('Raw Data', {}, '/other.dat') } let(:config) { Nanoc::Int::Configuration.new } let(:dependency_store) do Nanoc::Int::DependencyStore.new(items, layouts, config) end let(:layouts) do Nanoc::Int::LayoutCollection.new(config) end it 'prints a legend' do expect { subject }.to output(%r{Item dependencies =+\n\nLegend:}).to_stdout end context 'no dependencies' do it 'outputs no dependencies for /about.md' do expect { subject }.to output(%r{^item /about.md depends on:\n \(nothing\)$}m).to_stdout end it 'outputs no dependencies for /dog.md' do expect { subject }.to output(%r{^item /dog.md depends on:\n \(nothing\)$}m).to_stdout end it 'outputs no dependencies for /other.dat' do expect { subject }.to output(%r{^item /other.dat depends on:\n \(nothing\)$}m).to_stdout end end context 'dependency (without props) from config to dog' do before do dependency_store.record_dependency(item_dog, config) end it 'outputs no dependencies for /about.md' do expect { subject }.to output(%r{^item /about.md depends on:\n \(nothing\)$}m).to_stdout end it 'outputs dependencies for /dog.md' do expect { subject }.to output(%r{^item /dog.md depends on:\n \[ config \] \(racp\) $}m).to_stdout end it 'outputs no dependencies for /other.dat' do expect { subject }.to output(%r{^item /other.dat depends on:\n \(nothing\)$}m).to_stdout end end context 'dependency (without props) from about to dog' do before do dependency_store.record_dependency(item_dog, item_about) end it 'outputs no dependencies for /about.md' do expect { subject }.to output(%r{^item /about.md depends on:\n \(nothing\)$}m).to_stdout end it 'outputs dependencies for /dog.md' do expect { subject }.to output(%r{^item /dog.md depends on:\n \[ item \] \(racp\) /about.md$}m).to_stdout end it 'outputs no dependencies for /other.dat' do expect { subject }.to output(%r{^item /other.dat depends on:\n \(nothing\)$}m).to_stdout end end context 'dependency (with raw_content prop) from about to dog' do before do dependency_store.record_dependency(item_dog, item_about, raw_content: true) end it 'outputs dependencies for /dog.md' do expect { subject }.to output(%r{^item /dog.md depends on:\n \[ item \] \(r___\) /about.md$}m).to_stdout end end context 'dependency (with attributes prop) from about to dog' do before do dependency_store.record_dependency(item_dog, item_about, attributes: true) end it 'outputs dependencies for /dog.md' do expect { subject }.to output(%r{^item /dog.md depends on:\n \[ item \] \(_a__\) /about.md$}m).to_stdout end end context 'dependency (with attributes prop) from config to dog' do before do dependency_store.record_dependency(item_dog, config, attributes: true) end it 'outputs dependencies for /dog.md' do expect { subject }.to output(%r{^item /dog.md depends on:\n \[ config \] \(_a__\) $}m).to_stdout end end context 'dependency (with compiled_content prop) from about to dog' do before do dependency_store.record_dependency(item_dog, item_about, compiled_content: true) end it 'outputs dependencies for /dog.md' do expect { subject }.to output(%r{^item /dog.md depends on:\n \[ item \] \(__c_\) /about.md$}m).to_stdout end end context 'dependency (with path prop) from about to dog' do before do dependency_store.record_dependency(item_dog, item_about, path: true) end it 'outputs dependencies for /dog.md' do expect { subject }.to output(%r{^item /dog.md depends on:\n \[ item \] \(___p\) /about.md$}m).to_stdout end end context 'dependency (with multiple props) from about to dog' do before do dependency_store.record_dependency(item_dog, item_about, attributes: true, raw_content: true) end it 'outputs dependencies for /dog.md' do expect { subject }.to output(%r{^item /dog.md depends on:\n \[ item \] \(ra__\) /about.md$}m).to_stdout end end end describe '#print_item_rep_outdatedness' do subject { runner.send(:print_item_rep_outdatedness, items, compiler) } let(:runner) do described_class.new(options, arguments, command) end let(:options) { {} } let(:arguments) { [] } let(:command) { double(:command) } let(:config) { Nanoc::Int::Configuration.new } let(:items) do Nanoc::Int::ItemCollection.new( config, [ item_about, item_dog, ], ) end let(:item_about) { Nanoc::Int::Item.new('About Me', {}, '/about.md') } let(:item_dog) { Nanoc::Int::Item.new('About My Dog', {}, '/dog.md') } let(:item_rep_about) { Nanoc::Int::ItemRep.new(item_about, :default) } let(:item_rep_dog) { Nanoc::Int::ItemRep.new(item_dog, :default) } let(:site) { double(:site) } let(:compiler) { double(:compiler) } let(:outdatedness_checker) { double(:outdatedness_checker) } let(:reps) do { item_about => [item_rep_about], item_dog => [item_rep_dog], } end before do allow(runner).to receive(:site).and_return(site) allow(site).to receive(:compiler).and_return(compiler) allow(compiler).to receive(:create_outdatedness_checker).and_return(outdatedness_checker) allow(compiler).to receive(:reps).and_return(reps) allow(compiler).to receive(:calculate_checksums) end context 'not outdated' do before do allow(outdatedness_checker).to receive(:outdatedness_reasons_for).with(item_rep_about).and_return([]) allow(outdatedness_checker).to receive(:outdatedness_reasons_for).with(item_rep_dog).and_return([]) end example do expect { subject }.to output(%r{^item /about.md, rep default:\n is not outdated$}).to_stdout end example do expect { subject }.to output(%r{^item /dog.md, rep default:\n is not outdated$}).to_stdout end end context 'outdated' do before do reasons_about = [ Nanoc::Int::OutdatednessReasons::ContentModified, Nanoc::Int::OutdatednessReasons::AttributesModified.new([:title]), ] reasons_dog = [Nanoc::Int::OutdatednessReasons::DependenciesOutdated] allow(outdatedness_checker).to receive(:outdatedness_reasons_for) .with(item_rep_about).and_return(reasons_about) allow(outdatedness_checker).to receive(:outdatedness_reasons_for) .with(item_rep_dog).and_return(reasons_dog) end example do expect { subject }.to output(%r{^item /about.md, rep default:\n is outdated:\n - The content of this item has been modified since the last time the site was compiled.\n - The attributes of this item have been modified since the last time the site was compiled.$}).to_stdout end example do expect { subject }.to output(%r{^item /dog.md, rep default:\n is outdated:\n - This item uses content or attributes that have changed since the last time the site was compiled.$}).to_stdout end end end describe '#print_layouts' do subject { runner.send(:print_layouts, layouts, compiler) } let(:runner) do described_class.new(options, arguments, command) end let(:options) { {} } let(:arguments) { [] } let(:command) { double(:command) } let(:config) { Nanoc::Int::Configuration.new } let(:layouts) do Nanoc::Int::LayoutCollection.new(config, [layout]) end let(:layout) { Nanoc::Int::Layout.new('stuff', {}, '/default.erb') } let(:site) { double(:site) } let(:compiler) { double(:compiler) } let(:outdatedness_checker) { double(:outdatedness_checker) } before do allow(runner).to receive(:site).and_return(site) allow(site).to receive(:compiler).and_return(compiler) allow(compiler).to receive(:create_outdatedness_checker).and_return(outdatedness_checker) allow(compiler).to receive(:calculate_checksums) end context 'not outdated' do before do allow(outdatedness_checker).to receive(:outdatedness_reasons_for).with(layout).and_return([]) end example do expect { subject }.to output(%r{^layout /default.erb:\n is not outdated$}).to_stdout end end context 'outdated' do before do reasons = [ Nanoc::Int::OutdatednessReasons::ContentModified, Nanoc::Int::OutdatednessReasons::AttributesModified.new([:title]), ] allow(outdatedness_checker).to receive(:outdatedness_reasons_for) .with(layout).and_return(reasons) end example do expect { subject }.to output(%r{^layout /default.erb:\n is outdated:\n - The content of this item has been modified since the last time the site was compiled.\n - The attributes of this item have been modified since the last time the site was compiled.$}).to_stdout end end end end nanoc-4.8.0/spec/nanoc/cli/stream_cleaners/0000755000004100000410000000000013134141051020610 5ustar www-datawww-datananoc-4.8.0/spec/nanoc/cli/stream_cleaners/utf8_spec.rb0000644000004100000410000000042013134141051023031 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::CLI::StreamCleaners::UTF8 do subject { described_class.new } it 'handles all cases' do expect(subject.clean('┼─ “© Denis” ‘and others…’ ─┼')).to eq('+- "(c) Denis" \'and others...\' -+') end end nanoc-4.8.0/spec/nanoc/spec_spec.rb0000644000004100000410000000270213134141051017164 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::Spec::HelperContext do let(:helper) do Module.new {} end subject(:ctx) { described_class.new(helper) } it 'has no items by default' do # TODO: Add #empty? to item collection view expect(subject.items.size).to eq(0) end it 'has no layouts by default' do # TODO: Add #empty? to item collection view expect(subject.layouts.size).to eq(0) end describe '#create_item' do subject { ctx.create_item('foo', {}, '/foo.md') } it 'creates item' do expect { subject } .to change { ctx.items.size } .from(0).to(1) end it 'creates item without reps' do subject expect(ctx.items['/foo.md'].reps.size).to eq(0) end it 'returns self' do expect(subject).to eq(ctx) end end describe '#create_layout' do subject { ctx.create_layout('foo', {}, '/foo.md') } it 'creates layout' do expect { subject } .to change { ctx.layouts.size } .from(0).to(1) end it 'returns self' do expect(subject).to eq(ctx) end end describe '#create_rep' do before do ctx.create_item('foo', {}, '/foo.md') end subject { ctx.create_rep(ctx.items['/foo.md'], '/foo.html') } it 'creates rep' do expect { subject } .to change { ctx.items['/foo.md'].reps.size } .from(0).to(1) end it 'returns self' do expect(subject).to eq(ctx) end end end nanoc-4.8.0/spec/nanoc/helpers/0000755000004100000410000000000013134141051016334 5ustar www-datawww-datananoc-4.8.0/spec/nanoc/helpers/child_parent_spec.rb0000644000004100000410000000522213134141051022330 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::Helpers::ChildParent, helper: true do describe '#children_of' do subject { helper.children_of(item) } before { ctx.create_item('some content', {}, identifier) } let(:item) { ctx.items[identifier] } context 'legacy identifier' do let(:identifier) { Nanoc::Identifier.new('/foo/', type: :legacy) } before do ctx.create_item('abc', {}, Nanoc::Identifier.new('/foo/a/', type: :legacy)) ctx.create_item('def', {}, Nanoc::Identifier.new('/foo/a/b/', type: :legacy)) ctx.create_item('xyz', {}, Nanoc::Identifier.new('/bar/', type: :legacy)) end it 'returns only direct children' do expect(subject).to eql([ctx.items['/foo/a/']]) end end context 'full identifier' do let(:identifier) { Nanoc::Identifier.new('/foo.md', type: :full) } before do ctx.create_item('abc', {}, Nanoc::Identifier.new('/foo/a.md', type: :full)) ctx.create_item('def', {}, Nanoc::Identifier.new('/foo/a/b.md', type: :full)) ctx.create_item('xyz', {}, Nanoc::Identifier.new('/bar.md', type: :full)) ctx.create_item('xyz', {}, Nanoc::Identifier.new('/foo/a/index.md', type: :full)) end it 'returns only direct children' do expect(subject).to eql([ctx.items['/foo/a.md']]) end end end describe '#parent_of' do subject { helper.parent_of(item) } before { ctx.create_item('some content', {}, identifier) } let(:item) { ctx.items[identifier] } context 'legacy identifier' do let(:identifier) { Nanoc::Identifier.new('/foo/bar/', type: :legacy) } before do ctx.create_item('abc', {}, Nanoc::Identifier.new('/foo/', type: :legacy)) ctx.create_item('def', {}, Nanoc::Identifier.new('/foo/qux/', type: :legacy)) ctx.create_item('xyz', {}, Nanoc::Identifier.new('/foo/bar/asdf/', type: :legacy)) ctx.create_item('opq', {}, Nanoc::Identifier.new('/', type: :legacy)) end it 'returns parent' do expect(subject).to eql(ctx.items['/foo/']) end end context 'full identifier' do let(:identifier) { Nanoc::Identifier.new('/foo/bar.md', type: :full) } before do ctx.create_item('abc', {}, Nanoc::Identifier.new('/foo.md', type: :full)) ctx.create_item('def', {}, Nanoc::Identifier.new('/foo/qux.md', type: :full)) ctx.create_item('xyz', {}, Nanoc::Identifier.new('/foo/bar/asdf.md', type: :full)) ctx.create_item('opq', {}, Nanoc::Identifier.new('/index.md', type: :full)) end it 'returns parent' do expect(subject).to eql(ctx.items['/foo.md']) end end end end nanoc-4.8.0/spec/nanoc/helpers/text_spec.rb0000644000004100000410000000253613134141051020665 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::Helpers::Text, helper: true do describe '#excerptize' do subject { helper.excerptize(string, params) } let(:string) { 'Foo bar baz quux meow woof' } let(:params) { {} } context 'no params' do it 'takes 25 characters' do expect(subject).to eql('Foo bar baz quux meow ...') end end context 'perfect fit' do let(:params) { { length: 26 } } it 'does not truncate' do expect(subject).to eql('Foo bar baz quux meow woof') end end context 'long length' do let(:params) { { length: 27 } } it 'does not truncate' do expect(subject).to eql('Foo bar baz quux meow woof') end end context 'short length' do let(:params) { { length: 3 } } it 'truncates' do expect(subject).to eql('...') end end context 'length shorter than omission' do let(:params) { { length: 2 } } it 'truncates, disregarding length' do expect(subject).to eql('...') end end context 'custom omission' do let(:params) { { omission: '[continued]' } } it 'uses custom omission string' do expect(subject).to eql('Foo bar baz qu[continued]') end end end describe '#strip_html' do # TODO: test this… or get rid of it (it’s bad!) end end nanoc-4.8.0/spec/nanoc/helpers/rendering_spec.rb0000644000004100000410000001046613134141051021657 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::Helpers::Rendering, helper: true do describe '#render' do subject { helper.instance_eval { render('/partial.erb') } } let(:action_sequence_for_layout) do [Nanoc::Int::ProcessingActions::Filter.new(:erb, {})] end let(:layout_view) { ctx.layouts[layout_identifier] } let(:layout) { layout_view.unwrap } before do ctx.create_layout(layout_content, {}, layout_identifier) ctx.update_action_sequence(layout, action_sequence_for_layout) end context 'legacy identifier' do let(:layout_identifier) { Nanoc::Identifier.new('/partial/', type: :legacy) } context 'cleaned identifier' do subject { helper.instance_eval { render('/partial/') } } context 'layout without instructions' do let(:layout_content) { 'blah' } it { is_expected.to eql('blah') } it 'tracks proper dependencies' do expect(ctx.dependency_tracker).to receive(:enter) .with(an_instance_of(Nanoc::Int::LayoutCollection), raw_content: ['/partial/'], attributes: false, compiled_content: false, path: false) .ordered expect(ctx.dependency_tracker).to receive(:enter) .with(layout, raw_content: true, attributes: false, compiled_content: false, path: false) .ordered subject end end context 'layout with instructions' do let(:layout_content) { 'blah <%= @layout.identifier %>' } it { is_expected.to eql('blah /partial/') } end end context 'non-cleaned identifier' do subject { helper.instance_eval { render('/partial') } } context 'layout without instructions' do let(:layout_content) { 'blah' } it { is_expected.to eql('blah') } end context 'layout with instructions' do let(:layout_content) { 'blah <%= @layout.identifier %>' } it { is_expected.to eql('blah /partial/') } end end end context 'full-style identifier' do let(:layout_identifier) { Nanoc::Identifier.new('/partial.erb') } context 'layout without instructions' do let(:layout_content) { 'blah' } it { is_expected.to eql('blah') } end context 'layout with instructions' do let(:layout_content) { 'blah <%= @layout.identifier %>' } it { is_expected.to eql('blah /partial.erb') } end context 'printing wrapped layout class' do let(:layout_content) { 'blah <%= @layout.class %>' } it { is_expected.to eql('blah Nanoc::LayoutView') } end context 'printing unwrapped layout class' do let(:layout_content) { 'blah <%= @layout.unwrap.class %>' } it { is_expected.to eql('blah Nanoc::Int::Layout') } end context 'unknown layout' do subject { helper.instance_eval { render('/unknown.erb') } } let(:layout_content) { 'blah' } it 'raises' do expect { subject }.to raise_error(Nanoc::Int::Errors::UnknownLayout) end end context 'layout with unknown filter' do let(:action_sequence_for_layout) do [Nanoc::Int::ProcessingActions::Filter.new(:donkey, {})] end let(:layout_content) { 'blah' } it 'raises' do expect { subject }.to raise_error(Nanoc::Int::Errors::UnknownFilter) end end context 'layout without filter' do let(:action_sequence_for_layout) do [Nanoc::Int::ProcessingActions::Filter.new(nil, {})] end let(:layout_content) { 'blah' } it 'raises' do expect { subject }.to raise_error(Nanoc::Int::Errors::CannotDetermineFilter) end end context 'with block' do subject do helper.instance_eval do render('/partial.erb') { _erbout << 'extra content' } end end before do ctx.erbout << '[erbout-before]' end let(:layout_content) { '[partial-before]<%= yield %>[partial-after]' } it 'returns an empty string' do expect(subject).to eql('') end it 'modifies erbout' do subject expect(ctx.erbout).to eql('[erbout-before][partial-before]extra content[partial-after]') end end end end end nanoc-4.8.0/spec/nanoc/helpers/link_to_spec.rb0000644000004100000410000002100113134141051021324 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::Helpers::LinkTo, helper: true do describe '#link_to' do subject { helper.link_to(text, target, attributes) } let(:text) { 'Text' } let(:target) { raise 'override me' } let(:attributes) { {} } context 'with string path' do let(:target) { '/foo/' } it { is_expected.to eql('Text') } context 'with attributes' do let(:attributes) { { title: 'Donkey' } } it { is_expected.to eql('Text') } end context 'special HTML characters in text' do let(:text) { 'Foo & Bar' } it { is_expected.to eql('Foo & Bar') } # Not escaped! end context 'special HTML characters in URL' do let(:target) { '/r&d/' } it { is_expected.to eql('Text') } end context 'special HTML characters in attribute' do let(:attributes) { { title: 'Research & Development' } } it { is_expected.to eql('Text') } end end context 'with rep' do before do ctx.create_item('content', {}, '/target/') ctx.create_rep(ctx.items['/target/'], '/target.html') end let(:target) { ctx.items['/target/'].reps[:default] } it { is_expected.to eql('Text') } end context 'with item' do before do ctx.create_item('content', {}, '/target/') end let(:target) { ctx.items['/target/'] } before do ctx.create_rep(target, '/target.html') end it { is_expected.to eql('Text') } end context 'with nil' do let(:target) { nil } it 'raises' do expect { subject }.to raise_error(ArgumentError) end end context 'with something else' do let(:target) { :donkey } it 'raises' do expect { subject }.to raise_error(ArgumentError) end end context 'with nil path' do before do ctx.create_item('content', {}, '/target/') ctx.create_rep(ctx.items['/target/'], nil) end let(:target) { ctx.items['/target/'].reps[:default] } it 'raises' do expect { subject }.to raise_error(RuntimeError) end end end describe '#link_to_unless_current' do subject { helper.link_to_unless_current(text, target, attributes) } let(:text) { 'Text' } let(:target) { raise 'override me' } let(:attributes) { {} } context 'with string path' do let(:target) { '/target.html' } context 'current' do before do ctx.create_item('content', {}, '/target.md') ctx.create_rep(ctx.items['/target.md'], '/target.html') ctx.item = ctx.items['/target.md'] ctx.item_rep = ctx.item.reps[:default] end it { is_expected.to eql('Text') } end context 'no item rep present' do it { is_expected.to eql('Text') } end context 'item rep present, but not current' do before do ctx.create_item('content', {}, '/other.md') ctx.create_rep(ctx.items['/other.md'], '/other.html') ctx.item = ctx.items['/other.md'] ctx.item_rep = ctx.item.reps[:default] end it { is_expected.to eql('Text') } end end context 'with rep' do before do ctx.create_item('content', {}, '/target.md') ctx.create_rep(ctx.items['/target.md'], '/target.html') ctx.create_item('content', {}, '/other.md') ctx.create_rep(ctx.items['/other.md'], '/other.html') ctx.item = ctx.items['/target.md'] ctx.item_rep = ctx.item.reps[:default] end let(:some_item) { ctx.items['/other.md'] } let(:some_item_rep) { some_item.reps[:default] } context 'current' do let(:target) { ctx.item_rep } it { is_expected.to eql('Text') } end context 'no item rep present' do let(:target) { some_item_rep } before do ctx.item = nil ctx.item_rep = nil end it { is_expected.to eql('Text') } end context 'item rep present, but not current' do let(:target) { some_item_rep } it { is_expected.to eql('Text') } end end context 'with item' do before do ctx.create_item('content', {}, '/target.md') ctx.create_rep(ctx.items['/target.md'], '/target.html') ctx.create_item('content', {}, '/other.md') ctx.create_rep(ctx.items['/other.md'], '/other.html') ctx.item = ctx.items['/target.md'] ctx.item_rep = ctx.item.reps[:default] end let(:some_item) { ctx.items['/other.md'] } let(:some_item_rep) { some_item.reps[:default] } context 'current' do let(:target) { ctx.item } it { is_expected.to eql('Text') } end context 'no item rep present' do let(:target) { some_item } before do ctx.item = nil ctx.item_rep = nil end it { is_expected.to eql('Text') } end context 'item rep present, but not current' do let(:target) { some_item } it { is_expected.to eql('Text') } end end end describe '#relative_path_to' do subject { helper.relative_path_to(target) } before do ctx.create_item('content', {}, '/foo/self.md') ctx.create_rep(ctx.items['/foo/self.md'], self_path) ctx.item = ctx.items['/foo/self.md'] ctx.item_rep = ctx.item.reps[:default] end context 'current item rep has non-nil path' do let(:self_path) { '/foo/self.html' } context 'to string path' do context 'to relative path' do let(:target) { 'bar/target.html' } it 'errors' do # TODO: Might make sense to allow this case (and return the path itself) expect { subject }.to raise_error(ArgumentError) end end context 'to path without trailing slash' do let(:target) { '/bar/target.html' } it { is_expected.to eql('../bar/target.html') } end context 'to path with trailing slash' do let(:target) { '/bar/target/' } it { is_expected.to eql('../bar/target/') } end context 'to Windows/UNC path (forward slashes)' do let(:target) { '//foo' } it { is_expected.to eql('//foo') } end context 'to Windows/UNC path (backslashes)' do let(:target) { '\\\\foo' } it { is_expected.to eql('\\\\foo') } end end context 'to rep' do before do ctx.create_rep(ctx.item, '/bar/target.html', :special) end let(:target) { ctx.item.reps[:special] } it { is_expected.to eql('../bar/target.html') } context 'to self' do let(:target) { ctx.item_rep } context 'self is a filename' do it { is_expected.to eql('self.html') } end context 'self is a directory' do let(:self_path) { '/foo/self/' } it { is_expected.to eql('./') } end end end context 'to item' do let(:target) { ctx.items['/bar/target.md'] } before do ctx.create_item('content', {}, '/bar/target.md') ctx.create_rep(ctx.items['/bar/target.md'], '/bar/target.html') end it { is_expected.to eql('../bar/target.html') } context 'to self' do let(:target) { ctx.item } context 'self is a filename' do it { is_expected.to eql('self.html') } end context 'self is a directory' do let(:self_path) { '/foo/self/' } it { is_expected.to eql('./') } end end end context 'to nil path' do let(:target) { ctx.item.reps[:special] } before do ctx.create_rep(ctx.item, nil, :special) end it 'raises' do expect { subject }.to raise_error(RuntimeError) end end end context 'current item rep has nil path' do let(:self_path) { nil } let(:target) { '/bar/target.html' } it 'errors' do expect { subject }.to raise_error(RuntimeError) end end end end nanoc-4.8.0/spec/nanoc/helpers/blogging_spec.rb0000644000004100000410000001351513134141051021470 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::Helpers::Blogging, helper: true do before do allow(ctx.dependency_tracker).to receive(:enter) allow(ctx.dependency_tracker).to receive(:exit) end describe '#articles' do subject { helper.articles } before do ctx.create_item('blah', { kind: 'item' }, '/0/') ctx.create_item('blah blah', { kind: 'article' }, '/1/') ctx.create_item('blah blah blah', { kind: 'article' }, '/2/') end it 'returns the two articles' do expect(subject.map(&:identifier)).to match_array(['/1/', '/2/']) end end describe '#sorted_articles' do subject { helper.sorted_articles } before do attrs = { kind: 'item' } ctx.create_item('blah', attrs, '/0/') attrs = { kind: 'article', created_at: (Date.today - 1).to_s } ctx.create_item('blah blah', attrs, '/1/') attrs = { kind: 'article', created_at: (Time.now - 500).to_s } ctx.create_item('blah blah blah', attrs, '/2/') end it 'returns the two articles in descending order' do expect(subject.map(&:identifier)).to eq(['/2/', '/1/']) end end describe '#url_for' do subject { helper.url_for(ctx.items['/stuff/']) } let(:item_attributes) { {} } before do ctx.create_item('Stuff', item_attributes, '/stuff/') ctx.create_rep(ctx.items['/stuff/'], '/rep/path/stuff.html') ctx.config[:base_url] = base_url end context 'without base_url' do let(:base_url) { nil } it 'raises' do expect { subject }.to raise_error(Nanoc::Error) end end context 'with base_url' do let(:base_url) { 'http://url.base' } context 'with custom_url_in_feed' do let(:item_attributes) do { custom_url_in_feed: 'http://example.com/stuff.html' } end it 'returns custom URL' do expect(subject).to eql('http://example.com/stuff.html') end end context 'without custom_url_in_feed' do context 'with custom_path_in_feed' do let(:item_attributes) do { custom_path_in_feed: '/stuff.html' } end it 'returns base URL + custom path' do expect(subject).to eql('http://url.base/stuff.html') end end context 'without custom_path_in_feed' do it 'returns base URL + path' do expect(subject).to eql('http://url.base/rep/path/stuff.html') end end end end end describe '#feed_url' do subject { helper.feed_url } let(:item_attributes) { {} } before do ctx.create_item('Feed', item_attributes, '/feed/') ctx.create_rep(ctx.items['/feed/'], '/feed.xml') ctx.item = ctx.items['/feed/'] ctx.config[:base_url] = base_url end context 'without base_url' do let(:base_url) { nil } it 'raises' do expect { subject }.to raise_error(Nanoc::Error) end end context 'with base_url' do let(:base_url) { 'http://url.base' } context 'with feed_url' do let(:item_attributes) do { feed_url: 'http://custom.feed.url/feed.rss' } end it 'returns custom URL' do expect(subject).to eql('http://custom.feed.url/feed.rss') end end context 'without feed_url' do it 'returns base URL + path' do expect(subject).to eql('http://url.base/feed.xml') end end end end describe '#attribute_to_time' do subject { helper.attribute_to_time(arg) } let(:noon_s) { 1_446_903_076 } let(:beginning_of_day_s) { 1_446_854_400 } let(:around_noon_local) { Time.at(noon_s - Time.at(noon_s).utc_offset) } let(:around_noon_utc) { Time.at(noon_s) } let(:beginning_of_day_utc) { Time.at(beginning_of_day_s) } context 'with Time instance' do let(:arg) { around_noon_utc } it { is_expected.to eql(around_noon_utc) } end context 'with Date instance' do let(:arg) { Date.new(2015, 11, 7) } it { is_expected.to eql(beginning_of_day_utc) } end context 'with DateTime instance' do let(:arg) { DateTime.new(2015, 11, 7, 13, 31, 16) } it { is_expected.to eql(around_noon_utc) } end context 'with string' do let(:arg) { '2015-11-7 13:31:16' } it { is_expected.to eql(around_noon_local) } end end describe '#atom_tag_for' do subject { helper.atom_tag_for(ctx.items['/stuff/']) } let(:item_attributes) { { created_at: '2015-05-19 12:34:56' } } let(:item_rep_path) { '/stuff.xml' } let(:base_url) { 'http://url.base' } before do ctx.create_item('Stuff', item_attributes, '/stuff/') ctx.create_rep(ctx.items['/stuff/'], item_rep_path) ctx.config[:base_url] = base_url end context 'item with path' do let(:item_rep_path) { '/stuff.xml' } it { is_expected.to eql('tag:url.base,2015-05-19:/stuff.xml') } end context 'item without path' do let(:item_rep_path) { nil } it { is_expected.to eql('tag:url.base,2015-05-19:/stuff/') } end context 'bare URL without subdir' do let(:base_url) { 'http://url.base' } it { is_expected.to eql('tag:url.base,2015-05-19:/stuff.xml') } end context 'bare URL with subdir' do let(:base_url) { 'http://url.base/sub' } it { is_expected.to eql('tag:url.base,2015-05-19:/sub/stuff.xml') } end context 'created_at is date' do let(:item_attributes) do { created_at: Date.parse('2015-05-19 12:34:56') } end it { is_expected.to eql('tag:url.base,2015-05-19:/stuff.xml') } end context 'created_at is time' do let(:item_attributes) do { created_at: Time.parse('2015-05-19 12:34:56') } end it { is_expected.to eql('tag:url.base,2015-05-19:/stuff.xml') } end # TODO: handle missing base_dir # TODO: handle missing created_at end end nanoc-4.8.0/spec/nanoc/helpers/html_escape_spec.rb0000644000004100000410000000166013134141051022162 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::Helpers::HTMLEscape, helper: true do describe '#html_escape' do subject { helper.html_escape(string) } context 'given strings to escape' do let(:string) { '< > & "' } it { is_expected.to eql('< > & "') } end context 'given a block' do let!(:_erbout) { String.new('moo') } it 'adds escaped content to _erbout' do helper.html_escape { _erbout << '

Stuff!

' } expect(_erbout).to eql('moo<h1>Stuff!</h1>') end end context 'given no argument nor block' do subject { helper.html_escape } it 'raises' do expect { subject }.to raise_error(RuntimeError) end end context 'given argument that is not a string' do let(:string) { 1 } it 'raises an ArgumentError' do expect { subject }.to raise_error(ArgumentError) end end end end nanoc-4.8.0/spec/nanoc/helpers/filtering_spec.rb0000644000004100000410000000360313134141051021660 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::Helpers::Filtering, helper: true do describe '#filter' do before do ctx.create_item('some content', { title: 'Hello!' }, '/about.md') ctx.create_rep(ctx.items['/about.md'], '/about.html') ctx.item = ctx.items['/about.md'] ctx.item_rep = ctx.item.reps[:default] end let(:content) do "A<% filter :erb do %><%%= 'X' %><% end %>B" end subject { ::ERB.new(content).result(helper.get_binding) } context 'basic case' do it { is_expected.to eql('AXB') } it 'notifies' do ns = Set.new Nanoc::Int::NotificationCenter.on(:filtering_started) { ns << :filtering_started } Nanoc::Int::NotificationCenter.on(:filtering_ended) { ns << :filtering_ended } subject expect(ns).to include(:filtering_started) expect(ns).to include(:filtering_ended) end end context 'with assigns' do let(:content) do 'A<% filter :erb do %><%%= @item[:title] %><% end %>B' end it { is_expected.to eql('AHello!B') } end context 'unknonwn filter name' do let(:content) do 'A<% filter :donkey do %>X<% end %>B' end it 'errors' do expect { subject }.to raise_error(Nanoc::Int::Errors::UnknownFilter) end end context 'with locals' do let(:content) do "A<% filter :erb, locals: { sheep: 'baah' } do %><%%= @sheep %><% end %>B" end it { is_expected.to eql('AbaahB') } end context 'with Haml' do let(:content) do "%p Foo.\n" \ "- filter(:erb) do\n" \ " <%= 'abc' + 'xyz' %>\n" \ "%p Bar.\n" end before do require 'haml' end subject { ::Haml::Engine.new(content).render(helper.get_binding) } it { is_expected.to match(%r{^

Foo.

\s*abcxyz\s*

Bar.

$}) } end end end nanoc-4.8.0/spec/nanoc/helpers/tagging_spec.rb0000644000004100000410000000601713134141051021317 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::Helpers::Tagging, helper: true do describe '#tags_for' do subject { helper.tags_for(item, params) } let(:item) { ctx.items['/me.*'] } let(:params) { {} } let(:item_attributes) { {} } before do ctx.create_item('content', item_attributes, '/me.md') end context 'no tags' do let(:item_attributes) { {} } it { is_expected.to eql('(none)') } end context 'nil tag list' do let(:item_attributes) { { tags: nil } } it { is_expected.to eql('(none)') } end context 'empty tag list' do let(:item_attributes) { { tags: [] } } it { is_expected.to eql('(none)') } end context 'no tags, and custom none text' do let(:item_attributes) { {} } let(:params) { { none_text: 'no tags for you, fool' } } it { is_expected.to eql('no tags for you, fool') } end context 'one tag' do let(:item_attributes) { { tags: %w[donkey] } } context 'implicit base_url' do it { is_expected.to eql('donkey') } end context 'explicit nil base_url' do let(:params) { { base_url: nil } } it { is_expected.to eql('donkey') } end context 'explicit other base_url' do let(:params) { { base_url: 'http://nanoc.ws/tag/' } } it { is_expected.to eql('') } end end context 'two tags' do let(:item_attributes) { { tags: %w[donkey giraffe] } } it { is_expected.to eql('donkey, giraffe') } end context 'three tags' do let(:item_attributes) { { tags: %w[donkey giraffe zebra] } } it { is_expected.to eql('donkey, giraffe, zebra') } context 'custom separator' do let(:item_attributes) { { tags: %w[donkey giraffe zebra] } } let(:params) { { separator: ' / ' } } it { is_expected.to eql('donkey / giraffe / zebra') } end end end describe '#items_with_tag' do subject { helper.items_with_tag(tag) } before do ctx.create_item('item 1', { tags: [:foo] }, '/item1.md') ctx.create_item('item 2', { tags: [:bar] }, '/item2.md') ctx.create_item('item 3', { tags: %i[foo bar] }, '/item3.md') ctx.create_item('item 4', { tags: nil }, '/item4.md') ctx.create_item('item 5', {}, '/item5.md') end context 'tag that exists' do let(:tag) { :foo } it { is_expected.to contain_exactly(ctx.items['/item1.md'], ctx.items['/item3.md']) } end context 'tag that does not exists' do let(:tag) { :other } it { is_expected.to be_empty } end end describe '#link_for_tag' do subject { helper.link_for_tag(tag, base_url) } let(:tag) { 'foo' } let(:base_url) { 'http://nanoc.ws/tag/' } it { is_expected.to eql('') } context 'tag with special HTML characters' do let(:tag) { 'R&D' } it { is_expected.to eql('') } end end end nanoc-4.8.0/spec/nanoc/helpers/breadcrumbs_spec.rb0000644000004100000410000001031413134141051022163 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::Helpers::Breadcrumbs, helper: true do before do allow(ctx.dependency_tracker).to receive(:enter) allow(ctx.dependency_tracker).to receive(:exit) end describe '#breadcrumbs_trail' do subject { helper.breadcrumbs_trail } context 'legacy identifiers' do context 'root' do before do ctx.create_item('root', {}, Nanoc::Identifier.new('/', type: :legacy)) ctx.item = ctx.items['/'] end it 'returns an array with the item' do expect(subject).to eql([ctx.items['/']]) end end context 'root and direct child' do before do ctx.create_item('child', {}, Nanoc::Identifier.new('/foo/', type: :legacy)) ctx.create_item('root', {}, Nanoc::Identifier.new('/', type: :legacy)) ctx.item = ctx.items['/foo/'] end it 'returns an array with the items' do expect(subject).to eql([ctx.items['/'], ctx.items['/foo/']]) end end context 'root, child and grandchild' do before do ctx.create_item('grandchild', {}, Nanoc::Identifier.new('/foo/bar/', type: :legacy)) ctx.create_item('child', {}, Nanoc::Identifier.new('/foo/', type: :legacy)) ctx.create_item('root', {}, Nanoc::Identifier.new('/', type: :legacy)) ctx.item = ctx.items['/foo/bar/'] end it 'returns an array with the items' do expect(subject).to eql([ctx.items['/'], ctx.items['/foo/'], ctx.items['/foo/bar/']]) end end context 'root, missing child and grandchild' do before do ctx.create_item('grandchild', {}, Nanoc::Identifier.new('/foo/bar/', type: :legacy)) ctx.create_item('root', {}, Nanoc::Identifier.new('/', type: :legacy)) ctx.item = ctx.items['/foo/bar/'] end it 'returns an array with the items' do expect(subject).to eql([ctx.items['/'], nil, ctx.items['/foo/bar/']]) end end end context 'non-legacy identifiers' do context 'root' do before do ctx.create_item('root', {}, Nanoc::Identifier.new('/index.md')) ctx.item = ctx.items['/index.md'] end it 'returns an array with the item' do expect(subject).to eql([ctx.items['/index.md']]) end end context 'root and direct child' do before do ctx.create_item('child', {}, Nanoc::Identifier.new('/foo.md')) ctx.create_item('root', {}, Nanoc::Identifier.new('/index.md')) ctx.item = ctx.items['/foo.md'] end it 'returns an array with the items' do expect(subject).to eql([ctx.items['/index.md'], ctx.items['/foo.md']]) end end context 'root, child and grandchild' do before do ctx.create_item('grandchild', {}, Nanoc::Identifier.new('/foo/bar.md')) ctx.create_item('child', {}, Nanoc::Identifier.new('/foo.md')) ctx.create_item('root', {}, Nanoc::Identifier.new('/index.md')) ctx.item = ctx.items['/foo/bar.md'] end it 'returns an array with the items' do expect(subject).to eql([ctx.items['/index.md'], ctx.items['/foo.md'], ctx.items['/foo/bar.md']]) end end context 'root, missing child and grandchild' do before do ctx.create_item('grandchild', {}, Nanoc::Identifier.new('/foo/bar.md')) ctx.create_item('root', {}, Nanoc::Identifier.new('/index.md')) ctx.item = ctx.items['/foo/bar.md'] end it 'returns an array with the items' do expect(subject).to eql([ctx.items['/index.md'], nil, ctx.items['/foo/bar.md']]) end end context 'index.md child' do # No special handling of non-root index.* files. before do ctx.create_item('grandchild', {}, Nanoc::Identifier.new('/foo/index.md')) ctx.create_item('root', {}, Nanoc::Identifier.new('/index.md')) ctx.item = ctx.items['/foo/index.md'] end it 'returns an array with the items' do expect(subject).to eql([ctx.items['/index.md'], nil, ctx.items['/foo/index.md']]) end end end end end nanoc-4.8.0/spec/nanoc/helpers/capturing_spec.rb0000644000004100000410000001742613134141051021701 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::Helpers::Capturing, helper: true do describe '#content_for' do before do ctx.create_item('some content', {}, '/about.md') ctx.create_rep(ctx.items['/about.md'], '/about.html') ctx.item = ctx.items['/about.md'] end describe 'setting content' do let(:_erbout) { String.new('existing content') } let(:params) { raise 'overwrite me' } let(:contents_enumerator) { %w[foo bar].to_enum } shared_examples 'setting content' do context 'only name given' do subject { subject_proc_without_params.call } it 'stores snapshot content' do subject expect(ctx.snapshot_repo.get(ctx.item.reps[:default].unwrap, :__capture_foo).string).to eql('foo') end end context 'name and params given' do subject { subject_proc_with_params.call } let(:params) { raise 'overwrite me' } context 'no existing behavior specified' do let(:params) { {} } it 'errors after two times' do subject_proc_with_params.call expect { subject_proc_with_params.call }.to raise_error(RuntimeError) end end context 'existing behavior is :overwrite' do let(:params) { { existing: :overwrite } } it 'overwrites' do subject_proc_with_params.call subject_proc_with_params.call expect(ctx.snapshot_repo.get(ctx.item.reps[:default].unwrap, :__capture_foo).string).to eql('bar') end end context 'existing behavior is :append' do let(:params) { { existing: :append } } it 'appends' do subject_proc_with_params.call subject_proc_with_params.call expect(ctx.snapshot_repo.get(ctx.item.reps[:default].unwrap, :__capture_foo).string).to eql('foobar') end end context 'existing behavior is :error' do let(:params) { { existing: :error } } it 'errors after two times' do subject_proc_with_params.call expect { subject_proc_with_params.call }.to raise_error(RuntimeError) end end context 'existing behavior is :something else' do let(:params) { { existing: :donkey } } it 'errors' do expect { subject }.to raise_error(ArgumentError) end end end end context 'symbol name + block' do let(:subject_proc_without_params) do -> { helper.content_for(:foo) { _erbout << contents_enumerator.next } } end let(:subject_proc_with_params) do -> { helper.content_for(:foo, params) { _erbout << contents_enumerator.next } } end include_examples 'setting content' end context 'string name + block' do let(:subject_proc_without_params) do -> { helper.content_for('foo') { _erbout << contents_enumerator.next } } end let(:subject_proc_with_params) do -> { helper.content_for('foo', params) { _erbout << contents_enumerator.next } } end include_examples 'setting content' end context 'symbol name + string' do let(:subject_proc_without_params) do -> { helper.content_for(:foo, contents_enumerator.next) } end let(:subject_proc_with_params) do -> { helper.content_for(:foo, params, contents_enumerator.next) } end include_examples 'setting content' end context 'string name + string' do let(:subject_proc_without_params) do -> { helper.content_for('foo', contents_enumerator.next) } end let(:subject_proc_with_params) do -> { helper.content_for('foo', params, contents_enumerator.next) } end include_examples 'setting content' end end describe 'with item + name' do subject { helper.content_for(item, :foo) } let(:_erbout) { String.new('existing content') } context 'requesting for same item' do let(:item) { ctx.item } context 'nothing captured' do it { is_expected.to be_nil } end context 'something captured' do before do helper.content_for(:foo) { _erbout << 'I have been captured!' } end it { is_expected.to eql('I have been captured!') } end end context 'requesting for other item' do let(:item) { ctx.items['/other.md'] } before do ctx.create_item('other content', {}, '/other.md') ctx.create_rep(ctx.items['/other.md'], '/other.html') end context 'other item is not yet compiled' do it 'raises an unmet dependency error' do expect(ctx.dependency_tracker).to receive(:bounce).with(item.unwrap, compiled_content: true) expect { subject }.to raise_error(FiberError) end it 're-runs when fiber is resumed' do expect(ctx.dependency_tracker).to receive(:bounce).with(item.unwrap, compiled_content: true).twice fiber = Fiber.new { subject } expect(fiber.resume).to be_a(Nanoc::Int::Errors::UnmetDependency) item.reps[:default].unwrap.compiled = true ctx.snapshot_repo.set( item.reps[:default].unwrap, :__capture_foo, Nanoc::Int::TextualContent.new('content after compilation'), ) expect(fiber.resume).to eql('content after compilation') end end context 'other item is compiled' do before do item.reps[:default].unwrap.compiled = true ctx.snapshot_repo.set( item.reps[:default].unwrap, :__capture_foo, Nanoc::Int::TextualContent.new('other captured foo'), ) end it 'returns the captured content' do expect(ctx.dependency_tracker).to receive(:bounce).with(item.unwrap, compiled_content: true) expect(subject).to eql('other captured foo') end end end end end describe '#capture' do context 'with string' do let(:_erbout) { String.new('existing content') } subject { helper.capture { _erbout << 'new content' } } it 'returns the appended content' do expect(subject).to eql('new content') end it 'does not modify _erbout' do expect { subject }.not_to change { _erbout } end end context 'with array' do let(:_erbout) { ['existing content'] } shared_examples 'returns properly joined output' do subject { helper.capture { _erbout << %w[new _ content] } } it 'returns the appended content, joined' do expect(subject).to eql('new_content') end it 'does not modify _erbout' do expect { subject }.not_to change { _erbout.join('') } end end context 'default output field separator' do include_examples 'returns properly joined output' end context 'output field separator set to ,' do around do |ex| orig_output_field_separator = $OUTPUT_FIELD_SEPARATOR $OUTPUT_FIELD_SEPARATOR = ',' ex.run $OUTPUT_FIELD_SEPARATOR = orig_output_field_separator end include_examples 'returns properly joined output' end context 'output field separator set to nothing' do around do |ex| orig_output_field_separator = $OUTPUT_FIELD_SEPARATOR $OUTPUT_FIELD_SEPARATOR = String.new ex.run $OUTPUT_FIELD_SEPARATOR = orig_output_field_separator end include_examples 'returns properly joined output' end end end end nanoc-4.8.0/spec/nanoc/data_sources/0000755000004100000410000000000013134141051017346 5ustar www-datawww-datananoc-4.8.0/spec/nanoc/data_sources/filesystem_spec.rb0000644000004100000410000000476313134141051023103 0ustar www-datawww-data# frozen_string_literal: true describe Nanoc::DataSources::Filesystem do let(:data_source) { Nanoc::DataSources::Filesystem.new(site.config, nil, nil, params) } let(:params) { {} } let(:site) { Nanoc::Int::SiteLoader.new.new_empty } before { Timecop.freeze(now) } after { Timecop.return } let(:now) { Time.local(2008, 1, 2, 14, 5, 0) } describe '#load_objects' do subject { data_source.send(:load_objects, 'foo', klass) } let(:klass) { raise 'override me' } context 'items' do let(:klass) { Nanoc::Int::Item } context 'no files' do it 'loads nothing' do expect(subject).to be_empty end end context 'one regular file' do before do FileUtils.mkdir_p('foo') File.write('foo/bar.html', "---\nnum: 1\n---\ntest 1") FileUtils.touch('foo/bar.html', mtime: now) end let(:expected_attributes) do { content_filename: 'foo/bar.html', extension: 'html', filename: 'foo/bar.html', meta_filename: nil, mtime: now, num: 1, } end it 'loads that file' do expect(subject.size).to eq(1) expect(subject[0].content.string).to eq('test 1') expect(subject[0].attributes).to eq(expected_attributes) expect(subject[0].identifier).to eq(Nanoc::Identifier.new('/bar/')) expect(subject[0].checksum_data).to be_nil expect(subject[0].attributes_checksum_data).to be_a(String) expect(subject[0].attributes_checksum_data.size).to eq(20) expect(subject[0].content_checksum_data).to be_a(String) expect(subject[0].content_checksum_data.size).to eq(20) end context 'split files' do let(:block) do lambda do FileUtils.mkdir_p('foo') File.write('foo/bar.html', 'test 1') FileUtils.touch('foo/bar.html', mtime: now) File.write('foo/bar.yaml', "---\nnum: 1\n") FileUtils.touch('foo/bar.yaml', mtime: now) end end it 'has a different attributes checksum' do expect(block).to change { data_source.send(:load_objects, 'foo', klass)[0].attributes_checksum_data } end it 'has the same content checksum' do expect(block).not_to change { data_source.send(:load_objects, 'foo', klass)[0].content_checksum_data } end end end end end end nanoc-4.8.0/spec/contributors_spec.rb0000644000004100000410000000122113134141051017664 0ustar www-datawww-data# frozen_string_literal: true describe 'list of contributors in README', chdir: false do let(:contributors_in_readme) do File.readlines('README.md').last.chomp("\n").split(', ') end let(:contributors_in_release_notes) do File.read('NEWS.md').scan(/\[[^\]]+\]$/).map { |s| s[1..-2].split(', ') }.flatten end it 'should include everyone mentioned in NEWS.md' do diff = (contributors_in_release_notes - contributors_in_readme).uniq.sort expect(diff).to be_empty, "some contributors are missing from the README: #{diff.join(', ')}" end it 'should be sorted' do expect(contributors_in_readme).to be_humanly_sorted end end nanoc-4.8.0/.travis.yml0000644000004100000410000000072713134141051014761 0ustar www-datawww-datalanguage: ruby rvm: - "2.3" - "2.4" branches: only: - "master" env: global: - LC_ALL=en_US.UTF_8 LANG=en_US.UTF_8 matrix: fast_finish: true include: - rvm: jruby-9.1.9.0 env: DISABLE_NOKOGIRI=1 allow_failures: - rvm: jruby-9.1.9.0 env: DISABLE_NOKOGIRI=1 script: - bundle exec rake test_ci - bundle exec appraisal install && FOCUS=rouge bundle exec appraisal rake spec cache: bundler sudo: false git: depth: 10 nanoc-4.8.0/lib/0000755000004100000410000000000013134141051013410 5ustar www-datawww-datananoc-4.8.0/lib/nanoc.rb0000644000004100000410000000264313134141051015040 0ustar www-datawww-data# frozen_string_literal: true module Nanoc # @return [String] A string containing information about this Nanoc version # and its environment (Ruby engine and version, Rubygems version if any). # # @api private def self.version_information gem_info = defined?(Gem) ? "with RubyGems #{Gem::VERSION}" : 'without RubyGems' engine = defined?(RUBY_ENGINE) ? RUBY_ENGINE : 'ruby' res = String.new res << "Nanoc #{Nanoc::VERSION} © 2007-2017 Denis Defreyne.\n" res << "Running #{engine} #{RUBY_VERSION} (#{RUBY_RELEASE_DATE}) on #{RUBY_PLATFORM} #{gem_info}.\n" res end # @return [Boolean] True if the current platform is Windows, false otherwise. # # @api private def self.on_windows? RUBY_PLATFORM =~ /windows|bccwin|cygwin|djgpp|mingw|mswin|wince/i end end # Load external dependencies require 'hamster' require 'ref' require 'ddplugin' # Load general requirements require 'digest' require 'enumerator' require 'fiber' require 'fileutils' require 'forwardable' require 'pathname' require 'pstore' require 'set' require 'singleton' require 'tempfile' require 'thread' require 'time' require 'yaml' require 'uri' require 'English' # Load Nanoc require 'nanoc/version' require 'nanoc/base' require 'nanoc/telemetry' require 'nanoc/checking' require 'nanoc/deploying' require 'nanoc/extra' require 'nanoc/data_sources' require 'nanoc/filters' require 'nanoc/helpers' require 'nanoc/rule_dsl' nanoc-4.8.0/lib/nanoc/0000755000004100000410000000000013134141051014506 5ustar www-datawww-datananoc-4.8.0/lib/nanoc/base/0000755000004100000410000000000013134141051015420 5ustar www-datawww-datananoc-4.8.0/lib/nanoc/base/core_ext.rb0000644000004100000410000000021313134141051017551 0ustar www-datawww-data# frozen_string_literal: true require 'nanoc/base/core_ext/array' require 'nanoc/base/core_ext/hash' require 'nanoc/base/core_ext/string' nanoc-4.8.0/lib/nanoc/base/views/0000755000004100000410000000000013134141051016555 5ustar www-datawww-datananoc-4.8.0/lib/nanoc/base/views/item_rep_view.rb0000644000004100000410000000540713134141051021746 0ustar www-datawww-data# frozen_string_literal: true module Nanoc class ItemRepView < ::Nanoc::View # @api private def initialize(item_rep, context) super(context) @item_rep = item_rep end # @api private def unwrap @item_rep end # @see Object#== def ==(other) other.respond_to?(:item) && other.respond_to?(:name) && item == other.item && name == other.name end # @see Object#eql? def eql?(other) other.is_a?(self.class) && item.eql?(other.item) && name.eql?(other.name) end # @see Object#hash def hash self.class.hash ^ item.identifier.hash ^ name.hash end # @return [Symbol] def name @item_rep.name end # Returns the compiled content. # # @param [String] snapshot The name of the snapshot from which to # fetch the compiled content. By default, the returned compiled content # will be the content compiled right before the first layout call (if # any). # # @return [String] The content at the given snapshot. def compiled_content(snapshot: nil) @context.dependency_tracker.bounce(unwrap.item, compiled_content: true) @context.snapshot_repo.compiled_content(rep: unwrap, snapshot: snapshot) end def snapshot?(name) @context.dependency_tracker.bounce(unwrap.item, compiled_content: true) @item_rep.snapshot?(name) end # Returns the item rep’s path, as used when being linked to. It starts # with a slash and it is relative to the output directory. It does not # include the path to the output directory. It will not include the # filename if the filename is an index filename. # # @param [Symbol] snapshot The snapshot for which the path should be # returned. # # @return [String] The item rep’s path. def path(snapshot: :last) @context.dependency_tracker.bounce(unwrap.item, path: true) @item_rep.path(snapshot: snapshot) end # Returns the item that this item rep belongs to. # # @return [Nanoc::ItemWithRepsView] def item Nanoc::ItemWithRepsView.new(@item_rep.item, @context) end # @api private def raw_path(snapshot: :last) @context.dependency_tracker.bounce(unwrap.item, compiled_content: true) res = @item_rep.raw_path(snapshot: snapshot) unless @item_rep.compiled? Fiber.yield(Nanoc::Int::Errors::UnmetDependency.new(@item_rep)) end res end # @api private def binary? snapshot_def = unwrap.snapshot_defs.find { |sd| sd.name == :last } raise Nanoc::Int::Errors::NoSuchSnapshot.new(unwrap, :last) if snapshot_def.nil? snapshot_def.binary? end def inspect "<#{self.class} item.identifier=#{item.identifier} name=#{name}>" end end end nanoc-4.8.0/lib/nanoc/base/views/config_view.rb0000644000004100000410000000207413134141051021404 0ustar www-datawww-data# frozen_string_literal: true module Nanoc class ConfigView < ::Nanoc::View # @api private NONE = Object.new.freeze # @api private def initialize(config, context) super(context) @config = config end # @api private def unwrap @config end # @see Hash#fetch def fetch(key, fallback = NONE, &_block) @context.dependency_tracker.bounce(unwrap, attributes: [key]) @config.fetch(key) do if !fallback.equal?(NONE) fallback elsif block_given? yield(key) else raise KeyError, "key not found: #{key.inspect}" end end end # @see Hash#key? def key?(key) @context.dependency_tracker.bounce(unwrap, attributes: [key]) @config.key?(key) end # @see Hash#[] def [](key) @context.dependency_tracker.bounce(unwrap, attributes: [key]) @config[key] end # @see Hash#each def each(&block) @context.dependency_tracker.bounce(unwrap, attributes: true) @config.each(&block) end end end nanoc-4.8.0/lib/nanoc/base/views/mixins/0000755000004100000410000000000013134141051020064 5ustar www-datawww-datananoc-4.8.0/lib/nanoc/base/views/mixins/document_view_mixin.rb0000644000004100000410000000350013134141051024463 0ustar www-datawww-data# frozen_string_literal: true module Nanoc module DocumentViewMixin # @api private NONE = Object.new.freeze # @api private def initialize(document, context) super(context) @document = document end # @api private def unwrap @document end # @see Object#== def ==(other) other.respond_to?(:identifier) && identifier == other.identifier end # @see Object#eql? def eql?(other) other.is_a?(self.class) && identifier.eql?(other.identifier) end # @see Object#hash def hash self.class.hash ^ identifier.hash end # @return [Nanoc::Identifier] def identifier unwrap.identifier end # @see Hash#[] def [](key) @context.dependency_tracker.bounce(unwrap, attributes: [key]) unwrap.attributes[key] end # @return [Hash] def attributes # TODO: Refine dependencies @context.dependency_tracker.bounce(unwrap, attributes: true) unwrap.attributes end # @see Hash#fetch def fetch(key, fallback = NONE, &_block) @context.dependency_tracker.bounce(unwrap, attributes: [key]) if unwrap.attributes.key?(key) unwrap.attributes[key] elsif !fallback.equal?(NONE) fallback elsif block_given? yield(key) else raise KeyError, "key not found: #{key.inspect}" end end # @see Hash#key? def key?(key) @context.dependency_tracker.bounce(unwrap, attributes: [key]) unwrap.attributes.key?(key) end # @api private def reference unwrap.reference end # @api private def raw_content @context.dependency_tracker.bounce(unwrap, raw_content: true) unwrap.content.string end def inspect "<#{self.class} identifier=#{unwrap.identifier}>" end end end nanoc-4.8.0/lib/nanoc/base/views/mixins/mutable_document_view_mixin.rb0000644000004100000410000000247313134141051026204 0ustar www-datawww-data# frozen_string_literal: true module Nanoc module MutableDocumentViewMixin # @api private class DisallowedAttributeValueError < Nanoc::Error attr_reader :value def initialize(value) @value = value end def message "The #{value.class} cannot be stored inside an attribute. Store its identifier instead." end end def raw_content=(arg) unwrap.content = Nanoc::Int::Content.create(arg) end # Sets the value for the given attribute. # # @param [Symbol] key # # @see Hash#[]= def []=(key, value) disallowed_value_classes = Set.new([ Nanoc::Int::Item, Nanoc::Int::Layout, Nanoc::ItemWithRepsView, Nanoc::LayoutView, ]) if disallowed_value_classes.include?(value.class) raise DisallowedAttributeValueError.new(value) end unwrap.attributes[key] = value end # Sets the identifier to the given argument. # # @param [String, Nanoc::Identifier] arg def identifier=(arg) unwrap.identifier = Nanoc::Identifier.from(arg) end # Updates the attributes based on the given hash. # # @param [Hash] hash # # @return [self] def update_attributes(hash) hash.each { |k, v| unwrap.attributes[k] = v } self end end end nanoc-4.8.0/lib/nanoc/base/views/mixins/with_reps_view_mixin.rb0000644000004100000410000000321013134141051024647 0ustar www-datawww-data# frozen_string_literal: true module Nanoc module WithRepsViewMixin # Returns the compiled content. # # @param [String] rep The name of the representation # from which the compiled content should be fetched. By default, the # compiled content will be fetched from the default representation. # # @param [String] snapshot The name of the snapshot from which to # fetch the compiled content. By default, the returned compiled content # will be the content compiled right before the first layout call (if # any). # # @return [String] The content of the given rep at the given snapshot. def compiled_content(rep: :default, snapshot: nil) reps.fetch(rep).compiled_content(snapshot: snapshot) end # Returns the item path, as used when being linked to. It starts # with a slash and it is relative to the output directory. It does not # include the path to the output directory. It will not include the # filename if the filename is an index filename. # # @param [String] rep The name of the representation # from which the path should be fetched. By default, the path will be # fetched from the default representation. # # @param [Symbol] snapshot The snapshot for which the # path should be returned. # # @return [String] The item’s path. def path(rep: :default, snapshot: :last) reps.fetch(rep).path(snapshot: snapshot) end # Returns the representations of this item. # # @return [Nanoc::ItemRepCollectionView] def reps Nanoc::ItemRepCollectionView.new(@context.reps[unwrap], @context) end end end nanoc-4.8.0/lib/nanoc/base/views/item_with_reps_view.rb0000644000004100000410000000022313134141051023153 0ustar www-datawww-data# frozen_string_literal: true module Nanoc class ItemWithRepsView < ::Nanoc::ItemWithoutRepsView include Nanoc::WithRepsViewMixin end end nanoc-4.8.0/lib/nanoc/base/views/layout_collection_view.rb0000644000004100000410000000027713134141051023672 0ustar www-datawww-data# frozen_string_literal: true module Nanoc class LayoutCollectionView < ::Nanoc::IdentifiableCollectionView # @api private def view_class Nanoc::LayoutView end end end nanoc-4.8.0/lib/nanoc/base/views/mutable_config_view.rb0000644000004100000410000000040713134141051023113 0ustar www-datawww-data# frozen_string_literal: true module Nanoc class MutableConfigView < Nanoc::ConfigView # Sets the value for the given attribute. # # @param [Symbol] key # # @see Hash#[]= def []=(key, value) @config[key] = value end end end nanoc-4.8.0/lib/nanoc/base/views/mutable_layout_collection_view.rb0000644000004100000410000000126313134141051025377 0ustar www-datawww-data# frozen_string_literal: true module Nanoc class MutableLayoutCollectionView < Nanoc::MutableIdentifiableCollectionView # @api private def view_class Nanoc::MutableLayoutView end # Creates a new layout and adds it to the site’s collection of layouts. # # @param [String] content The layout content. # # @param [Hash] attributes A hash containing this layout's attributes. # # @param [Nanoc::Identifier, String] identifier This layout's identifier. # # @return [self] def create(content, attributes, identifier) @objects = @objects.add(Nanoc::Int::Layout.new(content, attributes, identifier)) self end end end nanoc-4.8.0/lib/nanoc/base/views/layout_view.rb0000644000004100000410000000017613134141051021455 0ustar www-datawww-data# frozen_string_literal: true module Nanoc class LayoutView < ::Nanoc::View include Nanoc::DocumentViewMixin end end nanoc-4.8.0/lib/nanoc/base/views/mutable_item_view.rb0000644000004100000410000000022713134141051022604 0ustar www-datawww-data# frozen_string_literal: true module Nanoc class MutableItemView < Nanoc::ItemWithoutRepsView include Nanoc::MutableDocumentViewMixin end end nanoc-4.8.0/lib/nanoc/base/views/post_compile_item_view.rb0000644000004100000410000000060113134141051023644 0ustar www-datawww-data# frozen_string_literal: true module Nanoc class PostCompileItemView < Nanoc::ItemWithRepsView def reps Nanoc::PostCompileItemRepCollectionView.new(@context.reps[unwrap], @context) end # @deprecated Use {#modified_reps} instead def modified modified_reps end def modified_reps reps.select { |rep| rep.unwrap.modified? } end end end nanoc-4.8.0/lib/nanoc/base/views/item_collection_with_reps_view.rb0000644000004100000410000000031313134141051025366 0ustar www-datawww-data# frozen_string_literal: true module Nanoc class ItemCollectionWithRepsView < ::Nanoc::IdentifiableCollectionView # @api private def view_class Nanoc::ItemWithRepsView end end end nanoc-4.8.0/lib/nanoc/base/views/item_without_reps_view.rb0000644000004100000410000000305613134141051023712 0ustar www-datawww-data# frozen_string_literal: true module Nanoc class ItemWithoutRepsView < ::Nanoc::View include Nanoc::DocumentViewMixin # Returns the children of this item. For items with identifiers that have # extensions, returns an empty collection. # # @return [Enumerable] def children unless unwrap.identifier.legacy? raise Nanoc::Int::Errors::CannotGetParentOrChildrenOfNonLegacyItem.new(unwrap.identifier) end children_pattern = Nanoc::Int::Pattern.from(unwrap.identifier.to_s + '*/') children = @context.items.select { |i| children_pattern.match?(i.identifier) } children.map { |i| self.class.new(i, @context) }.freeze end # Returns the parent of this item, if one exists. For items with identifiers # that have extensions, returns nil. # # @return [Nanoc::ItemWithRepsView] if the item has a parent # # @return [nil] if the item has no parent def parent unless unwrap.identifier.legacy? raise Nanoc::Int::Errors::CannotGetParentOrChildrenOfNonLegacyItem.new(unwrap.identifier) end parent_identifier = '/' + unwrap.identifier.components[0..-2].join('/') + '/' parent_identifier = '/' if parent_identifier == '//' parent = @context.items[parent_identifier] parent && self.class.new(parent, @context) end # @return [Boolean] True if the item is binary, false otherwise def binary? unwrap.content.binary? end # @api private def raw_filename unwrap.content.filename end end end nanoc-4.8.0/lib/nanoc/base/views/mutable_layout_view.rb0000644000004100000410000000022013134141051023154 0ustar www-datawww-data# frozen_string_literal: true module Nanoc class MutableLayoutView < Nanoc::LayoutView include Nanoc::MutableDocumentViewMixin end end nanoc-4.8.0/lib/nanoc/base/views/view.rb0000644000004100000410000000076413134141051020063 0ustar www-datawww-data# frozen_string_literal: true module Nanoc class View # @api private def initialize(context) @context = context end # @api private def _context @context end # @api private def unwrap raise NotImplementedError end # True if the wrapped object is frozen; false otherwise. # # @return [Boolean] # # @see Object#frozen? def frozen? unwrap.frozen? end def inspect "<#{self.class}>" end end end nanoc-4.8.0/lib/nanoc/base/views/identifiable_collection_view.rb0000644000004100000410000000455713134141051025001 0ustar www-datawww-data# frozen_string_literal: true module Nanoc class IdentifiableCollectionView < ::Nanoc::View include Enumerable # @api private def initialize(objects, context) super(context) @objects = objects end # @api private def unwrap @objects end # @abstract # # @api private def view_class raise NotImplementedError end # Calls the given block once for each object, passing that object as a parameter. # # @yieldparam [#identifier] object # # @yieldreturn [void] # # @return [self] def each @context.dependency_tracker.bounce(unwrap, raw_content: true) @objects.each { |i| yield view_class.new(i, @context) } self end # @return [Integer] def size @context.dependency_tracker.bounce(unwrap, raw_content: true) @objects.size end # Finds all objects whose identifier matches the given argument. # # @param [String, Regex] arg # # @return [Enumerable] def find_all(arg) prop_attribute = case arg when String, Nanoc::Identifier [arg.to_s] when Regexp [arg] else true end @context.dependency_tracker.bounce(unwrap, raw_content: prop_attribute) @objects.find_all(arg).map { |i| view_class.new(i, @context) } end # @overload [](string) # # Finds the object whose identifier matches the given string. # # If the glob syntax is enabled, the string can be a glob, in which case # this method finds the first object that matches the given glob. # # @param [String] string # # @return [nil] if no object matches the string # # @return [#identifier] if an object was found # # @overload [](regex) # # Finds the object whose identifier matches the given regular expression. # # @param [Regex] regex # # @return [nil] if no object matches the regex # # @return [#identifier] if an object was found def [](arg) prop_attribute = case arg when String, Nanoc::Identifier [arg.to_s] when Regexp [arg] else true end @context.dependency_tracker.bounce(unwrap, raw_content: prop_attribute) res = @objects[arg] res && view_class.new(res, @context) end end end nanoc-4.8.0/lib/nanoc/base/views/post_compile_item_collection_view.rb0000644000004100000410000000031713134141051026063 0ustar www-datawww-data# frozen_string_literal: true module Nanoc class PostCompileItemCollectionView < Nanoc::IdentifiableCollectionView # @api private def view_class Nanoc::PostCompileItemView end end end nanoc-4.8.0/lib/nanoc/base/views/view_context.rb0000644000004100000410000000100313134141051021612 0ustar www-datawww-data# frozen_string_literal: true module Nanoc # @api private class ViewContext attr_reader :reps attr_reader :items attr_reader :dependency_tracker attr_reader :compilation_context attr_reader :snapshot_repo def initialize(reps:, items:, dependency_tracker:, compilation_context:, snapshot_repo:) @reps = reps @items = items @dependency_tracker = dependency_tracker @compilation_context = compilation_context @snapshot_repo = snapshot_repo end end end nanoc-4.8.0/lib/nanoc/base/views/mutable_item_collection_view.rb0000644000004100000410000000211113134141051025011 0ustar www-datawww-data# frozen_string_literal: true module Nanoc class MutableItemCollectionView < Nanoc::MutableIdentifiableCollectionView # @api private def view_class Nanoc::MutableItemView end # Creates a new item and adds it to the site’s collection of items. # # @param [String] content The uncompiled item content (if it is a textual # item) or the path to the filename containing the content (if it is a # binary item). # # @param [Hash] attributes A hash containing this item's attributes. # # @param [Nanoc::Identifier, String] identifier This item's identifier. # # @param [Boolean] binary Whether or not this item is binary # # @param [String] filename Absolute path to the file # containing this content (if any) # # @return [self] def create(content, attributes, identifier, binary: false, filename: nil) content = Nanoc::Int::Content.create(content, binary: binary, filename: filename) @objects = @objects.add(Nanoc::Int::Item.new(content, attributes, identifier)) self end end end nanoc-4.8.0/lib/nanoc/base/views/item_collection_without_reps_view.rb0000644000004100000410000000032113134141051026115 0ustar www-datawww-data# frozen_string_literal: true module Nanoc class ItemCollectionWithoutRepsView < ::Nanoc::IdentifiableCollectionView # @api private def view_class Nanoc::ItemWithoutRepsView end end end nanoc-4.8.0/lib/nanoc/base/views/post_compile_item_rep_view.rb0000644000004100000410000000133113134141051024513 0ustar www-datawww-data# frozen_string_literal: true module Nanoc class PostCompileItemRepView < ::Nanoc::ItemRepView def compiled_content(snapshot: nil) snapshot_contents = @context.compilation_context.compiled_content_cache[unwrap] snapshot_name = snapshot || (snapshot_contents[:pre] ? :pre : :last) unless snapshot_contents[snapshot_name] raise Nanoc::Int::Errors::NoSuchSnapshot.new(unwrap, snapshot_name) end content = snapshot_contents[snapshot_name] if content.binary? raise Nanoc::Int::Errors::CannotGetCompiledContentOfBinaryItem.new(unwrap) end content.string end def raw_path(snapshot: :last) @item_rep.raw_path(snapshot: snapshot) end end end nanoc-4.8.0/lib/nanoc/base/views/mutable_identifiable_collection_view.rb0000644000004100000410000000065313134141051026503 0ustar www-datawww-data# frozen_string_literal: true module Nanoc class MutableIdentifiableCollectionView < Nanoc::IdentifiableCollectionView # Deletes every object for which the block evaluates to true. # # @yieldparam [#identifier] object # # @yieldreturn [Boolean] # # @return [self] def delete_if(&_block) @objects = @objects.reject { |o| yield(view_class.new(o, @context)) } self end end end nanoc-4.8.0/lib/nanoc/base/views/post_compile_item_rep_collection_view.rb0000644000004100000410000000032013134141051026723 0ustar www-datawww-data# frozen_string_literal: true module Nanoc class PostCompileItemRepCollectionView < Nanoc::ItemRepCollectionView # @api private def view_class Nanoc::PostCompileItemRepView end end end nanoc-4.8.0/lib/nanoc/base/views/item_rep_collection_view.rb0000644000004100000410000000415113134141051024154 0ustar www-datawww-data# frozen_string_literal: true module Nanoc class ItemRepCollectionView < ::Nanoc::View include Enumerable class NoSuchItemRepError < ::Nanoc::Error def initialize(rep_name) super("No rep named #{rep_name.inspect} was found.") end end # @api private def initialize(item_reps, context) super(context) @item_reps = item_reps end # @api private def unwrap @item_reps end # @api private def view_class Nanoc::ItemRepView end def to_ary @item_reps.map { |ir| view_class.new(ir, @context) } end # Calls the given block once for each item rep, passing that item rep as a parameter. # # @yieldparam [Object] item rep view # # @yieldreturn [void] # # @return [self] def each @item_reps.each { |ir| yield view_class.new(ir, @context) } self end # @return [Integer] def size @item_reps.size end # Return the item rep with the given name, or nil if no item rep exists. # # @param [Symbol] rep_name # # @return [nil] if no item rep with the given name was found # # @return [Nanoc::ItemRepView] if an item rep with the given name was found def [](rep_name) case rep_name when Symbol res = @item_reps.find { |ir| ir.name == rep_name } res && view_class.new(res, @context) when Integer raise ArgumentError, "expected ItemRepCollectionView#[] to be called with a symbol (you likely want `.reps[:default]` rather than `.reps[#{rep_name}]`)" else raise ArgumentError, 'expected ItemRepCollectionView#[] to be called with a symbol' end end # Return the item rep with the given name, or raises an exception if there # is no rep with the given name. # # @param [Symbol] rep_name # # @return [Nanoc::ItemRepView] # # @raise if no rep was found def fetch(rep_name) res = @item_reps.find { |ir| ir.name == rep_name } if res view_class.new(res, @context) else raise NoSuchItemRepError.new(rep_name) end end end end nanoc-4.8.0/lib/nanoc/base/core_ext/0000755000004100000410000000000013134141051017230 5ustar www-datawww-datananoc-4.8.0/lib/nanoc/base/core_ext/string.rb0000644000004100000410000000052613134141051021066 0ustar www-datawww-data# frozen_string_literal: true # @api private module Nanoc::StringExtensions # Transforms string into an actual identifier # # @return [String] The identifier generated from the receiver def __nanoc_cleaned_identifier "/#{self}/".gsub(/^\/+|\/+$/, '/') end end # @api private class String include Nanoc::StringExtensions end nanoc-4.8.0/lib/nanoc/base/core_ext/hash.rb0000644000004100000410000000234013134141051020477 0ustar www-datawww-data# frozen_string_literal: true # @api private module Nanoc::HashExtensions # Returns a new hash where all keys are recursively converted to symbols by # calling {Nanoc::ArrayExtensions#__nanoc_symbolize_keys_recursively} or # {Nanoc::HashExtensions#__nanoc_symbolize_keys_recursively}. # # @return [Hash] The converted hash def __nanoc_symbolize_keys_recursively hash = {} each_pair do |key, value| new_key = key.respond_to?(:to_sym) ? key.to_sym : key new_value = value.respond_to?(:__nanoc_symbolize_keys_recursively) ? value.__nanoc_symbolize_keys_recursively : value hash[new_key] = new_value end hash end # Freezes the contents of the hash, as well as all hash values. The hash # values will be frozen using {#__nanoc_freeze_recursively} if they respond to # that message, or #freeze if they do not. # # @see Array#__nanoc_freeze_recursively # # @return [void] def __nanoc_freeze_recursively return if frozen? freeze each_pair do |_key, value| if value.respond_to?(:__nanoc_freeze_recursively) value.__nanoc_freeze_recursively else value.freeze end end end end # @api private class Hash include Nanoc::HashExtensions end nanoc-4.8.0/lib/nanoc/base/core_ext/array.rb0000644000004100000410000000221113134141051020667 0ustar www-datawww-data# frozen_string_literal: true # @api private module Nanoc::ArrayExtensions # Returns a new array where all items' keys are recursively converted to # symbols by calling {Nanoc::ArrayExtensions#__nanoc_symbolize_keys_recursively} or # {Nanoc::HashExtensions#__nanoc_symbolize_keys_recursively}. # # @return [Array] The converted array def __nanoc_symbolize_keys_recursively array = [] each do |element| array << (element.respond_to?(:__nanoc_symbolize_keys_recursively) ? element.__nanoc_symbolize_keys_recursively : element) end array end # Freezes the contents of the array, as well as all array elements. The # array elements will be frozen using {#__nanoc_freeze_recursively} if they respond # to that message, or #freeze if they do not. # # @see Hash#__nanoc_freeze_recursively # # @return [void] def __nanoc_freeze_recursively return if frozen? freeze each do |value| if value.respond_to?(:__nanoc_freeze_recursively) value.__nanoc_freeze_recursively else value.freeze end end end end # @api private class Array include Nanoc::ArrayExtensions end nanoc-4.8.0/lib/nanoc/base/repos.rb0000644000004100000410000000114413134141051017075 0ustar www-datawww-data# frozen_string_literal: true require_relative 'repos/store' require_relative 'repos/checksum_store' require_relative 'repos/compiled_content_cache' require_relative 'repos/config_loader' require_relative 'repos/data_source' require_relative 'repos/dependency_store' require_relative 'repos/item_rep_repo' require_relative 'repos/outdatedness_store' require_relative 'repos/action_sequence_store' require_relative 'repos/site_loader' require_relative 'repos/snapshot_repo' require_relative 'repos/in_mem_data_source' require_relative 'repos/aggregate_data_source' require_relative 'repos/prefixed_data_source' nanoc-4.8.0/lib/nanoc/base/errors.rb0000644000004100000410000002262313134141051017266 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int # Module that contains all Nanoc-specific errors. # # @api private module Errors Generic = ::Nanoc::Error # Generic trivial error. Superclass for all Nanoc-specific errors that are # considered "trivial", i.e. errors that do not require a full crash report. class GenericTrivial < Generic end # Error that is raised when compilation of an item rep fails. The # underlying error is available by calling `#unwrap`. class CompilationError < Generic attr_reader :item_rep def initialize(wrapped, item_rep) @wrapped = wrapped @item_rep = item_rep end def unwrap @wrapped end end # Error that is raised when a site is loaded that uses a data source with # an unknown identifier. class UnknownDataSource < Generic # @param [String] data_source_name The data source name for which no # data source could be found def initialize(data_source_name) super("The data source specified in the site’s configuration file, “#{data_source_name}”, does not exist.") end end # Error that is raised during site compilation when an item uses a layout # that is not present in the site. class UnknownLayout < Generic # @param [String] layout_identifier The layout identifier for which no # layout could be found def initialize(layout_identifier) super("The site does not have a layout with identifier “#{layout_identifier}”.") end end # Error that is raised during site compilation when an item uses a filter # that is not known. class UnknownFilter < Generic # @param [Symbol] filter_name The filter name for which no filter could # be found def initialize(filter_name) super("The requested filter, “#{filter_name}”, does not exist.") end end # Error that is raised during site compilation when a layout is compiled # for which the filter cannot be determined. This is similar to the # {UnknownFilter} error, but specific for filters for layouts. class CannotDetermineFilter < Generic # @param [String] layout_identifier The identifier of the layout for # which the filter could not be determined def initialize(layout_identifier) super("The filter to be used for the “#{layout_identifier}” layout could not be determined. Make sure the layout does have a filter.") end end # Error that is raised during site compilation when an item (directly or # indirectly) includes its own item content, leading to endless recursion. class DependencyCycle < Generic def initialize(stack) start_idx = stack.index(stack.last) cycle = stack[start_idx..-2] msg_bits = [] msg_bits << 'The site cannot be compiled because there is a dependency cycle:' msg_bits << '' cycle.each.with_index do |r, i| msg_bits << " (#{i + 1}) item #{r.item.identifier}, rep #{r.name.inspect}, uses compiled content of" end msg_bits << msg_bits.pop + ' (1)' super(msg_bits.map { |x| x + "\n" }.join('')) end end # Error that is raised when no rules file can be found in the current # working directory. class NoRulesFileFound < Generic def initialize super('This site does not have a rules file, which is required for Nanoc sites.') end end # Error that is raised when no compilation rule that can be applied to the # current item can be found. class NoMatchingCompilationRuleFound < Generic # @param [Nanoc::Int::Item] item The item for which no compilation rule # could be found def initialize(item) super("No compilation rules were found for the “#{item.identifier}” item.") end end # Error that is raised when no routing rule that can be applied to the # current item can be found. class NoMatchingRoutingRuleFound < Generic # @param [Nanoc::Int::ItemRep] rep The item repiresentation for which no # routing rule could be found def initialize(rep) super("No routing rules were found for the “#{rep.item.identifier}” item (rep “#{rep.name}”).") end end # Error that is raised when an rep cannot be compiled because it depends # on other representations. class UnmetDependency < Generic # @return [Nanoc::Int::ItemRep] The item representation that cannot yet be # compiled attr_reader :rep # @param [Nanoc::Int::ItemRep] rep The item representation that cannot yet be # compiled def initialize(rep) @rep = rep super("The current item cannot be compiled yet because of an unmet dependency on the “#{rep.item.identifier}” item (rep “#{rep.name}”).") end end # Error that is raised when a binary item is attempted to be laid out. class CannotLayoutBinaryItem < Generic # @param [Nanoc::Int::ItemRep] rep The item representation that was attempted # to be laid out def initialize(rep) super("The “#{rep.item.identifier}” item (rep “#{rep.name}”) cannot be laid out because it is a binary item. If you are getting this error for an item that should be textual instead of binary, make sure that its extension is included in the text_extensions array in the site configuration.") end end # Error that is raised when a textual filter is attempted to be applied to # a binary item representation. class CannotUseTextualFilter < Generic # @param [Nanoc::Int::ItemRep] rep The item representation that was # attempted to be filtered # # @param [Class] filter_class The filter class that was used def initialize(rep, filter_class) super("The “#{filter_class.inspect}” filter cannot be used to filter the “#{rep.item.identifier}” item (rep “#{rep.name}”), because textual filters cannot be used on binary items.") end end # Error that is raised when a binary filter is attempted to be applied to # a textual item representation. class CannotUseBinaryFilter < Generic # @param [Nanoc::Int::ItemRep] rep The item representation that was # attempted to be filtered # # @param [Class] filter_class The filter class that was used def initialize(rep, filter_class) super("The “#{filter_class.inspect}” filter cannot be used to filter the “#{rep.item.identifier}” item (rep “#{rep.name}”), because binary filters cannot be used on textual items. If you are getting this error for an item that should be textual instead of binary, make sure that its extension is included in the text_extensions array in the site configuration.") end end # Error that is raised when the compiled content at a non-existing snapshot # is requested. class NoSuchSnapshot < Generic # @return [Nanoc::Int::ItemRep] The item rep from which the compiled content # was requested attr_reader :item_rep # @return [Symbol] The requested snapshot attr_reader :snapshot # @param [Nanoc::Int::ItemRep] item_rep The item rep from which the compiled # content was requested # # @param [Symbol] snapshot The requested snapshot def initialize(item_rep, snapshot) @item_rep = item_rep @snapshot = snapshot super("The “#{item_rep.inspect}” item rep does not have a snapshot “#{snapshot.inspect}”") end end # Error that is raised when a snapshot with an existing name is made. class CannotCreateMultipleSnapshotsWithSameName < Generic # @param [Nanoc::Int::ItemRep] rep The item representation for which a # snapshot was attempted to be made # # @param [Symbol] snapshot The name of the snapshot that was attempted to # be made def initialize(rep, snapshot) super("Attempted to create a snapshot with a duplicate name #{snapshot.inspect} for the item rep “#{rep.inspect}”") end end # Error that is raised when the compiled content of a binary item is attempted to be accessed. class CannotGetCompiledContentOfBinaryItem < Generic # @param [Nanoc::Int::ItemRep] rep The binary item representation whose compiled content was attempted to be accessed def initialize(rep) super("You cannot access the compiled content of a binary item representation (but you can access the path). The offending item rep is #{rep.inspect}.") end end # Error that is raised when multiple items or layouts with the same identifier exist. class DuplicateIdentifier < Generic def initialize(identifier, type) super("There are multiple #{type}s with the #{identifier} identifier.") end end # Error that is raised when attempting to call #parent or #children on an item with a legacy identifier. class CannotGetParentOrChildrenOfNonLegacyItem < Generic def initialize(identifier) super("You cannot get the parent or children of an item that has a “full” identifier (#{identifier}). Getting the parent or children of an item is only possible for items that have a legacy identifier.") end end class UndefinedFilterForLayout < Generic def initialize(layout) super("There is no filter defined for the layout #{layout.identifier}") end end class InternalInconsistency < Generic end end end nanoc-4.8.0/lib/nanoc/base/entities/0000755000004100000410000000000013134141051017244 5ustar www-datawww-datananoc-4.8.0/lib/nanoc/base/entities/code_snippet.rb0000644000004100000410000000251613134141051022251 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int # Nanoc::Int::CodeSnippet represent a piece of custom code of a Nanoc site. # # @api private class CodeSnippet include Nanoc::Int::ContractsSupport # A string containing the actual code in this code snippet. # # @return [String] attr_reader :data # The filename corresponding to this code snippet. # # @return [String] attr_reader :filename contract String, String => C::Any # Creates a new code snippet. # # @param [String] data The raw source code which will be executed before # compilation # # @param [String] filename The filename corresponding to this code snippet def initialize(data, filename) @data = data @filename = filename end contract C::None => nil # Loads the code by executing it. # # @return [void] def load eval('def self.use_helper(mod); Nanoc::Int::Context.instance_eval { include mod }; end', TOPLEVEL_BINDING) eval(@data, TOPLEVEL_BINDING, @filename) nil end # Returns an object that can be used for uniquely identifying objects. # # @return [Object] An unique reference to this object def reference "code_snippet:#{filename}" end def inspect "<#{self.class} filename=\"#{filename}\">" end end end nanoc-4.8.0/lib/nanoc/base/entities/identifiable_collection.rb0000644000004100000410000000631313134141051024426 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int # @api private class IdentifiableCollection include Nanoc::Int::ContractsSupport include Enumerable extend Nanoc::Int::Memoization extend Forwardable def_delegator :@objects, :each def_delegator :@objects, :size def initialize(*) raise 'IdentifiableCollection is abstract and cannot be instantiated' end contract C::Or[Hash, C::Named['Nanoc::Int::Configuration']], C::IterOf[C::RespondTo[:identifier]], C::Maybe[String] => C::Any def initialize_basic(config, objects = [], name = nil) @config = config @objects = Hamster::Vector.new(objects) @name = name end contract C::None => self def freeze @objects.freeze @objects.each(&:freeze) build_mapping super end contract C::Any => C::Maybe[C::RespondTo[:identifier]] def [](arg) if frozen? get_memoized(arg) else get_unmemoized(arg) end end contract C::Any => C::IterOf[C::RespondTo[:identifier]] def find_all(arg) if frozen? find_all_memoized(arg) else find_all_unmemoized(arg) end end contract C::None => C::ArrayOf[C::RespondTo[:identifier]] def to_a @objects.to_a end contract C::None => C::Bool def empty? @objects.empty? end contract C::RespondTo[:identifier] => self def add(obj) self.class.new(@config, @objects.add(obj)) end contract C::Func[C::RespondTo[:identifier] => C::Any] => self def reject(&block) self.class.new(@config, @objects.reject(&block)) end contract C::Any => C::Maybe[C::RespondTo[:identifier]] def object_with_identifier(identifier) if frozen? @mapping[identifier.to_s] else @objects.find { |i| i.identifier == identifier } end end protected contract C::Any => C::Maybe[C::RespondTo[:identifier]] def get_unmemoized(arg) case arg when Nanoc::Identifier object_with_identifier(arg) when String object_with_identifier(arg) || object_matching_glob(arg) when Regexp @objects.find { |i| i.identifier.to_s =~ arg } else raise ArgumentError, "don’t know how to fetch objects by #{arg.inspect}" end end contract C::Any => C::Maybe[C::RespondTo[:identifier]] memoized def get_memoized(arg) get_unmemoized(arg) end contract C::Any => C::IterOf[C::RespondTo[:identifier]] def find_all_unmemoized(arg) pat = Nanoc::Int::Pattern.from(arg) select { |i| pat.match?(i.identifier) } end contract C::Any => C::IterOf[C::RespondTo[:identifier]] memoized def find_all_memoized(arg) find_all_unmemoized(arg) end def object_matching_glob(glob) if use_globs? pat = Nanoc::Int::Pattern.from(glob) @objects.find { |i| pat.match?(i.identifier) } else nil end end def build_mapping @mapping = {} @objects.each do |object| @mapping[object.identifier.to_s] = object end @mapping.freeze end contract C::None => C::Bool def use_globs? @config[:string_pattern_type] == 'glob' end end end nanoc-4.8.0/lib/nanoc/base/entities/configuration.rb0000644000004100000410000001122713134141051022443 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int # Represents the site configuration. # # @api private class Configuration include Nanoc::Int::ContractsSupport NONE = Object.new.freeze # The default configuration for a data source. A data source's # configuration overrides these options. DEFAULT_DATA_SOURCE_CONFIG = { type: 'filesystem', items_root: '/', layouts_root: '/', config: {}, identifier_type: 'full', }.freeze # The default configuration for a site. A site's configuration overrides # these options: when a {Nanoc::Int::Site} is created with a configuration # that lacks some options, the default value will be taken from # `DEFAULT_CONFIG`. DEFAULT_CONFIG = { text_extensions: %w[adoc asciidoc atom css erb haml htm html js less markdown md php rb sass scss txt xhtml xml coffee hb handlebars mustache ms slim rdoc].sort, lib_dirs: %w[lib], commands_dirs: %w[commands], output_dir: 'output', data_sources: [{}], index_filenames: ['index.html'], enable_output_diff: false, prune: { auto_prune: false, exclude: ['.git', '.hg', '.svn', 'CVS'] }, string_pattern_type: 'glob', }.freeze # @return [String, nil] The active environment for the configuration attr_reader :env_name # Configuration environments property key ENVIRONMENTS_CONFIG_KEY = :environments NANOC_ENV = 'NANOC_ENV' NANOC_ENV_DEFAULT = 'default' contract C::KeywordArgs[hash: C::Optional[Hash], env_name: C::Maybe[String]] => C::Any # Creates a new configuration with the given hash. # # @param [Hash] hash The actual configuration hash # @param [String, nil] env_name The active environment for this configuration def initialize(hash: {}, env_name: nil) @env_name = env_name @wrapped = hash.__nanoc_symbolize_keys_recursively end contract C::None => self def with_defaults new_wrapped = DEFAULT_CONFIG.merge(@wrapped) new_wrapped[:data_sources] = new_wrapped[:data_sources].map do |ds| DEFAULT_DATA_SOURCE_CONFIG.merge(ds) end self.class.new(hash: new_wrapped, env_name: @env_name) end def with_environment return self unless @wrapped.key?(ENVIRONMENTS_CONFIG_KEY) # Set active environment env_name = @env_name || ENV.fetch(NANOC_ENV, NANOC_ENV_DEFAULT) # Load given environment configuration env_config = @wrapped[ENVIRONMENTS_CONFIG_KEY].fetch(env_name.to_sym, {}) self.class.new(hash: @wrapped, env_name: env_name).merge(env_config) end contract C::None => Hash def to_h @wrapped end # For compat contract C::None => Hash def attributes to_h end contract C::Any => C::Bool def key?(key) @wrapped.key?(key) end contract C::Any => C::Any def [](key) @wrapped[key] end contract C::Any, C::Maybe[C::Any], C::Maybe[C::Func[C::None => C::Any]] => C::Any def fetch(key, fallback = NONE, &_block) @wrapped.fetch(key) do if !fallback.equal?(NONE) fallback elsif block_given? yield(key) else raise KeyError, "key not found: #{key.inspect}" end end end contract C::Any, C::Any => C::Any def []=(key, value) @wrapped[key] = value end def merge_recursively(c1, c2) c1.merge(c2) { |_, v1, v2| v1.is_a?(Hash) && v2.is_a?(Hash) ? merge_recursively(v1, v2) : v2 } end private :merge_recursively contract C::Or[Hash, self] => self def merge(hash) self.class.new(hash: merge_recursively(@wrapped, hash.to_h), env_name: @env_name) end contract C::Any => self def without(key) self.class.new(hash: @wrapped.reject { |k, _v| k == key }, env_name: @env_name) end contract C::Any => self def update(hash) @wrapped.update(hash) self end contract C::Func[C::Any, C::Any => C::Any] => self def each @wrapped.each { |k, v| yield(k, v) } self end contract C::None => self def freeze super @wrapped.__nanoc_freeze_recursively self end contract C::None => String def output_dir self[:output_dir] end contract C::None => C::IterOf[String] def output_dirs envs = @wrapped.fetch(ENVIRONMENTS_CONFIG_KEY, {}) res = [output_dir] + envs.values.map { |v| v[:output_dir] } res.uniq.compact end # Returns an object that can be used for uniquely identifying objects. # # @return [Object] An unique reference to this object def reference 'configuration' end def inspect "<#{self.class}>" end end end nanoc-4.8.0/lib/nanoc/base/entities/props.rb0000644000004100000410000000570313134141051020741 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int # @api private class Props include Nanoc::Int::ContractsSupport attr_reader :attributes attr_reader :raw_content # TODO: Split raw_content for documents and collections C_RAW_CONTENT = C::Or[C::IterOf[C::Or[String, Regexp]], C::Bool] C_ATTRS = C::Or[C::IterOf[Symbol], C::Bool] contract C::KeywordArgs[raw_content: C::Optional[C_RAW_CONTENT], attributes: C::Optional[C_ATTRS], compiled_content: C::Optional[C::Bool], path: C::Optional[C::Bool]] => C::Any def initialize(raw_content: false, attributes: false, compiled_content: false, path: false) @compiled_content = compiled_content @path = path @attributes = case attributes when Enumerable Set.new(attributes) else attributes end @raw_content = case raw_content when Enumerable Set.new(raw_content) else raw_content end end contract C::None => String def inspect String.new.tap do |s| s << 'Props(' s << (raw_content? ? 'r' : '_') s << (attributes? ? 'a' : '_') s << (compiled_content? ? 'c' : '_') s << (path? ? 'p' : '_') s << ')' end end contract C::None => C::Bool def raw_content? case @raw_content when Enumerable @raw_content.any? else @raw_content end end contract C::None => C::Bool def attributes? case @attributes when Enumerable @attributes.any? else @attributes end end contract C::None => C::Bool def compiled_content? @compiled_content end contract C::None => C::Bool def path? @path end contract Nanoc::Int::Props => Nanoc::Int::Props def merge(other) Props.new( raw_content: merge_raw_content(other), attributes: merge_attributes(other), compiled_content: compiled_content? || other.compiled_content?, path: path? || other.path?, ) end def merge_raw_content(other) merge_prop(raw_content, other.raw_content) end def merge_attributes(other) merge_prop(attributes, other.attributes) end def merge_prop(own, other) case own when true true when false other else case other when true true when false own else own + other end end end contract C::None => Set def active Set.new.tap do |pr| pr << :raw_content if raw_content? pr << :attributes if attributes? pr << :compiled_content if compiled_content? pr << :path if path? end end contract C::None => Hash def to_h { raw_content: raw_content, attributes: attributes, compiled_content: compiled_content?, path: path?, } end end end nanoc-4.8.0/lib/nanoc/base/entities/pattern.rb0000644000004100000410000000340613134141051021251 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int # @api private class Pattern include Nanoc::Int::ContractsSupport contract C::Any => self def self.from(obj) case obj when Nanoc::Int::StringPattern, Nanoc::Int::RegexpPattern obj when String Nanoc::Int::StringPattern.new(obj) when Regexp Nanoc::Int::RegexpPattern.new(obj) else raise ArgumentError, "Do not know how to convert `#{obj.inspect}` into a Nanoc::Pattern" end end def initialize(_obj) raise NotImplementedError end def match?(_identifier) raise NotImplementedError end def captures(_identifier) raise NotImplementedError end end # @api private class StringPattern < Pattern MATCH_OPTS = File::FNM_PATHNAME | File::FNM_EXTGLOB contract String => C::Any def initialize(string) @string = string end contract C::Or[Nanoc::Identifier, String] => C::Bool def match?(identifier) File.fnmatch(@string, identifier.to_s, MATCH_OPTS) end contract C::Or[Nanoc::Identifier, String] => nil def captures(_identifier) nil end contract C::None => String def to_s @string end end # @api private class RegexpPattern < Pattern contract Regexp => C::Any def initialize(regexp) @regexp = regexp end contract C::Or[Nanoc::Identifier, String] => C::Bool def match?(identifier) (identifier.to_s =~ @regexp) != nil end contract C::Or[Nanoc::Identifier, String] => C::Maybe[C::ArrayOf[String]] def captures(identifier) matches = @regexp.match(identifier.to_s) matches && matches.captures end contract C::None => String def to_s @regexp.to_s end end end nanoc-4.8.0/lib/nanoc/base/entities/context.rb0000644000004100000410000000344713134141051021265 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int # Provides a context and a binding for use in filters such as the ERB and # Haml ones. # # @api private class Context # Creates a new context based off the contents of the hash. # # Each pair in the hash will be converted to an instance variable and an # instance method. For example, passing the hash `{ :foo => 'bar' }` will # cause `@foo` to have the value `"bar"`, and the instance method `#foo` # to return the same value `"bar"`. # # @param [Hash] hash A list of key-value pairs to make available # # @example Defining a context and accessing values # # context = Nanoc::Int::Context.new( # :name => 'Max Payne', # :location => 'in a cheap motel' # ) # context.instance_eval do # "I am #{name} and I am hiding #{@location}." # end # # => "I am Max Payne and I am hiding in a cheap motel." def initialize(hash) hash.each_pair do |key, value| instance_variable_set('@' + key.to_s, value) end end # Returns a binding for this instance. # # @return [Binding] A binding for this instance # rubocop:disable Style/AccessorMethodName def get_binding binding end # rubocop:enable Style/AccessorMethodName def method_missing(method, *args, &blk) ivar_name = '@' + method.to_s if instance_variable_defined?(ivar_name) instance_variable_get(ivar_name) else super end end def respond_to_missing?(method, include_all) ivar_name = '@' + method.to_s instance_variable_defined?(ivar_name) || super end def include(mod) metaclass = class << self; self; end metaclass.instance_eval { include(mod) } end end end nanoc-4.8.0/lib/nanoc/base/entities/dependency.rb0000644000004100000410000000152613134141051021713 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int # @api private # A dependency between two items/layouts. class Dependency include Nanoc::Int::ContractsSupport C_OBJ_FROM = C::Or[Nanoc::Int::Item, Nanoc::Int::Layout, Nanoc::Int::Configuration, Nanoc::Int::IdentifiableCollection] C_OBJ_TO = Nanoc::Int::Item contract C::None => C::Maybe[C_OBJ_FROM] attr_reader :from contract C::None => C::Maybe[C_OBJ_TO] attr_reader :to contract C::None => Nanoc::Int::Props attr_reader :props contract C::Maybe[C_OBJ_FROM], C::Maybe[C_OBJ_TO], Nanoc::Int::Props => C::Any def initialize(from, to, props) @from = from @to = to @props = props end contract C::None => String def inspect "Dependency(#{@from.inspect} -> #{@to.inspect}, #{@props.inspect})" end end end nanoc-4.8.0/lib/nanoc/base/entities/item.rb0000644000004100000410000000024713134141051020532 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int # @api private class Item < ::Nanoc::Int::Document def reference "item:#{identifier}" end end end nanoc-4.8.0/lib/nanoc/base/entities/outdatedness_status.rb0000644000004100000410000000075013134141051023700 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int # @api private class OutdatednessStatus attr_reader :reasons attr_reader :props def initialize(reasons: [], props: Props.new) @reasons = reasons @props = props end def useful_to_apply?(rule) (rule.affected_props - @props.active).any? end def update(reason) self.class.new( reasons: @reasons + [reason], props: @props.merge(reason.props), ) end end end nanoc-4.8.0/lib/nanoc/base/entities/lazy_value.rb0000644000004100000410000000212213134141051021741 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int # Holds a value that might be generated lazily. # # @api private class LazyValue include Nanoc::Int::ContractsSupport # @param [Object, Proc] value_or_proc A value or a proc to generate the value def initialize(value_or_proc) @value = { raw: value_or_proc } end # @return [Object] The value, generated when needed def value if @value.key?(:raw) value = @value.delete(:raw) @value[:final] = value.respond_to?(:call) ? value.call : value @value.__nanoc_freeze_recursively if frozen? end @value[:final] end contract C::Func[C::Any => C::Any] => self # Returns a new lazy value that will apply the given transformation when the value is requested. # # @yield resolved value # # @return [Nanoc::Int::LazyValue] def map Nanoc::Int::LazyValue.new(-> { yield(value) }) end contract C::None => self # @return [void] def freeze super @value.__nanoc_freeze_recursively unless @value[:raw] self end end end nanoc-4.8.0/lib/nanoc/base/entities/document.rb0000644000004100000410000000550413134141051021413 0ustar www-datawww-data# frozen_string_literal: true module Nanoc module Int # @api private class Document include Nanoc::Int::ContractsSupport # @return [Nanoc::Int::Content] attr_accessor :content # @return [Hash] def attributes @attributes.value end # @return [Nanoc::Identifier] attr_accessor :identifier # @return [String, nil] attr_accessor :checksum_data # @return [String, nil] attr_accessor :content_checksum_data # @return [String, nil] attr_accessor :attributes_checksum_data c_content = C::Or[String, Nanoc::Int::Content] c_attributes = C::Or[Hash, Proc] c_identifier = C::Or[String, Nanoc::Identifier] c_checksum_data = C::KeywordArgs[ checksum_data: C::Optional[C::Maybe[String]], content_checksum_data: C::Optional[C::Maybe[String]], attributes_checksum_data: C::Optional[C::Maybe[String]], ] contract c_content, c_attributes, c_identifier, c_checksum_data => C::Any # @param [String, Nanoc::Int::Content] content # # @param [Hash, Proc] attributes # # @param [String, Nanoc::Identifier] identifier # # @param [String, nil] checksum_data # # @param [String, nil] content_checksum_data # # @param [String, nil] attributes_checksum_data def initialize(content, attributes, identifier, checksum_data: nil, content_checksum_data: nil, attributes_checksum_data: nil) @content = Nanoc::Int::Content.create(content) @attributes = Nanoc::Int::LazyValue.new(attributes).map(&:__nanoc_symbolize_keys_recursively) @identifier = Nanoc::Identifier.from(identifier) @checksum_data = checksum_data @content_checksum_data = content_checksum_data @attributes_checksum_data = attributes_checksum_data end contract C::None => self # @return [void] def freeze super @content.freeze @attributes.freeze self end contract String => self def with_identifier_prefix(prefix) other = dup other.identifier = @identifier.prefix(prefix) other end contract C::None => String # @abstract # # @return Unique reference to this object def reference raise NotImplementedError end contract C::None => String def inspect "<#{self.class} identifier=\"#{identifier}\">" end contract C::None => C::Num def hash self.class.hash ^ identifier.hash end contract C::Any => C::Bool def ==(other) other.respond_to?(:identifier) && identifier == other.identifier end contract C::Any => C::Bool def eql?(other) other.is_a?(self.class) && identifier == other.identifier end end end end nanoc-4.8.0/lib/nanoc/base/entities/processing_action.rb0000644000004100000410000000075713134141051023313 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int class ProcessingAction def serialize raise NotImplementedError.new('Nanoc::ProcessingAction subclasses must implement #serialize and #to_s') end def to_s raise NotImplementedError.new('Nanoc::ProcessingAction subclasses must implement #serialize and #to_s') end def inspect format( '<%s %s>', self.class.to_s, serialize[1..-1].map(&:inspect).join(', '), ) end end end nanoc-4.8.0/lib/nanoc/base/entities/outdatedness_reasons.rb0000644000004100000410000000525413134141051024033 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int # Module that contains all outdatedness reasons. # # @api private module OutdatednessReasons # A generic outdatedness reason. An outdatedness reason is basically a # descriptive message that explains why a given object is outdated. class Generic # @return [String] A descriptive message for this outdatedness reason attr_reader :message # @return [Nanoc::Int::Props] attr_reader :props # @param [String] message The descriptive message for this outdatedness # reason def initialize(message, props = Nanoc::Int::Props.new) @message = message @props = props end end CodeSnippetsModified = Generic.new( 'The code snippets have been modified since the last time the site was compiled.', Props.new(raw_content: true, attributes: true, compiled_content: true, path: true), ) DependenciesOutdated = Generic.new( 'This item uses content or attributes that have changed since the last time the site was compiled.', ) NotWritten = Generic.new( 'This item representation has not yet been written to the output directory (but it does have a path).', Props.new(raw_content: true, attributes: true, compiled_content: true, path: true), ) RulesModified = Generic.new( 'The rules file has been modified since the last time the site was compiled.', Props.new(compiled_content: true, path: true), ) ContentModified = Generic.new( 'The content of this item has been modified since the last time the site was compiled.', Props.new(raw_content: true, compiled_content: true), ) class DocumentCollectionExtended < Generic attr_reader :objects def initialize(objects) super( 'New items/layouts have been added to the site.', Props.new(raw_content: true), ) @objects = objects end end class ItemCollectionExtended < DocumentCollectionExtended end class LayoutCollectionExtended < DocumentCollectionExtended end class AttributesModified < Generic attr_reader :attributes def initialize(attributes) super( 'The attributes of this item have been modified since the last time the site was compiled.', Props.new(attributes: true, compiled_content: true), ) @attributes = attributes end end UsesAlwaysOutdatedFilter = Generic.new( 'This item rep uses one or more filters that cannot track dependencies, and will thus always be considered as outdated.', Props.new(raw_content: true, attributes: true, compiled_content: true), ) end end nanoc-4.8.0/lib/nanoc/base/entities/site.rb0000644000004100000410000000307713134141051020544 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int # @api private class Site include Nanoc::Int::ContractsSupport attr_reader :code_snippets attr_reader :config attr_accessor :data_source attr_accessor :compiler contract C::KeywordArgs[config: Nanoc::Int::Configuration, code_snippets: C::IterOf[Nanoc::Int::CodeSnippet], data_source: C::Maybe[C::Named['Nanoc::DataSource']]] => C::Any def initialize(config:, code_snippets:, data_source:) @config = config @code_snippets = code_snippets @data_source = data_source ensure_identifier_uniqueness(@data_source.items, 'item') ensure_identifier_uniqueness(@data_source.layouts, 'layout') end contract C::None => self def compile compiler.run_all self end contract C::None => C::Named['Nanoc::Int::Compiler'] def compiler @compiler ||= Nanoc::Int::CompilerLoader.new.load(self) end def items @data_source.items end def layouts @data_source.layouts end contract C::None => self def freeze config.freeze items.freeze layouts.freeze code_snippets.__nanoc_freeze_recursively self end contract C::IterOf[C::Or[Nanoc::Int::Item, Nanoc::Int::Layout]], String => self def ensure_identifier_uniqueness(objects, type) seen = Set.new objects.each do |obj| if seen.include?(obj.identifier) raise Nanoc::Int::Errors::DuplicateIdentifier.new(obj.identifier, type) end seen << obj.identifier end self end end end nanoc-4.8.0/lib/nanoc/base/entities/snapshot_def.rb0000644000004100000410000000062713134141051022253 0ustar www-datawww-data# frozen_string_literal: true module Nanoc module Int class SnapshotDef include Nanoc::Int::ContractsSupport attr_reader :name attr_reader :binary contract Symbol, C::KeywordArgs[binary: C::Optional[C::Bool]] => C::Any def initialize(name, binary:) @name = name @binary = binary end def binary? @binary end end end end nanoc-4.8.0/lib/nanoc/base/entities/identifier.rb0000644000004100000410000001104513134141051021714 0ustar www-datawww-data# frozen_string_literal: true module Nanoc class Identifier include Comparable include Nanoc::Int::ContractsSupport # @api private class InvalidIdentifierError < ::Nanoc::Error def initialize(string) super("Invalid identifier (does not start with a slash): #{string.inspect}") end end # @api private class InvalidTypeError < ::Nanoc::Error def initialize(type) super("Invalid type for identifier: #{type.inspect} (can be :full or :legacy)") end end # @api private class InvalidPrefixError < ::Nanoc::Error def initialize(string) super("Invalid prefix (does not start with a slash): #{string.inspect}") end end # @api private class UnsupportedLegacyOperationError < ::Nanoc::Error def initialize super('Cannot use this method on legacy identifiers') end end # @api private class NonCoercibleObjectError < ::Nanoc::Error def initialize(obj) super("#{obj.inspect} cannot be converted into a Nanoc::Identifier") end end contract C::Any => self def self.from(obj) case obj when Nanoc::Identifier obj when String Nanoc::Identifier.new(obj) else raise NonCoercibleObjectError.new(obj) end end contract String, C::KeywordArgs[type: C::Optional[Symbol]] => C::Any def initialize(string, type: :full) @type = type case @type when :legacy @string = "/#{string}/".gsub(/^\/+|\/+$/, '/').freeze when :full if string !~ /\A\// raise InvalidIdentifierError.new(string) end @string = string.dup.freeze else raise InvalidTypeError.new(@type) end end contract C::Any => C::Bool def ==(other) case other when Nanoc::Identifier, String to_s == other.to_s else false end end contract C::Any => C::Bool def eql?(other) other.is_a?(self.class) && to_s == other.to_s end contract C::None => C::Num def hash self.class.hash ^ to_s.hash end contract C::Any => C::Maybe[C::Num] def =~(other) Nanoc::Int::Pattern.from(other).match?(to_s) ? 0 : nil end contract C::Any => C::Num def <=>(other) to_s <=> other.to_s end contract C::None => C::Bool # Whether or not this is a full identifier (i.e.includes the extension). def full? @type == :full end contract C::None => C::Bool # Whether or not this is a legacy identifier (i.e. does not include the extension). def legacy? @type == :legacy end contract C::None => String # @return [String] def chop to_s.chop end contract String => String # @return [String] def +(other) to_s + other end contract String => self # @return [Nanoc::Identifier] def prefix(string) if string !~ /\A\// raise InvalidPrefixError.new(string) end Nanoc::Identifier.new(string.sub(/\/+\z/, '') + @string, type: @type) end contract C::None => String # The identifier, as string, with the last extension removed def without_ext unless full? raise UnsupportedLegacyOperationError end extname = File.extname(@string) if !extname.empty? @string[0..-extname.size - 1] else @string end end contract C::None => C::Maybe[String] # The extension, without a leading dot def ext unless full? raise UnsupportedLegacyOperationError end s = File.extname(@string) s && s[1..-1] end contract C::None => String # The identifier, as string, with all extensions removed def without_exts extname = exts.join('.') if !extname.empty? @string[0..-extname.size - 2] else @string end end contract C::None => C::ArrayOf[String] # The list of extensions, without a leading dot def exts unless full? raise UnsupportedLegacyOperationError end s = File.basename(@string) s ? s.split('.', -1).drop(1) : [] end contract C::None => C::ArrayOf[String] def components res = to_s.split('/') if res.empty? [] else res[1..-1] end end contract C::None => String def to_s @string end contract C::None => String def to_str @string end contract C::None => String def inspect "" end end end nanoc-4.8.0/lib/nanoc/base/entities/layout.rb0000644000004100000410000000025313134141051021106 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int # @api private class Layout < ::Nanoc::Int::Document def reference "layout:#{identifier}" end end end nanoc-4.8.0/lib/nanoc/base/entities/action_sequence.rb0000644000004100000410000000355613134141051022747 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int class ActionSequence include Nanoc::Int::ContractsSupport include Enumerable extend Nanoc::Int::Memoization attr_reader :item_rep attr_reader :actions def initialize(item_rep, actions: []) @item_rep = item_rep @actions = actions end def self.build(rep) builder = Nanoc::Int::ActionSequenceBuilder.new(rep) yield(builder) builder.action_sequence end contract C::None => Numeric def size @actions.size end contract Numeric => C::Maybe[Nanoc::Int::ProcessingAction] def [](idx) @actions[idx] end contract C::None => C::ArrayOf[Nanoc::Int::ProcessingAction] def snapshot_actions @actions.select { |a| a.is_a?(Nanoc::Int::ProcessingActions::Snapshot) } end contract C::None => Array def paths snapshot_actions.map { |a| [a.snapshot_names, a.paths] } end # TODO: Add contract memoized def serialize to_a.map(&:serialize) end contract C::Func[Nanoc::Int::ProcessingAction => C::Any] => self def each @actions.each { |a| yield(a) } self end contract C::Func[Nanoc::Int::ProcessingAction => C::Any] => self def map self.class.new( @item_rep, actions: @actions.map { |a| yield(a) }, ) end def snapshots_defs is_binary = @item_rep.item.content.binary? snapshot_defs = [] each do |action| case action when Nanoc::Int::ProcessingActions::Snapshot action.snapshot_names.each do |snapshot_name| snapshot_defs << Nanoc::Int::SnapshotDef.new(snapshot_name, binary: is_binary) end when Nanoc::Int::ProcessingActions::Filter is_binary = Nanoc::Filter.named!(action.filter_name).to_binary? end end snapshot_defs end end end nanoc-4.8.0/lib/nanoc/base/entities/item_rep.rb0000644000004100000410000000453613134141051021405 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int # @api private class ItemRep include Nanoc::Int::ContractsSupport contract C::None => C::Bool attr_accessor :compiled alias compiled? compiled contract C::None => C::HashOf[Symbol => C::IterOf[String]] attr_reader :raw_paths contract C::None => C::HashOf[Symbol => C::IterOf[String]] attr_reader :paths contract C::None => Nanoc::Int::Item attr_reader :item contract C::None => Symbol attr_reader :name contract C::None => C::IterOf[C::Named['Nanoc::Int::SnapshotDef']] attr_accessor :snapshot_defs contract C::None => C::Bool attr_accessor :modified alias modified? modified contract Nanoc::Int::Item, Symbol => C::Any def initialize(item, name) # Set primary attributes @item = item @name = name # Set default attributes @raw_paths = {} @paths = {} @snapshot_defs = [] # Reset flags @compiled = false @modified = false end contract C::HashOf[Symbol => C::IterOf[String]] => self def raw_paths=(val) @raw_paths = val self end contract C::HashOf[Symbol => C::IterOf[String]] => self def paths=(val) @paths = val self end contract Symbol => C::Bool def snapshot?(name) snapshot_defs.any? { |sd| sd.name == name } end contract C::KeywordArgs[snapshot: C::Optional[Symbol]] => C::Maybe[String] # Returns the item rep’s raw path. It includes the path to the output # directory and the full filename. def raw_path(snapshot: :last) @raw_paths.fetch(snapshot, []).first end contract C::KeywordArgs[snapshot: C::Optional[Symbol]] => C::Maybe[String] # Returns the item rep’s path, as used when being linked to. It starts # with a slash and it is relative to the output directory. It does not # include the path to the output directory. It will not include the # filename if the filename is an index filename. def path(snapshot: :last) @paths.fetch(snapshot, []).first end # Returns an object that can be used for uniquely identifying objects. def reference "item_rep:#{item.identifier}:#{name}" end def inspect "<#{self.class} name=\"#{name}\" raw_path=\"#{raw_path}\" item.identifier=\"#{item.identifier}\">" end end end nanoc-4.8.0/lib/nanoc/base/entities/layout_collection.rb0000644000004100000410000000041513134141051023321 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int # @api private class LayoutCollection < IdentifiableCollection def initialize(config, objects = []) initialize_basic(config, objects, 'layouts') end def reference 'layouts' end end end nanoc-4.8.0/lib/nanoc/base/entities/processing_actions.rb0000644000004100000410000000025013134141051023462 0ustar www-datawww-data# frozen_string_literal: true require_relative 'processing_actions/filter' require_relative 'processing_actions/layout' require_relative 'processing_actions/snapshot' nanoc-4.8.0/lib/nanoc/base/entities/directed_graph.rb0000644000004100000410000001360113134141051022536 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int # Represents a directed graph. It is used by the dependency tracker for # storing and querying dependencies between items. # # @example Creating and using a directed graph # # # Create a graph with three vertices # graph = Nanoc::Int::DirectedGraph.new(%w( a b c d e )) # # # Add edges # graph.add_edge('a', 'b') # graph.add_edge('b', 'c') # graph.add_edge('c', 'd') # graph.add_edge('b', 'e') # # # Get (direct) successors # graph.direct_successors_of('a').sort # # => %w( b ) # graph.successors_of('a').sort # # => %w( b c d e ) # # # Modify edges # graph.delete_edges_to('c') # # # Get (direct) successors again # graph.direct_successors_of('a').sort # # => %w( b ) # graph.successors_of('a').sort # # => %w( b e ) # # @api private class DirectedGraph # @group Creating a graph # Creates a new directed graph with the given vertices. def initialize(vertices) @vertices = {} @next_vertex_idx = 0 vertices.each do |v| @vertices[v] = @next_vertex_idx.tap { @next_vertex_idx += 1 } end @from_graph = {} @to_graph = {} @edge_props = {} invalidate_caches end def inspect s = [] @vertices.each_pair do |v1, _| direct_successors_of(v1).each do |v2| s << [v1.inspect + ' -> ' + v2.inspect + ' props=' + @edge_props[[v1, v2]].inspect] end end self.class.to_s + '(' + s.join(', ') + ')' end # @group Modifying the graph # Adds an edge from the first vertex to the second vertex. # # @param from Vertex where the edge should start # # @param to Vertex where the edge should end # # @return [void] def add_edge(from, to, props: nil) add_vertex(from) add_vertex(to) @from_graph[from] ||= Set.new @from_graph[from] << to @to_graph[to] ||= Set.new @to_graph[to] << from if props @edge_props[[from, to]] = props end invalidate_caches end # Adds the given vertex to the graph. # # @param v The vertex to add to the graph # # @return [void] def add_vertex(v) return if @vertices.key?(v) @vertices[v] = @next_vertex_idx.tap { @next_vertex_idx += 1 } end # Deletes all edges going to the given vertex. # # @param to Vertex to which all edges should be removed # # @return [void] def delete_edges_to(to) return if @to_graph[to].nil? @to_graph[to].each do |from| @from_graph[from].delete(to) @edge_props.delete([from, to]) end @to_graph.delete(to) invalidate_caches end # @group Querying the graph # Returns the direct predecessors of the given vertex, i.e. the vertices # x where there is an edge from x to the given vertex y. # # @param to The vertex of which the predecessors should be calculated # # @return [Array] Direct predecessors of the given vertex def direct_predecessors_of(to) @to_graph[to].to_a end # Returns the direct successors of the given vertex, i.e. the vertices y # where there is an edge from the given vertex x to y. # # @param from The vertex of which the successors should be calculated # # @return [Array] Direct successors of the given vertex def direct_successors_of(from) @from_graph[from].to_a end # Returns the predecessors of the given vertex, i.e. the vertices x for # which there is a path from x to the given vertex y. # # @param to The vertex of which the predecessors should be calculated # # @return [Array] Predecessors of the given vertex def predecessors_of(to) @predecessors[to] ||= recursively_find_vertices(to, :direct_predecessors_of) end # Returns the successors of the given vertex, i.e. the vertices y for # which there is a path from the given vertex x to y. # # @param from The vertex of which the successors should be calculated # # @return [Array] Successors of the given vertex def successors_of(from) @successors[from] ||= recursively_find_vertices(from, :direct_successors_of) end def props_for(from, to) @edge_props[[from, to]] end # @return [Array] The list of all vertices in this graph. def vertices @vertices.keys.sort_by { |v| @vertices[v] } end # Returns an array of tuples representing the edges. The result of this # method may take a while to compute and should be cached if possible. # # @return [Array] The list of all edges in this graph. def edges result = [] @vertices.each_pair do |v1, i1| direct_successors_of(v1).map { |v2| [@vertices[v2], v2] }.each do |i2, v2| result << [i1, i2, @edge_props[[v1, v2]]] end end result end private # Invalidates cached data. This method should be called when the internal # graph representation is changed. def invalidate_caches @predecessors = {} @successors = {} end # Recursively finds vertices, starting at the vertex start, using the # given method, which should be a symbol to a method that takes a vertex # and returns related vertices (e.g. predecessors, successors). def recursively_find_vertices(start, method) all_vertices = Set.new processed_vertices = Set.new unprocessed_vertices = [start] until unprocessed_vertices.empty? # Get next unprocessed vertex vertex = unprocessed_vertices.pop next if processed_vertices.include?(vertex) processed_vertices << vertex # Add predecessors of this vertex send(method, vertex).each do |v| all_vertices << v unless all_vertices.include?(v) unprocessed_vertices << v end end all_vertices.to_a end end end nanoc-4.8.0/lib/nanoc/base/entities/content.rb0000644000004100000410000000544713134141051021255 0ustar www-datawww-data# frozen_string_literal: true module Nanoc module Int # Abstract content. # # The filename is the full filename on the default filesystem. It can be # nil. It is used by filters such as Sass, which look up items on the # filesystem. # # @abstract # # @api private class Content include Nanoc::Int::ContractsSupport # @return [String, nil] attr_reader :filename contract C::Maybe[String] => C::Any # @param [String, nil] filename def initialize(filename) if filename && Pathname.new(filename).relative? raise ArgumentError, 'Content filename is not absolute' end @filename = filename end contract C::None => self def freeze super @filename.freeze self end contract C::Or[Nanoc::Int::Content, String, Proc], C::KeywordArgs[binary: C::Optional[C::Bool], filename: C::Optional[C::Maybe[String]]] => self # @param [Nanoc::Int::Content, String, Proc] content The uncompiled item # content (if it is textual content) or the path to the filename # containing the content (if this is binary content). # # @param [Boolean] binary Whether or not this item is binary # # @param [String] filename Absolute path to the file containing this # content (if any) def self.create(content, binary: false, filename: nil) if content.nil? raise ArgumentError, 'Cannot create nil content' elsif content.is_a?(Nanoc::Int::Content) content elsif binary Nanoc::Int::BinaryContent.new(content) else Nanoc::Int::TextualContent.new(content, filename: filename) end end # @abstract # # @return [Boolean] def binary? raise NotImplementedError end end # @api private class TextualContent < Content contract C::None => String # @return [String] def string @string.value end contract C::Or[String, Proc], C::KeywordArgs[filename: C::Optional[C::Maybe[String]]] => C::Any def initialize(string, filename: nil) super(filename) @string = Nanoc::Int::LazyValue.new(string) end contract C::None => self def freeze super @string.freeze self end contract C::None => C::Bool def binary? false end # TODO: Add contract def marshal_dump [filename, string] end # TODO: Add contract def marshal_load(array) @filename = array[0] @string = Nanoc::Int::LazyValue.new(array[1]) end end # @api private class BinaryContent < Content contract C::None => C::Bool def binary? true end end end end nanoc-4.8.0/lib/nanoc/base/entities/processing_actions/0000755000004100000410000000000013134141051023140 5ustar www-datawww-datananoc-4.8.0/lib/nanoc/base/entities/processing_actions/snapshot.rb0000644000004100000410000000167013134141051025330 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int::ProcessingActions class Snapshot < Nanoc::Int::ProcessingAction # snapshot :before_layout # snapshot :before_layout, path: '/about.md' include Nanoc::Int::ContractsSupport attr_reader :snapshot_names attr_reader :paths contract C::IterOf[Symbol], C::IterOf[String] => C::Any def initialize(snapshot_names, paths) @snapshot_names = snapshot_names @paths = paths end contract C::None => Array def serialize [:snapshot, @snapshot_names, true, @paths] end contract C::KeywordArgs[snapshot_names: C::Optional[C::IterOf[Symbol]], paths: C::Optional[C::IterOf[String]]] => self def update(snapshot_names: [], paths: []) self.class.new(@snapshot_names + snapshot_names, @paths + paths) end contract C::None => String def to_s "snapshot #{@snapshot_names.inspect}, paths: #{@paths.inspect}" end end end nanoc-4.8.0/lib/nanoc/base/entities/processing_actions/filter.rb0000644000004100000410000000077513134141051024763 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int::ProcessingActions class Filter < Nanoc::Int::ProcessingAction # filter :foo # filter :foo, params attr_reader :filter_name attr_reader :params def initialize(filter_name, params) @filter_name = filter_name @params = params end def serialize [:filter, @filter_name, Nanoc::Int::Checksummer.calc(@params)] end def to_s "filter #{@filter_name.inspect}, #{@params.inspect}" end end end nanoc-4.8.0/lib/nanoc/base/entities/processing_actions/layout.rb0000644000004100000410000000105013134141051024776 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int::ProcessingActions class Layout < Nanoc::Int::ProcessingAction # layout '/foo.erb' # layout '/foo.erb', params attr_reader :layout_identifier attr_reader :params def initialize(layout_identifier, params) @layout_identifier = layout_identifier @params = params end def serialize [:layout, @layout_identifier, Nanoc::Int::Checksummer.calc(@params)] end def to_s "layout #{@layout_identifier.inspect}, #{@params.inspect}" end end end nanoc-4.8.0/lib/nanoc/base/entities/item_collection.rb0000644000004100000410000000040713134141051022743 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int # @api private class ItemCollection < IdentifiableCollection def initialize(config, objects = []) initialize_basic(config, objects, 'items') end def reference 'items' end end end nanoc-4.8.0/lib/nanoc/base/entities/checksum_collection.rb0000644000004100000410000000137513134141051023614 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int class ChecksumCollection include Nanoc::Int::ContractsSupport extend Nanoc::Int::Memoization c_obj = C::Or[Nanoc::Int::Item, Nanoc::Int::Layout, Nanoc::Int::Configuration, Nanoc::Int::CodeSnippet] def initialize(checksums) @checksums = checksums end contract c_obj => C::Maybe[String] def checksum_for(obj) @checksums[obj.reference] end contract c_obj => C::Maybe[String] def content_checksum_for(obj) @checksums[[obj.reference, :content]] end contract c_obj => C::Maybe[C::HashOf[Symbol, String]] def attributes_checksum_for(obj) @checksums[[obj.reference, :each_attribute]] end def to_h @checksums end end end nanoc-4.8.0/lib/nanoc/base/services/0000755000004100000410000000000013134141051017243 5ustar www-datawww-datananoc-4.8.0/lib/nanoc/base/services/outdatedness_rules.rb0000644000004100000410000000107713134141051023511 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int # @api private module OutdatednessRules end end require_relative 'outdatedness_rules/attributes_modified' require_relative 'outdatedness_rules/code_snippets_modified' require_relative 'outdatedness_rules/content_modified' require_relative 'outdatedness_rules/not_written' require_relative 'outdatedness_rules/rules_modified' require_relative 'outdatedness_rules/uses_always_outdated_filter' require_relative 'outdatedness_rules/item_collection_extended' require_relative 'outdatedness_rules/layout_collection_extended' nanoc-4.8.0/lib/nanoc/base/services/action_sequence_builder.rb0000644000004100000410000000236113134141051024445 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int class ActionSequenceBuilder include Nanoc::Int::ContractsSupport def initialize(item_rep) @item_rep = item_rep @actions = [] end contract Symbol, Hash => self def add_filter(filter_name, params) @actions << Nanoc::Int::ProcessingActions::Filter.new(filter_name, params) self end contract String, C::Maybe[Hash] => self def add_layout(layout_identifier, params) @actions << Nanoc::Int::ProcessingActions::Layout.new(layout_identifier, params) self end contract Symbol, C::Maybe[String] => self def add_snapshot(snapshot_name, path) will_add_snapshot(snapshot_name) @actions << Nanoc::Int::ProcessingActions::Snapshot.new([snapshot_name], path ? [path] : []) self end contract C::None => Nanoc::Int::ActionSequence def action_sequence Nanoc::Int::ActionSequence.new(@item_rep, actions: @actions) end private def will_add_snapshot(name) @_snapshot_names ||= Set.new if @_snapshot_names.include?(name) raise Nanoc::Int::Errors::CannotCreateMultipleSnapshotsWithSameName.new(@item_rep, name) else @_snapshot_names << name end end end end nanoc-4.8.0/lib/nanoc/base/services/compiler/0000755000004100000410000000000013134141051021055 5ustar www-datawww-datananoc-4.8.0/lib/nanoc/base/services/compiler/stages.rb0000644000004100000410000000111213134141051022663 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int::Compiler::Stages end require_relative 'stages/calculate_checksums' require_relative 'stages/cleanup' require_relative 'stages/compile_reps' require_relative 'stages/determine_outdatedness' require_relative 'stages/prune' require_relative 'stages/preprocess' require_relative 'stages/load_stores' require_relative 'stages/forget_outdated_dependencies' require_relative 'stages/store_pre_compilation_state' require_relative 'stages/store_post_compilation_state' require_relative 'stages/postprocess' require_relative 'stages/build_reps' nanoc-4.8.0/lib/nanoc/base/services/compiler/phases/0000755000004100000410000000000013134141051022340 5ustar www-datawww-datananoc-4.8.0/lib/nanoc/base/services/compiler/phases/cache.rb0000644000004100000410000000246713134141051023741 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int::Compiler::Phases # Provides functionality for (re)calculating the content of an item rep, with caching or # outdatedness checking. Delegates to s::Recalculate if outdated or no cache available. class Cache < Abstract include Nanoc::Int::ContractsSupport def initialize(wrapped:, compiled_content_cache:, snapshot_repo:) super(wrapped: wrapped) @compiled_content_cache = compiled_content_cache @snapshot_repo = snapshot_repo end contract Nanoc::Int::ItemRep, C::KeywordArgs[is_outdated: C::Bool], C::Func[C::None => C::Any] => C::Any def run(rep, is_outdated:) if can_reuse_content_for_rep?(rep, is_outdated: is_outdated) Nanoc::Int::NotificationCenter.post(:cached_content_used, rep) @snapshot_repo.set_all(rep, @compiled_content_cache[rep]) else yield end rep.compiled = true @compiled_content_cache[rep] = @snapshot_repo.get_all(rep) end contract Nanoc::Int::ItemRep, C::KeywordArgs[is_outdated: C::Bool] => C::Bool def can_reuse_content_for_rep?(rep, is_outdated:) if is_outdated false else cache = @compiled_content_cache[rep] cache ? cache.none? { |_snapshot_name, content| content.binary? } : false end end end end nanoc-4.8.0/lib/nanoc/base/services/compiler/phases/resume.rb0000644000004100000410000000247613134141051024176 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int::Compiler::Phases # Provides functionality for suspending and resuming item rep compilation (using fibers). class Resume < Abstract include Nanoc::Int::ContractsSupport def initialize(wrapped:) super(wrapped: wrapped) end contract Nanoc::Int::ItemRep, C::KeywordArgs[is_outdated: C::Bool], C::Func[C::None => C::Any] => C::Any def run(rep, is_outdated:) fiber = fiber_for(rep, is_outdated: is_outdated) { yield } while fiber.alive? Nanoc::Int::NotificationCenter.post(:compilation_started, rep) res = fiber.resume case res when Nanoc::Int::Errors::UnmetDependency Nanoc::Int::NotificationCenter.post(:compilation_suspended, rep, res) raise(res) when Proc fiber.resume(res.call) else # TODO: raise end end Nanoc::Int::NotificationCenter.post(:compilation_ended, rep) end private contract Nanoc::Int::ItemRep, C::KeywordArgs[is_outdated: C::Bool], C::Func[C::None => C::Any] => Fiber def fiber_for(rep, is_outdated:) # rubocop:disable Lint/UnusedMethodArgument @fibers ||= {} @fibers[rep] ||= Fiber.new do yield @fibers.delete(rep) end @fibers[rep] end end end nanoc-4.8.0/lib/nanoc/base/services/compiler/phases/mark_done.rb0000644000004100000410000000101613134141051024622 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int::Compiler::Phases class MarkDone < Abstract include Nanoc::Int::ContractsSupport def initialize(wrapped:, outdatedness_store:) super(wrapped: wrapped) @outdatedness_store = outdatedness_store end contract Nanoc::Int::ItemRep, C::KeywordArgs[is_outdated: C::Bool], C::Func[C::None => C::Any] => C::Any def run(rep, is_outdated:) # rubocop:disable Lint/UnusedMethodArgument yield @outdatedness_store.remove(rep) end end end nanoc-4.8.0/lib/nanoc/base/services/compiler/phases/recalculate.rb0000644000004100000410000000317313134141051025155 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int::Compiler::Phases # Provides functionality for (re)calculating the content of an item rep, without caching or # outdatedness checking. class Recalculate < Abstract include Nanoc::Int::ContractsSupport def initialize(action_sequences:, dependency_store:, compilation_context:) super(wrapped: nil) @action_sequences = action_sequences @dependency_store = dependency_store @compilation_context = compilation_context end contract Nanoc::Int::ItemRep, C::KeywordArgs[is_outdated: C::Bool], C::Func[C::None => C::Any] => C::Any def run(rep, is_outdated:) # rubocop:disable Lint/UnusedMethodArgument dependency_tracker = Nanoc::Int::DependencyTracker.new(@dependency_store) dependency_tracker.enter(rep.item) executor = Nanoc::Int::Executor.new(rep, @compilation_context, dependency_tracker) @compilation_context.snapshot_repo.set(rep, :last, rep.item.content) actions = @action_sequences[rep] actions.each do |action| case action when Nanoc::Int::ProcessingActions::Filter executor.filter(action.filter_name, action.params) when Nanoc::Int::ProcessingActions::Layout executor.layout(action.layout_identifier, action.params) when Nanoc::Int::ProcessingActions::Snapshot action.snapshot_names.each do |snapshot_name| executor.snapshot(snapshot_name) end else raise Nanoc::Int::Errors::InternalInconsistency, "unknown action #{action.inspect}" end end ensure dependency_tracker.exit end end end nanoc-4.8.0/lib/nanoc/base/services/compiler/phases/write.rb0000644000004100000410000000103213134141051024013 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int::Compiler::Phases class Write < Abstract include Nanoc::Int::ContractsSupport def initialize(snapshot_repo:, wrapped:) super(wrapped: wrapped) @snapshot_repo = snapshot_repo end contract Nanoc::Int::ItemRep, C::KeywordArgs[is_outdated: C::Bool], C::Func[C::None => C::Any] => C::Any def run(rep, is_outdated:) # rubocop:disable Lint/UnusedMethodArgument yield Nanoc::Int::ItemRepWriter.new.write_all(rep, @snapshot_repo) end end end nanoc-4.8.0/lib/nanoc/base/services/compiler/phases/abstract.rb0000644000004100000410000000162613134141051024475 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int::Compiler::Phases class Abstract include Nanoc::Int::ContractsSupport def initialize(wrapped:) @wrapped = wrapped end def call(rep, is_outdated:) notify(:phase_started, rep) run(rep, is_outdated: is_outdated) do notify(:phase_yielded, rep) @wrapped.call(rep, is_outdated: is_outdated) notify(:phase_resumed, rep) end notify(:phase_ended, rep) rescue notify(:phase_aborted, rep) raise end contract Nanoc::Int::ItemRep, C::KeywordArgs[is_outdated: C::Bool], C::Func[C::None => C::Any] => C::Any def run(_rep, is_outdated:) # rubocop:disable Lint/UnusedMethodArgument raise NotImplementedError end private def notify(sym, rep) name = self.class.to_s.sub(/^.*::/, '') Nanoc::Int::NotificationCenter.post(sym, name, rep) end end end nanoc-4.8.0/lib/nanoc/base/services/compiler/phases.rb0000644000004100000410000000042713134141051022670 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int::Compiler::Phases end require_relative 'phases/abstract' require_relative 'phases/recalculate' require_relative 'phases/cache' require_relative 'phases/resume' require_relative 'phases/write' require_relative 'phases/mark_done' nanoc-4.8.0/lib/nanoc/base/services/compiler/stages/0000755000004100000410000000000013134141051022343 5ustar www-datawww-datananoc-4.8.0/lib/nanoc/base/services/compiler/stages/cleanup.rb0000644000004100000410000000173113134141051024321 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int::Compiler::Stages class Cleanup def initialize(config) @config = config end def run cleanup_temps(Nanoc::Filter::TMP_BINARY_ITEMS_DIR) cleanup_temps(Nanoc::Int::ItemRepWriter::TMP_TEXT_ITEMS_DIR) cleanup_unused_stores cleanup_old_stores end private def cleanup_temps(dir) Nanoc::Int::TempFilenameFactory.instance.cleanup(dir) end def cleanup_unused_stores used_paths = @config.output_dirs.map { |d| Nanoc::Int::Store.tmp_path_prefix(d) } all_paths = Dir.glob('tmp/nanoc/*') (all_paths - used_paths).each do |obsolete_path| FileUtils.rm_rf(obsolete_path) end end def cleanup_old_stores %w[checksums compiled_content dependencies outdatedness action_sequence].each do |fn| full_fn = File.join('tmp', fn) if File.file?(full_fn) FileUtils.rm_f(full_fn) end end end end end nanoc-4.8.0/lib/nanoc/base/services/compiler/stages/postprocess.rb0000644000004100000410000000057213134141051025260 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int::Compiler::Stages class Postprocess include Nanoc::Int::ContractsSupport def initialize(action_provider:, site:, reps:) @action_provider = action_provider @site = site @reps = reps end contract C::None => C::Any def run @action_provider.postprocess(@site, @reps) end end end nanoc-4.8.0/lib/nanoc/base/services/compiler/stages/build_reps.rb0000644000004100000410000000135613134141051025025 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int::Compiler::Stages class BuildReps def initialize(site:, action_provider:, reps:) @site = site @action_provider = action_provider @reps = reps end def run # FIXME: This also, as a side effect, generates the action sequences. :( # Better: let this stage return a mapping of reps onto (raw) paths *and* a mapping of objects # onto action sequences. builder = Nanoc::Int::ItemRepBuilder.new( @site, @action_provider, @reps ) action_sequences = builder.run @site.layouts.each do |layout| action_sequences[layout] = @action_provider.action_sequence_for(layout) end action_sequences end end end nanoc-4.8.0/lib/nanoc/base/services/compiler/stages/forget_outdated_dependencies.rb0000644000004100000410000000063213134141051030556 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int::Compiler::Stages class ForgetOutdatedDependencies include Nanoc::Int::ContractsSupport def initialize(dependency_store:) @dependency_store = dependency_store end contract C::IterOf[Nanoc::Int::Item] => C::Any def run(outdated_items) outdated_items.each { |i| @dependency_store.forget_dependencies_for(i) } end end end nanoc-4.8.0/lib/nanoc/base/services/compiler/stages/preprocess.rb0000644000004100000410000000137213134141051025060 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int::Compiler::Stages class Preprocess def initialize(action_provider:, site:, dependency_store:, checksum_store:) @action_provider = action_provider @site = site @dependency_store = dependency_store @checksum_store = checksum_store end def run if @action_provider.need_preprocessing? @site.data_source = Nanoc::Int::InMemDataSource.new(@site.items, @site.layouts) @action_provider.preprocess(@site) @dependency_store.items = @site.items @dependency_store.layouts = @site.layouts @checksum_store.objects = @site.items.to_a + @site.layouts.to_a + @site.code_snippets + [@site.config] end @site.freeze end end end nanoc-4.8.0/lib/nanoc/base/services/compiler/stages/calculate_checksums.rb0000644000004100000410000000213713134141051026675 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int::Compiler::Stages class CalculateChecksums def initialize(items:, layouts:, code_snippets:, config:) @items = items @layouts = layouts @code_snippets = code_snippets @config = config end def run checksums = {} [@items, @layouts].each do |documents| documents.each do |document| checksums[[document.reference, :content]] = Nanoc::Int::Checksummer.calc_for_content_of(document) checksums[[document.reference, :each_attribute]] = Nanoc::Int::Checksummer.calc_for_each_attribute_of(document) end end [@items, @layouts, @code_snippets].each do |objs| objs.each do |obj| checksums[obj.reference] = Nanoc::Int::Checksummer.calc(obj) end end checksums[@config.reference] = Nanoc::Int::Checksummer.calc(@config) checksums[[@config.reference, :each_attribute]] = Nanoc::Int::Checksummer.calc_for_each_attribute_of(@config) Nanoc::Int::ChecksumCollection.new(checksums) end end end nanoc-4.8.0/lib/nanoc/base/services/compiler/stages/store_post_compilation_state.rb0000644000004100000410000000050413134141051030666 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int::Compiler::Stages class StorePostCompilationState include Nanoc::Int::ContractsSupport def initialize(dependency_store:) @dependency_store = dependency_store end contract C::None => C::Any def run @dependency_store.store end end end nanoc-4.8.0/lib/nanoc/base/services/compiler/stages/store_pre_compilation_state.rb0000644000004100000410000000146413134141051030475 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int::Compiler::Stages class StorePreCompilationState include Nanoc::Int::ContractsSupport def initialize(reps:, layouts:, checksum_store:, action_sequence_store:, action_sequences:) @reps = reps @layouts = layouts @checksum_store = checksum_store @action_sequence_store = action_sequence_store @action_sequences = action_sequences end contract Nanoc::Int::ChecksumCollection => C::Any def run(checksums) # Calculate action sequence (@reps.to_a + @layouts.to_a).each do |obj| @action_sequence_store[obj] = @action_sequences[obj].serialize end @action_sequence_store.store # Set checksums @checksum_store.checksums = checksums.to_h @checksum_store.store end end end nanoc-4.8.0/lib/nanoc/base/services/compiler/stages/determine_outdatedness.rb0000644000004100000410000000135013134141051027425 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int::Compiler::Stages class DetermineOutdatedness include Nanoc::Int::ContractsSupport def initialize(reps:, outdatedness_checker:, outdatedness_store:) @reps = reps @outdatedness_checker = outdatedness_checker @outdatedness_store = outdatedness_store end contract C::None => C::Any def run outdated_reps_tmp = @reps.select do |r| @outdatedness_store.include?(r) || @outdatedness_checker.outdated?(r) end outdated_items = outdated_reps_tmp.map(&:item).uniq outdated_reps = Set.new(outdated_items.flat_map { |i| @reps[i] }) outdated_reps.each { |r| @outdatedness_store.add(r) } outdated_items end end end nanoc-4.8.0/lib/nanoc/base/services/compiler/stages/load_stores.rb0000644000004100000410000000131413134141051025205 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int::Compiler::Stages class LoadStores include Nanoc::Int::ContractsSupport def initialize(checksum_store:, compiled_content_cache:, dependency_store:, action_sequence_store:, outdatedness_store:) @checksum_store = checksum_store @compiled_content_cache = compiled_content_cache @dependency_store = dependency_store @action_sequence_store = action_sequence_store @outdatedness_store = outdatedness_store end contract C::None => C::Any def run @checksum_store.load @compiled_content_cache.load @dependency_store.load @action_sequence_store.load @outdatedness_store.load end end end nanoc-4.8.0/lib/nanoc/base/services/compiler/stages/compile_reps.rb0000644000004100000410000000400713134141051025352 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int::Compiler::Stages class CompileReps def initialize(outdatedness_store:, dependency_store:, action_sequences:, compilation_context:, compiled_content_cache:) @outdatedness_store = outdatedness_store @dependency_store = dependency_store @action_sequences = action_sequences @compilation_context = compilation_context @compiled_content_cache = compiled_content_cache end def run selector = Nanoc::Int::ItemRepSelector.new(@outdatedness_store.to_a) selector.each do |rep| handle_errors_while(rep) { compile_rep(rep, is_outdated: @outdatedness_store.include?(rep)) } end ensure @outdatedness_store.store @compiled_content_cache.store end private def handle_errors_while(item_rep) yield rescue => e raise Nanoc::Int::Errors::CompilationError.new(e, item_rep) end def compile_rep(rep, is_outdated:) item_rep_compiler.call(rep, is_outdated: is_outdated) end def item_rep_compiler @_item_rep_compiler ||= begin recalculate_phase = Nanoc::Int::Compiler::Phases::Recalculate.new( action_sequences: @action_sequences, dependency_store: @dependency_store, compilation_context: @compilation_context, ) cache_phase = Nanoc::Int::Compiler::Phases::Cache.new( compiled_content_cache: @compiled_content_cache, snapshot_repo: @compilation_context.snapshot_repo, wrapped: recalculate_phase, ) resume_phase = Nanoc::Int::Compiler::Phases::Resume.new( wrapped: cache_phase, ) write_phase = Nanoc::Int::Compiler::Phases::Write.new( snapshot_repo: @compilation_context.snapshot_repo, wrapped: resume_phase, ) mark_done_phase = Nanoc::Int::Compiler::Phases::MarkDone.new( wrapped: write_phase, outdatedness_store: @outdatedness_store, ) mark_done_phase end end end end nanoc-4.8.0/lib/nanoc/base/services/compiler/stages/prune.rb0000644000004100000410000000072213134141051024022 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int::Compiler::Stages class Prune def initialize(config:, reps:) @config = config @reps = reps end def run if @config[:prune][:auto_prune] Nanoc::Pruner.new(@config, @reps, exclude: prune_config_exclude).run end end private def prune_config @config[:prune] || {} end def prune_config_exclude prune_config[:exclude] || {} end end end nanoc-4.8.0/lib/nanoc/base/services/item_rep_selector.rb0000644000004100000410000000241213134141051023273 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int # Yields item reps to compile. # # @api private class ItemRepSelector def initialize(reps) @reps = reps end class MicroGraph def initialize(reps) @reps = Set.new(reps) @stack = [] end def next if @stack.any? @stack.last elsif @reps.any? @reps.each { |rep| break rep }.tap do |rep| @reps.delete(rep) @stack.push(rep) end else nil end end def mark_ok @stack.pop end def mark_failed(dep) if @stack.include?(dep) raise Nanoc::Int::Errors::DependencyCycle.new(@stack + [dep]) end @reps.delete(dep) @stack.push(dep) end end def each mg = MicroGraph.new(@reps) loop do rep = mg.next break if rep.nil? begin yield(rep) mg.mark_ok rescue => e actual_error = e.is_a?(Nanoc::Int::Errors::CompilationError) ? e.unwrap : e if actual_error.is_a?(Nanoc::Int::Errors::UnmetDependency) mg.mark_failed(actual_error.rep) else raise(e) end end end end end end nanoc-4.8.0/lib/nanoc/base/services/outdatedness_checker.rb0000644000004100000410000001611713134141051023764 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int # Responsible for determining whether an item or a layout is outdated. # # @api private class OutdatednessChecker class Basic extend Nanoc::Int::Memoization include Nanoc::Int::ContractsSupport Rules = Nanoc::Int::OutdatednessRules RULES_FOR_ITEM_REP = [ Rules::RulesModified, Rules::ContentModified, Rules::AttributesModified, Rules::NotWritten, Rules::CodeSnippetsModified, Rules::UsesAlwaysOutdatedFilter, ].freeze RULES_FOR_LAYOUT = [ Rules::RulesModified, Rules::ContentModified, Rules::AttributesModified, Rules::UsesAlwaysOutdatedFilter, ].freeze RULES_FOR_CONFIG = [ Rules::AttributesModified, ].freeze RULES_FOR_ITEM_COLLECTION = [ Rules::ItemCollectionExtended, ].freeze RULES_FOR_LAYOUT_COLLECTION = [ Rules::LayoutCollectionExtended, ].freeze C_OBJ_MAYBE_REP = C::Or[Nanoc::Int::Item, Nanoc::Int::ItemRep, Nanoc::Int::Configuration, Nanoc::Int::Layout, Nanoc::Int::ItemCollection, Nanoc::Int::LayoutCollection] contract C::KeywordArgs[outdatedness_checker: OutdatednessChecker, reps: Nanoc::Int::ItemRepRepo] => C::Any def initialize(outdatedness_checker:, reps:) @outdatedness_checker = outdatedness_checker @reps = reps end contract C_OBJ_MAYBE_REP => C::Maybe[OutdatednessStatus] memoized def outdatedness_status_for(obj) case obj when Nanoc::Int::ItemRep apply_rules(RULES_FOR_ITEM_REP, obj) when Nanoc::Int::Item apply_rules_multi(RULES_FOR_ITEM_REP, @reps[obj]) when Nanoc::Int::Layout apply_rules(RULES_FOR_LAYOUT, obj) when Nanoc::Int::Configuration apply_rules(RULES_FOR_CONFIG, obj) when Nanoc::Int::ItemCollection apply_rules(RULES_FOR_ITEM_COLLECTION, obj) when Nanoc::Int::LayoutCollection apply_rules(RULES_FOR_LAYOUT_COLLECTION, obj) else raise Nanoc::Int::Errors::InternalInconsistency, "do not know how to check outdatedness of #{obj.inspect}" end end private contract C::ArrayOf[Class], C_OBJ_MAYBE_REP, OutdatednessStatus => C::Maybe[OutdatednessStatus] def apply_rules(rules, obj, status = OutdatednessStatus.new) rules.inject(status) do |acc, rule| if !acc.useful_to_apply?(rule) acc else reason = rule.instance.call(obj, @outdatedness_checker) if reason acc.update(reason) else acc end end end end contract C::ArrayOf[Class], C::ArrayOf[C_OBJ_MAYBE_REP] => C::Maybe[OutdatednessStatus] def apply_rules_multi(rules, objs) objs.inject(OutdatednessStatus.new) { |acc, elem| apply_rules(rules, elem, acc) } end end extend Nanoc::Int::Memoization include Nanoc::Int::ContractsSupport attr_reader :checksum_store attr_reader :checksums attr_reader :dependency_store attr_reader :action_sequence_store attr_reader :action_sequences attr_reader :site Reasons = Nanoc::Int::OutdatednessReasons C_OBJ = C::Or[Nanoc::Int::Item, Nanoc::Int::ItemRep, Nanoc::Int::Configuration, Nanoc::Int::Layout, Nanoc::Int::ItemCollection] C_ITEM_OR_REP = C::Or[Nanoc::Int::Item, Nanoc::Int::ItemRep] C_ACTION_SEQUENCES = C::HashOf[C_OBJ => Nanoc::Int::ActionSequence] contract C::KeywordArgs[site: Nanoc::Int::Site, checksum_store: Nanoc::Int::ChecksumStore, checksums: Nanoc::Int::ChecksumCollection, dependency_store: Nanoc::Int::DependencyStore, action_sequence_store: Nanoc::Int::ActionSequenceStore, action_sequences: C_ACTION_SEQUENCES, reps: Nanoc::Int::ItemRepRepo] => C::Any def initialize(site:, checksum_store:, checksums:, dependency_store:, action_sequence_store:, action_sequences:, reps:) @site = site @checksum_store = checksum_store @checksums = checksums @dependency_store = dependency_store @action_sequence_store = action_sequence_store @action_sequences = action_sequences @reps = reps @objects_outdated_due_to_dependencies = {} end def action_sequence_for(rep) @action_sequences.fetch(rep) end contract C_OBJ => C::Bool def outdated?(obj) outdatedness_reasons_for(obj).any? end contract C_OBJ => C::IterOf[Reasons::Generic] def outdatedness_reasons_for(obj) reasons = basic.outdatedness_status_for(obj).reasons if reasons.any? reasons elsif outdated_due_to_dependencies?(obj) [Reasons::DependenciesOutdated] else [] end end private contract C::None => Basic def basic @_basic ||= Basic.new(outdatedness_checker: self, reps: @reps) end contract C_ITEM_OR_REP, Hamster::Set => C::Bool def outdated_due_to_dependencies?(obj, processed = Hamster::Set.new) # Convert from rep to item if necessary obj = obj.item if obj.is_a?(Nanoc::Int::ItemRep) # Get from cache if @objects_outdated_due_to_dependencies.key?(obj) return @objects_outdated_due_to_dependencies[obj] end # Check processed # Don’t return true; the false will be or’ed into a true if there # really is a dependency that is causing outdatedness. return false if processed.include?(obj) # Calculate is_outdated = dependency_store.dependencies_causing_outdatedness_of(obj).any? do |dep| dependency_causes_outdatedness?(dep) || (dep.props.compiled_content? && outdated_due_to_dependencies?(dep.from, processed.merge([obj]))) end # Cache @objects_outdated_due_to_dependencies[obj] = is_outdated # Done is_outdated end contract Nanoc::Int::Dependency => C::Bool def dependency_causes_outdatedness?(dependency) return true if dependency.from.nil? status = basic.outdatedness_status_for(dependency.from) active = status.props.active & dependency.props.active active.delete(:attributes) if attributes_unaffected?(status, dependency) active.delete(:raw_content) if raw_content_unaffected?(status, dependency) active.any? end def attributes_unaffected?(status, dependency) reason = status.reasons.find { |r| r.is_a?(Nanoc::Int::OutdatednessReasons::AttributesModified) } reason && dependency.props.attributes.is_a?(Enumerable) && (dependency.props.attributes & reason.attributes).empty? end def raw_content_unaffected?(status, dependency) reason = status.reasons.find { |r| r.is_a?(Nanoc::Int::OutdatednessReasons::DocumentCollectionExtended) } if reason.nil? false elsif !dependency.props.raw_content.is_a?(Enumerable) false else patterns = dependency.props.raw_content.map { |r| Nanoc::Int::Pattern.from(r) } patterns.none? { |pat| reason.objects.any? { |obj| pat.match?(obj.identifier) } } end end end end nanoc-4.8.0/lib/nanoc/base/services/outdatedness_rule.rb0000644000004100000410000000147013134141051023323 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int # @api private class OutdatednessRule include Nanoc::Int::ContractsSupport include Singleton def call(obj, outdatedness_checker) Nanoc::Int::NotificationCenter.post(:outdatedness_rule_started, self.class, obj) apply(obj, outdatedness_checker) ensure Nanoc::Int::NotificationCenter.post(:outdatedness_rule_ended, self.class, obj) end def apply(_obj, _outdatedness_checker) raise NotImplementedError.new('Nanoc::Int::OutdatednessRule subclasses must implement #apply') end contract C::None => String def inspect "#{self.class.name}(#{reason})" end def self.affects_props(*names) @affected_props = Set.new(names) end def self.affected_props @affected_props end end end nanoc-4.8.0/lib/nanoc/base/services/item_rep_builder.rb0000644000004100000410000000124213134141051023101 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int # @api private class ItemRepBuilder attr_reader :reps def initialize(site, action_provider, reps) @site = site @action_provider = action_provider @reps = reps end def run @site.items.each do |item| @action_provider.rep_names_for(item).each do |rep_name| @reps << Nanoc::Int::ItemRep.new(item, rep_name) end end action_sequences = Nanoc::Int::ItemRepRouter.new(@reps, @action_provider, @site).run @reps.each do |rep| rep.snapshot_defs = action_sequences[rep].snapshots_defs end action_sequences end end end nanoc-4.8.0/lib/nanoc/base/services/filter.rb0000644000004100000410000001471013134141051021060 0ustar www-datawww-data# frozen_string_literal: true module Nanoc # Nanoc::Filter is responsible for filtering items. It is the superclass # for all textual filters. # # A filter instance should only be used once. Filters should not be reused # since they store state. # # When creating a filter with a hash containing assigned variables, those # variables will be made available in the `@assigns` instance variable and # the {#assigns} method. The assigns itself will also be available as # instance variables and instance methods. # # @example Accessing assigns in different ways # # filter = SomeFilter.new({ :foo => 'bar' }) # filter.instance_eval { @assigns[:foo] } # # => 'bar' # filter.instance_eval { assigns[:foo] } # # => 'bar' # filter.instance_eval { @foo } # # => 'bar' # filter.instance_eval { foo } # # => 'bar' # # @abstract Subclass and override {#run} to implement a custom filter. class Filter < Nanoc::Int::Context # @api private TMP_BINARY_ITEMS_DIR = 'binary_items' extend DDPlugin::Plugin class << self def define(ident, &block) filter_class = Class.new(::Nanoc::Filter) { identifier(ident) } filter_class.send(:define_method, :run) do |content, params| instance_exec(content, params, &block) end end def named!(name) klass = named(name) raise Nanoc::Int::Errors::UnknownFilter.new(name) if klass.nil? klass end # Sets the new type for the filter. The type can be `:binary` (default) # or `:text`. The given argument can either be a symbol indicating both # “from” and “to” types, or a hash where the only key is the “from” type # and the only value is the “to” type. # # @example Specifying a text-to-text filter # # type :text # # @example Specifying a text-to-binary filter # # type :text => :binary # # @param [Symbol, Hash] arg The new type of this filter # # @return [void] def type(arg) if arg.is_a?(Hash) @from = arg.keys[0] @to = arg.values[0] else @from = arg @to = arg end end # @return [Boolean] True if this filter can be applied to binary item # representations, false otherwise # # @api private def from_binary? (@from || :text) == :binary end # @return [Boolean] True if this filter results in a binary item # representation, false otherwise # # @api private def to_binary? (@to || :text) == :binary end # @return [Boolean] # # @api private def always_outdated? @always_outdated || false end # Marks this filter as always causing the item rep to be outdated. This is useful for filters # that cannot do dependency tracking properly. # # @return [void] def always_outdated @always_outdated = true end # @overload requires(*requires) # Sets the required libraries for this filter. # @param [Array] requires A list of library names that are required # @return [void] # @overload requires # Returns the required libraries for this filter. # @return [Enumerable] This filter’s list of library names that are required def requires(*requires) if requires.any? @requires = requires else @requires || [] end end # Requires the filter’s required library if necessary. # # @return [void] # # @api private def setup @setup ||= begin requires.each { |r| require r } true end end end # Creates a new filter that has access to the given assigns. # # @param [Hash] hash A hash containing variables that should be made # available during filtering. # # @api private def initialize(hash = {}) @assigns = hash super end # Sets up the filter and runs the filter. This method passes its arguments # to {#run} unchanged and returns the return value from {#run}. # # @see #run # # @api private def setup_and_run(*args) self.class.setup run(*args) end # Runs the filter on the given content or filename. # # @abstract # # @param [String] content_or_filename The unprocessed content that should # be filtered (if the item is a textual item) or the path to the file # that should be filtered (if the item is a binary item) # # @param [Hash] params A hash containing parameters. Filter subclasses can # use these parameters to allow modifying the filter's behaviour. # # @return [String, void] If the filter output binary content, the return # value is undefined; if the filter outputs textual content, the return # value will be the filtered content. def run(content_or_filename, params = {}) # rubocop:disable Lint/UnusedMethodArgument raise NotImplementedError.new('Nanoc::Filter subclasses must implement #run') end # Returns a filename that is used to write data to. This method is only # used on binary items. When running a binary filter on a file, the # resulting file must end up in the location returned by this method. # # The returned filename will be absolute, so it is safe to change to # another directory inside the filter. # # @return [String] The output filename def output_filename @output_filename ||= Nanoc::Int::TempFilenameFactory.instance.create(TMP_BINARY_ITEMS_DIR) end # Returns the filename associated with the item that is being filtered. # It is in the format `item (rep )`. # # @return [String] The filename # # @api private def filename if @layout "layout #{@layout.identifier}" elsif @item "item #{@item.identifier} (rep #{@item_rep.name})" else '?' end end # @api private def on_main_fiber(&block) Fiber.yield(block) end # Creates a dependency from the item that is currently being filtered onto # the given collection of items. In other words, require the given items # to be compiled first before this items is processed. # # @return [void] def depend_on(items) items.flat_map(&:reps).flat_map(&:raw_path) end end end nanoc-4.8.0/lib/nanoc/base/services/item_rep_writer.rb0000644000004100000410000000345013134141051022772 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int # @api private class ItemRepWriter TMP_TEXT_ITEMS_DIR = 'text_items' def write_all(item_rep, snapshot_repo) written_paths = Set.new item_rep.snapshot_defs.map(&:name).each do |snapshot_name| write(item_rep, snapshot_repo, snapshot_name, written_paths) end end def write(item_rep, snapshot_repo, snapshot_name, written_paths) item_rep.raw_paths.fetch(snapshot_name, []).each do |raw_path| write_single(item_rep, snapshot_repo, snapshot_name, raw_path, written_paths) end end def write_single(item_rep, snapshot_repo, snapshot_name, raw_path, written_paths) # Don’t write twice # TODO: test written_paths behavior return if written_paths.include?(raw_path) written_paths << raw_path # Create parent directory FileUtils.mkdir_p(File.dirname(raw_path)) # Check if file will be created is_created = !File.file?(raw_path) # Notify Nanoc::Int::NotificationCenter.post( :will_write_rep, item_rep, raw_path ) content = snapshot_repo.get(item_rep, snapshot_name) if content.binary? temp_path = content.filename else temp_path = temp_filename File.write(temp_path, content.string) end # Check whether content was modified is_modified = is_created || !FileUtils.identical?(raw_path, temp_path) # Write FileUtils.cp(temp_path, raw_path) if is_modified item_rep.modified = is_modified # Notify Nanoc::Int::NotificationCenter.post( :rep_written, item_rep, content.binary?, raw_path, is_created, is_modified ) end def temp_filename Nanoc::Int::TempFilenameFactory.instance.create(TMP_TEXT_ITEMS_DIR) end end end nanoc-4.8.0/lib/nanoc/base/services/notification_center.rb0000644000004100000410000000540113134141051023616 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int # Provides a way to send notifications between objects. It allows blocks # associated with a certain notification name to be registered; these blocks # will be called when the notification with the given name is posted. # # It is a slightly different implementation of the Observer pattern; the # table of subscribers is not stored in the observable object itself, but in # the notification center. # # @api private class NotificationCenter class << self # Adds the given block to the list of blocks that should be called when # the notification with the given name is received. # # @param [String, Symbol] name The name of the notification that will # cause the given block to be called. # # @param [String, Symbol, nil] id An identifier for the block. This is # only used to be able to remove the block (using the remove method) # later. Can be nil, but this is not recommended because it prevents # the given notification block from being unregistered. # # @yield [*args] Will be executed with the arguments passed to {.post} # # @return [void] def on(name, id = nil, &block) initialize_if_necessary(name) # Add observer @notifications[name] << { id: id, block: block } end # Posts a notification with the given name and the given arguments. # # @param [String, Symbol] name The name of the notification that should # be posted. # # @param args Arguments that wil be passed to the blocks handling the # notification. # # @return [void] def post(name, *args) initialize_if_necessary(name) # Notify all observers @notifications[name].each do |observer| observer[:block].call(*args) end end # Removes the block with the given identifier from the list of blocks # that should be called when the notification with the given name is # posted. # # @param [String, Symbol] name The name of the notification that should # no longer be registered. # # @param [String, Symbol] id The identifier of the block that should be # removed. # # @return [void] def remove(name, id) initialize_if_necessary(name) # Remove relevant observers @notifications[name].reject! { |i| i[:id] == id } end # @api private # # @return [void] def reset @notifications = nil end private def initialize_if_necessary(name) @notifications ||= {} # name => observers dictionary @notifications[name] ||= [] # list of observers end end end end nanoc-4.8.0/lib/nanoc/base/services/compiler_loader.rb0000644000004100000410000000240413134141051022730 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int # @api private class CompilerLoader def load(site, action_provider: nil) action_sequence_store = Nanoc::Int::ActionSequenceStore.new(site: site) dependency_store = Nanoc::Int::DependencyStore.new(site.items, site.layouts, site.config, site: site) objects = site.items.to_a + site.layouts.to_a + site.code_snippets + [site.config] checksum_store = Nanoc::Int::ChecksumStore.new(site: site, objects: objects) item_rep_repo = Nanoc::Int::ItemRepRepo.new action_provider ||= Nanoc::Int::ActionProvider.named(:rule_dsl).for(site) outdatedness_store = Nanoc::Int::OutdatednessStore.new(site: site, reps: item_rep_repo) compiled_content_cache = Nanoc::Int::CompiledContentCache.new( site: site, items: site.items, ) params = { compiled_content_cache: compiled_content_cache, checksum_store: checksum_store, action_sequence_store: action_sequence_store, dependency_store: dependency_store, reps: item_rep_repo, action_provider: action_provider, outdatedness_store: outdatedness_store, } Nanoc::Int::Compiler.new(site, params) end end end nanoc-4.8.0/lib/nanoc/base/services/compiler.rb0000644000004100000410000001326313134141051021407 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int class Compiler include Nanoc::Int::ContractsSupport # @api private attr_reader :site # @api private attr_reader :compiled_content_cache # @api private attr_reader :checksum_store # @api private attr_reader :action_sequence_store # @api private attr_reader :action_provider # @api private attr_reader :dependency_store # @api private attr_reader :reps # @api private attr_reader :outdatedness_store # @api private attr_reader :snapshot_repo def initialize(site, compiled_content_cache:, checksum_store:, action_sequence_store:, action_provider:, dependency_store:, reps:, outdatedness_store:) @site = site @compiled_content_cache = compiled_content_cache @checksum_store = checksum_store @action_sequence_store = action_sequence_store @dependency_store = dependency_store @reps = reps @action_provider = action_provider @outdatedness_store = outdatedness_store # TODO: inject @snapshot_repo = Nanoc::Int::SnapshotRepo.new end def create_outdatedness_checker Nanoc::Int::OutdatednessChecker.new( site: @site, checksum_store: @checksum_store, dependency_store: @dependency_store, action_sequence_store: @action_sequence_store, action_sequences: @action_sequences, checksums: @checksums, reps: reps, ) end def run_all prepare run_stage(forget_outdated_dependencies_stage, @outdated_items) run_stage(store_pre_compilation_state_stage(@action_sequences), @checksums) run_stage(prune_stage) run_stage(compile_reps_stage(@action_sequences)) run_stage(store_post_compilation_state_stage) run_stage(postprocess_stage) ensure run_stage(cleanup_stage) end def prepare # FIXME: State is ugly run_stage(preprocess_stage) @action_sequences = run_stage(build_reps_stage) run_stage(load_stores_stage) @checksums = run_stage(calculate_checksums_stage) @outdated_items = run_stage(determine_outdatedness_stage) end def compilation_context @_compilation_context ||= Nanoc::Int::CompilationContext.new( action_provider: action_provider, reps: @reps, site: @site, compiled_content_cache: compiled_content_cache, snapshot_repo: snapshot_repo, ) end # TODO: remove def load_stores load_stores_stage.run end # TODO: remove def build_reps @action_sequences = build_reps_stage.run end # TODO: remove def calculate_checksums @checksums = run_stage(calculate_checksums_stage) end private def run_stage(stage, *args) Nanoc::Int::NotificationCenter.post(:stage_started, stage.class) stage.run(*args) ensure Nanoc::Int::NotificationCenter.post(:stage_ended, stage.class) end def preprocess_stage @_preprocess_stage ||= Stages::Preprocess.new( action_provider: action_provider, site: site, dependency_store: dependency_store, checksum_store: checksum_store, ) end def build_reps_stage @_build_reps_stage ||= Stages::BuildReps.new( site: site, action_provider: action_provider, reps: @reps, ) end def prune_stage @_prune_stage ||= Stages::Prune.new( config: site.config, reps: reps, ) end def load_stores_stage @_load_stores_stage ||= Stages::LoadStores.new( checksum_store: checksum_store, compiled_content_cache: compiled_content_cache, dependency_store: @dependency_store, action_sequence_store: action_sequence_store, outdatedness_store: @outdatedness_store, ) end def calculate_checksums_stage @_calculate_checksums_stage ||= Stages::CalculateChecksums.new( items: @site.items, layouts: @site.layouts, code_snippets: @site.code_snippets, config: @site.config, ) end def determine_outdatedness_stage @_determine_outdatedness_stage ||= Stages::DetermineOutdatedness.new( reps: reps, outdatedness_checker: create_outdatedness_checker, outdatedness_store: outdatedness_store, ) end def store_pre_compilation_state_stage(action_sequences) @_store_pre_compilation_state_stage ||= Stages::StorePreCompilationState.new( reps: @reps, layouts: site.layouts, checksum_store: checksum_store, action_sequence_store: action_sequence_store, action_sequences: action_sequences, ) end def compile_reps_stage(action_sequences) @_compile_reps_stage ||= Stages::CompileReps.new( outdatedness_store: @outdatedness_store, dependency_store: @dependency_store, action_sequences: action_sequences, compilation_context: compilation_context, compiled_content_cache: compiled_content_cache, ) end def store_post_compilation_state_stage @_store_post_compilation_state_stage ||= Stages::StorePostCompilationState.new( dependency_store: dependency_store, ) end def postprocess_stage @_postprocess_stage ||= Stages::Postprocess.new( action_provider: @action_provider, site: @site, reps: @reps, ) end def cleanup_stage @_cleanup_stage ||= Stages::Cleanup.new(site.config) end def forget_outdated_dependencies_stage @_forget_outdated_dependencies_stage ||= Stages::ForgetOutdatedDependencies.new( dependency_store: @dependency_store, ) end end end nanoc-4.8.0/lib/nanoc/base/services/item_rep_router.rb0000644000004100000410000000566613134141051023011 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int # Assigns paths to reps. # # @api private class ItemRepRouter include Nanoc::Int::ContractsSupport class IdenticalRoutesError < ::Nanoc::Error def initialize(output_path, rep_a, rep_b) super("The item representations #{rep_a.inspect} and #{rep_b.inspect} are both routed to #{output_path}.") end end class RouteWithoutSlashError < ::Nanoc::Error def initialize(output_path, rep) super("The item representation #{rep.inspect} is routed to #{output_path}, which does not start with a slash, as required.") end end def initialize(reps, action_provider, site) @reps = reps @action_provider = action_provider @site = site end def run action_sequences = {} assigned_paths = {} @reps.each do |rep| # Sigh. We route reps twice, because the first time, the paths might not have converged # yet. This isn’t ideal, but it’s the only way to work around the divergence issues that # I can think of. For details, see # https://github.com/nanoc/nanoc/pull/1085#issuecomment-280628426. @action_provider.action_sequence_for(rep).paths.each do |(snapshot_names, paths)| route_rep(rep, paths, snapshot_names, {}) end seq = @action_provider.action_sequence_for(rep) action_sequences[rep] = seq seq.paths.each do |(snapshot_names, paths)| route_rep(rep, paths, snapshot_names, assigned_paths) end # TODO: verify that paths converge end action_sequences end contract Nanoc::Int::ItemRep, C::IterOf[String], C::IterOf[Symbol], C::HashOf[String => Nanoc::Int::ItemRep] => C::Any def route_rep(rep, paths, snapshot_names, assigned_paths) # Encode paths = paths.map { |path| path.encode('UTF-8') } # Validate format paths.each do |path| unless path.start_with?('/') raise RouteWithoutSlashError.new(path, rep) end end # Validate uniqueness paths.each do |path| if assigned_paths.include?(path) # TODO: Include snapshot names in error message raise IdenticalRoutesError.new(path, assigned_paths[path], rep) end end paths.each do |path| assigned_paths[path] = rep end # Assign snapshot_names.each do |snapshot_name| rep.raw_paths[snapshot_name] = paths.map { |path| @site.config[:output_dir] + path } rep.paths[snapshot_name] = paths.map { |path| strip_index_filename(path) } end end contract String => String def strip_index_filename(basic_path) @site.config[:index_filenames].each do |index_filename| slashed_index_filename = '/' + index_filename if basic_path.end_with?(slashed_index_filename) return basic_path[0..-index_filename.length - 1] end end basic_path end end end nanoc-4.8.0/lib/nanoc/base/services/checksummer.rb0000644000004100000410000001521613134141051022103 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int # Creates checksums for given objects. # # A checksum is a string, such as “mL+TaqNsEeiPkWloPgCtAofT1yg=”, that is used # to determine whether a piece of data has changed. # # @api private class Checksummer class VerboseDigest def initialize @str = String.new end def update(str) @str << str end def to_s @str end end class CompactDigest def initialize @digest = Digest::SHA1.new end def update(str) @digest.update(str) end def to_s @digest.base64digest end end class << self # @param obj The object to create a checksum for # # @return [String] The digest def calc(obj, digest_class = CompactDigest) digest = digest_class.new update(obj, digest) digest.to_s end def calc_for_content_of(obj) obj.content_checksum_data || obj.checksum_data || Nanoc::Int::Checksummer.calc(obj.content) end def calc_for_each_attribute_of(obj, digest_class = CompactDigest) obj.attributes.each_with_object({}) do |(key, value), memo| memo[key] = Nanoc::Int::Checksummer.calc(value, digest_class) end end private def update(obj, digest, visited = Hamster::Set.new) digest.update(obj.class.to_s) if visited.include?(obj) digest.update('') else digest.update('<') behavior_for(obj).update(obj, digest) { |o| update(o, digest, visited.add(obj)) } digest.update('>') end end def behavior_for(obj) case obj when String, Symbol, Numeric RawUpdateBehavior when Pathname PathnameUpdateBehavior when Nanoc::Int::BinaryContent BinaryContentUpdateBehavior when Array, Nanoc::Int::IdentifiableCollection ArrayUpdateBehavior when Hash, Nanoc::Int::Configuration HashUpdateBehavior when Nanoc::Int::Item, Nanoc::Int::Layout DocumentUpdateBehavior when Nanoc::Int::ItemRep ItemRepUpdateBehavior when NilClass, TrueClass, FalseClass NoUpdateBehavior when Time ToIToSUpdateBehavior when Nanoc::Identifier ToSUpdateBehavior when Nanoc::RuleDSL::RulesCollection, Nanoc::Int::CodeSnippet DataUpdateBehavior when Nanoc::Int::TextualContent StringUpdateBehavior when Nanoc::View UnwrapUpdateBehavior when Nanoc::RuleDSL::RuleContext RuleContextUpdateBehavior when Nanoc::Int::Context ContextUpdateBehavior else RescueUpdateBehavior end end end class UpdateBehavior def self.update(_obj, _digest) raise NotImpementedError end end class RuleContextUpdateBehavior < UpdateBehavior def self.update(obj, digest) digest.update('item=') yield(obj.item) digest.update(',rep=') yield(obj.rep) digest.update(',items=') yield(obj.items) digest.update(',layouts=') yield(obj.layouts) digest.update(',config=') yield(obj.config) end end class ContextUpdateBehavior < UpdateBehavior def self.update(obj, digest) obj.instance_variables.each do |var| digest.update(var.to_s) digest.update('=') yield(obj.instance_variable_get(var)) digest.update(',') end end end class RawUpdateBehavior < UpdateBehavior def self.update(obj, digest) digest.update(obj.to_s) end end class ToSUpdateBehavior < UpdateBehavior def self.update(obj, _digest) yield(obj.to_s) end end class ToIToSUpdateBehavior < UpdateBehavior def self.update(obj, digest) digest.update(obj.to_i.to_s) end end class StringUpdateBehavior < UpdateBehavior def self.update(obj, _digest) yield(obj.string) end end class DataUpdateBehavior < UpdateBehavior def self.update(obj, _digest) yield(obj.data) end end class NoUpdateBehavior < UpdateBehavior def self.update(_obj, _digest); end end class UnwrapUpdateBehavior < UpdateBehavior def self.update(obj, _digest) yield(obj.unwrap) end end class ArrayUpdateBehavior < UpdateBehavior def self.update(obj, digest) obj.each do |el| yield(el) digest.update(',') end end end class HashUpdateBehavior < UpdateBehavior def self.update(obj, digest) obj.each do |key, value| yield(key) digest.update('=') yield(value) digest.update(',') end end end class DocumentUpdateBehavior < UpdateBehavior def self.update(obj, digest) if obj.checksum_data digest.update('checksum_data=' + obj.checksum_data) else if obj.content_checksum_data digest.update('content_checksum_data=' + obj.content_checksum_data) else digest.update('content=') yield(obj.content) end if obj.attributes_checksum_data digest.update(',attributes_checksum_data=' + obj.attributes_checksum_data) else digest.update(',attributes=') yield(obj.attributes) end digest.update(',identifier=') yield(obj.identifier) end end end class ItemRepUpdateBehavior < UpdateBehavior def self.update(obj, digest) digest.update('item=') yield(obj.item) digest.update(',name=') yield(obj.name) end end class PathnameUpdateBehavior < UpdateBehavior def self.update(obj, digest) filename = obj.to_s if File.exist?(filename) stat = File.stat(filename) digest.update(stat.size.to_s + '-' + stat.mtime.to_i.to_s) else digest.update('???') end end end class BinaryContentUpdateBehavior < UpdateBehavior def self.update(obj, _digest) yield(Pathname.new(obj.filename)) end end class RescueUpdateBehavior < UpdateBehavior def self.update(obj, digest) if obj.class.to_s == 'Sass::Importers::Filesystem' digest.update('root=') digest.update(obj.root) return end data = begin Marshal.dump(obj) rescue obj.inspect end digest.update(data) end end end end nanoc-4.8.0/lib/nanoc/base/services/action_provider.rb0000644000004100000410000000104013134141051022752 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int # @private class ActionProvider extend DDPlugin::Plugin def self.for(_site) raise NotImplementedError end def rep_names_for(_item) raise NotImplementedError end def action_sequence_for(_rep) raise NotImplementedError end def need_preprocessing? raise NotImplementedError end def preprocess(_site) raise NotImplementedError end def postprocess(_site, _reps) raise NotImplementedError end end end nanoc-4.8.0/lib/nanoc/base/services/pruner.rb0000644000004100000410000000600513134141051021104 0ustar www-datawww-data# frozen_string_literal: true require 'find' module Nanoc # Responsible for finding and deleting files in the site’s output directory # that are not managed by Nanoc. # # @api private class Pruner # @param [Nanoc::Int::Configuration] config # # @param [Nanoc::Int::ItemRepRepo] reps # # @param [Boolean] dry_run true if the files to be deleted # should only be printed instead of actually deleted, false if the files # should actually be deleted. # # @param [Enumerable] exclude def initialize(config, reps, dry_run: false, exclude: []) @config = config @reps = reps @dry_run = dry_run @exclude = Set.new(exclude) end # Prunes all output files not managed by Nanoc. # # @return [void] def run return unless File.directory?(@config[:output_dir]) compiled_files = @reps.flat_map { |r| r.raw_paths.values.flatten }.compact present_files, present_dirs = files_and_dirs_in(@config[:output_dir] + '/') remove_stray_files(present_files, compiled_files) remove_empty_directories(present_dirs) end def exclude?(component) @exclude.include?(component) end # @param [String] filename The filename to check # # @return [Boolean] true if the given file is excluded, false otherwise def filename_excluded?(filename) pathname = Pathname.new(filename) @exclude.any? { |e| pathname_components(pathname).include?(e) } end def pathname_components(pathname) components = [] tmp = pathname loop do old = tmp components << File.basename(tmp) tmp = File.dirname(tmp) break if old == tmp end components.reverse end # @api private def remove_stray_files(present_files, compiled_files) (present_files - compiled_files).each do |f| delete_file(f) unless exclude?(f) end end # @api private def remove_empty_directories(present_dirs) present_dirs.reverse_each do |dir| next if Dir.foreach(dir) { |n| break true if n !~ /\A\.\.?\z/ } next if exclude?(dir) delete_dir(dir) end end # @api private def files_and_dirs_in(dir) present_files = [] present_dirs = [] Find.find(dir) do |f| basename = File.basename(f) case File.ftype(f) when 'file' unless exclude?(basename) present_files << f end when 'directory' if exclude?(basename) Find.prune else present_dirs << f end end end [present_files, present_dirs] end protected def delete_file(file) log_delete_and_run(file) { FileUtils.rm(file) } end def delete_dir(dir) log_delete_and_run(dir) { Dir.rmdir(dir) } end def log_delete_and_run(thing) if @dry_run puts thing else Nanoc::CLI::Logger.instance.file(:high, :delete, thing) yield end end end end nanoc-4.8.0/lib/nanoc/base/services/executor.rb0000644000004100000410000001074413134141051021434 0ustar www-datawww-data# frozen_string_literal: true module Nanoc module Int class Executor class OutputNotWrittenError < ::Nanoc::Error def initialize(filter_name, output_filename) super("The #{filter_name.inspect} filter did not write anything to the required output file, #{output_filename}.") end end def initialize(rep, compilation_context, dependency_tracker) @rep = rep @compilation_context = compilation_context @dependency_tracker = dependency_tracker end def filter(filter_name, filter_args = {}) filter = filter_for_filtering(@rep, filter_name) begin Nanoc::Int::NotificationCenter.post(:filtering_started, @rep, filter_name) # Run filter last = @compilation_context.snapshot_repo.get(@rep, :last) source = last.binary? ? last.filename : last.string filter_args.freeze result = filter.setup_and_run(source, filter_args) last = if filter.class.to_binary? Nanoc::Int::BinaryContent.new(filter.output_filename).tap(&:freeze) else Nanoc::Int::TextualContent.new(result).tap(&:freeze) end # Store @compilation_context.snapshot_repo.set(@rep, :last, last) # Check whether file was written if filter.class.to_binary? && !File.file?(filter.output_filename) raise OutputNotWrittenError.new(filter_name, filter.output_filename) end ensure Nanoc::Int::NotificationCenter.post(:filtering_ended, @rep, filter_name) end end def layout(layout_identifier, extra_filter_args = nil) layout = find_layout(layout_identifier) filter_name, filter_args = *@compilation_context.filter_name_and_args_for_layout(layout) if filter_name.nil? raise Nanoc::Int::Errors::Generic, "Cannot find rule for layout matching #{layout_identifier}" end filter_args = filter_args.merge(extra_filter_args || {}) filter_args.freeze # Check whether item can be laid out last = @compilation_context.snapshot_repo.get(@rep, :last) raise Nanoc::Int::Errors::CannotLayoutBinaryItem.new(@rep) if last.binary? # Create filter klass = Nanoc::Filter.named!(filter_name) view_context = @compilation_context.create_view_context(@dependency_tracker) layout_view = Nanoc::LayoutView.new(layout, view_context) filter = klass.new(assigns_for(@rep).merge(layout: layout_view)) # Visit @dependency_tracker.bounce(layout, raw_content: true) begin Nanoc::Int::NotificationCenter.post(:filtering_started, @rep, filter_name) # Layout content = layout.content arg = content.binary? ? content.filename : content.string res = filter.setup_and_run(arg, filter_args) # Store last = Nanoc::Int::TextualContent.new(res).tap(&:freeze) @compilation_context.snapshot_repo.set(@rep, :last, last) ensure Nanoc::Int::NotificationCenter.post(:filtering_ended, @rep, filter_name) end end def snapshot(snapshot_name) last = @compilation_context.snapshot_repo.get(@rep, :last) @compilation_context.snapshot_repo.set(@rep, snapshot_name, last) end def assigns_for(rep) @compilation_context.assigns_for(rep, @dependency_tracker) end def layouts @compilation_context.site.layouts end def find_layout(arg) req_id = arg.__nanoc_cleaned_identifier layout = layouts.find { |l| l.identifier == req_id } return layout if layout if use_globs? pat = Nanoc::Int::Pattern.from(arg) layout = layouts.find { |l| pat.match?(l.identifier) } return layout if layout end raise Nanoc::Int::Errors::UnknownLayout.new(arg) end def filter_for_filtering(rep, filter_name) klass = Nanoc::Filter.named!(filter_name) last = @compilation_context.snapshot_repo.get(@rep, :last) if klass.from_binary? && !last.binary? raise Nanoc::Int::Errors::CannotUseBinaryFilter.new(rep, klass) elsif !klass.from_binary? && last.binary? raise Nanoc::Int::Errors::CannotUseTextualFilter.new(rep, klass) end klass.new(assigns_for(rep)) end def use_globs? @compilation_context.site.config[:string_pattern_type] == 'glob' end end end end nanoc-4.8.0/lib/nanoc/base/services/outdatedness_rules/0000755000004100000410000000000013134141051023157 5ustar www-datawww-datananoc-4.8.0/lib/nanoc/base/services/outdatedness_rules/code_snippets_modified.rb0000644000004100000410000000137313134141051030207 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int::OutdatednessRules class CodeSnippetsModified < Nanoc::Int::OutdatednessRule extend Nanoc::Int::Memoization include Nanoc::Int::ContractsSupport affects_props :raw_content, :attributes, :compiled_content, :path def apply(_obj, outdatedness_checker) if any_snippets_modified?(outdatedness_checker) Nanoc::Int::OutdatednessReasons::CodeSnippetsModified end end private memoized def any_snippets_modified?(outdatedness_checker) outdatedness_checker.site.code_snippets.any? do |cs| ch_old = outdatedness_checker.checksum_store[cs] ch_new = outdatedness_checker.checksums.checksum_for(cs) ch_old != ch_new end end end end nanoc-4.8.0/lib/nanoc/base/services/outdatedness_rules/uses_always_outdated_filter.rb0000644000004100000410000000120313134141051031275 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int::OutdatednessRules class UsesAlwaysOutdatedFilter < Nanoc::Int::OutdatednessRule affects_props :raw_content, :attributes, :path def apply(obj, outdatedness_checker) seq = outdatedness_checker.action_sequence_for(obj) if any_always_outdated?(seq) Nanoc::Int::OutdatednessReasons::UsesAlwaysOutdatedFilter end end def any_always_outdated?(seq) seq .select { |a| a.is_a?(Nanoc::Int::ProcessingActions::Filter) } .map { |a| Nanoc::Filter.named(a.filter_name) } .compact .any?(&:always_outdated?) end end end nanoc-4.8.0/lib/nanoc/base/services/outdatedness_rules/item_collection_extended.rb0000644000004100000410000000103513134141051030534 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int::OutdatednessRules class ItemCollectionExtended < Nanoc::Int::OutdatednessRule affects_props :raw_content contract Nanoc::Int::ItemCollection, C::Named['Nanoc::Int::OutdatednessChecker'] => C::Maybe[Nanoc::Int::OutdatednessReasons::Generic] def apply(_obj, outdatedness_checker) new_items = outdatedness_checker.dependency_store.new_items if new_items.any? Nanoc::Int::OutdatednessReasons::ItemCollectionExtended.new(new_items) end end end end nanoc-4.8.0/lib/nanoc/base/services/outdatedness_rules/not_written.rb0000644000004100000410000000060313134141051026057 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int::OutdatednessRules class NotWritten < Nanoc::Int::OutdatednessRule affects_props :raw_content, :attributes, :compiled_content, :path def apply(obj, _outdatedness_checker) if obj.raw_paths.values.flatten.compact.any? { |fn| !File.file?(fn) } Nanoc::Int::OutdatednessReasons::NotWritten end end end end nanoc-4.8.0/lib/nanoc/base/services/outdatedness_rules/layout_collection_extended.rb0000644000004100000410000000105313134141051031113 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int::OutdatednessRules class LayoutCollectionExtended < Nanoc::Int::OutdatednessRule affects_props :raw_content contract Nanoc::Int::LayoutCollection, C::Named['Nanoc::Int::OutdatednessChecker'] => C::Maybe[Nanoc::Int::OutdatednessReasons::Generic] def apply(_obj, outdatedness_checker) new_layouts = outdatedness_checker.dependency_store.new_layouts if new_layouts.any? Nanoc::Int::OutdatednessReasons::LayoutCollectionExtended.new(new_layouts) end end end end nanoc-4.8.0/lib/nanoc/base/services/outdatedness_rules/rules_modified.rb0000644000004100000410000000071413134141051026500 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int::OutdatednessRules class RulesModified < Nanoc::Int::OutdatednessRule affects_props :compiled_content, :path def apply(obj, outdatedness_checker) seq_old = outdatedness_checker.action_sequence_store[obj] seq_new = outdatedness_checker.action_sequence_for(obj).serialize unless seq_old.eql?(seq_new) Nanoc::Int::OutdatednessReasons::RulesModified end end end end nanoc-4.8.0/lib/nanoc/base/services/outdatedness_rules/content_modified.rb0000644000004100000410000000102313134141051027012 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int::OutdatednessRules class ContentModified < Nanoc::Int::OutdatednessRule affects_props :raw_content, :compiled_content def apply(obj, outdatedness_checker) obj = obj.item if obj.is_a?(Nanoc::Int::ItemRep) ch_old = outdatedness_checker.checksum_store.content_checksum_for(obj) ch_new = outdatedness_checker.checksums.content_checksum_for(obj) if ch_old != ch_new Nanoc::Int::OutdatednessReasons::ContentModified end end end end nanoc-4.8.0/lib/nanoc/base/services/outdatedness_rules/attributes_modified.rb0000644000004100000410000000263313134141051027536 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int::OutdatednessRules class AttributesModified < Nanoc::Int::OutdatednessRule include Nanoc::Int::ContractsSupport affects_props :attributes, :compiled_content contract C::Or[Nanoc::Int::ItemRep, Nanoc::Int::Item, Nanoc::Int::Configuration, Nanoc::Int::Layout], C::Named['Nanoc::Int::OutdatednessChecker'] => C::Maybe[Nanoc::Int::OutdatednessReasons::Generic] def apply(obj, outdatedness_checker) case obj when Nanoc::Int::ItemRep apply(obj.item, outdatedness_checker) when Nanoc::Int::Item, Nanoc::Int::Layout, Nanoc::Int::Configuration if outdatedness_checker.checksum_store[obj] == outdatedness_checker.checksums.checksum_for(obj) return nil end old_checksums = outdatedness_checker.checksum_store.attributes_checksum_for(obj) unless old_checksums return Nanoc::Int::OutdatednessReasons::AttributesModified.new(true) end new_checksums = outdatedness_checker.checksums.attributes_checksum_for(obj) attributes = Set.new(old_checksums.keys) + Set.new(new_checksums.keys) changed_attributes = attributes.reject { |a| old_checksums[a] == new_checksums[a] } if changed_attributes.any? Nanoc::Int::OutdatednessReasons::AttributesModified.new(changed_attributes) end else raise ArgumentError end end end end nanoc-4.8.0/lib/nanoc/base/services/temp_filename_factory.rb0000644000004100000410000000240313134141051024123 0ustar www-datawww-data# frozen_string_literal: true require 'tmpdir' module Nanoc::Int # @api private class TempFilenameFactory # @return [String] The root directory for all temporary filenames attr_reader :root_dir # @return [Nanoc::Int::TempFilenameFactory] A common instance def self.instance @instance ||= new end def initialize @counts = {} @root_dir = Dir.mktmpdir('nanoc') end # @param [String] prefix A string prefix to include in the temporary # filename, often the type of filename being provided. # # @return [String] A new unused filename def create(prefix) count = @counts.fetch(prefix, 0) @counts[prefix] = count + 1 dirname = File.join(@root_dir, prefix) filename = File.join(@root_dir, prefix, count.to_s) FileUtils.mkdir_p(dirname) filename end # @param [String] prefix A string prefix that indicates which temporary # filenames should be deleted. # # @return [void] def cleanup(prefix) path = File.join(@root_dir, prefix) if File.exist?(path) FileUtils.rm_rf(path) end @counts.delete(prefix) if @counts.empty? && File.directory?(@root_dir) FileUtils.rm_rf(@root_dir) end end end end nanoc-4.8.0/lib/nanoc/base/services/dependency_tracker.rb0000644000004100000410000000357613134141051023434 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int # @api private class DependencyTracker include Nanoc::Int::ContractsSupport C_OBJ = C::Or[Nanoc::Int::Item, Nanoc::Int::Layout, Nanoc::Int::Configuration, Nanoc::Int::IdentifiableCollection] C_RAW_CONTENT = C::Or[C::IterOf[C::Or[String, Regexp]], C::Bool] C_ATTR = C::Or[C::IterOf[Symbol], C::Bool] C_ARGS = C::KeywordArgs[raw_content: C::Optional[C_RAW_CONTENT], attributes: C::Optional[C_ATTR], compiled_content: C::Optional[C::Bool], path: C::Optional[C::Bool]] class Null include Nanoc::Int::ContractsSupport contract C_OBJ, C_ARGS => C::Any def enter(_obj, raw_content: false, attributes: false, compiled_content: false, path: false); end contract C_OBJ => C::Any def exit; end contract C_OBJ, C_ARGS => C::Any def bounce(_obj, raw_content: false, attributes: false, compiled_content: false, path: false); end end def initialize(dependency_store) @dependency_store = dependency_store @stack = [] end contract C_OBJ, C_ARGS => C::Any def enter(obj, raw_content: false, attributes: false, compiled_content: false, path: false) unless @stack.empty? Nanoc::Int::NotificationCenter.post(:dependency_created, @stack.last, obj) @dependency_store.record_dependency( @stack.last, obj, raw_content: raw_content, attributes: attributes, compiled_content: compiled_content, path: path, ) end @stack.push(obj) end contract C_OBJ => C::Any def exit @stack.pop end contract C_OBJ, C_ARGS => C::Any def bounce(obj, raw_content: false, attributes: false, compiled_content: false, path: false) enter(obj, raw_content: raw_content, attributes: attributes, compiled_content: compiled_content, path: path) exit end end end nanoc-4.8.0/lib/nanoc/base/services/compilation_context.rb0000644000004100000410000000350013134141051023650 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int class CompilationContext attr_reader :site attr_reader :compiled_content_cache attr_reader :snapshot_repo def initialize(action_provider:, reps:, site:, compiled_content_cache:, snapshot_repo:) @action_provider = action_provider @reps = reps @site = site @compiled_content_cache = compiled_content_cache @snapshot_repo = snapshot_repo end def filter_name_and_args_for_layout(layout) seq = @action_provider.action_sequence_for(layout) if seq.nil? || seq.size != 1 || !seq[0].is_a?(Nanoc::Int::ProcessingActions::Filter) raise Nanoc::Int::Errors::UndefinedFilterForLayout.new(layout) end [seq[0].filter_name, seq[0].params] end def create_view_context(dependency_tracker) Nanoc::ViewContext.new( reps: @reps, items: @site.items, dependency_tracker: dependency_tracker, compilation_context: self, snapshot_repo: @snapshot_repo, ) end def assigns_for(rep, dependency_tracker) last_content = @snapshot_repo.get(rep, :last) content_or_filename_assigns = if last_content.binary? { filename: last_content.filename } else { content: last_content.string } end view_context = create_view_context(dependency_tracker) content_or_filename_assigns.merge( item: Nanoc::ItemWithRepsView.new(rep.item, view_context), rep: Nanoc::ItemRepView.new(rep, view_context), item_rep: Nanoc::ItemRepView.new(rep, view_context), items: Nanoc::ItemCollectionWithRepsView.new(@site.items, view_context), layouts: Nanoc::LayoutCollectionView.new(@site.layouts, view_context), config: Nanoc::ConfigView.new(@site.config, view_context), ) end end end nanoc-4.8.0/lib/nanoc/base/views.rb0000644000004100000410000000236313134141051017106 0ustar www-datawww-data# frozen_string_literal: true require_relative 'views/mixins/document_view_mixin' require_relative 'views/mixins/mutable_document_view_mixin' require_relative 'views/mixins/with_reps_view_mixin' require_relative 'views/view' require_relative 'views/view_context' require_relative 'views/config_view' require_relative 'views/identifiable_collection_view' require_relative 'views/item_without_reps_view' require_relative 'views/item_with_reps_view' require_relative 'views/item_collection_with_reps_view' require_relative 'views/item_collection_without_reps_view' require_relative 'views/item_rep_view' require_relative 'views/item_rep_collection_view' require_relative 'views/layout_view' require_relative 'views/layout_collection_view' require_relative 'views/mutable_config_view' require_relative 'views/mutable_identifiable_collection_view' require_relative 'views/mutable_item_view' require_relative 'views/mutable_item_collection_view' require_relative 'views/mutable_layout_view' require_relative 'views/mutable_layout_collection_view' require_relative 'views/post_compile_item_view' require_relative 'views/post_compile_item_collection_view' require_relative 'views/post_compile_item_rep_view' require_relative 'views/post_compile_item_rep_collection_view' nanoc-4.8.0/lib/nanoc/base/memoization.rb0000644000004100000410000000454213134141051020305 0ustar www-datawww-data# frozen_string_literal: true require 'weakref' module Nanoc::Int # Adds support for memoizing functions. # # @api private module Memoization class Value attr_reader :value def initialize(value) @value = value end end NONE = Object.new # Memoizes the method with the given name. The modified method will cache # the results of the original method, so that calling a method twice with # the same arguments will short-circuit and return the cached results # immediately. # # Memoization assumes that the current object as well as the function # arguments are immutable. Mutating the object or the arguments will not # cause memoized methods to recalculate their results. There is no way to # un-memoize a result, and calculation results will remain in memory even # if they are no longer needed. # # @example A fast fib function due to memoization # # class FibFast # # extend Nanoc::Int::Memoization # # def run(n) # if n == 0 # 0 # elsif n == 1 # 1 # else # run(n-1) + run(n-2) # end # end # memoize :run # # end # # @param [Symbol, String] method_name The name of the method to memoize # # @return [void] def memoize(method_name) original_method_name = '__nonmemoized_' + method_name.to_s alias_method original_method_name, method_name instance_cache = Hash.new { |hash, key| hash[key] = {} } define_method(method_name) do |*args| instance_method_cache = instance_cache[self] value = NONE if instance_method_cache.key?(args) object = instance_method_cache[args].object value = object ? object.value : NONE end counter_label = is_a?(Class) ? "#{self}.#{method_name}" : "#{self.class}##{method_name}" if value.equal?(NONE) Nanoc::Int::NotificationCenter.post(:memoization_miss, counter_label) send(original_method_name, *args).tap do |r| instance_method_cache[args] = Ref::SoftReference.new(Value.new(r)) end else Nanoc::Int::NotificationCenter.post(:memoization_hit, counter_label) value end end end alias memoized memoize end end nanoc-4.8.0/lib/nanoc/base/services.rb0000644000004100000410000000167713134141051017603 0ustar www-datawww-data# frozen_string_literal: true require_relative 'services/action_provider' require_relative 'services/action_sequence_builder' require_relative 'services/checksummer' require_relative 'services/compilation_context' require_relative 'services/compiler' require_relative 'services/compiler_loader' require_relative 'services/dependency_tracker' require_relative 'services/executor' require_relative 'services/filter' require_relative 'services/item_rep_builder' require_relative 'services/item_rep_router' require_relative 'services/item_rep_selector' require_relative 'services/item_rep_writer' require_relative 'services/notification_center' require_relative 'services/pruner' require_relative 'services/temp_filename_factory' require_relative 'services/outdatedness_rule' require_relative 'services/outdatedness_rules' require_relative 'services/compiler/phases' require_relative 'services/compiler/stages' require_relative 'services/outdatedness_checker' nanoc-4.8.0/lib/nanoc/base/repos/0000755000004100000410000000000013134141051016550 5ustar www-datawww-datananoc-4.8.0/lib/nanoc/base/repos/compiled_content_cache.rb0000644000004100000410000000341513134141051023551 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int # Represents a cache than can be used to store already compiled content, # to prevent it from being needlessly recompiled. # # @api private class CompiledContentCache < ::Nanoc::Int::Store include Nanoc::Int::ContractsSupport contract C::KeywordArgs[site: C::Maybe[Nanoc::Int::Site], items: C::IterOf[Nanoc::Int::Item]] => C::Any def initialize(site: nil, items:) super(Nanoc::Int::Store.tmp_path_for(site: site, store_name: 'compiled_content'), 2) @items = items @cache = {} end contract Nanoc::Int::ItemRep => C::Maybe[C::HashOf[Symbol => Nanoc::Int::Content]] # Returns the cached compiled content for the given item representation. # # This cached compiled content is a hash where the keys are the snapshot # names. and the values the compiled content at the given snapshot. def [](rep) item_cache = @cache[rep.item.identifier] || {} item_cache[rep.name] end contract Nanoc::Int::ItemRep, C::HashOf[Symbol => Nanoc::Int::Content] => self # Sets the compiled content for the given representation. # # This cached compiled content is a hash where the keys are the snapshot # names. and the values the compiled content at the given snapshot. def []=(rep, content) @cache[rep.item.identifier] ||= {} @cache[rep.item.identifier][rep.name] = content self end protected def data @cache end def data=(new_data) @cache = {} item_identifiers = Set.new(@items.map(&:identifier)) new_data.each_pair do |item_identifier, content_per_rep| if item_identifiers.include?(item_identifier) @cache[item_identifier] ||= content_per_rep end end end end end nanoc-4.8.0/lib/nanoc/base/repos/snapshot_repo.rb0000644000004100000410000000416513134141051021767 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int # @api private class SnapshotRepo include Nanoc::Int::ContractsSupport def initialize @contents = {} end contract Nanoc::Int::ItemRep, Symbol => C::Maybe[Nanoc::Int::Content] def get(rep, snapshot_name) @contents[rep] ||= {} @contents[rep][snapshot_name] end contract Nanoc::Int::ItemRep, Symbol, Nanoc::Int::Content => C::Any def set(rep, snapshot_name, contents) @contents[rep] ||= {} @contents[rep][snapshot_name] = contents end contract Nanoc::Int::ItemRep => C::HashOf[Symbol => Nanoc::Int::Content] def get_all(rep) @contents[rep] || {} end contract Nanoc::Int::ItemRep, C::HashOf[Symbol => Nanoc::Int::Content] => C::Any def set_all(rep, contents_per_snapshot) @contents[rep] = contents_per_snapshot end contract C::KeywordArgs[rep: Nanoc::Int::ItemRep, snapshot: C::Optional[C::Maybe[Symbol]]] => Nanoc::Int::Content def raw_compiled_content(rep:, snapshot: nil) # Get name of last pre-layout snapshot snapshot_name = snapshot || (get(rep, :pre) ? :pre : :last) # Check existance of snapshot snapshot_def = rep.snapshot_defs.reverse.find { |sd| sd.name == snapshot_name } unless snapshot_def raise Nanoc::Int::Errors::NoSuchSnapshot.new(rep, snapshot_name) end # Verify snapshot is usable stopped_moving = snapshot_name != :last || rep.compiled? is_usable_snapshot = get(rep, snapshot_name) && stopped_moving unless is_usable_snapshot Fiber.yield(Nanoc::Int::Errors::UnmetDependency.new(rep)) return raw_compiled_content(rep: rep, snapshot: snapshot) end get(rep, snapshot_name) end contract C::KeywordArgs[rep: Nanoc::Int::ItemRep, snapshot: C::Optional[C::Maybe[Symbol]]] => String def compiled_content(rep:, snapshot: nil) snapshot_content = raw_compiled_content(rep: rep, snapshot: snapshot) if snapshot_content.binary? raise Nanoc::Int::Errors::CannotGetCompiledContentOfBinaryItem.new(rep) end snapshot_content.string end end end nanoc-4.8.0/lib/nanoc/base/repos/action_sequence_store.rb0000644000004100000410000000226513134141051023463 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int # Stores action sequences for objects that can be run through a rule (item # representations and layouts). # # @api private class ActionSequenceStore < ::Nanoc::Int::Store def initialize(site: nil) super(Nanoc::Int::Store.tmp_path_for(site: site, store_name: 'rule_memory'), 1) @action_sequences = {} end # @param [Nanoc::Int::ItemRep, Nanoc::Int::Layout] obj The item representation or # the layout to get the action sequence for # # @return [Array] The action sequence for the given object def [](obj) @action_sequences[obj.reference] end # @param [Nanoc::Int::ItemRep, Nanoc::Int::Layout] obj The item representation or # the layout to set the action sequence for # # @param [Array] action_sequence The new action sequence to be stored # # @return [void] def []=(obj, action_sequence) @action_sequences[obj.reference] = action_sequence end protected # @see Nanoc::Int::Store#data def data @action_sequences end # @see Nanoc::Int::Store#data= def data=(new_data) @action_sequences = new_data end end end nanoc-4.8.0/lib/nanoc/base/repos/item_rep_repo.rb0000644000004100000410000000077413134141051021736 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int # Stores item reps (in memory). # # @api private class ItemRepRepo include Enumerable def initialize @reps = [] @reps_by_item = {} end def <<(rep) @reps << rep @reps_by_item[rep.item] ||= [] @reps_by_item[rep.item] << rep end def to_a @reps end def each(&block) @reps.each(&block) self end def [](item) @reps_by_item.fetch(item, []) end end end nanoc-4.8.0/lib/nanoc/base/repos/outdatedness_store.rb0000644000004100000410000000222313134141051023012 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int # @api private class OutdatednessStore < ::Nanoc::Int::Store include Nanoc::Int::ContractsSupport contract C::KeywordArgs[site: C::Maybe[Nanoc::Int::Site], reps: Nanoc::Int::ItemRepRepo] => C::Any def initialize(site: nil, reps:) super(Nanoc::Int::Store.tmp_path_for(site: site, store_name: 'outdatedness'), 1) @outdated_reps = Set.new @all_reps = reps end contract Nanoc::Int::ItemRep => C::Bool def include?(obj) @outdated_reps.include?(obj) end contract Nanoc::Int::ItemRep => self def add(obj) @outdated_reps << obj self end contract Nanoc::Int::ItemRep => self def remove(obj) @outdated_reps.delete(obj) self end contract C::None => C::ArrayOf[Nanoc::Int::ItemRep] def to_a @outdated_reps.to_a end protected def data @outdated_reps.map(&:reference) end def data=(new_data) outdated_refs = Set.new(new_data) all_reps = Set.new(@all_reps) @outdated_reps = Set.new(all_reps.select { |rep| outdated_refs.include?(rep.reference) }) end end end nanoc-4.8.0/lib/nanoc/base/repos/site_loader.rb0000644000004100000410000000530413134141051021371 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int class SiteLoader def new_empty site_from_config(Nanoc::Int::Configuration.new.with_defaults) end def new_with_config(hash) site_from_config(Nanoc::Int::Configuration.new(hash: hash).with_defaults) end def new_from_cwd site_from_config(Nanoc::Int::ConfigLoader.new.new_from_cwd) end # @return [Boolean] def self.cwd_is_nanoc_site? Nanoc::Int::ConfigLoader.cwd_is_nanoc_site? end private def site_from_config(config) code_snippets = code_snippets_from_config(config) code_snippets.each(&:load) data_sources_to_aggregate = with_data_sources(config) do |data_sources| data_sources.map do |ds| Nanoc::Int::PrefixedDataSource.new(ds, ds.items_root, ds.layouts_root) end end data_source = Nanoc::Int::AggregateDataSource.new(data_sources_to_aggregate, config) Nanoc::Int::Site.new( config: config, code_snippets: code_snippets, data_source: data_source, ) end def with_data_sources(config, &_block) data_sources = create_data_sources(config) begin data_sources.each(&:use) yield(data_sources) ensure data_sources.each(&:unuse) end end def create_data_sources(config) config[:data_sources].map do |data_source_hash| # Get data source class data_source_class = Nanoc::DataSource.named(data_source_hash[:type].to_sym) if data_source_class.nil? raise Nanoc::Int::Errors::UnknownDataSource.new(data_source_hash[:type]) end # Create data source data_source_class.new( config, data_source_hash[:items_root], data_source_hash[:layouts_root], data_source_hash.merge(data_source_hash[:config] || {}), ) end end def code_snippets_from_config(config) config[:lib_dirs].flat_map do |lib| Dir["#{lib}/**/*.rb"].sort.map do |filename| Nanoc::Int::CodeSnippet.new( read_code_snippet_contents(filename), filename, ) end end end ENCODING_REGEX = /\A#\s+(-\*-\s+)?(en)?coding: (?[^\s]+)(\s+-\*-\s*)?\n{0,2}/ def encoding_from_magic_comment(raw) match = ENCODING_REGEX.match(raw) match ? match['encoding'] : nil end def read_code_snippet_contents(filename) raw = File.read(filename, encoding: 'ASCII-8BIT') enc = encoding_from_magic_comment(raw) if enc raw = raw.force_encoding(enc).encode('UTF-8').sub(ENCODING_REGEX, '') else raw.force_encoding('UTF-8') end raw end end end nanoc-4.8.0/lib/nanoc/base/repos/checksum_store.rb0000644000004100000410000000362013134141051022114 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int # Stores checksums for objects in order to be able to detect whether a file # has changed since the last site compilation. # # @api private class ChecksumStore < ::Nanoc::Int::Store include Nanoc::Int::ContractsSupport attr_writer :checksums attr_accessor :objects c_obj = C::Or[Nanoc::Int::Item, Nanoc::Int::Layout, Nanoc::Int::Configuration, Nanoc::Int::CodeSnippet] contract C::KeywordArgs[site: C::Maybe[Nanoc::Int::Site], objects: C::IterOf[c_obj]] => C::Any def initialize(site: nil, objects:) super(Nanoc::Int::Store.tmp_path_for(site: site, store_name: 'checksums'), 2) @objects = objects @checksums = {} end contract c_obj => C::Maybe[String] def [](obj) @checksums[obj.reference] end contract c_obj => self def add(obj) if obj.is_a?(Nanoc::Int::Document) @checksums[[obj.reference, :content]] = Nanoc::Int::Checksummer.calc_for_content_of(obj) end if obj.is_a?(Nanoc::Int::Document) || obj.is_a?(Nanoc::Int::Configuration) @checksums[[obj.reference, :each_attribute]] = Nanoc::Int::Checksummer.calc_for_each_attribute_of(obj) end @checksums[obj.reference] = Nanoc::Int::Checksummer.calc(obj) self end contract c_obj => C::Maybe[String] def content_checksum_for(obj) @checksums[[obj.reference, :content]] end contract c_obj => C::Maybe[C::HashOf[Symbol, String]] def attributes_checksum_for(obj) @checksums[[obj.reference, :each_attribute]] end protected def data @checksums end def data=(new_data) references = Set.new(@objects.map(&:reference)) @checksums = {} new_data.each_pair do |key, checksum| if references.include?(key) || references.include?(key.first) @checksums[key] = checksum end end end end end nanoc-4.8.0/lib/nanoc/base/repos/data_source.rb0000644000004100000410000001471613134141051021377 0ustar www-datawww-data# frozen_string_literal: true module Nanoc # Responsible for loading site data. It is the (abstract) superclass for all # data sources. Subclasses must at least implement the data reading methods # ({#items} and {#layouts}). # # Apart from the methods for loading and storing data, there are the {#up} # and {#down} methods for bringing up and tearing down the connection to the # data source. These should be overridden in subclasses. The {#loading} # method wraps {#up} and {#down}. {#loading} is a convenience method for the # more low-level methods {#use} and {#unuse}, which respectively increment # and decrement the reference count; when the reference count goes from 0 to # 1, the data source will be loaded ({#up} will be called) and when the # reference count goes from 1 to 0, the data source will be unloaded # ({#down} will be called). # # @abstract Subclasses should at least implement {#items} and {#layouts}. class DataSource # @return [String] The root path where items returned by this data source # should be mounted. attr_reader :items_root # @return [String] The root path where layouts returned by this data # source should be mounted. attr_reader :layouts_root # @return [Hash] The configuration for this data source. For example, # online data sources could contain authentication details. attr_reader :config extend DDPlugin::Plugin def initialize(site_config, items_root, layouts_root, config) @site_config = site_config @items_root = items_root @layouts_root = layouts_root @config = config || {} @references = 0 end # Loads the data source when necessary (calling {#up}), yields, and # unloads (using {#down}) the data source when it is not being used # elsewhere. All data source queries and data manipulations should be # wrapped in a {#loading} block; it ensures that the data source is loaded # when necessary and makes sure the data source does not get unloaded # while it is still being used elsewhere. # # @return [void] def loading use yield ensure unuse end # Marks the data source as used by the caller. # # Calling this method increases the internal reference count. When the # data source is used for the first time (first {#use} call), the data # source will be loaded ({#up} will be called). # # @return [void] def use up if @references.zero? @references += 1 end # Marks the data source as unused by the caller. # # Calling this method decreases the internal reference count. When the # reference count reaches zero, i.e. when the data source is not used any # more, the data source will be unloaded ({#down} will be called). # # @return [void] def unuse @references -= 1 down if @references.zero? end # Brings up the connection to the data. Depending on the way data is # stored, this may not be necessary. This is the ideal place to connect to # the database, for example. # # Subclasses may override this method, but are not required to do so; the # default implementation simply does nothing. # # @return [void] def up; end # Brings down the connection to the data. This method should undo the # effects of the {#up} method. For example, a database connection # established in {#up} should be closed in this method. # # Subclasses may override this method, but are not required to do so; the # default implementation simply does nothing. # # @return [void] def down; end # Returns the collection of items (represented by {Nanoc::Int::Item}) in # this site. The default implementation simply returns an empty array. # # Subclasses should not prepend `items_root` to the item's identifiers, as # this will be done automatically. # # Subclasses may override this method, but are not required to do so; the # default implementation simply does nothing. # # @return [Enumerable] The collection of items def items [] end # Returns the collection of layouts (represented by {Nanoc::Int::Layout}) in # this site. The default implementation simply returns an empty array. # # Subclasses should prepend `layout_root` to the layout's identifiers, # since this is not done automatically. # # Subclasses may override this method, but are not required to do so; the # default implementation simply does nothing. # # @return [Enumerable] The collection of layouts def layouts [] end # Creates a new in-memory item instance. This is intended for use within # the {#items} method. # # @param [String, Proc] content The uncompiled item content # (if it is a textual item) or the path to the filename containing the # content (if it is a binary item). # # @param [Hash, Proc] attributes A hash containing this item's attributes. # # @param [String] identifier This item's identifier. # # @param [Boolean] binary Whether or not this item is binary # # @param [String, nil] checksum_data # # @param [String, nil] content_checksum_data # # @param [String, nil] attributes_checksum_data def new_item(content, attributes, identifier, binary: false, checksum_data: nil, content_checksum_data: nil, attributes_checksum_data: nil) content = Nanoc::Int::Content.create(content, binary: binary) Nanoc::Int::Item.new(content, attributes, identifier, checksum_data: checksum_data, content_checksum_data: content_checksum_data, attributes_checksum_data: attributes_checksum_data) end # Creates a new in-memory layout instance. This is intended for use within # the {#layouts} method. # # @param [String] raw_content The raw content of this layout. # # @param [Hash] attributes A hash containing this layout's attributes. # # @param [String] identifier This layout's identifier. # # @param [String, nil] checksum_data # # @param [String, nil] content_checksum_data # # @param [String, nil] attributes_checksum_data def new_layout(raw_content, attributes, identifier, checksum_data: nil, content_checksum_data: nil, attributes_checksum_data: nil) Nanoc::Int::Layout.new(raw_content, attributes, identifier, checksum_data: checksum_data, content_checksum_data: content_checksum_data, attributes_checksum_data: attributes_checksum_data) end end end nanoc-4.8.0/lib/nanoc/base/repos/dependency_store.rb0000644000004100000410000001452213134141051022433 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int # @api private class DependencyStore < ::Nanoc::Int::Store include Nanoc::Int::ContractsSupport attr_accessor :items attr_accessor :layouts contract Nanoc::Int::ItemCollection, Nanoc::Int::LayoutCollection, Nanoc::Int::Configuration, C::KeywordArgs[site: C::Optional[C::Maybe[Nanoc::Int::Site]]] => C::Any def initialize(items, layouts, config, site: nil) super(Nanoc::Int::Store.tmp_path_for(site: site, store_name: 'dependencies'), 5) @items = items @layouts = layouts @refs2objs = {} items.each { |o| add_vertex_for(o) } layouts.each { |o| add_vertex_for(o) } add_vertex_for(config) add_vertex_for(items) add_vertex_for(layouts) @new_objects = [] @graph = Nanoc::Int::DirectedGraph.new([nil] + objs2refs(@items) + objs2refs(@layouts)) end C_OBJ_SRC = Nanoc::Int::Item C_OBJ_DST = C::Or[Nanoc::Int::Item, Nanoc::Int::Layout, Nanoc::Int::Configuration, Nanoc::Int::IdentifiableCollection] contract C_OBJ_SRC => C::ArrayOf[Nanoc::Int::Dependency] def dependencies_causing_outdatedness_of(object) objects_causing_outdatedness_of(object).map do |other_object| props = props_for(other_object, object) Nanoc::Int::Dependency.new( other_object, object, Nanoc::Int::Props.new( raw_content: props.fetch(:raw_content, false), attributes: props.fetch(:attributes, false), compiled_content: props.fetch(:compiled_content, false), path: props.fetch(:path, false), ), ) end end def items=(items) @items = items items.each { |o| @refs2objs[obj2ref(o)] = o } add_vertex_for(items) end def layouts=(layouts) @layouts = layouts layouts.each { |o| @refs2objs[obj2ref(o)] = o } add_vertex_for(layouts) end def new_items @new_objects.select { |o| o.is_a?(Nanoc::Int::Item) } end def new_layouts @new_objects.select { |o| o.is_a?(Nanoc::Int::Layout) } end # Returns the direct dependencies for the given object. # # The direct dependencies of the given object include the items and # layouts that, when outdated will cause the given object to be marked as # outdated. Indirect dependencies will not be returned (e.g. if A depends # on B which depends on C, then the direct dependencies of A do not # include C). # # The direct predecessors can include nil, which indicates an item that is # no longer present in the site. # # @param [Nanoc::Int::Item, Nanoc::Int::Layout] object The object for # which to fetch the direct predecessors # # @return [Array] The direct # predecessors of # the given object def objects_causing_outdatedness_of(object) refs2objs(@graph.direct_predecessors_of(obj2ref(object))) end C_RAW_CONTENT = C::Or[C::IterOf[C::Or[String, Regexp]], C::Bool] C_ATTR = C::Or[C::IterOf[Symbol], C::Bool] C_KEYWORD_PROPS = C::KeywordArgs[raw_content: C::Optional[C_RAW_CONTENT], attributes: C::Optional[C_ATTR], compiled_content: C::Optional[C::Bool], path: C::Optional[C::Bool]] contract C::Maybe[C_OBJ_SRC], C::Maybe[C_OBJ_DST], C_KEYWORD_PROPS => C::Any # Records a dependency from `src` to `dst` in the dependency graph. When # `dst` is oudated, `src` will also become outdated. # # @param [Nanoc::Int::Item, Nanoc::Int::Layout] src The source of the dependency, # i.e. the object that will become outdated if dst is outdated # # @param [Nanoc::Int::Item, Nanoc::Int::Layout] dst The destination of the # dependency, i.e. the object that will cause the source to become # outdated if the destination is outdated # # @return [void] def record_dependency(src, dst, raw_content: false, attributes: false, compiled_content: false, path: false) return if src == dst add_vertex_for(src) add_vertex_for(dst) src_ref = obj2ref(src) dst_ref = obj2ref(dst) existing_props = Nanoc::Int::Props.new(@graph.props_for(dst_ref, src_ref) || {}) new_props = Nanoc::Int::Props.new(raw_content: raw_content, attributes: attributes, compiled_content: compiled_content, path: path) props = existing_props.merge(new_props) @graph.add_edge(dst_ref, src_ref, props: props.to_h) end def add_vertex_for(o) @refs2objs[obj2ref(o)] = o end # Empties the list of dependencies for the given object. This is necessary # before recompiling the given object, because otherwise old dependencies # will stick around and new dependencies will appear twice. This function # removes all incoming edges for the given vertex. # # @param [Nanoc::Int::Item, Nanoc::Int::Layout] object The object for which to # forget all dependencies # # @return [void] def forget_dependencies_for(object) @graph.delete_edges_to(obj2ref(object)) end protected def obj2ref(obj) obj && obj.reference end def ref2obj(reference) if reference @refs2objs[reference] else nil end end def objs2refs(objs) objs.map { |o| obj2ref(o) } end def refs2objs(refs) refs.map { |r| ref2obj(r) } end def props_for(a, b) props = @graph.props_for(obj2ref(a), obj2ref(b)) || {} if props.values.any? { |v| v } props else { raw_content: true, attributes: true, compiled_content: true, path: true } end end def data { edges: @graph.edges, vertices: @graph.vertices, } end def data=(new_data) objects = Set.new(@items.to_a + @layouts.to_a) refs = objs2refs(objects) # Create new graph @graph = Nanoc::Int::DirectedGraph.new([nil] + refs) # Load vertices previous_refs = new_data[:vertices] previous_objects = Set.new(refs2objs(previous_refs)) # Load edges new_data[:edges].each do |edge| from_index, to_index, props = *edge from = from_index && previous_refs[from_index] to = to_index && previous_refs[to_index] @graph.add_edge(from, to, props: props) end # Record dependency from all items on new items @new_objects = objects - previous_objects end end end nanoc-4.8.0/lib/nanoc/base/repos/aggregate_data_source.rb0000644000004100000410000000107113134141051023373 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int class AggregateDataSource < Nanoc::DataSource def initialize(data_sources, config) super({}, '/', '/', {}) @data_sources = data_sources @config = config end def items @_items ||= begin objs = @data_sources.flat_map(&:items) Nanoc::Int::ItemCollection.new(@config, objs) end end def layouts @_layouts ||= begin objs = @data_sources.flat_map(&:layouts) Nanoc::Int::LayoutCollection.new(@config, objs) end end end end nanoc-4.8.0/lib/nanoc/base/repos/store.rb0000644000004100000410000000656413134141051020244 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int # An abstract superclass for classes that need to store data to the # filesystem, such as checksums, cached compiled content and dependency # graphs. # # Each store has a version number. When attempting to load data from a store # that has an incompatible version number, no data will be loaded, but # {#version_mismatch_detected} will be called. # # @abstract Subclasses must implement {#data} and {#data=}, and may # implement {#no_data_found} and {#version_mismatch_detected}. # # @api private class Store include Nanoc::Int::ContractsSupport # @return [String] The name of the file where data will be loaded from and # stored to. attr_reader :filename # @return [Numeric] The version number corresponding to the file format # the data is in. When the file format changes, the version number # should be incremented. attr_reader :version # Creates a new store for the given filename. # # @param [String] filename The name of the file where data will be loaded # from and stored to. # # @param [Numeric] version The version number corresponding to the file # format the data is in. When the file format changes, the version # number should be incremented. def initialize(filename, version) @filename = filename @version = version end # Logic for building tmp path from active environment and store name # @api private contract C::KeywordArgs[site: C::Maybe[Nanoc::Int::Site], store_name: String] => String def self.tmp_path_for(store_name:, site:) # FIXME: disallow site from being nil output_dir = site ? site.config.output_dir : '' File.join(tmp_path_prefix(output_dir), store_name) end def self.tmp_path_prefix(output_dir) dir = Digest::SHA1.hexdigest(output_dir)[0..12] File.join('tmp', 'nanoc', dir) end # @group Loading and storing data # @return The data that should be written to the disk # # @abstract This method must be implemented by the subclass. def data raise NotImplementedError.new('Nanoc::Int::Store subclasses must implement #data and #data=') end # @param new_data The data that has been loaded from the disk # # @abstract This method must be implemented by the subclass. # # @return [void] def data=(new_data) # rubocop:disable Lint/UnusedMethodArgument raise NotImplementedError.new('Nanoc::Int::Store subclasses must implement #data and #data=') end # Loads the data from the filesystem into memory. This method will set the # loaded data using the {#data=} method. # # @return [void] def load return unless File.file?(filename) begin pstore.transaction do return if pstore[:version] != version self.data = pstore[:data] end rescue FileUtils.rm_f(filename) load end end # Stores the data contained in memory to the filesystem. This method will # use the {#data} method to fetch the data that should be written. # # @return [void] def store FileUtils.mkdir_p(File.dirname(filename)) pstore.transaction do pstore[:data] = data pstore[:version] = version end end private def pstore @pstore ||= PStore.new(filename) end end end nanoc-4.8.0/lib/nanoc/base/repos/prefixed_data_source.rb0000644000004100000410000000101013134141051023244 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int class PrefixedDataSource < Nanoc::DataSource def initialize(data_source, items_prefix, layouts_prefix) super({}, '/', '/', {}) @data_source = data_source @items_prefix = items_prefix @layouts_prefix = layouts_prefix end def items @data_source.items.map { |d| d.with_identifier_prefix(@items_prefix) } end def layouts @data_source.layouts.map { |d| d.with_identifier_prefix(@layouts_prefix) } end end end nanoc-4.8.0/lib/nanoc/base/repos/config_loader.rb0000644000004100000410000000407213134141051021673 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int # @api private class ConfigLoader class NoConfigFileFoundError < ::Nanoc::Error def initialize super('No configuration file found') end end class NoParentConfigFileFoundError < ::Nanoc::Error def initialize(filename) super("There is no parent configuration file at #{filename}") end end class CyclicalConfigFileError < ::Nanoc::Error def initialize(filename) super("The parent configuration file at #{filename} includes one of its descendants") end end # @return [Boolean] def self.cwd_is_nanoc_site? !config_filename_for_cwd.nil? end # @return [String] def self.config_filename_for_cwd filenames = %w[nanoc.yaml config.yaml] candidate = filenames.find { |f| File.file?(f) } candidate && File.expand_path(candidate) end def new_from_cwd # Determine path filename = self.class.config_filename_for_cwd raise NoConfigFileFoundError if filename.nil? # Read config = apply_parent_config( Nanoc::Int::Configuration.new(hash: YAML.load_file(filename)), [filename], ).with_defaults # Load environment config.with_environment end # @api private def apply_parent_config(config, processed_paths = []) parent_path = config[:parent_config_file] return config if parent_path.nil? # Get absolute path parent_path = File.absolute_path(parent_path, File.dirname(processed_paths.last)) unless File.file?(parent_path) raise NoParentConfigFileFoundError.new(parent_path) end # Check recursion if processed_paths.include?(parent_path) raise CyclicalConfigFileError.new(parent_path) end # Load parent_config = Nanoc::Int::Configuration.new(hash: YAML.load_file(parent_path)) full_parent_config = apply_parent_config(parent_config, processed_paths + [parent_path]) full_parent_config.merge(config.without(:parent_config_file)) end end end nanoc-4.8.0/lib/nanoc/base/repos/in_mem_data_source.rb0000644000004100000410000000042013134141051022706 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Int class InMemDataSource < Nanoc::DataSource attr_reader :items attr_reader :layouts def initialize(items, layouts) super({}, '/', '/', {}) @items = items @layouts = layouts end end end nanoc-4.8.0/lib/nanoc/base/feature.rb0000644000004100000410000000450513134141051017404 0ustar www-datawww-data# frozen_string_literal: true module Nanoc # @api private # # @example Defining a feature and checking its enabledness # # Nanoc::Feature.define('environments', version: '4.3') # Nanoc::Feaure.enabled?(Nanoc::Feature::ENVIRONMENTS) # module Feature # Defines a new feature with the given name, experimental in the given # version. The feature will be made available as a constant with the same # name, in uppercase, on the Nanoc::Feature module. # # @example Defining Nanoc::Feature::ENVIRONMENTS # # Nanoc::Feature.define('environments', version: '4.3') # # @param name The name of the feature # # @param version The minor version in which the feature is considered # experimental. # # @return [void] def self.define(name, version:) repo[name] = version const_set(name.upcase, name) end # Undefines the feature with the given name. For testing purposes only. # # @param name The name of the feature # # @return [void] # # @private def self.undefine(name) repo.delete(name) remove_const(name.upcase) end # @param [String] feature_name # # @return [Boolean] Whether or not the feature with the given name is enabled def self.enabled?(feature_name) enabled_features.include?(feature_name) || enabled_features.include?('all') end # @api private def self.enable(feature_name) raise ArgumentError, 'no block given' unless block_given? if enabled?(feature_name) yield else begin enabled_features << feature_name yield ensure enabled_features.delete(feature_name) end end end # @api private def self.reset_caches @enabled_features = nil end # @api private def self.enabled_features @enabled_features ||= Set.new(ENV.fetch('NANOC_FEATURES', '').split(',')) end # @api private def self.repo @repo ||= {} end # @return [Enumerable] Names of features that still exist, but # should not be considered as experimental in the current version of # Nanoc. def self.all_outdated repo.keys.reject do |name| version = repo[name] Nanoc::VERSION.start_with?(version) end end end end nanoc-4.8.0/lib/nanoc/base/entities.rb0000644000004100000410000000204413134141051017571 0ustar www-datawww-data# frozen_string_literal: true require_relative 'entities/context' require_relative 'entities/directed_graph' require_relative 'entities/identifier' require_relative 'entities/content' require_relative 'entities/processing_action' require_relative 'entities/processing_actions' require_relative 'entities/identifiable_collection' require_relative 'entities/item_collection' require_relative 'entities/layout_collection' require_relative 'entities/code_snippet' require_relative 'entities/configuration' require_relative 'entities/lazy_value' require_relative 'entities/document' require_relative 'entities/item' require_relative 'entities/item_rep' require_relative 'entities/layout' require_relative 'entities/pattern' require_relative 'entities/props' require_relative 'entities/action_sequence' require_relative 'entities/site' require_relative 'entities/snapshot_def' require_relative 'entities/checksum_collection' require_relative 'entities/outdatedness_status' require_relative 'entities/outdatedness_reasons' require_relative 'entities/dependency' nanoc-4.8.0/lib/nanoc/base/contracts_support.rb0000644000004100000410000000537313134141051021551 0ustar www-datawww-data# frozen_string_literal: true require 'singleton' module Nanoc::Int # @api private module ContractsSupport class Ignorer include Singleton # rubocop:disable Style/MethodMissing def method_missing(*_args) self end # rubocop:enable Style/MethodMissing def respond_to_missing?(*_args) true end end module DisabledContracts Any = Ignorer.instance Bool = Ignorer.instance Num = Ignorer.instance KeywordArgs = Ignorer.instance Optional = Ignorer.instance Maybe = Ignorer.instance None = Ignorer.instance ArrayOf = Ignorer.instance Or = Ignorer.instance Func = Ignorer.instance RespondTo = Ignorer.instance Named = Ignorer.instance IterOf = Ignorer.instance HashOf = Ignorer.instance def contract(*args); end end module EnabledContracts class AbstractContract def self.[](*vals) new(*vals) end end class Named < AbstractContract def initialize(name) @name = name end def valid?(val) val.is_a?(Kernel.const_get(@name)) end def inspect "#{self.class}(#{@name})" end end class IterOf < AbstractContract def initialize(contract) @contract = contract end def valid?(val) val.respond_to?(:each) && val.all? { |v| Contract.valid?(v, @contract) } end def inspect "#{self.class}(#{@contract})" end end def contract(*args) Contract(*args) end end def self.setup_once @_contracts_support__setup ||= false return @_contracts_support__should_enable if @_contracts_support__setup @_contracts_support__setup = true contracts_loadable = begin require 'contracts' true rescue LoadError false end @_contracts_support__should_enable = contracts_loadable && !ENV.key?('DISABLE_CONTRACTS') if @_contracts_support__should_enable # FIXME: ugly ::Contracts.const_set('Named', EnabledContracts::Named) ::Contracts.const_set('IterOf', EnabledContracts::IterOf) end @_contracts_support__should_enable end def self.included(base) should_enable = setup_once if should_enable unless base.include?(::Contracts::Core) base.include(::Contracts::Core) base.extend(EnabledContracts) base.const_set('C', ::Contracts) end else base.extend(DisabledContracts) base.const_set('C', DisabledContracts) end end end end nanoc-4.8.0/lib/nanoc/base/error.rb0000644000004100000410000000022313134141051017073 0ustar www-datawww-data# frozen_string_literal: true module Nanoc # Generic error. Superclass for all Nanoc-specific errors. class Error < ::StandardError end end nanoc-4.8.0/lib/nanoc/deploying.rb0000644000004100000410000000024213134141051017023 0ustar www-datawww-data# frozen_string_literal: true module Nanoc # @api private module Deploying end end require 'nanoc/deploying/deployer' require 'nanoc/deploying/deployers' nanoc-4.8.0/lib/nanoc/filters/0000755000004100000410000000000013134141051016156 5ustar www-datawww-datananoc-4.8.0/lib/nanoc/filters/markaby.rb0000644000004100000410000000076613134141051020142 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Filters # @api private class Markaby < Nanoc::Filter identifier :markaby requires 'markaby' # Runs the content through [Markaby](http://markaby.github.io/). # This method takes no options. # # @param [String] content The content to filter # # @return [String] The filtered content def run(content, _params = {}) # Get result ::Markaby::Builder.new(assigns).instance_eval(content).to_s end end end nanoc-4.8.0/lib/nanoc/filters/redcarpet.rb0000644000004100000410000000532213134141051020456 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Filters # @api private class Redcarpet < Nanoc::Filter identifier :redcarpet requires 'redcarpet' # Runs the content through [Redcarpet](https://github.com/vmg/redcarpet). # This method optionally takes processing options to pass on to Redcarpet. # # @overload run(content, params={}) # # For Redcarpet 1.x # # @param [String] content The content to filter # # @option params [Array] :options ([]) A list of options to pass on to # Redcarpet # # @return [String] The filtered content # # @overload run(content, params={}) # # For Redcarpet 2.x # # @param [String] content The content to filter # # @option params [Hash] :options ({}) A list of options to pass on to # Redcarpet itself (not the renderer) # # @option params [::Redcarpet::Render::Base] :renderer # (::Redcarpet::Render::HTML) The class of the renderer to use # # @option params [Hash] :renderer_options ({}) A list of options to pass # on to the Redcarpet renderer # # @option params [Boolean] :with_toc (false) A boolean to request a table # of contents # # @return [String] The filtered content def run(content, params = {}) if ::Redcarpet::VERSION > '2' options = params.fetch(:options, {}) renderer_class = params.fetch(:renderer, ::Redcarpet::Render::HTML) renderer_options = params.fetch(:renderer_options, {}) with_toc = params.fetch(:with_toc, false) if options.is_a?(Array) warn 'WARNING: You are passing an array of options to the :redcarpet filter, but Redcarpet 2.x expects a hash instead. This will likely fail.' end # Setup TOC if with_toc unless renderer_class <= ::Redcarpet::Render::HTML raise "Unexpected renderer: #{renderer_class}" end # `with_toc` implies `with_toc_data` for the HTML renderer renderer_options[:with_toc_data] = true end # Create renderer renderer = if renderer_class == ::Redcarpet::Render::HTML_TOC renderer_class.new else renderer_class.new(renderer_options) end # Render if with_toc renderer_toc = ::Redcarpet::Render::HTML_TOC.new toc = ::Redcarpet::Markdown.new(renderer_toc, options).render(content) body = ::Redcarpet::Markdown.new(renderer, options).render(content) toc + body else ::Redcarpet::Markdown.new(renderer, options).render(content) end end end end end nanoc-4.8.0/lib/nanoc/filters/rdiscount.rb0000644000004100000410000000110413134141051020511 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Filters # @api private class RDiscount < Nanoc::Filter identifier :rdiscount requires 'rdiscount' # Runs the content through [RDiscount](http://github.com/rtomayko/rdiscount). # # @option params [Array] :extensions ([]) A list of RDiscount extensions # # @param [String] content The content to filter # # @return [String] The filtered content def run(content, params = {}) extensions = params[:extensions] || [] ::RDiscount.new(content, *extensions).to_html end end end nanoc-4.8.0/lib/nanoc/filters/less.rb0000644000004100000410000000451013134141051017451 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Filters # @api private class Less < Nanoc::Filter identifier :less requires 'less' # Runs the content through [LESS](http://lesscss.org/). # This method takes no options. # # @param [String] content The content to filter # # @return [String] The filtered content def run(content, params = {}) # Create dependencies imported_filenames = imported_filenames_from(content) imported_items = imported_filenames_to_items(imported_filenames) depend_on(imported_items) # Add filename to load path paths = [File.dirname(@item[:content_filename])] on_main_fiber do parser = ::Less::Parser.new(paths: paths) parser.parse(content).to_css(params) end end def imported_filenames_from(content) imports = [] imports.concat(content.scan(/^@import\s+(["'])([^\1]+?)\1;/)) imports.concat(content.scan(/^@import\s+url\((["']?)([^)]+?)\1\);/)) imports.map { |i| i[1] =~ /\.(less|css)$/ ? i[1] : i[1] + '.less' } end def imported_filenames_to_items(imported_filenames) item_dir_path = Pathname.new(@item[:content_filename]).dirname.realpath cwd = Pathname.pwd # FIXME: ugly (get site dir instead) imported_filenames.map do |filename| full_paths = Set.new imported_pathname = Pathname.new(filename) full_paths << find_file(imported_pathname, item_dir_path) full_paths << find_file(imported_pathname, cwd) # Find matching item @items.find do |i| next if i[:content_filename].nil? item_path = Pathname.new(i[:content_filename]).realpath full_paths.any? { |fp| fp == item_path } end end.compact end # @param [Pathname] pathname Pathname of the file to find. Can be relative or absolute. # # @param [Pathname] root_pathname Directory pathname from which the search will start. # # @return [String, nil] A string containing the full path if a file is found, otherwise nil. def find_file(pathname, root_pathname) absolute_pathname = if pathname.relative? root_pathname + pathname else pathname end if absolute_pathname.exist? absolute_pathname.realpath else nil end end end end nanoc-4.8.0/lib/nanoc/filters/bluecloth.rb0000644000004100000410000000073513134141051020471 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Filters # @api private class BlueCloth < Nanoc::Filter identifier :bluecloth requires 'bluecloth' # Runs the content through [BlueCloth](http://deveiate.org/projects/BlueCloth). # This method takes no options. # # @param [String] content The content to filter # # @return [String] The filtered content def run(content, _params = {}) ::BlueCloth.new(content).to_html end end end nanoc-4.8.0/lib/nanoc/filters/coffeescript.rb0000644000004100000410000000077213134141051021165 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Filters # @api private class CoffeeScript < Nanoc::Filter identifier :coffeescript requires 'coffee-script' # Runs the content through [CoffeeScript](http://coffeescript.org/). # This method takes no options. # # @param [String] content The CoffeeScript content to turn into JavaScript # # @return [String] The resulting JavaScript def run(content, _params = {}) ::CoffeeScript.compile(content) end end end nanoc-4.8.0/lib/nanoc/filters/redcloth.rb0000644000004100000410000000315113134141051020307 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Filters # @api private class RedCloth < Nanoc::Filter identifier :redcloth requires 'redcloth' # Runs the content through [RedCloth](http://redcloth.org/). This method # takes the following options: # # * `:filter_class` # * `:filter_html` # * `:filter_ids` # * `:filter_style` # * `:hard_breaks` # * `:lite_mode` # * `:no_span_caps` # * `:sanitize_htm` # # Each of these options sets the corresponding attribute on the `RedCloth` # instance. For example, when the `:hard_breaks => false` option is passed # to this filter, the filter will call `r.hard_breaks = false` (with `r` # being the `RedCloth` instance). # # @param [String] content The content to filter # # @return [String] The filtered content def run(content, params = {}) # Create formatter r = ::RedCloth.new(content) # Set options r.filter_classes = params[:filter_classes] if params.key?(:filter_classes) r.filter_html = params[:filter_html] if params.key?(:filter_html) r.filter_ids = params[:filter_ids] if params.key?(:filter_ids) r.filter_styles = params[:filter_styles] if params.key?(:filter_styles) r.hard_breaks = params[:hard_breaks] if params.key?(:hard_breaks) r.lite_mode = params[:lite_mode] if params.key?(:lite_mode) r.no_span_caps = params[:no_span_caps] if params.key?(:no_span_caps) r.sanitize_html = params[:sanitize_html] if params.key?(:sanitize_html) # Get result r.to_html end end end nanoc-4.8.0/lib/nanoc/filters/sass.rb0000644000004100000410000000231613134141051017456 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Filters # @api private class Sass < Nanoc::Filter identifier :sass requires 'sass', 'nanoc/filters/sass/sass_filesystem_importer' # Runs the content through [Sass](http://sass-lang.com/). # Parameters passed to this filter will be passed on to Sass. # # @param [String] content The content to filter # # @return [String] The filtered content def run(content, params = {}) options = params.merge( nanoc_current_filter: self, filename: @item && @item.raw_filename, ) engine = ::Sass::Engine.new(content, options) engine.render end def self.item_filename_map_for_config(config, items) @item_filename_map ||= {} @item_filename_map[config] ||= {}.tap do |map| items.each do |item| if item.raw_filename path = Pathname.new(item.raw_filename).realpath.to_s map[path] = item end end end end def imported_filename_to_item(filename) realpath = Pathname.new(filename).realpath.to_s map = self.class.item_filename_map_for_config(@config, @items) map[realpath] end end end nanoc-4.8.0/lib/nanoc/filters/xsl.rb0000644000004100000410000000306513134141051017315 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Filters # @api private class XSL < Nanoc::Filter identifier :xsl requires 'nokogiri' always_outdated # Runs the item content through an [XSLT](http://www.w3.org/TR/xslt) # stylesheet using [Nokogiri](http://nokogiri.org/). # # This filter can only be run for layouts, because it will need both the # XML to convert (= the item content) as well as the XSLT stylesheet (= # the layout content). # # Additional parameters can be passed to the layout call. These parameters # will be turned into `xsl:param` elements. # # @example Invoking the filter as a layout # # compile '/reports/*/' do # layout 'xsl-report' # end # # layout 'xsl-report', :xsl, :awesome => 'definitely' # # @param [String] _content Ignored. As the filter can be run only as a # layout, the value of the `:content` parameter passed to the class at # initialization is used as the content to transform. # # @param [Hash] params The parameters that will be stored in corresponding # `xsl:param` elements. # # @return [String] The transformed content def run(_content, params = {}) Nanoc::Extra::JRubyNokogiriWarner.check_and_warn if assigns[:layout].nil? raise 'The XSL filter can only be run as a layout' end xml = ::Nokogiri::XML(assigns[:content]) xsl = ::Nokogiri::XSLT(assigns[:layout].raw_content) xsl.apply_to(xml, ::Nokogiri::XSLT.quote_params(params)) end end end nanoc-4.8.0/lib/nanoc/filters/mustache.rb0000644000004100000410000000103613134141051020314 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Filters # @api private class Mustache < Nanoc::Filter identifier :mustache requires 'mustache' # Runs the content through # [Mustache](http://github.com/defunkt/mustache). This method takes no # options. # # @param [String] content The content to filter # # @return [String] The filtered content def run(content, _params = {}) context = item.attributes.merge(yield: assigns[:content]) ::Mustache.render(content, context) end end end nanoc-4.8.0/lib/nanoc/filters/handlebars.rb0000644000004100000410000000157413134141051020615 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Filters # @api private class Handlebars < Nanoc::Filter identifier :handlebars requires 'handlebars' # Runs the content through # [Handlebars](http://handlebarsjs.com/) using # [Handlebars.rb](https://github.com/cowboyd/handlebars.rb). # This method takes no options. # # @param [String] content The content to filter # # @return [String] The filtered content def run(content, _params = {}) context = item.attributes.dup context[:item] = assigns[:item].attributes context[:config] = assigns[:config] context[:yield] = assigns[:content] if assigns.key?(:layout) context[:layout] = assigns[:layout].attributes end handlebars = ::Handlebars::Context.new template = handlebars.compile(content) template.call(context) end end end nanoc-4.8.0/lib/nanoc/filters/typogruby.rb0000644000004100000410000000075213134141051020553 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Filters # @api private class Typogruby < Nanoc::Filter identifier :typogruby requires 'typogruby' # Runs the content through [Typogruby](http://avdgaag.github.com/typogruby/). # This method takes no options. # # @param [String] content The content to filter # # @return [String] The filtered content def run(content, _params = {}) # Get result ::Typogruby.improve(content) end end end nanoc-4.8.0/lib/nanoc/filters/relativize_paths.rb0000644000004100000410000001107213134141051022061 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Filters # @api private class RelativizePaths < Nanoc::Filter identifier :relativize_paths require 'nanoc/helpers/link_to' include Nanoc::Helpers::LinkTo SELECTORS = ['*/@href', '*/@src', 'object/@data', 'param[@name="movie"]/@content', 'form/@action', 'comment()'].freeze # Relativizes all paths in the given content, which can be HTML, XHTML, XML # or CSS. This filter is quite useful if a site needs to be hosted in a # subdirectory instead of a subdomain. In HTML, all `href` and `src` # attributes will be relativized. In CSS, all `url()` references will be # relativized. # # @param [String] content The content to filter # # @option params [Symbol] :type The type of content to filter; can be # `:html`, `:xhtml`, `:xml` or `:css`. # # @option params [Array] :select The XPath expressions that matches the # nodes to modify. This param is useful only for the `:html`, `:xml` and # `:xhtml` types. # # @option params [Hash] :namespaces The pairs `prefix => uri` to define # any namespace you want to use in the XPath expressions. This param # is useful only for the `:xml` and `:xhtml` types. # # @return [String] The filtered content def run(content, params = {}) Nanoc::Extra::JRubyNokogiriWarner.check_and_warn # Set assigns so helper function can be used @item_rep = assigns[:item_rep] if @item_rep.nil? # Filter case params[:type] when :css relativize_css(content) when :html, :html5, :xml, :xhtml relativize_html_like(content, params) else raise 'The relativize_paths needs to know the type of content to ' \ 'process. Pass a :type to the filter call (:html for HTML, ' \ ':xhtml for XHTML, :xml for XML, or :css for CSS).' end end protected def relativize_css(content) # FIXME: parse CSS the proper way using csspool or something content.gsub(/url\((['"]?)(\/(?:[^\/].*?)?)\1\)/) do quote = Regexp.last_match[1] path = Regexp.last_match[2] 'url(' + quote + relative_path_to(path) + quote + ')' end end def relativize_html_like(content, params) selectors = params.fetch(:select, SELECTORS) namespaces = params.fetch(:namespaces, {}) type = params.fetch(:type) parser = parser_for(type) content = fix_content(content, type) nokogiri_process(content, selectors, namespaces, parser, type) end def parser_for(type) case type when :html require 'nokogiri' ::Nokogiri::HTML when :html5 require 'nokogumbo' ::Nokogiri::HTML5 when :xml require 'nokogiri' ::Nokogiri::XML when :xhtml require 'nokogiri' ::Nokogiri::XML end end def fix_content(content, type) case type when :xhtml # FIXME: cleanup because it is ugly # this cleans the XHTML namespace to process fragments and full # documents in the same way. At least, Nokogiri adds this namespace # if detects the `html` element. content.sub(%r{(]+)xmlns="http://www.w3.org/1999/xhtml"}, '\1') else content end end def nokogiri_process(content, selectors, namespaces, klass, type) # Ensure that all prefixes are strings namespaces = namespaces.reduce({}) { |new, (prefix, uri)| new.merge(prefix.to_s => uri) } doc = content =~ /]/ ? klass.parse(content) : klass.fragment(content) selector = selectors.map { |sel| "descendant-or-self::#{sel}" }.join('|') doc.xpath(selector, namespaces).each do |node| if node.name == 'comment' nokogiri_process_comment(node, doc, selectors, namespaces, klass, type) elsif path_is_relativizable?(node.content) node.content = relative_path_to(node.content) end end case type when :html5 doc.to_html else doc.send("to_#{type}") end end def nokogiri_process_comment(node, doc, selectors, namespaces, klass, type) content = node.content.dup.sub(%r{^(\s*\[.+?\]>\s*)(.+?)(\s* { assigns[:content] } : nil assigns_binding = context.get_binding(&proc) # Get result safe_level = params[:safe_level] trim_mode = params[:trim_mode] erb = ::ERB.new(content, safe_level, trim_mode) erb.filename = filename erb.result(assigns_binding) end end end nanoc-4.8.0/lib/nanoc/filters/rainpress.rb0000644000004100000410000000100113134141051020501 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Filters # @api private class Rainpress < Nanoc::Filter identifier :rainpress requires 'rainpress' # Runs the content through [Rainpress](http://code.google.com/p/rainpress/). # Parameters passed to this filter will be passed on to Rainpress. # # @param [String] content The content to filter # # @return [String] The filtered content def run(content, params = {}) ::Rainpress.compress(content, params) end end end nanoc-4.8.0/lib/nanoc/filters/sass/0000755000004100000410000000000013134141051017127 5ustar www-datawww-datananoc-4.8.0/lib/nanoc/filters/sass/sass_filesystem_importer.rb0000644000004100000410000000105713134141051024615 0ustar www-datawww-data# frozen_string_literal: true # @api private class ::Sass::Importers::Filesystem alias _orig_find _find def _find(dir, name, options) # Find filename full_filename, _syntax = ::Sass::Util.destructure(find_real_file(dir, name, options)) return nil if full_filename.nil? # Create dependency filter = options[:nanoc_current_filter] if filter item = filter.imported_filename_to_item(full_filename) filter.depend_on([item]) unless item.nil? end # Call original _find _orig_find(dir, name, options) end end nanoc-4.8.0/lib/nanoc/filters/rdoc.rb0000644000004100000410000000107613134141051017436 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Filters # @api private class RDoc < Nanoc::Filter identifier :rdoc requires 'rdoc' # Runs the content through [RDoc::Markup](http://docs.seattlerb.org/rdoc/RDoc/Markup.html). # This method takes no options. # # @param [String] content The content to filter # # @return [String] The filtered content def run(content, _params = {}) options = ::RDoc::Options.new to_html = ::RDoc::Markup::ToHtml.new(options) ::RDoc::Markup.new.convert(content, to_html) end end end nanoc-4.8.0/lib/nanoc/filters/asciidoc.rb0000644000004100000410000000112013134141051020253 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Filters # @api private class AsciiDoc < Nanoc::Filter identifier :asciidoc # Runs the content through [AsciiDoc](http://www.methods.co.nz/asciidoc/). # This method takes no options. # # @param [String] content The content to filter # # @return [String] The filtered content def run(content, _params = {}) stdout = StringIO.new stderr = $stderr piper = Nanoc::Extra::Piper.new(stdout: stdout, stderr: stderr) piper.run(%w[asciidoc -o - -], content) stdout.string end end end nanoc-4.8.0/lib/nanoc/filters/haml.rb0000644000004100000410000000133513134141051017426 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Filters # @api private class Haml < Nanoc::Filter identifier :haml requires 'haml' # Runs the content through [Haml](http://haml-lang.com/). # Parameters passed to this filter will be passed on to Haml. # # @param [String] content The content to filter # # @return [String] The filtered content def run(content, params = {}) # Get options options = params.merge(filename: filename) # Create context context = ::Nanoc::Int::Context.new(assigns) # Get result proc = assigns[:content] ? -> { assigns[:content] } : nil ::Haml::Engine.new(content, options).render(context, assigns, &proc) end end end nanoc-4.8.0/lib/nanoc/filters/colorize_syntax.rb0000644000004100000410000001064713134141051021747 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Filters # @api private class ColorizeSyntax < Nanoc::Filter identifier :colorize_syntax requires 'nokogiri', 'stringio', 'open3' DEFAULT_COLORIZER = :coderay ExtractedLanguage = Struct.new(:language, :from_class) def run(content, params = {}) Nanoc::Extra::JRubyNokogiriWarner.check_and_warn @colorizers = colorizers_from_params(params) syntax = params.fetch(:syntax, :html) parser = parser_for(syntax) # Colorize doc = parse(content, parser, params.fetch(:is_fullpage, false)) selector = params[:outside_pre] ? 'code' : 'pre > code' doc.css(selector).each do |element| # Get language extracted_language = extract_language(element) # Give up if there is no hope left next unless extracted_language # Highlight raw = strip(element.inner_text) highlighted_code = highlight(raw, extracted_language.language, params) element.children = parse_fragment(parser, strip(highlighted_code)) # Add language-something class unless extracted_language.from_class klass = element['class'] || String.new klass << ' ' unless [' ', nil].include?(klass[-1, 1]) klass << "language-#{extracted_language.language}" element['class'] = klass end highlight_postprocess(extracted_language.language, element.parent) end serialize(doc, syntax) end def extract_language(element) has_class = false language = nil if element['class'] # Get language from class match = element['class'].match(/(^| )language-([^ ]+)/) language = match[2] if match has_class = true if language else # Get language from comment line match = element.inner_text.strip.split[0].match(/^#!([^\/][^\n]*)$/) language = match[1] if match element.content = element.content.sub(/^#!([^\/][^\n]*)$\n/, '') if language end language ? ExtractedLanguage.new(language, has_class) : nil end def colorizers_from_params(params) colorizers = Hash.new(params[:default_colorizer] || DEFAULT_COLORIZER) (params[:colorizers] || {}).each_pair do |language, colorizer| colorizers[language] = colorizer end colorizers end def parser_for(syntax) case syntax when :html require 'nokogiri' Nokogiri::HTML when :html5 require 'nokogumbo' Nokogiri::HTML5 when :xml, :xhtml require 'nokogiri' Nokogiri::XML else raise "unknown syntax: #{syntax.inspect} (expected :html, :html5, or :xml)" end end def serialize(doc, syntax) case syntax when :html5 doc.to_html else doc.send("to_#{syntax}", encoding: 'UTF-8') end end def parse_full(parser_class, content) if parser_class.to_s == 'Nokogiri::HTML5' parser_class.parse(content) else parser_class.parse(content, nil, 'UTF-8') end end def parse_fragment(parser_class, content) parser_class.fragment(content) end def parse(content, klass, is_fullpage) if is_fullpage parse_full(klass, content) else parse_fragment(klass, content) end rescue => e if e.message =~ /can't modify frozen string/ parse(content.dup, klass, is_fullpage) else raise e end end protected # Removes the first blank lines and any whitespace at the end. def strip(s) s.lines.drop_while { |line| line.strip.empty? }.join.rstrip end def colorizer_name_for(language) @colorizers[language.to_sym] end def colorizer_named(name) colorizer = Colorizers::Abstract.named(name.to_sym) unless colorizer raise "I don’t know how to highlight code using the “#{name}” colorizer" end colorizer end def highlight(code, language, params = {}) colorizer_name = colorizer_name_for(language) colorizer = colorizer_named(colorizer_name) colorizer.new.process(code, language, params[colorizer_name] || {}) end def highlight_postprocess(language, element) colorizer_name = colorizer_name_for(language) colorizer = colorizer_named(colorizer_name) colorizer.new.postprocess(language, element) end end end require_relative 'colorize_syntax/colorizers' nanoc-4.8.0/lib/nanoc/filters/pandoc.rb0000644000004100000410000000230713134141051017751 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Filters # @api private class Pandoc < Nanoc::Filter identifier :pandoc requires 'pandoc-ruby' # Runs the content through [Pandoc](http://johnmacfarlane.net/pandoc/) # using [PandocRuby](https://github.com/alphabetum/pandoc-ruby). # # Arguments can be passed to PandocRuby in two ways: # # * Use the `:args` option. This approach is more flexible, since it # allows passing an array instead of a hash. # # * Pass the arguments directly to the filter. With this approach, only # hashes can be passed, which is more limiting than the `:args` approach. # # The `:args` approach is recommended. # # @example Passing arguments using `:arg` # # filter :pandoc, args: [:s, {:f => :markdown, :to => :html}, 'no-wrap', :toc] # # @example Passing arguments not using `:arg` # # filter :pandoc, :f => :markdown, :to => :html # # @param [String] content The content to filter # # @return [String] The filtered content def run(content, params = {}) args = params.key?(:args) ? params[:args] : params PandocRuby.convert(content, *args) end end end nanoc-4.8.0/lib/nanoc/filters/colorize_syntax/0000755000004100000410000000000013134141051021412 5ustar www-datawww-datananoc-4.8.0/lib/nanoc/filters/colorize_syntax/colorizers.rb0000644000004100000410000001124213134141051024132 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Filters::ColorizeSyntax::Colorizers class Abstract extend DDPlugin::Plugin def process(_code, _language, params = {}) # rubocop:disable Lint/UnusedMethodArgument raise NotImplementedError end def postprocess(_language, _element); end private def check_availability(*cmd) piper = Nanoc::Extra::Piper.new(stdout: StringIO.new, stderr: StringIO.new) piper.run(cmd, nil) end end class DummyColorizer < Abstract identifier :dummy def process(code, language, params = {}) # rubocop:disable Lint/UnusedMethodArgument code end end class CoderayColorizer < Abstract identifier :coderay def process(code, language, params = {}) require 'coderay' ::CodeRay.scan(code, language).html(params) end def postprocess(_language, element) # Skip if we're a free return if element.parent.nil? #
div_inner = Nokogiri::XML::Node.new('div', element.document) div_inner['class'] = 'code' div_inner.children = element.dup #
div_outer = Nokogiri::XML::Node.new('div', element.document) div_outer['class'] = 'CodeRay' div_outer.children = div_inner # orig element element.swap div_outer end end class PygmentizeColorizer < Abstract identifier :pygmentize def process(code, language, params = {}) check_availability('pygmentize', '-V') params[:encoding] ||= 'utf-8' params[:nowrap] ||= 'True' cmd = ['pygmentize', '-l', language, '-f', 'html'] cmd << '-O' << params.map { |k, v| "#{k}=#{v}" }.join(',') unless params.empty? stdout = StringIO.new stderr = $stderr piper = Nanoc::Extra::Piper.new(stdout: stdout, stderr: stderr) piper.run(cmd, code) stdout.string end end class PygmentsrbColorizer < Abstract identifier :pygmentsrb def process(code, language, params = {}) require 'pygments' args = params.dup args[:lexer] ||= language args[:options] ||= {} args[:options][:encoding] ||= 'utf-8' args[:options][:nowrap] ||= 'True' Pygments.highlight(code, args) end end class SimonHighlightColorizer < Abstract identifier :simon_highlight SIMON_HIGHLIGHT_OPT_MAP = { wrap: '-W', include_style: '-I', line_numbers: '-l', }.freeze def process(code, language, params = {}) check_availability('highlight', '--version') cmd = ['highlight', '--syntax', language, '--fragment'] params.each do |key, _value| if SIMON_HIGHLIGHT_OPT_MAP[key] cmd << SIMON_HIGHLIGHT_OPT_MAP[key] else # TODO: allow passing other options case key when :style cmd << '--style' << params[:style] end end end stdout = StringIO.new stderr = $stderr piper = Nanoc::Extra::Piper.new(stdout: stdout, stderr: stderr) piper.run(cmd, code) stdout.string end end class RougeColorizer < Abstract identifier :rouge def process(code, language, params = {}) require 'rouge' if Rouge.version < '2' || params.fetch(:legacy, false) # Rouge 1.x or Rouge 2.x legacy options formatter_options = { css_class: params.fetch(:css_class, 'highlight'), inline_theme: params.fetch(:inline_theme, nil), line_numbers: params.fetch(:line_numbers, false), start_line: params.fetch(:start_line, 1), wrap: params.fetch(:wrap, false), } formatter_cls = Rouge::Formatters.const_get(Rouge.version < '2' ? 'HTML' : 'HTMLLegacy') formatter = formatter_cls.new(formatter_options) else formatter = params.fetch(:formatter, Rouge::Formatters::HTML.new) end lexer = Rouge::Lexer.find_fancy(language, code) || Rouge::Lexers::PlainText formatter.format(lexer.lex(code)) end def postprocess(_language, element) # Removes the double wrapping. # # Before: # #

      #
      # After:
      #
      #   


      return if element.name != 'pre'

      code1 = element.xpath('code').first
      return if code1.nil?

      div = code1.xpath('div').first

      # For Rouge 2.x and 1.x, respectively
      pre = (div || code1).xpath('pre').first
      return if pre.nil?

      code2 = pre.xpath('code').first
      return if code2.nil?

      code1.inner_html = code2.inner_html
      code1['class'] = [code1['class'], pre['class']].compact.join(' ')
    end
  end
end
nanoc-4.8.0/lib/nanoc/filters/kramdown.rb0000644000004100000410000000176013134141051020331 0ustar  www-datawww-data# frozen_string_literal: true

module Nanoc::Filters
  # @api private
  class Kramdown < Nanoc::Filter
    identifier :kramdown

    requires 'kramdown'

    # Runs the content through [Kramdown](http://kramdown.gettalong.org/).
    # Parameters passed to this filter will be passed on to Kramdown.
    #
    # @param [String] content The content to filter
    #
    # @return [String] The filtered content
    def run(content, params = {})
      params = params.dup
      warning_filters = params.delete(:warning_filters)
      document = ::Kramdown::Document.new(content, params)

      if warning_filters
        r = Regexp.union(warning_filters)
        warnings = document.warnings.reject { |warning| r =~ warning }
      else
        warnings = document.warnings
      end

      if warnings.any?
        $stderr.puts "kramdown warning(s) for #{@item_rep.inspect}"
        warnings.each do |warning|
          $stderr.puts "  #{warning}"
        end
      end

      document.to_html
    end
  end
end
nanoc-4.8.0/lib/nanoc/filters/erubis.rb0000644000004100000410000000155513134141051020002 0ustar  www-datawww-data# frozen_string_literal: true

module Nanoc::Filters
  # @api private
  class Erubis < Nanoc::Filter
    identifier :erubis

    requires 'erubis'

    # Runs the content through [Erubis](http://www.kuwata-lab.com/erubis/).
    # This method takes no options.
    #
    # @param [String] content The content to filter
    #
    # @return [String] The filtered content
    def run(content, _params = {})
      # Create context
      context = ::Nanoc::Int::Context.new(assigns)

      # Get binding
      proc = assigns[:content] ? -> { assigns[:content] } : nil
      assigns_binding = context.get_binding(&proc)

      # Get result
      erubis_with_erbout.new(content, filename: filename).result(assigns_binding)
    end

    private

    def erubis_with_erbout
      @_erubis_with_erbout ||= Class.new(::Erubis::Eruby) { include ::Erubis::ErboutEnhancer }
    end
  end
end
nanoc-4.8.0/lib/nanoc/filters/slim.rb0000644000004100000410000000135113134141051017447 0ustar  www-datawww-data# frozen_string_literal: true

module Nanoc::Filters
  # @api private
  class Slim < Nanoc::Filter
    identifier :slim

    requires 'slim'

    # Runs the content through [Slim](http://slim-lang.com/).
    # This method takes no options.
    #
    # @param [String] content The content to filter
    #
    # @return [String] The filtered content
    def run(content, params = {})
      params = {
        disable_capture: true, # Capture managed by Nanoc
        buffer: '_erbout', # Force slim to output to the buffer used by Nanoc
      }.merge params

      # Create context
      context = ::Nanoc::Int::Context.new(assigns)

      ::Slim::Template.new(filename, params) { content }.render(context) { assigns[:content] }
    end
  end
end
nanoc-4.8.0/lib/nanoc/filters/yui_compressor.rb0000644000004100000410000000122013134141051021560 0ustar  www-datawww-data# frozen_string_literal: true

module Nanoc::Filters
  # @api private
  class YUICompressor < Nanoc::Filter
    identifier :yui_compressor

    requires 'yuicompressor'

    # Compress Javascript or CSS using [YUICompressor](http://rubydoc.info/gems/yuicompressor).
    # This method optionally takes options to pass directly to the
    # YUICompressor gem.
    #
    # @param [String] content JavaScript or CSS input
    #
    # @param [Hash] params Options passed to YUICompressor
    #
    # @return [String] Compressed but equivalent JavaScript or CSS
    def run(content, params = {})
      ::YUICompressor.compress(content, params)
    end
  end
end
nanoc-4.8.0/lib/nanoc/filters/rubypants.rb0000644000004100000410000000075513134141051020541 0ustar  www-datawww-data# frozen_string_literal: true

module Nanoc::Filters
  # @api private
  class RubyPants < Nanoc::Filter
    identifier :rubypants

    requires 'rubypants'

    # Runs the content through [RubyPants](http://rubydoc.info/gems/rubypants/).
    # This method takes no options.
    #
    # @param [String] content The content to filter
    #
    # @return [String] The filtered content
    def run(content, _params = {})
      # Get result
      ::RubyPants.new(content).to_html
    end
  end
end
nanoc-4.8.0/lib/nanoc/filters/maruku.rb0000644000004100000410000000100413134141051020002 0ustar  www-datawww-data# frozen_string_literal: true

module Nanoc::Filters
  # @api private
  class Maruku < Nanoc::Filter
    identifier :maruku

    requires 'maruku'

    # Runs the content through [Maruku](https://github.com/bhollis/maruku/).
    # Parameters passed to this filter will be passed on to Maruku.
    #
    # @param [String] content The content to filter
    #
    # @return [String] The filtered content
    def run(content, params = {})
      # Get result
      ::Maruku.new(content, params).to_html
    end
  end
end
nanoc-4.8.0/lib/nanoc/filters/erubi.rb0000644000004100000410000000156313134141051017616 0ustar  www-datawww-data# frozen_string_literal: true

module Nanoc::Filters
  # @api private
  class Erubi < Nanoc::Filter
    identifier :erubi

    requires 'erubi'

    # Runs the content through [Erubi](https://github.com/jeremyevans/erubi).
    # To prevent single quote escaping use :escapefunc => 'Nanoc::Helpers::HTMLEscape.html_escape'
    # See the Erubi documentation for more options.
    #
    # @param [String] content The content to filter
    #
    # @return [String] The filtered content
    def run(content, params = {})
      # Create context
      context = ::Nanoc::Int::Context.new(assigns)

      # Get binding
      proc = assigns[:content] ? -> { assigns[:content] } : nil
      assigns_binding = context.get_binding(&proc)

      # Get result
      eval(::Erubi::Engine.new(content, { bufvar: '_erbout', filename: filename }.merge(params)).src, assigns_binding)
    end
  end
end
nanoc-4.8.0/lib/nanoc/filters/uglify_js.rb0000644000004100000410000000117513134141051020502 0ustar  www-datawww-data# frozen_string_literal: true

module Nanoc::Filters
  # @api private
  class UglifyJS < Nanoc::Filter
    identifier :uglify_js

    requires 'uglifier'

    # Runs the content through [UglifyJS](https://github.com/mishoo/UglifyJS2/).
    # This method optionally takes options to pass directly to Uglifier.
    #
    # @param [String] content The content to filter
    #
    # @option params [Array] :options ([]) A list of options to pass on to Uglifier
    #
    # @return [String] The filtered content
    def run(content, params = {})
      # Add filename to load path
      Uglifier.new(params).compile(content)
    end
  end
end
nanoc-4.8.0/lib/nanoc/telemetry/0000755000004100000410000000000013134141051016520 5ustar  www-datawww-datananoc-4.8.0/lib/nanoc/telemetry/counter.rb0000644000004100000410000000030213134141051020517 0ustar  www-datawww-data# frozen_string_literal: true

module Nanoc::Telemetry
  class Counter
    attr_reader :value

    def initialize
      @value = 0
    end

    def increment
      @value += 1
    end
  end
end
nanoc-4.8.0/lib/nanoc/telemetry/labelled_summary.rb0000644000004100000410000000127213134141051022370 0ustar  www-datawww-data# frozen_string_literal: true

module Nanoc::Telemetry
  class LabelledSummary
    def initialize
      @summaries = {}
    end

    def observe(value, label)
      get(label).observe(value)
    end

    def get(label)
      @summaries.fetch(label) { @summaries[label] = Summary.new }
    end

    def empty?
      @summaries.empty?
    end

    def quantile(fraction, label)
      get(label).quantile(fraction)
    end

    def map
      @summaries.map { |(label, summary)| yield(label, summary) }
    end

    # TODO: add quantiles(fraction)
    # TODO: add min(label)
    # TODO: add mins
    # TODO: add max(label)
    # TODO: add maxs
    # TODO: add sum(label)
    # TODO: add sums
  end
end
nanoc-4.8.0/lib/nanoc/telemetry/table.rb0000644000004100000410000000155613134141051020143 0ustar  www-datawww-data# frozen_string_literal: true

module Nanoc::Telemetry
  class Table
    def initialize(rows)
      @rows = rows
    end

    def to_s
      columns = @rows.transpose
      column_lengths = columns.map { |c| c.map(&:size).max }

      [].tap do |lines|
        lines << row_to_s(@rows[0], column_lengths)
        lines << separator(column_lengths)
        lines.concat(@rows.drop(1).map { |r| row_to_s(r, column_lengths) })
      end.join("\n")
    end

    private

    def row_to_s(row, column_lengths)
      values = row.zip(column_lengths).map { |text, length| text.rjust(length) }
      values[0] + ' │ ' + values[1..-1].join('   ')
    end

    def separator(column_lengths)
      String.new.tap do |s|
        s << '─' * column_lengths[0]
        s << '─┼─'
        s << column_lengths[1..-1].map { |l| '─' * l }.join('───')
      end
    end
  end
end
nanoc-4.8.0/lib/nanoc/telemetry/labelled_counter.rb0000644000004100000410000000114113134141051022345 0ustar  www-datawww-data# frozen_string_literal: true

module Nanoc::Telemetry
  class LabelledCounter
    def initialize
      @counters = {}
    end

    def increment(label)
      get(label).increment
    end

    def get(label)
      @counters.fetch(label) { @counters[label] = Counter.new }
    end

    def empty?
      @counters.empty?
    end

    def value(label)
      get(label).value
    end

    def values
      @counters.each_with_object({}) do |(label, counter), res|
        res[label] = counter.value
      end
    end

    def map
      @counters.map { |(label, counter)| yield(label, counter) }
    end
  end
end
nanoc-4.8.0/lib/nanoc/telemetry/registry.rb0000644000004100000410000000054413134141051020720 0ustar  www-datawww-data# frozen_string_literal: true

module Nanoc::Telemetry
  class Registry
    def initialize
      @counters = {}
      @summaries = {}
    end

    def counter(name)
      @counters.fetch(name) { @counters[name] = LabelledCounter.new }
    end

    def summary(name)
      @summaries.fetch(name) { @summaries[name] = LabelledSummary.new }
    end
  end
end
nanoc-4.8.0/lib/nanoc/telemetry/summary.rb0000644000004100000410000000164713134141051020552 0ustar  www-datawww-data# frozen_string_literal: true

module Nanoc::Telemetry
  class Summary
    class EmptySummaryError < StandardError
      def message
        'Cannot calculate quantile for empty summary'
      end
    end

    def initialize
      @values = []
    end

    def observe(value)
      @values << value
      @sorted_values = nil
    end

    def count
      @values.size
    end

    def sum
      raise EmptySummaryError if @values.empty?
      @values.reduce(:+)
    end

    def avg
      sum / count
    end

    def min
      quantile(0.0)
    end

    def max
      quantile(1.0)
    end

    def quantile(fraction)
      raise EmptySummaryError if @values.empty?

      target = (@values.size - 1) * fraction.to_f
      interp = target % 1.0
      sorted_values[target.floor] * (1.0 - interp) + sorted_values[target.ceil] * interp
    end

    private

    def sorted_values
      @sorted_values ||= @values.sort
    end
  end
end
nanoc-4.8.0/lib/nanoc/telemetry/stopwatch.rb0000644000004100000410000000142013134141051021056 0ustar  www-datawww-data# frozen_string_literal: true

module Nanoc::Telemetry
  class Stopwatch
    attr_reader :duration

    class AlreadyRunningError < StandardError
      def message
        'Cannot start, because stopwatch is already running'
      end
    end

    class NotRunningError < StandardError
      def message
        'Cannot stop, because stopwatch is not running'
      end
    end

    def initialize
      @duration = 0.0
      @last_start = nil
    end

    def start
      raise AlreadyRunningError if running?
      @last_start = Time.now
    end

    def stop
      raise NotRunningError unless running?
      @duration += (Time.now - @last_start)
      @last_start = nil
    end

    def running?
      !@last_start.nil?
    end

    def stopped?
      !running?
    end
  end
end
nanoc-4.8.0/lib/nanoc/filters.rb0000644000004100000410000000213213134141051016501 0ustar  www-datawww-data# frozen_string_literal: true

# @api private
module Nanoc::Filters
end

require_relative 'filters/asciidoc'
require_relative 'filters/asciidoctor'
require_relative 'filters/bluecloth'
require_relative 'filters/colorize_syntax'
require_relative 'filters/coffeescript'
require_relative 'filters/erb'
require_relative 'filters/erubi'
require_relative 'filters/erubis'
require_relative 'filters/haml'
require_relative 'filters/handlebars'
require_relative 'filters/kramdown'
require_relative 'filters/less'
require_relative 'filters/markaby'
require_relative 'filters/maruku'
require_relative 'filters/mustache'
require_relative 'filters/pandoc'
require_relative 'filters/rainpress'
require_relative 'filters/rdiscount'
require_relative 'filters/rdoc'
require_relative 'filters/redcarpet'
require_relative 'filters/redcloth'
require_relative 'filters/relativize_paths'
require_relative 'filters/rubypants'
require_relative 'filters/sass'
require_relative 'filters/slim'
require_relative 'filters/typogruby'
require_relative 'filters/uglify_js'
require_relative 'filters/xsl'
require_relative 'filters/yui_compressor'
nanoc-4.8.0/lib/nanoc/checking.rb0000644000004100000410000000036713134141051016614 0ustar  www-datawww-data# frozen_string_literal: true

# @api private
module Nanoc::Checking
end

require_relative 'checking/check'
require_relative 'checking/checks'
require_relative 'checking/dsl'
require_relative 'checking/runner.rb'
require_relative 'checking/issue'
nanoc-4.8.0/lib/nanoc/spec.rb0000644000004100000410000001254313134141051015772 0ustar  www-datawww-data# frozen_string_literal: true

module Nanoc
  # @api private
  module Spec
    class HelperContext
      # @return [Nanoc::Int::DependencyTracker]
      attr_reader :dependency_tracker

      attr_reader :erbout

      # @param [Module] mod The helper module to create a context for
      def initialize(mod)
        @mod = mod

        @erbout = String.new
        @action_sequence = {}
        @config = Nanoc::Int::Configuration.new.with_defaults
        @reps = Nanoc::Int::ItemRepRepo.new
        @items = Nanoc::Int::ItemCollection.new(@config)
        @layouts = Nanoc::Int::LayoutCollection.new(@config)
        @dependency_tracker = Nanoc::Int::DependencyTracker.new(Object.new)
      end

      # Creates a new item and adds it to the site’s collection of items.
      #
      # @param [String] content The uncompiled item content
      #
      # @param [Hash] attributes A hash containing this item's attributes
      #
      # @param [Nanoc::Identifier, String] identifier This item's identifier
      #
      # @return [Nanoc::ItemWithRepsView] A view for the newly created item
      def create_item(content, attributes, identifier)
        item = Nanoc::Int::Item.new(content, attributes, identifier)
        @items = @items.add(item)
        self
      end

      # Creates a new layout and adds it to the site’s collection of layouts.
      #
      # @param [String] content The raw layout content
      #
      # @param [Hash] attributes A hash containing this layout's attributes
      #
      # @param [Nanoc::Identifier, String] identifier This layout's identifier
      #
      # @return [Nanoc::ItemWithRepsView] A view for the newly created layout
      def create_layout(content, attributes, identifier)
        layout = Nanoc::Int::Layout.new(content, attributes, identifier)
        @layouts = @layouts.add(layout)
        self
      end

      # Creates a new representation for the given item.
      #
      # @param [Nanoc::ItemWithRepsView] item The item to create a represetation for
      #
      # @param [String] path The path of the `:last` snapshot of this item representation
      # @param [Symbol] rep The rep name to create
      def create_rep(item, path, rep = :default)
        rep = Nanoc::Int::ItemRep.new(item.unwrap, rep)
        rep.paths[:last] = [path]
        @reps << rep
        self
      end

      # @return [Object] An object that includes the helper functions
      def helper
        mod = @mod
        klass = Class.new(Nanoc::Int::Context) { include mod }
        klass.new(assigns)
      end

      def item=(item)
        @item = item ? item.unwrap : nil
      end

      def item_rep=(item_rep)
        @item_rep = item_rep ? item_rep.unwrap : nil
      end

      # @return [Nanoc::MutableConfigView]
      def config
        assigns[:config]
      end

      # @return [Nanoc::ItemWithRepsView, nil]
      def item
        assigns[:item]
      end

      # @return [Nanoc::ItemRepView, nil]
      def item_rep
        assigns[:item_rep]
      end

      # @return [Nanoc::ItemCollectionWithRepsView]
      def items
        assigns[:items]
      end

      # @return [Nanoc::LayoutCollectionView]
      def layouts
        assigns[:layouts]
      end

      def action_sequence_for(obj)
        @action_sequence.fetch(obj, [])
      end

      def update_action_sequence(obj, memory)
        @action_sequence[obj] = memory
      end

      def snapshot_repo
        view_context.snapshot_repo
      end

      private

      def view_context
        Nanoc::ViewContext.new(
          reps: @reps,
          items: @items,
          dependency_tracker: @dependency_tracker,
          compilation_context: site.compiler.compilation_context,
          snapshot_repo: site.compiler.compilation_context.snapshot_repo,
        )
      end

      def new_action_provider
        Class.new(Nanoc::Int::ActionProvider) do
          def self.for(_context)
            raise NotImplementedError
          end

          def initialize(context)
            @context = context
          end

          def rep_names_for(_item)
            [:default]
          end

          def action_sequence_for(obj)
            @context.action_sequence_for(obj)
          end

          def snapshots_defs_for(_rep)
            [Nanoc::Int::SnapshotDef.new(:last, binary: false)]
          end
        end.new(self)
      end

      def new_compiler_for(site)
        Nanoc::Int::CompilerLoader.new.load(site, action_provider: new_action_provider)
      end

      def site
        @_site ||= begin
          site = Nanoc::Int::Site.new(
            config: @config,
            code_snippets: [],
            data_source: Nanoc::Int::InMemDataSource.new(@items, @layouts),
          )
          site.compiler = new_compiler_for(site)
          site
        end
      end

      def assigns
        {
          config: Nanoc::MutableConfigView.new(@config, view_context),
          item_rep: @item_rep ? Nanoc::ItemRepView.new(@item_rep, view_context) : nil,
          item: @item ? Nanoc::ItemWithRepsView.new(@item, view_context) : nil,
          items: Nanoc::ItemCollectionWithRepsView.new(@items, view_context),
          layouts: Nanoc::LayoutCollectionView.new(@layouts, view_context),
          _erbout: @erbout,
        }
      end
    end

    module HelperHelper
      def ctx
        @_ctx ||= HelperContext.new(described_class)
      end

      def helper
        @_helper ||= ctx.helper
      end
    end
  end
end
nanoc-4.8.0/lib/nanoc/telemetry.rb0000644000004100000410000000064013134141051017045 0ustar  www-datawww-data# frozen_string_literal: true

module Nanoc
  # @api private
  module Telemetry
    def self.new
      Registry.new
    end
  end
end

require_relative 'telemetry/counter'
require_relative 'telemetry/summary'

require_relative 'telemetry/labelled_counter'
require_relative 'telemetry/labelled_summary'

require_relative 'telemetry/registry'
require_relative 'telemetry/stopwatch'

require_relative 'telemetry/table'
nanoc-4.8.0/lib/nanoc/cli.rb0000644000004100000410000001352013134141051015603 0ustar  www-datawww-data# frozen_string_literal: true

begin
  require 'cri'
rescue LoadError => e
  $stderr.puts e
  $stderr.puts "If you are using a Gemfile, make sure that the Gemfile contains Nanoc ('gem \"nanoc\"')."
  exit 1
end

# @api private
module Nanoc::CLI
  module Commands
  end
end

require_relative 'cli/ansi_string_colorizer'
require_relative 'cli/logger'
require_relative 'cli/command_runner'
require_relative 'cli/cleaning_stream'
require_relative 'cli/stream_cleaners'
require_relative 'cli/error_handler'

# @api private
module Nanoc::CLI
  # @return [Boolean] true if debug output is enabled, false if not
  def self.debug?
    @debug || false
  end

  # @param [Boolean] boolean true if debug output should be enabled,
  #   false if it should not
  #
  # @return [void]
  def self.debug=(boolean)
    @debug = boolean
  end

  def self.verbosity
    @verbosity || 0
  end

  def self.verbosity=(val)
    @verbosity = val
  end

  # Invokes the Nanoc command-line tool with the given arguments.
  #
  # @param [Array] args An array of command-line arguments
  #
  # @return [void]
  def self.run(args)
    Nanoc::CLI::ErrorHandler.handle_while do
      setup
      root_command.run(args)
    end
  end

  # @return [Cri::Command] The root command, i.e. the command-line tool itself
  def self.root_command
    @root_command
  end

  # Adds the given command to the collection of available commands.
  #
  # @param [Cri::Command] cmd The command to add
  #
  # @return [void]
  def self.add_command(cmd)
    root_command.add_command(cmd)
  end

  # Schedules the given block to be executed after the CLI has been set up.
  #
  # @return [void]
  def self.after_setup(&block)
    # TODO: decide what should happen if the CLI is already set up
    add_after_setup_proc(block)
  end

  # Makes the command-line interface ready for use.
  #
  # @return [void]
  def self.setup
    setup_cleaning_streams
    setup_commands
    load_custom_commands
    after_setup_procs.each(&:call)
  end

  # Sets up the root command and base subcommands.
  #
  # @return [void]
  def self.setup_commands
    # Reinit
    @root_command = nil

    # Add root command
    filename = File.dirname(__FILE__) + '/cli/commands/nanoc.rb'
    @root_command = load_command_at(filename)

    # Add help command
    help_cmd = Cri::Command.new_basic_help
    add_command(help_cmd)

    # Add other commands
    cmd_filenames = Dir[File.dirname(__FILE__) + '/cli/commands/*.rb']
    cmd_filenames.each do |cmd_filename|
      next if File.basename(cmd_filename, '.rb') == 'nanoc'
      cmd = load_command_at(cmd_filename)
      add_command(cmd)
    end

    if defined?(Bundler)
      # Discover external commands through Bundler
      begin
        Bundler.require(:nanoc)
      rescue Bundler::GemfileNotFound
        # When running nanoc with Bundler being defined but
        # no gemfile being present (rubygems automatically loads
        # Bundler when executing from command line), don't crash.
      end
    end
  end

  # Loads site-specific commands.
  #
  # @return [void]
  def self.load_custom_commands
    if Nanoc::Int::SiteLoader.cwd_is_nanoc_site?
      config = Nanoc::Int::ConfigLoader.new.new_from_cwd
      config[:commands_dirs].each do |path|
        load_commands_at(path)
      end
    end
  end

  def self.load_commands_at(path)
    recursive_contents_of(path).each do |filename|
      # Create command
      command = Nanoc::CLI.load_command_at(filename)

      # Get supercommand
      pieces = filename.gsub(/^#{path}\/|\.rb$/, '').split('/')
      pieces = pieces[0, pieces.size - 1] || []
      root = Nanoc::CLI.root_command
      supercommand = pieces.reduce(root) do |cmd, piece|
        cmd.nil? ? nil : cmd.command_named(piece)
      end

      # Add to supercommand
      if supercommand.nil?
        raise "Cannot load command at #{filename} because its supercommand cannot be found"
      end
      supercommand.add_command(command)
    end
  end

  # Loads the command in the file with the given filename.
  #
  # @param [String] filename The name of the file that contains the command
  #
  # @return [Cri::Command] The loaded command
  def self.load_command_at(filename, command_name = nil)
    # Load
    code = File.read(filename, encoding: 'UTF-8')
    cmd = Cri::Command.define(code, filename)

    # Set name
    command_name ||= File.basename(filename, '.rb')
    cmd.modify { name command_name }

    # Done
    cmd
  end

  # @return [Array] The directory contents
  def self.recursive_contents_of(path)
    return [] unless File.directory?(path)
    files, dirs = *Dir[path + '/*'].sort.partition { |e| File.file?(e) }
    dirs.each { |d| files.concat recursive_contents_of(d) }
    files
  end

  # Wraps the given stream in a cleaning stream. The cleaning streams will
  # have the proper stream cleaners configured.
  #
  # @param [IO] io The stream to wrap
  #
  # @return [::Nanoc::CLI::CleaningStream]
  def self.wrap_in_cleaning_stream(io)
    cio = ::Nanoc::CLI::CleaningStream.new(io)

    unless enable_utf8?(io)
      cio.add_stream_cleaner(Nanoc::CLI::StreamCleaners::UTF8)
    end

    unless enable_ansi_colors?(io)
      cio.add_stream_cleaner(Nanoc::CLI::StreamCleaners::ANSIColors)
    end

    cio
  end

  # Wraps `$stdout` and `$stderr` in appropriate cleaning streams.
  #
  # @return [void]
  def self.setup_cleaning_streams
    $stdout = wrap_in_cleaning_stream($stdout)
    $stderr = wrap_in_cleaning_stream($stderr)
  end

  # @return [Boolean] true if UTF-8 support is present, false if not
  def self.enable_utf8?(io)
    return true unless io.tty?

    %w[LC_ALL LC_CTYPE LANG].any? { |e| ENV[e] =~ /UTF/i }
  end

  # @return [Boolean] true if color support is present, false if not
  def self.enable_ansi_colors?(io)
    io.tty?
  end

  def self.after_setup_procs
    @after_setup_procs || []
  end

  def self.add_after_setup_proc(proc)
    @after_setup_procs ||= []
    @after_setup_procs << proc
  end
end
nanoc-4.8.0/lib/nanoc/helpers.rb0000644000004100000410000000072013134141051016474 0ustar  www-datawww-data# frozen_string_literal: true

module Nanoc::Helpers
end

require_relative 'helpers/blogging'
require_relative 'helpers/breadcrumbs'
require_relative 'helpers/capturing'
require_relative 'helpers/child_parent'
require_relative 'helpers/filtering'
require_relative 'helpers/html_escape'
require_relative 'helpers/link_to'
require_relative 'helpers/rendering'
require_relative 'helpers/tagging'
require_relative 'helpers/text'
require_relative 'helpers/xml_sitemap'
nanoc-4.8.0/lib/nanoc/version.rb0000644000004100000410000000014313134141051016516 0ustar  www-datawww-data# frozen_string_literal: true

module Nanoc
  # The current Nanoc version.
  VERSION = '4.8.0'
end
nanoc-4.8.0/lib/nanoc/extra/0000755000004100000410000000000013134141051015631 5ustar  www-datawww-datananoc-4.8.0/lib/nanoc/extra/core_ext.rb0000644000004100000410000000015313134141051017765 0ustar  www-datawww-data# frozen_string_literal: true

require 'nanoc/extra/core_ext/pathname'
require 'nanoc/extra/core_ext/time'
nanoc-4.8.0/lib/nanoc/extra/jruby_nokogiri_warner.rb0000644000004100000410000000237113134141051022573 0ustar  www-datawww-data# frozen_string_literal: true

require 'singleton'

module Nanoc::Extra
  # @api private
  class JRubyNokogiriWarner
    include Singleton

    TEXT = <<~EOS
      --------------------------------------------------------------------------------
      Note:

      The behavior of Pure Java Nokogiri differs from the Nokogiri used on the
      standard Ruby interpreter (MRI) due to differences in underlying libraries.

      These sometimes problematic behavioral differences can cause Nanoc filters not
      to function properly, if at all. If you need reliable (X)HTML and XML handling
      functionality, consider not using Nokogiri on JRuby for the time being.

      These issues are being worked on both from the Nokogiri and the Nanoc side. Keep
      your Nokogiri and Nanoc versions up to date!

      For details, see https://github.com/nanoc/nanoc/pull/422.
      --------------------------------------------------------------------------------
EOS

    def self.check_and_warn
      instance.check_and_warn
    end

    def initialize
      @warned = false
    end

    def check_and_warn
      return unless defined?(RUBY_ENGINE)
      return if RUBY_ENGINE != 'jruby'
      return if @warned

      $stderr.puts TEXT
      @warned = true
    end
  end
end
nanoc-4.8.0/lib/nanoc/extra/link_collector.rb0000644000004100000410000000373013134141051021164 0ustar  www-datawww-data# frozen_string_literal: true

require 'set'

module ::Nanoc::Extra
  # @api private
  class LinkCollector
    URI_ATTRS = {
      'a' => :href,
      'audio' => :src,
      'form' => :action,
      'iframe' => :src,
      'img' => :src,
      'link' => :href,
      'script' => :src,
      'video' => :src,
    }.freeze

    def initialize(filenames, mode = nil)
      Nanoc::Extra::JRubyNokogiriWarner.check_and_warn

      @filenames = filenames
      @filter =
        case mode
        when nil
          ->(_h) { true }
        when :external
          ->(h) { external_href?(h) }
        when :internal
          ->(h) { !external_href?(h) }
        else
          raise ArgumentError, 'Expected mode argument to be :internal, :external or nil'
        end
    end

    def filenames_per_href
      grouped_filenames { |filename| hrefs_in_file(filename) }
    end

    def filenames_per_resource_uri
      grouped_filenames { |filename| resource_uris_in_file(filename) }
    end

    def external_href?(href)
      href =~ %r{^(\/\/|[a-z\-]+:)}
    end

    def hrefs_in_file(filename)
      uris_in_file filename, %w[a img]
    end

    def resource_uris_in_file(filename)
      uris_in_file filename, %w[audio form img iframe link script video]
    end

    private

    def grouped_filenames
      require 'nokogiri'
      grouped_filenames = {}
      @filenames.each do |filename|
        yield(filename).each do |resouce_uri|
          grouped_filenames[resouce_uri] ||= Set.new
          grouped_filenames[resouce_uri] << filename
        end
      end
      grouped_filenames
    end

    def uris_in_file(filename, tag_names)
      uris = Set.new
      doc = Nokogiri::HTML(::File.read(filename))
      tag_names.each do |tag_name|
        attr = URI_ATTRS[tag_name]
        doc.css(tag_name).each do |e|
          uris << e[attr] unless e[attr].nil?
        end
      end

      # Strip fragment
      uris.map! { |href| href.gsub(/#.*$/, '') }

      uris.select(&@filter)
    end
  end
end
nanoc-4.8.0/lib/nanoc/extra/core_ext/0000755000004100000410000000000013134141051017441 5ustar  www-datawww-datananoc-4.8.0/lib/nanoc/extra/core_ext/time.rb0000644000004100000410000000064313134141051020727 0ustar  www-datawww-data# frozen_string_literal: true

# @api private
module Nanoc::Extra::TimeExtensions
  # @return [String] The time in an ISO-8601 date format.
  def __nanoc_to_iso8601_date
    getutc.strftime('%Y-%m-%d')
  end

  # @return [String] The time in an ISO-8601 time format.
  def __nanoc_to_iso8601_time
    getutc.strftime('%Y-%m-%dT%H:%M:%SZ')
  end
end

# @api private
class Time
  include Nanoc::Extra::TimeExtensions
end
nanoc-4.8.0/lib/nanoc/extra/core_ext/pathname.rb0000644000004100000410000000102213134141051021556 0ustar  www-datawww-data# frozen_string_literal: true

module Nanoc::Extra
  # @api private
  module PathnameExtensions
    def __nanoc_components
      components = []
      tmp = self
      loop do
        old = tmp
        components << File.basename(tmp)
        tmp = File.dirname(tmp)
        break if old == tmp
      end
      components.reverse
    end

    def __nanoc_include_component?(component)
      __nanoc_components.include?(component)
    end
  end
end

# @api private
class ::Pathname
  include ::Nanoc::Extra::PathnameExtensions
end
nanoc-4.8.0/lib/nanoc/extra/piper.rb0000644000004100000410000000215013134141051017273 0ustar  www-datawww-data# frozen_string_literal: true

require 'open3'

module Nanoc::Extra
  # @api private
  class Piper
    class Error < ::Nanoc::Int::Errors::Generic
      def initialize(command, exit_code)
        @command   = command
        @exit_code = exit_code
      end

      def message
        "command exited with a nonzero status code #{@exit_code} (command: #{@command.join(' ')})"
      end
    end

    # @param [IO] stdout
    # @param [IO] stderr
    def initialize(stdout: $stdout, stderr: $stderr)
      @stdout = stdout
      @stderr = stderr
    end

    # @param [Array] cmd
    #
    # @param [String, nil] input
    def run(cmd, input)
      Open3.popen3(*cmd) do |stdin, stdout, stderr, wait_thr|
        stdout_thread = Thread.new { @stdout << stdout.read }
        stderr_thread = Thread.new { @stderr << stderr.read }

        if input
          stdin << input
        end
        stdin.close

        stdout_thread.join
        stderr_thread.join

        exit_status = wait_thr.value
        unless exit_status.success?
          raise Error.new(cmd, exit_status.to_i)
        end
      end
    end
  end
end
nanoc-4.8.0/lib/nanoc/extra/parallel_collection.rb0000644000004100000410000000233213134141051022165 0ustar  www-datawww-data# frozen_string_literal: true

require 'thread'

module Nanoc::Extra
  # @api private
  class ParallelCollection
    STOP = Object.new

    include Nanoc::Int::ContractsSupport

    contract C::RespondTo[:each], C::KeywordArgs[parallelism: Integer] => C::Any
    def initialize(enum, parallelism: 2)
      @enum = enum
      @parallelism = parallelism
    end

    contract C::Func[C::Any => C::Any] => self
    def each
      queue = SizedQueue.new(2 * @parallelism)
      error = nil

      threads = (1..@parallelism).map do
        Thread.new do
          loop do
            begin
              elem = queue.pop
              break if error
              break if STOP.equal?(elem)
              yield elem
            rescue => err
              error = err
              break
            end
          end
        end
      end

      @enum.each { |e| queue << e }
      @parallelism.times { queue << STOP }

      threads.each(&:join)

      raise error if error
      self
    end

    contract C::Func[C::Any => C::Any] => C::RespondTo[:each]
    def map
      [].tap do |all|
        mutex = Mutex.new
        each do |e|
          res = yield(e)
          mutex.synchronize { all << res }
        end
      end
    end
  end
end
nanoc-4.8.0/lib/nanoc/deploying/0000755000004100000410000000000013134141051016500 5ustar  www-datawww-datananoc-4.8.0/lib/nanoc/deploying/deployers/0000755000004100000410000000000013134141051020506 5ustar  www-datawww-datananoc-4.8.0/lib/nanoc/deploying/deployers/git.rb0000644000004100000410000000626213134141051021624 0ustar  www-datawww-data# frozen_string_literal: true

module Nanoc::Deploying::Deployers
  # A deployer that deploys a site using [Git](http://git-scm.com).
  #
  # @example A deployment configuration for GitHub Pages:
  #
  #   deploy:
  #     default:
  #       kind:       git
  #       remote:     git@github.com:myself/myproject.git
  #       branch:     gh-pages
  #       forced:     true
  #
  class Git < ::Nanoc::Deploying::Deployer
    identifier :git

    module Errors
      class Generic < ::Nanoc::Error
      end

      class OutputDirDoesNotExist < Generic
        def initialize(path)
          super("The directory to deploy, #{path}, does not exist.")
        end
      end

      class OutputDirIsNotAGitRepo < Generic
        def initialize(path)
          super("The directory to deploy, #{path}, is not a Git repository.")
        end
      end

      class RemoteDoesNotExist < Generic
        def initialize(remote)
          super("The remote to deploy to, #{remote}, does not exist.")
        end
      end

      class BranchDoesNotExist < Generic
        def initialize(branch)
          super("The branch to deploy, #{branch}, does not exist.")
        end
      end
    end

    def run
      unless File.exist?(source_path)
        raise Errors::OutputDirDoesNotExist.new(source_path)
      end

      remote = config.fetch(:remote, 'origin')
      branch = config.fetch(:branch, 'master')
      forced = config.fetch(:forced, false)

      puts "Deploying via Git to branch “#{branch}” on remote “#{remote}”…"

      Dir.chdir(source_path) do
        unless File.exist?('.git')
          raise Errors::OutputDirIsNotAGitRepo.new(source_path)
        end

        # Verify existence of remote, if remote is not a URL
        if remote_is_name?(remote)
          begin
            run_cmd(%W[git config --get remote.#{remote}.url])
          rescue Nanoc::Extra::Piper::Error
            raise Errors::RemoteDoesNotExist.new(remote)
          end
        end

        # If the branch exists then switch to it, otherwise prompt the user to create one.
        begin
          run_cmd_unless_dry(%W[git checkout #{branch}])
        rescue Nanoc::Extra::Piper::Error
          raise Errors::BranchDoesNotExist.new(branch)
        end

        return if clean_repo?

        msg = "Automated commit at #{Time.now.utc} by Nanoc #{Nanoc::VERSION}"
        author = 'Nanoc <>'
        run_cmd_unless_dry(%w[git add -A])
        run_cmd_unless_dry(%W[git commit -a --author #{author} -m #{msg}])

        if forced
          run_cmd_unless_dry(%W[git push -f #{remote} #{branch}])
        else
          run_cmd_unless_dry(%W[git push #{remote} #{branch}])
        end
      end
    end

    private

    def remote_is_name?(remote)
      remote !~ /:\/\/|@.+:/
    end

    def run_cmd(cmd)
      piper = Nanoc::Extra::Piper.new(stdout: $stdout, stderr: $stderr)
      piper.run(cmd, nil)
    end

    def run_cmd_unless_dry(cmd)
      if dry_run
        puts cmd.join(' ')
      else
        run_cmd(cmd)
      end
    end

    def clean_repo?
      stdout = StringIO.new
      piper = Nanoc::Extra::Piper.new(stdout: stdout, stderr: $stderr)
      piper.run(%w[git status --porcelain], nil)
      stdout.string.empty?
    end
  end
end
nanoc-4.8.0/lib/nanoc/deploying/deployers/rsync.rb0000644000004100000410000000350013134141051022167 0ustar  www-datawww-data# frozen_string_literal: true

module Nanoc::Deploying::Deployers
  # A deployer that deploys a site using rsync.
  #
  # The configuration has should include a `:dst` value, a string containing
  # the destination to where rsync should upload its data. It will likely be
  # in `host:path` format. It should not end with a slash. For example,
  # `"example.com:/var/www/sites/mysite/html"`.
  #
  # @example A deployment configuration with public and staging configurations
  #
  #   deploy:
  #     public:
  #       kind: rsync
  #       dst: "ectype:sites/stoneship/public"
  #     staging:
  #       kind: rsync
  #       dst: "ectype:sites/stoneship-staging/public"
  #       options: [ "-glpPrtvz" ]
  #
  # @api private
  class Rsync < ::Nanoc::Deploying::Deployer
    identifier :rsync

    # Default rsync options
    DEFAULT_OPTIONS = [
      '--group',
      '--links',
      '--perms',
      '--partial',
      '--progress',
      '--recursive',
      '--times',
      '--verbose',
      '--compress',
      '--exclude=".hg"',
      '--exclude=".svn"',
      '--exclude=".git"',
    ].freeze

    # @see Nanoc::Deploying::Deployer#run
    def run
      # Get params
      src = source_path + '/'
      dst = config[:dst]
      options = config[:options] || DEFAULT_OPTIONS

      # Validate
      raise 'No dst found in deployment configuration' if dst.nil?
      raise 'dst requires no trailing slash' if dst[-1, 1] == '/'

      # Run
      if dry_run
        warn 'Performing a dry-run; no actions will actually be performed'
        run_shell_cmd(['echo', 'rsync', options, src, dst].flatten)
      else
        run_shell_cmd(['rsync', options, src, dst].flatten)
      end
    end

    private

    def run_shell_cmd(cmd)
      piper = Nanoc::Extra::Piper.new(stdout: $stdout, stderr: $stderr)
      piper.run(cmd, nil)
    end
  end
end
nanoc-4.8.0/lib/nanoc/deploying/deployers/fog.rb0000644000004100000410000001220713134141051021610 0ustar  www-datawww-data# frozen_string_literal: true

module Nanoc::Deploying::Deployers
  # A deployer that deploys a site using [fog](https://github.com/geemus/fog).
  #
  # @example A deployment configuration with public and staging configurations
  #
  #   deploy:
  #     public:
  #       kind:       fog
  #       bucket:     nanoc-site
  #       cdn_id:     XXXXXX
  #     preprod:
  #       kind:       fog
  #       provider:   local
  #       local_root: ~/myCloud
  #       bucket:     nanoc-site
  #     staging:
  #       kind:       fog
  #       provider:   local
  #       local_root: ~/myCloud
  #       bucket:     nanoc-site-staging
  #
  # @api private
  class Fog < ::Nanoc::Deploying::Deployer
    identifier :fog

    class FogWrapper
      def initialize(directory, is_dry_run)
        @directory = directory
        @is_dry_run = is_dry_run
      end

      def upload(source_filename, destination_key)
        log_effectful("uploading #{source_filename} -> #{destination_key}")

        unless dry_run?
          File.open(source_filename) do |io|
            @directory.files.create(
              key: destination_key,
              body: io,
              public: true,
            )
          end
        end
      end

      def remove(keys)
        keys.each do |key|
          log_effectful("removing #{key}")

          unless dry_run?
            @directory.files.get(key).destroy
          end
        end
      end

      def invalidate(keys, cdn, distribution)
        keys.each_slice(1000) do |keys_slice|
          keys_slice.each do |key|
            log_effectful("invalidating #{key}")
          end

          unless dry_run?
            cdn.post_invalidation(distribution, keys_slice)
          end
        end
      end

      def dry_run?
        @is_dry_run
      end

      def log_effectful(s)
        if @is_dry_run
          puts "[dry run] #{s}"
        else
          puts s
        end
      end
    end

    # @see Nanoc::Deploying::Deployer#run
    def run
      require 'fog'

      src      = File.expand_path(source_path)
      bucket   = config[:bucket] || config[:bucket_name]
      path     = config[:path]
      cdn_id   = config[:cdn_id]

      if path && path.end_with?('/')
        raise "The path `#{path}` is not supposed to have a trailing slash"
      end

      connection = connect
      directory = get_or_create_bucket(connection, bucket, path)
      wrapper = FogWrapper.new(directory, dry_run?)

      remote_files = list_remote_files(directory)
      etags = read_etags(remote_files)

      modified_keys, retained_keys = upload_all(src, path, etags, wrapper)

      removed_keys = remote_files.map(&:key) - retained_keys - modified_keys
      wrapper.remove(removed_keys)

      if cdn_id
        cdn = ::Fog::CDN.new(config_for_fog)
        distribution = cdn.get_distribution(cdn_id)
        wrapper.invalidate(modified_keys + removed_keys, cdn, distribution)
      end
    end

    private

    def config_for_fog
      config.dup.tap do |c|
        c.delete(:bucket)
        c.delete(:bucket_name)
        c.delete(:path)
        c.delete(:cdn_id)
        c.delete(:kind)
      end
    end

    def connect
      ::Fog::Storage.new(config_for_fog)
    end

    def get_or_create_bucket(connection, bucket, path)
      directory =
        begin
          connection.directories.get(bucket, prefix: path)
        rescue ::Excon::Errors::NotFound
          nil
        end

      if directory
        directory
      elsif dry_run?
        puts '[dry run] creating bucket'
      else
        puts 'creating bucket'
        connection.directories.create(key: bucket, prefix: path)
      end
    end

    def remote_key_for_local_filename(local_filename, src, path)
      relative_local_filename = local_filename.sub(/\A#{src}\//, '')

      if path
        File.join(path, relative_local_filename)
      else
        relative_local_filename
      end
    end

    def list_remote_files(directory)
      if directory
        [].tap do |files|
          directory.files.each { |file| files << file }
        end
      else
        []
      end
    end

    def list_local_files(src)
      Dir[src + '/**/*'].select { |f| File.file?(f) }
    end

    def upload_all(src, path, etags, wrapper)
      modified_keys = []
      retained_keys = []

      local_files = list_local_files(src)
      local_files.each do |file_path|
        key = remote_key_for_local_filename(file_path, src, path)
        if needs_upload?(key, file_path, etags)
          wrapper.upload(file_path, key)
          modified_keys.push(key)
        else
          retained_keys.push(key)
        end
      end

      [modified_keys, retained_keys]
    end

    def needs_upload?(key, file_path, etags)
      remote_etag = etags[key]
      return true if remote_etag.nil?

      local_etag = calc_local_etag(file_path)
      remote_etag != local_etag
    end

    def read_etags(files)
      case config[:provider]
      when 'aws'
        files.each_with_object({}) do |file, etags|
          etags[file.key] = file.etag
        end
      else
        {}
      end
    end

    def calc_local_etag(file_path)
      case config[:provider]
      when 'aws'
        Digest::MD5.file(file_path).hexdigest
      end
    end
  end
end
nanoc-4.8.0/lib/nanoc/deploying/deployer.rb0000644000004100000410000000260113134141051020647 0ustar  www-datawww-data# frozen_string_literal: true

module Nanoc::Deploying
  # Represents a deployer, an object that allows uploading the compiled site
  # to a specific (remote) location.
  #
  # @abstract Subclass and override {#run} to implement a custom filter.
  #
  # @api private
  class Deployer
    extend DDPlugin::Plugin

    # @return [String] The path to the directory that contains the files to
    #   upload. It should not have a trailing slash.
    attr_reader :source_path

    # @return [Hash] The deployer configuration
    attr_reader :config

    # @return [Boolean] true if the deployer should only show what would be
    #   deployed instead of doing the actual deployment
    attr_reader :dry_run
    alias dry_run? dry_run

    # @param [String] source_path The path to the directory that contains the
    #   files to upload. It should not have a trailing slash.
    #
    # @return [Hash] config The deployer configuration
    #
    # @param [Boolean] dry_run true if the deployer should
    #   only show what would be deployed instead actually deploying
    def initialize(source_path, config, dry_run: false)
      @source_path  = source_path
      @config       = config
      @dry_run      = dry_run
    end

    # Performs the actual deployment.
    #
    # @abstract
    def run
      raise NotImplementedError.new('Nanoc::Deploying::Deployer subclasses must implement #run')
    end
  end
end
nanoc-4.8.0/lib/nanoc/deploying/deployers.rb0000644000004100000410000000027313134141051021035 0ustar  www-datawww-data# frozen_string_literal: true

# @api private
module Nanoc::Deploying::Deployers
end

require_relative 'deployers/fog'
require_relative 'deployers/git'
require_relative 'deployers/rsync'
nanoc-4.8.0/lib/nanoc/checking/0000755000004100000410000000000013134141051016261 5ustar  www-datawww-datananoc-4.8.0/lib/nanoc/checking/issue.rb0000644000004100000410000000050613134141051017737 0ustar  www-datawww-data# frozen_string_literal: true

module Nanoc::Checking
  # @api private
  class Issue
    attr_reader :description
    attr_reader :subject
    attr_reader :check_class

    def initialize(desc, subject, check_class)
      @description   = desc
      @subject       = subject
      @check_class = check_class
    end
  end
end
nanoc-4.8.0/lib/nanoc/checking/check.rb0000644000004100000410000000264113134141051017666 0ustar  www-datawww-data# frozen_string_literal: true

module Nanoc::Checking
  # @api private
  class OutputDirNotFoundError < Nanoc::Int::Errors::Generic
    def initialize(directory_path)
      super("Unable to run check against output directory at “#{directory_path}”: directory does not exist.")
    end
  end

  # @api private
  class Check < Nanoc::Int::Context
    extend DDPlugin::Plugin

    attr_reader :issues

    def self.create(site)
      output_dir = site.config[:output_dir]
      unless File.exist?(output_dir)
        raise Nanoc::Checking::OutputDirNotFoundError.new(output_dir)
      end
      output_filenames = Dir[output_dir + '/**/*'].select { |f| File.file?(f) }

      # FIXME: ugly
      view_context = site.compiler.compilation_context.create_view_context(Nanoc::Int::DependencyTracker::Null.new)

      context = {
        items: Nanoc::PostCompileItemCollectionView.new(site.items, view_context),
        layouts: Nanoc::LayoutCollectionView.new(site.layouts, view_context),
        config: Nanoc::ConfigView.new(site.config, view_context),
        output_filenames: output_filenames,
      }

      new(context)
    end

    def initialize(context)
      super(context)

      @issues = Set.new
    end

    def run
      raise NotImplementedError.new('Nanoc::Checking::Check subclasses must implement #run')
    end

    def add_issue(desc, subject: nil)
      @issues << Issue.new(desc, subject, self.class)
    end
  end
end
nanoc-4.8.0/lib/nanoc/checking/checks/0000755000004100000410000000000013134141051017521 5ustar  www-datawww-datananoc-4.8.0/lib/nanoc/checking/checks/internal_links.rb0000644000004100000410000000542113134141051023064 0ustar  www-datawww-data# frozen_string_literal: true

require 'uri'

module Nanoc::Checking::Checks
  # A check that verifies that all internal links point to a location that exists.
  #
  # @api private
  class InternalLinks < ::Nanoc::Checking::Check
    identifiers :internal_links, :ilinks

    # Starts the validator. The results will be printed to stdout.
    #
    # Internal links that match a regexp pattern in `@config[:checks][:internal_links][:exclude]` will
    # be skipped.
    #
    # @return [void]
    def run
      # TODO: de-duplicate this (duplicated in external links check)
      filenames = output_filenames.select { |f| File.extname(f) == '.html' }
      hrefs_with_filenames = ::Nanoc::Extra::LinkCollector.new(filenames, :internal).filenames_per_href
      resource_uris_with_filenames = ::Nanoc::Extra::LinkCollector.new(filenames, :internal).filenames_per_resource_uri

      uris = hrefs_with_filenames.merge(resource_uris_with_filenames)
      uris.each_pair do |href, fns|
        fns.each do |filename|
          next if valid?(href, filename)

          add_issue(
            "broken reference to #{href}",
            subject: filename,
          )
        end
      end
    end

    protected

    def valid?(href, origin)
      # Skip hrefs that point to self
      # FIXME: this is ugly and won’t always be correct
      return true if href == '.'

      # Skip hrefs that are specified in the exclude configuration
      return true if excluded?(href, origin)

      # Remove target
      path = href.sub(/#.*$/, '')
      return true if path.empty?

      # Remove query string
      path = path.sub(/\?.*$/, '')
      return true if path.empty?

      # Decode URL (e.g. '%20' -> ' ')
      path = URI.unescape(path)

      # Make absolute
      path =
        if path[0, 1] == '/'
          @config[:output_dir] + path
        else
          ::File.expand_path(path, ::File.dirname(origin))
        end

      # Check whether file exists
      return true if File.file?(path)

      # Check whether directory with index file exists
      return true if File.directory?(path) && @config[:index_filenames].any? { |fn| File.file?(File.join(path, fn)) }

      # Nope :(
      false
    end

    def excluded?(href, origin)
      config = @config.fetch(:checks, {}).fetch(:internal_links, {})
      excluded_target?(href, config) || excluded_origin?(origin, config)
    end

    def excluded_target?(href, config)
      excludes = config.fetch(:exclude_targets, config.fetch(:exclude, []))
      excludes.any? { |pattern| Regexp.new(pattern).match(href) }
    end

    def excluded_origin?(origin, config)
      relative_origin = origin[@config[:output_dir].size..-1]
      excludes = config.fetch(:exclude_origins, [])
      excludes.any? { |pattern| Regexp.new(pattern).match(relative_origin) }
    end
  end
end
nanoc-4.8.0/lib/nanoc/checking/checks/mixed_content.rb0000644000004100000410000000171413134141051022711 0ustar  www-datawww-data# frozen_string_literal: true

module Nanoc::Checking::Checks
  # A check that verifies HTML files do not reference external resources with
  # URLs that would trigger "mixed content" warnings.
  #
  # @api private
  class MixedContent < ::Nanoc::Checking::Check
    identifier :mixed_content

    PROTOCOL_PATTERN = /^(\w+):\/\//

    def run
      filenames = output_filenames.select { |f| File.extname(f) == '.html' }
      resource_uris_with_filenames = ::Nanoc::Extra::LinkCollector.new(filenames).filenames_per_resource_uri

      resource_uris_with_filenames.each_pair do |uri, fns|
        next unless guaranteed_insecure?(uri)
        fns.each do |filename|
          add_issue(
            "mixed content include: #{uri}",
            subject: filename,
          )
        end
      end
    end

    private

    def guaranteed_insecure?(href)
      protocol = PROTOCOL_PATTERN.match(href)

      protocol && protocol[1].casecmp('http').zero?
    end
  end
end
nanoc-4.8.0/lib/nanoc/checking/checks/css.rb0000644000004100000410000000042113134141051020633 0ustar  www-datawww-data# frozen_string_literal: true

module ::Nanoc::Checking::Checks
  # @api private
  class CSS < ::Nanoc::Checking::Checks::W3CValidator
    identifier :css

    def extension
      'css'
    end

    def validator_class
      ::W3CValidators::CSSValidator
    end
  end
end
nanoc-4.8.0/lib/nanoc/checking/checks/w3c_validator.rb0000644000004100000410000000147413134141051022615 0ustar  www-datawww-data# frozen_string_literal: true

module ::Nanoc::Checking::Checks
  # @api private
  class W3CValidator < ::Nanoc::Checking::Check
    def run
      require 'w3c_validators'
      require 'resolv-replace'

      Dir[@config[:output_dir] + '/**/*.' + extension].each do |filename|
        results = validator_class.new.validate_file(filename)
        lines = File.readlines(filename)
        results.errors.each do |e|
          line_num = e.line.to_i - 1
          line = lines[line_num]
          message = e.message.gsub(%r{\s+}, ' ').strip.sub(/\s+:$/, '')
          desc = "line #{line_num + 1}: #{message}: #{line}"
          add_issue(desc, subject: filename)
        end
      end
    end

    def extension
      raise NotImplementedError
    end

    def validator_class
      raise NotImplementedError
    end
  end
end
nanoc-4.8.0/lib/nanoc/checking/checks/stale.rb0000644000004100000410000000156213134141051021162 0ustar  www-datawww-data# frozen_string_literal: true

module Nanoc::Checking::Checks
  # @api private
  class Stale < ::Nanoc::Checking::Check
    identifier :stale

    def run
      require 'set'

      output_filenames.each do |f|
        next if pruner.filename_excluded?(f)
        next if item_rep_paths.include?(f)

        add_issue(
          'file without matching item',
          subject: f,
        )
      end
    end

    protected

    def item_rep_paths
      @item_rep_paths ||=
        Set.new(
          @items
            .flat_map(&:reps)
            .map(&:unwrap)
            .flat_map(&:raw_paths)
            .flat_map(&:values)
            .flatten,
        )
    end

    def pruner
      exclude_config = @config.fetch(:prune, {}).fetch(:exclude, [])
      # FIXME: reps=nil is icky
      @pruner ||= Nanoc::Pruner.new(@config, nil, exclude: exclude_config)
    end
  end
end
nanoc-4.8.0/lib/nanoc/checking/checks/external_links.rb0000644000004100000410000000717613134141051023103 0ustar  www-datawww-data# frozen_string_literal: true

require 'net/http'
require 'net/https'
require 'timeout'
require 'uri'

module ::Nanoc::Checking::Checks
  # A validator that verifies that all external links point to a location that exists.
  #
  # @api private
  class ExternalLinks < ::Nanoc::Checking::Check
    identifiers :external_links, :elinks

    def run
      # Find all broken external hrefs
      # TODO: de-duplicate this (duplicated in internal links check)
      filenames = output_filenames.select { |f| File.extname(f) == '.html' && !excluded_file?(f) }
      hrefs_with_filenames = ::Nanoc::Extra::LinkCollector.new(filenames, :external).filenames_per_href
      results = select_invalid(hrefs_with_filenames.keys)

      # Report them
      results.each do |res|
        filenames = hrefs_with_filenames[res.href]
        filenames.each do |filename|
          add_issue(
            "broken reference to #{res.href}: #{res.explanation}",
            subject: filename,
          )
        end
      end
    end

    class Result
      attr_reader :href
      attr_reader :explanation

      def initialize(href, explanation)
        @href        = href
        @explanation = explanation
      end
    end

    def select_invalid(hrefs)
      col = Nanoc::Extra::ParallelCollection.new(hrefs, parallelism: 10)
      col.map { |href| validate(href) }.compact
    end

    def validate(href)
      # Parse
      url = nil
      begin
        url = URI.parse(href)
      rescue URI::InvalidURIError
        return Result.new(href, 'invalid URI')
      end

      # Skip excluded URLs
      return nil if excluded?(href)

      # Skip non-HTTP URLs
      return nil if url.scheme !~ /^https?$/

      # Get status
      res = nil
      last_err = nil
      timeouts = [3, 5, 10, 30, 60]
      5.times do |i|
        begin
          Timeout.timeout(timeouts[i]) do
            res = request_url_once(url)
          end
        rescue => e
          last_err = e
          next
        end

        if res.code =~ /^3..$/
          if i == 4
            return Result.new(href, 'too many redirects')
          end

          # Find proper location
          location = res['Location']
          if location !~ /^https?:\/\//
            base_url = url.dup
            base_url.path = (location =~ /^\// ? '' : '/')
            base_url.query = nil
            base_url.fragment = nil
            location = base_url.to_s + location
          end

          url = URI.parse(location)
        elsif res.code == '200'
          return nil
        else
          return Result.new(href, res.code)
        end
      end
      if last_err
        return Result.new(href, last_err.message)
      else
        raise Nanoc::Int::Errors::InternalInconsistency, 'last_err cannot be nil'
      end
    end

    def path_for_url(url)
      path =
        if url.path.nil? || url.path.empty?
          '/'
        else
          url.path
        end

      if url.query
        path = path + '?' + url.query
      end

      path
    end

    def request_url_once(url)
      req = Net::HTTP::Get.new(path_for_url(url))
      http = Net::HTTP.new(url.host, url.port)
      if url.instance_of? URI::HTTPS
        http.use_ssl = true
        http.verify_mode = OpenSSL::SSL::VERIFY_NONE
      end
      http.request(req)
    end

    def excluded?(href)
      excludes = @config.fetch(:checks, {}).fetch(:external_links, {}).fetch(:exclude, [])
      excludes.any? { |pattern| Regexp.new(pattern).match(href) }
    end

    def excluded_file?(file)
      excludes = @config.fetch(:checks, {}).fetch(:external_links, {}).fetch(:exclude_files, [])
      excludes.any? { |pattern| Regexp.new(pattern).match(file) }
    end
  end
end
nanoc-4.8.0/lib/nanoc/checking/checks/html.rb0000644000004100000410000000043113134141051021010 0ustar  www-datawww-data# frozen_string_literal: true

module ::Nanoc::Checking::Checks
  # @api private
  class HTML < ::Nanoc::Checking::Checks::W3CValidator
    identifier :html

    def extension
      '{htm,html}'
    end

    def validator_class
      ::W3CValidators::NuValidator
    end
  end
end
nanoc-4.8.0/lib/nanoc/checking/runner.rb0000644000004100000410000000727213134141051020127 0ustar  www-datawww-data# frozen_string_literal: true

module Nanoc::Checking
  # Runner is reponsible for running issue checks.
  #
  # @api private
  class Runner
    CHECKS_FILENAMES = ['Checks', 'Checks.rb', 'checks', 'checks.rb'].freeze

    # @param [Nanoc::Int::Site] site The Nanoc site this runner is for
    def initialize(site)
      @site = site
    end

    # @return [String] The name of the Checks file
    def checks_filename
      @_checks_filename ||= CHECKS_FILENAMES.find { |f| File.file?(f) }
    end

    # @return [Boolean] true if a Checks file exists, false otherwise
    def dsl_present?
      checks_filename && File.file?(checks_filename)
    end
    alias has_dsl? dsl_present?

    # Lists all available checks on stdout.
    #
    # @return [void]
    def list_checks
      load_dsl_if_available

      puts 'Available checks:'
      puts
      puts all_check_classes.map { |i| '  ' + i.identifier.to_s }.sort.join("\n")
    end

    # Runs all checks.
    #
    # @return [Boolean] true if successful, false otherwise
    def run_all
      load_dsl_if_available

      run_check_classes(all_check_classes)
    end

    # Runs the checks marked for deployment.
    #
    # @return [Boolean] true if successful, false otherwise
    def run_for_deploy
      require_dsl

      return true if dsl.nil?
      run_check_classes(check_classes_named(dsl.deploy_checks))
    end

    # Runs the checks with the given names.
    #
    # @param [Array] check_class_names The names of the checks
    #
    # @return [Boolean] true if successful, false otherwise
    def run_specific(check_class_names)
      load_dsl_if_available

      run_check_classes(check_classes_named(check_class_names))
    end

    def load_dsl_if_available
      @dsl_loaded ||= false
      unless @dsl_loaded
        @dsl =
          if dsl_present?
            Nanoc::Checking::DSL.from_file(checks_filename)
          else
            nil
          end
        @dsl_loaded = true
      end
    end

    def require_dsl
      load_dsl_if_available
      if dsl.nil?
        raise Nanoc::Int::Errors::GenericTrivial, "No checks defined (no #{CHECKS_FILENAMES.first} file present)"
      end
    end

    def dsl
      @dsl
    end

    def run_check_classes(classes)
      issues = run_checks(classes)
      print_issues(issues)
      issues.empty? ? true : false
    end

    def all_check_classes
      Nanoc::Checking::Check.all
    end

    def check_classes_named(n)
      n.map do |a|
        klass = Nanoc::Checking::Check.named(a.to_sym)
        raise Nanoc::Int::Errors::GenericTrivial, "Unknown check: #{a}" if klass.nil?
        klass
      end
    end

    def run_checks(classes)
      return [] if classes.empty?

      # TODO: remove me
      @site.compiler.build_reps

      checks = []
      issues = Set.new
      length = classes.map { |c| c.identifier.to_s.length }.max + 18
      classes.each do |klass|
        print format("  %-#{length}s", "Running check #{klass.identifier}… ")

        check = klass.create(@site)
        check.run

        checks << check
        issues.merge(check.issues)

        # TODO: report progress

        puts check.issues.empty? ? 'ok'.green : 'error'.red
      end
      issues
    end

    def subject_to_s(s)
      s || '(global)'
    end

    def print_issues(issues)
      require 'colored'

      return if issues.empty?
      puts 'Issues found!'
      issues.group_by(&:subject).to_a.sort_by { |s| subject_to_s(s.first) }.each do |pair|
        subject = pair.first
        issues  = pair.last
        next if issues.empty?

        puts "  #{subject_to_s(subject)}:"
        issues.each do |i|
          puts "    [ #{'ERROR'.red} ] #{i.check_class.identifier} - #{i.description}"
        end
      end
    end
  end
end
nanoc-4.8.0/lib/nanoc/checking/checks.rb0000644000004100000410000000052213134141051020045 0ustar  www-datawww-data# frozen_string_literal: true

# @api private
module Nanoc::Checking::Checks
end

require_relative 'checks/w3c_validator'

require_relative 'checks/css'
require_relative 'checks/external_links'
require_relative 'checks/html'
require_relative 'checks/internal_links'
require_relative 'checks/mixed_content'
require_relative 'checks/stale'
nanoc-4.8.0/lib/nanoc/checking/dsl.rb0000644000004100000410000000120013134141051017361 0ustar  www-datawww-data# frozen_string_literal: true

module Nanoc::Checking
  # @api private
  class DSL
    attr_reader :deploy_checks

    def self.from_file(filename)
      dsl = new
      absolute_filename = File.expand_path(filename)
      dsl.instance_eval(File.read(filename), absolute_filename)
      dsl
    end

    def initialize
      @deploy_checks = []
    end

    def check(identifier, &block)
      klass = Class.new(::Nanoc::Checking::Check)
      klass.send(:define_method, :run, &block)
      klass.send(:identifier, identifier)
    end

    def deploy_check(*identifiers)
      identifiers.each { |i| @deploy_checks << i }
    end
  end
end
nanoc-4.8.0/lib/nanoc/rule_dsl/0000755000004100000410000000000013134141051016317 5ustar  www-datawww-datananoc-4.8.0/lib/nanoc/rule_dsl/rules_collection.rb0000644000004100000410000000772213134141051022221 0ustar  www-datawww-data# frozen_string_literal: true

module Nanoc::RuleDSL
  # Keeps track of the rules in a site.
  #
  # @api private
  class RulesCollection
    # @return [String] the contents of the Rules file
    attr_accessor :data

    # The hash containing layout-to-filter mapping rules. This hash is
    # ordered: iterating over the hash will happen in insertion order.
    #
    # @return [Hash] The layout-to-filter mapping rules
    attr_reader :layout_filter_mapping

    # The hash containing preprocessor code blocks that will be executed after
    #   all data is loaded but before the site is compiled.
    #
    # @return [Hash] The hash containing the preprocessor code blocks that will
    #   be executed after all data is loaded but before the site is compiled
    attr_accessor :preprocessors

    # The hash containing postprocessor code blocks that will be executed after
    #   all data is loaded and the site is compiled.
    #
    # @return [Hash] The hash containing the postprocessor code blocks that will
    #   be executed after all data is loaded and the site is compiled
    attr_accessor :postprocessors

    def initialize
      @item_compilation_rules = []
      @item_routing_rules     = []
      @layout_filter_mapping  = {}
      @preprocessors          = {}
      @postprocessors         = {}
    end

    # Add the given rule to the list of item compilation rules.
    #
    # @param [Nanoc::Int::Rule] rule The item compilation rule to add
    #
    # @return [void]
    def add_item_compilation_rule(rule)
      @item_compilation_rules << rule
    end

    # Add the given rule to the list of item routing rules.
    #
    # @param [Nanoc::Int::Rule] rule The item routing rule to add
    #
    # @return [void]
    def add_item_routing_rule(rule)
      @item_routing_rules << rule
    end

    # @param [Nanoc::Int::Item] item The item for which the compilation rules
    #   should be retrieved
    #
    # @return [Array] The list of item compilation rules for the given item
    def item_compilation_rules_for(item)
      @item_compilation_rules.select { |r| r.applicable_to?(item) }
    end

    # Finds the first matching compilation rule for the given item
    # representation.
    #
    # @param [Nanoc::Int::ItemRep] rep The item rep for which to fetch the rule
    #
    # @return [Nanoc::Int::Rule, nil] The compilation rule for the given item rep,
    #   or nil if no rules have been found
    def compilation_rule_for(rep)
      @item_compilation_rules.find do |rule|
        rule.applicable_to?(rep.item) && rule.rep_name == rep.name
      end
    end

    # Returns the list of routing rules that can be applied to the given item
    # representation. For each snapshot, the first matching rule will be
    # returned. The result is a hash containing the corresponding rule for
    # each snapshot.
    #
    # @param [Nanoc::Int::ItemRep] rep The item rep for which to fetch the rules
    #
    # @return [Hash] The routing rules for the given rep
    def routing_rules_for(rep)
      rules = {}
      @item_routing_rules.each do |rule|
        next unless rule.applicable_to?(rep.item)
        next if rule.rep_name != rep.name
        next if rules.key?(rule.snapshot_name)

        rules[rule.snapshot_name] = rule
      end
      rules
    end

    # Finds the filter name and arguments to use for the given layout.
    #
    # @param [Nanoc::Int::Layout] layout The layout for which to fetch the filter.
    #
    # @return [Array, nil] A tuple containing the filter name and the filter
    #   arguments for the given layout.
    def filter_for_layout(layout)
      @layout_filter_mapping.each_pair do |pattern, filter_name_and_args|
        return filter_name_and_args if pattern.match?(layout.identifier)
      end
      nil
    end

    # Returns an object that can be used for uniquely identifying objects.
    #
    # @return [Object] An unique reference to this object
    def reference
      'rules'
    end

    def inspect
      "<#{self.class}>"
    end
  end
end
nanoc-4.8.0/lib/nanoc/rule_dsl/rule.rb0000644000004100000410000000453113134141051017616 0ustar  www-datawww-data# frozen_string_literal: true

module Nanoc::RuleDSL
  # Contains the processing information for a item.
  #
  # @api private
  class Rule
    # @return [Symbol] The name of the representation that will be compiled
    #   using this rule
    attr_reader :rep_name

    # @return [Symbol] The name of the snapshot this rule will apply to.
    #   Ignored for compilation rules, but used for routing rules.
    attr_reader :snapshot_name

    attr_reader :pattern

    # Creates a new item compilation rule with the given identifier regex,
    # compiler and block. The block will be called during compilation with the
    # item rep as its argument.
    #
    # @param [Nanoc::Int::Pattern] pattern
    #
    # @param [String, Symbol] rep_name The name of the item representation
    #   where this rule can be applied to
    #
    # @param [Proc] block A block that will be called when matching items are
    #   compiled
    #
    # @param [Symbol, nil] snapshot_name The name of the snapshot this rule will
    #   apply to. Ignored for compilation rules, but used for routing rules.
    def initialize(pattern, rep_name, block, snapshot_name: nil)
      @pattern = pattern
      @rep_name = rep_name.to_sym
      @snapshot_name = snapshot_name
      @block = block
    end

    # @param [Nanoc::Int::Item] item The item to check
    #
    # @return [Boolean] true if this rule can be applied to the given item
    #   rep, false otherwise
    def applicable_to?(item)
      @pattern.match?(item.identifier)
    end

    # Applies this rule to the given item rep.
    #
    # @param [Nanoc::Int::ItemRep] rep
    # @param [Nanoc::Int::Site] site
    # @param [Nanoc::Int::Executor, Nanoc::RuleDSL::RecordingExecutor] executor
    # @param [Nanoc::ViewContext] view_context
    #
    # @return [void]
    def apply_to(rep, site:, executor:, view_context:)
      context = Nanoc::RuleDSL::RuleContext.new(
        rep: rep, executor: executor, site: site, view_context: view_context,
      )
      context.instance_exec(matches(rep.item.identifier), &@block)
    end

    protected

    # Matches the rule regexp against items identifier and gives back group
    # captures if any
    #
    # @param [String] identifier Identifier to capture groups for
    #
    # @return [nil, Array] Captured groups, if any
    def matches(identifier)
      @pattern.captures(identifier)
    end
  end
end
nanoc-4.8.0/lib/nanoc/rule_dsl/compiler_dsl.rb0000644000004100000410000002551413134141051021327 0ustar  www-datawww-data# frozen_string_literal: true

module Nanoc::RuleDSL
  # Contains methods that will be executed by the site’s `Rules` file.
  #
  # @api private
  class CompilerDSL < Nanoc::Int::Context
    # The current rules filename.
    #
    # @return [String] The current rules filename.
    #
    # @api private
    attr_accessor :rules_filename

    # Creates a new compiler DSL for the given collection of rules.
    #
    # @api private
    #
    # @param [Nanoc::RuleDSL::RulesCollection] rules_collection The collection of
    #   rules to modify when loading this DSL
    #
    # @param [Hash] config The site configuration
    def initialize(rules_collection, config)
      @rules_collection = rules_collection
      @config = config
      super({ config: config })
    end

    # Creates a preprocessor block that will be executed after all data is
    # loaded, but before the site is compiled.
    #
    # @yield The block that will be executed before site compilation starts
    #
    # @return [void]
    def preprocess(&block)
      if @rules_collection.preprocessors[rules_filename]
        warn 'WARNING: A preprocess block is already defined. Defining ' \
          'another preprocess block overrides the previously one.'
      end
      @rules_collection.preprocessors[rules_filename] = block
    end

    # Creates a compilation rule for all items whose identifier match the
    # given identifier, which may either be a string containing the *
    # wildcard, or a regular expression.
    #
    # This rule will be applicable to reps with a name equal to `:default`;
    # this can be changed by giving an explicit `:rep` parameter.
    #
    # An item rep will be compiled by calling the given block and passing the
    # rep as a block argument.
    #
    # @param [String] identifier A pattern matching identifiers of items that
    #   should be compiled using this rule
    #
    # @param [Symbol] rep The name of the representation
    #
    # @yield The block that will be executed when an item matching this
    #   compilation rule needs to be compiled
    #
    # @return [void]
    #
    # @example Compiling the default rep of the `/foo/` item
    #
    #     compile '/foo/' do
    #       rep.filter :erb
    #     end
    #
    # @example Compiling the `:raw` rep of the `/bar/` item
    #
    #     compile '/bar/', :rep => :raw do
    #       # do nothing
    #     end
    def compile(identifier, rep: :default, &block)
      raise ArgumentError.new('#compile requires a block') unless block_given?

      rule = Nanoc::RuleDSL::Rule.new(create_pattern(identifier), rep, block)
      @rules_collection.add_item_compilation_rule(rule)
    end

    # Creates a routing rule for all items whose identifier match the
    # given identifier, which may either be a string containing the `*`
    # wildcard, or a regular expression.
    #
    # This rule will be applicable to reps with a name equal to `:default`;
    # this can be changed by giving an explicit `:rep` parameter.
    #
    # The path of an item rep will be determined by calling the given block
    # and passing the rep as a block argument.
    #
    # @param [String] identifier A pattern matching identifiers of items that
    #   should be routed using this rule
    #
    # @param [Symbol] rep The name of the representation
    #
    # @param [Symbol] snapshot The name of the snapshot
    #
    # @yield The block that will be executed when an item matching this
    #   compilation rule needs to be routed
    #
    # @return [void]
    #
    # @example Routing the default rep of the `/foo/` item
    #
    #     route '/foo/' do
    #       item.identifier + 'index.html'
    #     end
    #
    # @example Routing the `:raw` rep of the `/bar/` item
    #
    #     route '/bar/', :rep => :raw do
    #       '/raw' + item.identifier + 'index.txt'
    #     end
    def route(identifier, rep: :default, snapshot: :last, &block)
      raise ArgumentError.new('#route requires a block') unless block_given?

      rule = Nanoc::RuleDSL::Rule.new(create_pattern(identifier), rep, block, snapshot_name: snapshot)
      @rules_collection.add_item_routing_rule(rule)
    end

    # Creates a layout rule for all layouts whose identifier match the given
    # identifier, which may either be a string containing the * wildcard, or a
    # regular expression. The layouts matching the identifier will be filtered
    # using the filter specified in the second argument. The params hash
    # contains filter arguments that will be passed to the filter.
    #
    # @param [String] identifier A pattern matching identifiers of layouts
    #   that should be filtered using this rule
    #
    # @param [Symbol] filter_name The name of the filter that should be run
    #   when processing the layout
    #
    # @param [Hash] params Extra filter arguments that should be passed to the
    #   filter when processing the layout (see {Nanoc::Filter#run})
    #
    # @return [void]
    #
    # @example Specifying the filter to use for a layout
    #
    #     layout '/default/', :erb
    #
    # @example Using custom filter arguments for a layout
    #
    #     layout '/custom/',  :haml, :format => :html5
    def layout(identifier, filter_name, params = {})
      pattern = Nanoc::Int::Pattern.from(create_pattern(identifier))
      @rules_collection.layout_filter_mapping[pattern] = [filter_name, params]
    end

    # Creates a pair of compilation and routing rules that indicate that the
    # specified item(s) should be copied to the output folder as-is. The items
    # are selected using an identifier, which may either be a string
    # containing the `*` wildcard, or a regular expression.
    #
    # This meta-rule will be applicable to reps with a name equal to
    # `:default`; this can be changed by giving an explicit `:rep` parameter.
    #
    # @param [String] identifier A pattern matching identifiers of items that
    #   should be processed using this meta-rule
    #
    # @param [Symbol] rep The name of the representation
    #
    # @return [void]
    #
    # @example Copying the `/foo/` item as-is
    #
    #     passthrough '/foo/'
    #
    # @example Copying the `:raw` rep of the `/bar/` item as-is
    #
    #     passthrough '/bar/', :rep => :raw
    def passthrough(identifier, rep: :default)
      raise ArgumentError.new('#passthrough does not require a block') if block_given?

      compilation_block = proc {}
      compilation_rule = Nanoc::RuleDSL::Rule.new(create_pattern(identifier), rep, compilation_block)
      @rules_collection.add_item_compilation_rule(compilation_rule)

      # Create routing rule
      routing_block = proc do
        if item.identifier.full?
          item.identifier.to_s
        else
          # This is a temporary solution until an item can map back to its data
          # source.
          # ATM item[:content_filename] is nil for items coming from the static
          # data source.
          item[:extension].nil? || (item[:content_filename].nil? && item.identifier =~ %r{#{item[:extension]}/$}) ? item.identifier.chop : item.identifier.chop + '.' + item[:extension]
        end
      end
      routing_rule = Nanoc::RuleDSL::Rule.new(create_pattern(identifier), rep, routing_block, snapshot_name: :last)
      @rules_collection.add_item_routing_rule(routing_rule)
    end

    # Creates a pair of compilation and routing rules that indicate that the
    # specified item(s) should be ignored, e.g. compiled and routed with an
    # empty rule. The items are selected using an identifier, which may either
    # be a string containing the `*` wildcard, or a regular expression.
    #
    # This meta-rule will be applicable to reps with a name equal to
    # `:default`; this can be changed by giving an explicit `:rep` parameter.
    #
    # @param [String] identifier A pattern matching identifiers of items that
    #   should be processed using this meta-rule
    #
    # @param [Symbol] rep The name of the representation
    #
    # @return [void]
    #
    # @example Suppressing compilation and output for all all `/foo/*` items.
    #
    #     ignore '/foo/*'
    def ignore(identifier, rep: :default)
      raise ArgumentError.new('#ignore does not require a block') if block_given?

      compilation_rule = Nanoc::RuleDSL::Rule.new(create_pattern(identifier), rep, proc {})
      @rules_collection.add_item_compilation_rule(compilation_rule)

      routing_rule = Nanoc::RuleDSL::Rule.new(create_pattern(identifier), rep, proc {}, snapshot_name: :last)
      @rules_collection.add_item_routing_rule(routing_rule)
    end

    # Includes an additional rules file in the current rules collection.
    #
    # @param [String] name The name of the rules file — an ".rb" extension is
    #   implied if not explicitly given
    #
    # @return [void]
    #
    # @example Including two additional rules files, 'rules/assets.rb' and
    #   'rules/content.rb'
    #
    #     include_rules 'rules/assets'
    #     include_rules 'rules/content'
    def include_rules(name)
      filename = [name.to_s, "#{name}.rb", "./#{name}", "./#{name}.rb"].find { |f| File.file?(f) }
      raise Nanoc::Int::Errors::NoRulesFileFound.new if filename.nil?

      Nanoc::RuleDSL::RulesLoader.new(@config, @rules_collection).parse(filename)
    end

    # Creates a postprocessor block that will be executed after all data is
    # loaded and the site is compiled.
    #
    # @yield The block that will be executed after site compilation completes
    #
    # @return [void]
    def postprocess(&block)
      if @rules_collection.postprocessors[rules_filename]
        warn 'WARNING: A postprocess block is already defined. Defining ' \
          'another postprocess block overrides the previously one.'
      end
      @rules_collection.postprocessors[rules_filename] = block
    end

    # @api private
    def create_pattern(arg)
      case @config[:string_pattern_type]
      when 'glob'
        Nanoc::Int::Pattern.from(arg)
      when 'legacy'
        Nanoc::Int::Pattern.from(identifier_to_regex(arg))
      else
        raise(
          Nanoc::Int::Errors::GenericTrivial,
          "Invalid string_pattern_type: #{@config[:string_pattern_type]}",
        )
      end
    end

    private

    # Converts the given identifier, which can contain the '*' or '+'
    # wildcard characters, matching zero or more resp. one or more
    # characters, to a regex. For example, 'foo/*/bar' is transformed
    # into /^foo\/(.*?)\/bar$/ and 'foo+' is transformed into /^foo(.+?)/.
    def identifier_to_regex(identifier)
      if identifier.is_a? String
        # Add leading/trailing slashes if necessary
        new_identifier = identifier.dup
        new_identifier[/^/] = '/' if identifier[0, 1] != '/'
        new_identifier[/$/] = '/?' unless ['*', '/'].include?(identifier[-1, 1])

        regex_string =
          new_identifier
          .gsub('.', '\.')
          .gsub('*', '(.*?)')
          .gsub('+', '(.+?)')

        /^#{regex_string}$/
      else
        identifier
      end
    end
  end
end
nanoc-4.8.0/lib/nanoc/rule_dsl/rules_loader.rb0000644000004100000410000000152413134141051021326 0ustar  www-datawww-data# frozen_string_literal: true

module Nanoc::RuleDSL
  # @api private
  class RulesLoader
    def initialize(config, rules_collection)
      @dsl = Nanoc::RuleDSL::CompilerDSL.new(rules_collection, config)
    end

    def load
      # Find rules file
      rules_filenames = ['Rules', 'rules', 'Rules.rb', 'rules.rb']
      rules_filename = rules_filenames.find { |f| File.file?(f) }
      raise Nanoc::Int::Errors::NoRulesFileFound.new if rules_filename.nil?

      parse(rules_filename)
    end

    def parse(rules_filename)
      rules_filename = File.absolute_path(rules_filename)

      # Get rule data
      data = File.read(rules_filename)

      old_rules_filename = @dsl.rules_filename
      @dsl.rules_filename = rules_filename
      @dsl.instance_eval(data, rules_filename)
      @dsl.rules_filename = old_rules_filename
    end
  end
end
nanoc-4.8.0/lib/nanoc/rule_dsl/action_sequence_calculator.rb0000644000004100000410000001113213134141051024220 0ustar  www-datawww-data# frozen_string_literal: true

module Nanoc::RuleDSL
  # Calculates action sequences for objects that can be run through a rule (item
  # representations and layouts).
  #
  # @api private
  class ActionSequenceCalculator
    extend Nanoc::Int::Memoization

    class UnsupportedObjectTypeException < ::Nanoc::Error
      def initialize(obj)
        super("Do not know how to calculate the action sequence for #{obj.inspect}")
      end
    end

    class NoActionSequenceForLayoutException < ::Nanoc::Error
      def initialize(layout)
        super("There is no layout rule specified for #{layout.inspect}")
      end
    end

    class NoActionSequenceForItemRepException < ::Nanoc::Error
      def initialize(item)
        super("There is no compilation rule specified for #{item.inspect}")
      end
    end

    class PathWithoutInitialSlashError < ::Nanoc::Error
      def initialize(rep, basic_path)
        super("The path returned for the #{rep.inspect} item representation, “#{basic_path}”, does not start with a slash. Please ensure that all routing rules return a path that starts with a slash.")
      end
    end

    # @api private
    attr_accessor :rules_collection

    # @param [Nanoc::Int::Site] site
    # @param [Nanoc::RuleDSL::RulesCollection] rules_collection
    def initialize(site:, rules_collection:)
      @site = site
      @rules_collection = rules_collection
    end

    # @param [#reference] obj
    #
    # @return [Nanoc::Int::ActionSequence]
    def [](obj)
      case obj
      when Nanoc::Int::ItemRep
        new_action_sequence_for_rep(obj)
      when Nanoc::Int::Layout
        new_action_sequence_for_layout(obj)
      else
        raise UnsupportedObjectTypeException.new(obj)
      end
    end

    def new_action_sequence_for_rep(rep)
      dependency_tracker = Nanoc::Int::DependencyTracker::Null.new
      view_context = @site.compiler.compilation_context.create_view_context(dependency_tracker)

      executor = Nanoc::RuleDSL::RecordingExecutor.new(rep)
      rule = @rules_collection.compilation_rule_for(rep)

      unless rule
        raise NoActionSequenceForItemRepException.new(rep)
      end

      executor.snapshot(:raw)
      rule.apply_to(rep, executor: executor, site: @site, view_context: view_context)
      executor.snapshot(:post) if executor.any_layouts?
      executor.snapshot(:last) unless executor.last_snapshot?
      executor.snapshot(:pre) unless executor.pre_snapshot?

      copy_paths_from_routing_rules(compact_snapshots(executor.action_sequence), rep: rep)
    end

    # @param [Nanoc::Int::Layout] layout
    #
    # @return [Nanoc::Int::ActionSequence]
    def new_action_sequence_for_layout(layout)
      res = @rules_collection.filter_for_layout(layout)

      unless res
        raise NoActionSequenceForLayoutException.new(layout)
      end

      Nanoc::Int::ActionSequence.build(layout) do |b|
        b.add_filter(res[0], res[1])
      end
    end

    def compact_snapshots(seq)
      actions = []
      seq.actions.each do |action|
        if [actions.last, action].all? { |a| a.is_a?(Nanoc::Int::ProcessingActions::Snapshot) }
          actions[-1] = actions.last.update(snapshot_names: action.snapshot_names, paths: action.paths)
        else
          actions << action
        end
      end
      Nanoc::Int::ActionSequence.new(seq.item_rep, actions: actions)
    end

    def copy_paths_from_routing_rules(seq, rep:)
      seq.map do |action|
        if action.is_a?(Nanoc::Int::ProcessingActions::Snapshot) && action.paths.empty?
          copy_path_from_routing_rule(action, rep: rep)
        else
          action
        end
      end
    end

    def copy_path_from_routing_rule(action, rep:)
      paths_from_rules =
        action.snapshot_names.map do |snapshot_name|
          basic_path_from_rules_for(rep, snapshot_name)
        end.compact

      if paths_from_rules.any?
        action.update(paths: paths_from_rules.map(&:to_s))
      else
        action
      end
    end

    # FIXME: ugly
    def basic_path_from_rules_for(rep, snapshot_name)
      routing_rules = @rules_collection.routing_rules_for(rep)
      routing_rule = routing_rules[snapshot_name]
      return nil if routing_rule.nil?

      dependency_tracker = Nanoc::Int::DependencyTracker::Null.new
      view_context = Nanoc::ViewContext.new(reps: nil, items: nil, dependency_tracker: dependency_tracker, compilation_context: nil, snapshot_repo: nil)
      basic_path = routing_rule.apply_to(rep, executor: nil, site: @site, view_context: view_context)
      if basic_path && !basic_path.start_with?('/')
        raise PathWithoutInitialSlashError.new(rep, basic_path)
      end
      basic_path
    end
  end
end
nanoc-4.8.0/lib/nanoc/rule_dsl/action_provider.rb0000644000004100000410000000562213134141051022040 0ustar  www-datawww-data# frozen_string_literal: true

module Nanoc::RuleDSL
  class ActionProvider < Nanoc::Int::ActionProvider
    identifier :rule_dsl

    # @api private
    attr_reader :rules_collection

    def self.for(site)
      rules_collection = Nanoc::RuleDSL::RulesCollection.new

      action_sequence_calculator =
        Nanoc::RuleDSL::ActionSequenceCalculator.new(
          rules_collection: rules_collection, site: site,
        )

      action_provider = new(rules_collection, action_sequence_calculator)

      Nanoc::RuleDSL::RulesLoader.new(site.config, rules_collection).load

      action_provider
    end

    def initialize(rules_collection, action_sequence_calculator)
      @rules_collection = rules_collection
      @action_sequence_calculator = action_sequence_calculator
    end

    def rep_names_for(item)
      matching_rules = @rules_collection.item_compilation_rules_for(item)
      raise Nanoc::Int::Errors::NoMatchingCompilationRuleFound.new(item) if matching_rules.empty?

      matching_rules.map(&:rep_name).uniq
    end

    def action_sequence_for(rep)
      @action_sequence_calculator[rep]
    end

    def need_preprocessing?
      @rules_collection.preprocessors.any?
    end

    def preprocess(site)
      ctx = new_preprocessor_context(site)

      @rules_collection.preprocessors.each_value do |preprocessor|
        ctx.instance_eval(&preprocessor)
      end

      site.data_source =
        Nanoc::Int::InMemDataSource.new(ctx.items.unwrap, ctx.layouts.unwrap)
    end

    def postprocess(site, reps)
      dependency_tracker = Nanoc::Int::DependencyTracker::Null.new
      view_context =
        Nanoc::ViewContext.new(
          reps: reps,
          items: site.items,
          dependency_tracker: dependency_tracker,
          compilation_context: site.compiler.compilation_context,
          snapshot_repo: nil,
        )
      ctx = new_postprocessor_context(site, view_context)

      @rules_collection.postprocessors.each_value do |postprocessor|
        ctx.instance_eval(&postprocessor)
      end
    end

    # @api private
    def new_preprocessor_context(site)
      dependency_tracker = Nanoc::Int::DependencyTracker::Null.new
      view_context =
        Nanoc::ViewContext.new(
          reps: nil,
          items: nil,
          dependency_tracker: dependency_tracker,
          compilation_context: nil,
          snapshot_repo: nil,
        )

      Nanoc::Int::Context.new(
        config: Nanoc::MutableConfigView.new(site.config, view_context),
        items: Nanoc::MutableItemCollectionView.new(site.items, view_context),
        layouts: Nanoc::MutableLayoutCollectionView.new(site.layouts, view_context),
      )
    end

    # @api private
    def new_postprocessor_context(site, view_context)
      Nanoc::Int::Context.new(
        config: Nanoc::ConfigView.new(site.config, view_context),
        items: Nanoc::PostCompileItemCollectionView.new(site.items, view_context),
      )
    end
  end
end
nanoc-4.8.0/lib/nanoc/rule_dsl/rule_context.rb0000644000004100000410000000637013134141051021365 0ustar  www-datawww-data# frozen_string_literal: true

module Nanoc::RuleDSL
  # Provides a context in which compilation and routing rules can be executed.
  # It provides access to the item representation that is being compiled or
  # routed.
  #
  # @api private
  class RuleContext < Nanoc::Int::Context
    # @param [Nanoc::Int::ItemRep] rep
    # @param [Nanoc::Int::Site] site
    # @param [Nanoc::Int::Executor, Nanoc::RuleDSL::RecordingExecutor] executor
    # @param [Nanoc::ViewContext] view_context
    def initialize(rep:, site:, executor:, view_context:)
      @_executor = executor

      super({
        item: Nanoc::ItemWithoutRepsView.new(rep.item, view_context),
        rep: Nanoc::ItemRepView.new(rep, view_context),
        item_rep: Nanoc::ItemRepView.new(rep, view_context),
        items: Nanoc::ItemCollectionWithoutRepsView.new(site.items, view_context),
        layouts: Nanoc::LayoutCollectionView.new(site.layouts, view_context),
        config: Nanoc::ConfigView.new(site.config, view_context),
      })
    end

    # Filters the current representation (calls {Nanoc::Int::ItemRep#filter} with
    # the given arguments on the rep).
    #
    # @see Nanoc::Int::ItemRep#filter
    #
    # @param [Symbol] filter_name The name of the filter to run the item
    #   representations' content through
    #
    # @param [Hash] filter_args The filter arguments that should be passed to
    #   the filter's #run method
    #
    # @return [void]
    def filter(filter_name, filter_args = {})
      @_executor.filter(filter_name, filter_args)
    end

    # Layouts the current representation (calls {Nanoc::Int::ItemRep#layout} with
    # the given arguments on the rep).
    #
    # @see Nanoc::Int::ItemRep#layout
    #
    # @param [String] layout_identifier The identifier of the layout the item
    #   should be laid out with
    #
    # @return [void]
    def layout(layout_identifier, extra_filter_args = nil)
      @_executor.layout(layout_identifier, extra_filter_args)
    end

    # Creates a snapshot of the current compiled item content. Calls
    # {Nanoc::Int::ItemRep#snapshot} with the given arguments on the rep.
    #
    # @see Nanoc::Int::ItemRep#snapshot
    #
    # @param [Symbol] snapshot_name The name of the snapshot to create
    #
    # @param [String, nil] path
    #
    # @return [void]
    def snapshot(snapshot_name, path: nil)
      @_executor.snapshot(snapshot_name, path: path)
    end

    # Creates a snapshot named :last the current compiled item content, with
    # the given path. This is a convenience method for {#snapshot}.
    #
    # @see #snapshot
    #
    # @param [String] path
    #
    # @return [void]
    def write(arg)
      @_write_snapshot_counter ||= 0
      snapshot_name = "_#{@_write_snapshot_counter}".to_sym
      @_write_snapshot_counter += 1

      case arg
      when String, Nanoc::Identifier
        snapshot(snapshot_name, path: arg)
      when Hash
        if arg.key?(:ext)
          ext = arg[:ext].sub(/\A\./, '')
          path = @item.identifier.without_exts + '.' + ext
          snapshot(snapshot_name, path: path)
        else
          raise ArgumentError, 'Cannot call #write this way (need path or :ext)'
        end
      else
        raise ArgumentError, 'Cannot call #write this way (need path or :ext)'
      end
    end
  end
end
nanoc-4.8.0/lib/nanoc/rule_dsl/recording_executor.rb0000644000004100000410000000343213134141051022540 0ustar  www-datawww-data# frozen_string_literal: true

module Nanoc
  module RuleDSL
    class RecordingExecutor
      include Nanoc::Int::ContractsSupport

      contract Nanoc::Int::ItemRep => C::Any
      def initialize(rep)
        @action_sequence_builder = Nanoc::Int::ActionSequenceBuilder.new(rep)

        @any_layouts = false
        @last_snapshot = false
        @pre_snapshot = false
      end

      def filter(filter_name, filter_args = {})
        @action_sequence_builder.add_filter(filter_name, filter_args)
      end

      def layout(layout_identifier, extra_filter_args = {})
        unless layout_identifier.is_a?(String)
          raise ArgumentError.new('The layout passed to #layout must be a string')
        end

        unless any_layouts?
          @pre_snapshot = true
          @action_sequence_builder.add_snapshot(:pre, nil)
        end

        @action_sequence_builder.add_layout(layout_identifier, extra_filter_args)
        @any_layouts = true
      end

      Pathlike = C::Maybe[C::Or[String, Nanoc::Identifier]]
      contract Symbol, C::KeywordArgs[path: C::Optional[Pathlike]] => nil
      def snapshot(snapshot_name, path: nil)
        @action_sequence_builder.add_snapshot(snapshot_name, path && path.to_s)
        case snapshot_name
        when :last
          @last_snapshot = true
        when :pre
          @pre_snapshot = true
        end
        nil
      end

      contract C::None => Nanoc::Int::ActionSequence
      def action_sequence
        @action_sequence_builder.action_sequence
      end

      contract C::None => C::Bool
      def any_layouts?
        @any_layouts
      end

      contract C::None => C::Bool
      def last_snapshot?
        @last_snapshot
      end

      contract C::None => C::Bool
      def pre_snapshot?
        @pre_snapshot
      end
    end
  end
end
nanoc-4.8.0/lib/nanoc/cli/0000755000004100000410000000000013134141051015255 5ustar  www-datawww-datananoc-4.8.0/lib/nanoc/cli/ansi_string_colorizer.rb0000644000004100000410000000131113134141051022206 0ustar  www-datawww-data# frozen_string_literal: true

module Nanoc::CLI
  # A simple ANSI colorizer for strings. When given a string and a list of
  # attributes, it returns a colorized string.
  #
  # @api private
  module ANSIStringColorizer
    # TODO: complete mapping
    MAPPING = {
      bold: "\e[1m",
      red: "\e[31m",
      green: "\e[32m",
      yellow: "\e[33m",
      blue: "\e[34m",
    }.freeze

    # @param [String] s The string to colorize
    #
    # @param [Array] as An array of attributes from `MAPPING` to colorize the
    #   string with
    #
    # @return [String] A string colorized using the given attributes
    def self.c(s, *as)
      as.map { |a| MAPPING[a] }.join('') + s + "\e[0m"
    end
  end
end
nanoc-4.8.0/lib/nanoc/cli/commands/0000755000004100000410000000000013134141051017056 5ustar  www-datawww-datananoc-4.8.0/lib/nanoc/cli/commands/check.rb0000644000004100000410000000254513134141051020466 0ustar  www-datawww-data# frozen_string_literal: true

usage 'check [options] [names]'
summary 'run issue checks'
description "
Run issue checks on the current site. If the `--all` option is passed, all available issue checks will be run. If the `--deploy` option is passed, the issue checks marked for deployment will be run.
"

flag :a, :all,    'run all checks'
flag :L, :list,   'list all checks'
flag :d, :deploy, 'run checks for deployment'

module Nanoc::CLI::Commands
  class Check < ::Nanoc::CLI::CommandRunner
    def run
      validate_options_and_arguments
      load_site(preprocess: true)

      runner = Nanoc::Checking::Runner.new(site)

      if options[:list]
        runner.list_checks
        return
      end

      success =
        if options[:all]
          runner.run_all
        elsif options[:deploy]
          runner.run_for_deploy
        else
          runner.run_specific(arguments)
        end

      unless success
        raise Nanoc::Int::Errors::GenericTrivial, 'One or more checks failed'
      end
    end

    protected

    def validate_options_and_arguments
      if arguments.empty? && !options[:all] && !options[:deploy] && !options[:list]
        raise(
          Nanoc::Int::Errors::GenericTrivial,
          'nothing to do (pass either --all, --deploy or --list or a list of checks)',
        )
      end
    end
  end
end

runner Nanoc::CLI::Commands::Check
nanoc-4.8.0/lib/nanoc/cli/commands/shell.rb0000644000004100000410000000261513134141051020516 0ustar  www-datawww-data# frozen_string_literal: true

usage 'shell'
summary 'open a shell on the Nanoc environment'
aliases 'console'
description "
Open an IRB shell on a context that contains @items, @layouts, and @config.
"
flag :p, :preprocess, 'run preprocessor'

module Nanoc::CLI::Commands
  class Shell < ::Nanoc::CLI::CommandRunner
    def run
      require 'pry'

      load_site(preprocess: options[:preprocess])

      Nanoc::Int::Context.new(env).pry
    end

    def env
      self.class.env_for_site(site)
    end

    def self.reps_for(site)
      Nanoc::Int::ItemRepRepo.new.tap do |reps|
        action_provider = Nanoc::Int::ActionProvider.named(:rule_dsl).for(site)
        builder = Nanoc::Int::ItemRepBuilder.new(site, action_provider, reps)
        builder.run
      end
    end

    def self.view_context_for(site)
      Nanoc::ViewContext.new(
        reps: reps_for(site),
        items: site.items,
        dependency_tracker: Nanoc::Int::DependencyTracker::Null.new,
        compilation_context: nil,
        snapshot_repo: nil,
      )
    end

    def self.env_for_site(site)
      view_context = view_context_for(site)

      {
        items: Nanoc::ItemCollectionWithRepsView.new(site.items, view_context),
        layouts: Nanoc::LayoutCollectionView.new(site.layouts, view_context),
        config: Nanoc::ConfigView.new(site.config, view_context),
      }
    end
  end
end

runner Nanoc::CLI::Commands::Shell
nanoc-4.8.0/lib/nanoc/cli/commands/view.rb0000644000004100000410000000417113134141051020360 0ustar  www-datawww-data# frozen_string_literal: true

usage 'view [options]'
summary 'start the web server that serves static files'
description <<~EOS
  Start the static web server. Unless specified, the web server will run on port
  3000 and listen on all IP addresses. Running this static web server requires
  `adsf` (not `asdf`!).
EOS

required :H, :handler, 'specify the handler to use (webrick/mongrel/...)'
required :o, :host,    'specify the host to listen on (default: 0.0.0.0)'
required :p, :port,    'specify the port to listen on (default: 3000)'

module Nanoc::CLI::Commands
  class View < ::Nanoc::CLI::CommandRunner
    DEFAULT_HANDLER_NAME = :thin

    def run
      load_adsf
      require 'rack'

      load_site

      # Set options
      options_for_rack = {
        Port: (options[:port] || 3000).to_i,
        Host: (options[:host] || '0.0.0.0'),
      }

      # Get handler
      if options.key?(:handler)
        handler = Rack::Handler.get(options[:handler])
      else
        begin
          handler = Rack::Handler.get(DEFAULT_HANDLER_NAME)
        rescue LoadError
          handler = Rack::Handler::WEBrick
        end
      end

      # Build app
      site = self.site
      app = Rack::Builder.new do
        use Rack::CommonLogger
        use Rack::ShowExceptions
        use Rack::Lint
        use Rack::Head
        use Adsf::Rack::IndexFileFinder,
            root: site.config[:output_dir],
            index_filenames: site.config[:index_filenames]
        run Rack::File.new(site.config[:output_dir])
      end.to_app

      # Run autocompiler
      handler.run(app, options_for_rack)
    end

    protected

    def load_adsf
      # Load adsf
      begin
        require 'adsf'
        return
      rescue LoadError
        $stderr.puts "Could not find the required 'adsf' gem, " \
          'which is necessary for the view command.'
      end

      # Check asdf
      begin
        require 'asdf'
        $stderr.puts "You appear to have 'asdf' installed, " \
          "but not 'adsf'. Please install 'adsf' (check the spelling)!"
      rescue LoadError
      end

      # Done
      exit 1
    end
  end
end

runner Nanoc::CLI::Commands::View
nanoc-4.8.0/lib/nanoc/cli/commands/create-site.rb0000644000004100000410000002620013134141051021610 0ustar  www-datawww-data# frozen_string_literal: true

usage 'create-site [options] path'
aliases :create_site, :cs
summary 'create a site'
description 'Create a new site at the given path. The site will use the `filesystem` data source.'
flag nil, :force, 'force creation of new site'

module Nanoc::CLI::Commands
  class CreateSite < ::Nanoc::CLI::CommandRunner
    class << self
      protected

      # Converts the given array to YAML format
      def array_to_yaml(array)
        '[ ' + array.map { |s| "'" + s + "'" }.join(', ') + ' ]'
      end
    end

    DEFAULT_CONFIG = <<~EOS unless defined? DEFAULT_CONFIG
      # The syntax to use for patterns in the Rules file. Can be either `"glob"`
      # (default) or `"legacy"`. The former will enable glob patterns, which behave
      # like Ruby’s File.fnmatch. The latter will enable Nanoc 3.x-style patterns.
      string_pattern_type: glob

      # A list of file extensions that Nanoc will consider to be textual rather than
      # binary. If an item with an extension not in this list is found,  the file
      # will be considered as binary.
      text_extensions: #{array_to_yaml(Nanoc::Int::Configuration::DEFAULT_CONFIG[:text_extensions])}

      # The path to the directory where all generated files will be written to. This
      # can be an absolute path starting with a slash, but it can also be path
      # relative to the site directory.
      output_dir: #{Nanoc::Int::Configuration::DEFAULT_CONFIG[:output_dir]}

      # A list of index filenames, i.e. names of files that will be served by a web
      # server when a directory is requested. Usually, index files are named
      # “index.html”, but depending on the web server, this may be something else,
      # such as “default.htm”. This list is used by Nanoc to generate pretty URLs.
      index_filenames: #{array_to_yaml(Nanoc::Int::Configuration::DEFAULT_CONFIG[:index_filenames])}

      # Whether or not to generate a diff of the compiled content when compiling a
      # site. The diff will contain the differences between the compiled content
      # before and after the last site compilation.
      enable_output_diff: false

      prune:
        # Whether to automatically remove files not managed by Nanoc from the output
        # directory.
        auto_prune: true

        # Which files and directories you want to exclude from pruning. If you version
        # your output directory, you should probably exclude VCS directories such as
        # .git, .svn etc.
        exclude: [ '.git', '.hg', '.svn', 'CVS' ]

      # The data sources where Nanoc loads its data from. This is an array of
      # hashes; each array element represents a single data source. By default,
      # there is only a single data source that reads data from the “content/” and
      # “layout/” directories in the site directory.
      data_sources:
        -
          # The type is the identifier of the data source.
          type: #{Nanoc::Int::Configuration::DEFAULT_DATA_SOURCE_CONFIG[:type]}

          # The path where items should be mounted (comparable to mount points in
          # Unix-like systems). This is “/” by default, meaning that items will have
          # “/” prefixed to their identifiers. If the items root were “/en/”
          # instead, an item at content/about.html would have an identifier of
          # “/en/about/” instead of just “/about/”.
          items_root: #{Nanoc::Int::Configuration::DEFAULT_DATA_SOURCE_CONFIG[:items_root]}

          # The path where layouts should be mounted. The layouts root behaves the
          # same as the items root, but applies to layouts rather than items.
          layouts_root: #{Nanoc::Int::Configuration::DEFAULT_DATA_SOURCE_CONFIG[:layouts_root]}

          # The encoding to use for input files. If your input files are not in
          # UTF-8 (which they should be!), change this.
          encoding: utf-8

          # The kind of identifier to use for items and layouts. The default is
          # “full”, meaning that identifiers include file extensions. This can also
          # be “legacy”, primarily used by older Nanoc sites.
          identifier_type: full

      # Configuration for the “check” command, which run unit tests on the site.
      checks:
        # Configuration for the “internal_links” checker, which checks whether all
        # internal links are valid.
        internal_links:
          # A list of patterns, specified as regular expressions, to exclude from the check.
          # If an internal link matches this pattern, the validity check will be skipped.
          # E.g.:
          #   exclude: ['^/server_status']
          exclude: []

        # Configuration for the “external_links” checker, which checks whether all
        # external links are valid.
        external_links:
          # A list of patterns, specified as regular expressions, to exclude from the check.
          # If an external link matches this pattern, the validity check will be skipped.
          # E.g.:
          #   exclude: ['^http://example.com$']
          exclude: []

          # A list of file patterns, specified as regular expressions, to exclude from the check.
          # If a file matches this pattern, the links from this file will not be checked.
          # E.g.:
          #   exclude_files: ['blog/page']
          exclude_files: []
EOS

    DEFAULT_RULES = <<~EOS unless defined? DEFAULT_RULES
      #!/usr/bin/env ruby

      compile '/**/*.html' do
        layout '/default.*'
      end

      # This is an example rule that matches Markdown (.md) files, and filters them
      # using the :kramdown filter. It is commented out by default, because kramdown
      # is not bundled with Nanoc or Ruby.
      #
      #compile '/**/*.md' do
      #  filter :kramdown
      #  layout '/default.*'
      #end

      route '/**/*.{html,md}' do
        if item.identifier =~ '/index.*'
          '/index.html'
        else
          item.identifier.without_ext + '/index.html'
        end
      end

      compile '/**/*' do
        write item.identifier.to_s
      end

      layout '/**/*', :erb
EOS

    DEFAULT_ITEM = <<~EOS unless defined? DEFAULT_ITEM
      ---
      title: Home
      ---

      

A Brand New Nanoc Site

You’ve just created a new Nanoc site. The page you are looking at right now is the home page for your site. To get started, consider replacing this default homepage with your own customized homepage. Some pointers on how to do so:

  • Change this page’s content by editing the “index.html” file in the “content” directory. This is the actual page content, and therefore doesn’t include the header, sidebar or style information (those are part of the layout).

  • Change the layout, which is the “default.html” file in the “layouts” directory, and create something unique (and hopefully less bland).

If you need any help with customizing your Nanoc web site, be sure to check out the documentation (see sidebar), and be sure to subscribe to the discussion group (also see sidebar). Enjoy!

EOS DEFAULT_STYLESHEET = <<~EOS unless defined? DEFAULT_STYLESHEET * { margin: 0; padding: 0; font-family: Georgia, Palatino, serif; } body { background: #fff; } a { text-decoration: none; } a:link, a:visited { color: #f30; } a:hover { color: #f90; } #main { position: absolute; top: 40px; left: 280px; width: 500px; } #main h1 { font-size: 40px; font-weight: normal; line-height: 40px; letter-spacing: -1px; } #main p { margin: 20px 0; font-size: 15px; line-height: 20px; } #main ul, #main ol { margin: 20px; } #main li { font-size: 15px; line-height: 20px; } #main ul li { list-style-type: square; } #sidebar { position: absolute; top: 40px; left: 20px; width: 200px; padding: 20px 20px 0 0; border-right: 1px solid #ccc; text-align: right; } #sidebar h2 { text-transform: uppercase; font-size: 13px; color: #333; letter-spacing: 1px; line-height: 20px; } #sidebar ul { list-style-type: none; margin: 20px 0; } #sidebar li { font-size: 14px; line-height: 20px; } EOS DEFAULT_LAYOUT = <<~EOS unless defined? DEFAULT_LAYOUT A Brand New Nanoc Site - <%= @item[:title] %>
<%= yield %>
EOS def run # Extract arguments if arguments.length != 1 raise Nanoc::Int::Errors::GenericTrivial, "usage: #{command.usage}" end path = arguments[0] # Check whether site exists if File.exist?(path) && (!File.directory?(path) || !(Dir.entries(path) - %w[. ..]).empty?) && !options[:force] raise( Nanoc::Int::Errors::GenericTrivial, "The site was not created because '#{path}' already exists. " \ 'Re-run the command using --force to create the site anyway.', ) end # Setup notifications Nanoc::Int::NotificationCenter.on(:file_created) do |file_path| Nanoc::CLI::Logger.instance.file(:high, :create, file_path) end # Build entire site FileUtils.mkdir_p(path) FileUtils.cd(File.join(path)) do FileUtils.mkdir_p('content') FileUtils.mkdir_p('layouts') FileUtils.mkdir_p('lib') FileUtils.mkdir_p('output') write('nanoc.yaml', DEFAULT_CONFIG) write('Rules', DEFAULT_RULES) write('content/index.html', DEFAULT_ITEM) write('content/stylesheet.css', DEFAULT_STYLESHEET) write('layouts/default.html', DEFAULT_LAYOUT) end puts "Created a blank nanoc site at '#{path}'. Enjoy!" end private def write(filename, content) File.write(filename, content) Nanoc::Int::NotificationCenter.post(:file_created, filename) end end end runner Nanoc::CLI::Commands::CreateSite nanoc-4.8.0/lib/nanoc/cli/commands/compile_listeners/0000755000004100000410000000000013134141051022576 5ustar www-datawww-datananoc-4.8.0/lib/nanoc/cli/commands/compile_listeners/file_action_printer.rb0000644000004100000410000000326413134141051027147 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::CLI::Commands::CompileListeners class FileActionPrinter < Abstract def initialize(reps:) @start_times = {} @acc_durations = {} @reps = reps end # @see Listener#start def start Nanoc::Int::NotificationCenter.on(:compilation_started, self) do |rep| @start_times[rep] = Time.now @acc_durations[rep] ||= 0.0 end Nanoc::Int::NotificationCenter.on(:compilation_suspended, self) do |rep| @acc_durations[rep] += Time.now - @start_times[rep] end Nanoc::Int::NotificationCenter.on(:rep_written, self) do |rep, _binary, path, is_created, is_modified| @acc_durations[rep] += Time.now - @start_times[rep] duration = @acc_durations[rep] action = if is_created then :create elsif is_modified then :update else :identical end level = if is_created then :high elsif is_modified then :high else :low end log(level, action, path, duration) end end # @see Listener#stop def stop super Nanoc::Int::NotificationCenter.remove(:compilation_started, self) Nanoc::Int::NotificationCenter.remove(:compilation_suspended, self) Nanoc::Int::NotificationCenter.remove(:rep_written, self) @reps.reject(&:compiled?).each do |rep| raw_paths = rep.raw_paths.values.flatten.uniq raw_paths.each do |raw_path| log(:low, :skip, raw_path, nil) end end end private def log(level, action, path, duration) Nanoc::CLI::Logger.instance.file(level, action, path, duration) end end end nanoc-4.8.0/lib/nanoc/cli/commands/compile_listeners/timing_recorder.rb0000644000004100000410000001360713134141051026306 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::CLI::Commands::CompileListeners class TimingRecorder < Abstract attr_reader :telemetry # @see Listener#enable_for? def self.enable_for?(_command_runner) Nanoc::CLI.verbosity >= 1 end # @param [Enumerable] reps def initialize(reps:) @reps = reps @telemetry = Nanoc::Telemetry.new end # @see Listener#start def start stage_stopwatch = Nanoc::Telemetry::Stopwatch.new on(:stage_started) do |_klass| stage_stopwatch.start end on(:stage_ended) do |klass| stage_stopwatch.stop name = klass.to_s.sub(/.*::/, '') @telemetry.summary(:stages).observe(stage_stopwatch.duration, name) stage_stopwatch = Nanoc::Telemetry::Stopwatch.new end outdatedness_rule_stopwatches = {} on(:outdatedness_rule_started) do |klass, obj| stopwatches = outdatedness_rule_stopwatches.fetch(klass) { outdatedness_rule_stopwatches[klass] = {} } stopwatch = stopwatches.fetch(obj) { stopwatches[obj] = Nanoc::Telemetry::Stopwatch.new } stopwatch.start end on(:outdatedness_rule_ended) do |klass, obj| stopwatches = outdatedness_rule_stopwatches.fetch(klass) stopwatch = stopwatches.fetch(obj) stopwatch.stop name = klass.to_s.sub(/.*::/, '') @telemetry.summary(:outdatedness_rules).observe(stopwatch.duration, name) end filter_stopwatches = {} on(:filtering_started) do |rep, _filter_name| stopwatch_stack = filter_stopwatches.fetch(rep) { filter_stopwatches[rep] = [] } stopwatch_stack << Nanoc::Telemetry::Stopwatch.new stopwatch_stack.last.start end on(:filtering_ended) do |rep, filter_name| stopwatch = filter_stopwatches.fetch(rep).pop stopwatch.stop @telemetry.summary(:filters).observe(stopwatch.duration, filter_name.to_s) end on(:compilation_suspended) do |rep, _exception| filter_stopwatches.fetch(rep).each(&:stop) end on(:compilation_started) do |rep| filter_stopwatches.fetch(rep, []).each(&:start) end phase_stopwatches = {} on(:phase_started) do |phase_name, rep| stopwatches = phase_stopwatches.fetch(rep) { phase_stopwatches[rep] = {} } stopwatches[phase_name] = Nanoc::Telemetry::Stopwatch.new.tap(&:start) end on(:phase_ended) do |phase_name, rep| stopwatch = phase_stopwatches.fetch(rep).fetch(phase_name) stopwatch.stop @telemetry.summary(:phases).observe(stopwatch.duration, phase_name) end on(:phase_yielded) do |phase_name, rep| stopwatch = phase_stopwatches.fetch(rep).fetch(phase_name) stopwatch.stop end on(:phase_resumed) do |phase_name, rep| stopwatch = phase_stopwatches.fetch(rep).fetch(phase_name) stopwatch.start if stopwatch.stopped? end on(:phase_aborted) do |phase_name, rep| stopwatch = phase_stopwatches.fetch(rep).fetch(phase_name) stopwatch.stop if stopwatch.running? @telemetry.summary(:phases).observe(stopwatch.duration, phase_name) end on(:memoization_miss) do |label| @telemetry.counter(:memoization).increment([label, :miss]) end on(:memoization_hit) do |label| @telemetry.counter(:memoization).increment([label, :hit]) end end # @see Listener#stop def stop print_profiling_feedback super end protected def table_for_summary(name) headers = [name.to_s, 'count', 'min', '.50', '.90', '.95', 'max', 'tot'] rows = @telemetry.summary(name).map do |filter_name, summary| count = summary.count min = summary.min p50 = summary.quantile(0.50) p90 = summary.quantile(0.90) p95 = summary.quantile(0.95) tot = summary.sum max = summary.max [filter_name, count.to_s] + [min, p50, p90, p95, max, tot].map { |r| "#{format('%4.2f', r)}s" } end [headers] + rows end def table_for_summary_durations(name) headers = [name.to_s, 'tot'] rows = @telemetry.summary(:stages).map do |stage_name, summary| [stage_name, "#{format('%4.2f', summary.sum)}s"] end [headers] + rows end def table_for_memoization headers = %w[memoization hit miss %] rows_raw = @telemetry.counter(:memoization).map do |(name, type), counter| { name: name, type: type, count: counter.value } end rows = rows_raw.group_by { |r| r[:name] }.map do |name, rows_for_name| rows_by_type = rows_for_name.group_by { |r| r[:type] } num_hit = rows_by_type.fetch(:hit, []).fetch(0, {}).fetch(:count, 0) num_miss = rows_by_type.fetch(:miss, []).fetch(0, {}).fetch(:count, 0) pct = num_hit.to_f / (num_hit + num_miss).to_f [name, num_hit.to_s, num_miss.to_s, "#{format('%3.1f', pct * 100)}%"] end [headers] + rows end def print_profiling_feedback print_table_for_summary(:filters) print_table_for_summary(:phases) if Nanoc::CLI.verbosity >= 2 print_table_for_summary_duration(:stages) if Nanoc::CLI.verbosity >= 2 print_table_for_summary(:outdatedness_rules) if Nanoc::CLI.verbosity >= 2 print_table_for_memoization if Nanoc::CLI.verbosity >= 2 end def print_table_for_summary(name) return if @telemetry.summary(name).empty? puts print_table(table_for_summary(name)) end def print_table_for_summary_duration(name) return if @telemetry.summary(name).empty? puts print_table(table_for_summary_durations(name)) end def print_table_for_memoization return if @telemetry.counter(:memoization).empty? puts print_table(table_for_memoization) end def print_table(rows) puts Nanoc::Telemetry::Table.new(rows).to_s end end end nanoc-4.8.0/lib/nanoc/cli/commands/compile_listeners/diff_generator.rb0000644000004100000410000000477013134141051026111 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::CLI::Commands::CompileListeners class DiffGenerator < Abstract # @see Listener#enable_for? def self.enable_for?(command_runner) command_runner.site.config[:enable_output_diff] || command_runner.options[:diff] end # @see Listener#start def start require 'tempfile' setup_diffs old_contents = {} Nanoc::Int::NotificationCenter.on(:will_write_rep, self) do |rep, path| old_contents[rep] = File.file?(path) ? File.read(path) : nil end Nanoc::Int::NotificationCenter.on(:rep_written, self) do |rep, binary, path, _is_created, _is_modified| unless binary new_contents = File.file?(path) ? File.read(path) : nil if old_contents[rep] && new_contents generate_diff_for(path, old_contents[rep], new_contents) end old_contents.delete(rep) end end end # @see Listener#stop def stop super Nanoc::Int::NotificationCenter.remove(:will_write_rep, self) Nanoc::Int::NotificationCenter.remove(:rep_written, self) teardown_diffs end protected def setup_diffs @diff_lock = Mutex.new @diff_threads = [] FileUtils.rm('output.diff') if File.file?('output.diff') end def teardown_diffs @diff_threads.each(&:join) end def generate_diff_for(path, old_content, new_content) return if old_content == new_content @diff_threads << Thread.new do # Generate diff diff = diff_strings(old_content, new_content) diff.sub!(/^--- .*/, '--- ' + path) diff.sub!(/^\+\+\+ .*/, '+++ ' + path) # Write diff @diff_lock.synchronize do File.open('output.diff', 'a') { |io| io.write(diff) } end end end def diff_strings(a, b) require 'open3' # Create files Tempfile.open('old') do |old_file| Tempfile.open('new') do |new_file| # Write files old_file.write(a) old_file.flush new_file.write(b) new_file.flush # Diff cmd = ['diff', '-u', old_file.path, new_file.path] Open3.popen3(*cmd) do |_stdin, stdout, _stderr| result = stdout.read return (result == '' ? nil : result) end end end rescue Errno::ENOENT warn 'Failed to run `diff`, so no diff with the previously compiled ' \ 'content will be available.' nil end end end nanoc-4.8.0/lib/nanoc/cli/commands/compile_listeners/debug_printer.rb0000644000004100000410000000254313134141051025760 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::CLI::Commands::CompileListeners class DebugPrinter < Abstract # @see Listener#enable_for? def self.enable_for?(command_runner) command_runner.debug? end # @see Listener#start def start Nanoc::Int::NotificationCenter.on(:compilation_started) do |rep| puts "*** Started compilation of #{rep.inspect}" end Nanoc::Int::NotificationCenter.on(:compilation_ended) do |rep| puts "*** Ended compilation of #{rep.inspect}" puts end Nanoc::Int::NotificationCenter.on(:compilation_suspended) do |rep, e| puts "*** Suspended compilation of #{rep.inspect}: #{e.message}" end Nanoc::Int::NotificationCenter.on(:cached_content_used) do |rep| puts "*** Used cached compiled content for #{rep.inspect} instead of recompiling" end Nanoc::Int::NotificationCenter.on(:filtering_started) do |rep, filter_name| puts "*** Started filtering #{rep.inspect} with #{filter_name}" end Nanoc::Int::NotificationCenter.on(:filtering_ended) do |rep, filter_name| puts "*** Ended filtering #{rep.inspect} with #{filter_name}" end Nanoc::Int::NotificationCenter.on(:dependency_created) do |src, dst| puts "*** Dependency created from #{src.inspect} onto #{dst.inspect}" end end end end nanoc-4.8.0/lib/nanoc/cli/commands/compile_listeners/abstract.rb0000644000004100000410000000117513134141051024732 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::CLI::Commands::CompileListeners class Abstract def initialize(*); end def self.enable_for?(command_runner) # rubocop:disable Lint/UnusedMethodArgument true end def start raise NotImplementedError, "Subclasses of #{self.class} must implement #start" end def stop; end def start_safely start @_started = true end def stop_safely stop if @_started @_started = false end def on(sym) # TODO: clean up on stop Nanoc::Int::NotificationCenter.on(sym, self) { |*args| yield(*args) } end end end nanoc-4.8.0/lib/nanoc/cli/commands/compile.rb0000644000004100000410000000350713134141051021040 0ustar www-datawww-data# frozen_string_literal: true usage 'compile [options]' summary 'compile items of this site' description <<~EOS Compile all items of the current site. EOS flag nil, :diff, 'generate diff' require_relative 'compile_listeners/abstract' require_relative 'compile_listeners/debug_printer' require_relative 'compile_listeners/diff_generator' require_relative 'compile_listeners/file_action_printer' require_relative 'compile_listeners/timing_recorder' module Nanoc::CLI::Commands class Compile < ::Nanoc::CLI::CommandRunner attr_accessor :listener_classes def initialize(options, arguments, command) super @listener_classes = default_listener_classes end def run time_before = Time.now load_site puts 'Compiling site…' run_listeners_while do site.compile end time_after = Time.now puts puts "Site compiled in #{format('%.2f', time_after - time_before)}s." end protected def default_listener_classes [ Nanoc::CLI::Commands::CompileListeners::DiffGenerator, Nanoc::CLI::Commands::CompileListeners::DebugPrinter, Nanoc::CLI::Commands::CompileListeners::TimingRecorder, Nanoc::CLI::Commands::CompileListeners::FileActionPrinter, ] end def setup_listeners @listeners = @listener_classes .select { |klass| klass.enable_for?(self) } .map { |klass| klass.new(reps: reps) } @listeners.each(&:start_safely) end def listeners @listeners end def run_listeners_while setup_listeners yield ensure teardown_listeners end def teardown_listeners return unless @listeners @listeners.reverse_each(&:stop_safely) end def reps site.compiler.reps end end end runner Nanoc::CLI::Commands::Compile nanoc-4.8.0/lib/nanoc/cli/commands/show-rules.rb0000644000004100000410000000315213134141051021514 0ustar www-datawww-data# frozen_string_literal: true usage 'show-rules [thing]' aliases :explain summary 'describe the rules for each item' description " Prints the rules used for all items and layouts in the current site. " module Nanoc::CLI::Commands class ShowRules < ::Nanoc::CLI::CommandRunner def run load_site @c = Nanoc::CLI::ANSIStringColorizer compiler = site.compiler compiler.build_reps @reps = compiler.reps action_provider = site.compiler.action_provider unless action_provider.respond_to?(:rules_collection) raise( ::Nanoc::Int::Errors::GenericTrivial, 'The show-rules command can only be used for sites with the Rule DSL action provider.', ) end @rules = action_provider.rules_collection site.items.sort_by(&:identifier).each { |e| explain_item(e) } site.layouts.sort_by(&:identifier).each { |e| explain_layout(e) } end def explain_item(item) puts "#{@c.c('Item ' + item.identifier, :bold, :yellow)}:" @reps[item].each do |rep| rule = @rules.compilation_rule_for(rep) puts " Rep #{rep.name}: #{rule ? rule.pattern : '(none)'}" end puts end def explain_layout(layout) puts "#{@c.c('Layout ' + layout.identifier, :bold, :yellow)}:" found = false @rules.layout_filter_mapping.each do |pattern, _| if pattern.match?(layout.identifier) puts " #{pattern}" found = true break end end unless found puts ' (none)' end puts end end end runner Nanoc::CLI::Commands::ShowRules nanoc-4.8.0/lib/nanoc/cli/commands/nanoc.rb0000644000004100000410000000211613134141051020501 0ustar www-datawww-data# frozen_string_literal: true usage 'nanoc command [options] [arguments]' summary 'Nanoc, a static site compiler written in Ruby' default_subcommand 'compile' opt :l, :color, 'enable color' do $stdout.remove_stream_cleaner(Nanoc::CLI::StreamCleaners::ANSIColors) $stderr.remove_stream_cleaner(Nanoc::CLI::StreamCleaners::ANSIColors) end opt :d, :debug, 'enable debugging' do Nanoc::CLI.debug = true end opt :e, :env, 'set environment', argument: :required do |value| ENV.store('NANOC_ENV', value) end opt :h, :help, 'show the help message and quit' do |_value, cmd| puts cmd.help exit 0 end opt :C, :'no-color', 'disable color' do $stdout.add_stream_cleaner(Nanoc::CLI::StreamCleaners::ANSIColors) $stderr.add_stream_cleaner(Nanoc::CLI::StreamCleaners::ANSIColors) end opt :V, :verbose, 'make output more detailed', multiple: true do |val| Nanoc::CLI::Logger.instance.level = :low Nanoc::CLI.verbosity = val.size end opt :v, :version, 'show version information and quit' do puts Nanoc.version_information exit 0 end opt :w, :warn, 'enable warnings' do $-w = true end nanoc-4.8.0/lib/nanoc/cli/commands/show-data.rb0000644000004100000410000001167113134141051021300 0ustar www-datawww-data# frozen_string_literal: true usage 'show-data' aliases :debug summary 'show data in this site' description <<~EOS Show information about all items, item representations and layouts in the current site, along with dependency information. EOS module Nanoc::CLI::Commands class ShowData < ::Nanoc::CLI::CommandRunner def run load_site(preprocess: true) # Get data items = site.items layouts = site.layouts # Get dependency tracker compiler = site.compiler compiler.load_stores dependency_store = compiler.dependency_store # Build reps compiler.build_reps # Print data print_item_dependencies(items, dependency_store) print_item_rep_paths(items) print_item_rep_outdatedness(items, compiler) print_layouts(layouts, compiler) end protected def sorted_with_prev(objects) prev = nil objects.sort_by(&:identifier).each do |object| yield(object, prev) prev = object end end def sorted_reps_with_prev(items) prev = nil items.sort_by(&:identifier).each do |item| site.compiler.reps[item].sort_by { |r| r.name.to_s }.each do |rep| yield(rep, prev) prev = rep end end end def print_header(title) header = '=' * 78 header[3..(title.length + 5)] = " #{title} " puts puts header puts end def print_item_dependencies(items, dependency_store) print_header('Item dependencies') puts 'Legend:' puts ' r = dependency on raw content' puts ' a = dependency on attributes' puts ' c = dependency on compiled content' puts ' p = dependency on the path' puts sorter = lambda do |dep| case dep when Nanoc::Int::Document dep.from.identifier.to_s else '' end end sorted_with_prev(items) do |item, prev| puts if prev puts "item #{item.identifier} depends on:" dependencies = dependency_store .dependencies_causing_outdatedness_of(item) .sort_by(&sorter) dependencies.each do |dep| pred = dep.from type = case pred when Nanoc::Int::Layout 'layout' when Nanoc::Int::Item 'item' when Nanoc::Int::Configuration 'config' when Nanoc::Int::ItemCollection 'items' when Nanoc::Int::LayoutCollection 'layouts' else raise Nanoc::Int::Errors::InternalInconsistency, "unexpected pred type #{pred}" end pred_identifier = case pred when Nanoc::Int::Document pred.identifier.to_s when Nanoc::Int::Configuration nil end props = String.new props << (dep.props.raw_content? ? 'r' : '_') props << (dep.props.attributes? ? 'a' : '_') props << (dep.props.compiled_content? ? 'c' : '_') props << (dep.props.path? ? 'p' : '_') if pred puts " [ #{format '%6s', type} ] (#{props}) #{pred_identifier}" else puts ' ( removed item )' end end puts ' (nothing)' if dependencies.empty? end end def print_item_rep_paths(items) print_header('Item representation paths') sorted_reps_with_prev(items) do |rep, prev| puts if prev puts "item #{rep.item.identifier}, rep #{rep.name}:" if rep.raw_paths.empty? puts ' (not written)' end length = rep.raw_paths.keys.map { |s| s.to_s.length }.max rep.raw_paths.each do |snapshot_name, raw_paths| raw_paths.each do |raw_path| puts format(" [ %-#{length}s ] %s", snapshot_name, raw_path) end end end end def print_item_rep_outdatedness(items, compiler) print_header('Item representation outdatedness') sorted_reps_with_prev(items) do |rep, prev| puts if prev puts "item #{rep.item.identifier}, rep #{rep.name}:" print_outdatedness_reasons_for(rep, compiler) end end def print_layouts(layouts, compiler) print_header('Layouts') sorted_with_prev(layouts) do |layout, prev| puts if prev puts "layout #{layout.identifier}:" print_outdatedness_reasons_for(layout, compiler) end end def print_outdatedness_reasons_for(obj, compiler) compiler.calculate_checksums outdatedness_checker = compiler.create_outdatedness_checker reasons = outdatedness_checker.outdatedness_reasons_for(obj) if reasons.any? puts ' is outdated:' reasons.each do |reason| puts " - #{reason.message}" end else puts ' is not outdated' end end end end runner Nanoc::CLI::Commands::ShowData nanoc-4.8.0/lib/nanoc/cli/commands/deploy.rb0000644000004100000410000000715513134141051020707 0ustar www-datawww-data# frozen_string_literal: true usage 'deploy [target] [options]' summary 'deploy the compiled site' description " Deploys the compiled site. The compiled site contents in the output directory will be uploaded to the destination, which is specified using the `--target` option. " option :t, :target, 'specify the location to deploy to (default: `default`)', argument: :required flag :C, :'no-check', 'do not run the issue checks marked for deployment' flag :L, :list, 'list available locations to deploy to' flag :D, :'list-deployers', 'list available deployers' option :n, :'dry-run', 'show what would be deployed' module Nanoc::CLI::Commands class Deploy < ::Nanoc::CLI::CommandRunner def run load_site(preprocess: true) if options[:'list-deployers'] list_deployers elsif options[:list] list_deploy_configs else deploy end end private def list_deployers deployers = Nanoc::Deploying::Deployer.all deployer_names = deployers.map(&:identifier).sort puts 'Available deployers:' deployer_names.each do |name| puts " #{name}" end end def list_deploy_configs if deploy_configs.empty? puts 'No deployment configurations.' else puts 'Available deployment configurations:' deploy_configs.keys.each do |name| puts " #{name}" end end end def deploy deployer = deployer_for(deploy_config) checks_successful = options[:'no-check'] ? true : check return unless checks_successful deployer.run end def deploy_config if deploy_configs.empty? raise Nanoc::Int::Errors::GenericTrivial, 'The site has no deployment configurations.' end if arguments.length > 1 raise Nanoc::Int::Errors::GenericTrivial, "usage: #{command.usage}" end target_from_arguments = arguments[0] target_from_options = options.fetch(:target, nil) if target_from_arguments && target_from_options raise Nanoc::Int::Errors::GenericTrivial, 'Only one deployment target can be specified on the command line.' end target = target_from_arguments || target_from_options || :default deploy_configs.fetch(target.to_sym) do raise Nanoc::Int::Errors::GenericTrivial, "The site has no deployment configuration named `#{target}`." end end def deployer_for(config) deployer_class_for_config(config).new( site.config[:output_dir], config, dry_run: options[:'dry-run'], ) end def check runner = Nanoc::Checking::Runner.new(site) if runner.dsl_present? puts 'Running issue checks…' is_success = runner.run_for_deploy if is_success puts 'No issues found. Deploying!' else puts 'Issues found, deploy aborted.' end is_success else true end end def deploy_configs site.config.fetch(:deploy, {}) end def deployer_class_for_config(config) name = config.fetch(:kind) do $stderr.puts 'Warning: The specified deploy target does not have a kind attribute. Assuming rsync.' 'rsync' end deployer_class = Nanoc::Deploying::Deployer.named(name.to_sym) if deployer_class.nil? names = Nanoc::Deploying::Deployer.all.map(&:identifier) raise Nanoc::Int::Errors::GenericTrivial, "The specified deploy target has an unrecognised kind “#{name}” (expected one of #{names.join(', ')})." end deployer_class end end end runner Nanoc::CLI::Commands::Deploy nanoc-4.8.0/lib/nanoc/cli/commands/show-plugins.rb0000644000004100000410000000522513134141051022046 0ustar www-datawww-data# frozen_string_literal: true summary 'show all available plugins' aliases :info usage 'show-plugins [options]' description <<~EOS Show a list of available plugins, including filters and data sources. If the current directory contains a Nanoc web site, the plugins defined in this site will be shown as well. EOS module Nanoc::CLI::Commands class ShowPlugins < ::Nanoc::CLI::CommandRunner def run # Check arguments if arguments.any? raise Nanoc::Int::Errors::GenericTrivial, "usage: #{command.usage}" end # Get list of plugins (before and after) plugins_before = PLUGIN_CLASSES.keys.each_with_object({}) { |c, acc| acc[c] = c.all } site&.code_snippets plugins_after = PLUGIN_CLASSES.keys.each_with_object({}) { |c, acc| acc[c] = c.all } # Divide list of plugins into builtin and custom plugins_builtin = plugins_before plugins_custom = plugins_after.each_with_object({}) do |(superclass, klasses), acc| acc[superclass] = klasses - plugins_before[superclass] end # Find max identifiers length all_identifiers = plugins_after.values.flatten.map(&:identifiers) max_identifiers_length = all_identifiers.map(&:to_s).map(&:size).max PLUGIN_CLASS_ORDER.each do |superclass| plugins_with_this_superclass = { builtin: plugins_builtin.fetch(superclass, []), custom: plugins_custom.fetch(superclass, []), } # Print kind kind = name_for_plugin_class(superclass) puts "#{kind}:" puts # Print plugins organised by subtype %i[builtin custom].each do |type| # Find relevant plugins relevant_plugins = plugins_with_this_superclass[type] # Print type puts " #{type}:" if relevant_plugins.empty? puts ' (none)' next end # Print plugins relevant_plugins.sort_by { |k| k.identifiers.join(', ') }.each do |plugin| # Display puts format( " %-#{max_identifiers_length}s (%s)", plugin.identifiers.join(', '), plugin.to_s.sub(/^::/, ''), ) end end puts end end private PLUGIN_CLASS_ORDER = [ Nanoc::Filter, Nanoc::DataSource, Nanoc::Deploying::Deployer, ].freeze PLUGIN_CLASSES = { Nanoc::Filter => 'Filters', Nanoc::DataSource => 'Data Sources', Nanoc::Deploying::Deployer => 'Deployers', }.freeze def name_for_plugin_class(klass) PLUGIN_CLASSES[klass] end end end runner Nanoc::CLI::Commands::ShowPlugins nanoc-4.8.0/lib/nanoc/cli/commands/prune.rb0000644000004100000410000000323113134141051020533 0ustar www-datawww-data# frozen_string_literal: true usage 'prune' summary 'remove files not managed by Nanoc from the output directory' description <<~EOS Find all files in the output directory that do not correspond to an item managed by Nanoc and remove them. Since this is a hazardous operation, an additional `--yes` flag is needed as confirmation. Also see the `auto_prune` configuration option in `nanoc.yaml` (`config.yaml` for older Nanoc sites), which will automatically prune after compilation. EOS flag :y, :yes, 'confirm deletion' flag :n, :'dry-run', 'print files to be deleted instead of actually deleting them' module Nanoc::CLI::Commands class Prune < ::Nanoc::CLI::CommandRunner def run load_site(preprocess: true) site.compiler.build_reps if options.key?(:yes) Nanoc::Pruner.new(site.config, site.compiler.reps, exclude: prune_config_exclude).run elsif options.key?(:'dry-run') Nanoc::Pruner.new(site.config, site.compiler.reps, exclude: prune_config_exclude, dry_run: true).run else $stderr.puts 'WARNING: Since the prune command is a destructive command, it requires an additional --yes flag in order to work.' $stderr.puts $stderr.puts 'Please ensure that the output directory does not contain any files (such as images or stylesheets) that are necessary but are not managed by Nanoc. If you want to get a list of all files that would be removed, pass --dry-run.' exit 1 end end protected def prune_config site.config[:prune] || {} end def prune_config_exclude prune_config[:exclude] || {} end end end runner Nanoc::CLI::Commands::Prune nanoc-4.8.0/lib/nanoc/cli/error_handler.rb0000644000004100000410000002447013134141051020437 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::CLI # Catches errors and prints nice diagnostic messages, then exits. # # @api private class ErrorHandler # @param [Nanoc::CLI::Command, nil] command The command that is # currently being executed, or nil if there is none def initialize(command: nil) @command = command end # Enables error handling in the given block. # # @param [Nanoc::CLI::Command, nil] command The command that is # currently being executed, or nil if there is none # # @return [void] def self.handle_while(command: nil, &block) if @disabled yield else new(command: command).handle_while(&block) end end # Disables error handling. This is used by the test cases to prevent error # from being handled by the CLI while tests are running. def self.disable @disabled = true end # Re-enables error handling after it was disabled. This is used by the test # cases to prevent error from being handled by the CLI while tests are # running. def self.enable @disabled = false end # Enables error handling in the given block. This method should not be # called directly; use {Nanoc::CLI::ErrorHandler.handle_while} instead. # # @return [void] def handle_while(&_block) # Set exit handler %w[INT TERM].each do |signal| Signal.trap(signal) do puts exit!(0) end end # Set stack trace dump handler if !defined?(RUBY_ENGINE) || RUBY_ENGINE != 'jruby' begin Signal.trap('USR1') do puts 'Caught USR1; dumping a stack trace' puts caller.map { |i| " #{i}" }.join("\n") end rescue ArgumentError end end # Run yield rescue Nanoc::Int::Errors::GenericTrivial => e $stderr.puts "Error: #{e.message}" exit(1) rescue Interrupt exit(1) rescue StandardError, ScriptError => e print_error(e) exit(1) end # Prints the given error to stderr. Includes message, possible resolution # (see {#resolution_for}), compilation stack, backtrace, etc. # # @param [Error] error The error that should be described # # @return [void] def self.print_error(error) new.print_error(error) end # Prints the given error to stderr. Includes message, possible resolution # (see {#resolution_for}), compilation stack, backtrace, etc. # # @param [Error] error The error that should be described # # @return [void] def print_error(error) write_compact_error(error, $stderr) File.open('crash.log', 'w') do |io| cio = Nanoc::CLI.wrap_in_cleaning_stream(io) cio.add_stream_cleaner(::Nanoc::CLI::StreamCleaners::ANSIColors) write_verbose_error(error, cio) end end # Writes a compact representation of the error, suitable for a terminal, on # the given stream (probably stderr). # # @param [Error] error The error that should be described # # @param [IO] stream The stream to write the description too # # @return [void] def write_compact_error(error, stream) # Header stream.puts stream.puts 'Captain! We’ve been hit!' # Sections write_error_message(stream, error) write_item_rep(stream, error) write_stack_trace(stream, error) # Issue link write_issue_link(stream) end # Writes a verbose representation of the error on the given stream. # # @param [Error] error The error that should be described # # @param [IO] stream The stream to write the description too # # @return [void] def write_verbose_error(error, stream) # Header stream.puts "Crashlog created at #{Time.now}" # Sections write_error_message(stream, error, verbose: true) write_item_rep(stream, error, verbose: true) write_stack_trace(stream, error, verbose: true) write_version_information(stream, verbose: true) write_system_information(stream, verbose: true) write_installed_gems(stream, verbose: true) write_gemfile_lock(stream, verbose: true) write_load_paths(stream, verbose: true) end protected # @return [Boolean] true if debug output is enabled, false if not # # @see Nanoc::CLI.debug? def debug? Nanoc::CLI.debug? end # @return [Nanoc::Int::Site] The site that is currently being processed def site @command && @command.site end # @return [Nanoc::Int::Compiler] The compiler for the current site def compiler site && site.compiler end # @return [Hash] A hash containing the gem names as keys and gem versions as value def gems_and_versions gems = {} Gem::Specification.find_all.sort_by { |s| [s.name, s.version] }.each do |spec| gems[spec.name] ||= [] gems[spec.name] << spec.version.to_s end gems end # A hash that contains the name of the gem for a given required file. If a # `#require` fails, the gem name is looked up in this hash. GEM_NAMES = { 'adsf' => 'adsf', 'bluecloth' => 'bluecloth', 'builder' => 'builder', 'coderay' => 'coderay', 'cri' => 'cri', 'erubi' => 'erubi', 'erubis' => 'erubis', 'escape' => 'escape', 'fog' => 'fog', 'haml' => 'haml', 'handlebars' => 'hbs', 'json' => 'json', 'kramdown' => 'kramdown', 'less' => 'less', 'listen' => 'listen', 'markaby' => 'markaby', 'maruku' => 'maruku', 'mime/types' => 'mime-types', 'nokogiri' => 'nokogiri', 'nokogumbo' => 'nokogumbo', 'pry' => 'pry', 'rack' => 'rack', 'rack/cache' => 'rack-cache', 'rainpress' => 'rainpress', 'rdiscount' => 'rdiscount', 'redcarpet' => 'redcarpet', 'redcloth' => 'RedCloth', 'rubypants' => 'rubypants', 'sass' => 'sass', 'w3c_validators' => 'w3c_validators', }.freeze # Attempts to find a resolution for the given error, or nil if no # resolution can be automatically obtained. # # @param [Error] error The error to find a resolution for # # @return [String] The resolution for the given error def resolution_for(error) error = unwrap_error(error) case error when LoadError # Get gem name matches = error.message.match(/(no such file to load|cannot load such file) -- ([^\s]+)/) return nil if matches.nil? gem_name = GEM_NAMES[matches[2]] # Build message if gem_name if using_bundler? 'Make sure the gem is added to Gemfile and run `bundle install`.' else "Install the '#{gem_name}' gem using `gem install #{gem_name}`." end end when RuntimeError if error.message =~ /^can't modify frozen/ 'You attempted to modify immutable data. Some data cannot ' \ 'be modified once compilation has started. Such data includes ' \ 'content and attributes of items and layouts, and filter arguments.' end end end def using_bundler? defined?(Bundler) && Bundler::SharedHelpers.in_bundle? end def write_section_header(stream, title, verbose: false) stream.puts if verbose stream.puts '===== ' + title.upcase + ':' else stream.puts "\e[1m\e[31m" + title + ':' + "\e[0m" end stream.puts end def write_error_message(stream, error, verbose: false) write_section_header(stream, 'Message', verbose: verbose) error = unwrap_error(error) stream.puts "#{error.class}: #{error.message}" resolution = resolution_for(error) stream.puts resolution.to_s if resolution end def write_item_rep(stream, error, verbose: false) return unless error.is_a?(Nanoc::Int::Errors::CompilationError) write_section_header(stream, 'Item being compiled', verbose: verbose) item_rep = error.item_rep stream.puts "Item identifier: #{item_rep.item.identifier}" stream.puts "Item rep name: #{item_rep.name.inspect}" end def write_stack_trace(stream, error, verbose: false) write_section_header(stream, 'Stack trace', verbose: verbose) error = unwrap_error(error) count = verbose ? -1 : 10 error.backtrace[0...count].each_with_index do |item, index| stream.puts " #{index}. #{item}" end if !verbose && error.backtrace.size > count stream.puts " ... #{error.backtrace.size - count} more lines omitted. See full crash log for details." end end def write_issue_link(stream, _params = {}) stream.puts stream.puts 'If you believe this is a bug in Nanoc, please do report it at' stream.puts '-> https://github.com/nanoc/nanoc/issues/new <-' stream.puts stream.puts 'A detailed crash log has been written to ./crash.log.' end def write_version_information(stream, verbose: false) write_section_header(stream, 'Version information', verbose: verbose) stream.puts Nanoc.version_information end def write_system_information(stream, verbose: false) uname = `uname -a` write_section_header(stream, 'System information', verbose: verbose) stream.puts uname rescue Errno::ENOENT end def write_installed_gems(stream, verbose: false) write_section_header(stream, 'Installed gems', verbose: verbose) gems_and_versions.each do |g| stream.puts " #{g.first} #{g.last.join(', ')}" end end def write_gemfile_lock(stream, verbose: false) if File.exist?('Gemfile.lock') write_section_header(stream, 'Gemfile.lock', verbose: verbose) stream.puts File.read('Gemfile.lock') end end def write_load_paths(stream, verbose: false) write_section_header(stream, 'Load paths', verbose: verbose) $LOAD_PATH.each_with_index do |i, index| stream.puts " #{index}. #{i}" end end def unwrap_error(e) case e when Nanoc::Int::Errors::CompilationError e.unwrap else e end end end end nanoc-4.8.0/lib/nanoc/cli/logger.rb0000644000004100000410000000362213134141051017064 0ustar www-datawww-data# frozen_string_literal: true require 'singleton' module Nanoc::CLI # Nanoc::CLI::Logger is a singleton class responsible for generating # feedback in the terminal. # # @api private class Logger # Maps actions (`:create`, `:update`, `:identical`, `:skip` and `:delete`) # onto their ANSI color codes. ACTION_COLORS = { create: "\e[32m", # green update: "\e[33m", # yellow identical: '', # (nothing) skip: '', # (nothing) delete: "\e[31m" # red }.freeze include Singleton # Returns the log level, which can be :high, :low or :off (which will log # all messages, only high-priority messages, or no messages at all, # respectively). # # @return [Symbol] The log level attr_accessor :level def initialize @level = :high end # Logs a file-related action. # # @param [:high, :low] level The importance of this action # # @param [:create, :update, :identical, :skip, :delete] action The kind of file action # # @param [String] name The name of the file the action was performed on # # @return [void] def file(level, action, name, duration = nil) log( level, format( '%s%12s%s %s%s', ACTION_COLORS[action.to_sym], action, "\e[0m", duration.nil? ? '' : format('[%2.2fs] ', duration), name, ), ) end # Logs a message. # # @param [:high, :low] level The importance of this message # # @param [String] message The message to be logged # # @param [#puts] io The stream to which the message should be written # # @return [void] def log(level, message, io = $stdout) # Don't log when logging is disabled return if @level == :off # Log when level permits it io.puts(message) if @level == :low || @level == level end end end nanoc-4.8.0/lib/nanoc/cli/stream_cleaners/0000755000004100000410000000000013134141051020424 5ustar www-datawww-datananoc-4.8.0/lib/nanoc/cli/stream_cleaners/ansi_colors.rb0000644000004100000410000000042313134141051023263 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::CLI::StreamCleaners # Removes ANSI color escape sequences. # # @api private class ANSIColors < Abstract # @see Nanoc::CLI::StreamCleaners::Abstract#clean def clean(s) s.gsub(/\e\[.+?m/, '') end end end nanoc-4.8.0/lib/nanoc/cli/stream_cleaners/utf8.rb0000644000004100000410000000071113134141051021636 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::CLI::StreamCleaners # Simplifies output by replacing UTF-8 characters with their ASCII decompositions. # # @api private class UTF8 < Abstract # @see Nanoc::CLI::StreamCleaners::Abstract#clean def clean(s) # FIXME: this decomposition is not generally usable s .unicode_normalize(:nfkd) .tr('─┼“”‘’', '-+""\'\'') .gsub('©', '(c)') end end end nanoc-4.8.0/lib/nanoc/cli/stream_cleaners/abstract.rb0000644000004100000410000000127213134141051022556 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::CLI::StreamCleaners # Superclass for all stream cleaners. Stream cleaners have a single method, # {#clean}, that takes a string and returns a cleaned string. Stream cleaners # can have state, so they can act as a FSM. # # @abstract Subclasses must implement {#clean} # # @api private class Abstract # Returns a cleaned version of the given string. # # @param [String] s The string to clean # # @return [String] The cleaned string def clean(s) # rubocop:disable Lint/UnusedMethodArgument raise NotImplementedError, 'Subclasses of Nanoc::CLI::StreamCleaners::Abstract must implement #clean' end end end nanoc-4.8.0/lib/nanoc/cli/stream_cleaners.rb0000644000004100000410000000033113134141051020746 0ustar www-datawww-data# frozen_string_literal: true # @api private module Nanoc::CLI::StreamCleaners end require_relative 'stream_cleaners/abstract' require_relative 'stream_cleaners/ansi_colors' require_relative 'stream_cleaners/utf8' nanoc-4.8.0/lib/nanoc/cli/cleaning_stream.rb0000644000004100000410000000633613134141051020745 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::CLI # An output stream that passes output through stream cleaners. This can be # used to strip ANSI color sequences, for instance. # # @api private class CleaningStream # @param [IO, StringIO] stream The stream to wrap def initialize(stream) @stream = stream @stream_cleaners = [] end # Adds a stream cleaner for the given class to this cleaning stream. If the # cleaning stream already has the given stream cleaner, nothing happens. # # @param [Nanoc::CLI::StreamCleaners::Abstract] klass The class of the # stream cleaner to add # # @return [void] def add_stream_cleaner(klass) unless @stream_cleaners.map(&:class).include?(klass) @stream_cleaners << klass.new end end # Removes the stream cleaner for the given class from this cleaning stream. # If the cleaning stream does not have the given stream cleaner, nothing # happens. # # @param [Nanoc::CLI::StreamCleaners::Abstract] klass The class of the # stream cleaner to add # # @return [void] def remove_stream_cleaner(klass) @stream_cleaners.delete_if { |c| c.class == klass } end # @group IO proxy methods # @see IO#write def write(s) _nanoc_swallow_broken_pipe_errors_while do @stream.write(_nanoc_clean(s)) end end # @see IO#<< def <<(s) _nanoc_swallow_broken_pipe_errors_while do @stream.<<(_nanoc_clean(s)) end end # @see IO#tty? def tty? @cached_is_tty ||= @stream.tty? end # @see IO#isatty def isatty tty? end # @see IO#flush def flush _nanoc_swallow_broken_pipe_errors_while do @stream.flush end end # @see IO#tell def tell @stream.tell end # @see IO#print def print(s) _nanoc_swallow_broken_pipe_errors_while do @stream.print(_nanoc_clean(s)) end end # @see IO#puts def puts(*s) _nanoc_swallow_broken_pipe_errors_while do @stream.puts(*s.map { |ss| _nanoc_clean(ss) }) end end # @see StringIO#string def string @stream.string end # @see IO#reopen def reopen(*a) @stream.reopen(*a) end # @see IO#close def close @stream.close end # @see File#exist? def exist? @stream.exist? end # @see File.exists? def exists? @stream.exists? end # @see IO.winsize def winsize @stream.winsize end # @see IO.winsize= def winsize=(arg) @stream.winsize = arg end # @see IO.sync def sync @stream.sync end # @see IO.sync= def sync=(arg) @stream.sync = arg end # @see IO.sync= def external_encoding @stream.external_encoding end # @see ARGF.set_encoding # rubocop:disable Style/AccessorMethodName def set_encoding(*args) @stream.set_encoding(*args) end # rubocop:enable Style/AccessorMethodName protected def _nanoc_clean(s) @stream_cleaners.reduce(s.to_s.scrub) { |acc, elem| elem.clean(acc) } end def _nanoc_swallow_broken_pipe_errors_while yield rescue Errno::EPIPE end end end nanoc-4.8.0/lib/nanoc/cli/command_runner.rb0000644000004100000410000000342213134141051020612 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::CLI # A command runner subclass for Nanoc commands that adds Nanoc-specific # convenience methods and error handling. # # @api private class CommandRunner < ::Cri::CommandRunner # @see http://rubydoc.info/gems/cri/Cri/CommandRunner#call-instance_method # # @return [void] def call Nanoc::CLI::ErrorHandler.handle_while(command: self) do run end end # Gets the site ({Nanoc::Int::Site} instance) in the current directory and # loads its data. # # @return [Nanoc::Int::Site] The site in the current working directory def site # Load site if possible @site ||= nil if is_in_site_dir? && @site.nil? @site = Nanoc::Int::SiteLoader.new.new_from_cwd end @site end # For debugging purposes. # # @api private def site=(new_site) @site = new_site end # @return [Boolean] true if the current working directory is a Nanoc site # directory, false otherwise def in_site_dir? Nanoc::Int::SiteLoader.cwd_is_nanoc_site? end alias is_in_site_dir? in_site_dir? # Asserts that the current working directory contains a site and loads the site into memory. # # @return [void] def load_site(preprocess: false) $stderr.print 'Loading site… ' $stderr.flush if site.nil? raise ::Nanoc::Int::Errors::GenericTrivial, 'The current working directory does not seem to be a Nanoc site.' end if preprocess site.compiler.action_provider.preprocess(site) end $stderr.puts 'done' end # @return [Boolean] true if debug output is enabled, false if not # # @see Nanoc::CLI.debug? def debug? Nanoc::CLI.debug? end end end nanoc-4.8.0/lib/nanoc/base.rb0000644000004100000410000000062013134141051015743 0ustar www-datawww-data# frozen_string_literal: true # @api private module Nanoc::Int end require_relative 'base/core_ext' require_relative 'base/contracts_support' require_relative 'base/memoization' require_relative 'base/error' require_relative 'base/errors' require_relative 'base/entities' require_relative 'base/feature' require_relative 'base/repos' require_relative 'base/services' require_relative 'base/views' nanoc-4.8.0/lib/nanoc/helpers/0000755000004100000410000000000013134141051016150 5ustar www-datawww-datananoc-4.8.0/lib/nanoc/helpers/breadcrumbs.rb0000644000004100000410000000140113134141051020762 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Helpers # @see http://nanoc.ws/doc/reference/helpers/#breadcrumbs module Breadcrumbs # @return [Array] def breadcrumbs_trail # e.g. ['', '/foo', '/foo/bar'] components = item.identifier.components prefixes = components.inject(['']) { |acc, elem| acc + [acc.last + '/' + elem] } if @item.identifier.legacy? prefixes.map { |pr| @items[Nanoc::Identifier.new('/' + pr, type: :legacy)] } else prefixes .reject { |pr| pr =~ /^\/index\./ } .map do |pr| if pr == '' @items['/index.*'] else @items[Nanoc::Identifier.new(pr).without_ext + '.*'] end end end end end end nanoc-4.8.0/lib/nanoc/helpers/tagging.rb0000644000004100000410000000166713134141051020127 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Helpers # @see http://nanoc.ws/doc/reference/helpers/#tagging module Tagging require 'nanoc/helpers/html_escape' include Nanoc::Helpers::HTMLEscape # @param [String] base_url # @param [String] none_text # @param [String] separator # # @return [String] def tags_for(item, base_url: nil, none_text: '(none)', separator: ', ') if item[:tags].nil? || item[:tags].empty? none_text else item[:tags].map { |tag| base_url ? link_for_tag(tag, base_url) : tag }.join(separator) end end # @param [String] tag # # @return [Array] def items_with_tag(tag) @items.select { |i| (i[:tags] || []).include?(tag) } end # @param [String] tag # @param [String] base_url # # @return [String] def link_for_tag(tag, base_url) %() end end end nanoc-4.8.0/lib/nanoc/helpers/text.rb0000644000004100000410000000130613134141051017461 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Helpers # @see http://nanoc.ws/doc/reference/helpers/#text module Text # @param [String] string # @param [Number] length # @param [String] omission # # @return [String] def excerptize(string, length: 25, omission: '...') if string.length > length excerpt_length = [0, length - omission.length].max string[0...excerpt_length] + omission else string end end # @param [String] string # # @return [String] def strip_html(string) # FIXME: will need something more sophisticated than this, because it sucks string.gsub(/<[^>]*(>+|\s*\z)/m, '').strip end end end nanoc-4.8.0/lib/nanoc/helpers/link_to.rb0000644000004100000410000000534013134141051020136 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Helpers # @see http://nanoc.ws/doc/reference/helpers/#linkto module LinkTo require 'nanoc/helpers/html_escape' include Nanoc::Helpers::HTMLEscape # @param [String] text # # @param [Hash] attributes # # @return [String] def link_to(text, target, attributes = {}) # Find path path = case target when String target when Nanoc::ItemWithRepsView, Nanoc::ItemWithoutRepsView, Nanoc::ItemRepView raise "Cannot create a link to #{target.inspect} because this target is not outputted (its routing rule returns nil)" if target.path.nil? target.path else raise ArgumentError, "Cannot link to #{target.inspect} (expected a string or an item, not a #{target.class.name})" end # Join attributes attributes = attributes.reduce('') do |memo, (key, value)| memo + key.to_s + '="' + h(value) + '" ' end # Create link "#{text}" end # @param [String] text # # @param [Hash] attributes # # @return [String] def link_to_unless_current(text, target, attributes = {}) # Find path path = target.is_a?(String) ? target : target.path if @item_rep && @item_rep.path == path # Create message "#{text}" else link_to(text, target, attributes) end end # @return [String] def relative_path_to(target) require 'pathname' # Find path if target.is_a?(String) path = target else path = target.path if path.nil? # TODO: get proper error raise "Cannot get the relative path to #{target.inspect} because this target is not outputted (its routing rule returns nil)" end end # Handle Windows network (UNC) paths if path.start_with?('//', '\\\\') return path end # Get source and destination paths dst_path = Pathname.new(path) if @item_rep.path.nil? # TODO: get proper error raise "Cannot get the relative path to #{path} because the current item representation, #{@item_rep.inspect}, is not outputted (its routing rule returns nil)" end src_path = Pathname.new(@item_rep.path) # Calculate the relative path (method depends on whether destination is # a directory or not). from = src_path.to_s.end_with?('/') ? src_path : src_path.dirname relative_path = dst_path.relative_path_from(from).to_s # Add trailing slash if necessary if dst_path.to_s.end_with?('/') relative_path << '/' end # Done relative_path end end end nanoc-4.8.0/lib/nanoc/helpers/filtering.rb0000644000004100000410000000215413134141051020462 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Helpers # @see http://nanoc.ws/doc/reference/helpers/#filtering module Filtering require 'nanoc/helpers/capturing' include Nanoc::Helpers::Capturing # @param [Symbol] filter_name # @param [Hash] arguments # # @return [void] def filter(filter_name, arguments = {}, &block) # Capture block data = capture(&block) # Find filter klass = Nanoc::Filter.named!(filter_name) # Create filter assigns = { item: @item, rep: @rep, item_rep: @item_rep, items: @items, layouts: @layouts, config: @config, content: @content, } filter = klass.new(assigns) # Filter captured data Nanoc::Int::NotificationCenter.post(:filtering_started, @item_rep.unwrap, filter_name) filtered_data = filter.setup_and_run(data, arguments) Nanoc::Int::NotificationCenter.post(:filtering_ended, @item_rep.unwrap, filter_name) # Append filtered data to buffer buffer = eval('_erbout', block.binding) buffer << filtered_data end end end nanoc-4.8.0/lib/nanoc/helpers/html_escape.rb0000644000004100000410000000216313134141051020763 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Helpers # @see http://nanoc.ws/doc/reference/helpers/#filtering module HTMLEscape require 'nanoc/helpers/capturing' include Nanoc::Helpers::Capturing # @param [String] string # # @return [String] def html_escape(string = nil, &block) if block_given? # Capture and escape block data = capture(&block) escaped_data = html_escape(data) # Append filtered data to buffer buffer = eval('_erbout', block.binding) buffer << escaped_data elsif string unless string.is_a? String raise ArgumentError, 'The #html_escape or #h function needs either a ' \ "string or a block to HTML-escape, but #{string.class} was given" end string .gsub('&', '&') .gsub('<', '<') .gsub('>', '>') .gsub('"', '"') else raise 'The #html_escape or #h function needs either a ' \ 'string or a block to HTML-escape, but neither a string nor a block was given' end end alias h html_escape end end nanoc-4.8.0/lib/nanoc/helpers/rendering.rb0000644000004100000410000000376513134141051020465 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Helpers # @see http://nanoc.ws/doc/reference/helpers/#rendering module Rendering include Nanoc::Helpers::Capturing # @param [String] identifier # @param [Hash] other_assigns # # @raise [Nanoc::Int::Errors::UnknownLayout] # @raise [Nanoc::Int::Errors::CannotDetermineFilter] # @raise [Nanoc::Int::Errors::UnknownFilter] # # @return [String, nil] def render(identifier, other_assigns = {}, &block) # Find layout layout_view = @layouts[identifier] layout_view ||= @layouts[identifier.__nanoc_cleaned_identifier] raise Nanoc::Int::Errors::UnknownLayout.new(identifier) if layout_view.nil? layout = layout_view.unwrap # Visit dependency_tracker = @config._context.dependency_tracker dependency_tracker.bounce(layout, raw_content: true) # Capture content, if any captured_content = block_given? ? capture(&block) : nil # Get assigns assigns = { content: captured_content, item: @item, item_rep: @item_rep, items: @items, layout: layout_view, layouts: @layouts, config: @config, }.merge(other_assigns) # Get filter name filter_name, filter_args = *@config._context.compilation_context.filter_name_and_args_for_layout(layout) raise Nanoc::Int::Errors::CannotDetermineFilter.new(layout.identifier) if filter_name.nil? # Get filter class filter_class = Nanoc::Filter.named!(filter_name) # Create filter filter = filter_class.new(assigns) # Layout content = layout.content arg = content.binary? ? content.filename : content.string result = filter.setup_and_run(arg, filter_args) # Append to erbout if we have a block if block_given? # Append result and return nothing erbout = eval('_erbout', block.binding) erbout << result '' else # Return result result end end end end nanoc-4.8.0/lib/nanoc/helpers/xml_sitemap.rb0000644000004100000410000000301513134141051021016 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Helpers # @see http://nanoc.ws/doc/reference/helpers/#xmlsitemap module XMLSitemap # @option params [Array] :items # @option params [Proc] :rep_select # # @return [String] def xml_sitemap(params = {}) require 'builder' # Extract parameters items = params.fetch(:items) { @items.reject { |i| i[:is_hidden] } } select_proc = params.fetch(:rep_select, nil) # Create builder buffer = String.new xml = Builder::XmlMarkup.new(target: buffer, indent: 2) # Check for required attributes if @config[:base_url].nil? raise 'The Nanoc::Helpers::XMLSitemap helper requires the site configuration to specify the base URL for the site.' end # Build sitemap xml.instruct! xml.urlset(xmlns: 'http://www.sitemaps.org/schemas/sitemap/0.9') do # Add item items.sort_by(&:identifier).each do |item| reps = item.reps.select(&:path) reps.select! { |r| select_proc[r] } if select_proc reps.sort_by { |r| r.name.to_s }.each do |rep| xml.url do xml.loc URI.escape(@config[:base_url] + rep.path) xml.lastmod item[:mtime].__nanoc_to_iso8601_date unless item[:mtime].nil? xml.changefreq item[:changefreq] unless item[:changefreq].nil? xml.priority item[:priority] unless item[:priority].nil? end end end end # Return sitemap buffer end end end nanoc-4.8.0/lib/nanoc/helpers/child_parent.rb0000644000004100000410000000110513134141051021126 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Helpers # @see http://nanoc.ws/doc/reference/helpers/#childparent module ChildParent def parent_of(item) if item.identifier.legacy? item.parent else path_without_last_component = item.identifier.to_s.sub(/[^\/]+$/, '').chop @items[path_without_last_component + '.*'] end end def children_of(item) if item.identifier.legacy? item.children else pattern = item.identifier.without_ext + '/*' @items.find_all(pattern) end end end end nanoc-4.8.0/lib/nanoc/helpers/capturing.rb0000644000004100000410000001211013134141051020464 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Helpers # @see http://nanoc.ws/doc/reference/helpers/#capturing module Capturing # @api private class SetContent include Nanoc::Helpers::Capturing def initialize(name, params, item) @name = name @params = params @item = item end def run(&block) existing_behavior = @params.fetch(:existing, :error) # Capture content_string = capture(&block) # Get existing contents and prep for store snapshot_repo = @item._context.snapshot_repo rep = @item.reps[:default].unwrap capture_name = "__capture_#{@name}".to_sym old_content_string = case existing_behavior when :overwrite '' when :append c = snapshot_repo.get(rep, capture_name) c ? c.string : '' when :error contents = snapshot_repo.get(rep, capture_name) if contents && contents.string != content_string # FIXME: get proper exception raise "a capture named #{@name.inspect} for #{@item.identifier} already exists" else '' end else raise ArgumentError, 'expected :existing_behavior param to #content_for to be one of ' \ ":overwrite, :append, or :error, but #{existing_behavior.inspect} was given" end # Store new_content = Nanoc::Int::TextualContent.new(old_content_string + content_string) snapshot_repo.set(rep, capture_name, new_content) end end # @api private class GetContent def initialize(requested_item, name, item, config) @requested_item = requested_item @name = name @item = item @config = config end def run rep = @requested_item.reps[:default].unwrap # Create dependency if @item.nil? || @requested_item != @item.unwrap dependency_tracker = @config._context.dependency_tracker dependency_tracker.bounce(@requested_item.unwrap, compiled_content: true) unless rep.compiled? Fiber.yield(Nanoc::Int::Errors::UnmetDependency.new(rep)) return run end end snapshot_repo = @config._context.snapshot_repo content = snapshot_repo.get(rep, "__capture_#{@name}".to_sym) content ? content.string : nil end end # @overload content_for(name, &block) # @param [Symbol, String] name # @return [void] # # @overload content_for(name, params, &block) # @param [Symbol, String] name # @option params [Symbol] existing # @return [void] # # @overload content_for(name, content) # @param [Symbol, String] name # @param [String] content # @return [void] # # @overload content_for(name, params, content) # @param [Symbol, String] name # @param [String] content # @option params [Symbol] existing # @return [void] # # @overload content_for(item, name) # @param [Symbol, String] name # @return [String] def content_for(*args, &block) if block_given? # Set content name = args[0] params = case args.size when 1 {} when 2 args[1] else raise ArgumentError, 'expected 1 or 2 argument (the name ' \ "of the capture, and optionally params) but got #{args.size} instead" end SetContent.new(name, params, @item).run(&block) elsif args.size > 1 && (args.first.is_a?(Symbol) || args.first.is_a?(String)) # Set content name = args[0] content = args.last params = case args.size when 2 {} when 3 args[1] else raise ArgumentError, 'expected 2 or 3 arguments (the name ' \ "of the capture, optionally params, and the content) but got #{args.size} instead" end _erbout = String.new # rubocop:disable Lint/UnderscorePrefixedVariableName SetContent.new(name, params, @item).run { _erbout << content } else # Get content if args.size != 2 raise ArgumentError, 'expected 2 arguments (the item ' \ "and the name of the capture) but got #{args.size} instead" end requested_item = args[0] name = args[1] GetContent.new(requested_item, name, @item, @config).run end end # @return [String] def capture(&block) # Get erbout so far erbout = eval('_erbout', block.binding) erbout_length = erbout.length # Execute block yield # Get new piece of erbout erbout_addition = erbout[erbout_length..-1] # Remove addition erbout[erbout_length..-1] = String.new # Depending on how the filter outputs, the result might be a # single string or an array of strings (slim outputs the latter). erbout_addition = erbout_addition.join('') if erbout_addition.is_a? Array # Done. erbout_addition end end end nanoc-4.8.0/lib/nanoc/helpers/blogging.rb0000644000004100000410000001663613134141051020301 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::Helpers # @see http://nanoc.ws/doc/reference/helpers/#blogging module Blogging # @return [Array] def articles blk = -> { @items.select { |item| item[:kind] == 'article' } } if @items.frozen? @article_items ||= blk.call else blk.call end end # @return [Array] def sorted_articles blk = -> { articles.sort_by { |a| attribute_to_time(a[:created_at]) }.reverse } if @items.frozen? @sorted_article_items ||= blk.call else blk.call end end class AtomFeedBuilder include Nanoc::Helpers::Blogging attr_accessor :config attr_accessor :limit attr_accessor :relevant_articles attr_accessor :preserve_order attr_accessor :content_proc attr_accessor :excerpt_proc attr_accessor :title attr_accessor :author_name attr_accessor :author_uri attr_accessor :icon attr_accessor :logo def initialize(config, item) @config = config @item = item end def validate validate_config validate_feed_item validate_articles end def build buffer = String.new xml = Builder::XmlMarkup.new(target: buffer, indent: 2) build_for_feed(xml) buffer end protected def sorted_relevant_articles all = relevant_articles unless @preserve_order all = all.sort_by { |a| attribute_to_time(a[:created_at]) } end all.reverse.first(limit) end def last_article sorted_relevant_articles.first end def updated relevant_articles.map { |a| attribute_to_time(a[:updated_at] || a[:created_at]) }.max end def validate_config if @config[:base_url].nil? raise Nanoc::Int::Errors::GenericTrivial.new('Cannot build Atom feed: site configuration has no base_url') end end def validate_feed_item if title.nil? raise Nanoc::Int::Errors::GenericTrivial.new('Cannot build Atom feed: no title in params, item or site config') end if author_name.nil? raise Nanoc::Int::Errors::GenericTrivial.new('Cannot build Atom feed: no author_name in params, item or site config') end if author_uri.nil? raise Nanoc::Int::Errors::GenericTrivial.new('Cannot build Atom feed: no author_uri in params, item or site config') end end def validate_articles if relevant_articles.empty? raise Nanoc::Int::Errors::GenericTrivial.new('Cannot build Atom feed: no articles') end if relevant_articles.any? { |a| a[:created_at].nil? } raise Nanoc::Int::Errors::GenericTrivial.new('Cannot build Atom feed: one or more articles lack created_at') end end def build_for_feed(xml) root_url = @config[:base_url] + '/' xml.instruct! xml.feed(xmlns: 'http://www.w3.org/2005/Atom', 'xml:base' => root_url) do # Add primary attributes xml.id root_url xml.title title # Add date xml.updated(updated.__nanoc_to_iso8601_time) # Add links xml.link(rel: 'alternate', href: root_url) xml.link(rel: 'self', href: feed_url) # Add author information xml.author do xml.name author_name xml.uri author_uri end # Add icon and logo xml.icon icon if icon xml.logo logo if logo # Add articles sorted_relevant_articles.each do |a| build_for_article(a, xml) end end end def build_for_article(a, xml) # Get URL url = url_for(a) return if url.nil? xml.entry do # Add primary attributes xml.id atom_tag_for(a) xml.title a[:title], type: 'html' # Add dates xml.published attribute_to_time(a[:created_at]).__nanoc_to_iso8601_time xml.updated attribute_to_time(a[:updated_at] || a[:created_at]).__nanoc_to_iso8601_time # Add specific author information if a[:author_name] || a[:author_uri] xml.author do xml.name a[:author_name] || author_name xml.uri a[:author_uri] || author_uri end end # Add link xml.link(rel: 'alternate', href: url) # Add content summary = excerpt_proc.call(a) xml.content content_proc.call(a), type: 'html' xml.summary summary, type: 'html' unless summary.nil? end end end # @option params [Number] :limit # @option params [Array] :articles # @option params [Boolean] :preserve_order # @option params [Proc] :content_proc # @option params [Proc] :excerpt_proc # @option params [String] :title # @option params [String] :author_name # @option params [String] :author_uri # @option params [String] :icon # @option params [String] :logo # # @return [String] def atom_feed(params = {}) require 'builder' # Create builder builder = AtomFeedBuilder.new(@config, @item) # Fill builder builder.limit = params[:limit] || 5 builder.relevant_articles = params[:articles] || articles || [] builder.preserve_order = params.fetch(:preserve_order, false) builder.content_proc = params[:content_proc] || ->(a) { a.compiled_content(snapshot: :pre) } builder.excerpt_proc = params[:excerpt_proc] || ->(a) { a[:excerpt] } builder.title = params[:title] || @item[:title] || @config[:title] builder.author_name = params[:author_name] || @item[:author_name] || @config[:author_name] builder.author_uri = params[:author_uri] || @item[:author_uri] || @config[:author_uri] builder.icon = params[:icon] builder.logo = params[:logo] # Run builder.validate builder.build end # @return [String] def url_for(item) # Check attributes if @config[:base_url].nil? raise Nanoc::Int::Errors::GenericTrivial.new('Cannot build Atom feed: site configuration has no base_url') end # Build URL if item[:custom_url_in_feed] item[:custom_url_in_feed] elsif item[:custom_path_in_feed] @config[:base_url] + item[:custom_path_in_feed] elsif item.path @config[:base_url] + item.path end end # @return [String] def feed_url # Check attributes if @config[:base_url].nil? raise Nanoc::Int::Errors::GenericTrivial.new('Cannot build Atom feed: site configuration has no base_url') end @item[:feed_url] || @config[:base_url] + @item.path end # @return [String] def atom_tag_for(item) hostname, base_dir = %r{^.+?://([^/]+)(.*)$}.match(@config[:base_url])[1..2] formatted_date = attribute_to_time(item[:created_at]).__nanoc_to_iso8601_date 'tag:' + hostname + ',' + formatted_date + ':' + base_dir + (item.path || item.identifier.to_s) end # @param [String, Time, Date, DateTime] arg # # @return [Time] def attribute_to_time(arg) case arg when DateTime arg.to_time when Date Time.utc(arg.year, arg.month, arg.day) when String Time.parse(arg) else arg end end end end nanoc-4.8.0/lib/nanoc/rule_dsl.rb0000644000004100000410000000057213134141051016650 0ustar www-datawww-data# frozen_string_literal: true require_relative 'rule_dsl/compiler_dsl' require_relative 'rule_dsl/action_provider' require_relative 'rule_dsl/recording_executor' require_relative 'rule_dsl/rule_context' require_relative 'rule_dsl/action_sequence_calculator' require_relative 'rule_dsl/rule' require_relative 'rule_dsl/rules_collection' require_relative 'rule_dsl/rules_loader' nanoc-4.8.0/lib/nanoc/extra.rb0000644000004100000410000000072313134141051016160 0ustar www-datawww-data# frozen_string_literal: true require 'nanoc/checking' require 'nanoc/deploying' # @api private module Nanoc::Extra # @deprecated Checking = Nanoc::Checking # @deprecated Deployer = Nanoc::Deploying::Deployer # @deprecated Pruner = Nanoc::Pruner end require_relative 'extra/link_collector.rb' require_relative 'extra/piper' require_relative 'extra/jruby_nokogiri_warner' require_relative 'extra/core_ext' require_relative 'extra/parallel_collection' nanoc-4.8.0/lib/nanoc/data_sources/0000755000004100000410000000000013134141051017162 5ustar www-datawww-datananoc-4.8.0/lib/nanoc/data_sources/filesystem/0000755000004100000410000000000013134141051021346 5ustar www-datawww-datananoc-4.8.0/lib/nanoc/data_sources/filesystem/errors.rb0000644000004100000410000000332413134141051023211 0ustar www-datawww-data# frozen_string_literal: true class Nanoc::DataSources::Filesystem < Nanoc::DataSource # @api private module Errors class Generic < ::Nanoc::Error end class BinaryLayout < Generic def initialize(content_filename) super("The layout file '#{content_filename}' is a binary file, but layouts can only be textual") end end class MultipleMetaFiles < Generic def initialize(meta_filenames, basename) super("Found #{meta_filenames.size} meta files for #{basename}; expected 0 or 1") end end class MultipleContentFiles < Generic def initialize(content_filenames, basename) super("Found #{content_filenames.size} content files for #{basename}; expected 0 or 1") end end class InvalidFormat < Generic def initialize(content_filename) super("The file '#{content_filename}' appears to start with a metadata section (three or five dashes at the top) but it does not seem to be in the correct format.") end end class UnparseableMetadata < Generic def initialize(filename, error) super("Could not parse metadata for #{filename}: #{error.message}") end end class InvalidMetadata < Generic def initialize(filename, klass) super("The file #{filename} has invalid metadata (expected key-value pairs, found #{klass} instead)") end end class InvalidEncoding < Generic def initialize(filename, encoding) super("Could not read #{filename} because the file is not valid #{encoding}.") end end class FileUnreadable < Generic def initialize(filename, error) super("Could not read #{filename}: #{error.inspect}") end end end end nanoc-4.8.0/lib/nanoc/data_sources/filesystem/tools.rb0000644000004100000410000001250613134141051023037 0ustar www-datawww-data# frozen_string_literal: true class Nanoc::DataSources::Filesystem < Nanoc::DataSource # Contains useful functions for managing the filesystem. # # @api private module Tools # Error that is raised when too many symlink indirections are encountered. class MaxSymlinkDepthExceededError < ::Nanoc::Int::Errors::GenericTrivial # @return [String] The last filename that was attempted to be # resolved before giving up attr_reader :filename # @param [String] filename The last filename that was attempted to be # resolved before giving up def initialize(filename) @filename = filename super("Too many indirections while resolving symlinks. I gave up after finding out #{filename} was yet another symlink. Sorry!") end end # Error that is raised when a file of an unknown type is encountered # (something other than file, directory or link). class UnsupportedFileTypeError < ::Nanoc::Int::Errors::GenericTrivial # @return [String] The filename of the file whose type is not supported attr_reader :filename # @param [String] filename The filename of the file whose type is not # supported def initialize(filename) @filename = filename super("The file at #{filename} is of an unsupported type (expected file, directory or link, but it is #{File.ftype(filename)}") end end # Returns all files in the given directory and directories below it, # following symlinks up to a maximum of `recursion_limit` times. # # @param [String] dir_name The name of the directory whose contents to # fetch # # @param [String, Array, nil] extra_files The list of extra patterns # to extend the file search for files not found by default, example # "**/.{htaccess,htpasswd}" # # @param [Integer] recursion_limit The maximum number of times to # recurse into a symlink to a directory # # @return [Array] A list of file names # # @raise [MaxSymlinkDepthExceededError] if too many indirections are # encountered while resolving symlinks # # @raise [UnsupportedFileTypeError] if a file of an unsupported type is # detected (something other than file, directory or link) def all_files_in(dir_name, extra_files, recursion_limit = 10) all_files_and_dirs_in(dir_name, extra_files).map do |fn| case File.ftype(fn) when 'link' if recursion_limit.zero? raise MaxSymlinkDepthExceededError.new(fn) else absolute_target = resolve_symlink(fn) if File.file?(absolute_target) fn else all_files_in(absolute_target, extra_files, recursion_limit - 1).map do |sfn| fn + sfn[absolute_target.size..-1] end end end when 'file' fn when 'directory' nil else raise UnsupportedFileTypeError.new(fn) end end.compact.flatten end module_function :all_files_in # Returns all files and directories in the given directory and # directories below it. # # @param [String] dir_name The name of the directory whose contents to # fetch # # @param [String, Array, nil] extra_files The list of extra patterns # to extend the file search for files not found by default, example # "**/.{htaccess,htpasswd}" # # @return [Array] A list of files and directories # # @raise [GenericTrivial] when pattern can not be handled def all_files_and_dirs_in(dir_name, extra_files) base_patterns = ["#{dir_name}/**/*"] extra_patterns = case extra_files when nil [] when String ["#{dir_name}/#{extra_files}"] when Array extra_files.map { |extra_file| "#{dir_name}/#{extra_file}" } else raise( Nanoc::Int::Errors::GenericTrivial, "Do not know how to handle extra_files: #{extra_files.inspect}", ) end patterns = base_patterns + extra_patterns Dir.glob(patterns) end module_function :all_files_and_dirs_in # Resolves the given symlink into an absolute path. # # @param [String] filename The filename of the symlink to resolve # # @param [Integer] recursion_limit The maximum number of times to recurse # into a symlink # # @return [String] The absolute resolved filename of the symlink target # # @raise [MaxSymlinkDepthExceededError] if too many indirections are # encountered while resolving symlinks # # @raise [UnsupportedFileTypeError] if a file of an unsupported type is # detected (something other than file, directory or link) def resolve_symlink(filename, recursion_limit = 5) target = File.readlink(filename) absolute_target = File.expand_path(target, File.dirname(filename)) case File.ftype(absolute_target) when 'link' if recursion_limit.zero? raise MaxSymlinkDepthExceededError.new(absolute_target) else resolve_symlink(absolute_target, recursion_limit - 1) end when 'file', 'directory' absolute_target else raise UnsupportedFileTypeError.new(absolute_target) end end module_function :resolve_symlink end end nanoc-4.8.0/lib/nanoc/data_sources/filesystem.rb0000644000004100000410000003575513134141051021712 0ustar www-datawww-data# frozen_string_literal: true module Nanoc::DataSources # The filesystem data source stores its items and layouts in nested # directories. Items and layouts are represented by one or two files; if it # is represented using one file, the metadata can be contained in this file. # # The default root directory for items is the `content` directory; for # layouts, this is the `layouts` directory. This can be overridden # in the data source configuration: # # data_sources: # - type: filesystem # content_dir: items # layouts_dir: layouts # # The metadata for items and layouts can be stored in a separate file with # the same base name but with the `.yaml` extension. If such a file is # found, metadata is read from that file. Alternatively, the content file # itself can start with a metadata section: it can be stored at the top of # the file, between `---` (three dashes) separators. For example: # # --- # title: "Moo!" # --- # h1. Hello! # # The metadata section can be omitted. If the file does not start with # three or five dashes, the entire file will be considered as content. # # The identifier of items and layouts is the filename itself, without the # root directory (as determined by the `content_dir` or `layouts_dir` # configuration attribute, for items resp. layouts). For example: # # foo/bar/index.html → /foo/bar/index.html # foo/bar.html → /foo/bar.html # # Note that each item must have an unique identifier. Nanoc will display an # error if two items with the same identifier are found. # # The file extension does not determine the filters to run on items; the # Rules file is used to specify processing instructors for each item. # # It is possible to set an explicit encoding that should be used when reading # files. In the data source configuration, set `encoding` to an encoding # understood by Ruby’s `Encoding`. If no encoding is set in the configuration, # one will be inferred from the environment. # # @api private class Filesystem < Nanoc::DataSource identifiers :filesystem, :filesystem_unified # See {Nanoc::DataSource#up}. def up; end # See {Nanoc::DataSource#down}. def down; end def content_dir_name config.fetch(:content_dir, 'content') end def layouts_dir_name config.fetch(:layouts_dir, 'layouts') end # See {Nanoc::DataSource#items}. def items load_objects(content_dir_name, Nanoc::Int::Item) end # See {Nanoc::DataSource#layouts}. def layouts load_objects(layouts_dir_name, Nanoc::Int::Layout) end protected class ProtoDocument attr_reader :attributes attr_reader :content_checksum_data attr_reader :attributes_checksum_data attr_reader :is_binary alias binary? is_binary def initialize(is_binary:, content: nil, filename: nil, attributes:, content_checksum_data: nil, attributes_checksum_data: nil) if content.nil? && filename.nil? raise ArgumentError, '#initialize needs at least content or filename' end @is_binary = is_binary @content = content @filename = filename @attributes = attributes @content_checksum_data = content_checksum_data @attributes_checksum_data = attributes_checksum_data end def content if binary? raise ArgumentError, 'cannot fetch content of binary item' else @content end end def filename if binary? @filename else raise ArgumentError, 'cannot fetch filename of non-binary item' end end end def read_proto_document(content_filename, meta_filename, klass) is_binary = content_filename && !@site_config[:text_extensions].include?(File.extname(content_filename)[1..-1]) if is_binary && klass == Nanoc::Int::Item meta = (meta_filename && YAML.load_file(meta_filename)) || {} ProtoDocument.new(is_binary: true, filename: content_filename, attributes: meta) elsif is_binary && klass == Nanoc::Int::Layout raise Errors::BinaryLayout.new(content_filename) else parse_result = parse(content_filename, meta_filename) ProtoDocument.new( is_binary: false, content: parse_result.content, attributes: parse_result.attributes, content_checksum_data: parse_result.content, attributes_checksum_data: parse_result.attributes_data, ) end end # Creates instances of klass corresponding to the files in dir_name. The # kind attribute indicates the kind of object that is being loaded and is # used solely for debugging purposes. # # This particular implementation loads objects from a filesystem-based # data source where content and attributes can be spread over two separate # files. The content and meta-file are optional (but at least one of them # needs to be present, obviously) and the content file can start with a # metadata section. # # @see Nanoc::DataSources::Filesystem#load_objects def load_objects(dir_name, klass) res = [] return [] if dir_name.nil? all_split_files_in(dir_name).each do |base_filename, (meta_ext, content_exts)| content_exts.each do |content_ext| meta_filename = filename_for(base_filename, meta_ext) content_filename = filename_for(base_filename, content_ext) proto_doc = read_proto_document(content_filename, meta_filename, klass) content = content_for(proto_doc, content_filename) attributes = attributes_for(proto_doc, content_filename, meta_filename) identifier = identifier_for(content_filename, meta_filename, dir_name) res << klass.new( content, attributes, identifier, content_checksum_data: content_checksum_data_for(proto_doc), attributes_checksum_data: attributes_checksum_data_for(proto_doc, content_filename, meta_filename), ) end end res end def content_checksum_data_for(proto_doc) Digest::SHA1.digest( proto_doc.content_checksum_data || '', ) end def attributes_checksum_data_for(proto_doc, content_filename, meta_filename) Digest::SHA1.digest( Marshal.dump( attributes: proto_doc.attributes_checksum_data, extra_attributes: extra_attributes_for(content_filename, meta_filename), ), ) end def extra_attributes_for(content_filename, meta_filename) { filename: content_filename, content_filename: content_filename, meta_filename: meta_filename, extension: content_filename ? ext_of(content_filename)[1..-1] : nil, mtime: mtime_of(content_filename, meta_filename), } end def attributes_for(proto_doc, content_filename, meta_filename) extra_attributes_for(content_filename, meta_filename).merge(proto_doc.attributes) end def identifier_for(content_filename, meta_filename, dir_name) if content_filename identifier_for_filename(content_filename[dir_name.length..-1]) elsif meta_filename identifier_for_filename(meta_filename[dir_name.length..-1]) else raise 'meta_filename and content_filename are both nil' end end def content_for(proto_doc, content_filename) full_content_filename = content_filename && File.expand_path(content_filename) if proto_doc.binary? Nanoc::Int::BinaryContent.new(full_content_filename) else Nanoc::Int::TextualContent.new(proto_doc.content, filename: full_content_filename) end end def mtime_of(content_filename, meta_filename) meta_mtime = meta_filename ? File.stat(meta_filename).mtime : nil content_mtime = content_filename ? File.stat(content_filename).mtime : nil if meta_mtime && content_mtime meta_mtime > content_mtime ? meta_mtime : content_mtime elsif meta_mtime meta_mtime elsif content_mtime content_mtime else raise 'meta_mtime and content_mtime are both nil' end end # e.g. # # { # 'content/foo' => [ 'yaml', ['html', 'md'] ], # 'content/bar' => [ 'yaml', [nil] ], # 'content/qux' => [ nil, ['html'] ] # } def all_split_files_in(dir_name) by_basename = all_files_in(dir_name) .reject { |fn| fn =~ /(~|\.orig|\.rej|\.bak)$/ } .group_by { |fn| basename_of(fn) } all = {} by_basename.each_pair do |basename, filenames| # Divide meta_filenames = filenames.select { |fn| ext_of(fn) == '.yaml' } content_filenames = filenames.reject { |fn| ext_of(fn) == '.yaml' } # Check number of files per type unless [0, 1].include?(meta_filenames.size) raise Errors::MultipleMetaFiles.new(meta_filenames, basename) end unless config[:identifier_type] == 'full' unless [0, 1].include?(content_filenames.size) raise Errors::MultipleContentFiles.new(meta_filenames, basename) end end all[basename] = [] all[basename][0] = meta_filenames[0] ? 'yaml' : nil all[basename][1] = content_filenames.any? ? content_filenames.map { |fn| ext_of(fn)[1..-1] || '' } : [nil] end all end # Returns all files in the given directory and directories below it. def all_files_in(dir_name) Nanoc::DataSources::Filesystem::Tools.all_files_in(dir_name, config[:extra_files]) end # Returns the filename for the given base filename and the extension. # # If the extension is nil, this function should return nil as well. # # A simple implementation would simply concatenate the base filename, a # period and an extension (which is what the # {Nanoc::DataSources::FilesystemCompact} data source does), but other # data sources may prefer to implement this differently (for example, # {Nanoc::DataSources::FilesystemVerbose} doubles the last part of the # basename before concatenating it with a period and the extension). def filename_for(base_filename, ext) if ext.nil? nil elsif ext.empty? base_filename else base_filename + '.' + ext end end # Returns the identifier that corresponds with the given filename, which # can be the content filename or the meta filename. def identifier_for_filename(filename) if config[:identifier_type] == 'full' return Nanoc::Identifier.new(filename) end regex = if filename =~ /(^|\/)index(\.[^\/]+)?$/ allow_periods_in_identifiers? ? /\/?(index)?(\.[^\/\.]+)?$/ : /\/?index(\.[^\/]+)?$/ else allow_periods_in_identifiers? ? /\.[^\/\.]+$/ : /\.[^\/]+$/ end Nanoc::Identifier.new(filename.sub(regex, ''), type: :legacy) end # Returns the base name of filename, i.e. filename with the first or all # extensions stripped off. By default, all extensions are stripped off, # but when allow_periods_in_identifiers is set to true in the site # configuration, only the last extension will be stripped . def basename_of(filename) filename.sub(extension_regex, '') end # Returns the extension(s) of filename. Supports multiple extensions. # Includes the leading period. def ext_of(filename) filename =~ extension_regex ? Regexp.last_match[1] : '' end # Returns a regex that is used for determining the extension of a file # name. The first match group will be the entire extension, including the # leading period. # # @return [Regex] def extension_regex if allow_periods_in_identifiers? /(\.[^\/\.]+$)/ else /(\.[^\/]+$)/ end end def allow_periods_in_identifiers? if @config @config[:allow_periods_in_identifiers] || @config[:identifier_type] == 'full' else false end end # @return [ParseResult] def parse(content_filename, meta_filename) if meta_filename parse_with_separate_meta_filename(content_filename, meta_filename) else parse_with_frontmatter(content_filename) end end # @return [ParseResult] def parse_with_separate_meta_filename(content_filename, meta_filename) content = content_filename ? read(content_filename) : '' meta_raw = read(meta_filename) meta = parse_metadata(meta_raw, meta_filename) ParseResult.new(content: content, attributes: meta, attributes_data: meta_raw) end # @return [ParseResult] def parse_with_frontmatter(content_filename) data = read(content_filename) if data !~ /\A-{3,5}\s*$/ return ParseResult.new(content: data, attributes: {}, attributes_data: '') end pieces = data.split(/^(-{5}|-{3})[ \t]*\r?\n?/, 3) if pieces.size < 4 raise Errors::InvalidFormat.new(content_filename) end meta = parse_metadata(pieces[2], content_filename) content = pieces[4] ParseResult.new(content: content, attributes: meta, attributes_data: pieces[2]) end # @return [Hash] def parse_metadata(data, filename) begin meta = YAML.load(data) || {} rescue => e raise Errors::UnparseableMetadata.new(filename, e) end verify_meta(meta, filename) meta end class ParseResult attr_reader :content attr_reader :attributes attr_reader :attributes_data def initialize(content:, attributes:, attributes_data:) @content = content @attributes = attributes @attributes_data = attributes_data end end def verify_meta(meta, filename) return if meta.is_a?(Hash) raise Errors::InvalidMetadata.new(filename, meta.class) end # Reads the content of the file with the given name and returns a string # in UTF-8 encoding. The original encoding of the string is derived from # the default external encoding, but this can be overridden by the # “encoding” configuration attribute in the data source configuration. def read(filename) # Read begin data = File.read(filename) rescue => e raise Errors::FileUnreadable.new(filename, e) end # Set original encoding, if any if @config && @config[:encoding] original_encoding = Encoding.find(@config[:encoding]) data.force_encoding(@config[:encoding]) else original_encoding = data.encoding end # Set encoding to UTF-8 begin data.encode!('UTF-8') rescue raise Errors::InvalidEncoding.new(filename, original_encoding) end # Verify unless data.valid_encoding? raise Errors::InvalidEncoding.new(filename, original_encoding) end # Remove UTF-8 BOM (ugly) data.delete!("\xEF\xBB\xBF") data end end end require_relative 'filesystem/tools' require_relative 'filesystem/errors' nanoc-4.8.0/lib/nanoc/data_sources.rb0000644000004100000410000000017013134141051017505 0ustar www-datawww-data# frozen_string_literal: true # @api private module Nanoc::DataSources end require_relative 'data_sources/filesystem' nanoc-4.8.0/.rubocop.yml0000644000004100000410000000712413134141051015120 0ustar www-datawww-data# ----- CONFIGURED ----- AllCops: TargetRubyVersion: 2.3 DisplayCopNames: true # We use filenames such as “create-site.rb” that translate to method names. FileName: Exclude: - 'lib/nanoc/cli/commands/*.rb' - 'Appraisals' # A common pattern in tests is to define anonymous classes in which methods are defined, which trips # up Rubocop’s nested method definition cop. Lint/NestedMethodDefinition: Exclude: - 'test/**/*.rb' - 'spec/**/*.rb' # This is used in tests, to verify the effect of state-changing functions. Style/GlobalVars: Exclude: - 'test/**/*.rb' Style/TrailingCommaInArguments: EnforcedStyleForMultiline: comma Style/TrailingCommaInLiteral: EnforcedStyleForMultiline: comma # `rescue nil` is useful in specs where the exception is not important, but # the size effects are. Style/RescueModifier: Exclude: - 'spec/**/*.rb' Layout/IndentArray: EnforcedStyle: consistent Lint/DuplicateMethods: Exclude: - 'test/data_sources/test_filesystem.rb' - 'spec/spec_helper.rb' # This needs to be fixed in Ruby 2.4. Lint/UnifiedInteger: Enabled: false Layout/IndentHeredoc: EnforcedStyle: squiggly # This breaks RSpec on occasion, e.g. `expect { subject }.not_to change { foo }`, # and generally does not provide useful warnings Lint/AmbiguousBlockAssociation: Enabled: false # ----- TO ENABLE LATER ----- # Valid cops, but fixing the offenses they report is non-trivial. RegexpLiteral: Enabled: false ClassAndModuleChildren: Enabled: false Style/EmptyElse: Enabled: false Style/Next: Enabled: false # ----- DISABLED (hard) ----- # Rubocop trips up on this. Layout/LeadingCommentSpace: Enabled: false # ----- DISABLED (security) ----- # Nanoc runs offline in a trusted environment, and these security checks are false positives. Security/YAMLLoad: Enabled: false Security/MarshalLoad: Enabled: false Security/Eval: Exclude: - 'test/**/*.rb' - 'spec/**/*.rb' - 'lib/nanoc/base/entities/code_snippet.rb' - 'lib/nanoc/filters/erubi.rb' # ----- DISABLED (metrics) ----- # Cops for metrics are disabled because they should not cause tests to fail. Metrics/AbcSize: Enabled: false Metrics/BlockLength: Enabled: false Metrics/BlockNesting: Enabled: false Metrics/ClassLength: Enabled: false Metrics/CyclomaticComplexity: Enabled: false Metrics/LineLength: Enabled: false Metrics/MethodLength: Enabled: false Metrics/ModuleLength: Enabled: false Metrics/ParameterLists: Enabled: false Metrics/PerceivedComplexity: Enabled: false # ----- DISABLED (opinionated) ----- # We should embrace UTF-8, not avoid it. Since the Encoding cop is enabled, # there’s no point in enforcing ASCII comments. AsciiComments: Enabled: false # It does not make sense to enforce everything to have documentation. Documentation: Enabled: false # Nanoc suppresses exceptions for valid reasons in a few cases. HandleExceptions: Enabled: false # if/unless at the end of the line makes it too easy to oversee. IfUnlessModifier: Enabled: false # Personal preference is to have decent constructors for exceptions rather than # just a class and a message. RaiseArgs: Enabled: false # Personal preference is to use `raise` to signal exceptions (normal control # flow should not use exceptions anyway). SignalException: Enabled: false # Some methods that appear to be accessors (return a single value or set a # single value) should still not be considered to be accessors. This is a purely # semantic difference. TrivialAccessors: Enabled: false # This does not always semantically make sense. GuardClause: Enabled: false nanoc-4.8.0/scripts/0000755000004100000410000000000013134141051014331 5ustar www-datawww-datananoc-4.8.0/scripts/release0000755000004100000410000000413213134141051015677 0ustar www-datawww-data#!/usr/bin/env ruby # frozen_string_literal: true require 'fileutils' require 'octokit' def run(*args) puts 'I will execute the following:' puts ' ' + args.map { |a| a =~ /\s/ ? a.inspect : a }.join(' ') print 'Is this correct? [y/N] ' res = gets unless res.strip.casecmp('y').zero? $stderr.puts 'Answer was not Y; release aborted.' exit 1 end system('echo', *args) system(*args) print 'Continue? [y/N] ' res = gets unless res.strip.casecmp('y').zero? $stderr.puts 'Answer was not Y; release aborted.' exit 1 end end puts '=== Logging in to GitHub’s API…' client = Octokit::Client.new(netrc: true) puts puts '=== Deleting old *.gem files…' Dir['*.gem'].each do |fn| puts " #{fn}…" FileUtils.rm_f(fn) end puts puts '=== Verifying presence of release date…' unless File.readlines('NEWS.md').drop(2).first =~ / \(\d{4}-\d{2}-\d{2}\)$/ $stderr.puts 'No proper release date found!' exit 1 end puts puts '=== Building new gem…' run('gem', 'build', 'nanoc.gemspec') puts puts '=== Reading version…' require './lib/nanoc/version' puts "Version = #{Nanoc::VERSION}" puts puts '=== Verifying that release does not yet exist…' releases = client.releases('nanoc/nanoc') release = releases.find { |r| r.tag_name == Nanoc::VERSION } if release $stderr.puts 'Release already exists!' $stderr.puts 'ABORTED!' exit 1 end puts puts '=== Creating Git tag…' run('git', 'tag', '--sign', '--annotate', Nanoc::VERSION, '--message', "Version #{Nanoc::VERSION}") puts puts '=== Pushing Git data…' run('git', 'push', 'origin', '--tags') puts puts '=== Pushing gem…' run('gem', 'push', "nanoc-#{Nanoc::VERSION}.gem") puts puts '=== Reading release notes…' release_notes = File.readlines('NEWS.md') .drop(4) .take_while { |l| l !~ /^## / } .join puts puts '=== Creating release on GitHub…' sleep 3 # Give GitHub some time to detect the new tag is_prerelease = Nanoc::VERSION =~ /a|b|rc/ || Nanoc::VERSION =~ /^0/ client.create_release( 'nanoc/nanoc', Nanoc::VERSION, prerelease: !is_prerelease.nil?, body: release_notes ) puts puts 'DONE!' nanoc-4.8.0/test/0000755000004100000410000000000013134141051013621 5ustar www-datawww-datananoc-4.8.0/test/base/0000755000004100000410000000000013134141051014533 5ustar www-datawww-datananoc-4.8.0/test/base/test_item_array.rb0000644000004100000410000000153713134141051020261 0ustar www-datawww-data# frozen_string_literal: true require 'helper' class Nanoc::Int::IdentifiableCollectionTest < Nanoc::TestCase def setup super @one = Nanoc::Int::Item.new('Item One', {}, '/one/') @two = Nanoc::Int::Item.new('Item Two', {}, '/two/') @items = Nanoc::Int::ItemCollection.new({}, [@one, @two]) end def test_change_item_identifier assert_equal @one, @items['/one/'] assert_nil @items['/foo/'] @one.identifier = '/foo/' assert_nil @items['/one/'] assert_equal @one, @items['/foo/'] end def test_enumerable assert_equal @one, @items.find { |i| i.identifier == '/one/' } end def test_less_than_less_than assert_nil @items['/foo/'] foo = Nanoc::Int::Item.new('Item Foo', {}, '/foo/') @items = Nanoc::Int::ItemCollection.new({}, [@one, @two, foo]) assert_equal foo, @items['/foo/'] end end nanoc-4.8.0/test/base/test_dependency_tracker.rb0000644000004100000410000002144213134141051021753 0ustar www-datawww-data# frozen_string_literal: true require 'helper' class Nanoc::Int::DependencyTrackerTest < Nanoc::TestCase def test_initialize # Mock objects config = Nanoc::Int::Configuration.new.with_defaults layouts = Nanoc::Int::LayoutCollection.new(config) items = Nanoc::Int::ItemCollection.new(config, [ Nanoc::Int::Item.new('a', {}, '/a.md'), Nanoc::Int::Item.new('b', {}, '/b.md'), ]) # Create store = Nanoc::Int::DependencyStore.new(items, layouts, config) # Verify no dependencies yet assert_empty store.objects_causing_outdatedness_of(items.to_a[0]) assert_empty store.objects_causing_outdatedness_of(items.to_a[1]) end def test_record_dependency # Mock objects config = Nanoc::Int::Configuration.new.with_defaults layouts = Nanoc::Int::LayoutCollection.new(config) items = Nanoc::Int::ItemCollection.new(config, [ Nanoc::Int::Item.new('a', {}, '/a.md'), Nanoc::Int::Item.new('b', {}, '/b.md'), ]) # Create store = Nanoc::Int::DependencyStore.new(items, layouts, config) # Record some dependencies store.record_dependency(items.to_a[0], items.to_a[1]) # Verify dependencies assert_contains_exactly [items.to_a[1]], store.objects_causing_outdatedness_of(items.to_a[0]) end def test_record_dependency_no_self # Mock objects config = Nanoc::Int::Configuration.new.with_defaults layouts = Nanoc::Int::LayoutCollection.new(config) items = Nanoc::Int::ItemCollection.new(config, [ Nanoc::Int::Item.new('a', {}, '/a.md'), Nanoc::Int::Item.new('b', {}, '/b.md'), ]) # Create store = Nanoc::Int::DependencyStore.new(items, layouts, config) # Record some dependencies store.record_dependency(items.to_a[0], items.to_a[0]) store.record_dependency(items.to_a[0], items.to_a[1]) # Verify dependencies assert_contains_exactly [items.to_a[1]], store.objects_causing_outdatedness_of(items.to_a[0]) end def test_record_dependency_no_doubles # Mock objects config = Nanoc::Int::Configuration.new.with_defaults layouts = Nanoc::Int::LayoutCollection.new(config) items = Nanoc::Int::ItemCollection.new(config, [ Nanoc::Int::Item.new('a', {}, '/a.md'), Nanoc::Int::Item.new('b', {}, '/b.md'), ]) # Create store = Nanoc::Int::DependencyStore.new(items, layouts, config) # Record some dependencies store.record_dependency(items.to_a[0], items.to_a[1]) store.record_dependency(items.to_a[0], items.to_a[1]) store.record_dependency(items.to_a[0], items.to_a[1]) # Verify dependencies assert_contains_exactly [items.to_a[1]], store.objects_causing_outdatedness_of(items.to_a[0]) end def test_objects_causing_outdatedness_of # Mock objects config = Nanoc::Int::Configuration.new.with_defaults layouts = Nanoc::Int::LayoutCollection.new(config) items = Nanoc::Int::ItemCollection.new(config, [ Nanoc::Int::Item.new('a', {}, '/a.md'), Nanoc::Int::Item.new('b', {}, '/b.md'), Nanoc::Int::Item.new('c', {}, '/c.md'), ]) # Create store = Nanoc::Int::DependencyStore.new(items, layouts, config) # Record some dependencies store.record_dependency(items.to_a[0], items.to_a[1]) store.record_dependency(items.to_a[1], items.to_a[2]) # Verify dependencies assert_contains_exactly [items.to_a[1]], store.objects_causing_outdatedness_of(items.to_a[0]) end def test_store_graph_and_load_graph_simple # Mock objects config = Nanoc::Int::Configuration.new.with_defaults layouts = Nanoc::Int::LayoutCollection.new(config) items = Nanoc::Int::ItemCollection.new(config, [ Nanoc::Int::Item.new('a', {}, '/a.md'), Nanoc::Int::Item.new('b', {}, '/b.md'), Nanoc::Int::Item.new('c', {}, '/c.md'), Nanoc::Int::Item.new('d', {}, '/d.md'), ]) # Create store = Nanoc::Int::DependencyStore.new(items, layouts, config) # Record some dependencies store.record_dependency(items.to_a[0], items.to_a[1]) store.record_dependency(items.to_a[1], items.to_a[2]) store.record_dependency(items.to_a[1], items.to_a[3]) # Store store.store assert File.file?(store.filename) # Re-create store = Nanoc::Int::DependencyStore.new(items, layouts, config) # Load store.load # Check loaded graph assert_contains_exactly [items.to_a[1]], store.objects_causing_outdatedness_of(items.to_a[0]) assert_contains_exactly [items.to_a[2], items.to_a[3]], store.objects_causing_outdatedness_of(items.to_a[1]) assert_empty store.objects_causing_outdatedness_of(items.to_a[2]) assert_empty store.objects_causing_outdatedness_of(items.to_a[3]) end def test_store_graph_and_load_graph_with_removed_items # Mock objects config = Nanoc::Int::Configuration.new.with_defaults layouts = Nanoc::Int::LayoutCollection.new(config) items = Nanoc::Int::ItemCollection.new(config, [ Nanoc::Int::Item.new('a', {}, '/a.md'), Nanoc::Int::Item.new('b', {}, '/b.md'), Nanoc::Int::Item.new('c', {}, '/c.md'), Nanoc::Int::Item.new('d', {}, '/d.md'), ]) # Create new and old lists old_items = Nanoc::Int::ItemCollection.new(config, [items.to_a[0], items.to_a[1], items.to_a[2], items.to_a[3]]) new_items = Nanoc::Int::ItemCollection.new(config, [items.to_a[0], items.to_a[1], items.to_a[2]]) # Create store = Nanoc::Int::DependencyStore.new(old_items, layouts, config) # Record some dependencies store.record_dependency(items.to_a[0], items.to_a[1]) store.record_dependency(items.to_a[1], items.to_a[2]) store.record_dependency(items.to_a[1], items.to_a[3]) # Store store.store assert File.file?(store.filename) # Re-create store = Nanoc::Int::DependencyStore.new(new_items, layouts, config) # Load store.load # Check loaded graph assert_contains_exactly [items.to_a[1]], store.objects_causing_outdatedness_of(items.to_a[0]) assert_contains_exactly [items.to_a[2], nil], store.objects_causing_outdatedness_of(items.to_a[1]) assert_empty store.objects_causing_outdatedness_of(items.to_a[2]) end def test_store_graph_with_nils_in_dst # Mock objects config = Nanoc::Int::Configuration.new.with_defaults layouts = Nanoc::Int::LayoutCollection.new(config) items = Nanoc::Int::ItemCollection.new(config, [ Nanoc::Int::Item.new('a', {}, '/a.md'), Nanoc::Int::Item.new('b', {}, '/b.md'), Nanoc::Int::Item.new('c', {}, '/c.md'), ]) # Create store = Nanoc::Int::DependencyStore.new(items, layouts, config) # Record some dependencies store.record_dependency(items.to_a[0], items.to_a[1]) store.record_dependency(items.to_a[1], nil) # Store store.store assert File.file?(store.filename) # Re-create store = Nanoc::Int::DependencyStore.new(items, layouts, config) # Load store.load # Check loaded graph assert_contains_exactly [items.to_a[1]], store.objects_causing_outdatedness_of(items.to_a[0]) assert_contains_exactly [nil], store.objects_causing_outdatedness_of(items.to_a[1]) end def test_store_graph_with_nils_in_src # Mock objects config = Nanoc::Int::Configuration.new.with_defaults layouts = Nanoc::Int::LayoutCollection.new(config) items = Nanoc::Int::ItemCollection.new(config, [ Nanoc::Int::Item.new('a', {}, '/a.md'), Nanoc::Int::Item.new('b', {}, '/b.md'), Nanoc::Int::Item.new('c', {}, '/c.md'), ]) # Create store = Nanoc::Int::DependencyStore.new(items, layouts, config) # Record some dependencies store.record_dependency(items.to_a[0], items.to_a[1]) store.record_dependency(nil, items.to_a[2]) # Store store.store assert File.file?(store.filename) # Re-create store = Nanoc::Int::DependencyStore.new(items, layouts, config) # Load store.load # Check loaded graph assert_contains_exactly [items.to_a[1]], store.objects_causing_outdatedness_of(items.to_a[0]) assert_empty store.objects_causing_outdatedness_of(items.to_a[1]) end def test_forget_dependencies_for # Mock objects config = Nanoc::Int::Configuration.new.with_defaults layouts = Nanoc::Int::LayoutCollection.new(config) items = Nanoc::Int::ItemCollection.new(config, [ Nanoc::Int::Item.new('a', {}, '/a.md'), Nanoc::Int::Item.new('b', {}, '/b.md'), Nanoc::Int::Item.new('c', {}, '/c.md'), ]) # Create store = Nanoc::Int::DependencyStore.new(items, layouts, config) # Record some dependencies store.record_dependency(items.to_a[0], items.to_a[1]) store.record_dependency(items.to_a[1], items.to_a[2]) assert_contains_exactly [items.to_a[1]], store.objects_causing_outdatedness_of(items.to_a[0]) # Forget dependencies store.forget_dependencies_for(items.to_a[0]) assert_empty store.objects_causing_outdatedness_of(items.to_a[0]) end end nanoc-4.8.0/test/base/test_directed_graph.rb0000644000004100000410000000462413134141051021071 0ustar www-datawww-data# frozen_string_literal: true require 'helper' class Nanoc::Int::DirectedGraphTest < Nanoc::TestCase def test_add_edge graph = Nanoc::Int::DirectedGraph.new([1, 2, 3]) assert_equal [], graph.successors_of(1) assert_equal [], graph.predecessors_of(2) graph.add_edge(1, 2) assert_equal [2], graph.successors_of(1) assert_equal [1], graph.predecessors_of(2) end def test_add_edge_with_new_vertices graph = Nanoc::Int::DirectedGraph.new([1]) graph.add_edge(1, 2) graph.add_edge(3, 2) assert graph.vertices.include?(2) assert graph.vertices.include?(3) end def test_delete_edges_to graph = Nanoc::Int::DirectedGraph.new([1, 2, 3]) graph.add_edge(1, 2) graph.add_edge(2, 1) graph.add_edge(2, 3) graph.add_edge(3, 2) graph.add_edge(1, 3) graph.add_edge(3, 1) assert_equal [2, 3], graph.direct_predecessors_of(1).sort assert_equal [2, 3], graph.direct_successors_of(1).sort assert_equal [1, 3], graph.direct_predecessors_of(2).sort assert_equal [1, 3], graph.direct_successors_of(2).sort assert_equal [1, 2], graph.direct_predecessors_of(3).sort assert_equal [1, 2], graph.direct_successors_of(3).sort graph.delete_edges_to(1) assert_equal [], graph.direct_predecessors_of(1).sort assert_equal [2, 3], graph.direct_successors_of(1).sort assert_equal [1, 3], graph.direct_predecessors_of(2).sort assert_equal [3], graph.direct_successors_of(2).sort assert_equal [1, 2], graph.direct_predecessors_of(3).sort assert_equal [2], graph.direct_successors_of(3).sort graph.delete_edges_to(2) assert_equal [], graph.direct_predecessors_of(1).sort assert_equal [3], graph.direct_successors_of(1).sort assert_equal [], graph.direct_predecessors_of(2).sort assert_equal [3], graph.direct_successors_of(2).sort assert_equal [1, 2], graph.direct_predecessors_of(3).sort assert_equal [], graph.direct_successors_of(3).sort end def test_should_return_empty_array_for_nonexistant_vertices graph = Nanoc::Int::DirectedGraph.new([1, 2, 3]) assert_equal [], graph.direct_predecessors_of(4) assert_equal [], graph.predecessors_of(4) assert_equal [], graph.direct_successors_of(4) assert_equal [], graph.successors_of(4) end def test_example YARD.parse(LIB_DIR + '/nanoc/base/entities/directed_graph.rb') assert_examples_correct 'Nanoc::Int::DirectedGraph' end end nanoc-4.8.0/test/base/test_notification_center.rb0000644000004100000410000000134313134141051022146 0ustar www-datawww-data# frozen_string_literal: true require 'helper' class Nanoc::Int::NotificationCenterTest < Nanoc::TestCase def test_post # Set up notification Nanoc::Int::NotificationCenter.on :ping_received, :test do @ping_received = true end # Post @ping_received = false Nanoc::Int::NotificationCenter.post :ping_received assert(@ping_received) end def test_remove # Set up notification Nanoc::Int::NotificationCenter.on :ping_received, :test do @ping_received = true end # Remove observer Nanoc::Int::NotificationCenter.remove :ping_received, :test # Post @ping_received = false Nanoc::Int::NotificationCenter.post :ping_received assert(!@ping_received) end end nanoc-4.8.0/test/base/test_site.rb0000644000004100000410000001045213134141051017065 0ustar www-datawww-data# frozen_string_literal: true require 'helper' class Nanoc::Int::SiteTest < Nanoc::TestCase def test_initialize_with_dir_without_config_yaml assert_raises(Nanoc::Int::ConfigLoader::NoConfigFileFoundError) do Nanoc::Int::SiteLoader.new.new_from_cwd end end def test_initialize_with_dir_with_config_yaml File.open('config.yaml', 'w') { |io| io.write('output_dir: public_html') } site = Nanoc::Int::SiteLoader.new.new_from_cwd assert_equal 'public_html', site.config[:output_dir] end def test_initialize_with_dir_with_nanoc_yaml File.open('nanoc.yaml', 'w') { |io| io.write('output_dir: public_html') } site = Nanoc::Int::SiteLoader.new.new_from_cwd assert_equal 'public_html', site.config[:output_dir] end def test_initialize_with_config_hash site = Nanoc::Int::SiteLoader.new.new_with_config(foo: 'bar') assert_equal 'bar', site.config[:foo] end def test_initialize_with_incomplete_data_source_config site = Nanoc::Int::SiteLoader.new.new_with_config(data_sources: [{ items_root: '/bar/' }]) assert_equal('filesystem', site.config[:data_sources][0][:type]) assert_equal('/bar/', site.config[:data_sources][0][:items_root]) assert_equal('/', site.config[:data_sources][0][:layouts_root]) assert_equal({}, site.config[:data_sources][0][:config]) end def test_initialize_with_existing_parent_config_file File.open('nanoc.yaml', 'w') do |io| io.write <<~EOF output_dir: public_html parent_config_file: foo/foo.yaml EOF end FileUtils.mkdir_p('foo') FileUtils.cd('foo') do File.open('foo.yaml', 'w') do |io| io.write <<~EOF parent_config_file: ../bar/bar.yaml EOF end end FileUtils.mkdir_p('bar') FileUtils.cd('bar') do File.open('bar.yaml', 'w') do |io| io.write <<~EOF enable_output_diff: true foo: bar output_dir: output EOF end end site = Nanoc::Int::SiteLoader.new.new_from_cwd assert_nil site.config[:parent_config_file] assert site.config[:enable_output_diff] assert_equal 'bar', site.config[:foo] assert_equal 'public_html', site.config[:output_dir] end def test_initialize_with_missing_parent_config_file File.open('nanoc.yaml', 'w') do |io| io.write <<~EOF parent_config_file: foo/foo.yaml EOF end assert_raises(Nanoc::Int::ConfigLoader::NoParentConfigFileFoundError) do Nanoc::Int::SiteLoader.new.new_from_cwd end end def test_initialize_with_parent_config_file_cycle File.open('nanoc.yaml', 'w') do |io| io.write <<~EOF parent_config_file: foo/foo.yaml EOF end FileUtils.mkdir_p('foo') FileUtils.cd('foo') do File.open('foo.yaml', 'w') do |io| io.write <<~EOF parent_config_file: ../nanoc.yaml EOF end end assert_raises(Nanoc::Int::ConfigLoader::CyclicalConfigFileError) do Nanoc::Int::SiteLoader.new.new_from_cwd end end def test_identifier_classes Nanoc::CLI.run %w[create_site bar] FileUtils.cd('bar') do FileUtils.mkdir_p('content') FileUtils.mkdir_p('layouts') File.open('content/foo_bar.md', 'w') { |io| io << 'asdf' } File.open('layouts/detail.erb', 'w') { |io| io << 'asdf' } site = Nanoc::Int::SiteLoader.new.new_from_cwd site.items.each do |item| assert_equal Nanoc::Identifier, item.identifier.class end site.layouts.each do |layout| assert_equal Nanoc::Identifier, layout.identifier.class end end end def test_multiple_items_with_same_identifier with_site do File.open('content/sam.html', 'w') { |io| io.write('I am Sam!') } FileUtils.mkdir_p('content/sam') File.open('content/sam/index.html', 'w') { |io| io.write('I am Sam, too!') } assert_raises(Nanoc::Int::Errors::DuplicateIdentifier) do Nanoc::Int::SiteLoader.new.new_from_cwd end end end def test_multiple_layouts_with_same_identifier with_site do File.open('layouts/sam.html', 'w') { |io| io.write('I am Sam!') } FileUtils.mkdir_p('layouts/sam') File.open('layouts/sam/index.html', 'w') { |io| io.write('I am Sam, too!') } assert_raises(Nanoc::Int::Errors::DuplicateIdentifier) do Nanoc::Int::SiteLoader.new.new_from_cwd end end end end nanoc-4.8.0/test/base/test_compiler.rb0000644000004100000410000003103013134141051017726 0ustar www-datawww-data# frozen_string_literal: true require 'helper' class Nanoc::Int::CompilerTest < Nanoc::TestCase def test_compile_rep_should_write_proper_snapshots_real with_site do |site| File.write('content/moo.txt', '<%= 1 %> <%%= 2 %> <%%%= 3 %>') File.write('layouts/default.erb', 'head <%= yield %> foot') File.open('Rules', 'w') do |io| io.write "compile '/**/*' do\n" io.write " filter :erb\n" io.write " filter :erb\n" io.write " layout 'default'\n" io.write " filter :erb\n" io.write "end\n" io.write "\n" io.write "route '/**/*', snapshot: :raw do\n" io.write " '/moo-raw.txt'\n" io.write "end\n" io.write "\n" io.write "route '/**/*', snapshot: :pre do\n" io.write " '/moo-pre.txt'\n" io.write "end\n" io.write "\n" io.write "route '/**/*', snapshot: :post do\n" io.write " '/moo-post.txt'\n" io.write "end\n" io.write "\n" io.write "route '/**/*' do\n" io.write " '/moo-last.txt'\n" io.write "end\n" io.write "\n" io.write "layout '/**/*', :erb\n" end site = Nanoc::Int::SiteLoader.new.new_from_cwd site.compile assert File.file?('output/moo-raw.txt') assert File.file?('output/moo-pre.txt') assert File.file?('output/moo-post.txt') assert File.file?('output/moo-last.txt') assert_equal '<%= 1 %> <%%= 2 %> <%%%= 3 %>', File.read('output/moo-raw.txt') assert_equal '1 2 <%= 3 %>', File.read('output/moo-pre.txt') assert_equal 'head 1 2 3 foot', File.read('output/moo-post.txt') assert_equal 'head 1 2 3 foot', File.read('output/moo-last.txt') end end def test_compile_with_no_reps with_site do |site| site.compile assert Dir['output/*'].empty? end end def test_compile_with_one_rep with_site do |site| File.open('content/index.html', 'w') { |io| io.write('o hello') } site = Nanoc::Int::SiteLoader.new.new_from_cwd site.compile assert Dir['output/*'].size == 1 assert File.file?('output/index.html') assert File.read('output/index.html') == 'o hello' end end def test_compile_with_two_independent_reps with_site do |site| File.open('content/foo.html', 'w') { |io| io.write('o hai') } File.open('content/bar.html', 'w') { |io| io.write('o bai') } site = Nanoc::Int::SiteLoader.new.new_from_cwd site.compile assert Dir['output/*'].size == 2 assert File.file?('output/foo/index.html') assert File.file?('output/bar/index.html') assert File.read('output/foo/index.html') == 'o hai' assert File.read('output/bar/index.html') == 'o bai' end end def test_compile_with_two_dependent_reps with_site(compilation_rule_content: 'filter :erb') do |site| File.open('content/foo.html', 'w') do |io| io.write('<%= @items.find { |i| i.identifier == "/bar/" }.compiled_content %>!!!') end File.open('content/bar.html', 'w') do |io| io.write('manatee') end site = Nanoc::Int::SiteLoader.new.new_from_cwd site.compile assert Dir['output/*'].size == 2 assert File.file?('output/foo/index.html') assert File.file?('output/bar/index.html') assert File.read('output/foo/index.html') == 'manatee!!!' assert File.read('output/bar/index.html') == 'manatee' end end def test_compile_with_two_mutually_dependent_reps with_site(compilation_rule_content: 'filter :erb') do |site| File.open('content/foo.html', 'w') do |io| io.write('<%= @items.find { |i| i.identifier == "/bar/" }.compiled_content %>') end File.open('content/bar.html', 'w') do |io| io.write('<%= @items.find { |i| i.identifier == "/foo/" }.compiled_content %>') end site = Nanoc::Int::SiteLoader.new.new_from_cwd assert_raises Nanoc::Int::Errors::DependencyCycle do site.compile end end end def test_disallow_routes_not_starting_with_slash # Create site Nanoc::CLI.run %w[create_site bar] FileUtils.cd('bar') do # Create routes File.open('Rules', 'w') do |io| io.write "compile '/**/*' do\n" io.write " layout 'default'\n" io.write "end\n" io.write "\n" io.write "route '/**/*' do\n" io.write " 'index.html'\n" io.write "end\n" io.write "\n" io.write "layout '/**/*', :erb\n" end # Create site site = Nanoc::Int::SiteLoader.new.new_from_cwd error = assert_raises(Nanoc::Error) do site.compile end assert_match(/^The path returned for the.*does not start with a slash. Please ensure that all routing rules return a path that starts with a slash./, error.message) end end def test_disallow_duplicate_routes # Create site Nanoc::CLI.run %w[create_site bar] FileUtils.cd('bar') do # Create routes File.open('Rules', 'w') do |io| io.write "compile '/**/*' do\n" io.write "end\n" io.write "\n" io.write "route '/**/*' do\n" io.write " '/index.html'\n" io.write "end\n" end # Create files File.write('content/foo.html', 'asdf') File.write('content/bar.html', 'asdf') # Create site site = Nanoc::Int::SiteLoader.new.new_from_cwd assert_raises(Nanoc::Int::ItemRepRouter::IdenticalRoutesError) do site.compile end end end def test_disallow_multiple_snapshots_with_the_same_name # Create site Nanoc::CLI.run %w[create_site bar] FileUtils.cd('bar') do # Create routes File.open('Rules', 'w') do |io| io.write "compile '/**/*' do\n" io.write " snapshot :aaa\n" io.write " snapshot :aaa\n" io.write "end\n" io.write "\n" io.write "route '/**/*' do\n" io.write " item.identifier.to_s\n" io.write "end\n" io.write "\n" io.write "layout '/**/*', :erb\n" end # Compile site = Nanoc::Int::SiteLoader.new.new_from_cwd assert_raises Nanoc::Int::Errors::CannotCreateMultipleSnapshotsWithSameName do site.compile end end end def test_include_compiled_content_of_active_item_at_previous_snapshot with_site do |site| # Create item File.open('content/index.html', 'w') do |io| io.write('[<%= @item.compiled_content(:snapshot => :aaa) %>]') end # Create routes File.open('Rules', 'w') do |io| io.write "compile '*' do\n" io.write " snapshot :aaa\n" io.write " filter :erb\n" io.write " filter :erb\n" io.write "end\n" io.write "\n" io.write "route '*' do\n" io.write " '/index.html'\n" io.write "end\n" io.write "\n" io.write "layout '*', :erb\n" end # Compile site = Nanoc::Int::SiteLoader.new.new_from_cwd site.compile # Check assert_equal '[[[<%= @item.compiled_content(:snapshot => :aaa) %>]]]', File.read('output/index.html') end end def test_mutually_include_compiled_content_at_previous_snapshot with_site do |site| # Create items File.open('content/a.html', 'w') do |io| io.write('[<%= @items.find { |i| i.identifier == "/z/" }.compiled_content(:snapshot => :guts) %>]') end File.open('content/z.html', 'w') do |io| io.write('stuff') end # Create routes File.open('Rules', 'w') do |io| io.write "compile '*' do\n" io.write " snapshot :guts\n" io.write " filter :erb\n" io.write "end\n" io.write "\n" io.write "route '*' do\n" io.write " item.identifier + 'index.html'\n" io.write "end\n" io.write "\n" io.write "layout '*', :erb\n" end # Compile site = Nanoc::Int::SiteLoader.new.new_from_cwd site.compile # Check assert_equal '[stuff]', File.read('output/a/index.html') assert_equal 'stuff', File.read('output/z/index.html') end end def test_layout_with_extra_filter_args with_site do |site| # Create item File.open('content/index.html', 'w') do |io| io.write('This is <%= @foo %>.') end # Create routes File.open('Rules', 'w') do |io| io.write "compile '*' do\n" io.write " filter :erb, :locals => { :foo => 123 }\n" io.write "end\n" io.write "\n" io.write "route '*' do\n" io.write " '/index.html'\n" io.write "end\n" io.write "\n" io.write "layout '*', :erb\n" end # Compile site = Nanoc::Int::SiteLoader.new.new_from_cwd site.compile # Check assert_equal 'This is 123.', File.read('output/index.html') end end def test_change_routing_rule_and_recompile with_site do |site| # Create items File.open('content/a.html', 'w') do |io| io.write('

A

') end File.open('content/b.html', 'w') do |io| io.write('

B

') end # Create routes File.open('Rules', 'w') do |io| io.write "compile '*' do\n" io.write "end\n" io.write "\n" io.write "route '/a/' do\n" io.write " '/index.html'\n" io.write "end\n" io.write "\n" io.write "route '*' do\n" io.write " nil\n" io.write "end\n" end # Compile site = Nanoc::Int::SiteLoader.new.new_from_cwd site.compile # Check assert_equal '

A

', File.read('output/index.html') # Create routes File.open('Rules', 'w') do |io| io.write "compile '*' do\n" io.write "end\n" io.write "\n" io.write "route '/b/' do\n" io.write " '/index.html'\n" io.write "end\n" io.write "\n" io.write "route '*' do\n" io.write " nil\n" io.write "end\n" end # Compile site = Nanoc::Int::SiteLoader.new.new_from_cwd site.compile # Check assert_equal '

B

', File.read('output/index.html') end end def test_rep_assigns with_site do |site| # Create item File.open('content/index.html', 'w') do |io| io.write('@rep.name = <%= @rep.name %> - @item_rep.name = <%= @item_rep.name %>') end # Create routes File.open('Rules', 'w') do |io| io.write "compile '*' do\n" io.write " if @rep.name == :default && @item_rep.name == :default\n" io.write " filter :erb\n" io.write " end\n" io.write "end\n" io.write "\n" io.write "route '*' do\n" io.write " '/index.html'\n" io.write "end\n" io.write "\n" io.write "layout '*', :erb\n" end # Compile site = Nanoc::Int::SiteLoader.new.new_from_cwd site.compile # Check assert_equal '@rep.name = default - @item_rep.name = default', File.read('output/index.html') end end def test_unfiltered_binary_item_should_not_be_moved_outside_content with_site do File.open('content/blah.dat', 'w') { |io| io.write('o hello') } File.open('Rules', 'w') do |io| io.write "compile '*' do\n" io.write "end\n" io.write "\n" io.write "route '*' do\n" io.write " item.identifier.chop + '.' + item[:extension]\n" io.write "end\n" io.write "\n" io.write "layout '*', :erb\n" end site = Nanoc::Int::SiteLoader.new.new_from_cwd site.compile assert_equal Set.new(%w[content/blah.dat]), Set.new(Dir['content/*']) assert_equal Set.new(%w[output/blah.dat]), Set.new(Dir['output/*']) end end def test_tmp_text_items_are_removed_after_compilation with_site do |site| # Create item File.open('content/index.html', 'w') do |io| io.write('stuff') end # Compile site = Nanoc::Int::SiteLoader.new.new_from_cwd site.compile # Check assert Dir['tmp/text_items/*'].empty? end end def test_find_layouts_by_glob Nanoc::CLI.run %w[create_site bar] FileUtils.cd('bar') do File.open('Rules', 'w') do |io| io.write "compile '/**/*' do\n" io.write " layout '/default.*'\n" io.write "end\n" io.write "\n" io.write "route '/**/*' do\n" io.write " item.identifier.to_s\n" io.write "end\n" io.write "\n" io.write "layout '/**/*', :erb\n" end site = Nanoc::Int::SiteLoader.new.new_from_cwd site.compile end end end nanoc-4.8.0/test/base/test_context.rb0000644000004100000410000000163013134141051017603 0ustar www-datawww-data# frozen_string_literal: true require 'helper' class Nanoc::Int::ContextTest < Nanoc::TestCase def test_context_with_instance_variable # Create context context = Nanoc::Int::Context.new(foo: 'bar', baz: 'quux') # Ensure correct evaluation assert_equal('bar', eval('@foo', context.get_binding)) end def test_context_with_instance_method # Create context context = Nanoc::Int::Context.new(foo: 'bar', baz: 'quux') # Ensure correct evaluation assert_equal('bar', eval('foo', context.get_binding)) end def test_example # Parse YARD.parse(LIB_DIR + '/nanoc/base/entities/context.rb') # Run assert_examples_correct 'Nanoc::Int::Context#initialize' end def test_include context = Nanoc::Int::Context.new({}) eval('include Nanoc::Helpers::HTMLEscape', context.get_binding) assert_equal('<>', eval('h("<>")', context.get_binding)) end end nanoc-4.8.0/test/base/test_filter.rb0000644000004100000410000000310313134141051017401 0ustar www-datawww-data# frozen_string_literal: true require 'helper' class Nanoc::FilterTest < Nanoc::TestCase def test_initialize # Create filter filter = Nanoc::Filter.new # Test assigns assert_equal({}, filter.instance_eval { @assigns }) end def test_assigns_with_instance_variables # Create filter filter = Nanoc::Filter.new(foo: 'bar') # Check assigns assert_equal('bar', filter.instance_eval { @foo }) end def test_assigns_with_instance_methods # Create filter filter = Nanoc::Filter.new(foo: 'bar') # Check assigns assert_equal('bar', filter.instance_eval { foo }) end def test_run # Create filter filter = Nanoc::Filter.new # Make sure an error is raised assert_raises(NotImplementedError) do filter.run(nil) end end def test_filename_item # Mock items item = mock item.expects(:identifier).returns('/foo/bar/baz/') item_rep = mock item_rep.expects(:name).returns(:quux) # Create filter filter = Nanoc::Filter.new(item: item, item_rep: item_rep) # Check filename assert_equal('item /foo/bar/baz/ (rep quux)', filter.filename) end def test_filename_layout # Mock items layout = mock layout.expects(:identifier).returns('/wohba/') # Create filter filter = Nanoc::Filter.new(item: mock, item_rep: mock, layout: layout) # Check filename assert_equal('layout /wohba/', filter.filename) end def test_filename_unknown # Create filter filter = Nanoc::Filter.new({}) # Check filename assert_equal('?', filter.filename) end end nanoc-4.8.0/test/base/test_code_snippet.rb0000644000004100000410000000125013134141051020571 0ustar www-datawww-data# frozen_string_literal: true require 'helper' class Nanoc::Int::CodeSnippetTest < Nanoc::TestCase def test_load # Initialize $complete_insane_parrot = 'meow' # Create code and load it code_snippet = Nanoc::Int::CodeSnippet.new("$complete_insane_parrot = 'woof'", 'parrot.rb') code_snippet.load # Ensure code is loaded assert_equal('woof', $complete_insane_parrot) end def test_load_with_toplevel_binding # Initialize @foo = 'meow' # Create code and load it code_snippet = Nanoc::Int::CodeSnippet.new("@foo = 'woof'", 'dog.rb') code_snippet.load # Ensure binding is correct assert_equal('meow', @foo) end end nanoc-4.8.0/test/base/test_data_source.rb0000644000004100000410000000547013134141051020416 0ustar www-datawww-data# frozen_string_literal: true require 'helper' class Nanoc::DataSourceTest < Nanoc::TestCase def test_loading # Create data source data_source = Nanoc::DataSource.new(nil, nil, nil, nil) data_source.expects(:up).times(1) data_source.expects(:down).times(1) # Test nested loading assert_equal(0, data_source.instance_eval { @references }) data_source.loading do assert_equal(1, data_source.instance_eval { @references }) data_source.loading do assert_equal(2, data_source.instance_eval { @references }) end assert_equal(1, data_source.instance_eval { @references }) end assert_equal(0, data_source.instance_eval { @references }) end def test_not_implemented # Create data source data_source = Nanoc::DataSource.new(nil, nil, nil, nil) # Test optional methods data_source.up data_source.down # Test methods - loading data assert_equal [], data_source.items assert_equal [], data_source.layouts end def test_new_item data_source = Nanoc::DataSource.new(nil, nil, nil, nil) item = data_source.new_item('stuff', { title: 'Stuff!' }, '/asdf/', checksum_data: 'abcdef') assert_equal 'stuff', item.content.string assert_equal 'Stuff!', item.attributes[:title] assert_equal Nanoc::Identifier.new('/asdf/'), item.identifier assert_equal 'abcdef', item.checksum_data end def test_new_item_with_checksums data_source = Nanoc::DataSource.new(nil, nil, nil, nil) item = data_source.new_item('stuff', { title: 'Stuff!' }, '/asdf/', content_checksum_data: 'con-cs', attributes_checksum_data: 'attr-cs') assert_equal 'stuff', item.content.string assert_equal 'Stuff!', item.attributes[:title] assert_equal Nanoc::Identifier.new('/asdf/'), item.identifier assert_equal 'con-cs', item.content_checksum_data assert_equal 'attr-cs', item.attributes_checksum_data end def test_new_layout data_source = Nanoc::DataSource.new(nil, nil, nil, nil) layout = data_source.new_layout('stuff', { title: 'Stuff!' }, '/asdf/', checksum_data: 'abcdef') assert_equal 'stuff', layout.content.string assert_equal 'Stuff!', layout.attributes[:title] assert_equal Nanoc::Identifier.new('/asdf/'), layout.identifier assert_equal 'abcdef', layout.checksum_data end def test_new_layout_with_checksums data_source = Nanoc::DataSource.new(nil, nil, nil, nil) layout = data_source.new_layout('stuff', { title: 'Stuff!' }, '/asdf/', content_checksum_data: 'con-cs', attributes_checksum_data: 'attr-cs') assert_equal 'stuff', layout.content.string assert_equal 'Stuff!', layout.attributes[:title] assert_equal Nanoc::Identifier.new('/asdf/'), layout.identifier assert_equal 'con-cs', layout.content_checksum_data assert_equal 'attr-cs', layout.attributes_checksum_data end end nanoc-4.8.0/test/base/test_store.rb0000644000004100000410000000134513134141051017256 0ustar www-datawww-data# frozen_string_literal: true require 'helper' class Nanoc::Int::StoreTest < Nanoc::TestCase class TestStore < Nanoc::Int::Store def data @data end def data=(new_data) @data = new_data end end def test_delete_and_reload_on_error store = TestStore.new('test.db', 1) # Create store.load store.data = { fun: 'sure' } store.store # Test stored values store = TestStore.new('test.db', 1) store.load assert_equal({ fun: 'sure' }, store.data) # Mess up File.open('test.db', 'w') do |io| io << 'Damn {}#}%@}$^)@&$&*^#@ broken stores!!!' end # Reload store = TestStore.new('test.db', 1) store.load assert_equal(nil, store.data) end end nanoc-4.8.0/test/helper.rb0000644000004100000410000001505013134141051015426 0ustar www-datawww-data# frozen_string_literal: true $VERBOSE = false require 'simplecov' SimpleCov.start require 'codecov' SimpleCov.formatter = SimpleCov::Formatter::Codecov require 'minitest/autorun' require 'mocha/setup' require 'vcr' require 'tmpdir' require 'stringio' require 'yard' VCR.configure do |c| c.cassette_library_dir = 'test/fixtures/vcr_cassettes' c.hook_into :webmock end require 'nanoc' require 'nanoc/cli' Nanoc::CLI.setup module Nanoc::TestHelpers LIB_DIR = File.expand_path(File.dirname(__FILE__) + '/../lib') def disable_nokogiri? ENV.key?('DISABLE_NOKOGIRI') end def if_have(*libs) libs.each do |lib| if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby' && lib == 'nokogiri' && disable_nokogiri? skip 'Pure Java Nokogiri has issues that cause problems with nanoc (see https://github.com/nanoc/nanoc/pull/422) -- run without DISABLE_NOKOGIRI to enable Nokogiri tests' end begin require lib rescue LoadError skip "requiring #{lib} failed" end end yield end def if_implemented yield rescue NotImplementedError, NameError skip $ERROR_INFO return end def with_site(params = {}) # Build site name site_name = params[:name] if site_name.nil? @site_num ||= 0 site_name = "site-#{@site_num}" @site_num += 1 end # Build rules rules_content = <<~EOS compile '*' do {{compilation_rule_content}} end route '*' do if item.binary? item.identifier.chop + (item[:extension] ? '.' + item[:extension] : '') else item.identifier + 'index.html' end end layout '*', :erb EOS rules_content = rules_content.gsub( '{{compilation_rule_content}}', params[:compilation_rule_content] || '', ) # Create site unless File.directory?(site_name) FileUtils.mkdir_p(site_name) FileUtils.cd(site_name) do FileUtils.mkdir_p('content') FileUtils.mkdir_p('layouts') FileUtils.mkdir_p('lib') FileUtils.mkdir_p('output') if params[:has_layout] File.open('layouts/default.html', 'w') do |io| io.write('... <%= @yield %> ...') end end File.open('nanoc.yaml', 'w') do |io| io << 'string_pattern_type: legacy' << "\n" if params.fetch(:legacy, true) io << 'data_sources:' << "\n" io << ' -' << "\n" io << ' type: filesystem' << "\n" io << ' identifier_type: legacy' << "\n" if params.fetch(:legacy, true) end File.open('Rules', 'w') { |io| io.write(rules_content) } end end # Yield site FileUtils.cd(site_name) do yield Nanoc::Int::SiteLoader.new.new_from_cwd end end def setup # Check skipped if ENV['skip'] if ENV['skip'].split(',').include?(self.class.to_s) skip 'manually skipped' end end # Clean up GC.start # Go quiet unless ENV['QUIET'] == 'false' @orig_stdout = $stdout @orig_stderr = $stderr $stdout = StringIO.new $stderr = StringIO.new end # Enter tmp @tmp_dir = Dir.mktmpdir('nanoc-test') @orig_wd = FileUtils.pwd FileUtils.cd(@tmp_dir) # Let us get to the raw errors Nanoc::CLI::ErrorHandler.disable end def teardown # Restore normal error handling Nanoc::CLI::ErrorHandler.enable # Exit tmp FileUtils.cd(@orig_wd) FileUtils.rm_rf(@tmp_dir) # Go unquiet unless ENV['QUIET'] == 'false' $stdout = @orig_stdout $stderr = @orig_stderr end end def capturing_stdio(&_block) # Store orig_stdout = $stdout orig_stderr = $stderr # Run $stdout = StringIO.new $stderr = StringIO.new yield { stdout: $stdout.string, stderr: $stderr.string } ensure # Restore $stdout = orig_stdout $stderr = orig_stderr end # Adapted from http://github.com/lsegal/yard-examples/tree/master/doctest def assert_examples_correct(object) P(object).tags(:example).each do |example| # Classify lines = example.text.lines.map do |line| [line =~ /^\s*# ?=>/ ? :result : :code, line] end # Join pieces = [] lines.each do |line| if !pieces.empty? && pieces.last.first == line.first pieces.last.last << line.last else pieces << line end end lines = pieces.map(&:last) # Test b = binding lines.each_slice(2) do |pair| actual_out = eval(pair.first, b) expected_out = eval(pair.last.match(/# ?=>(.*)/)[1], b) assert_equal( expected_out, actual_out, "Incorrect example:\n#{pair.first}", ) end end end def assert_contains_exactly(expected, actual) assert_equal( expected.size, actual.size, format('Expected %s to be of same size as %s', actual.inspect, expected.inspect), ) remaining = actual.dup.to_a expected.each do |e| index = remaining.index(e) remaining.delete_at(index) if index end assert( remaining.empty?, format('Expected %s to contain all the elements of %s', actual.inspect, expected.inspect), ) end def assert_raises_frozen_error error = assert_raises(RuntimeError, TypeError) { yield } assert_match(/(^can't modify frozen |^unable to modify frozen object$)/, error.message) end def with_env_vars(hash, &_block) orig_env_hash = ENV.to_hash hash.each_pair { |k, v| ENV[k] = v } yield ensure orig_env_hash.each_pair { |k, v| ENV[k] = v } end def on_windows? Nanoc.on_windows? end def command?(cmd) which, null = on_windows? ? %w[where NUL] : ['which', '/dev/null'] system("#{which} #{cmd} > #{null} 2>&1") end def symlinks_supported? File.symlink nil, nil rescue NotImplementedError return false rescue return true end def skip_unless_have_command(cmd) skip "Could not find external command \"#{cmd}\"" unless command?(cmd) end def skip_unless_symlinks_supported skip 'Symlinks are not supported by Ruby on Windows' unless symlinks_supported? end def root_dir File.absolute_path(File.dirname(__FILE__) + '/..') end end class Nanoc::TestCase < Minitest::Test include Nanoc::TestHelpers end # Unexpected system exit is unexpected ::Minitest::Test::PASSTHROUGH_EXCEPTIONS.delete(SystemExit) # A more precise inspect method for Time improves assert failure messages. # class Time def inspect strftime("%a %b %d %H:%M:%S.#{format('%06d', usec)} %Z %Y") end end nanoc-4.8.0/test/filters/0000755000004100000410000000000013134141051015271 5ustar www-datawww-datananoc-4.8.0/test/filters/test_slim.rb0000644000004100000410000000323113134141051017620 0ustar www-datawww-data# frozen_string_literal: true require 'helper' class Nanoc::Filters::SlimTest < Nanoc::TestCase def test_filter if_have 'slim' do # Create filter filter = ::Nanoc::Filters::Slim.new(rabbit: 'The rabbit is on the branch.') # Run filter (no assigns) result = filter.setup_and_run('html') assert_match(/.*<\/html>/, result) # Run filter (assigns without @) result = filter.setup_and_run('p = rabbit') assert_equal('

The rabbit is on the branch.

', result) # Run filter (assigns with @) result = filter.setup_and_run('p = @rabbit') assert_equal('

The rabbit is on the branch.

', result) end end def test_filter_with_yield if_have 'slim' do filter = ::Nanoc::Filters::Slim.new(content: 'The rabbit is on the branch.') result = filter.setup_and_run('p = yield') assert_equal('

The rabbit is on the branch.

', result) end end def new_view_context Nanoc::ViewContext.new( reps: :__irrelevat_reps, items: :__irrelevat_items, dependency_tracker: :__irrelevant_dependency_tracker, compilation_context: :__irrelevat_compiler, snapshot_repo: :__irrelevant_snapshot_repo, ) end def test_filter_slim_reports_filename if_have 'slim' do layout = Nanoc::Int::Layout.new('', {}, '/layout.slim') layout = Nanoc::LayoutView.new(layout, new_view_context) assigns = { layout: layout } filter = ::Nanoc::Filters::Slim.new(assigns) error = assert_raises(NameError) { filter.setup_and_run('deliberate=failure') } assert_match(%r{^layout /layout.slim}, error.backtrace[1]) end end end nanoc-4.8.0/test/filters/test_redcloth.rb0000644000004100000410000000151713134141051020465 0ustar www-datawww-data# frozen_string_literal: true require 'helper' class Nanoc::Filters::RedClothTest < Nanoc::TestCase def test_filter if_have 'redcloth' do # Get filter filter = ::Nanoc::Filters::RedCloth.new # Run filter result = filter.setup_and_run('h1. Foo') assert_equal('

Foo

', result) end end def test_filter_with_options if_have 'redcloth' do # Get filter filter = ::Nanoc::Filters::RedCloth.new # Run filter without options result = filter.setup_and_run('I am a member of SPECTRE.') assert_equal('

I am a member of SPECTRE.

', result) # Run filter with options result = filter.setup_and_run('I am a member of SPECTRE.', no_span_caps: true) assert_equal('

I am a member of SPECTRE.

', result) end end end nanoc-4.8.0/test/filters/test_uglify_js.rb0000644000004100000410000000156613134141051020660 0ustar www-datawww-data# frozen_string_literal: true require 'helper' class Nanoc::Filters::UglifyJSTest < Nanoc::TestCase def test_filter if_have 'uglifier' do # Create filter filter = ::Nanoc::Filters::UglifyJS.new # Run filter input = 'foo = 1; (function(bar) { if (true) alert(bar); })(foo)' result = filter.setup_and_run(input) assert_match(/foo=1,function\((.)\)\{alert\(\1\)\}\(foo\);/, result) end end def test_filter_with_options if_have 'uglifier' do filter = ::Nanoc::Filters::UglifyJS.new input = "if(donkey) alert('It is a donkey!');" result = filter.setup_and_run(input, output: { beautify: false }) assert_equal 'donkey&&alert("It is a donkey!");', result result = filter.setup_and_run(input, output: { beautify: true }) assert_equal 'donkey && alert("It is a donkey!");', result end end end nanoc-4.8.0/test/filters/test_yui_compressor.rb0000644000004100000410000000214613134141051021742 0ustar www-datawww-data# frozen_string_literal: true require 'helper' class Nanoc::Filters::YUICompressorTest < Nanoc::TestCase def test_filter_javascript if_have 'yuicompressor' do filter = ::Nanoc::Filters::YUICompressor.new sample_js = <<-JAVASCRIPT function factorial(n) { var result = 1; for (var i = 2; i <= n; i++) { result *= i } return result; } JAVASCRIPT result = filter.setup_and_run(sample_js, type: 'js', munge: true) assert_match 'function factorial(c){var a=1;for(var b=2;b<=c;b++){a*=b}return a};', result result = filter.setup_and_run(sample_js, type: 'js', munge: false) assert_match 'function factorial(n){var result=1;for(var i=2;i<=n;i++){result*=i}return result};', result end end def test_filter_css if_have 'yuicompressor' do filter = ::Nanoc::Filters::YUICompressor.new sample_css = <<-CSS * { margin: 0; } CSS result = filter.setup_and_run(sample_css, type: 'css') assert_match '*{margin:0}', result end end end nanoc-4.8.0/test/filters/test_typogruby.rb0000644000004100000410000000110413134141051020715 0ustar www-datawww-data# frozen_string_literal: true require 'helper' class Nanoc::Filters::TypogrubyTest < Nanoc::TestCase def test_filter if_have 'typogruby' do # Get filter filter = ::Nanoc::Filters::Typogruby.new # Run filter a = '"Typogruby makes HTML look smarter & better, don\'t you think?"' b = 'Typogruby makes HTML look smarter & better, don’t you think?”' result = filter.setup_and_run(a) assert_equal(b, result) end end end nanoc-4.8.0/test/filters/test_maruku.rb0000644000004100000410000000060013134141051020155 0ustar www-datawww-data# frozen_string_literal: true require 'helper' class Nanoc::Filters::MarukuTest < Nanoc::TestCase def test_filter if_have 'maruku' do # Create filter filter = ::Nanoc::Filters::Maruku.new # Run filter result = filter.setup_and_run('This is _so_ *cool*!') assert_equal('

This is so cool!

', result.strip) end end end nanoc-4.8.0/test/filters/test_coffeescript.rb0000644000004100000410000000063613134141051021336 0ustar www-datawww-data# frozen_string_literal: true require 'helper' class Nanoc::Filters::CoffeeScriptTest < Nanoc::TestCase def test_filter if_have 'coffee-script' do # Create filter filter = ::Nanoc::Filters::CoffeeScript.new # Run filter (no assigns) result = filter.setup_and_run('alert 42') assert_equal('(function() { alert(42); }).call(this); ', result.gsub(/\s+/, ' ')) end end end nanoc-4.8.0/test/filters/test_pandoc.rb0000644000004100000410000000225713134141051020127 0ustar www-datawww-data# frozen_string_literal: true require 'helper' class Nanoc::Filters::PandocTest < Nanoc::TestCase def test_filter if_have 'pandoc-ruby' do skip_unless_have_command 'pandoc' # Create filter filter = ::Nanoc::Filters::Pandoc.new # Run filter result = filter.setup_and_run("# Heading\n") assert_match(%r{

Heading

\s*}, result) end end def test_params_old if_have 'pandoc-ruby' do skip_unless_have_command 'pandoc' # Create filter filter = ::Nanoc::Filters::Pandoc.new # Run filter args = { f: :markdown, to: :html } result = filter.setup_and_run("# Heading\n", args) assert_match(%r{

Heading

\s*}, result) end end def test_params_new if_have 'pandoc-ruby' do skip_unless_have_command 'pandoc' # Create filter filter = ::Nanoc::Filters::Pandoc.new # Run filter args = [:s, { f: :markdown, to: :html }, 'no-wrap', :toc] result = filter.setup_and_run("# Heading\n", args: args) assert_match '
', result assert_match(%r{

Heading

\s*}, result) end end end nanoc-4.8.0/test/filters/test_rainpress.rb0000644000004100000410000000121713134141051020664 0ustar www-datawww-data# frozen_string_literal: true require 'helper' class Nanoc::Filters::RainpressTest < Nanoc::TestCase def test_filter if_have 'rainpress' do # Create filter filter = ::Nanoc::Filters::Rainpress.new # Run filter result = filter.setup_and_run('body { color: black; }') assert_equal('body{color:#000}', result) end end def test_filter_with_options if_have 'rainpress' do # Create filter filter = ::Nanoc::Filters::Rainpress.new # Run filter result = filter.setup_and_run('body { color: #aabbcc; }', colors: false) assert_equal('body{color:#aabbcc}', result) end end end nanoc-4.8.0/test/filters/test_markaby.rb0000644000004100000410000000053113134141051020302 0ustar www-datawww-data# frozen_string_literal: true require 'helper' class Nanoc::Filters::MarkabyTest < Nanoc::TestCase def test_filter if_have 'markaby' do # Create filter filter = ::Nanoc::Filters::Markaby.new # Run filter result = filter.setup_and_run("html do\nend") assert_equal('', result) end end end nanoc-4.8.0/test/filters/test_rdiscount.rb0000644000004100000410000000166113134141051020673 0ustar www-datawww-data# frozen_string_literal: true require 'helper' class Nanoc::Filters::RDiscountTest < Nanoc::TestCase def test_filter if_have 'rdiscount' do # Create filter filter = ::Nanoc::Filters::RDiscount.new # Run filter result = filter.setup_and_run('> Quote') assert_match(/
\s*

Quote<\/p>\s*<\/blockquote>/, result) end end # FIXME: Re-enable this test (flaky; quotation marks are not transformed consistently) # def test_with_extensions # if_have 'rdiscount' do # # Create filter # filter = ::Nanoc::Filters::RDiscount.new # # # Run filter # input = "The quotation 'marks' sure make this look sarcastic!" # output_expected = /The quotation ‘marks’ sure make this look sarcastic!/ # output_actual = filter.setup_and_run(input, extensions: [:smart]) # assert_match(output_expected, output_actual) # end # end end nanoc-4.8.0/test/filters/test_rdoc.rb0000644000004100000410000000051513134141051017605 0ustar www-datawww-data# frozen_string_literal: true require 'helper' class Nanoc::Filters::RDocTest < Nanoc::TestCase def test_filter # Get filter filter = ::Nanoc::Filters::RDoc.new # Run filter result = filter.setup_and_run('= Foo') assert_match(%r{\A\s*Foo(.*)?\s*\Z}, result) end end nanoc-4.8.0/test/filters/test_sass.rb0000644000004100000410000002121013134141051017622 0ustar www-datawww-data# frozen_string_literal: true require 'helper' class Nanoc::Filters::SassTest < Nanoc::TestCase def setup super if_have 'sass' do unless ::Sass.load_paths.include?('.') ::Sass.load_paths << '.' end end end def test_filter if_have 'sass' do # Get filter filter = create_filter(foo: 'bar') # Run filter result = filter.setup_and_run(".foo #bar\n color: #f00") assert_match(/.foo\s+#bar\s*\{\s*color:\s+(red|#f00);?\s*\}/, result) end end def test_filter_with_params if_have 'sass' do # Create filter filter = create_filter(foo: 'bar') # Check with compact result = filter.setup_and_run(".foo #bar\n color: #f00", style: 'compact') assert_match(/^\.foo #bar[\s]*\{[\s]*color:\s*(red|#f00);?[\s]*\}/m, result) # Check with compressed result = filter.setup_and_run(".foo #bar\n color: #f00", style: 'compressed') assert_match(/^\.foo #bar[\s]*\{[\s]*color:\s*(red|#f00);?[\s]*\}/m, result) end end def test_filter_error if_have 'sass' do # Create filter filter = create_filter # Run filter raised = false begin filter.setup_and_run('$*#&!@($') rescue Sass::SyntaxError => e assert_match ':1', e.backtrace[0] raised = true end assert raised end end def test_filter_can_import_external_files if_have 'sass' do # Create filter filter = create_filter # Create sample file File.open('moo.sass', 'w') { |io| io.write "body\n color: red" } # Run filter filter.setup_and_run('@import moo') end end def test_filter_can_import_relative_files if_have 'sass' do # Create filter filter = create_filter # Create sample file File.open('moo.sass', 'w') { |io| io.write %(@import subdir/relative) } FileUtils.mkdir_p('subdir') File.open('subdir/relative.sass', 'w') { |io| io.write "body\n color: red" } # Run filter filter.setup_and_run('@import moo') end end def test_filter_will_skip_items_without_filename if_have 'sass' do # Create filter filter = create_filter # Create sample file File.open('moo.sass', 'w') { |io| io.write "body\n color: red" } # Run filter filter.setup_and_run('@import moo') end end def test_css_imports_work if_have 'sass' do # Create filter filter = create_filter # Run filter filter.setup_and_run('@import moo.css') end end def test_recompile_includes if_have 'sass' do with_site do |site| # Create two Sass files Dir['content/*'].each { |i| FileUtils.rm(i) } File.open('content/a.sass', 'w') do |io| io.write('@import b.sass') end File.open('content/b.sass', 'w') do |io| io.write("p\n color: red") end # Update rules File.open('Rules', 'w') do |io| io.write "compile '*' do\n" io.write " filter :sass\n" io.write "end\n" io.write "\n" io.write "route '/a/' do\n" io.write " item.identifier.chop + '.css'\n" io.write "end\n" io.write "\n" io.write "route '/b/' do\n" io.write " nil\n" io.write "end\n" end # Compile site = Nanoc::Int::SiteLoader.new.new_from_cwd site.compile # Check assert Dir['output/*'].size == 1 assert File.file?('output/a.css') refute File.file?('output/b.css') assert_match(/^p\s*\{\s*color:\s*red;?\s*\}/, File.read('output/a.css')) # Update included file File.open('content/b.sass', 'w') do |io| io.write("p\n color: blue") end # Recompile site = Nanoc::Int::SiteLoader.new.new_from_cwd site.compile # Recheck assert Dir['output/*'].size == 1 assert File.file?('output/a.css') refute File.file?('output/b.css') assert_match(/^p\s*\{\s*color:\s*blue;?\s*\}/, File.read('output/a.css')) end end end def test_recompile_includes_with_underscore_without_extension if_have 'sass' do with_site do |site| # Create two Sass files Dir['content/*'].each { |i| FileUtils.rm(i) } File.open('content/a.sass', 'w') do |io| io.write('@import b') end File.open('content/_b.sass', 'w') do |io| io.write("p\n color: red") end # Update rules File.open('Rules', 'w') do |io| io.write "compile '*' do\n" io.write " filter :sass\n" io.write "end\n" io.write "\n" io.write "route '/a/' do\n" io.write " item.identifier.chop + '.css'\n" io.write "end\n" io.write "\n" io.write "route '/_b/' do\n" io.write " nil\n" io.write "end\n" end # Compile site = Nanoc::Int::SiteLoader.new.new_from_cwd site.compile # Check assert Dir['output/*'].size == 1 assert File.file?('output/a.css') refute File.file?('output/b.css') assert_match(/^p\s*\{\s*color:\s*red;?\s*\}/, File.read('output/a.css')) # Update included file File.open('content/_b.sass', 'w') do |io| io.write("p\n color: blue") end # Recompile site = Nanoc::Int::SiteLoader.new.new_from_cwd site.compile # Recheck assert Dir['output/*'].size == 1 assert File.file?('output/a.css') refute File.file?('output/b.css') assert_match(/^p\s*\{\s*color:\s*blue;?\s*\}/, File.read('output/a.css')) end end end def test_recompile_includes_with_relative_path if_have 'sass', 'compass' do with_site do |site| # Write compass config FileUtils.mkdir_p('compass') File.open('compass/config.rb', 'w') do |io| io << "project_path = \".\"\n" io << "sass_path = \"content/style\"\n" end # Create two Sass files Dir['content/*'].each { |i| FileUtils.rm(i) } FileUtils.mkdir_p('content/style/super') FileUtils.mkdir_p('content/style/sub') File.open('content/style/super/main.sass', 'w') do |io| io.write('@import sub/include.sass') end File.open('content/style/sub/include.sass', 'w') do |io| io.write("p\n color: red") end # Update rules File.open('Rules', 'w') do |io| io.write "require 'compass'\n" io.write "Compass.add_project_configuration 'compass/config.rb'\n" io.write "\n" io.write "compile '*' do\n" io.write " filter :sass, Compass.sass_engine_options\n" io.write "end\n" io.write "\n" io.write "route '/style/super/main/' do\n" io.write " item.identifier.chop + '.css'\n" io.write "end\n" io.write "\n" io.write "route '/style/sub/include/' do\n" io.write " nil\n" io.write "end\n" end # Compile site = Nanoc::Int::SiteLoader.new.new_from_cwd site.compile # Check output_files = Dir['output/**/*'].select { |f| File.file?(f) } assert_equal ['output/style/super/main.css'], output_files assert_match(/^p\s*\{\s*color:\s*red;?\s*\}/, File.read('output/style/super/main.css')) # Update included file File.open('content/style/sub/include.sass', 'w') do |io| io.write("p\n color: blue") end # Recompile site = Nanoc::Int::SiteLoader.new.new_from_cwd site.compile # Recheck output_files = Dir['output/**/*'].select { |f| File.file?(f) } assert_equal ['output/style/super/main.css'], output_files assert_match(/^p\s*\{\s*color:\s*blue;?\s*\}/, File.read('output/style/super/main.css')) end end end def test_sass_without_filter if_have 'sass' do File.open('_morestuff.sass', 'w') do |io| io.write("p\n color: blue") end options = { filename: File.join(Dir.getwd, 'test.sass') } ::Sass::Engine.new('@import "morestuff"', options).render end end private def create_filter(params = {}) FileUtils.mkdir_p('content') File.open('content/xyzzy.sass', 'w') { |io| io.write('p\n color: green') } items = [ Nanoc::ItemWithRepsView.new( Nanoc::Int::Item.new( 'blah', { content_filename: 'content/xyzzy.sass' }, '/blah/', ), nil, ), ] params = { item: items[0], items: items }.merge(params) ::Nanoc::Filters::Sass.new(params) end end nanoc-4.8.0/test/filters/test_erubi.rb0000644000004100000410000000371713134141051017773 0ustar www-datawww-data# frozen_string_literal: true require 'helper' class Nanoc::Filters::ErubiTest < Nanoc::TestCase def test_filter_with_instance_variable if_have 'erubi' do # Create filter filter = ::Nanoc::Filters::Erubi.new(location: 'a cheap motel') # Run filter result = filter.setup_and_run('<%= "I was hiding in #{@location}." %>') assert_equal('I was hiding in a cheap motel.', result) end end def test_filter_with_instance_method if_have 'erubi' do # Create filter filter = ::Nanoc::Filters::Erubi.new(location: 'a cheap motel') # Run filter result = filter.setup_and_run('<%= "I was hiding in #{location}." %>') assert_equal('I was hiding in a cheap motel.', result) end end def test_filter_error if_have 'erubi' do # Create filter filter = ::Nanoc::Filters::Erubi.new # Run filter raised = false begin filter.setup_and_run('<%= this isn\'t really ruby so it\'ll break, muahaha %>') rescue SyntaxError => e assert_match 'syntax error', e.message raised = true end assert raised end end def test_filter_with_yield if_have 'erubi' do # Create filter filter = ::Nanoc::Filters::Erubi.new(content: 'a cheap motel') # Run filter result = filter.setup_and_run('<%= "I was hiding in #{yield}." %>') assert_equal('I was hiding in a cheap motel.', result) end end def test_filter_with_yield_without_content if_have 'erubi' do # Create filter filter = ::Nanoc::Filters::Erubi.new(location: 'a cheap motel') # Run filter assert_raises LocalJumpError do filter.setup_and_run('<%= "I was hiding in #{yield}." %>') end end end def test_filter_with_erbout if_have 'erubi' do filter = ::Nanoc::Filters::Erubi.new result = filter.setup_and_run('stuff<% _erbout << _erbout %>') assert_equal 'stuffstuff', result end end end nanoc-4.8.0/test/filters/test_asciidoc.rb0000644000004100000410000000055713134141051020442 0ustar www-datawww-data# frozen_string_literal: true require 'helper' class Nanoc::Filters::AsciiDocTest < Nanoc::TestCase def test_filter skip_unless_have_command 'asciidoc' # Create filter filter = ::Nanoc::Filters::AsciiDoc.new # Run filter result = filter.setup_and_run('== Blah blah') assert_match %r{

Blah blah

}, result end end nanoc-4.8.0/test/filters/test_mustache.rb0000644000004100000410000000211513134141051020465 0ustar www-datawww-data# frozen_string_literal: true require 'helper' class Nanoc::Filters::MustacheTest < Nanoc::TestCase def test_filter if_have 'mustache' do # Create item item = Nanoc::Int::Item.new( 'content', { title: 'Max Payne', protagonist: 'Max Payne' }, '/games/max-payne/', ) # Create filter filter = ::Nanoc::Filters::Mustache.new(item: item) # Run filter result = filter.setup_and_run('The protagonist of {{title}} is {{protagonist}}.') assert_equal('The protagonist of Max Payne is Max Payne.', result) end end def test_filter_with_yield if_have 'mustache' do # Create item item = Nanoc::Int::Item.new( 'content', { title: 'Max Payne', protagonist: 'Max Payne' }, '/games/max-payne/', ) # Create filter filter = ::Nanoc::Filters::Mustache.new( content: 'No Payne No Gayne', item: item, ) # Run filter result = filter.setup_and_run('Max says: {{yield}}.') assert_equal('Max says: No Payne No Gayne.', result) end end end nanoc-4.8.0/test/filters/colorize_syntax/0000755000004100000410000000000013134141051020525 5ustar www-datawww-datananoc-4.8.0/test/filters/colorize_syntax/test_pygments.rb0000644000004100000410000000134013134141051023755 0ustar www-datawww-data# frozen_string_literal: true require 'helper' class Nanoc::Filters::ColorizeSyntax::PygmentsTest < Nanoc::TestCase def test_pygmentsrb skip 'pygments.rb does not support Windows' if on_windows? if_have 'pygments', 'nokogiri' do # Create filter filter = ::Nanoc::Filters::ColorizeSyntax.new # Get input and expected output input = '
# comment…
' expected_output = '
# comment…
' # Run filter actual_output = filter.setup_and_run(input, colorizers: { ruby: :pygmentsrb }) assert_equal(expected_output, actual_output) end end end nanoc-4.8.0/test/filters/colorize_syntax/test_pygmentize.rb0000644000004100000410000000243213134141051024305 0ustar www-datawww-data# frozen_string_literal: true require 'helper' class Nanoc::Filters::ColorizeSyntax::PygmentizeTest < Nanoc::TestCase def test_pygmentize if_have 'nokogiri' do skip_unless_have_command 'pygmentize' # Create filter filter = ::Nanoc::Filters::ColorizeSyntax.new # Get input and expected output input = '
# comment
' expected_output = '
# comment
' # Run filter actual_output = filter.setup_and_run(input, colorizers: { ruby: :pygmentize }) assert_equal(expected_output, actual_output) end end def test_colorize_syntax_with_default_colorizer skip_unless_have_command 'pygmentize' if_have 'nokogiri' do # Create filter filter = ::Nanoc::Filters::ColorizeSyntax.new # Get input and expected output input = '
puts "foo"
' expected_output = '
puts "foo"
' # Run filter actual_output = filter.setup_and_run(input, default_colorizer: :pygmentize) assert_equal(expected_output, actual_output) end end end nanoc-4.8.0/test/filters/colorize_syntax/test_common.rb0000644000004100000410000000652213134141051023406 0ustar www-datawww-data# frozen_string_literal: true require 'helper' class Nanoc::Filters::ColorizeSyntax::CommonTest < Nanoc::TestCase def test_dummy if_have 'nokogiri' do # Create filter filter = ::Nanoc::Filters::ColorizeSyntax.new # Get input and expected output input = '
# comment
' expected_output = input # because we are using a dummy # Run filter actual_output = filter.setup_and_run(input, default_colorizer: :dummy) assert_equal(expected_output, actual_output) end end def test_with_frozen_input if_have 'nokogiri' do input = '
# comment
' input.freeze filter = ::Nanoc::Filters::ColorizeSyntax.new filter.setup_and_run(input, default_colorizer: :dummy) end end def test_full_page if_have 'nokogiri' do # Create filter filter = ::Nanoc::Filters::ColorizeSyntax.new # Get input and expected output input = <<~EOS Foo
# comment
EOS expected_output_regex = %r{^\s*\s*\s*\s*Foo\s*\s*\s*
# comment
\s*\s*} # Run filter actual_output = filter.setup_and_run(input, default_colorizer: :dummy, is_fullpage: true) assert_match expected_output_regex, actual_output end end def test_full_page_html5 # Create filter filter = ::Nanoc::Filters::ColorizeSyntax.new # Get input and expected output input = <<~EOS Foo
# comment
EOS expected_output_regex = %r{^\s*\s*\s*\s*Foo\s*\s*\s*
# comment
\s*\s*} # Run filter actual_output = filter.setup_and_run(input, syntax: :html5, default_colorizer: :dummy, is_fullpage: true) assert_match expected_output_regex, actual_output end def test_colorize_syntax_with_missing_executables if_have 'nokogiri' do begin original_path = ENV['PATH'] ENV['PATH'] = './blooblooblah' # Create filter filter = ::Nanoc::Filters::ColorizeSyntax.new # Get input and expected output input = '
puts "foo"
' # Run filter %i[albino pygmentize simon_highlight].each do |colorizer| begin input = '
puts "foo"
' filter.setup_and_run( input, colorizers: { ruby: colorizer }, ) flunk 'expected colorizer to raise if no executable is available' rescue end end ensure ENV['PATH'] = original_path end end end end nanoc-4.8.0/test/filters/colorize_syntax/test_simon.rb0000644000004100000410000000130613134141051023236 0ustar www-datawww-data# frozen_string_literal: true require 'helper' class Nanoc::Filters::ColorizeSyntax::SimonTest < Nanoc::TestCase def test_simon_highlight if_have 'nokogiri' do skip_unless_have_command 'highlight' # Create filter filter = ::Nanoc::Filters::ColorizeSyntax.new # Get input and expected output input = %(

# comment
) expected_output = '
# comment
' # Run filter actual_output = filter.setup_and_run(input, default_colorizer: :simon_highlight) assert_equal(expected_output, actual_output) end end end nanoc-4.8.0/test/filters/colorize_syntax/test_coderay.rb0000644000004100000410000001655513134141051023553 0ustar www-datawww-data# frozen_string_literal: true require 'helper' class Nanoc::Filters::ColorizeSyntax::CoderayTest < Nanoc::TestCase CODERAY_PRE = '
' CODERAY_POST = '
' def test_coderay_simple if_have 'coderay', 'nokogiri' do # Create filter filter = ::Nanoc::Filters::ColorizeSyntax.new # Get input and expected output input = '
# comment
' expected_output = CODERAY_PRE + '
# comment
' + CODERAY_POST # Run filter actual_output = filter.setup_and_run(input) assert_equal(expected_output, actual_output) end end def test_coderay_with_comment if_have 'coderay', 'nokogiri' do # Create filter filter = ::Nanoc::Filters::ColorizeSyntax.new # Get input and expected output input = %(
#!ruby
# comment
) expected_output = CODERAY_PRE + '
# comment
' + CODERAY_POST # Run filter actual_output = filter.setup_and_run(input) assert_equal(expected_output, actual_output) end end def test_coderay_with_comment_in_middle if_have 'coderay', 'nokogiri' do # Create filter filter = ::Nanoc::Filters::ColorizeSyntax.new # Get input and expected output input = %(
def moo ; end
#!ruby
# comment
) expected_output = "
def moo ; end\n#!ruby\n# comment
" # Run filter actual_output = filter.setup_and_run(input) assert_equal(expected_output, actual_output) end end def test_coderay_with_comment_and_class if_have 'coderay', 'nokogiri' do # Create filter filter = ::Nanoc::Filters::ColorizeSyntax.new # Get input and expected output input = %(
#!ruby
# comment
) expected_output = CODERAY_PRE + %(
#!ruby
# comment
) + CODERAY_POST # Run filter actual_output = filter.setup_and_run(input) assert_equal(expected_output, actual_output) end end def test_coderay_with_more_classes if_have 'coderay', 'nokogiri' do # Create filter filter = ::Nanoc::Filters::ColorizeSyntax.new # Get input and expected output input = '
# comment
' expected_output = CODERAY_PRE + '
# comment
' + CODERAY_POST # Run filter actual_output = filter.setup_and_run(input) assert_equal(expected_output, actual_output) end end def test_colorize_syntax_with_unknown_syntax if_have 'coderay', 'nokogiri' do # Create filter filter = ::Nanoc::Filters::ColorizeSyntax.new # Run filter assert_raises RuntimeError do filter.setup_and_run('

whatever

', syntax: :kasflwafhaweoineurl) end end end def test_colorize_syntax_with_xml if_have 'coderay', 'nokogiri' do # Create filter filter = ::Nanoc::Filters::ColorizeSyntax.new # Get input and expected output input = '

foo
bar

' expected_output = '

foo
bar

' # Run filter actual_output = filter.setup_and_run(input, syntax: :xml) assert_equal(expected_output, actual_output) end end def test_colorize_syntax_with_xhtml if_have 'coderay', 'nokogiri' do # Create filter filter = ::Nanoc::Filters::ColorizeSyntax.new # Get input and expected output input = '

foo
bar

' expected_output = '

foo
bar

' # Run filter actual_output = filter.setup_and_run(input, syntax: :xhtml) assert_equal(expected_output, actual_output) end end def test_colorize_syntax_with_non_language_shebang_line if_have 'coderay', 'nokogiri' do # Create filter filter = ::Nanoc::Filters::ColorizeSyntax.new # Get input and expected output input = <<~EOS before

        #!/usr/bin/env ruby
        puts 'hi!'
        
after EOS expected_output = <<~EOS.sub(/\s*\Z/m, '') before

        #!/usr/bin/env ruby
        puts 'hi!'
        
after EOS # Run filter actual_output = filter.setup_and_run(input).sub(/\s*\Z/m, '') assert_equal(expected_output, actual_output) end end def test_colorize_syntax_with_non_language_shebang_line_and_language_line if_have 'coderay', 'nokogiri' do # Create filter filter = ::Nanoc::Filters::ColorizeSyntax.new # Get input and expected output input = <<~EOS before

        #!ruby
        #!/usr/bin/env ruby
        puts 'hi!'
        
after EOS expected_output = <<~EOS.sub(/\s*\Z/m, '') before #{CODERAY_PRE}
#!/usr/bin/env ruby
        puts 'hi!'
#{CODERAY_POST} after EOS # Run filter actual_output = filter.setup_and_run(input).sub(/\s*\Z/m, '') assert_equal(expected_output, actual_output) end end def test_not_outside_pre if_have 'coderay', 'nokogiri' do # Create filter filter = ::Nanoc::Filters::ColorizeSyntax.new # Get input and expected output input = '# comment' expected_output = '# comment' # Run filter actual_output = filter.setup_and_run(input, outside_pre: false) assert_equal(expected_output, actual_output) end end def test_outside_pre if_have 'coderay', 'nokogiri' do # Create filter filter = ::Nanoc::Filters::ColorizeSyntax.new # Get input and expected output input = '# comment' expected_output = '# comment' # Run filter actual_output = filter.setup_and_run(input, outside_pre: true) assert_equal(expected_output, actual_output) end end def test_strip if_have 'coderay', 'nokogiri' do # Create filter filter = ::Nanoc::Filters::ColorizeSyntax.new # Simple test assert_equal ' bar', filter.send(:strip, "\n bar") # Get input and expected output input = <<~EOS before

          def foo
          end
        
after EOS expected_output = <<~EOS.sub(/\s*\Z/m, '') before #{CODERAY_PRE}
  def foo
          end
#{CODERAY_POST} after EOS # Run filter actual_output = filter.setup_and_run(input).sub(/\s*\Z/m, '') assert_equal(expected_output, actual_output) end end end nanoc-4.8.0/test/filters/test_redcarpet.rb0000644000004100000410000000641013134141051020627 0ustar www-datawww-data# frozen_string_literal: true require 'helper' class Nanoc::Filters::RedcarpetTest < Nanoc::TestCase def test_find if_have 'redcarpet' do refute Nanoc::Filter.named(:redcarpet).nil? end end def test_filter if_have 'redcarpet' do # Create filter filter = ::Nanoc::Filters::Redcarpet.new # Run filter result = filter.setup_and_run('> Quote') assert_match(/
\s*

Quote<\/p>\s*<\/blockquote>/, result) end end def test_with_extensions if_have 'redcarpet' do # Create filter filter = ::Nanoc::Filters::Redcarpet.new # Run filter if ::Redcarpet::VERSION > '2' input = 'this is ~~good~~ bad' output_expected = /this is good<\/del> bad/ output_actual = filter.setup_and_run(input, options: { strikethrough: true }) else input = "The quotation 'marks' sure make this look sarcastic!" output_expected = /The quotation ‘marks’ sure make this look sarcastic!/ output_actual = filter.setup_and_run(input, options: [:smart]) end assert_match(output_expected, output_actual) end end def test_html_by_default if_have 'redcarpet' do # Create filter filter = ::Nanoc::Filters::Redcarpet.new # Run filter input = "![Alt](/path/to/img 'Title')" output_expected = %r{Alt} output_actual = filter.setup_and_run(input) assert_match(output_expected, output_actual) end end def test_xhtml_if_requested if_have 'redcarpet' do # Create filter filter = ::Nanoc::Filters::Redcarpet.new # Run filter input = "![Alt](/path/to/img 'Title')" output_expected = %r{Alt} output_actual = if ::Redcarpet::VERSION > '2' filter.setup_and_run(input, renderer_options: { xhtml: true }) else filter.setup_and_run(input, options: [:xhtml]) end assert_match(output_expected, output_actual) end end def test_html_toc if_have 'redcarpet' do unless ::Redcarpet::VERSION > '2' skip 'Requires Redcarpet >= 2' end # Create filter filter = ::Nanoc::Filters::Redcarpet.new # Run filter input = "# Heading 1\n## Heading 2\n" output_actual = filter.run(input, renderer: Redcarpet::Render::HTML_TOC) # Test output_expected = %r{

} assert_match(output_expected, output_actual) end end def test_toc_if_requested if_have 'redcarpet' do # Create filter filter = ::Nanoc::Filters::Redcarpet.new # Run filter input = "A Title\n======" if ::Redcarpet::VERSION > '2' output_expected = %r{\n

A Title

\n} output_actual = filter.setup_and_run(input, with_toc: true) else output_expected = %r{

A Title

\n} output_actual = filter.setup_and_run(input) end # Test assert_match(output_expected, output_actual) end end end nanoc-4.8.0/test/filters/test_kramdown.rb0000644000004100000410000000347513134141051020510 0ustar www-datawww-data# frozen_string_literal: true require 'helper' class Nanoc::Filters::KramdownTest < Nanoc::TestCase def test_filter if_have 'kramdown' do # Create filter filter = ::Nanoc::Filters::Kramdown.new # Run filter result = filter.setup_and_run('This is _so_ **cool**!') assert_equal("

This is so cool!

\n", result) end end def test_warnings if_have 'kramdown' do # Create item item = Nanoc::Int::Item.new('foo', {}, '/foo.md') item_view = Nanoc::ItemWithRepsView.new(item, nil) item_rep = Nanoc::Int::ItemRep.new(item, :default) item_rep_view = Nanoc::ItemRepView.new(item_rep, nil) # Create filter filter = ::Nanoc::Filters::Kramdown.new(item: item_view, item_rep: item_rep_view) # Run filter io = capturing_stdio do filter.setup_and_run('{:foo}this is bogus') end assert_empty io[:stdout] assert_equal "kramdown warning(s) for #{item_rep_view.inspect}\n Found span IAL after text - ignoring it\n", io[:stderr] end end def test_warning_filters if_have 'kramdown' do # Create item item = Nanoc::Int::Item.new('foo', {}, '/foo.md') item_view = Nanoc::ItemWithRepsView.new(item, nil) item_rep = Nanoc::Int::ItemRep.new(item, :default) item_rep_view = Nanoc::ItemRepView.new(item_rep, nil) # Create filter filter = ::Nanoc::Filters::Kramdown.new(item: item_view, item_rep: item_rep_view) # Run filter io = capturing_stdio do filter.setup_and_run("{:foo}this is bogus\n[foo]: http://foo.com\n", warning_filters: 'No link definition') end assert_empty io[:stdout] assert_equal "kramdown warning(s) for #{item_rep_view.inspect}\n Found span IAL after text - ignoring it\n", io[:stderr] end end end nanoc-4.8.0/test/filters/test_rubypants.rb0000644000004100000410000000053713134141051020711 0ustar www-datawww-data# frozen_string_literal: true require 'helper' class Nanoc::Filters::RubyPantsTest < Nanoc::TestCase def test_filter if_have 'rubypants' do # Get filter filter = ::Nanoc::Filters::RubyPants.new # Run filter result = filter.setup_and_run('Wait---what?') assert_equal('Wait—what?', result) end end end nanoc-4.8.0/test/filters/test_erb.rb0000644000004100000410000000555313134141051017435 0ustar www-datawww-data# frozen_string_literal: true require 'helper' class Nanoc::Filters::ERBTest < Nanoc::TestCase def test_filter_with_instance_variable # Create filter filter = ::Nanoc::Filters::ERB.new(location: 'a cheap motel') # Run filter result = filter.setup_and_run('<%= "I was hiding in #{@location}." %>') assert_equal('I was hiding in a cheap motel.', result) end def test_filter_with_instance_method # Create filter filter = ::Nanoc::Filters::ERB.new(location: 'a cheap motel') # Run filter result = filter.setup_and_run('<%= "I was hiding in #{location}." %>') assert_equal('I was hiding in a cheap motel.', result) end def test_filter_error_item # Create item and item rep item = MiniTest::Mock.new item.expect(:identifier, '/foo/bar/baz/') item_rep = MiniTest::Mock.new item_rep.expect(:name, :quux) # Create filter filter = ::Nanoc::Filters::ERB.new( item: item, item_rep: item_rep, location: 'a cheap motel', ) # Run filter raised = false begin filter.setup_and_run('<%= this isn\'t really ruby so it\'ll break, muahaha %>') rescue SyntaxError => e e.message =~ /(.+?):\d+: / assert_match 'item /foo/bar/baz/ (rep quux)', Regexp.last_match[1] raised = true end assert raised end def test_filter_with_yield # Create filter filter = ::Nanoc::Filters::ERB.new(content: 'a cheap motel') # Run filter result = filter.setup_and_run('<%= "I was hiding in #{yield}." %>') assert_equal('I was hiding in a cheap motel.', result) end def test_filter_with_yield_without_content # Create filter filter = ::Nanoc::Filters::ERB.new(location: 'a cheap motel') # Run filter assert_raises LocalJumpError do filter.setup_and_run('<%= "I was hiding in #{yield}." %>') end end def test_safe_level if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby' skip 'JRuby does not implement safe levels' end # Set up filter = ::Nanoc::Filters::ERB.new File.open('moo', 'w') { |io| io.write('one miiillion dollars') } # Without res = filter.setup_and_run('<%= File.read("moo") %>', safe_level: nil) assert_equal 'one miiillion dollars', res # With assert_raises(SecurityError) do res = filter.setup_and_run('<%= eval File.read("moo") %>', safe_level: 1) end end def test_trim_mode # Set up filter = ::Nanoc::Filters::ERB.new(location: 'a cheap motel') $trim_mode_works = false # Without filter.setup_and_run('% $trim_mode_works = true') refute $trim_mode_works # With filter.setup_and_run('% $trim_mode_works = true', trim_mode: '%') assert $trim_mode_works end def test_locals filter = ::Nanoc::Filters::ERB.new result = filter.setup_and_run('<%= @local %>', locals: { local: 123 }) assert_equal '123', result end end nanoc-4.8.0/test/filters/test_relativize_paths.rb0000644000004100000410000005327013134141051022241 0ustar www-datawww-data# frozen_string_literal: true require 'helper' class Nanoc::Filters::RelativizePathsTest < Nanoc::TestCase def test_filter_html_with_double_quotes # Create filter with mock item filter = Nanoc::Filters::RelativizePaths.new # Mock item filter.instance_eval do @item_rep = Nanoc::Int::ItemRep.new( Nanoc::Int::Item.new( 'content', {}, '/foo/bar/baz/', ), :blah, ) @item_rep.paths[:last] = ['/foo/bar/baz/'] end # Set content raw_content = %(foo) expected_content = %(foo) # Test actual_content = filter.setup_and_run(raw_content, type: :html) assert_equal(expected_content, actual_content) end def test_filter_html_with_single_quotes # Create filter with mock item filter = Nanoc::Filters::RelativizePaths.new # Mock item filter.instance_eval do @item_rep = Nanoc::Int::ItemRep.new( Nanoc::Int::Item.new( 'content', {}, '/foo/bar/baz/', ), :blah, ) @item_rep.paths[:last] = ['/foo/bar/baz/'] end # Set content raw_content = %(foo) expected_content = %(foo) # Test actual_content = filter.setup_and_run(raw_content, type: :html) assert_equal(expected_content, actual_content) end def test_filter_html_without_quotes # Create filter with mock item filter = Nanoc::Filters::RelativizePaths.new # Mock item filter.instance_eval do @item_rep = Nanoc::Int::ItemRep.new( Nanoc::Int::Item.new( 'content', {}, '/foo/bar/baz/', ), :blah, ) @item_rep.paths[:last] = ['/foo/bar/baz/'] end # Set content raw_content = %(foo) expected_content = %(foo) # Test actual_content = filter.setup_and_run(raw_content, type: :html) assert_equal(expected_content, actual_content) end def test_filter_html_with_boilerplate # Create filter with mock item filter = Nanoc::Filters::RelativizePaths.new # Mock item filter.instance_eval do @item_rep = Nanoc::Int::ItemRep.new( Nanoc::Int::Item.new( 'content', {}, '/foo/bar/baz/', ), :blah, ) @item_rep.paths[:last] = ['/foo/bar/baz/'] end # Set content raw_content = <<~EOS Hello foo EOS expected0 = %r{foo} expected1 = %r{\A\s*\s*\s*(.|\s)*Hello\s*\s*\s*foo\s*\s*\s*\Z}m # Test actual_content = filter.setup_and_run(raw_content, type: :html) assert_match(expected0, actual_content) assert_match(expected1, actual_content) end def test_filter_html5_with_boilerplate # Create filter with mock item filter = Nanoc::Filters::RelativizePaths.new # Mock item filter.instance_eval do @item_rep = Nanoc::Int::ItemRep.new( Nanoc::Int::Item.new( 'content', {}, '/foo/bar/baz/', ), :blah, ) @item_rep.paths[:last] = ['/foo/bar/baz/'] end # Set content raw_content = <<~EOS Hello foo EOS expected0 = %r{foo} expected1 = %r{\A\s*\s*\s*\s*Hello\s*\s*\s*foo\s*\s*\s*\Z}m # Test actual_content = filter.setup_and_run(raw_content, type: :html5) assert_match(expected0, actual_content) assert_match(expected1, actual_content) end def test_filter_html_multiple # Create filter with mock item filter = Nanoc::Filters::RelativizePaths.new # Mock item filter.instance_eval do @item_rep = Nanoc::Int::ItemRep.new( Nanoc::Int::Item.new( 'content', {}, '/foo/bar/baz/', ), :blah, ) @item_rep.paths[:last] = ['/foo/bar/baz/'] end # Set content raw_content = %(foo bar) expected_content = %(foo bar) # Test actual_content = filter.setup_and_run(raw_content, type: :html) assert_equal(expected_content, actual_content) end def test_filter_html_nested # Create filter with mock item filter = Nanoc::Filters::RelativizePaths.new # Mock item filter.instance_eval do @item_rep = Nanoc::Int::ItemRep.new( Nanoc::Int::Item.new( 'content', {}, '/foo/bar/baz/', ), :blah, ) @item_rep.paths[:last] = ['/foo/bar/baz/'] end # Set content raw_content = %() expected_content = %() # Test actual_content = filter.setup_and_run(raw_content, type: :html) assert_equal(expected_content, actual_content) end def test_filter_html_outside_tag # Create filter with mock item filter = Nanoc::Filters::RelativizePaths.new # Mock item filter.instance_eval do @item_rep = Nanoc::Int::ItemRep.new( Nanoc::Int::Item.new( 'content', {}, '/foo/bar/baz/', ), :blah, ) @item_rep.paths[:last] = ['/foo/bar/baz/'] end # Set content raw_content = %(stuff href="/foo" more stuff) expected_content = %(stuff href="/foo" more stuff) # Test actual_content = filter.setup_and_run(raw_content, type: :html) assert_equal(expected_content, actual_content) end def test_filter_html_root # Create filter with mock item filter = Nanoc::Filters::RelativizePaths.new # Mock item filter.instance_eval do @item_rep = Nanoc::Int::ItemRep.new( Nanoc::Int::Item.new( 'content', {}, '/foo/bar/baz/', ), :blah, ) @item_rep.paths[:last] = ['/woof/meow/'] end # Set content raw_content = %(foo) expected_content = %(foo) # Test actual_content = filter.setup_and_run(raw_content, type: :html) assert_equal(expected_content, actual_content) end def test_filter_html_network_path # Create filter with mock item filter = Nanoc::Filters::RelativizePaths.new # Mock item filter.instance_eval do @item_rep = Nanoc::Int::ItemRep.new( Nanoc::Int::Item.new( 'content', {}, '/foo/bar/baz/', ), :blah, ) @item_rep.paths[:last] = ['/woof/meow/'] end # Set content raw_content = %(example.com) expected_content = %(example.com) # Test actual_content = filter.setup_and_run(raw_content, type: :html) assert_equal(expected_content, actual_content) end def test_filter_html_with_anchor # Create filter with mock item filter = Nanoc::Filters::RelativizePaths.new # Mock item filter.instance_eval do @item_rep = Nanoc::Int::ItemRep.new( Nanoc::Int::Item.new( 'content', {}, '/foo/bar/baz/', ), :blah, ) @item_rep.paths[:last] = ['/woof/meow/'] end # Set content raw_content = %(Max Payne) expected_content = %(Max Payne) # Test actual_content = filter.setup_and_run(raw_content, type: :html) assert_equal(expected_content, actual_content) end def test_filter_html_with_url # Create filter with mock item filter = Nanoc::Filters::RelativizePaths.new # Mock item filter.instance_eval do @item_rep = Nanoc::Int::ItemRep.new( Nanoc::Int::Item.new( 'content', {}, '/foo/bar/baz/', ), :blah, ) @item_rep.paths[:last] = ['/woof/meow/'] end # Set content raw_content = %(Example) expected_content = %(Example) # Test actual_content = filter.setup_and_run(raw_content, type: :html) assert_equal(expected_content, actual_content) end def test_filter_html_with_relative_path # Create filter with mock item filter = Nanoc::Filters::RelativizePaths.new # Mock item filter.instance_eval do @item_rep = Nanoc::Int::ItemRep.new( Nanoc::Int::Item.new( 'content', {}, '/foo/bar/baz/', ), :blah, ) @item_rep.paths[:last] = ['/woof/meow/'] end # Set content raw_content = %(Example) expected_content = %(Example) # Test actual_content = filter.setup_and_run(raw_content, type: :html) assert_equal(expected_content, actual_content) end def test_filter_html_object_with_relative_path # Create filter with mock item filter = Nanoc::Filters::RelativizePaths.new # Mock item filter.instance_eval do @item_rep = Nanoc::Int::ItemRep.new( Nanoc::Int::Item.new( 'content', {}, '/foo/bar/baz/', ), :blah, ) @item_rep.paths[:last] = ['/woof/meow/'] end raw_content = %() actual_content = filter.setup_and_run(raw_content, type: :html) assert_match(//, actual_content) assert_match(/) expected_content = %(
) # Test actual_content = filter.setup_and_run(raw_content, type: :html) assert_equal(expected_content, actual_content) end def test_filter_implicit # Create filter with mock item filter = Nanoc::Filters::RelativizePaths.new # Test assert_raises(RuntimeError) do filter.setup_and_run('moo') end end def test_filter_css_with_double_quotes # Create filter with mock item filter = Nanoc::Filters::RelativizePaths.new # Mock item filter.instance_eval do @item_rep = Nanoc::Int::ItemRep.new( Nanoc::Int::Item.new( 'content', {}, '/foo/bar/baz/', ), :blah, ) @item_rep.paths[:last] = ['/foo/bar/baz/'] end # Set content raw_content = %[background: url("/foo/bar/background.png");] expected_content = %[background: url("../background.png");] # Test actual_content = filter.setup_and_run(raw_content, type: :css) assert_equal(expected_content, actual_content) end def test_filter_css_with_single_quotes # Create filter with mock item filter = Nanoc::Filters::RelativizePaths.new # Mock item filter.instance_eval do @item_rep = Nanoc::Int::ItemRep.new( Nanoc::Int::Item.new( 'content', {}, '/foo/bar/baz/', ), :blah, ) @item_rep.paths[:last] = ['/foo/bar/baz/'] end # Set content raw_content = %[background: url('/foo/bar/background.png');] expected_content = %[background: url('../background.png');] # Test actual_content = filter.setup_and_run(raw_content, type: :css) assert_equal(expected_content, actual_content) end def test_filter_css_without_quotes # Create filter with mock item filter = Nanoc::Filters::RelativizePaths.new # Mock item filter.instance_eval do @item_rep = Nanoc::Int::ItemRep.new( Nanoc::Int::Item.new( 'content', {}, '/foo/bar/baz/', ), :blah, ) @item_rep.paths[:last] = ['/foo/bar/baz/'] end # Set content raw_content = %[background: url(/foo/bar/background.png);] expected_content = %[background: url(../background.png);] # Test actual_content = filter.setup_and_run(raw_content, type: :css) assert_equal(expected_content, actual_content) end def test_filter_css_multiple # Create filter with mock item filter = Nanoc::Filters::RelativizePaths.new # Mock item filter.instance_eval do @item_rep = Nanoc::Int::ItemRep.new( Nanoc::Int::Item.new( 'content', {}, '/foo/bar/baz/', ), :blah, ) @item_rep.paths[:last] = ['/foo/bar/baz/'] end # Set content raw_content = %[background: url(/foo/bar/a.png) url(/foo/bar/b.png);] expected_content = %[background: url(../a.png) url(../b.png);] # Test actual_content = filter.setup_and_run(raw_content, type: :css) assert_equal(expected_content, actual_content) end def test_filter_css_root # It is probably a bit weird to have “url(/)” in CSS, but I’ve made a # test case for this situation anyway. Can’t hurt… # Create filter with mock item filter = Nanoc::Filters::RelativizePaths.new # Mock item filter.instance_eval do @item_rep = Nanoc::Int::ItemRep.new( Nanoc::Int::Item.new( 'content', {}, '/foo/bar/baz/', ), :blah, ) @item_rep.paths[:last] = ['/woof/meow/'] end # Set content raw_content = %[background: url(/);] expected_content = %[background: url(../../);] # Test actual_content = filter.setup_and_run(raw_content, type: :css) assert_equal(expected_content, actual_content) end def test_filter_css_network_path # Create filter with mock item filter = Nanoc::Filters::RelativizePaths.new # Mock item filter.instance_eval do @item_rep = Nanoc::Int::ItemRep.new( Nanoc::Int::Item.new( 'content', {}, '/foo/bar/baz/', ), :blah, ) @item_rep.paths[:last] = ['/woof/meow/'] end # Set content raw_content = %[background: url(//example.com);] expected_content = %[background: url(//example.com);] # Test actual_content = filter.setup_and_run(raw_content, type: :css) assert_equal(expected_content, actual_content) end def test_filter_xml if_have 'nokogiri' do # Create filter with mock item filter = Nanoc::Filters::RelativizePaths.new # Mock item filter.instance_eval do @item_rep = Nanoc::Int::ItemRep.new( Nanoc::Int::Item.new( 'content', {}, '/foo/bar/baz/', ), :blah, ) @item_rep.paths[:last] = ['/foo/bar/baz/'] end # Set content expected = /baz<\/bar>/ raw_content = <<~XML baz XML actual_content = filter.setup_and_run(raw_content, type: :xml, select: ['*/@boo']) assert_match(expected, actual_content) end end def test_filter_fragment_xml if_have 'nokogiri' do # Create filter with mock item filter = Nanoc::Filters::RelativizePaths.new # Mock item filter.instance_eval do @item_rep = Nanoc::Int::ItemRep.new( Nanoc::Int::Item.new( 'content', {}, '/foo/bar/baz/', ), :blah, ) @item_rep.paths[:last] = ['/foo/bar/baz/'] end # Set content raw_content = <<~XML baz XML actual_content = filter.setup_and_run(raw_content, type: :xml, select: ['far/@href']) assert_match(//, actual_content) assert_match(/baz<\/far><\/bar>/, actual_content) end end def test_filter_xml_with_namespaces if_have 'nokogiri' do # Create filter with mock item filter = Nanoc::Filters::RelativizePaths.new # Mock item filter.instance_eval do @item_rep = Nanoc::Int::ItemRep.new( Nanoc::Int::Item.new( 'content', {}, '/foo/bar/baz/', ), :blah, ) @item_rep.paths[:last] = ['/foo/bar/baz/'] end # Set content raw_content = <<~XML baz XML options = { type: :xml, namespaces: { ex: 'http://example.org' }, select: ['ex:a/@href'], } actual_content = filter.setup_and_run(raw_content, options) assert_match(//, actual_content) assert_match(/baz<\/a><\/bar>/, actual_content) end end def test_filter_xhtml if_have 'nokogiri' do # Create filter with mock item filter = Nanoc::Filters::RelativizePaths.new # Mock item filter.instance_eval do @item_rep = Nanoc::Int::ItemRep.new( Nanoc::Int::Item.new( 'content', {}, '/foo/bar/baz/', ), :blah, ) @item_rep.paths[:last] = ['/foo/bar/baz/'] end # Set content raw_content = <<~XML bar XML actual_content = filter.setup_and_run(raw_content, type: :xhtml) assert_match(/]*href="..\/..\/..\/css"[^>]*\/>/, actual_content) assert_match(/', '
', '', '', '', ]) check = Nanoc::Checking::Checks::MixedContent.create(site) check.run assert_empty check.issues end end def test_root_relative_content with_site do |site| create_output_file('foo.html', [ '', '', '', '
', '', '', '', ]) check = Nanoc::Checking::Checks::MixedContent.create(site) check.run assert_empty check.issues end end def test_protocol_relative_content with_site do |site| create_output_file('foo.html', [ '', '', '', '
', '', '', '', ]) check = Nanoc::Checking::Checks::MixedContent.create(site) check.run assert_empty check.issues end end def test_document_relative_content with_site do |site| create_output_file('foo.html', [ '', '', '', '
', '', '', '', ]) check = Nanoc::Checking::Checks::MixedContent.create(site) check.run assert_empty check.issues end end def test_query_relative_content with_site do |site| create_output_file('foo.html', [ '', '', '', '
', '', '', '', ]) check = Nanoc::Checking::Checks::MixedContent.create(site) check.run assert_empty check.issues end end def test_fragment_relative_content with_site do |site| create_output_file('foo.html', [ '', '', '', '
', '', '', '', ]) check = Nanoc::Checking::Checks::MixedContent.create(site) check.run assert_empty check.issues end end def test_http_content with_site do |site| create_output_file('foo.html', [ '', '', '', '', '
', '', '', '', ]) check = Nanoc::Checking::Checks::MixedContent.create(site) check.run issues = check.issues.to_a assert_equal 8, issues.count descriptions = issues.map(&:description) issues.each do |issue| assert_equal 'output/foo.html', issue.subject end # The order of the reported issues is not important, so use this class's # `assert_include` helper to avoid asserting those details assert_include descriptions, 'mixed content include: http://nanoc.ws/logo.png' assert_include descriptions, 'mixed content include: HTTP://nanoc.ws/logo.png' assert_include descriptions, 'mixed content include: http://nanoc.ws/style.css' assert_include descriptions, 'mixed content include: http://nanoc.ws/app.js' assert_include descriptions, 'mixed content include: http://nanoc.ws/process.cgi' assert_include descriptions, 'mixed content include: http://nanoc.ws/preview.html' assert_include descriptions, 'mixed content include: http://nanoc.ws/theme-song.flac' assert_include descriptions, 'mixed content include: http://nanoc.ws/screencast.mkv' end end def test_inert_content with_site do |site| create_output_file('foo.html', [ 'The homepage', 'Content', '', '', '', '', '
', '', '', '', '

http://nanoc.ws/harmless-text

', ]) check = Nanoc::Checking::Checks::MixedContent.create(site) check.run assert_empty check.issues end end end nanoc-4.8.0/test/checking/checks/test_internal_links.rb0000644000004100000410000000745113134141051023243 0ustar www-datawww-data# frozen_string_literal: true require 'helper' class Nanoc::Checking::Checks::InternalLinksTest < Nanoc::TestCase def test_run with_site do |site| # Create files FileUtils.mkdir_p('output') FileUtils.mkdir_p('output/stuff') File.open('output/foo.txt', 'w') { |io| io.write('broken') } File.open('output/bar.html', 'w') { |io| io.write('not broken') } # Create check check = Nanoc::Checking::Checks::InternalLinks.create(site) check.run # Test assert check.issues.empty? end end def test_resource_uris with_site do |site| # Create files FileUtils.mkdir_p('output') File.open('output/bar.html', 'w') { |io| io.write('') } # Create check check = Nanoc::Checking::Checks::InternalLinks.create(site) check.run # Test assert check.issues.size == 1 end end def test_valid? with_site do |site| # Create files FileUtils.mkdir_p('output') FileUtils.mkdir_p('output/stuff') File.open('output/origin', 'w') { |io| io.write('hi') } File.open('output/foo', 'w') { |io| io.write('hi') } File.open('output/stuff/blah', 'w') { |io| io.write('hi') } # Create check check = Nanoc::Checking::Checks::InternalLinks.create(site) # Test assert check.send(:valid?, 'foo', 'output/origin') assert check.send(:valid?, 'origin', 'output/origin') assert check.send(:valid?, 'stuff/blah', 'output/origin') assert check.send(:valid?, '/foo', 'output/origin') assert check.send(:valid?, '/origin', 'output/origin') assert check.send(:valid?, '/stuff/blah', 'output/origin') end end def test_remove_query_string with_site do |site| FileUtils.mkdir_p('output/stuff') File.open('output/stuff/right', 'w') { |io| io.write('hi') } check = Nanoc::Checking::Checks::InternalLinks.create(site) assert check.send(:valid?, 'stuff/right?foo=123', 'output/origin') refute check.send(:valid?, 'stuff/wrong?foo=123', 'output/origin') end end def test_exclude with_site do |site| # Create check check = Nanoc::Checking::Checks::InternalLinks.create(site) site.config.update(checks: { internal_links: { exclude: ['^/excluded\d+'] } }) # Test assert check.send(:valid?, '/excluded1', 'output/origin') assert check.send(:valid?, '/excluded2', 'output/origin') assert !check.send(:valid?, '/excluded_not', 'output/origin') end end def test_exclude_targets with_site do |site| # Create check check = Nanoc::Checking::Checks::InternalLinks.create(site) site.config.update(checks: { internal_links: { exclude_targets: ['^/excluded\d+'] } }) # Test assert check.send(:valid?, '/excluded1', 'output/origin') assert check.send(:valid?, '/excluded2', 'output/origin') assert !check.send(:valid?, '/excluded_not', 'output/origin') end end def test_exclude_origins with_site do |site| # Create check check = Nanoc::Checking::Checks::InternalLinks.create(site) site.config.update(checks: { internal_links: { exclude_origins: ['^/excluded'] } }) # Test assert check.send(:valid?, '/foo', 'output/excluded') assert !check.send(:valid?, '/foo', 'output/not_excluded') end end def test_unescape_url with_site do |site| FileUtils.mkdir_p('output/stuff') File.open('output/stuff/right foo', 'w') { |io| io.write('hi') } check = Nanoc::Checking::Checks::InternalLinks.create(site) assert check.send(:valid?, 'stuff/right%20foo', 'output/origin') refute check.send(:valid?, 'stuff/wrong%20foo', 'output/origin') end end end nanoc-4.8.0/test/checking/test_runner.rb0000644000004100000410000000213513134141051020272 0ustar www-datawww-data# frozen_string_literal: true require 'helper' class Nanoc::Checking::RunnerTest < Nanoc::TestCase def test_run_specific with_site do |site| File.open('output/blah', 'w') { |io| io.write('I am stale! Haha!') } runner = Nanoc::Checking::Runner.new(site) runner.run_specific(%w[stale]) end end def test_run_specific_custom with_site do |site| File.open('Checks', 'w') do |io| io.write('check :my_foo_check do ; puts "I AM FOO!" ; end') end runner = Nanoc::Checking::Runner.new(site) ios = capturing_stdio do runner.run_specific(%w[my_foo_check]) end assert ios[:stdout].include?('I AM FOO!') end end def test_list_checks with_site do |site| File.open('Checks', 'w') do |io| io.write('check :my_foo_check do ; end') end runner = Nanoc::Checking::Runner.new(site) ios = capturing_stdio do runner.list_checks end assert ios[:stdout].include?('my_foo_check') assert ios[:stdout].include?('internal_links') assert ios[:stderr].empty? end end end nanoc-4.8.0/test/checking/test_dsl.rb0000644000004100000410000000163313134141051017545 0ustar www-datawww-data# frozen_string_literal: true require 'helper' class Nanoc::Checking::DSLTest < Nanoc::TestCase def test_from_file with_site do |_site| File.open('Checks', 'w') { |io| io.write("check :foo do\n\nend\ndeploy_check :bar\n") } dsl = Nanoc::Checking::DSL.from_file('Checks') # One new check refute Nanoc::Checking::Check.named(:foo).nil? # One check marked for deployment assert_equal [:bar], dsl.deploy_checks end end def test_has_base_path with_site do |_site| File.write('stuff.rb', '$greeting = "hello"') File.write('Checks', 'require "./stuff"') Nanoc::Checking::DSL.from_file('Checks') assert_equal 'hello', $greeting end end def test_has_absolute_path with_site do |_site| File.write('Checks', '$stuff = __FILE__') Nanoc::Checking::DSL.from_file('Checks') assert($stuff.start_with?('/')) end end end nanoc-4.8.0/test/rule_dsl/0000755000004100000410000000000013134141051015432 5ustar www-datawww-datananoc-4.8.0/test/rule_dsl/test_rules_collection.rb0000644000004100000410000000632013134141051022364 0ustar www-datawww-data# frozen_string_literal: true require 'helper' class Nanoc::RuleDSL::RulesCollectionTest < Nanoc::TestCase def test_compilation_rule_for # Mock rules rules = [mock, mock, mock] rules[0].expects(:applicable_to?).returns(false) rules[1].expects(:applicable_to?).returns(true) rules[1].expects(:rep_name).returns('wrong') rules[2].expects(:applicable_to?).returns(true) rules[2].expects(:rep_name).returns('right') rules_collection = Nanoc::RuleDSL::RulesCollection.new rules_collection.instance_eval { @item_compilation_rules = rules } # Mock rep rep = mock rep.stubs(:name).returns('right') item = mock rep.stubs(:item).returns(item) # Test assert_equal rules[2], rules_collection.compilation_rule_for(rep) end def test_filter_for_layout_with_existant_layout rules_collection = Nanoc::RuleDSL::RulesCollection.new rules_collection.layout_filter_mapping[Nanoc::Int::Pattern.from(/.*/)] = [:erb, { foo: 'bar' }] # Mock layout layout = MiniTest::Mock.new layout.expect(:identifier, '/some_layout/') # Check assert_equal([:erb, { foo: 'bar' }], rules_collection.filter_for_layout(layout)) end def test_filter_for_layout_with_existant_layout_and_unknown_filter rules_collection = Nanoc::RuleDSL::RulesCollection.new rules_collection.layout_filter_mapping[Nanoc::Int::Pattern.from(/.*/)] = [:some_unknown_filter, { foo: 'bar' }] # Mock layout layout = MiniTest::Mock.new layout.expect(:identifier, '/some_layout/') # Check assert_equal([:some_unknown_filter, { foo: 'bar' }], rules_collection.filter_for_layout(layout)) end def test_filter_for_layout_with_nonexistant_layout rules_collection = Nanoc::RuleDSL::RulesCollection.new rules_collection.layout_filter_mapping[Nanoc::Int::Pattern.from(%r{^/foo/$})] = [:erb, { foo: 'bar' }] # Mock layout layout = MiniTest::Mock.new layout.expect(:identifier, '/bar/') # Check assert_equal(nil, rules_collection.filter_for_layout(layout)) end def test_filter_for_layout_with_many_layouts rules_collection = Nanoc::RuleDSL::RulesCollection.new rules_collection.layout_filter_mapping[Nanoc::Int::Pattern.from(%r{^/a/b/c/.*/$})] = [:erb, { char: 'd' }] rules_collection.layout_filter_mapping[Nanoc::Int::Pattern.from(%r{^/a/.*/$})] = [:erb, { char: 'b' }] rules_collection.layout_filter_mapping[Nanoc::Int::Pattern.from(%r{^/a/b/.*/$})] = [:erb, { char: 'c' }] # never used! rules_collection.layout_filter_mapping[Nanoc::Int::Pattern.from(%r{^/.*/$})] = [:erb, { char: 'a' }] # Mock layout layouts = [mock, mock, mock, mock] layouts[0].stubs(:identifier).returns('/a/b/c/d/') layouts[1].stubs(:identifier).returns('/a/b/c/') layouts[2].stubs(:identifier).returns('/a/b/') layouts[3].stubs(:identifier).returns('/a/') # Get expectations expectations = { 0 => 'd', 1 => 'b', # never used! not c, because b takes priority 2 => 'b', 3 => 'a', } # Check expectations.each_pair do |num, char| filter_and_args = rules_collection.filter_for_layout(layouts[num]) refute_nil(filter_and_args) assert_equal(char, filter_and_args[1][:char]) end end end nanoc-4.8.0/test/rule_dsl/test_compiler_dsl.rb0000644000004100000410000002742113134141051021500 0ustar www-datawww-data# frozen_string_literal: true require 'helper' class Nanoc::RuleDSL::CompilerDSLTest < Nanoc::TestCase def test_compile # TODO: implement end def test_route # TODO: implement end def test_layout # TODO: implement end def test_preprocess_twice rules_collection = Nanoc::RuleDSL::RulesCollection.new compiler_dsl = Nanoc::RuleDSL::CompilerDSL.new(rules_collection, {}) # first time io = capturing_stdio do compiler_dsl.preprocess {} end assert_empty io[:stdout] assert_empty io[:stderr] # second time io = capturing_stdio do compiler_dsl.preprocess {} end assert_empty io[:stdout] assert_match(/WARNING: A preprocess block is already defined./, io[:stderr]) end def test_postprocess_twice rules_collection = Nanoc::RuleDSL::RulesCollection.new compiler_dsl = Nanoc::RuleDSL::CompilerDSL.new(rules_collection, {}) # first time io = capturing_stdio do compiler_dsl.postprocess {} end assert_empty io[:stdout] assert_empty io[:stderr] # second time io = capturing_stdio do compiler_dsl.postprocess {} end assert_empty io[:stdout] assert_match(/WARNING: A postprocess block is already defined./, io[:stderr]) end def test_postprocessor_modified_method with_site do |site| # Create rules File.open('Rules', 'w') do |io| io.write <<~EOS compile '*' do end route '*' do end postprocess do puts @items.select(&:modified).length end EOS end File.open('content/index.html', 'w') { |io| io.write('o hello') } io = capturing_stdio do site = Nanoc::Int::SiteLoader.new.new_from_cwd site.compile end assert_match(/1/, io[:stdout]) end end def test_include_rules with_site(legacy: false) do |site| # Create a bonus rules file File.write( 'more_rules.rb', "passthrough '/index.*'", ) # Adjust normal rules file File.write( 'Rules', "include_rules 'more_rules'\n\n" \ "route '/**/*' do ; nil ; end\n\n" \ "compile '/**/*' do ; end\n", ) # Create items File.write('content/index.html', 'hello!') # Compile site = Nanoc::Int::SiteLoader.new.new_from_cwd site.compile # Check assert File.file?('output/index.html') end end def test_passthrough with_site do # Create rules File.open('Rules', 'w') do |io| io.write <<~EOS passthrough "/robots/" compile '*' do ; end route '*' do ; item.identifier.chop + '-xyz' + item[:extension] ; end EOS end # Create items assert Dir['content/*'].empty? File.open('content/robots.txt', 'w') do |io| io.write 'Hello I am robots' end # Compile site = Nanoc::Int::SiteLoader.new.new_from_cwd site.compile # Check paths assert_equal ['output/robots.txt'], Dir['output/*'] end end def test_passthrough_no_ext with_site do # Create rules File.open('Rules', 'w') do |io| io.write <<~EOS passthrough "/foo/" EOS end # Create items assert Dir['content/*'].empty? File.open('content/foo', 'w') do |io| io.write 'Hello I am foo' end # Compile site = Nanoc::Int::SiteLoader.new.new_from_cwd site.compile # Check paths assert_equal ['output/foo'], Dir['output/*'] end end def test_passthrough_priority with_site do # Create rules File.open('Rules', 'w') do |io| io.write <<~EOS compile '*' do filter :erb end route '*' do item.identifier + 'index.html' end passthrough "/foo/" EOS end # Create items assert Dir['content/*'].empty? File.open('content/foo.txt', 'w') do |io| io.write "Hello I am <%= 'foo' %>" end # Compile site = Nanoc::Int::SiteLoader.new.new_from_cwd site.compile # Check paths assert_equal ['output/foo'], Dir['output/*'] assert_equal ['output/foo/index.html'], Dir['output/foo/*'] end end def test_passthrough_with_full_identifiers with_site do File.open('nanoc.yaml', 'w') do |io| io << 'string_pattern_type: legacy' << "\n" io << 'data_sources:' << "\n" io << ' -' << "\n" io << ' type: filesystem' << "\n" io << ' identifier_type: full' << "\n" end # Create rules File.open('Rules', 'w') do |io| io << 'passthrough \'*\'' end # Create items assert Dir['content/*'].empty? File.open('content/robots.txt', 'w') do |io| io.write 'Hello I am robots' end # Compile site = Nanoc::Int::SiteLoader.new.new_from_cwd site.compile # Check paths assert_equal ['output/robots.txt'], Dir['output/*'] end end def test_ignore with_site do # Create rules File.open('Rules', 'w') do |io| io.write <<~EOS ignore '/lame/' passthrough '*' EOS end # Create items assert Dir['content/*'].empty? File.open('content/lame.txt', 'w') do |io| io.write 'Hello I am lame' end File.open('content/notlame.txt', 'w') do |io| io.write 'Hello I am not lame' end # Compile site = Nanoc::Int::SiteLoader.new.new_from_cwd site.compile # Check paths assert_equal ['output/notlame.txt'], Dir['output/*'] end end def test_ignore_priority with_site do # Create rules File.open('Rules', 'w') do |io| io.write <<~EOS compile '*' do filter :erb end route '*' do item.identifier + 'index.html' end ignore "/foo/" EOS end # Create items assert Dir['content/*'].empty? File.open('content/foo.txt', 'w') do |io| io.write "Hello I am <%= 'foo' %>" end # Compile site = Nanoc::Int::SiteLoader.new.new_from_cwd site.compile # Check paths assert_equal ['output/foo'], Dir['output/*'] assert_equal ['output/foo/index.html'], Dir['output/foo/*'] end end def test_create_pattern_with_string_with_no_config compiler_dsl = Nanoc::RuleDSL::CompilerDSL.new(nil, {}) err = assert_raises(Nanoc::Int::Errors::GenericTrivial) do compiler_dsl.create_pattern('/foo/*') end assert_equal 'Invalid string_pattern_type: ', err.message end def test_create_pattern_with_string_with_glob_string_pattern_type compiler_dsl = Nanoc::RuleDSL::CompilerDSL.new(nil, string_pattern_type: 'glob') pattern = compiler_dsl.create_pattern('/foo/*') assert pattern.match?('/foo/aaaa') refute pattern.match?('/foo/aaaa/') refute pattern.match?('/foo/a/a/a/a') end def test_create_pattern_with_regex compiler_dsl = Nanoc::RuleDSL::CompilerDSL.new(nil, string_pattern_type: 'glob') pattern = compiler_dsl.create_pattern(%r{\A/foo/a*/}) assert pattern.match?('/foo/aaaa/') end def test_create_pattern_with_string_with_unknown_string_pattern_type compiler_dsl = Nanoc::RuleDSL::CompilerDSL.new(nil, string_pattern_type: 'donkey') err = assert_raises(Nanoc::Int::Errors::GenericTrivial) do compiler_dsl.create_pattern('/foo/*') end assert_equal 'Invalid string_pattern_type: donkey', err.message end def test_identifier_to_regex_without_wildcards # Create compiler DSL compiler_dsl = Nanoc::RuleDSL::CompilerDSL.new(nil, {}) actual = compiler_dsl.instance_eval { identifier_to_regex('foo') } expected = %r{^/foo/?$} assert_equal(expected.to_s, actual.to_s) assert_equal(expected.source, actual.source) assert_equal(expected.casefold?, actual.casefold?) assert_equal(expected.options, actual.options) end def test_identifier_to_regex_with_one_wildcard # Create compiler DSL compiler_dsl = Nanoc::RuleDSL::CompilerDSL.new(nil, {}) actual = compiler_dsl.instance_eval { identifier_to_regex('foo/*/bar') } expected = %r{^/foo/(.*?)/bar/?$} assert_equal(expected.to_s, actual.to_s) assert_equal(expected.source, actual.source) assert_equal(expected.casefold?, actual.casefold?) assert_equal(expected.options, actual.options) end def test_identifier_to_regex_with_two_wildcards # Create compiler DSL compiler_dsl = Nanoc::RuleDSL::CompilerDSL.new(nil, {}) actual = compiler_dsl.instance_eval { identifier_to_regex('foo/*/bar/*/qux') } expected = %r{^/foo/(.*?)/bar/(.*?)/qux/?$} assert_equal(expected.to_s, actual.to_s) assert_equal(expected.source, actual.source) assert_equal(expected.casefold?, actual.casefold?) assert_equal(expected.options, actual.options) end def test_identifier_to_regex_with_just_one_wildcard # Create compiler DSL compiler_dsl = Nanoc::RuleDSL::CompilerDSL.new(nil, {}) actual = compiler_dsl.instance_eval { identifier_to_regex('*') } expected = %r{^/(.*?)$} assert_equal(expected.to_s, actual.to_s) assert_equal(expected.source, actual.source) assert_equal(expected.casefold?, actual.casefold?) assert_equal(expected.options, actual.options) end def test_identifier_to_regex_with_root # Create compiler DSL compiler_dsl = Nanoc::RuleDSL::CompilerDSL.new(nil, {}) actual = compiler_dsl.instance_eval { identifier_to_regex('/') } expected = %r{^/$} assert_equal(expected.to_s, actual.to_s) assert_equal(expected.source, actual.source) assert_equal(expected.casefold?, actual.casefold?) assert_equal(expected.options, actual.options) end def test_identifier_to_regex_with_only_children # Create compiler DSL compiler_dsl = Nanoc::RuleDSL::CompilerDSL.new(nil, {}) actual = compiler_dsl.instance_eval { identifier_to_regex('/foo/*/') } expected = %r{^/foo/(.*?)/$} assert_equal(expected.to_s, actual.to_s) assert_equal(expected.source, actual.source) assert_equal(expected.casefold?, actual.casefold?) assert_equal(expected.options, actual.options) end def test_identifier_to_regex_with_plus_wildcard # Create compiler DSL compiler_dsl = Nanoc::RuleDSL::CompilerDSL.new(nil, {}) actual = compiler_dsl.instance_eval { identifier_to_regex('/foo/+') } expected = %r{^/foo/(.+?)/?$} assert_equal(expected.to_s, actual.to_s) assert_equal(expected.source, actual.source) assert_equal(expected.casefold?, actual.casefold?) assert_equal(expected.options, actual.options) assert('/foo/bar/' =~ actual) refute('/foo/' =~ actual) end def test_identifier_to_regex_with_full_identifier # Create compiler DSL compiler_dsl = Nanoc::RuleDSL::CompilerDSL.new(nil, {}) actual = compiler_dsl.instance_eval { identifier_to_regex('/favicon.ico') } expected = %r{^/favicon\.ico/?$} assert_equal(expected.to_s, actual.to_s) assert('/favicon.ico' =~ actual) assert('/favicon.ico/' =~ actual) refute('/faviconxico' =~ actual) end def test_dsl_has_no_access_to_compiler compiler_dsl = Nanoc::RuleDSL::CompilerDSL.new(nil, {}) assert_raises(NameError) do compiler_dsl.instance_eval { compiler } end end def test_config $venetian = 'unsnares' compiler_dsl = Nanoc::RuleDSL::CompilerDSL.new(nil, venetian: 'snares') compiler_dsl.instance_eval { $venetian = @config[:venetian] } assert_equal 'snares', $venetian end def test_config_without_sigil $venetian = 'unsnares' compiler_dsl = Nanoc::RuleDSL::CompilerDSL.new(nil, venetian: 'snares') compiler_dsl.instance_eval { $venetian = config[:venetian] } assert_equal 'snares', $venetian end end nanoc-4.8.0/test/rule_dsl/test_action_provider.rb0000644000004100000410000000453413134141051022213 0ustar www-datawww-data# frozen_string_literal: true require 'helper' class Nanoc::RuleDSL::ActionProviderTest < Nanoc::TestCase def new_action_provider(site) rules_collection = Nanoc::RuleDSL::RulesCollection.new action_sequence_calculator = Nanoc::RuleDSL::ActionSequenceCalculator.new( rules_collection: rules_collection, site: site, ) action_provider = Nanoc::RuleDSL::ActionProvider.new( rules_collection, action_sequence_calculator ) Nanoc::RuleDSL::RulesLoader.new(site.config, rules_collection).load action_provider end def test_per_rules_file_preprocessor # Create site Nanoc::CLI.run %w[create_site foo] FileUtils.cd('foo') do # Create a bonus rules file File.write( 'more_rules.rb', "preprocess { @items['/index.*'][:preprocessed] = true }", ) # Adjust normal rules file File.write( 'Rules', "include_rules 'more_rules'\n\npreprocess {}\n\n" + File.read('Rules'), ) # Create site and compiler site = Nanoc::Int::SiteLoader.new.new_from_cwd action_provider = new_action_provider(site) # Check that the two preprocess blocks have been added assert_equal 2, action_provider.rules_collection.preprocessors.size refute_nil action_provider.rules_collection.preprocessors.first refute_nil action_provider.rules_collection.preprocessors.to_a.last # Apply preprocess blocks action_provider.preprocess(site) assert site.items['/index.*'].attributes[:preprocessed] end end def test_per_rules_file_postprocessor # Create site Nanoc::CLI.run %w[create_site foo] FileUtils.cd('foo') do # Create a bonus rules file File.write( 'more_rules.rb', 'postprocess {}', ) # Adjust normal rules file File.write( 'Rules', "include_rules 'more_rules'\n\npostprocess {}\n\n" + File.read('Rules'), ) # Create site and compiler site = Nanoc::Int::SiteLoader.new.new_from_cwd action_provider = new_action_provider(site) # Check that the two postprocess blocks have been added assert_equal 2, action_provider.rules_collection.postprocessors.size refute_nil action_provider.rules_collection.postprocessors.first refute_nil action_provider.rules_collection.postprocessors.to_a.last end end end nanoc-4.8.0/test/rule_dsl/test_rule.rb0000644000004100000410000000102213134141051017760 0ustar www-datawww-data# frozen_string_literal: true require 'helper' class Nanoc::Int::RuleTest < Nanoc::TestCase def test_initialize # TODO: implement end def test_applicable_to # TODO: implement end def test_apply_to # TODO: implement end def test_matches pattern = Nanoc::Int::Pattern.from(%r{/(.*)/(.*)/}) identifier = '/anything/else/' expected = %w[anything else] rule = Nanoc::RuleDSL::Rule.new(pattern, :string, proc {}) assert_equal expected, rule.send(:matches, identifier) end end nanoc-4.8.0/test/cli/0000755000004100000410000000000013134141051014370 5ustar www-datawww-datananoc-4.8.0/test/cli/commands/0000755000004100000410000000000013134141051016171 5ustar www-datawww-datananoc-4.8.0/test/cli/commands/test_compile.rb0000644000004100000410000001516613134141051021216 0ustar www-datawww-data# frozen_string_literal: true require 'helper' class Nanoc::CLI::Commands::CompileTest < Nanoc::TestCase def test_profiling_information with_site do |_site| File.open('content/foo.md', 'w') { |io| io << 'asdf' } File.open('content/bar.md', 'w') { |io| io << 'asdf' } File.open('content/baz.md', 'w') { |io| io << 'asdf' } File.open('Rules', 'w') do |io| io.write "compile '*' do\n" io.write " filter :erb\n" io.write "end\n" io.write "\n" io.write "route '*' do\n" io.write " if item.binary?\n" io.write " item.identifier.chop + '.' + item[:extension]\n" io.write " else\n" io.write " item.identifier + 'index.html'\n" io.write " end\n" io.write "end\n" io.write "\n" io.write "layout '*', :erb\n" end Nanoc::CLI.run %w[compile --verbose] end end def test_auto_prune with_site do |_site| File.open('content/foo.md', 'w') { |io| io << 'asdf' } File.open('content/bar.md', 'w') { |io| io << 'asdf' } File.open('content/baz.md', 'w') { |io| io << 'asdf' } File.open('Rules', 'w') do |io| io.write "compile '*' do\n" io.write " filter :erb\n" io.write "end\n" io.write "\n" io.write "route '*' do\n" io.write " if item.binary?\n" io.write " item.identifier.chop + '.' + item[:extension]\n" io.write " else\n" io.write " item.identifier + 'index.html'\n" io.write " end\n" io.write "end\n" io.write "\n" io.write "layout '*', :erb\n" end File.open('nanoc.yaml', 'w') do |io| io.write "string_pattern_type: legacy\n" io.write "prune:\n" io.write " auto_prune: false\n" end File.open('output/stray.html', 'w') do |io| io.write 'I am a stray file and I am about to be deleted!' end assert File.file?('output/stray.html') Nanoc::CLI.run %w[compile] assert File.file?('output/stray.html') File.open('nanoc.yaml', 'w') do |io| io.write "string_pattern_type: legacy\n" io.write "prune:\n" io.write " auto_prune: true\n" end assert File.file?('output/stray.html') Nanoc::CLI.run %w[compile] refute File.file?('output/stray.html') end end def test_auto_prune_with_exclude with_site do |_site| File.open('content/foo.md', 'w') { |io| io << 'asdf' } File.open('content/bar.md', 'w') { |io| io << 'asdf' } File.open('content/baz.md', 'w') { |io| io << 'asdf' } File.open('Rules', 'w') do |io| io.write "compile '*' do\n" io.write " filter :erb\n" io.write "end\n" io.write "\n" io.write "route '*' do\n" io.write " if item.binary?\n" io.write " item.identifier.chop + '.' + item[:extension]\n" io.write " else\n" io.write " item.identifier + 'index.html'\n" io.write " end\n" io.write "end\n" io.write "\n" io.write "layout '*', :erb\n" end Dir.mkdir('output/excluded_dir') File.open('nanoc.yaml', 'w') do |io| io.write "string_pattern_type: legacy\n" io.write "prune:\n" io.write " auto_prune: false\n" end File.open('output/stray.html', 'w') do |io| io.write 'I am a stray file and I am about to be deleted!' end assert File.file?('output/stray.html') Nanoc::CLI.run %w[compile] assert File.file?('output/stray.html') File.open('nanoc.yaml', 'w') do |io| io.write "string_pattern_type: legacy\n" io.write "prune:\n" io.write " auto_prune: true\n" io.write " exclude: [ 'excluded_dir' ]\n" end assert File.file?('output/stray.html') Nanoc::CLI.run %w[compile] refute File.file?('output/stray.html') assert File.directory?('output/excluded_dir'), 'excluded_dir should still be there' end end def test_setup_and_teardown_listeners with_site do test_listener_class = Class.new(::Nanoc::CLI::Commands::CompileListeners::Abstract) do def start @started = true end def stop @stopped = true end def started? @started end def stopped? @stopped end end options = {} arguments = [] cmd = nil cmd_runner = Nanoc::CLI::Commands::Compile.new(options, arguments, cmd) cmd_runner.listener_classes = [test_listener_class] cmd_runner.run listeners = cmd_runner.send(:listeners) assert listeners.size == 1 assert listeners.first.started? assert listeners.first.stopped? end end def test_file_action_printer_normal # Create data item = Nanoc::Int::Item.new('content', {}, '/') rep = Nanoc::Int::ItemRep.new(item, :default) rep.raw_paths[:last] = ['output/foo.txt'] rep.compiled = true # Listen listener = new_file_action_printer([rep]) listener.start Nanoc::Int::NotificationCenter.post(:compilation_started, rep) Nanoc::Int::NotificationCenter.post(:rep_written, rep, false, rep.raw_path, false, true) listener.stop # Check assert_equal 1, listener.events.size assert_equal :high, listener.events[0][:level] assert_equal :update, listener.events[0][:action] assert_equal 'output/foo.txt', listener.events[0][:path] assert_in_delta 0.0, listener.events[0][:duration], 1.0 end def test_file_action_printer_skip # Create data item = Nanoc::Int::Item.new('content', {}, '/') rep = Nanoc::Int::ItemRep.new(item, :default) rep.raw_paths[:last] = ['output/foo.txt'] # Listen listener = new_file_action_printer([rep]) listener.start Nanoc::Int::NotificationCenter.post(:compilation_started, rep) listener.stop # Check assert_equal 1, listener.events.size assert_equal :low, listener.events[0][:level] assert_equal :skip, listener.events[0][:action] assert_equal 'output/foo.txt', listener.events[0][:path] assert_nil listener.events[0][:duration] end def new_file_action_printer(reps) # Ensure CLI is loaded begin Nanoc::CLI.run(%w[help %]) rescue SystemExit end listener = Nanoc::CLI::Commands::CompileListeners::FileActionPrinter.new(reps: reps) def listener.log(level, action, path, duration) @events ||= [] @events << { level: level, action: action, path: path, duration: duration, } end def listener.events @events end listener end end nanoc-4.8.0/test/cli/commands/test_check.rb0000644000004100000410000000072613134141051020637 0ustar www-datawww-data# frozen_string_literal: true require 'helper' class Nanoc::CLI::Commands::CheckTest < Nanoc::TestCase def test_check_stale with_site do |_site| FileUtils.mkdir_p('output') # Should not raise now Nanoc::CLI.run %w[check stale] # Should raise now File.open('output/blah.html', 'w') { |io| io.write 'moo' } assert_raises Nanoc::Int::Errors::GenericTrivial do Nanoc::CLI.run %w[check stale] end end end end nanoc-4.8.0/test/cli/commands/test_prune.rb0000644000004100000410000001200613134141051020705 0ustar www-datawww-data# frozen_string_literal: true require 'helper' class Nanoc::CLI::Commands::PruneTest < Nanoc::TestCase def test_run_without_yes with_site do |_site| # Set output dir File.open('nanoc.yaml', 'w') { |io| io.write "output_dir: output2\nstring_pattern_type: legacy\n" } FileUtils.mkdir_p('output2') # Create source files File.open('content/index.html', 'w') { |io| io.write 'stuff' } # Create output files File.open('output2/foo.html', 'w') { |io| io.write 'this is a foo.' } File.open('output2/index.html', 'w') { |io| io.write 'this is a index.' } assert_raises SystemExit do Nanoc::CLI.run %w[prune] end assert File.file?('output2/index.html') assert File.file?('output2/foo.html') end end def test_run_with_yes with_site do |_site| # Set output dir File.open('nanoc.yaml', 'w') do |io| io << 'output_dir: output2' << "\n" io << 'string_pattern_type: legacy' << "\n" io << 'data_sources:' << "\n" io << ' -' << "\n" io << ' type: filesystem' << "\n" io << ' identifier_type: legacy' << "\n" end FileUtils.mkdir_p('output2') # Create source files File.open('content/index.html', 'w') { |io| io.write 'stuff' } # Create output files File.open('output2/foo.html', 'w') { |io| io.write 'this is a foo.' } File.open('output2/index.html', 'w') { |io| io.write 'this is a index.' } Nanoc::CLI.run %w[prune --yes] assert File.file?('output2/index.html') assert !File.file?('output2/foo.html') end end def test_run_with_dry_run with_site do |_site| # Set output dir File.open('nanoc.yaml', 'w') { |io| io.write "string_pattern_type: legacy\noutput_dir: output2" } FileUtils.mkdir_p('output2') # Create source files File.open('content/index.html', 'w') { |io| io.write 'stuff' } # Create output files File.open('output2/foo.html', 'w') { |io| io.write 'this is a foo.' } File.open('output2/index.html', 'w') { |io| io.write 'this is a index.' } Nanoc::CLI.run %w[prune --dry-run] assert File.file?('output2/index.html') assert File.file?('output2/foo.html') end end def test_run_with_exclude with_site do |_site| # Set output dir File.open('nanoc.yaml', 'w') do |io| io << 'prune:' << "\n" io << ' exclude: [ "good-dir", "good-file.html" ]' << "\n" io << 'string_pattern_type: legacy' << "\n" io << 'data_sources:' << "\n" io << ' -' << "\n" io << ' type: filesystem' << "\n" io << ' identifier_type: legacy' << "\n" end FileUtils.mkdir_p('output') # Create source files File.open('content/index.html', 'w') { |io| io.write 'stuff' } # Create output files FileUtils.mkdir_p('output/good-dir') FileUtils.mkdir_p('output/bad-dir') File.open('output/good-file.html', 'w') { |io| io.write 'stuff' } File.open('output/good-dir/blah', 'w') { |io| io.write 'stuff' } File.open('output/bad-file.html', 'w') { |io| io.write 'stuff' } File.open('output/bad-dir/blah', 'w') { |io| io.write 'stuff' } File.open('output/index.html', 'w') { |io| io.write 'stuff' } Nanoc::CLI.run %w[prune --yes] assert File.file?('output/index.html') assert File.file?('output/good-dir/blah') assert File.file?('output/good-file.html') assert !File.file?('output/bad-dir/blah') assert !File.file?('output/bad-file.html') end end def test_run_with_symlink_to_output_dir skip_unless_symlinks_supported if defined?(JRUBY_VERSION) skip 'JRuby has buggy File.find behavior (see https://github.com/jruby/jruby/issues/1647)' end with_site do |_site| # Set output dir FileUtils.rm_rf('output') FileUtils.mkdir_p('output-real') File.symlink('output-real', 'output') # Create source files File.open('content/index.html', 'w') { |io| io.write 'stuff' } # Create output files FileUtils.mkdir_p('output-real/some-dir') File.open('output-real/some-file.html', 'w') { |io| io.write 'stuff' } File.open('output-real/index.html', 'w') { |io| io.write 'stuff' } Nanoc::CLI.run %w[prune --yes] assert File.file?('output-real/index.html') assert !File.directory?('output-real/some-dir') assert !File.file?('output-real/some-file.html') end end def test_run_with_nested_empty_dirs with_site do |_site| # Set output dir File.open('nanoc.yaml', 'w') { |io| io.write 'output_dir: output' } FileUtils.mkdir_p('output') # Create output files FileUtils.mkdir_p('output/a/b/c') File.open('output/a/b/c/index.html', 'w') { |io| io.write 'stuff' } Nanoc::CLI.run %w[prune --yes] assert !File.file?('output/a/b/c/index.html') assert !File.directory?('output/a/b/c') assert !File.directory?('output/a/b') assert !File.directory?('output/a') end end end nanoc-4.8.0/test/cli/commands/test_info.rb0000644000004100000410000000023513134141051020510 0ustar www-datawww-data# frozen_string_literal: true require 'helper' class Nanoc::CLI::Commands::InfoTest < Nanoc::TestCase def test_run Nanoc::CLI.run %w[info] end end nanoc-4.8.0/test/cli/commands/test_create_site.rb0000644000004100000410000000650313134141051022050 0ustar www-datawww-data# frozen_string_literal: true require 'helper' class Nanoc::CLI::Commands::CreateSiteTest < Nanoc::TestCase def test_create_site_with_existing_name Nanoc::CLI.run %w[create_site foo] assert_raises(::Nanoc::Int::Errors::GenericTrivial) do Nanoc::CLI.run %w[create_site foo] end end def test_can_compile_new_site Nanoc::CLI.run %w[create_site foo] FileUtils.cd('foo') do site = Nanoc::Int::SiteLoader.new.new_from_cwd site.compile end end def test_can_compile_new_site_in_current_directory FileUtils.mkdir('foo') FileUtils.cd('foo') do Nanoc::CLI.run %w[create_site ./] site = Nanoc::Int::SiteLoader.new.new_from_cwd site.compile end end def test_can_compile_new_site_with_binary_items Nanoc::CLI.run %w[create_site foo] FileUtils.cd('foo') do File.open('content/blah', 'w') { |io| io << 'asdf' } site = Nanoc::Int::SiteLoader.new.new_from_cwd site.compile assert File.file?('output/blah') end end def test_can_compile_site_in_nonempty_directory FileUtils.mkdir('foo') FileUtils.touch(File.join('foo', 'SomeFile.txt')) Nanoc::CLI.run %w[create_site foo --force] FileUtils.cd('foo') do site = Nanoc::Int::SiteLoader.new.new_from_cwd site.compile end end def test_compiled_site_output FileUtils.mkdir('foo') FileUtils.touch(File.join('foo', 'SomeFile.txt')) Nanoc::CLI.run %w[create_site foo --force] FileUtils.cd('foo') do site = Nanoc::Int::SiteLoader.new.new_from_cwd site.compile assert File.file?('output/index.html') end end def test_default_encoding unless defined?(Encoding) skip 'No Encoding class' return end original_encoding = Encoding.default_external Encoding.default_external = 'ISO-8859-1' # ew! Nanoc::CLI.run %w[create_site foo] FileUtils.cd('foo') do # Try with encoding = default encoding = utf-8 File.open('content/index.html', 'w') { |io| io.write('Hello ' + 0xD6.chr + "!\n") } exception = assert_raises(Nanoc::DataSources::Filesystem::Errors::InvalidEncoding) do Nanoc::Int::SiteLoader.new.new_from_cwd end assert_equal 'Could not read content/index.html because the file is not valid UTF-8.', exception.message # Try with encoding = specific File.open('nanoc.yaml', 'w') do |io| io.write("string_pattern_type: glob\n") io.write("data_sources:\n") io.write(" -\n") io.write(" type: filesystem\n") io.write(" identifier_type: full\n") end site = Nanoc::Int::SiteLoader.new.new_from_cwd site.compile end FileUtils ensure Encoding.default_external = original_encoding end def test_new_site_has_correct_stylesheets Nanoc::CLI.run %w[create_site foo] FileUtils.cd('foo') do Nanoc::CLI.run %w[compile] assert File.file?('content/stylesheet.css') assert_match(/\/stylesheet.css/, File.read('output/index.html')) end end def test_new_site_prunes_by_default FileUtils.mkdir('foo') FileUtils.touch(File.join('foo', 'SomeFile.txt')) Nanoc::CLI.run %w[create_site foo --force] FileUtils.cd('foo') do File.write('output/blah.txt', 'stuff') Nanoc::CLI.run %w[compile] refute File.file?('output/blah.txt') end end end nanoc-4.8.0/test/cli/commands/test_help.rb0000644000004100000410000000027413134141051020510 0ustar www-datawww-data# frozen_string_literal: true require 'helper' class Nanoc::CLI::Commands::HelpTest < Nanoc::TestCase def test_run Nanoc::CLI.run %w[help] Nanoc::CLI.run %w[help co] end end nanoc-4.8.0/test/cli/test_error_handler.rb0000644000004100000410000000611713134141051020607 0ustar www-datawww-data# frozen_string_literal: true require 'helper' class Nanoc::CLI::ErrorHandlerTest < Nanoc::TestCase def setup super @handler = Nanoc::CLI::ErrorHandler.new end def test_resolution_for_with_unknown_gem error = LoadError.new('no such file to load -- afjlrestjlsgrshter') assert_nil @handler.send(:resolution_for, error) end def test_resolution_for_with_known_gem_without_bundler def @handler.using_bundler? false end error = LoadError.new('no such file to load -- kramdown') assert_match(/^Install the 'kramdown' gem using `gem install kramdown`./, @handler.send(:resolution_for, error)) end def test_resolution_for_with_known_gem_with_bundler def @handler.using_bundler? true end error = LoadError.new('no such file to load -- kramdown') assert_match(/^Make sure the gem is added to Gemfile/, @handler.send(:resolution_for, error)) end def test_resolution_for_with_not_load_error error = RuntimeError.new('nuclear meltdown detected') assert_nil @handler.send(:resolution_for, error) end def test_write_stack_trace_verbose error = new_error(20) stream = StringIO.new @handler.send(:write_stack_trace, stream, error, verbose: false) assert_match(/See full crash log for details./, stream.string) stream = StringIO.new @handler.send(:write_stack_trace, stream, error, verbose: false) assert_match(/See full crash log for details./, stream.string) stream = StringIO.new @handler.send(:write_stack_trace, stream, error, verbose: true) refute_match(/See full crash log for details./, stream.string) end def test_write_error_message_wrapped stream = StringIO.new @handler.send(:write_error_message, stream, new_wrapped_error(new_error), verbose: true) refute_match(/CompilationError/, stream.string) end def test_write_stack_trace_wrapped stream = StringIO.new @handler.send(:write_stack_trace, stream, new_wrapped_error(new_error), verbose: false) assert_match(/new_error/, stream.string) end def test_write_item_rep stream = StringIO.new @handler.send(:write_item_rep, stream, new_wrapped_error(new_error), verbose: false) assert_match(/^Item identifier: \/about\.md$/, stream.string) assert_match(/^Item rep name: :latex$/, stream.string) end def test_resolution_for_wrapped def @handler.using_bundler? true end error = new_wrapped_error(LoadError.new('no such file to load -- kramdown')) assert_match(/^Make sure the gem is added to Gemfile/, @handler.send(:resolution_for, error)) end def new_wrapped_error(wrapped) item = Nanoc::Int::Item.new('asdf', {}, '/about.md') item_rep = Nanoc::Int::ItemRep.new(item, :latex) raise Nanoc::Int::Errors::CompilationError.new(wrapped, item_rep) rescue => e return e end def new_error(amount_factor = 1) backtrace_generator = lambda do |af| if af.zero? raise 'finally!' else backtrace_generator.call(af - 1) end end begin backtrace_generator.call(amount_factor) rescue => e return e end end end nanoc-4.8.0/test/cli/test_cleaning_stream.rb0000644000004100000410000000401313134141051021105 0ustar www-datawww-data# frozen_string_literal: true require 'helper' class Nanoc::CLI::CleaningStreamTest < Nanoc::TestCase class Stream attr_accessor :called_methods def initialize @called_methods = [] end # rubocop:disable Style/MethodMissing def method_missing(symbol, *_args) @called_methods << symbol end # rubocop:enable Style/MethodMissing def respond_to_missing?(*_args) true end end def test_forward methods = %i[write << tty? tty? flush tell print puts string reopen exist? exists? close] s = Stream.new cs = Nanoc::CLI::CleaningStream.new(s) cs.write('aaa') cs << 'bb' cs.tty? cs.isatty cs.flush cs.tell cs.print('cc') cs.puts('dd') cs.string cs.reopen('/dev/null', 'r') cs.exist? cs.exists? cs.close methods.each do |m| assert s.called_methods.include?(m), "expected #{m} to be called" end end def test_forward_tty_cached s = Stream.new cs = Nanoc::CLI::CleaningStream.new(s) cs.tty? cs.isatty assert_equal [:tty?], s.called_methods end def test_works_with_logger require 'logger' stream = StringIO.new cleaning_stream = Nanoc::CLI::CleaningStream.new(stream) logger = Logger.new(cleaning_stream) logger.info('Some info') logger.warn('Something could start going wrong!') end def test_broken_pipe stream = StringIO.new def stream.write(_s) raise Errno::EPIPE.new end cleaning_stream = Nanoc::CLI::CleaningStream.new(stream) cleaning_stream.write('lol') end def test_non_string obj = Object.new def obj.to_s 'Hello… world!' end stream = StringIO.new cleaning_stream = Nanoc::CLI::CleaningStream.new(stream) cleaning_stream << obj assert_equal 'Hello… world!', stream.string end def test_invalid_string s = "\x80" stream = StringIO.new cleaning_stream = Nanoc::CLI::CleaningStream.new(stream) cleaning_stream << s assert_equal "\xef\xbf\xbd", stream.string end end nanoc-4.8.0/test/cli/test_logger.rb0000644000004100000410000000017113134141051017232 0ustar www-datawww-data# frozen_string_literal: true require 'helper' class Nanoc::CLI::LoggerTest < Nanoc::TestCase def test_stub; end end nanoc-4.8.0/test/cli/test_cli.rb0000644000004100000410000001251013134141051016522 0ustar www-datawww-data# frozen_string_literal: true require 'helper' class Nanoc::CLITest < Nanoc::TestCase COMMAND_CODE = <<~EOS usage '_test [options]' summary 'meh' description 'longer meh' run do |opts, args, cmd| File.open('_test.out', 'w') { |io| io.write('It works!') } end EOS SUBCOMMAND_CODE = <<~EOS usage '_sub [options]' summary 'meh sub' description 'longer meh sub' run do |opts, args, cmd| File.open('_test_sub.out', 'w') { |io| io.write('It works sub!') } end EOS def test_load_custom_commands Nanoc::CLI.run %w[create_site foo] FileUtils.cd('foo') do # Create command FileUtils.mkdir_p('commands') File.open('commands/_test.rb', 'w') { |io| io.write(COMMAND_CODE) } # Run command begin Nanoc::CLI.run %w[_test] rescue SystemExit assert false, 'Running _test should not cause system exit' end # Check assert File.file?('_test.out') assert_equal 'It works!', File.read('_test.out') end end def test_load_custom_commands_nested Nanoc::CLI.run %w[create_site foo] FileUtils.cd('foo') do # Create command FileUtils.mkdir_p('commands') File.open('commands/_test.rb', 'w') do |io| io.write(COMMAND_CODE) end # Create subcommand FileUtils.mkdir_p('commands/_test') File.open('commands/_test/_sub.rb', 'w') do |io| io.write(SUBCOMMAND_CODE) end # Run command begin Nanoc::CLI.run %w[_test _sub] rescue SystemExit assert false, 'Running _test sub should not cause system exit' end # Check assert File.file?('_test_sub.out') assert_equal 'It works sub!', File.read('_test_sub.out') end end def test_load_custom_commands_non_default_commands_dirs Nanoc::CLI.run %w[create_site foo] FileUtils.cd('foo') do File.open('nanoc.yaml', 'w') { |io| io.write('commands_dirs: [commands, commands_alt]') } # Create command FileUtils.mkdir_p('commands_alt') File.open('commands_alt/_test.rb', 'w') do |io| io.write(COMMAND_CODE) end # Create subcommand FileUtils.mkdir_p('commands_alt/_test') File.open('commands_alt/_test/_sub.rb', 'w') do |io| io.write(SUBCOMMAND_CODE) end # Run command begin Nanoc::CLI.run %w[_test _sub] rescue SystemExit assert false, 'Running _test sub should not cause system exit' end # Check assert File.file?('_test_sub.out') assert_equal 'It works sub!', File.read('_test_sub.out') end end def test_load_custom_commands_broken Nanoc::CLI.run %w[create_site foo] FileUtils.cd('foo') do # Create command FileUtils.mkdir_p('commands') File.open('commands/_test.rb', 'w') { |io| io.write('raise "meh"') } # Run command position_before = $stderr.tell Nanoc::CLI::ErrorHandler.disable assert_raises RuntimeError do Nanoc::CLI.run %w[_test] end Nanoc::CLI::ErrorHandler.enable assert_raises SystemExit do Nanoc::CLI.run %w[_test] end position_after = $stderr.tell # Check error output stderr_addition = $stderr.string[position_before, position_after] assert_match(/Stack trace:/, stderr_addition) assert_match(/commands\/_test.rb/, stderr_addition) end end def test_load_command_at_with_non_utf8_encoding Encoding.default_external = Encoding::US_ASCII Nanoc::CLI.load_command_at(root_dir + '/lib/nanoc/cli/commands/create-site.rb') ensure Encoding.default_external = Encoding::UTF_8 end def test_after_setup $after_setup_success = false Nanoc::CLI.after_setup do $after_setup_success = true end Nanoc::CLI.setup assert $after_setup_success end def test_enable_utf8_only_on_tty new_env_diff = { 'LC_ALL' => 'en_US.ISO-8859-1', 'LC_CTYPE' => 'en_US.ISO-8859-1', 'LANG' => 'en_US.ISO-8859-1', } with_env_vars(new_env_diff) do io = StringIO.new def io.tty? true end refute Nanoc::CLI.enable_utf8?(io) io = StringIO.new def io.tty? false end assert Nanoc::CLI.enable_utf8?(io) end end def test_enable_utf8 io = StringIO.new def io.tty? true end new_env_diff = { 'LC_ALL' => 'en_US.ISO-8859-1', 'LC_CTYPE' => 'en_US.ISO-8859-1', 'LANG' => 'en_US.ISO-8859-1', } with_env_vars(new_env_diff) do refute Nanoc::CLI.enable_utf8?(io) with_env_vars('LC_ALL' => 'en_US.UTF-8') { assert Nanoc::CLI.enable_utf8?(io) } with_env_vars('LC_CTYPE' => 'en_US.UTF-8') { assert Nanoc::CLI.enable_utf8?(io) } with_env_vars('LANG' => 'en_US.UTF-8') { assert Nanoc::CLI.enable_utf8?(io) } with_env_vars('LC_ALL' => 'en_US.utf-8') { assert Nanoc::CLI.enable_utf8?(io) } with_env_vars('LC_CTYPE' => 'en_US.utf-8') { assert Nanoc::CLI.enable_utf8?(io) } with_env_vars('LANG' => 'en_US.utf-8') { assert Nanoc::CLI.enable_utf8?(io) } with_env_vars('LC_ALL' => 'en_US.utf8') { assert Nanoc::CLI.enable_utf8?(io) } with_env_vars('LC_CTYPE' => 'en_US.utf8') { assert Nanoc::CLI.enable_utf8?(io) } with_env_vars('LANG' => 'en_US.utf8') { assert Nanoc::CLI.enable_utf8?(io) } end end end nanoc-4.8.0/test/helpers/0000755000004100000410000000000013134141051015263 5ustar www-datawww-datananoc-4.8.0/test/helpers/test_xml_sitemap.rb0000644000004100000410000002424113134141051021174 0ustar www-datawww-data# frozen_string_literal: true require 'helper' class Nanoc::Helpers::XMLSitemapTest < Nanoc::TestCase include Nanoc::Helpers::XMLSitemap def setup super config = Nanoc::Int::Configuration.new.with_defaults items = Nanoc::Int::ItemCollection.new(config) layouts = Nanoc::Int::LayoutCollection.new(config) dep_store = Nanoc::Int::DependencyStore.new(items, layouts, config) dependency_tracker = Nanoc::Int::DependencyTracker.new(dep_store) @reps = Nanoc::Int::ItemRepRepo.new @view_context = Nanoc::ViewContext.new( reps: @reps, items: nil, dependency_tracker: dependency_tracker, compilation_context: :__irrelevant__, snapshot_repo: :__irrelevant_snapshot_repo, ) @items = nil @item = nil @site = nil @config = nil end def test_xml_sitemap if_have 'builder', 'nokogiri' do # Create items items = [] # Create item 1 item = Nanoc::ItemWithRepsView.new(Nanoc::Int::Item.new('some content 1', {}, '/item-one/'), @view_context) items << item create_item_rep(item.unwrap, :one_a, '/item-one/a/') create_item_rep(item.unwrap, :one_b, '/item-one/b/') # Create item 2 item = Nanoc::ItemWithRepsView.new(Nanoc::Int::Item.new('some content 2', { is_hidden: true }, '/item-two/'), @view_context) items << item # Create item 3 attrs = { mtime: Time.parse('2004-07-12 00:00:00 +02:00'), changefreq: 'daily', priority: 0.5 } item = Nanoc::ItemWithRepsView.new(Nanoc::Int::Item.new('some content 3', attrs, '/item-three/'), @view_context) items << item create_item_rep(item.unwrap, :three_a, '/item-three/a/') create_item_rep(item.unwrap, :three_b, '/item-three/b/') # Create item 4 item = Nanoc::ItemWithRepsView.new(Nanoc::Int::Item.new('some content 4', {}, '/item-four/'), @view_context) items << item create_item_rep(item.unwrap, :four_a, nil) # Create items @items = Nanoc::Int::ItemCollection.new({}, items) # Create sitemap item @item = Nanoc::ItemWithRepsView.new(Nanoc::Int::Item.new('sitemap content', {}, '/sitemap/'), @view_context) # Create site config = Nanoc::Int::Configuration.new(hash: { base_url: 'http://example.com' }) @config = Nanoc::ConfigView.new(config, @view_context) # Build sitemap res = xml_sitemap # Check doc = Nokogiri::XML(res) urlsets = doc.css('> urlset') assert_equal 1, urlsets.size urls = urlsets.css('> url') assert_equal 4, urls.size assert_equal 'http://example.com/item-one/a/', urls[0].css('> loc').inner_text assert_equal 'http://example.com/item-one/b/', urls[1].css('> loc').inner_text assert_equal 'http://example.com/item-three/a/', urls[2].css('> loc').inner_text assert_equal 'http://example.com/item-three/b/', urls[3].css('> loc').inner_text assert_equal '', urls[0].css('> changefreq').inner_text assert_equal '', urls[1].css('> changefreq').inner_text assert_equal 'daily', urls[2].css('> changefreq').inner_text assert_equal 'daily', urls[3].css('> changefreq').inner_text assert_equal '', urls[0].css('> priority').inner_text assert_equal '', urls[1].css('> priority').inner_text assert_equal '0.5', urls[2].css('> priority').inner_text assert_equal '0.5', urls[3].css('> priority').inner_text assert_equal '', urls[0].css('> lastmod').inner_text assert_equal '', urls[1].css('> lastmod').inner_text assert_equal '2004-07-11', urls[2].css('> lastmod').inner_text assert_equal '2004-07-11', urls[3].css('> lastmod').inner_text end end def test_sitemap_with_items_as_param if_have 'builder', 'nokogiri' do # Create items items = [] items << nil item = Nanoc::ItemWithRepsView.new(Nanoc::Int::Item.new('some content 1', {}, '/item-one/'), @view_context) items << item create_item_rep(item.unwrap, :one_a, '/item-one/a/') create_item_rep(item.unwrap, :one_b, '/item-one/b/') items << nil @items = Nanoc::Int::ItemCollection.new({}) # Create sitemap item @item = Nanoc::Int::Item.new('sitemap content', {}, '/sitemap/') # Create site config = Nanoc::Int::Configuration.new(hash: { base_url: 'http://example.com' }) @config = Nanoc::ConfigView.new(config, @view_context) # Build sitemap res = xml_sitemap(items: [item]) # Check doc = Nokogiri::XML(res) urlsets = doc.css('> urlset') assert_equal 1, urlsets.size urls = urlsets.css('> url') assert_equal 2, urls.size assert_equal 'http://example.com/item-one/a/', urls[0].css('> loc').inner_text assert_equal 'http://example.com/item-one/b/', urls[1].css('> loc').inner_text assert_equal '', urls[0].css('> changefreq').inner_text assert_equal '', urls[1].css('> changefreq').inner_text assert_equal '', urls[0].css('> priority').inner_text assert_equal '', urls[1].css('> priority').inner_text assert_equal '', urls[0].css('> lastmod').inner_text assert_equal '', urls[1].css('> lastmod').inner_text end end def test_filter if_have 'builder', 'nokogiri' do # Create items item = Nanoc::ItemWithRepsView.new(Nanoc::Int::Item.new('some content 1', {}, '/item-one/'), @view_context) @items = Nanoc::Int::ItemCollection.new({}, [item]) create_item_rep(item.unwrap, :one_a, '/item-one/a/') create_item_rep(item.unwrap, :one_b, '/item-one/b/') # Create sitemap item @item = Nanoc::ItemWithRepsView.new(Nanoc::Int::Item.new('sitemap content', {}, '/sitemap/'), @view_context) # Create site config = Nanoc::Int::Configuration.new(hash: { base_url: 'http://example.com' }) @config = Nanoc::ConfigView.new(config, @view_context) # Build sitemap res = xml_sitemap(rep_select: ->(rep) { rep.name == :one_a }) # Check doc = Nokogiri::XML(res) urlsets = doc.css('> urlset') assert_equal 1, urlsets.size urls = urlsets.css('> url') assert_equal 1, urls.size assert_equal 'http://example.com/item-one/a/', urls[0].css('> loc').inner_text assert_equal '', urls[0].css('> changefreq').inner_text assert_equal '', urls[0].css('> priority').inner_text assert_equal '', urls[0].css('> lastmod').inner_text end end def test_sorted if_have 'builder', 'nokogiri' do # Create items items = [] item = Nanoc::ItemWithRepsView.new(Nanoc::Int::Item.new('some content 1', {}, '/george/'), @view_context) items << item create_item_rep(item.unwrap, :a_alice, '/george/alice/') create_item_rep(item.unwrap, :b_zoey, '/george/zoey/') item = Nanoc::ItemWithRepsView.new(Nanoc::Int::Item.new('some content 1', {}, '/walton/'), @view_context) items << item create_item_rep(item.unwrap, :a_eve, '/walton/eve/') create_item_rep(item.unwrap, :b_bob, '/walton/bob/') item = Nanoc::ItemWithRepsView.new(Nanoc::Int::Item.new('some content 1', {}, '/lucas/'), @view_context) items << item create_item_rep(item.unwrap, :a_trudy, '/lucas/trudy/') create_item_rep(item.unwrap, :b_mallory, '/lucas/mallory/') @items = Nanoc::Int::ItemCollection.new({}, items) # Create sitemap item @item = Nanoc::ItemWithRepsView.new(Nanoc::Int::Item.new('sitemap content', {}, '/sitemap/'), @view_context) # Create site config = Nanoc::Int::Configuration.new(hash: { base_url: 'http://example.com' }) @config = Nanoc::ConfigView.new(config, @view_context) # Build sitemap res = xml_sitemap(items: @items) # Check doc = Nokogiri::XML(res) urlsets = doc.css('> urlset') assert_equal 1, urlsets.size urls = urlsets.css('> url') assert_equal 6, urls.size assert_equal 'http://example.com/george/alice/', urls[0].css('> loc').inner_text assert_equal 'http://example.com/george/zoey/', urls[1].css('> loc').inner_text assert_equal 'http://example.com/lucas/trudy/', urls[2].css('> loc').inner_text assert_equal 'http://example.com/lucas/mallory/', urls[3].css('> loc').inner_text assert_equal 'http://example.com/walton/eve/', urls[4].css('> loc').inner_text assert_equal 'http://example.com/walton/bob/', urls[5].css('> loc').inner_text end end def test_url_escape if_have 'builder', 'nokogiri' do # Create items item = Nanoc::ItemWithRepsView.new(Nanoc::Int::Item.new('some content 1', {}, '/george/'), @view_context) @items = Nanoc::Int::ItemCollection.new({}, [item]) create_item_rep(item.unwrap, :default, '/cool projects/проверка') # Create sitemap item @item = Nanoc::ItemWithRepsView.new(Nanoc::Int::Item.new('sitemap content', {}, '/sitemap/'), @view_context) # Create site config = Nanoc::Int::Configuration.new(hash: { base_url: 'http://example.com' }) @config = Nanoc::ConfigView.new(config, @view_context) # Build sitemap res = xml_sitemap(items: @items) # Check doc = Nokogiri::XML(res) urlsets = doc.css('> urlset') assert_equal 1, urlsets.size urls = urlsets.css('> url') assert_equal 1, urls.size assert_equal 'http://example.com/cool%20projects/%D0%BF%D1%80%D0%BE%D0%B2%D0%B5%D1%80%D0%BA%D0%B0', urls[0].css('> loc').inner_text end end protected def create_item_rep(item, name, path) rep = Nanoc::Int::ItemRep.new(item, name) rep.paths = { last: (path ? [path] : []) } rep.raw_paths = { last: (path ? [path] : []) } @reps << rep rep end end nanoc-4.8.0/test/helpers/test_blogging.rb0000644000004100000410000005573513134141051020456 0ustar www-datawww-data# frozen_string_literal: true require 'helper' class Nanoc::Helpers::BloggingTest < Nanoc::TestCase include Nanoc::Helpers::Blogging include Nanoc::Helpers::Text def mock_article item = mock item.stubs(:[]).with(:updated_at).returns(Time.now - 500) item.stubs(:[]).with(:kind).returns('article') item.stubs(:[]).with(:created_at).returns(Time.now - 1000) item.stubs(:[]).with(:title).returns('An Item') item.stubs(:[]).with(:custom_path_in_feed).returns(nil) item.stubs(:[]).with(:custom_url_in_feed).returns(nil) item.stubs(:[]).with(:excerpt).returns(nil) item.stubs(:path).returns('/item/') item.stubs(:[]).with(:author_name).returns(nil) item.stubs(:[]).with(:author_uri).returns(nil) item.stubs(:compiled_content).returns('item content') item end def mock_item item = mock item.stubs(:[]).with(:kind).returns('item') item end def setup super config = Nanoc::Int::Configuration.new.with_defaults items = Nanoc::Int::ItemCollection.new(config) layouts = Nanoc::Int::LayoutCollection.new(config) dep_store = Nanoc::Int::DependencyStore.new(items, layouts, config) dependency_tracker = Nanoc::Int::DependencyTracker.new(dep_store) @view_context = Nanoc::ViewContext.new( reps: :__irrelevant__, items: nil, dependency_tracker: dependency_tracker, compilation_context: :__irrelevant__, snapshot_repo: :__irrelevant_snapshot_repo, ) end def test_atom_feed if_have 'builder' do # Create items @items = [mock, mock_article, mock_article] # Create item 0 @items[0].stubs(:[]).with(:kind).returns('item') # Create item 1 @items[1].stubs(:[]).with(:updated_at).returns(Date.today - 1) @items[1].stubs(:[]).with(:kind).returns('article') @items[1].stubs(:[]).with(:created_at).returns((Date.today - 2).to_s) @items[1].stubs(:[]).with(:title).returns('Item One') @items[1].stubs(:[]).with(:custom_path_in_feed).returns(nil) @items[1].stubs(:[]).with(:custom_url_in_feed).returns(nil) @items[1].stubs(:[]).with(:excerpt).returns(nil) @items[1].stubs(:path).returns('/item1/') @items[1].expects(:compiled_content).with(snapshot: :pre).returns('item 1 content') # Create item 2 @items[2].expects(:compiled_content).with(snapshot: :pre).returns('item 2 content') # Mock site config = Nanoc::Int::Configuration.new(hash: { base_url: 'http://example.com' }) @config = Nanoc::ConfigView.new(config, @view_context) # Create feed item @item = mock @item.stubs(:[]).with(:title).returns('My Cool Blog') @item.stubs(:[]).with(:author_name).returns('Denis Defreyne') @item.stubs(:[]).with(:author_uri).returns('http://stoneship.org/') @item.stubs(:[]).with(:feed_url).returns(nil) @item.stubs(:path).returns('/journal/feed/') # Check atom_feed end end def test_atom_feed_with_times if_have 'builder' do # Create items @items = [mock_item, mock_article, mock_article] # Create item 1 @items[1].stubs(:[]).with(:updated_at).returns(Time.now - 500) @items[1].stubs(:[]).with(:created_at).returns(Time.now - 1000) @items[1].expects(:compiled_content).returns('item 1 content') # Create item 2 @items[2].stubs(:[]).with(:updated_at).returns(Time.now - 250) @items[2].stubs(:[]).with(:created_at).returns(Time.now - 1200) @items[2].expects(:compiled_content).returns('item 2 content') # Mock site config = Nanoc::Int::Configuration.new(hash: { base_url: 'http://example.com' }) @config = Nanoc::ConfigView.new(config, @view_context) # Create feed item @item = mock @item.stubs(:[]).with(:title).returns('My Cool Blog') @item.stubs(:[]).with(:author_name).returns('Denis Defreyne') @item.stubs(:[]).with(:author_uri).returns('http://stoneship.org/') @item.stubs(:[]).with(:feed_url).returns(nil) @item.stubs(:path).returns('/journal/feed/') # Check atom_feed end end def test_atom_feed_updated_is_most_recent if_have 'builder' do # Create items @items = [mock_item, mock_article, mock_article] # Create item 1 @items[1].stubs(:[]).with(:updated_at).returns(nil) @items[1].stubs(:[]).with(:created_at).returns(Time.parse('2016-12-01 17:20:00 +00:00')) @items[1].expects(:compiled_content).returns('item 1 content') # Create item 2 @items[2].stubs(:[]).with(:updated_at).returns(nil) @items[2].stubs(:[]).with(:created_at).returns(Time.parse('2016-12-01 18:40:00 +00:00')) @items[2].expects(:compiled_content).returns('item 2 content') # Mock site config = Nanoc::Int::Configuration.new(hash: { base_url: 'http://example.com' }) @config = Nanoc::ConfigView.new(config, @view_context) # Create feed item @item = mock @item.stubs(:[]).with(:title).returns('My Cool Blog') @item.stubs(:[]).with(:author_name).returns('Denis Defreyne') @item.stubs(:[]).with(:author_uri).returns('http://stoneship.org/') @item.stubs(:[]).with(:feed_url).returns(nil) @item.stubs(:path).returns('/journal/feed/') # Check assert_match(%r{My Cool Blog\n 2016-12-01T18:40:00Z}, atom_feed) end end def test_atom_feed_updated_is_most_recent_updated_at if_have 'builder' do # Create items @items = [mock_item, mock_article, mock_article] # Create item 1 @items[1].stubs(:[]).with(:updated_at).returns(Time.parse('2016-12-01 19:20:00 +00:00')) @items[1].stubs(:[]).with(:created_at).returns(Time.parse('2016-12-01 17:20:00 +00:00')) @items[1].expects(:compiled_content).returns('item 1 content') # Create item 2 @items[2].stubs(:[]).with(:updated_at).returns(Time.parse('2016-12-01 20:40:00 +00:00')) @items[2].stubs(:[]).with(:created_at).returns(Time.parse('2016-12-01 18:40:00 +00:00')) @items[2].expects(:compiled_content).returns('item 2 content') # Mock site config = Nanoc::Int::Configuration.new(hash: { base_url: 'http://example.com' }) @config = Nanoc::ConfigView.new(config, @view_context) # Create feed item @item = mock @item.stubs(:[]).with(:title).returns('My Cool Blog') @item.stubs(:[]).with(:author_name).returns('Denis Defreyne') @item.stubs(:[]).with(:author_uri).returns('http://stoneship.org/') @item.stubs(:[]).with(:feed_url).returns(nil) @item.stubs(:path).returns('/journal/feed/') # Check assert_match(%r{My Cool Blog\n 2016-12-01T20:40:00Z}, atom_feed) end end def test_atom_feed_without_articles if_have 'builder' do # Mock items @items = [mock_item, mock_item] # Mock site config = Nanoc::Int::Configuration.new(hash: { base_url: 'http://example.com' }) @config = Nanoc::ConfigView.new(config, @view_context) # Create feed item @item = mock @item.stubs(:[]).with(:title).returns('My Blog Or Something') @item.stubs(:[]).with(:author_name).returns('J. Doe') @item.stubs(:[]).with(:author_uri).returns('http://example.com/~jdoe') # Check error = assert_raises(Nanoc::Int::Errors::GenericTrivial) do atom_feed end assert_equal( 'Cannot build Atom feed: no articles', error.message, ) end end def test_atom_feed_without_base_url if_have 'builder' do # Create items @items = [mock_item, mock_article] # Mock site config = Nanoc::Int::Configuration.new(hash: { base_url: nil }) @config = Nanoc::ConfigView.new(config, @view_context) # Create feed item @item = mock @item.stubs(:[]).with(:title).returns('My Blog Or Something') @item.stubs(:[]).with(:author_name).returns('J. Doe') @item.stubs(:[]).with(:author_uri).returns('http://example.com/~jdoe') # Check error = assert_raises(Nanoc::Int::Errors::GenericTrivial) do atom_feed end assert_equal( 'Cannot build Atom feed: site configuration has no base_url', error.message, ) end end def test_atom_feed_without_title if_have 'builder' do # Create items @items = [mock_item, mock_article] # Mock site config = Nanoc::Int::Configuration.new(hash: { base_url: 'http://example.com' }) @config = Nanoc::ConfigView.new(config, @view_context) # Create feed item @item = mock @item.stubs(:[]).with(:title).returns(nil) @item.stubs(:[]).with(:author_name).returns('J. Doe') @item.stubs(:[]).with(:author_uri).returns('http://example.com/~jdoe') # Check error = assert_raises(Nanoc::Int::Errors::GenericTrivial) do atom_feed end assert_equal( 'Cannot build Atom feed: no title in params, item or site config', error.message, ) end end def test_atom_feed_without_author_name if_have 'builder' do # Create items @items = [mock_item, mock_article] # Mock site config = Nanoc::Int::Configuration.new(hash: { base_url: 'http://example.com' }) @config = Nanoc::ConfigView.new(config, @view_context) # Create feed item @item = mock @item.stubs(:[]).with(:title).returns('My Blog Or Something') @item.stubs(:[]).with(:author_name).returns(nil) @item.stubs(:[]).with(:author_uri).returns('http://example.com/~jdoe') # Check error = assert_raises(Nanoc::Int::Errors::GenericTrivial) do atom_feed end assert_equal( 'Cannot build Atom feed: no author_name in params, item or site config', error.message, ) end end def test_atom_feed_with_author_name_and_uri_from_content_item if_have 'builder' do # Create items @items = [mock_article] # Create item 1 @items[0].stubs(:[]).with(:author_name).returns('Don Alias') @items[0].stubs(:[]).with(:author_uri).returns('http://don.example.com/') @items[0].expects(:compiled_content).returns('item 1 content') # Mock site config = Nanoc::Int::Configuration.new(hash: { base_url: 'http://example.com/' }) @config = Nanoc::ConfigView.new(config, @view_context) # Create feed item @item = mock @item.stubs(:[]).with(:kind).returns(nil) @item.stubs(:[]).with(:title).returns('My Cool Blog') @item.stubs(:[]).with(:author_name).returns('Denis Defreyne') @item.stubs(:[]).with(:author_uri).returns('http://stoneship.org/') @item.stubs(:[]).with(:feed_url).returns(nil) @item.stubs(:path).returns('/journal/feed/') # Check # TODO: Use xpath matchers for more specific test result = atom_feed # Still should keep feed level author assert_match( /#{Regexp.escape('Denis Defreyne')}/, #' result, ) assert_match( /#{Regexp.escape('http://stoneship.org/')}/, #' result, ) # Overrides on specific items assert_match( /#{Regexp.escape('Don Alias')}/, #' result, ) assert_match( /#{Regexp.escape('http://don.example.com/')}/, #' result, ) end end def test_atom_feed_without_author_uri if_have 'builder' do # Create items @items = [mock_item, mock_article] # Mock site config = Nanoc::Int::Configuration.new(hash: { base_url: 'http://example.com' }) @config = Nanoc::ConfigView.new(config, @view_context) # Create feed item @item = mock @item.stubs(:[]).with(:title).returns('My Blog Or Something') @item.stubs(:[]).with(:author_name).returns('J. Doe') @item.stubs(:[]).with(:author_uri).returns(nil) # Check error = assert_raises(Nanoc::Int::Errors::GenericTrivial) do atom_feed end assert_equal( 'Cannot build Atom feed: no author_uri in params, item or site config', error.message, ) end end def test_atom_feed_without_articles_created_at if_have 'builder' do # Create items @items = [mock_item, mock_article, mock_article] @items[1].stubs(:[]).with(:created_at).returns(Time.now.to_s) @items[2].stubs(:[]).with(:created_at).returns(nil) # Mock site config = Nanoc::Int::Configuration.new(hash: { base_url: 'http://example.com' }) @config = Nanoc::ConfigView.new(config, @view_context) # Create feed item @item = mock @item.stubs(:[]).with(:title).returns('My Blog Or Something') @item.stubs(:[]).with(:author_name).returns('J. Doe') @item.stubs(:[]).with(:author_uri).returns('http://example.com/~jdoe') # Check error = assert_raises(Nanoc::Int::Errors::GenericTrivial) do atom_feed end assert_equal( 'Cannot build Atom feed: one or more articles lack created_at', error.message, ) end end def test_atom_feed_with_title_author_name_and_uri_as_params if_have 'builder' do # Create items @items = [mock_item, mock_article] @items[1].expects(:compiled_content).with(snapshot: :pre).returns('asdf') # Mock site config = Nanoc::Int::Configuration.new(hash: { base_url: 'http://example.com' }) @config = Nanoc::ConfigView.new(config, @view_context) # Create feed item @item = mock @item.stubs(:[]).with(:title).returns(nil) @item.stubs(:[]).with(:author_name).returns(nil) @item.stubs(:[]).with(:author_uri).returns(nil) @item.stubs(:[]).with(:[]).with(:feed_url).returns('http://example.com/feed') # Check atom_feed( author_name: 'Bob', author_uri: 'http://example.com/~bob/', title: 'My Blog Or Something', ) end end def test_atom_feed_with_title_author_name_and_uri_from_config if_have 'builder' do # Create items @items = [mock_item, mock_article] @items[1].expects(:compiled_content).with(snapshot: :pre).returns('asdf') # Mock site config_hash = { author_name: 'Bob', author_uri: 'http://example.com/~bob/', title: 'My Blog Or Something', base_url: 'http://example.com', } config = Nanoc::Int::Configuration.new(hash: config_hash) @config = Nanoc::ConfigView.new(config, @view_context) # Create feed item @item = mock @item.stubs(:[]).with(:title).returns(nil) @item.stubs(:[]).with(:author_name).returns(nil) @item.stubs(:[]).with(:author_uri).returns(nil) @item.stubs(:[]).with(:[]).with(:feed_url).returns('http://example.com/feed') # Check atom_feed end end def test_atom_feed_with_articles_param if_have 'builder' do # Mock items @items = [mock_article, mock_article] @items[0].expects(:compiled_content).never @items[1].stubs(:[]).with(:title).returns('Item One') @items[1].expects(:compiled_content).with(snapshot: :pre).returns('asdf') # Mock site config = Nanoc::Int::Configuration.new(hash: { base_url: 'http://example.com' }) @config = Nanoc::ConfigView.new(config, @view_context) # Create feed item @item = mock @item.stubs(:[]).with(:title).returns('My Blog Or Something') @item.stubs(:[]).with(:author_name).returns('J. Doe') @item.stubs(:[]).with(:author_uri).returns('http://example.com/~jdoe') @item.stubs(:[]).with(:[]).with(:feed_url).returns('http://example.com/feed') # Check atom_feed articles: [@items[1]] end end def test_atom_feed_with_limit_param if_have 'builder' do # Mock articles @items = [mock_article, mock_article] @items.each_with_index do |article, i| article.stubs(:[]).with(:title).returns("Article #{i}") article.stubs(:[]).with(:created_at).returns(Time.now - i) end # Mock site config = Nanoc::Int::Configuration.new(hash: { base_url: 'http://example.com' }) @config = Nanoc::ConfigView.new(config, @view_context) # Create feed item @item = mock @item.stubs(:[]).with(:title).returns('My Blog Or Something') @item.stubs(:[]).with(:author_name).returns('J. Doe') @item.stubs(:[]).with(:author_uri).returns('http://example.com/~jdoe') @item.stubs(:[]).with(:feed_url).returns('http://example.com/feed') # Check result = atom_feed limit: 1, articles: @items assert_match( Regexp.new('Article 0', Regexp::MULTILINE), result, ) refute_match( Regexp.new('Article 1', Regexp::MULTILINE), result, ) end end def test_atom_feed_sorting if_have 'builder' do # Mock articles @items = [mock_article, mock_article] @items.each_with_index do |article, i| article.stubs(:[]).with(:title).returns("Article #{i}") end @items[0].stubs(:[]).with(:created_at).returns('23-02-2009') @items[1].stubs(:[]).with(:created_at).returns('22-03-2009') # Mock site config = Nanoc::Int::Configuration.new(hash: { base_url: 'http://example.com' }) @config = Nanoc::ConfigView.new(config, @view_context) # Create feed item @item = mock @item.stubs(:[]).with(:title).returns('My Blog Or Something') @item.stubs(:[]).with(:author_name).returns('J. Doe') @item.stubs(:[]).with(:author_uri).returns('http://example.com/~jdoe') @item.stubs(:[]).with(:feed_url).returns('http://example.com/feed') # Check result = atom_feed assert_match( Regexp.new('Article 1.*Article 0', Regexp::MULTILINE), result, ) end end def test_atom_feed_preserve_order if_have 'builder' do # Mock articles @items = [mock_article, mock_article] @items.each_with_index do |article, i| article.stubs(:[]).with(:title).returns("Article #{i}") end @items[0].stubs(:[]).with(:created_at).returns('01-01-2015') @items[1].stubs(:[]).with(:created_at).returns('01-01-2014') # Mock site config = Nanoc::Int::Configuration.new(hash: { base_url: 'http://example.com' }) @config = Nanoc::ConfigView.new(config, @view_context) # Create feed item @item = mock @item.stubs(:[]).with(:title).returns('My Blog Or Something') @item.stubs(:[]).with(:author_name).returns('J. Doe') @item.stubs(:[]).with(:author_uri).returns('http://example.com/~jdoe') @item.stubs(:[]).with(:feed_url).returns('http://example.com/feed') # Check result = atom_feed(preserve_order: true) assert_match( Regexp.new('Article 1.*Article 0', Regexp::MULTILINE), result, ) end end def test_atom_feed_with_content_proc_param if_have 'builder' do # Mock article @items = [mock_article] # Mock site config = Nanoc::Int::Configuration.new(hash: { base_url: 'http://example.com' }) @config = Nanoc::ConfigView.new(config, @view_context) # Create feed item @item = mock @item.stubs(:[]).with(:title).returns('My Blog Or Something') @item.stubs(:[]).with(:author_name).returns('J. Doe') @item.stubs(:[]).with(:author_uri).returns('http://example.com/~jdoe') @item.stubs(:[]).with(:feed_url).returns('http://example.com/feed') # Check result = atom_feed content_proc: ->(_a) { 'foobar!' } assert_match 'foobar!', result end end def test_atom_feed_with_excerpt_proc_param if_have 'builder' do # Mock article @items = [mock_article] # Mock site config = Nanoc::Int::Configuration.new(hash: { base_url: 'http://example.com' }) @config = Nanoc::ConfigView.new(config, @view_context) # Create feed item @item = mock @item.stubs(:[]).with(:title).returns('My Blog Or Something') @item.stubs(:[]).with(:author_name).returns('J. Doe') @item.stubs(:[]).with(:author_uri).returns('http://example.com/~jdoe') @item.stubs(:[]).with(:[]).with(:feed_url).returns('http://example.com/feed') # Check result = atom_feed excerpt_proc: ->(_a) { 'foobar!' } assert_match 'foobar!', result end end def test_atom_feed_with_icon_param if_have 'builder' do # Mock article @items = [mock_article] # Mock site config = Nanoc::Int::Configuration.new(hash: { base_url: 'http://example.com' }) @config = Nanoc::ConfigView.new(config, @view_context) # Create feed item @item = mock @item.stubs(:[]).with(:title).returns('My Blog Or Something') @item.stubs(:[]).with(:author_name).returns('J. Doe') @item.stubs(:[]).with(:author_uri).returns('http://example.com/~jdoe') @item.stubs(:[]).with(:feed_url).returns('http://example.com/feed') # Check result = atom_feed icon: 'http://example.com/icon.png' assert_match 'http://example.com/icon.png', result end end def test_atom_feed_with_logo_param if_have 'builder' do # Mock article @items = [mock_article] # Mock site config = Nanoc::Int::Configuration.new(hash: { base_url: 'http://example.com' }) @config = Nanoc::ConfigView.new(config, @view_context) # Create feed item @item = mock @item.stubs(:[]).with(:title).returns('My Blog Or Something') @item.stubs(:[]).with(:author_name).returns('J. Doe') @item.stubs(:[]).with(:author_uri).returns('http://example.com/~jdoe') @item.stubs(:[]).with(:feed_url).returns('http://example.com/feed') # Check result = atom_feed logo: 'http://example.com/logo.png' assert_match 'http://example.com/logo.png', result end end def test_atom_feed_with_xml_base if_have 'builder' do # Mock article @items = [mock_article] # Mock site config = Nanoc::Int::Configuration.new(hash: { base_url: 'http://example.com' }) @config = Nanoc::ConfigView.new(config, @view_context) # Create feed item @item = mock @item.stubs(:[]).with(:title).returns('My Blog Or Something') @item.stubs(:[]).with(:author_name).returns('J. Doe') @item.stubs(:[]).with(:author_uri).returns('http://example.com/~jdoe') @item.stubs(:[]).with(:feed_url).returns('http://example.com/feed') # Check result = atom_feed assert_match 'xml:base="http://example.com/"', result end end def test_atom_feed_with_item_without_path if_have 'builder' do # Create items @items = [mock_article] @items[0].stubs(:path).returns(nil) # Mock site config = Nanoc::Int::Configuration.new(hash: { base_url: 'http://example.com' }) @config = Nanoc::ConfigView.new(config, @view_context) # Create feed item @item = mock @item.stubs(:identifier).returns('/feed/') @item.stubs(:[]).with(:title).returns('My Cool Blog') @item.stubs(:[]).with(:author_name).returns('Denis Defreyne') @item.stubs(:[]).with(:author_uri).returns('http://stoneship.org/') @item.stubs(:[]).with(:feed_url).returns(nil) @item.stubs(:path).returns('/journal/feed/') # Check atom_feed end end end nanoc-4.8.0/test/helpers/test_capturing.rb0000644000004100000410000001316713134141051020653 0ustar www-datawww-data# frozen_string_literal: true require 'helper' class Nanoc::Helpers::CapturingTest < Nanoc::TestCase include Nanoc::Helpers::Capturing def item_rep_repo_for(item) Nanoc::Int::ItemRepRepo.new.tap do |repo| repo << Nanoc::Int::ItemRep.new(item, :default) end end def view_context_for(item) Nanoc::ViewContext.new( reps: item_rep_repo_for(item), items: :__irrelevant__, dependency_tracker: :__irrelevant__, compilation_context: :__irrelevant__, snapshot_repo: snapshot_repo, ) end def snapshot_repo @_snapshot_repo ||= Nanoc::Int::SnapshotRepo.new end def before super Nanoc::CLI::ErrorHandler.enable end def test_dependencies with_site do |_site| # Prepare File.open('lib/helpers.rb', 'w') do |io| io.write 'include Nanoc::Helpers::Capturing' end File.open('content/includer.erb', 'w') do |io| io.write '[<%= content_for(@items.find { |i| i.identifier == \'/includee/\' }, :blah) %>]' end File.open('Rules', 'w') do |io| io.write "compile '*' do ; filter :erb ; end\n" io.write "route '*' do ; item.identifier + 'index.html' ; end\n" end # Compile once File.open('content/includee.erb', 'w') do |io| io.write '{<% content_for :blah do %>Old content<% end %>}' end Nanoc::CLI.run(%w[compile]) assert_equal '[Old content]', File.read('output/includer/index.html') # Compile again File.open('content/includee.erb', 'w') do |io| io.write '{<% content_for :blah do %>New content<% end %>}' end Nanoc::CLI.run(%w[compile]) assert_equal '[New content]', File.read('output/includer/index.html') end end def test_content_for_recursively require 'erb' File.open('Rules', 'w') do |io| io.write "compile '*' do ; filter :erb ; end\n" io.write "route '*' do ; item.identifier + 'index.html' ; end\n" end content = <<~EOS head <% content_for :box do %> basic <% end %> <% content_for :outerbox do %> before <%= content_for @item, :box %> after <% end %> <%= content_for @item, :outerbox %> foot EOS item = Nanoc::Int::Item.new('content', {}, '/') view_context = view_context_for(item) @item = Nanoc::ItemWithRepsView.new(item, view_context_for(item)) @config = Nanoc::ConfigView.new(Nanoc::Int::Configuration.new, view_context) result = ::ERB.new(content).result(binding) expected = %w[head before basic after foot] actual = result.scan(/[a-z]+/) assert_equal expected, actual end def test_dependency_without_item_variable with_site do |_site| # Prepare File.open('lib/helpers.rb', 'w') do |io| io.write "include Nanoc::Helpers::Capturing\n" io.write "include Nanoc::Helpers::Rendering\n" end File.open('content/includer.erb', 'w') do |io| io.write '{<%= render \'partial\', :item => nil %>}' end File.open('layouts/partial.erb', 'w') do |io| io.write '[<%= @item.inspect %>-<%= content_for(@items.find { |i| i.identifier == \'/includee/\' }, :blah) %>]' end File.open('Rules', 'w') do |io| io.write "compile '*' do ; filter :erb ; end\n" io.write "route '*' do ; item.identifier + 'index.html' ; end\n" io.write "layout '*', :erb\n" end # Compile once File.open('content/includee.erb', 'w') do |io| io.write '{<% content_for :blah do %>Old content<% end %>}' end Nanoc::CLI.run(%w[compile]) assert_equal '{[nil-Old content]}', File.read('output/includer/index.html') # Compile again File.open('content/includee.erb', 'w') do |io| io.write '{<% content_for :blah do %>New content<% end %>}' end Nanoc::CLI.run(%w[compile]) assert_equal '{[nil-New content]}', File.read('output/includer/index.html') end end def test_self with_site do |_site| File.open('lib/helpers.rb', 'w') do |io| io.write 'include Nanoc::Helpers::Capturing' end File.open('content/self.erb', 'w') do |io| io.write '<% content_for :foo do %>Foo!<% end %>' io.write '<%= content_for(@item, :foo) %>' end File.open('Rules', 'w') do |io| io.write "compile '*' do ; filter :erb ; end\n" io.write "route '*' do ; item.identifier + 'index.html' ; end\n" end Nanoc::CLI.run(%w[compile]) assert_equal 'Foo!', File.read('output/self/index.html') end end def test_recompile_dependency with_site do |_site| # Prepare File.open('lib/helpers.rb', 'w') do |io| io.write 'include Nanoc::Helpers::Capturing' end File.open('content/includee.erb', 'w') do |io| io.write '{<% content_for :blah do %>Content<% end %>}' end File.open('Rules', 'w') do |io| io.write "compile '*' do ; filter :erb ; end\n" io.write "route '*' do ; item.identifier + 'index.html' ; end\n" end # Compile once File.open('content/includer.erb', 'w') do |io| io.write 'Old-<%= content_for(@items["/includee/"], :blah) %>' end Nanoc::CLI.run(%w[compile]) assert_equal '{}', File.read('output/includee/index.html') assert_equal 'Old-Content', File.read('output/includer/index.html') # Compile again File.open('content/includer.erb', 'w') do |io| io.write 'New-<%= content_for(@items["/includee/"], :blah) %>' end Nanoc::CLI.run(%w[compile]) assert_equal '{}', File.read('output/includee/index.html') assert_equal 'New-Content', File.read('output/includer/index.html') end end end nanoc-4.8.0/test/helpers/test_link_to.rb0000644000004100000410000000306113134141051020306 0ustar www-datawww-data# frozen_string_literal: true require 'helper' class Nanoc::Helpers::LinkToTest < Nanoc::TestCase include Nanoc::Helpers::LinkTo def test_examples_link_to # Parse YARD.parse(LIB_DIR + '/nanoc/helpers/link_to.rb') # Mock @items = [ Nanoc::ItemRepView.new(mock, {}), Nanoc::ItemRepView.new(mock, {}), Nanoc::ItemRepView.new(mock, {}), ] @items[0].stubs(:identifier).returns('/about/') @items[0].stubs(:path).returns('/about.html') @items[1].stubs(:identifier).returns('/software/') @items[1].stubs(:path).returns('/software.html') @items[2].stubs(:identifier).returns('/software/nanoc/') @items[2].stubs(:path).returns('/software/nanoc.html') about_rep_vcard = Nanoc::ItemRepView.new(mock, {}) about_rep_vcard.stubs(:path).returns('/about.vcf') @items[0].stubs(:rep).with(:vcard).returns(about_rep_vcard) # Run assert_examples_correct 'Nanoc::Helpers::LinkTo#link_to' end def test_examples_link_to_unless_current # Parse YARD.parse(LIB_DIR + '/nanoc/helpers/link_to.rb') # Mock @item_rep = mock @item_rep.stubs(:path).returns('/about/') @item = mock @item.stubs(:path).returns(@item_rep.path) # Run assert_examples_correct 'Nanoc::Helpers::LinkTo#link_to_unless_current' end def test_examples_relative_path_to # Parse YARD.parse(LIB_DIR + '/nanoc/helpers/link_to.rb') # Mock @item_rep = mock @item_rep.stubs(:path).returns('/foo/bar/') # Run assert_examples_correct 'Nanoc::Helpers::LinkTo#relative_path_to' end end nanoc-4.8.0/test/data_sources/0000755000004100000410000000000013134141051016275 5ustar www-datawww-datananoc-4.8.0/test/data_sources/test_filesystem_tools.rb0000644000004100000410000001132513134141051023267 0ustar www-datawww-data# frozen_string_literal: true require 'helper' class Nanoc::DataSources::FilesystemToolsTest < Nanoc::TestCase def setup super skip_unless_symlinks_supported end def test_all_files_in_follows_symlinks_to_dirs # Write sample files (0..15).each do |i| FileUtils.mkdir_p("dir#{i}") File.open("dir#{i}/foo.md", 'w') { |io| io.write('o hai') } end (1..10).each do |i| File.symlink("../dir#{i}", "dir#{i - 1}/sub") end # Check # 11 expected files (follow symlink 10 times) # sort required because 10 comes before 2 expected_files = [ 'dir0/foo.md', 'dir0/sub/foo.md', 'dir0/sub/sub/foo.md', 'dir0/sub/sub/sub/foo.md', 'dir0/sub/sub/sub/sub/foo.md', 'dir0/sub/sub/sub/sub/sub/foo.md', 'dir0/sub/sub/sub/sub/sub/sub/foo.md', 'dir0/sub/sub/sub/sub/sub/sub/sub/foo.md', 'dir0/sub/sub/sub/sub/sub/sub/sub/sub/foo.md', 'dir0/sub/sub/sub/sub/sub/sub/sub/sub/sub/foo.md', 'dir0/sub/sub/sub/sub/sub/sub/sub/sub/sub/sub/foo.md', ] actual_files = Nanoc::DataSources::Filesystem::Tools.all_files_in('dir0', nil).sort assert_equal expected_files, actual_files end def test_all_files_in_follows_symlinks_to_dirs_too_many # Write sample files (0..15).each do |i| FileUtils.mkdir_p("dir#{i}") File.open("dir#{i}/foo.md", 'w') { |io| io.write('o hai') } end (1..15).each do |i| File.symlink("../dir#{i}", "dir#{i - 1}/sub") end assert_raises Nanoc::DataSources::Filesystem::Tools::MaxSymlinkDepthExceededError do Nanoc::DataSources::Filesystem::Tools.all_files_in('dir0', nil) end end def test_all_files_in_relativizes_directory_names FileUtils.mkdir('foo') FileUtils.mkdir('bar') File.open('foo/x.md', 'w') { |io| io.write('o hai from foo/x') } File.open('bar/y.md', 'w') { |io| io.write('o hai from bar/y') } File.symlink('../bar', 'foo/barlink') expected_files = ['foo/barlink/y.md', 'foo/x.md'] actual_files = Nanoc::DataSources::Filesystem::Tools.all_files_in('foo', nil).sort assert_equal expected_files, actual_files end def test_all_files_in_follows_symlinks_to_files # Write sample files File.open('bar', 'w') { |io| io.write('o hai from bar') } FileUtils.mkdir_p('dir') File.open('dir/foo', 'w') { |io| io.write('o hai from foo') } File.symlink('../bar', 'dir/bar-link') # Check expected_files = ['dir/bar-link', 'dir/foo'] actual_files = Nanoc::DataSources::Filesystem::Tools.all_files_in('dir', nil).sort assert_equal expected_files, actual_files end def test_resolve_symlink File.open('foo', 'w') { |io| io.write('o hai') } File.symlink('foo', 'bar') File.symlink('bar', 'baz') File.symlink('baz', 'qux') expected = File.expand_path('foo') actual = Nanoc::DataSources::Filesystem::Tools.resolve_symlink('qux') assert_equal expected, actual end def test_resolve_symlink_too_many File.open('foo', 'w') { |io| io.write('o hai') } File.symlink('foo', 'symlin-0') (1..7).each do |i| File.symlink("symlink-#{i - 1}", "symlink-#{i}") end assert_raises Nanoc::DataSources::Filesystem::Tools::MaxSymlinkDepthExceededError do Nanoc::DataSources::Filesystem::Tools.resolve_symlink('symlink-7') end end def test_unwanted_dotfiles_not_found # Write sample files FileUtils.mkdir_p('dir') File.open('dir/.DS_Store', 'w') { |io| io.write('o hai') } File.open('dir/.htaccess', 'w') { |io| io.write('o hai') } actual_files = Nanoc::DataSources::Filesystem::Tools.all_files_in('dir', nil).sort assert_equal [], actual_files end def test_user_dotfiles_are_valid_items # Write sample files FileUtils.mkdir_p('dir') File.open('dir/.other', 'w') { |io| io.write('o hai') } actual_files = Nanoc::DataSources::Filesystem::Tools.all_files_in('dir', '**/.other').sort assert_equal ['dir/.other'], actual_files end def test_multiple_user_dotfiles_are_valid_items # Write sample files FileUtils.mkdir_p('dir') File.open('dir/.other', 'w') { |io| io.write('o hai') } File.open('dir/.DS_Store', 'w') { |io| io.write('o hai') } actual_files = Nanoc::DataSources::Filesystem::Tools.all_files_in('dir', ['**/.other', '**/.DS_Store']).sort assert_equal ['dir/.other', 'dir/.DS_Store'].sort, actual_files.sort end def test_unknown_pattern # Write sample files FileUtils.mkdir_p('dir') File.open('dir/.other', 'w') { |io| io.write('o hai') } pattern = { dotfiles: '**/.other' } assert_raises Nanoc::Int::Errors::GenericTrivial, "Do not know how to handle extra_files: #{pattern.inspect}" do Nanoc::DataSources::Filesystem::Tools.all_files_in('dir0', pattern) end end end nanoc-4.8.0/test/data_sources/test_filesystem.rb0000644000004100000410000007773313134141051022066 0ustar www-datawww-data# frozen_string_literal: true require 'helper' class Nanoc::DataSources::FilesystemTest < Nanoc::TestCase def new_data_source(params = nil) # Mock site site = Nanoc::Int::SiteLoader.new.new_empty # Create data source data_source = Nanoc::DataSources::Filesystem.new(site.config, nil, nil, params) # Done data_source end def test_load_objects # Create data source data_source = new_data_source # Create a fake class klass = Class.new do attr_reader :stuff def initialize(*stuff) @stuff = stuff end def ==(other) @stuff == other.stuff end end # Create sample files FileUtils.mkdir_p('foo') FileUtils.mkdir_p('foo/a/b') File.open('foo/bar.html', 'w') { |io| io.write("---\nnum: 1\n---\ntest 1") } File.open('foo/b.c.html', 'w') { |io| io.write("---\nnum: 2\n---\ntest 2") } File.open('foo/a/b/c.html', 'w') { |io| io.write("---\nnum: 3\n---\ntest 3") } File.open('foo/ugly.html~', 'w') { |io| io.write("---\nnum: 4\n---\ntest 4") } File.open('foo/ugly.html.orig', 'w') { |io| io.write("---\nnum: 5\n---\ntest 5") } File.open('foo/ugly.html.rej', 'w') { |io| io.write("---\nnum: 6\n---\ntest 6") } File.open('foo/ugly.html.bak', 'w') { |io| io.write("---\nnum: 7\n---\ntest 7") } # Get expected and actual output expected_out = [ klass.new( 'test 1', { 'num' => 1, :filename => 'foo/bar.html', :extension => 'html', mtime: File.mtime('foo/bar.html') }, '/bar/', ), klass.new( 'test 2', { 'num' => 2, :filename => 'foo/b.c.html', :extension => 'c.html', mtime: File.mtime('foo/b.c.html') }, '/b/', ), klass.new( 'test 3', { 'num' => 3, :filename => 'foo/a/b/c.html', :extension => 'html', mtime: File.mtime('foo/a/b/c.html') }, '/a/b/c/', ), ] actual_out = data_source.send(:load_objects, 'foo', klass).sort_by { |i| i.stuff[0].string } # Check (0..expected_out.size - 1).each do |i| assert_equal expected_out[i].stuff[0], actual_out[i].stuff[0].string, 'content must match' assert_equal expected_out[i].stuff[2], actual_out[i].stuff[2], 'identifier must match' ['num', :filename, :extension, :mtime].each do |key| assert_equal expected_out[i].stuff[1][key], actual_out[i].stuff[1][key], "attribute key #{key} must match" end end end def test_load_objects_with_same_extensions # Create data source data_source = new_data_source(identifier_type: 'full') # Create a fake class klass = Class.new do attr_reader :stuff def initialize(*stuff) @stuff = stuff end def ==(other) @stuff == other.stuff end end # Create sample files FileUtils.mkdir_p('foo') File.open('foo/bar.html', 'w') { |io| io.write("---\nnum: 1\n---\ntest 1") } File.open('foo/bar.md', 'w') { |io| io.write("---\nnum: 1\n---\ntest 1") } # Check actual_out = data_source.send(:load_objects, 'foo', klass) assert_equal 2, actual_out.size end def test_load_binary_objects # Create data source data_source = new_data_source # Create sample files FileUtils.mkdir_p('foo') File.open('foo/stuff.dat', 'w') { |io| io.write('random binary data') } # Load items = data_source.send(:load_objects, 'foo', Nanoc::Int::Item) # Check assert_equal 1, items.size assert items[0].content.binary? assert_equal "#{Dir.getwd}/foo/stuff.dat", items[0].content.filename assert_equal Nanoc::Int::BinaryContent, items[0].content.class end def test_load_layouts_with_nil_dir_name # Create data source data_source = new_data_source(layouts_dir: nil) # Create sample files FileUtils.mkdir_p('layouts') File.write('layouts/stuff.txt', 'blah blah') # Load layouts = data_source.layouts # Check assert_empty(layouts) end def test_load_binary_layouts # Create data source data_source = new_data_source # Create sample files FileUtils.mkdir_p('foo') File.open('foo/stuff.dat', 'w') { |io| io.write('random binary data') } # Load assert_raises(Nanoc::DataSources::Filesystem::Errors::BinaryLayout) do data_source.send(:load_objects, 'foo', Nanoc::Int::Layout) end end def test_identifier_for_filename_with_full_style_identifier # Create data source data_source = new_data_source(identifier_type: 'full') # Get input and expected output expected = { '/foo' => Nanoc::Identifier.new('/foo', type: :full), '/foo.html' => Nanoc::Identifier.new('/foo.html', type: :full), '/foo/index.html' => Nanoc::Identifier.new('/foo/index.html', type: :full), '/foo.html.erb' => Nanoc::Identifier.new('/foo.html.erb', type: :full), } # Check expected.each_pair do |input, expected_output| actual_output = data_source.send(:identifier_for_filename, input) assert_equal( expected_output, actual_output, "identifier_for_filename(#{input.inspect}) should equal #{expected_output.inspect}, not #{actual_output.inspect}" ) end end def test_identifier_for_filename_allowing_periods_in_identifiers # Create data source data_source = new_data_source(allow_periods_in_identifiers: true) # Get input and expected output expected = { '/foo' => '/foo/', '/foo.html' => '/foo/', '/foo/index.html' => '/foo/', '/foo.entry.html' => '/foo.entry/', } # Check expected.each_pair do |input, expected_output| actual_output = data_source.send(:identifier_for_filename, input) assert_equal( expected_output, actual_output, "identifier_for_filename(#{input.inspect}) should equal #{expected_output.inspect}, not #{actual_output.inspect}" ) end end def test_identifier_for_filename_disallowing_periods_in_identifiers # Create data source data_source = new_data_source # Get input and expected output expected = { '/foo' => '/foo/', '/foo.html' => '/foo/', '/foo/index.html' => '/foo/', '/foo.html.erb' => '/foo/', } # Check expected.each_pair do |input, expected_output| actual_output = data_source.send(:identifier_for_filename, input) assert_equal( expected_output, actual_output, "identifier_for_filename(#{input.inspect}) should equal #{expected_output.inspect}, not #{actual_output.inspect}" ) end end def test_identifier_for_filename_with_subfilename_allowing_periods_in_identifiers expectations = { 'foo/bar.yaml' => '/foo/bar/', 'foo/quxbar.yaml' => '/foo/quxbar/', 'foo/barqux.yaml' => '/foo/barqux/', 'foo/quxbarqux.yaml' => '/foo/quxbarqux/', 'foo/qux.bar.yaml' => '/foo/qux.bar/', 'foo/bar.qux.yaml' => '/foo/bar.qux/', 'foo/qux.bar.qux.yaml' => '/foo/qux.bar.qux/', 'foo/index.yaml' => '/foo/', 'index.yaml' => '/', 'foo/blah_index.yaml' => '/foo/blah_index/', } data_source = new_data_source(allow_periods_in_identifiers: true) expectations.each_pair do |meta_filename, expected_identifier| content_filename = meta_filename.sub(/yaml$/, 'html') [meta_filename, content_filename].each do |filename| assert_equal( expected_identifier, data_source.instance_eval { identifier_for_filename(filename) }, ) end end end def test_identifier_for_filename_with_subfilename_disallowing_periods_in_identifiers expectations = { 'foo/bar.yaml' => '/foo/bar/', 'foo/quxbar.yaml' => '/foo/quxbar/', 'foo/barqux.yaml' => '/foo/barqux/', 'foo/quxbarqux.yaml' => '/foo/quxbarqux/', 'foo/qux.bar.yaml' => '/foo/qux/', 'foo/bar.qux.yaml' => '/foo/bar/', 'foo/qux.bar.qux.yaml' => '/foo/qux/', 'foo/index.yaml' => '/foo/', 'index.yaml' => '/', 'foo/blah_index.yaml' => '/foo/blah_index/', } data_source = new_data_source expectations.each_pair do |meta_filename, expected_identifier| content_filename = meta_filename.sub(/yaml$/, 'html') [meta_filename, content_filename].each do |filename| assert_equal( expected_identifier, data_source.instance_eval { identifier_for_filename(filename) }, ) end end end def test_identifier_for_filename_with_index_filenames_allowing_periods_in_identifier expected = { '/index.html.erb' => '/index.html/', '/index.html' => '/', '/index' => '/', '/foo/index.html.erb' => '/foo/index.html/', '/foo/index.html' => '/foo/', '/foo/index' => '/foo/', } data_source = new_data_source(allow_periods_in_identifiers: true) expected.each_pair do |input, expected_output| actual_output = data_source.send(:identifier_for_filename, input) assert_equal( expected_output, actual_output, "identifier_for_filename(#{input.inspect}) should equal #{expected_output.inspect}, not #{actual_output.inspect}" ) end end def test_identifier_for_filename_with_index_filenames_disallowing_periods_in_identifier expected = { '/index.html.erb' => '/', '/index.html' => '/', '/index' => '/', '/foo/index.html.erb' => '/foo/', '/foo/index.html' => '/foo/', '/foo/index' => '/foo/', } data_source = new_data_source expected.each_pair do |input, expected_output| actual_output = data_source.send(:identifier_for_filename, input) assert_equal( expected_output, actual_output, "identifier_for_filename(#{input.inspect}) should equal #{expected_output.inspect}, not #{actual_output.inspect}" ) end end def test_load_objects_allowing_periods_in_identifiers # Create data source data_source = new_data_source(allow_periods_in_identifiers: true) # Create a fake class klass = Class.new do attr_reader :stuff def initialize(*stuff) @stuff = stuff end def ==(other) @stuff == other.stuff end end # Create sample files FileUtils.mkdir_p('foo') FileUtils.mkdir_p('foo/a/b') File.open('foo/a/b/c.yaml', 'w') { |io| io.write("---\nnum: 1\n") } File.open('foo/b.c.yaml', 'w') { |io| io.write("---\nnum: 2\n") } File.open('foo/b.c.html', 'w') { |io| io.write('test 2') } File.open('foo/car.html', 'w') { |io| io.write('test 3') } File.open('foo/ugly.yaml~', 'w') { |io| io.write('blah') } File.open('foo/ugly.html~', 'w') { |io| io.write('blah') } File.open('foo/ugly.html.orig', 'w') { |io| io.write('blah') } File.open('foo/ugly.html.rej', 'w') { |io| io.write('blah') } File.open('foo/ugly.html.bak', 'w') { |io| io.write('blah') } # Get expected output expected_out = [ klass.new( '', { 'num' => 1, :content_filename => nil, :meta_filename => 'foo/a/b/c.yaml', :extension => nil, :file => nil, mtime: File.mtime('foo/a/b/c.yaml'), }, '/a/b/c/', ), klass.new( 'test 2', { 'num' => 2, :content_filename => 'foo/b.c.html', :meta_filename => 'foo/b.c.yaml', :extension => 'html', :file => File.open('foo/b.c.html'), mtime: File.mtime('foo/b.c.html') > File.mtime('foo/b.c.yaml') ? File.mtime('foo/b.c.html') : File.mtime('foo/b.c.yaml'), }, '/b.c/', ), klass.new( 'test 3', { content_filename: 'foo/car.html', meta_filename: nil, extension: 'html', file: File.open('foo/car.html'), mtime: File.mtime('foo/car.html'), }, '/car/', ), ] # Get actual output ordered by identifier actual_out = data_source.send(:load_objects, 'foo', klass).sort_by { |i| i.stuff[2] } # Check (0..expected_out.size - 1).each do |i| assert_equal expected_out[i].stuff[0], actual_out[i].stuff[0].string, 'content must match' assert_equal expected_out[i].stuff[2], actual_out[i].stuff[2], 'identifier must match' ['num', :content_filename, :meta_filename, :extension, :mtime].each do |key| assert_equal expected_out[i].stuff[1][key], actual_out[i].stuff[1][key], "attribute key #{key} must match" end end end def test_load_objects_disallowing_periods_in_identifiers # Create data source data_source = new_data_source # Create a fake class klass = Class.new do attr_reader :stuff def initialize(*stuff) @stuff = stuff end def ==(other) @stuff == other.stuff end end # Create sample files FileUtils.mkdir_p('foo') FileUtils.mkdir_p('foo/a/b') File.open('foo/a/b/c.yaml', 'w') { |io| io.write("---\nnum: 1\n") } File.open('foo/b.yaml', 'w') { |io| io.write("---\nnum: 2\n") } File.open('foo/b.html.erb', 'w') { |io| io.write('test 2') } File.open('foo/car.html', 'w') { |io| io.write('test 3') } File.open('foo/ugly.yaml~', 'w') { |io| io.write('blah') } File.open('foo/ugly.html~', 'w') { |io| io.write('blah') } File.open('foo/ugly.html.orig', 'w') { |io| io.write('blah') } File.open('foo/ugly.html.rej', 'w') { |io| io.write('blah') } File.open('foo/ugly.html.bak', 'w') { |io| io.write('blah') } # Get expected output expected_out = [ klass.new( '', { 'num' => 1, :content_filename => nil, :meta_filename => 'foo/a/b/c.yaml', :extension => nil, :file => nil, mtime: File.mtime('foo/a/b/c.yaml'), }, '/a/b/c/', ), klass.new( 'test 2', { 'num' => 2, :content_filename => 'foo/b.html.erb', :meta_filename => 'foo/b.yaml', :extension => 'html.erb', :file => File.open('foo/b.html.erb'), mtime: File.mtime('foo/b.html.erb') > File.mtime('foo/b.yaml') ? File.mtime('foo/b.html.erb') : File.mtime('foo/b.yaml'), }, '/b/', ), klass.new( 'test 3', { content_filename: 'foo/car.html', meta_filename: nil, extension: 'html', file: File.open('foo/car.html'), mtime: File.mtime('foo/car.html'), }, '/car/', ), ] # Get actual output ordered by identifier actual_out = data_source.send(:load_objects, 'foo', klass).sort_by { |i| i.stuff[2] } # Check (0..expected_out.size - 1).each do |i| assert_equal expected_out[i].stuff[0], actual_out[i].stuff[0].string, 'content must match' assert_equal expected_out[i].stuff[2], actual_out[i].stuff[2], 'identifier must match' ['num', :content_filename, :meta_filename, :extension, :mtime].each do |key| assert_equal expected_out[i].stuff[1][key], actual_out[i].stuff[1][key], "attribute key #{key} must match" end end end def test_load_objects_correct_identifier_with_separate_yaml_file data_source = new_data_source(identifier_type: 'full') FileUtils.mkdir_p('foo') File.write('foo/donkey.jpeg', 'data') File.write('foo/donkey.yaml', "---\nalt: Donkey\n") objects = data_source.send(:load_objects, 'foo', Nanoc::Int::Item) assert_equal 1, objects.size assert_equal '/donkey.jpeg', objects.first.identifier.to_s end def test_filename_for data_source = new_data_source assert_equal '/foo.bar', data_source.send(:filename_for, '/foo', 'bar') assert_equal '/foo.bar.baz', data_source.send(:filename_for, '/foo', 'bar.baz') assert_equal '/foo', data_source.send(:filename_for, '/foo', '') assert_equal nil, data_source.send(:filename_for, '/foo', nil) end def test_compile_iso_8859_1_site # Create data source data_source = new_data_source # Create item FileUtils.mkdir_p('content') File.open('content/foo.md', 'w') { |io| io << 'Hëllö' } # Parse begin original_default_external_encoding = Encoding.default_external Encoding.default_external = 'ISO-8859-1' items = data_source.items assert_equal 1, items.size assert_equal Encoding.find('UTF-8'), items[0].content.string.encoding ensure Encoding.default_external = original_default_external_encoding end end def test_compile_iso_8859_1_site_with_explicit_encoding # Create data source data_source = new_data_source({}) data_source.config[:encoding] = 'ISO-8859-1' # Create item begin original_default_external_encoding = Encoding.default_external Encoding.default_external = 'ISO-8859-1' FileUtils.mkdir_p('content') File.open('content/foo.md', 'w') { |io| io << 'Hëllö' } ensure Encoding.default_external = original_default_external_encoding end # Parse items = data_source.items assert_equal 1, items.size assert_equal Encoding.find('UTF-8'), items[0].content.string.encoding end def test_all_split_files_in_allowing_periods_in_identifiers # Create data source data_source = Nanoc::DataSources::Filesystem.new(nil, nil, nil, allow_periods_in_identifiers: true) # Write sample files FileUtils.mkdir_p('foo') %w[foo.html foo.yaml bar.entry.html foo/qux.yaml].each do |filename| File.open(filename, 'w') { |io| io.write('test') } end # Write stray files %w[foo.html~ foo.yaml.orig bar.entry.html.bak].each do |filename| File.open(filename, 'w') { |io| io.write('test') } end # Get all files output_expected = { './foo' => ['yaml', ['html']], './bar.entry' => [nil, ['html']], './foo/qux' => ['yaml', [nil]], } output_actual = data_source.send :all_split_files_in, '.' # Check assert_equal output_expected, output_actual end def test_all_split_files_in_disallowing_periods_in_identifiers # Create data source data_source = Nanoc::DataSources::Filesystem.new(nil, nil, nil, nil) # Write sample files FileUtils.mkdir_p('foo') %w[foo.html foo.yaml bar.html.erb foo/qux.yaml].each do |filename| File.open(filename, 'w') { |io| io.write('test') } end # Write stray files %w[foo.html~ foo.yaml.orig bar.entry.html.bak].each do |filename| File.open(filename, 'w') { |io| io.write('test') } end # Get all files output_expected = { './foo' => ['yaml', ['html']], './bar' => [nil, ['html.erb']], './foo/qux' => ['yaml', [nil]], } output_actual = data_source.send :all_split_files_in, '.' # Check assert_equal output_expected, output_actual end def test_all_split_files_in_with_multiple_dirs # Create data source data_source = Nanoc::DataSources::Filesystem.new(nil, nil, nil, nil) # Write sample files %w[aaa/foo.html bbb/foo.html ccc/foo.html].each do |filename| FileUtils.mkdir_p(File.dirname(filename)) File.open(filename, 'w') { |io| io.write('test') } end # Check expected = { './aaa/foo' => [nil, ['html']], './bbb/foo' => [nil, ['html']], './ccc/foo' => [nil, ['html']], } assert_equal expected, data_source.send(:all_split_files_in, '.') end def test_all_split_files_in_with_same_extensions # Create data source config = { identifier_type: 'full' } data_source = Nanoc::DataSources::Filesystem.new(nil, nil, nil, config) # Write sample files %w[stuff/foo.html stuff/foo.md stuff/foo.yaml].each do |filename| FileUtils.mkdir_p(File.dirname(filename)) File.open(filename, 'w') { |io| io.write('test') } end # Check - { './stuff/foo' => ['yaml', ['html', 'md']] } res = data_source.send(:all_split_files_in, '.') assert_equal ['./stuff/foo'], res.keys assert_equal 2, res.values[0].size assert_equal 'yaml', res.values[0][0] assert_equal Array, res.values[0][1].class assert_equal %w[html md], res.values[0][1].sort end def test_all_split_files_in_with_multiple_content_files # Create data source data_source = Nanoc::DataSources::Filesystem.new(nil, nil, nil, nil) # Write sample files %w[foo.html foo.xhtml foo.txt foo.yaml bar.html qux.yaml].each do |filename| File.open(filename, 'w') { |io| io.write('test') } end # Check assert_raises(Nanoc::DataSources::Filesystem::Errors::MultipleContentFiles) do data_source.send(:all_split_files_in, '.') end end def test_basename_of_with_full_style_identifiers # Create data source config = { identifier_type: 'full' } data_source = Nanoc::DataSources::Filesystem.new(nil, nil, nil, config) # Get input and expected output expected = { '/' => '/', '/foo' => '/foo', '/foo.html' => '/foo', '/foo.xyz.html' => '/foo.xyz', '/foo/bar' => '/foo/bar', '/foo/bar.html' => '/foo/bar', '/foo/bar.xyz.html' => '/foo/bar.xyz', } # Check expected.each_pair do |input, expected_output| actual_output = data_source.send(:basename_of, input) assert_equal( expected_output, actual_output, "basename_of(#{input.inspect}) should equal #{expected_output.inspect}, not #{actual_output.inspect}" ) end end def test_basename_of_allowing_periods_in_identifiers # Create data source data_source = Nanoc::DataSources::Filesystem.new(nil, nil, nil, allow_periods_in_identifiers: true) # Get input and expected output expected = { '/' => '/', '/foo' => '/foo', '/foo.html' => '/foo', '/foo.xyz.html' => '/foo.xyz', '/foo/' => '/foo/', '/foo.xyz/' => '/foo.xyz/', '/foo/bar' => '/foo/bar', '/foo/bar.html' => '/foo/bar', '/foo/bar.xyz.html' => '/foo/bar.xyz', '/foo/bar/' => '/foo/bar/', '/foo/bar.xyz/' => '/foo/bar.xyz/', '/foo.xyz/bar.xyz/' => '/foo.xyz/bar.xyz/', } # Check expected.each_pair do |input, expected_output| actual_output = data_source.send(:basename_of, input) assert_equal( expected_output, actual_output, "basename_of(#{input.inspect}) should equal #{expected_output.inspect}, not #{actual_output.inspect}" ) end end def test_basename_of_disallowing_periods_in_identifiers # Create data source data_source = Nanoc::DataSources::Filesystem.new(nil, nil, nil, nil) # Get input and expected output expected = { '/' => '/', '/foo' => '/foo', '/foo.html' => '/foo', '/foo.xyz.html' => '/foo', '/foo/' => '/foo/', '/foo.xyz/' => '/foo.xyz/', '/foo/bar' => '/foo/bar', '/foo/bar.html' => '/foo/bar', '/foo/bar.xyz.html' => '/foo/bar', '/foo/bar/' => '/foo/bar/', '/foo/bar.xyz/' => '/foo/bar.xyz/', '/foo.xyz/bar.xyz/' => '/foo.xyz/bar.xyz/', } # Check expected.each_pair do |input, expected_output| actual_output = data_source.send(:basename_of, input) assert_equal( expected_output, actual_output, "basename_of(#{input.inspect}) should equal #{expected_output.inspect}, not #{actual_output.inspect}" ) end end def test_ext_of_allowing_periods_in_identifiers # Create data source data_source = Nanoc::DataSources::Filesystem.new(nil, nil, nil, allow_periods_in_identifiers: true) # Get input and expected output expected = { '/' => '', '/foo' => '', '/foo.html' => '.html', '/foo.xyz.html' => '.html', '/foo/' => '', '/foo.xyz/' => '', '/foo/bar' => '', '/foo/bar.html' => '.html', '/foo/bar.xyz.html' => '.html', '/foo/bar/' => '', '/foo/bar.xyz/' => '', '/foo.xyz/bar.xyz/' => '', } # Check expected.each_pair do |input, expected_output| actual_output = data_source.send(:ext_of, input) assert_equal( expected_output, actual_output, "basename_of(#{input.inspect}) should equal #{expected_output.inspect}, not #{actual_output.inspect}" ) end end def test_ext_of_disallowing_periods_in_identifiers # Create data source data_source = Nanoc::DataSources::Filesystem.new(nil, nil, nil, nil) # Get input and expected output expected = { '/' => '', '/foo' => '', '/foo.html' => '.html', '/foo.xyz.html' => '.xyz.html', '/foo/' => '', '/foo.xyz/' => '', '/foo/bar' => '', '/foo/bar.html' => '.html', '/foo/bar.xyz.html' => '.xyz.html', '/foo/bar/' => '', '/foo/bar.xyz/' => '', '/foo.xyz/bar.xyz/' => '', } # Check expected.each_pair do |input, expected_output| actual_output = data_source.send(:ext_of, input) assert_equal( expected_output, actual_output, "basename_of(#{input.inspect}) should equal #{expected_output.inspect}, not #{actual_output.inspect}" ) end end def test_parse_embedded_meta_only_1 # Create a file File.open('test.html', 'w') do |io| io.write "-----\r\n" io.write "foo: bar\n" io.write "-----\n" end # Create data source data_source = Nanoc::DataSources::Filesystem.new(nil, nil, nil, nil) # Parse it result = data_source.instance_eval { parse('test.html', nil) } assert_equal({ 'foo' => 'bar' }, result.attributes) assert_equal('', result.content) end def test_parse_embedded_meta_only_2 # Create a file File.open('test.html', 'w') do |io| io.write "-----\n" io.write "foo: bar\r\n" io.write "-----\r" end # Create data source data_source = Nanoc::DataSources::Filesystem.new(nil, nil, nil, nil) # Parse it result = data_source.instance_eval { parse('test.html', nil) } assert_equal({ 'foo' => 'bar' }, result.attributes) assert_equal('', result.content) end def test_parse_embedded_meta_only_3 # Create a file File.open('test.html', 'w') do |io| io.write "-----\r\n" io.write "foo: bar\n" io.write '-----' end # Create data source data_source = Nanoc::DataSources::Filesystem.new(nil, nil, nil, nil) # Parse it result = data_source.instance_eval { parse('test.html', nil) } assert_equal({ 'foo' => 'bar' }, result.attributes) assert_equal('', result.content) end def test_parse_embedded_invalid_2 # Create a file File.open('test.html', 'w') do |io| io.write "-----\n" io.write "blah blah\n" end # Create data source data_source = Nanoc::DataSources::Filesystem.new(nil, nil, nil, nil) # Parse it assert_raises(Nanoc::DataSources::Filesystem::Errors::InvalidFormat) do data_source.instance_eval { parse('test.html', nil) } end end def test_parse_embedded_separators_but_not_metadata # Create a file File.open('test.html', 'w') do |io| io.write "blah blah\n" io.write "-----\n" io.write "blah blah\n" end # Create data source data_source = Nanoc::DataSources::Filesystem.new(nil, nil, nil, nil) # Parse it result = data_source.instance_eval { parse('test.html', nil) } assert_equal(File.read('test.html'), result.content) assert_equal({}, result.attributes) end def test_parse_embedded_full_meta # Create a file File.open('test.html', 'w') do |io| io.write "-----\r\n" io.write "foo: bar\n" io.write "-----\n" io.write " \t\n blah blah\n" end # Create data source data_source = Nanoc::DataSources::Filesystem.new(nil, nil, nil, nil) # Parse it result = data_source.instance_eval { parse('test.html', nil) } assert_equal({ 'foo' => 'bar' }, result.attributes) assert_equal(" \t\n blah blah\n", result.content) end def test_parse_embedded_with_extra_spaces # Create a file File.open('test.html', 'w') do |io| io.write "----- \n" io.write "foo: bar\n" io.write "-----\t\t\t\t\t\n" io.write " blah blah\n" end # Create data source data_source = Nanoc::DataSources::Filesystem.new(nil, nil, nil, nil) # Parse it result = data_source.instance_eval { parse('test.html', nil) } assert_equal({ 'foo' => 'bar' }, result.attributes) assert_equal(" blah blah\n", result.content) end def test_parse_embedded_empty_meta # Create a file File.open('test.html', 'w') do |io| io.write "-----\n" io.write "-----\n" io.write "\nblah blah\n" io.write '-----' end # Create data source data_source = Nanoc::DataSources::Filesystem.new(nil, nil, nil, nil) # Parse it result = data_source.instance_eval { parse('test.html', nil) } assert_equal({}, result.attributes) assert_equal("\nblah blah\n-----", result.content) end def test_parse_utf8_bom File.open('test.html', 'w') do |io| io.write [0xEF, 0xBB, 0xBF].map(&:chr).join io.write "-----\n" io.write "utf8bomawareness: high\n" io.write "-----\n" io.write "content goes here\n" end data_source = Nanoc::DataSources::Filesystem.new(nil, nil, nil, encoding: 'utf-8') result = data_source.instance_eval { parse('test.html', nil) } assert_equal({ 'utf8bomawareness' => 'high' }, result.attributes) assert_equal("content goes here\n", result.content) end def test_parse_embedded_no_meta content = "blah\n" \ "blah blah blah\n" \ "blah blah\n" # Create a file File.open('test.html', 'w') { |io| io.write(content) } # Create data source data_source = Nanoc::DataSources::Filesystem.new(nil, nil, nil, nil) # Parse it result = data_source.instance_eval { parse('test.html', nil) } assert_equal({}, result.attributes) assert_equal(content, result.content) end def test_parse_embedded_diff content = \ "--- a/foo\n" \ "+++ b/foo\n" \ "blah blah\n" # Create a file File.open('test.html', 'w') { |io| io.write(content) } # Create data source data_source = Nanoc::DataSources::Filesystem.new(nil, nil, nil, nil) # Parse it result = data_source.instance_eval { parse('test.html', nil) } assert_equal({}, result.attributes) assert_equal(content, result.content) end def test_parse_external # Create a file File.open('test.html', 'w') { |io| io.write('blah blah') } File.open('test.yaml', 'w') { |io| io.write('foo: bar') } # Create data source data_source = Nanoc::DataSources::Filesystem.new(nil, nil, nil, nil) # Parse it result = data_source.instance_eval { parse('test.html', 'test.yaml') } assert_equal({ 'foo' => 'bar' }, result.attributes) assert_equal('blah blah', result.content) end def test_parse_internal_bad_metadata content = \ "---\n" \ "Hello world!\n" \ "---\n" \ "blah blah\n" File.open('test.html', 'w') { |io| io.write(content) } data_source = Nanoc::DataSources::Filesystem.new(nil, nil, nil, nil) assert_raises(Nanoc::DataSources::Filesystem::Errors::InvalidMetadata) do data_source.instance_eval { parse('test.html', nil) } end end def test_parse_external_bad_metadata File.open('test.html', 'w') { |io| io.write('blah blah') } File.open('test.yaml', 'w') { |io| io.write('Hello world!') } data_source = Nanoc::DataSources::Filesystem.new(nil, nil, nil, nil) assert_raises(Nanoc::DataSources::Filesystem::Errors::InvalidMetadata) do data_source.instance_eval { parse('test.html', 'test.yaml') } end end end nanoc-4.8.0/.gitignore0000644000004100000410000000011713134141051014631 0ustar www-datawww-datadoc/yardoc .DS_Store *.gem /coverage/ /.yardoc *~ /Gemfile.lock *.gemfile.lock nanoc-4.8.0/LICENSE0000644000004100000410000000207013134141051013646 0ustar www-datawww-dataCopyright (c) 2007-2017 Denis Defreyne and contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. nanoc-4.8.0/.github/0000755000004100000410000000000013134141051014202 5ustar www-datawww-datananoc-4.8.0/.github/CONTRIBUTING.md0000644000004100000410000000212113134141051016427 0ustar www-datawww-dataContributing ============ Reporting bugs -------------- If you find a bug in Nanoc, you should report it! Some information that you should include in your bug report is the Nanoc version (`nanoc --version`) and, if relevant, the crash log (`crash.log`). For details, check the [*bug reporting* section of the development guide](http://nanoc.ws/development/#reporting-bugs). Contributing code ----------------- Pull requests are appreciated! When submitting a PR, make sure that your changes have covering tests, that the documentation remains up-to-date and that you retain backwards compatibility. For details, check the [*contributing code* section of the development guide](http://nanoc.ws/development/#contributing-code). Contributor code of conduct --------------------------- In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone. For details, see the contributor code of conduct at http://nanoc.ws/contributing/#contributor-code-of-conduct. nanoc-4.8.0/.github/PULL_REQUEST_TEMPLATE.md0000644000004100000410000000045013134141051020002 0ustar www-datawww-data(Summarise the change in a single line.) ### Detailed description (Describe the change in detail.) ### To do (Include the to-do list for this PR to be finished here.) * [ ] Tests * [ ] Documentation * [ ] Feature flags * [ ] … ### Related issues (Add issue IDs for related issues here.) nanoc-4.8.0/.github/ISSUE_TEMPLATE.md0000644000004100000410000000072313134141051016711 0ustar www-datawww-data(Summarise the bug in a single line.) ### Steps to reproduce 1. [First step] 2. [Second step] 3. … ### Expected behavior (Describe what you expected to happen.) ### Actual behavior (Describe what actually happened instead.) ### Details (Describe other details, if any, that you believe might be relevant.) ### Crash log (When reporting a crash, create a [Gist](https://gist.github.com/) with the contents of the `crash.log` file, and link the Gist here.) nanoc-4.8.0/Appraisals0000644000004100000410000000027413134141051014667 0ustar www-datawww-data# frozen_string_literal: true appraise 'rouge-1' do group :plugins do gem 'rouge', '~> 1.0' end end appraise 'rouge-2' do group :plugins do gem 'rouge', '~> 2.0' end end nanoc-4.8.0/README.md0000644000004100000410000000504713134141051014127 0ustar www-datawww-data[![Gem version](http://img.shields.io/gem/v/nanoc.svg)](http://rubygems.org/gems/nanoc) [![Gem downloads](https://img.shields.io/gem/dt/nanoc.svg)](http://rubygems.org/gems/nanoc) [![Build status](http://img.shields.io/travis/nanoc/nanoc.svg)](https://travis-ci.org/nanoc/nanoc) [![Code Climate](http://img.shields.io/codeclimate/github/nanoc/nanoc.svg)](https://codeclimate.com/github/nanoc/nanoc) [![Code Coverage](https://img.shields.io/codecov/c/github/nanoc/nanoc.svg)](https://codecov.io/gh/nanoc/nanoc) ![Nanoc logo](https://avatars1.githubusercontent.com/u/3260163?s=140) # Nanoc Nanoc is a flexible static-site generator written in Ruby. See the [Nanoc web site](http://nanoc.ws) for more information. **Please take a moment and [donate](http://pledgie.com/campaigns/9282) to Nanoc. A lot of time has gone into developing Nanoc, and I would like to keep it going. Your support will ensure that Nanoc will continue to improve.** ## Contributing Contributions are greatly appreciated! Consult the [Development guidelines](http://nanoc.ws/development/) for information on how you can contribute. ### Contributors Many thanks to everyone who has contributed to Nanoc in one way or another: Ale Muñoz, Alexander Groß, Alexander Mankuta, Andy Drop, Arnau Siches, Ben Armston, Bil Bas, Brian Candler, Bruno Dufour, Cédric Boutillier, Chris Burkhardt, Chris Chapman, Chris Eppstein, Christian Plessl, Colin Barrett, Colin Seymour, Croath Liu, Damien Pollet, Dan Callahan, Daniel Hofstetter, Daniel Mendler, Daniel Wollschlaeger, David Alexander, David Everitt, Denis Defreyne, Dennis Sutch, Devon Luke Buchanan, Dmitry Bilunov, Eric Sunshine, Erik Hollensbe, Fabian Buch, Felix Hanley, Garen Torikian, Go Maeda, Grégory Karékinian, Gregory Pakosz, Guilherme Garnier, Hugo Peixoto, Jack Chu, Jake Benilov, Jan M. Faber, Jasper Van der Jeugt, Jeff Forcier, Jim Mendenhall, John Nishinaga, Justin Clift, Justin Hileman, Kevin Lynagh, Lorin Werthen, Louis T., Lucas Vuotto, Mathias Bynens, Matt Keveney, Matthew Frazier, Matthias Beyer, Matthias Reitinger, Matthias Vallentin, Micha Rosenbaum, Michal Cichra, Michal Papis, Mike Pennisi, Nelson Chen, Nicky Peeters, Nikhil Marathe, Oliver Byford, Paul Boone, Peter Aronoff, Raphael von der Grün, Rémi Barraquand, Remko Tronçon, Riley Goodside, Ruben Verborgh, Scott Vokes, Šime Ramov, Simon South, Spencer Whitt, Stanley Rost, Starr Horne, Stefan Bühler, Stuart Montgomery, Takashi Uchibe, Toon Willems, Tuomas Kareinen, Ursula Kallio, Vincent Driessen, Vlatko Kosturjak, whitequark, Xavier Shay, Yannick Ihmels, Zaiste de Grengolada nanoc-4.8.0/Guardfile0000644000004100000410000000014313134141051014465 0ustar www-datawww-data# frozen_string_literal: true guard 'rake', task: 'default' do watch(%r{^(lib|test|spec)/}) end