pax_global_header00006660000000000000000000000064134005017500014505gustar00rootroot0000000000000052 comment=e9086a38a91e9a0b4c32755cc9de917e318bc4f2 nanoc-4.11.0/000077500000000000000000000000001340050175000126665ustar00rootroot00000000000000nanoc-4.11.0/.github/000077500000000000000000000000001340050175000142265ustar00rootroot00000000000000nanoc-4.11.0/.github/CONTRIBUTING.md000066400000000000000000000021211340050175000164530ustar00rootroot00000000000000Contributing ============ 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.11.0/.github/ISSUE_TEMPLATE.md000066400000000000000000000007231340050175000167350ustar00rootroot00000000000000(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.11.0/.github/PULL_REQUEST_TEMPLATE.md000066400000000000000000000004501340050175000200260ustar00rootroot00000000000000(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.11.0/.gitignore000066400000000000000000000001341340050175000146540ustar00rootroot00000000000000.DS_Store *.gem *.gemfile.lock *~ coverage/ /.yardoc/ /Gemfile.lock /doc/yardoc/ /.vscode/ nanoc-4.11.0/.rubocop.yml000066400000000000000000000103161340050175000151410ustar00rootroot00000000000000# ----- CONFIGURED ----- AllCops: TargetRubyVersion: 2.4 DisplayCopNames: true # We use filenames such as “create-site.rb” that translate to method names. Naming/FileName: Exclude: - 'nanoc/lib/nanoc/cli/commands/*.rb' - 'nanoc-external/lib/nanoc-external.rb' - 'guard-nanoc/lib/guard-nanoc.rb' - '*/*.gemspec' Style/TrailingCommaInArguments: EnforcedStyleForMultiline: comma Style/TrailingCommaInArrayLiteral: EnforcedStyleForMultiline: comma Style/TrailingCommaInHashLiteral: EnforcedStyleForMultiline: comma Layout/IndentArray: EnforcedStyle: consistent Naming/UncommunicativeMethodParamName: AllowedNames: - "to" # destination - "e" # element - "id" # identifier - "io" # IO instance - "y" # enumerator yielder # ----- CONFIGURED (exceptions for tests) ----- # This breaks RSpec on occasion, e.g. `expect { subject }.not_to change { foo }`, # and generally does not provide useful warnings Lint/AmbiguousBlockAssociation: Exclude: - '*/spec/**/*.rb' # `rescue nil` is useful in specs where the exception is not important, but # the size effects are. Style/RescueModifier: Exclude: - '*/spec/**/*.rb' # 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' - '*/spec/**/*.rb' # ----- TO ENABLE LATER ----- # Valid cops, but fixing the offenses they report is non-trivial. Style/RegexpLiteral: Enabled: false Style/ClassAndModuleChildren: Enabled: false Style/EmptyElse: Enabled: false Style/Next: Enabled: false Naming/HeredocDelimiterNaming: Enabled: false Style/EvalWithLocation: Enabled: false Naming/MemoizedInstanceVariableName: 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' - 'nanoc/lib/nanoc/base/entities/code_snippet.rb' - 'nanoc/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. Style/AsciiComments: Enabled: false # It does not make sense to enforce everything to have documentation. Style/Documentation: Enabled: false # Nanoc suppresses exceptions for valid reasons in a few cases. Lint/HandleExceptions: Enabled: false # if/unless at the end of the line makes it too easy to oversee. Style/IfUnlessModifier: Enabled: false # Personal preference is to have decent constructors for exceptions rather than # just a class and a message. Style/RaiseArgs: 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. Style/TrivialAccessors: Enabled: false # This does not always semantically make sense. Style/GuardClause: Enabled: false # Used for “undo work, whatever error happens” Style/RescueStandardError: Enabled: false # For the sake of consistency, it makes the most sense to retain $stderr.puts # when used in situations where $stderr.flush, $stderr.print, … are also used. Style/StderrPuts: Enabled: false # This catches too many false positives. A string that contains a % followed by # a letter is not always a format string. Style/FormatStringToken: Enabled: false nanoc-4.11.0/.travis.yml000066400000000000000000000047721340050175000150110ustar00rootroot00000000000000language: ruby branches: only: - "master" stages: - warmup - test env: global: - LC_ALL=en_US.UTF_8 LANG=en_US.UTF_8 jobs: include: - stage: warmup script: true rvm: "2.4" - stage: warmup script: true rvm: "2.5" - stage: warmup script: true rvm: "jruby-9.2.0.0" - stage: test script: bundle exec rake rubocop env: JOB=rubocop rvm: "2.5" - stage: test script: bundle exec rake nanoc:test env: JOB=test_nanoc rvm: "2.4" - stage: test script: bundle exec rake nanoc_external:test env: JOB=test_nanoc_external rvm: "2.4" - stage: test script: bundle exec rake nanoc_live:test env: JOB=test_nanoc_live rvm: "2.4" - stage: test script: bundle exec rake guard_nanoc:test env: JOB=test_guard_nanoc rvm: "2.4" - stage: test script: bundle exec rake nanoc:test env: JOB=test_nanoc rvm: "2.5" - stage: test script: bundle exec rake nanoc_external:test env: JOB=test_nanoc_external rvm: "2.5" - stage: test script: bundle exec rake nanoc_live:test env: JOB=test_nanoc_live rvm: "2.5" - stage: test script: bundle exec rake guard_nanoc:test env: JOB=test_guard_nanoc rvm: "2.5" - stage: test script: bundle exec rake nanoc:test env: JOB=test_nanoc DISABLE_NOKOGIRI=1 rvm: "jruby-9.2.0.0" - stage: test script: bundle exec rake nanoc_external:test env: JOB=test_nanoc_external DISABLE_NOKOGIRI=1 rvm: "jruby-9.2.0.0" - stage: test script: bundle exec rake nanoc_live:test env: JOB=test_nanoc_live DISABLE_NOKOGIRI=1 rvm: "jruby-9.2.0.0" - stage: test script: bundle exec rake guard_nanoc:test env: JOB=test_guard_nanoc DISABLE_NOKOGIRI=1 rvm: "jruby-9.2.0.0" matrix: fast_finish: true allow_failures: - env: JOB=test_nanoc DISABLE_NOKOGIRI=1 rvm: "jruby-9.2.0.0" - env: JOB=test_nanoc_external DISABLE_NOKOGIRI=1 rvm: "jruby-9.2.0.0" - env: JOB=test_guard_nanoc DISABLE_NOKOGIRI=1 rvm: "jruby-9.2.0.0" - env: JOB=test_nanoc_live rvm: "2.4" - env: JOB=test_nanoc_live rvm: "2.5" - env: JOB=test_nanoc_live DISABLE_NOKOGIRI=1 rvm: "jruby-9.2.0.0" before_install: - gem update --system - gem install bundler - gem update bundler script: - bundle exec rake test cache: bundler sudo: false git: depth: 10 nanoc-4.11.0/CODE_OF_CONDUCT.md000066400000000000000000000062601340050175000154710ustar00rootroot00000000000000# 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, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, 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+coc@denis.ws. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and 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 https://www.contributor-covenant.org/version/1/4/code-of-conduct.html [homepage]: https://www.contributor-covenant.org nanoc-4.11.0/Gemfile000066400000000000000000000031101340050175000141540ustar00rootroot00000000000000# frozen_string_literal: true source 'https://rubygems.org' gemspec path: 'nanoc' gemspec path: 'nanoc-external' gemspec path: 'nanoc-live' gemspec path: 'guard-nanoc' group :devel do gem 'codecov', require: false gem 'contracts', '~> 0.16' gem 'fuubar' gem 'guard-rake' gem 'json', '~> 2.1' gem 'm', '~> 1.5' gem 'minitest', '~> 5.11' gem 'mocha' gem 'pry' gem 'rake' gem 'rdoc', '~> 6.0' gem 'rspec' gem 'rspec-its', '~> 1.2' gem 'rspec-mocks' gem 'rubocop' gem 'timecop' gem 'vcr' gem 'webmock' gem 'yard' gem 'yard-contracts' end group :plugins do gem 'adsf' gem 'adsf-live' gem 'asciidoctor' gem 'bluecloth', platforms: :ruby gem 'builder' gem 'coderay' gem 'coffee-script' gem 'erubi' gem 'erubis' gem 'fog-aws' gem 'fog-local', '~> 0.6' gem 'haml' gem 'handlebars', platforms: :ruby gem 'kramdown' gem 'less', '~> 2.6', platforms: :ruby gem 'libv8', platforms: :ruby gem 'listen', '~> 3.1' gem 'markaby' gem 'maruku' gem 'mime-types' gem 'mustache', '~> 1.0' gem 'nokogiri', '~> 1.8' gem 'nokogumbo', '~> 2.0', platforms: :ruby gem 'pandoc-ruby' gem 'pygments.rb', '~> 1.2', platforms: :ruby gem 'rack' gem 'rainpress' gem 'rdiscount', '~> 2.2', platforms: :ruby gem 'redcarpet', '~> 3.4', platforms: :ruby gem 'RedCloth', platforms: :ruby gem 'rouge', '~> 3.1' gem 'rubypants' gem 'sass' gem 'slim', '~> 3.0' gem 'therubyracer', '~> 0.12', platforms: :ruby gem 'typogruby' gem 'uglifier' gem 'w3c_validators' gem 'wdm', '>= 0.1.0' if Gem.win_platform? gem 'yuicompressor' end nanoc-4.11.0/README.md000066400000000000000000000053361340050175000141540ustar00rootroot00000000000000[![Gem version](https://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](https://img.shields.io/travis/nanoc/nanoc.svg)](https://travis-ci.org/nanoc/nanoc) [![Code Climate](https://img.shields.io/codeclimate/maintainability/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) ![Contributors](https://img.shields.io/github/contributors/nanoc/nanoc.svg) ![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](https://donorbox.org/nanoc) 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 [guidelines](https://nanoc.ws/contributing/) for information on how you can contribute. ### Contributors Many thanks to everyone who has contributed to Nanoc in one way or another: Abubakar Ango, Ale Muñoz, Alexander Groß, Alexander Mankuta, Andy Drop, Antonio Terceiro, 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, Farley Reynolds, Felix Hanley, Garen Torikian, Go Maeda, Grégory Karékinian, Gregory Pakosz, Guilherme Garnier, Hugo Peixoto, Ian Young, 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, Rien Maertens, Riley Goodside, Ruben Verborgh, Scott Vokes, Seiichi Yonezawa, Šime Ramov, Simon South, Spencer Whitt, Stanley Rost, Starr Horne, Stefan Bühler, Stuart Montgomery, Takashi Uchibe, Thomas Hochstein, Toon Willems, Tuomas Kareinen, Ursula Kallio, Vincent Driessen, Vlatko Kosturjak, whitequark, Xavier Shay, Yannick Ihmels, Zaiste de Grengolada nanoc-4.11.0/Rakefile000066400000000000000000000020551340050175000143350ustar00rootroot00000000000000# frozen_string_literal: true require 'rubocop/rake_task' RuboCop::RakeTask.new(:rubocop) def sub_sh(dir, cmd) Bundler.with_clean_env do Dir.chdir(dir) do puts "(in #{Dir.getwd})" sh(cmd) end end end namespace :nanoc do task(:test) { sub_sh('nanoc', 'bundle exec rake test') } task(:rubocop) { sub_sh('nanoc', 'bundle exec rake rubocop') } end namespace :nanoc_external do task(:test) { sub_sh('nanoc-external', 'bundle exec rake test') } task(:rubocop) { sub_sh('nanoc-external', 'bundle exec rake rubocop') } end namespace :nanoc_live do task(:test) { sub_sh('nanoc-live', 'bundle exec rake test') } task(:rubocop) { sub_sh('nanoc-live', 'bundle exec rake rubocop') } end namespace :guard_nanoc do task(:test) { sub_sh('guard-nanoc', 'bundle exec rake test') } task(:rubocop) { sub_sh('guard-nanoc', 'bundle exec rake rubocop') } end task test: %i[nanoc:test nanoc_external:test nanoc_live:test guard_nanoc:test] task gem: %i[nanoc:gem nanoc_external:gem nanoc_live:gem guard_nanoc:gem] task default: %i[test rubocop] nanoc-4.11.0/common/000077500000000000000000000000001340050175000141565ustar00rootroot00000000000000nanoc-4.11.0/common/spec/000077500000000000000000000000001340050175000151105ustar00rootroot00000000000000nanoc-4.11.0/common/spec/spec_helper_foot.rb000066400000000000000000000250611340050175000207610ustar00rootroot00000000000000# frozen_string_literal: true Nanoc::CLI.setup RSpec.configure do |c| c.include(Nanoc::Spec::Helper) c.include(Nanoc::Spec::HelperHelper, helper: true) 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| should_chdir = !example.metadata.key?(:chdir) || example.metadata[:chdir] if should_chdir Dir.mktmpdir('nanoc-test') do |dir| chdir(dir) { example.run } end else 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.before(:each, fork: true) do skip 'fork() is not supported on Windows' if Nanoc.on_windows? 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(coll) coll.sort_by do |elem| elem.dup.unicode_normalize(:nfd).encode('ASCII', fallback: ->(_) { '' }).downcase end 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 :have_correct_yard_examples do |_name, *_expected_args| chain :in_file do |file| root_dir = File.expand_path(__dir__ + '/../..') YARD.parse(root_dir + '/' + file) end match do |actual| examples = P(actual).tags(:example).flat_map do |example| # Classify lines = example.text.lines.map do |line| [/^\s*# ?=>/.match?(line) ? :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) # Collect lines.each_slice(2).to_a end b = binding executed_examples = examples.map do |pair| { input: pair.first, expected: eval(pair.last.match(/# ?=>(.*)/)[1], b), actual: eval(pair.first, b), } end @failing_examples = executed_examples.reject { |ex| ex[:expected] == ex[:actual] } @failing_examples.empty? end failure_message do |_actual| parts = @failing_examples.map do |ex| format( "%s\nexpected to be\n %s\nbut was\n %s", ex[:input], ex[:expected].inspect, ex[:actual].inspect, ) end parts.join("\n\n---\n\n") end end RSpec::Matchers.define :create_dependency_on do |expected| supports_block_expectations include RSpec::Matchers::Composable match do |actual| @to = expected dependency_tracker = @to._context.dependency_tracker dependency_store = dependency_tracker.dependency_store from = Nanoc::Int::Item.new('x', {}, '/x.md') a = dependency_store.objects_causing_outdatedness_of(from) begin dependency_tracker.enter(from) actual.call ensure dependency_tracker.exit end b = dependency_store.objects_causing_outdatedness_of(from) (b - a).include?(@to) end description do "create a dependency onto #{expected.inspect}" end failure_message do |_actual| "expected dependency to be created onto #{expected.inspect}" end failure_message_when_negated do |_actual| "expected no dependency to be created onto #{expected.inspect}" end end RSpec::Matchers.define :create_dependency_from do |expected| supports_block_expectations include RSpec::Matchers::Composable match do |actual| @from = expected dependency_tracker = @from._context.dependency_tracker dependency_store = dependency_tracker.dependency_store a = dependency_store.objects_causing_outdatedness_of(@from) begin dependency_tracker.enter(@from._unwrap) actual.call ensure dependency_tracker.exit end b = dependency_store.objects_causing_outdatedness_of(@from) @actual = b - a if @onto values_match?(@onto, @actual) else @actual.any? end end chain :onto do |onto| @onto = onto end description do "create a dependency from #{expected.inspect}" end failure_message do |_actual| "expected a dependency to be created from #{expected.inspect}#{@onto ? " onto #{@onto.inspect}" : nil}, but generated #{@actual.inspect}" end failure_message_when_negated do |_actual| "expected no dependency to be created from #{expected.inspect}, but generated #{@actual.inspect}" end end RSpec::Matchers.define :have_a_valid_manifest do match do |actual| manifest_lines = File.readlines(actual + '.manifest').map(&:chomp).reject(&:empty?) gemspec_lines = eval(File.read(actual + '.gemspec'), binding, actual + '.gemspec').files @missing_from_manifest = gemspec_lines - manifest_lines @extra_in_manifest = manifest_lines - gemspec_lines @missing_from_manifest.empty? && @extra_in_manifest.empty? end description do 'have a valid manifest' end failure_message do |_actual| reasons = [] if @missing_from_manifest.any? reasons << "file(s) missing from manifest (#{@missing_from_manifest.join(', ')})" end if @extra_in_manifest.any? reasons << "file(s) extra in manifest (#{@extra_in_manifest.join(', ')})" end "expected manifest to be valid (problems: #{reasons.join(' and ')})" end end nanoc-4.11.0/common/spec/spec_helper_head.rb000066400000000000000000000004501340050175000207060ustar00rootroot00000000000000# frozen_string_literal: true require 'simplecov' SimpleCov.start require 'codecov' if ENV['CI'] == 'true' SimpleCov.formatter = SimpleCov::Formatter::Codecov end require 'timecop' require 'rspec/its' require 'fuubar' require 'yard' require 'nanoc' require 'nanoc/cli' require 'nanoc/spec' nanoc-4.11.0/guard-nanoc/000077500000000000000000000000001340050175000150645ustar00rootroot00000000000000nanoc-4.11.0/guard-nanoc/.rspec000066400000000000000000000000611340050175000161760ustar00rootroot00000000000000-r ./spec/spec_helper.rb --format Fuubar --color nanoc-4.11.0/guard-nanoc/LICENSE000066400000000000000000000020421340050175000160670ustar00rootroot00000000000000Copyright (c) 2013 Denis Defreyne 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.11.0/guard-nanoc/NEWS.md000066400000000000000000000016321340050175000161640ustar00rootroot00000000000000# guard-nanoc Release Notes ## 2.1.4 (2018-09-15) * Fixed issue which caused `--host` and `--port` options to be mandatory. ## 2.1.3 (2018-07-28) * Added missing --live-reload option, passed through to `nanoc view` (#38, #39) ## 2.1.2 (2017-03-19) * Fixed compatibility with Nanoc 4.7.1 ## 2.1.1 (2016-11-18) * Fixed compatibility with Nanoc 4.3.8 (#32) * Fixed issue which would cause “constants redefined” warnings to be printed (#26, #34) ## 2.1.0 (2016-08-21) * Add `nanoc live` command, combining guard-nanoc with `nanoc view` [whitequark] ## 2.0.0 (2015-11-07) (Identical to 2.0.0b1) ## 2.0.0b1 (2015-06-21) * Added nanoc 4 compatibility ## 1.0.3 (2014-11-16) * Fix guard 2.8 deprecation warning (guard/guard-nanoc#16) ## 1.0.2 (2013-11-27) * Made guard-nanoc honor autoprune settings ## 1.0.1 (2013-05-15) * Added dependencies on `guard` and `nanoc` ## 1.0.0 (2013-04-28) Initial release. nanoc-4.11.0/guard-nanoc/README.md000066400000000000000000000025251340050175000163470ustar00rootroot00000000000000# Guard::Nanoc This is a guard for [nanoc](http://nanoc.ws/). `Guard` is a framework for listening to filesystem changes and acting upon them. `Guard::Nanoc` is a plugin for Guard that recompiles Nanoc sites on changes. ## Installation Add the `guard-nanoc` gem inside the `nanoc` group to your application's Gemfile: group :nanoc do gem 'guard-nanoc' end Unless your Gemfile already specifies a web server, you'll need one as well: gem 'adsf' Lastly, ensure that Nanoc is at least version 4.3: gem 'nanoc', '~> 4.3' And then execute: $ bundle ## Usage Enter the nanoc site directory for which you want to use guard-nanoc. Create a Guardfile using `guard init`: $ bundle exec guard init nanoc Then run: $ bundle exec nanoc live This will start a web server, like `nanoc view` would, and watch for changes to the site in the background, like `guard start` would. Whenever you change a file in the Nanoc site directory now, the site will be recompiled! Visit `http://localhost:3000` in browser to see it. (In some cases, the port number might not be `3000`; check what `nanoc live` prints to find out the actual port number.) After editing and saving a file, `nanoc live` will recompile the site, but it is necessary to reload the page in the browser in order to see the new content that is served by `nanoc live`. nanoc-4.11.0/guard-nanoc/Rakefile000066400000000000000000000004341340050175000165320ustar00rootroot00000000000000# frozen_string_literal: true require 'rspec/core/rake_task' require 'rubocop/rake_task' RuboCop::RakeTask.new(:rubocop) RSpec::Core::RakeTask.new(:spec) do |t| t.verbose = false end task test: :spec task :gem do sh('gem build *.gemspec') end task default: %i[test rubocop] nanoc-4.11.0/guard-nanoc/guard-nanoc.gemspec000066400000000000000000000012521340050175000206270ustar00rootroot00000000000000# frozen_string_literal: true require_relative './lib/guard/nanoc/version' Gem::Specification.new do |s| s.name = 'guard-nanoc' s.version = Guard::GUARD_NANOC_VERSION s.homepage = 'http://nanoc.ws/' s.summary = 'guard gem for nanoc' s.description = 'Automatically rebuilds nanoc sites' s.license = 'MIT' s.author = 'Denis Defreyne' s.email = 'denis.defreyne@stoneship.org' s.add_dependency 'guard', '~> 2.8' s.add_dependency 'guard-compat', '~> 1.0' s.add_dependency 'nanoc', '>= 4.3.8', '< 5.0' s.files = Dir['[A-Z]*'] + Dir['lib/**/*'] + ['guard-nanoc.gemspec'] s.require_paths = ['lib'] end nanoc-4.11.0/guard-nanoc/lib/000077500000000000000000000000001340050175000156325ustar00rootroot00000000000000nanoc-4.11.0/guard-nanoc/lib/guard-nanoc.rb000066400000000000000000000000651340050175000203560ustar00rootroot00000000000000# frozen_string_literal: true require 'guard/nanoc' nanoc-4.11.0/guard-nanoc/lib/guard/000077500000000000000000000000001340050175000167345ustar00rootroot00000000000000nanoc-4.11.0/guard-nanoc/lib/guard/nanoc.rb000066400000000000000000000032651340050175000203650ustar00rootroot00000000000000# frozen_string_literal: true require 'guard/compat/plugin' require 'nanoc' require 'nanoc/cli' module Guard class Nanoc < Plugin def self.live_cmd @_live_cmd ||= begin path = File.join(File.dirname(__FILE__), '..', 'nanoc', 'cli', 'commands', 'live.rb') ::Nanoc::CLI.load_command_at(path) end end def initialize(options = {}) @dir = options[:dir] || '.' super end def start setup_listeners recompile_in_subprocess end def run_all recompile_in_subprocess end def run_on_changes(_paths) recompile_in_subprocess end def run_on_removals(_paths) recompile_in_subprocess end protected def setup_listeners ::Nanoc::CLI.setup ::Nanoc::CLI::Commands::CompileListeners::FileActionPrinter .new(reps: []) .start end def recompile_in_subprocess if Process.respond_to?(:fork) pid = Process.fork { recompile } Process.waitpid(pid) else recompile end end def recompile Dir.chdir(@dir) do site = ::Nanoc::Int::SiteLoader.new.new_from_cwd site.compile end notify_success rescue => e notify_failure ::Nanoc::CLI::ErrorHandler.print_error(e) end def notify_success Compat::UI.notify('Compilation succeeded', title: 'nanoc', image: :success) Compat::UI.info 'Compilation succeeded.' end def notify_failure Compat::UI.notify('Compilation FAILED', title: 'nanoc', image: :failed) Compat::UI.error 'Compilation failed!' end end end ::Nanoc::CLI.after_setup do ::Nanoc::CLI.add_command(Guard::Nanoc.live_cmd) end nanoc-4.11.0/guard-nanoc/lib/guard/nanoc/000077500000000000000000000000001340050175000200325ustar00rootroot00000000000000nanoc-4.11.0/guard-nanoc/lib/guard/nanoc/templates/000077500000000000000000000000001340050175000220305ustar00rootroot00000000000000nanoc-4.11.0/guard-nanoc/lib/guard/nanoc/templates/Guardfile000066400000000000000000000003041340050175000236520ustar00rootroot00000000000000# frozen_string_literal: true guard 'nanoc' do watch('nanoc.yaml') # Change this to config.yaml if you use the old config file name watch('Rules') watch(%r{^(content|layouts|lib)/.*$}) end nanoc-4.11.0/guard-nanoc/lib/guard/nanoc/version.rb000066400000000000000000000001201340050175000220350ustar00rootroot00000000000000# frozen_string_literal: true module Guard GUARD_NANOC_VERSION = '2.1.4' end nanoc-4.11.0/guard-nanoc/lib/nanoc/000077500000000000000000000000001340050175000167305ustar00rootroot00000000000000nanoc-4.11.0/guard-nanoc/lib/nanoc/cli/000077500000000000000000000000001340050175000174775ustar00rootroot00000000000000nanoc-4.11.0/guard-nanoc/lib/nanoc/cli/commands/000077500000000000000000000000001340050175000213005ustar00rootroot00000000000000nanoc-4.11.0/guard-nanoc/lib/nanoc/cli/commands/live.rb000066400000000000000000000023321340050175000225640ustar00rootroot00000000000000# frozen_string_literal: true usage 'live [options]' summary 'start the web server, and recompile the site when changed' description <<~EOS Start the static web server (like `nanoc view` would), and watch for changes in the background (like `guard start` would). See the documentation of those two commands for details. The options are forwarded to `nanoc view` only. 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)', default: '127.0.0.1' required :p, :port, 'specify the port to listen on (default: 3000)', transform: Nanoc::CLI::Transform::Port, default: 3000 flag :L, :'live-reload', 'reload on changes' module Nanoc::CLI::Commands class Live < ::Nanoc::CLI::CommandRunner def run require 'guard' require 'guard/commander' Thread.new do # Crash the entire process if the viewer dies for some reason (e.g. # the port is already bound). Thread.current.abort_on_exception = true Nanoc::CLI::Commands::View.new(options, arguments, command).run end Guard.start(no_interactions: true) end end end runner Nanoc::CLI::Commands::Live nanoc-4.11.0/guard-nanoc/spec/000077500000000000000000000000001340050175000160165ustar00rootroot00000000000000nanoc-4.11.0/guard-nanoc/spec/lib/000077500000000000000000000000001340050175000165645ustar00rootroot00000000000000nanoc-4.11.0/guard-nanoc/spec/lib/guard/000077500000000000000000000000001340050175000176665ustar00rootroot00000000000000nanoc-4.11.0/guard-nanoc/spec/lib/guard/nanoc_spec.rb000066400000000000000000000030421340050175000223220ustar00rootroot00000000000000# frozen_string_literal: true require 'guard/compat/test/helper' require 'guard/nanoc' RSpec.describe Guard::Nanoc do before do allow(Process).to receive(:fork) do |_args, &block| @_fork_block = block end allow(Process).to receive(:waitpid) do @_fork_block.call end allow(Guard::Compat::UI).to receive(:notify) allow(Guard::Compat::UI).to receive(:error) allow(Guard::Compat::UI).to receive(:info) end describe '#start' do context 'with no errors' do it 'outputs success' do expect(Guard::Compat::UI).to receive(:info).with(/Compilation succeeded/) subject.start end it 'notifies about success' do expect(Guard::Compat::UI).to receive(:notify).with(/Compilation succeeded/, anything) subject.start end end context 'with errors' do before do File.write('layouts/default.html', '<%= raise "boom" %>') end it 'outputs failure' do expect(Guard::Compat::UI).to receive(:error).with(/Compilation failed/) subject.start end it 'notifies about failure' do expect(Guard::Compat::UI).to receive(:notify).with(/Compilation FAILED/, anything) subject.start end end end describe 'command' do it 'has an option set that is a superset of the view command’s options' do view_cmd = Nanoc::CLI.root_command.command_named('view') live_cmd = Guard::Nanoc.live_cmd expect(live_cmd.option_definitions).not_to eq(view_cmd.option_definitions) end end end nanoc-4.11.0/guard-nanoc/spec/spec_helper.rb000066400000000000000000000013331340050175000206340ustar00rootroot00000000000000# frozen_string_literal: true require_relative '../../common/spec/spec_helper_head' require 'guard-nanoc' require_relative '../../common/spec/spec_helper_foot' RSpec.configure do |config| # Swallow stdout/stderr config.around(:each) do |example| old_stdout = $stdout old_stderr = $stderr $stdout = StringIO.new $stderr = StringIO.new begin example.run ensure $stdout = old_stdout $stderr = old_stderr end end # In temporary site config.around(:each) do |example| Dir.mktmpdir('nanoc-test') do |dir| FileUtils.cd(dir) do Nanoc::CLI.run(%w[create-site foo]) FileUtils.cd('foo') do example.run end end end end end nanoc-4.11.0/nanoc-external/000077500000000000000000000000001340050175000156045ustar00rootroot00000000000000nanoc-4.11.0/nanoc-external/.rspec000066400000000000000000000000611340050175000167160ustar00rootroot00000000000000-r ./spec/spec_helper.rb --format Fuubar --color nanoc-4.11.0/nanoc-external/LICENSE000066400000000000000000000020701340050175000166100ustar00rootroot00000000000000Copyright (c) 2014-2018 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.11.0/nanoc-external/NEWS.md000066400000000000000000000002471340050175000167050ustar00rootroot00000000000000# nanoc-external news ## 1.0.2 (2017-12-03) * Dropped support for Ruby 2.2 and older ## 1.0.1 * Added support for Nanoc 4 ## 1.0.0 (2015-03-07) Initial release. nanoc-4.11.0/nanoc-external/README.md000066400000000000000000000015271340050175000170700ustar00rootroot00000000000000# nanoc-external This provides a filter that allows [Nanoc](http://nanoc.ws) to process content by executing an external program. ## Installation Add `nanoc-external` to the `nanoc` group of your Gemfile: ```ruby group :nanoc do gem 'nanoc-external' end ``` ## Usage Call the `:external` filter and pass the command to execute as the `:exec` argument. For example: ```ruby filter :external, exec: 'wc' ``` The external command must receive input from standard input (“stdin”) and must send its output to standard out (“stdout”). Options passed to this filter will be passed on to the external command. For example: ```ruby filter :external, exec: 'multimarkdown', options: %w(--accept --mask --labels --smart) ``` You can also pass the full path of the executable: ```ruby filter :external, exec: '/opt/local/bin/htmlcompressor' ``` nanoc-4.11.0/nanoc-external/Rakefile000066400000000000000000000004341340050175000172520ustar00rootroot00000000000000# frozen_string_literal: true require 'rspec/core/rake_task' require 'rubocop/rake_task' RuboCop::RakeTask.new(:rubocop) RSpec::Core::RakeTask.new(:spec) do |t| t.verbose = false end task test: :spec task :gem do sh('gem build *.gemspec') end task default: %i[test rubocop] nanoc-4.11.0/nanoc-external/lib/000077500000000000000000000000001340050175000163525ustar00rootroot00000000000000nanoc-4.11.0/nanoc-external/lib/nanoc-external.rb000066400000000000000000000000701340050175000216120ustar00rootroot00000000000000# frozen_string_literal: true require 'nanoc/external' nanoc-4.11.0/nanoc-external/lib/nanoc/000077500000000000000000000000001340050175000174505ustar00rootroot00000000000000nanoc-4.11.0/nanoc-external/lib/nanoc/external.rb000066400000000000000000000002121340050175000216120ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module External end end require 'nanoc/external/version' require 'nanoc/external/filter' nanoc-4.11.0/nanoc-external/lib/nanoc/external/000077500000000000000000000000001340050175000212725ustar00rootroot00000000000000nanoc-4.11.0/nanoc-external/lib/nanoc/external/filter.rb000066400000000000000000000006501340050175000231050ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc::External class Filter < Nanoc::Filter identifier :external def run(content, params = {}) cmd = params.fetch(:exec) opts = params.fetch(:options, []) cmd_with_opts = [cmd] + opts out = StringIO.new piper = Nanoc::Extra::Piper.new(stdout: out, stderr: $stderr) piper.run(cmd_with_opts, content) out.string end end end nanoc-4.11.0/nanoc-external/lib/nanoc/external/version.rb000066400000000000000000000001361340050175000233040ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module External VERSION = '1.0.2' end end nanoc-4.11.0/nanoc-external/nanoc-external.gemspec000066400000000000000000000011471340050175000220720ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'lib/nanoc/external/version' Gem::Specification.new do |s| s.name = 'nanoc-external' s.version = Nanoc::External::VERSION s.homepage = 'http://nanoc.ws/' s.summary = 'External filter for nanoc' s.description = 'Provides an :external filter for nanoc' s.author = 'Denis Defreyne' s.email = 'denis+rubygems@denis.ws' s.license = 'MIT' s.files = ['NEWS.md', 'README.md'] + Dir['lib/**/*.rb'] s.require_paths = ['lib'] s.required_ruby_version = '~> 2.4' s.add_runtime_dependency('nanoc', '~> 4.8') end nanoc-4.11.0/nanoc-external/nanoc-external.manifest000066400000000000000000000001721340050175000222520ustar00rootroot00000000000000NEWS.md README.md lib/nanoc-external.rb lib/nanoc/external.rb lib/nanoc/external/filter.rb lib/nanoc/external/version.rb nanoc-4.11.0/nanoc-external/spec/000077500000000000000000000000001340050175000165365ustar00rootroot00000000000000nanoc-4.11.0/nanoc-external/spec/filter_spec.rb000066400000000000000000000005441340050175000213650ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::External::Filter do example do filter = ::Nanoc::External::Filter.new({}) src = <<-SHAKESPEARE Shall I compare thee to a Summer's day? Thou art more lovely and more temperate SHAKESPEARE res = filter.run(src, exec: 'wc', options: %w[-l]) expect(res.strip).to eq('2') end end nanoc-4.11.0/nanoc-external/spec/manifest_spec.rb000066400000000000000000000002211340050175000216760ustar00rootroot00000000000000# frozen_string_literal: true describe 'manifest', chdir: false do example do expect('nanoc-external').to have_a_valid_manifest end end nanoc-4.11.0/nanoc-external/spec/spec_helper.rb000066400000000000000000000002461340050175000213560ustar00rootroot00000000000000# frozen_string_literal: true require_relative '../../common/spec/spec_helper_head' require 'nanoc/external' require_relative '../../common/spec/spec_helper_foot' nanoc-4.11.0/nanoc-live/000077500000000000000000000000001340050175000147215ustar00rootroot00000000000000nanoc-4.11.0/nanoc-live/.rspec000066400000000000000000000000611340050175000160330ustar00rootroot00000000000000-r ./spec/spec_helper.rb --format Fuubar --color nanoc-4.11.0/nanoc-live/LICENSE000066400000000000000000000020631340050175000157270ustar00rootroot00000000000000Copyright (c) 2018 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.11.0/nanoc-live/NEWS.md000066400000000000000000000010211340050175000160110ustar00rootroot00000000000000# Release notes for nanoc-live ## 1.0.0b3 (2018-08-31) Fixes: * Fixed issue which required all command-line options to be specified ## 1.0.0b2 (2018-06-10) Fixes: * Fixed issues that could cause nanoc-live to keep running in the background, using more and more memory and CPU ## 1.0.0b1 (2018-01-07) Changes: * Removed `--live-reload` (always enabled) (#1291) ## 1.0.0a2 (2017-12-09) Fixes: * Added missing dependency on `adsf-live` * Fixed errors not being printed (#1271) ## 1.0.0a1 (2017-12-03) Initial release. nanoc-4.11.0/nanoc-live/README.md000066400000000000000000000003071340050175000162000ustar00rootroot00000000000000# Nanoc::Live ⚠️ **Caution:** This project is currently experimental. The _nanoc-live_ gem provides a new `nanoc live` command, and is required to make the `nanoc compile --watch` option work. nanoc-4.11.0/nanoc-live/Rakefile000066400000000000000000000004341340050175000163670ustar00rootroot00000000000000# frozen_string_literal: true require 'rspec/core/rake_task' require 'rubocop/rake_task' RuboCop::RakeTask.new(:rubocop) RSpec::Core::RakeTask.new(:spec) do |t| t.verbose = false end task test: :spec task :gem do sh('gem build *.gemspec') end task default: %i[test rubocop] nanoc-4.11.0/nanoc-live/lib/000077500000000000000000000000001340050175000154675ustar00rootroot00000000000000nanoc-4.11.0/nanoc-live/lib/nanoc/000077500000000000000000000000001340050175000165655ustar00rootroot00000000000000nanoc-4.11.0/nanoc-live/lib/nanoc/live.rb000066400000000000000000000006611340050175000200540ustar00rootroot00000000000000# frozen_string_literal: true require 'adsf/live' require 'listen' require 'nanoc' require 'nanoc/cli' module Nanoc module Live end end require_relative 'live/version' require_relative 'live/live_recompiler' Nanoc::CLI.after_setup do root = File.dirname(__FILE__) live_command_path = File.join(root, 'live', 'commands', 'live.rb') Nanoc::CLI.add_command(Cri::Command.load_file(live_command_path, infer_name: true)) end nanoc-4.11.0/nanoc-live/lib/nanoc/live/000077500000000000000000000000001340050175000175245ustar00rootroot00000000000000nanoc-4.11.0/nanoc-live/lib/nanoc/live/commands/000077500000000000000000000000001340050175000213255ustar00rootroot00000000000000nanoc-4.11.0/nanoc-live/lib/nanoc/live/commands/live.rb000066400000000000000000000022521340050175000226120ustar00rootroot00000000000000# frozen_string_literal: true usage 'live' summary 'auto-recompile and serve' description <<~EOS Starts the live recompiler along with 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: 127.0.0.1)', default: '127.0.0.1' required :p, :port, 'specify the port to listen on (default: 3000)', transform: Nanoc::CLI::Transform::Port, default: 3000 no_params module Nanoc::Live::Commands class Live < ::Nanoc::CLI::CommandRunner def run self.class.enter_site_dir Thread.new do Thread.current.abort_on_exception = true if Thread.current.respond_to?(:report_on_exception) Thread.current.report_on_exception = false end view_options = options.merge('live-reload': true) Nanoc::CLI::Commands::View.new(view_options, [], self).run end Nanoc::Live::LiveRecompiler.new(command_runner: self).run end end end runner Nanoc::Live::Commands::Live nanoc-4.11.0/nanoc-live/lib/nanoc/live/live_recompiler.rb000066400000000000000000000064351340050175000232410ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc::Live class LiveRecompiler def initialize(command_runner:) @command_runner = command_runner end def run run_parent do |site| handle_changes(site, @command_runner) end end private def gen_changes_for_child(site) changes = [ site.data_source.item_changes, site.data_source.layout_changes, gen_config_and_rules_changes, ] SlowEnumeratorTools.batch(SlowEnumeratorTools.merge(changes)) end def run_child(pipe_write, pipe_read) pipe_write.close site = Nanoc::Int::SiteLoader.new.new_from_cwd changes_enum = gen_changes_for_child(site) yield(site) quit = Object.new parent_enum = Enumerator.new do |y| pipe_read.read y << quit end puts 'Listening for site changes…' SlowEnumeratorTools.merge([parent_enum, changes_enum]).each do |e| break if quit.equal?(e) $stderr.print 'Reloading site… ' $stderr.flush site_loader = Nanoc::Int::SiteLoader.new site = Nanoc::Int::Site.new( config: Nanoc::Int::ConfigLoader.new.new_from_cwd, data_source: site_loader.gen_data_source_for_config(site.config), code_snippets: site.code_snippets, ) $stderr.puts 'done' yield(site) end exit 0 rescue Interrupt exit 0 end def run_parent # create initial child pipe_read, pipe_write = IO.pipe fork { run_child(pipe_write, pipe_read) { |s| yield(s) } } pipe_read.close changes = gen_lib_changes puts 'Listening for lib/ changes…' changes.each do |_e| # stop child pipe_write.write('q') pipe_write.close Process.wait # create new child pipe_read, pipe_write = IO.pipe fork { run_child(pipe_write, pipe_read) { |s| yield(s) } } pipe_read.close end rescue Interrupt end def handle_changes(site, command_runner) Nanoc::CLI::ErrorHandler.handle_while(exit_on_error: false) do unsafe_handle_changes(site, command_runner) end end def unsafe_handle_changes(site, command_runner) time_before = Time.now puts 'Compiling site…' compiler = Nanoc::Int::Compiler.new_for(site) listener = Nanoc::CLI::Commands::CompileListeners::Aggregate.new( command_runner: command_runner, site: site, compiler: compiler, ) listener.run_while do compiler.run_until_end end time_after = Time.now puts "Site compiled in #{format('%.2f', time_after - time_before)}s." puts end def gen_lib_changes Nanoc::ChangesStream.new do |cl| opts = { latency: 0.0, wait_for_delay: 0.0, } listener = Listen.to('lib', opts) { |*| cl.lib } listener.start sleep end end def gen_config_and_rules_changes Nanoc::ChangesStream.new do |cl| opts = { only: /(\/|\A)(nanoc\.yaml|config\.yaml|rules|Rules|rules\.rb|Rules\.rb)\z/, latency: 0.0, wait_for_delay: 0.0, } listener = Listen.to('.', opts) { |*| cl.unknown } listener.start sleep end end end end nanoc-4.11.0/nanoc-live/lib/nanoc/live/version.rb000066400000000000000000000001341340050175000215340ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Live VERSION = '1.0.0b3' end end nanoc-4.11.0/nanoc-live/nanoc-live.gemspec000066400000000000000000000014151340050175000203220ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'lib/nanoc/live/version' Gem::Specification.new do |s| s.name = 'nanoc-live' s.version = Nanoc::Live::VERSION s.homepage = 'http://nanoc.ws/' s.summary = 'Live recompilation support for Nanoc' s.description = 'Provides support for auto-recompiling Nanoc sites.' s.author = 'Denis Defreyne' s.email = 'denis+rubygems@denis.ws' s.license = 'MIT' s.files = ['NEWS.md', 'README.md'] + Dir['lib/**/*.rb'] s.require_paths = ['lib'] s.required_ruby_version = '~> 2.4' s.add_runtime_dependency('adsf-live', '~> 1.4') s.add_runtime_dependency('cri', '~> 2.13') s.add_runtime_dependency('listen', '~> 3.0') s.add_runtime_dependency('nanoc', '~> 4.8', '>= 4.8.16') end nanoc-4.11.0/nanoc-live/nanoc-live.manifest000066400000000000000000000002011340050175000204750ustar00rootroot00000000000000NEWS.md README.md lib/nanoc/live.rb lib/nanoc/live/commands/live.rb lib/nanoc/live/live_recompiler.rb lib/nanoc/live/version.rb nanoc-4.11.0/nanoc-live/spec/000077500000000000000000000000001340050175000156535ustar00rootroot00000000000000nanoc-4.11.0/nanoc-live/spec/manifest_spec.rb000066400000000000000000000002151340050175000210160ustar00rootroot00000000000000# frozen_string_literal: true describe 'manifest', chdir: false do example do expect('nanoc-live').to have_a_valid_manifest end end nanoc-4.11.0/nanoc-live/spec/nanoc/000077500000000000000000000000001340050175000167515ustar00rootroot00000000000000nanoc-4.11.0/nanoc-live/spec/nanoc/live/000077500000000000000000000000001340050175000177105ustar00rootroot00000000000000nanoc-4.11.0/nanoc-live/spec/nanoc/live/commands/000077500000000000000000000000001340050175000215115ustar00rootroot00000000000000nanoc-4.11.0/nanoc-live/spec/nanoc/live/commands/live_spec.rb000066400000000000000000000033131340050175000240070ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Live::Commands::Live, site: true, stdio: true, fork: true do def run_cmd pipe_stdout_read, pipe_stdout_write = IO.pipe pid = fork do trap(:INT) { exit(0) } pipe_stdout_read.close $stdout = pipe_stdout_write Nanoc::CLI.run(['live']) end pipe_stdout_write.close # Wait until ready Timeout.timeout(5) do progress = 0 pipe_stdout_read.each_line do |line| progress += 1 if line.start_with?('Listening for lib/ changes') progress += 1 if line.start_with?('Listening for site changes') progress += 1 if line.start_with?('View the site at') break if progress == 3 end end sleep 0.5 # Still needs time to warm up… begin yield ensure Process.kill('INT', pid) Process.waitpid(pid) end end it 'watches' do run_cmd do File.write('content/lol.html', 'hej') sleep_until { File.file?('output/lol.html') } expect(File.read('output/lol.html')).to eq('hej') sleep 1.0 # HFS+ mtime resolution is 1s File.write('content/lol.html', 'bye') sleep_until { File.read('output/lol.html') == 'bye' } end end it 'listens' do run_cmd do File.write('content/lol.html', 'hej') sleep_until { File.file?('output/lol.html') } expect(File.read('output/lol.html')).to eq('hej') res = Net::HTTP.get_response(URI.parse('http://127.0.0.1:3000/lol.html')) expect(res.code).to eq('200') expect(res.body).to eq('hej') end end it 'listens for websocket connections' do run_cmd do socket = TCPSocket.new('localhost', 35_729) expect(socket).not_to be_closed end end end nanoc-4.11.0/nanoc-live/spec/nanoc/live/live_recompiler_spec.rb000066400000000000000000000103461340050175000244330ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Live::LiveRecompiler, site: true, stdio: true, fork: true do before do Nanoc::CLI::ErrorHandler.enable end it 'detects content changes' do command = nil command_runner = Nanoc::CLI::CommandRunner.new({}, [], command) live_recompiler = described_class.new(command_runner: command_runner) pid = fork do trap(:INT) { exit(0) } live_recompiler.run end # FIXME: wait is ugly sleep 0.5 File.write('content/lol.html', 'hej') sleep 0.1 until File.file?('output/lol.html') expect(File.read('output/lol.html')).to eq('hej') sleep 1.0 # HFS+ mtime resolution is 1s File.write('content/lol.html', 'bye') sleep 0.1 until File.read('output/lol.html') == 'bye' # Stop Process.kill('INT', pid) Process.waitpid(pid) end it 'detects rules changes' do command = nil command_runner = Nanoc::CLI::CommandRunner.new({}, [], command) live_recompiler = described_class.new(command_runner: command_runner) pid = fork do trap(:INT) { exit(0) } live_recompiler.run end # FIXME: wait is ugly sleep 0.5 File.write('content/lol.html', '<%= "hej" %>') sleep 0.1 until File.file?('output/lol.html') expect(File.read('output/lol.html')).to eq('<%= "hej" %>') sleep 1.0 # HFS+ mtime resolution is 1s File.write('Rules', <<~RULES) compile '/**/*' do filter :erb write item.identifier end RULES sleep 0.1 until File.read('output/lol.html') == 'hej' # Stop Process.kill('INT', pid) Process.waitpid(pid) end it 'detects lib changes' do command = nil command_runner = Nanoc::CLI::CommandRunner.new({}, [], command) live_recompiler = described_class.new(command_runner: command_runner) File.write('nanoc.yaml', 'site_name: Oldz') File.write('content/lol.html', '<%= @config[:site_name] %>') File.write('Rules', <<~RULES) compile '/**/*' do filter :erb write item.identifier end RULES pid = fork do trap(:INT) { exit(0) } live_recompiler.run end # FIXME: wait is ugly sleep 0.5 sleep 0.1 until File.file?('output/lol.html') expect(File.read('output/lol.html')).to eq('Oldz') sleep 1.0 # HFS+ mtime resolution is 1s File.write('nanoc.yaml', 'site_name: Newz') sleep 0.1 until File.read('output/lol.html') == 'Newz' # Stop Process.kill('INT', pid) Process.waitpid(pid) end it 'detects lib changes' do command = nil command_runner = Nanoc::CLI::CommandRunner.new({}, [], command) live_recompiler = described_class.new(command_runner: command_runner) FileUtils.mkdir_p('lib') File.write('lib/lol.rb', 'def greeting; "hi"; end') File.write('content/lol.html', '<%= greeting %>') File.write('Rules', <<~RULES) compile '/**/*' do filter :erb write item.identifier end RULES pid = fork do trap(:INT) { exit(0) } live_recompiler.run end # FIXME: wait is ugly sleep 0.5 sleep 0.1 until File.file?('output/lol.html') expect(File.read('output/lol.html')).to eq('hi') sleep 1.0 # HFS+ mtime resolution is 1s File.write('lib/lol.rb', 'def greeting; "yo"; end') sleep 0.1 until File.read('output/lol.html') == 'yo' # Stop Process.kill('INT', pid) Process.waitpid(pid) end it 'prints errors' do File.write('content/lol.html', '<%= __invalid_code_omg %>') stdout_r, stdout_w = *IO.pipe stderr_r, stderr_w = *IO.pipe pid = fork do stdout_r.close stderr_r.close trap(:INT) { exit(0) } $stdout = stdout_w $stderr = stderr_w command = nil command_runner = Nanoc::CLI::CommandRunner.new({}, [], command) live_recompiler = described_class.new(command_runner: command_runner) live_recompiler.run end stdout_w.close stderr_w.close # FIXME: wait is ugly sleep 0.5 File.write('Rules', <<~RULES) compile '/**/*' do filter :erb write item.identifier end RULES # FIXME: wait is ugly sleep 0.5 # Stop Process.kill('INT', pid) Process.waitpid(pid) expect(stderr_r.read).to include('Captain! We’ve been hit!') end end nanoc-4.11.0/nanoc-live/spec/nanoc/live_spec.rb000066400000000000000000000001511340050175000212440ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Live::VERSION do it { is_expected.to be_a(String) } end nanoc-4.11.0/nanoc-live/spec/spec_helper.rb000066400000000000000000000004201340050175000204650ustar00rootroot00000000000000# frozen_string_literal: true require_relative '../../common/spec/spec_helper_head' require 'nanoc' if Nanoc.on_windows? warn 'nanoc-live is not currently supported on Windows' exit 0 end require 'nanoc/live' require_relative '../../common/spec/spec_helper_foot' nanoc-4.11.0/nanoc/000077500000000000000000000000001340050175000137645ustar00rootroot00000000000000nanoc-4.11.0/nanoc/.rspec000066400000000000000000000000611340050175000150760ustar00rootroot00000000000000-r ./spec/spec_helper.rb --format Fuubar --color nanoc-4.11.0/nanoc/LICENSE000066400000000000000000000020701340050175000147700ustar00rootroot00000000000000Copyright (c) 2007-2018 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.11.0/nanoc/NEWS.md000066400000000000000000001631061340050175000150710ustar00rootroot00000000000000# Nanoc news ## 4.11.0 (2018-12-01) Features: * Added support for generating inline source maps for Sass (#1376) [Gregory Pakosz] * Added `:tiebreaker` option to the breadcrumbs helper, so that it can deal with ambiguity (#1368) ## 4.10.4 (2018-12-01) Fixes: * Fixed an error when importing a Sass/SCSS file outside of Nanoc’s content directory (#1378, #1379) ## 4.10.3 (2018-10-28) Fixes: * Fixed an issue which could cause `write nil` to still invoke the routing rule (#1374, #1375) ## 4.10.2 (2018-10-21) Fixes: * Fixed an issue where changing a layout rule’s parameters would not recompile items that use the layout for this layout rule (#1372, #1373) ## 4.10.1 (2018-10-16) Fixes: * Made Nanoc no longer crash when the Sass gem is not available (#1371) ## 4.10.0 (2018-10-14) Features: * Added source map support to the Sass filter (#1365) [Gregory Pakosz] * Added a `nanoc()` Sass function (#1365) [Gregory Pakosz] Fixes: * Ensured breadcrumb trail always ends in item itself (#1367) Enhancemens: * Made Nanoc error when meta-file cannot be unambiguously associated (#1370) ## 4.9.9 (2018-10-06) Fixes: * Allowed `items_root` and `layouts_root` to be null ## 4.9.8 (2018-10-05) Fixes: * Fixed missing `configuration-schema.json` file ## 4.9.7 (2018-10-05) Enhancements: * Added `title_proc:` option to the blogging helper (#1362, #1363) [Thomas Hochstein] * Dropped Ruby 2.3 support, as it is no longer maintained (#1361) * Added static configuration file verification (#1360) ## 4.9.6 (2018-09-19) Fixes: * Fixed an issue which would cause the checksum of binary items created by the filesystem data source not to be updated when the content changes (#1358, #1359) ## 4.9.5 (2018-09-15) Fixes: * Fixed an issue which would cause Nanoc to trip up when the working directory changes during compilation (#1338, #1353, #1354, #1357) ## 4.9.4 (2018-08-19) Fixes: * Fixed an issue where compiled content of items generated in the preprocessor would not be available in the postprocessor (#1341, #1348) ## 4.9.3 (2018-06-09) Enhancements: * Added an `:exclude` option to `relativize_paths`, to exclude certain paths from being relativized (#1340) ## 4.9.2 (2018-03-30) Fixes: * Fixed bug which caused `output_dir` not to be ignored when checking for excludes in pruner (#1313, #1324) * Fixed bug which caused `Filter#depend_on` not to create a proper dependency when the depended-on item has no representations (#1330) Enhancements: * Made Nanoc use hardlinks instead of copying files when possible (#1321) [Antonio Terceiro] * Made Nanoc show an understandable error when a filter returns `nil` by accident (#1323, #1326) * Made the checks also consider `.xhtml` and `.htm` files (#1328, #1329) * Made Nanoc not report thread exceptions, which would freeze Ruby (#1332) ## 4.9.1 (2018-02-22) Fixes: * Fixed issue which could cause terminal output to be jumbled up (#1312, #1316) * Made pruner ignore `output_dir` configuration attribute when checking for excludes (#1313, #1317) * Fixed handling of interrupts (SIGINT) in the `shell` command (#1309, #1318) * Added workaround for `relativize_paths` mangling `gcse:search` elements (#1319, #1320) Enhancements: * Allowed overriding `` and `` elements in Atom feed generator (#1301) [Ian Young] * Made `sh` an alias for the `shell` command (#1308) * Simplified the default configuration (#1311) * Removed routing rules from newly-created sites (#1310) ## 4.9.0 (2018-01-31) Features: * Added `Identifier#match?` (#1305) * Added `Nanoc::Check.define` * Made Nanoc read checks configuration from `nanoc.yaml` (#1296) Enhancements: * Made Nanoc write out files asynchronously (#1288) * Made the filesystem data source ignore an initial blank line after the frontmatter (#1292, #1293) * Made the `check` command run deploy checks by default (#1304) * Made Nanoc honor the `NO_COLOR` environment variable (#1306) Bugs: * Fixed a potential deadlock in the `external_links` check (#1294, #1295) * Fixed the `external_links` check’s handling of redirects without a `Location` header (#1297, #1302) * Fixed the `external_links` check’s handling of URLs with invalid components (#1303) ## 4.8.19 (2018-01-01) Enhancements: * Made `write nil` skip routing rule (#1286) ## 4.8.18 (2017-12-28) Enhancements: * Added `.tex` to the default list of text extensions (#1280) [Rien Maertens] Fixes: * Made `#breadcrumbs_trail` smarter about picking the right parent when there is ambiguity (#1278, #1279) * Made `#find_all` be callable with block instead of an argument (like `Enumerable#find_all`) (#1284, #1285) ## 4.8.17 (2017-12-16) Enhancements: * Made `LoadError` be printed more nicely (#1273, #1274, #1275) ## 4.8.16 (2017-12-09) Fixes: * Made `adsf-live` not be required for `nanoc view`, unless running with `--live-reload` ## 4.8.15 (2017-12-02) Fixes: * Made `nanoc` show item being compiled in crash logs of all errors, not just errors inheriting from `StandardError` (#1258) * Made Erubi stack traces include the filename of the item or layout that is being compiled (#1255, #1259) * Prevented `view --live-reload` from crashing when the output directory does not exist (#1254, #1261) Enhancements: * Made pruner not remove the output directory if it’s empty (#1260) ## 4.8.14 (2017-11-27) Same as 4.8.13, but published with the proper release date. ## 4.8.13 (2017-11-27) Enhancements: * Added `-L`/`--live-reload` option to `nanoc view` (#1251) ## 4.8.12 (2017-11-16) Fixes: * Fixed issue where changing `:output_dir` during preprocessing would prevent Nanoc from performing incremental compilation (#1248, #1249) Enhancements: * Allowed creation of pattern from symbols, not just strings (#1247) [Gregory Pakosz] ## 4.8.11 (2017-10-28) Enhancements: * Added `:save_with` parameter to `relativize_paths` filter (#1237) [Gregory Pakosz] * Made error messages more compact and easier to read ## 4.8.10 (2017-10-08) Fixes: * Fixed handling of four dashes for separating metadata (#1222, #1223) * Made `#raw_path` unavailable in rules context (#1231) ## 4.8.9 (2017-09-22) Fixes: * Fixed “uninitialized constant DisabledContracts::Args” error ## 4.8.8 (2017-09-22) Enhancements: * Added `#dig` to configuration (#1221) [Gregory Pakosz] ## 4.8.7 (2017-09-18) Fixes: * Fixed “uninitialized constant CGI” issue (#1218) ## 4.8.6 (2017-09-17) Fixes: * Fixed issue which would cause items not to be considered as outdated when their content and/or attributes are changed in the preprocessor (#1216, #1217) ## 4.8.5 (2017-09-07) Enhancements: * Made `nanoc check` accept hyphenated check names (e.g. `internal-links`, in addition to `internal_links`). * `compile --verbose` now prints “cached” rather than “identical” when cached compiled content is used. (#1214) ## 4.8.4 (2017-09-03) Fixes: * Fixed issue which caused the CLI to load the site at the wrong moment (#1211, #1212, #1213) ## 4.8.3 (2017-08-30) Fixes: * Disallowed full identifiers from ending with a slash (#1206) * Made `nanoc view` listen on 127.0.0.1, not 0.0.0.0, by default (#1205) Enhancements: * Made `nanoc view` print site URL (#1204) * Made `nanoc` search for parent directory containing Nanoc site (#1210) ## 4.8.2 (2017-08-19) Fixes: * Fixed compatibility issue with guard-nanoc ## 4.8.1 (2017-08-06) Enhancements: * Various speed improvements ## 4.8 (2017-07-17) Features: * 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/doc/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/doc/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.11.0/nanoc/README.md000066400000000000000000000003131340050175000152400ustar00rootroot00000000000000![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. nanoc-4.11.0/nanoc/Rakefile000066400000000000000000000006731340050175000154370ustar00rootroot00000000000000# frozen_string_literal: true require 'rake/testtask' require 'rspec/core/rake_task' require 'rubocop/rake_task' RuboCop::RakeTask.new(:rubocop) 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] task :gem do sh('gem build *.gemspec') end task default: %i[test rubocop] nanoc-4.11.0/nanoc/bin/000077500000000000000000000000001340050175000145345ustar00rootroot00000000000000nanoc-4.11.0/nanoc/bin/nanoc000077500000000000000000000005771340050175000155710ustar00rootroot00000000000000#!/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.11.0/nanoc/lib/000077500000000000000000000000001340050175000145325ustar00rootroot00000000000000nanoc-4.11.0/nanoc/lib/nanoc.rb000066400000000000000000000034061340050175000161600ustar00rootroot00000000000000# frozen_string_literal: true # Load external dependencies require 'addressable' require 'ddmemoize' require 'ddmetrics' require 'ddplugin' require 'hamster' require 'json' require 'json_schema' require 'parallel' require 'ref' require 'slow_enumerator_tools' DDMemoize.enable_metrics 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 "Nanoc #{Nanoc::VERSION} © 2007-2018 Denis Defreyne.\n" \ "Running #{RUBY_ENGINE} #{RUBY_VERSION} (#{RUBY_RELEASE_DATE}) on #{RUBY_PLATFORM} with RubyGems #{Gem::VERSION}.\n" 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 # Similar to `nil` except that it can only be compared against using # `UNDEFINED.equal?(x)`. Used in places where `nil` already has meaning, and # thus cannot be used to mean the presence of nothing. UNDEFINED = Object.new end # Load general requirements require 'base64' require 'cgi' require 'digest' require 'English' require 'fiber' require 'fileutils' require 'find' require 'forwardable' require 'net/http' require 'net/https' require 'open3' require 'pathname' require 'pstore' require 'set' require 'singleton' require 'stringio' require 'tempfile' require 'time' require 'timeout' require 'tomlrb' require 'tmpdir' require 'uri' require 'yaml' # Load Nanoc require 'nanoc/version' require 'nanoc/base' 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.11.0/nanoc/lib/nanoc/000077500000000000000000000000001340050175000156305ustar00rootroot00000000000000nanoc-4.11.0/nanoc/lib/nanoc/base.rb000066400000000000000000000006661340050175000170770ustar00rootroot00000000000000# frozen_string_literal: true # @api private module Nanoc::Int end require_relative 'base/core_ext' require_relative 'base/contracts_support' require_relative 'base/error' require_relative 'base/errors' require_relative 'base/changes_stream' require_relative 'base/assertions' require_relative 'base/entities' require_relative 'base/feature' require_relative 'base/repos' require_relative 'base/services' require_relative 'base/views' nanoc-4.11.0/nanoc/lib/nanoc/base/000077500000000000000000000000001340050175000165425ustar00rootroot00000000000000nanoc-4.11.0/nanoc/lib/nanoc/base/assertions.rb000066400000000000000000000017511340050175000212650ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Assertions class AssertionFailure < Nanoc::Int::Errors::InternalInconsistency end module Mixin def assert(assertion) return unless Nanoc::Int::ContractsSupport.enabled? unless assertion.call raise AssertionFailure, "assertion failed: #{assertion.class}" end end end class Base def call raise NotImplementedError end end class AllItemRepsHaveCompiledContent < Nanoc::Assertions::Base def initialize(compiled_content_cache:, item_reps:) @compiled_content_cache = compiled_content_cache @item_reps = item_reps end def call @item_reps.all? do |rep| @compiled_content_cache[rep] end end end class PathIsAbsolute < Nanoc::Assertions::Base def initialize(path:) @path = path end def call Pathname.new(@path).absolute? end end end end nanoc-4.11.0/nanoc/lib/nanoc/base/changes_stream.rb000066400000000000000000000014541340050175000220560ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc class ChangesStream class ChangesListener def initialize(y) @y = y end def unknown @y << :unknown end def lib @y << :lib end def to_stop(&block) if block_given? @to_stop = block else @to_stop end end end def initialize(enum: nil) @enum = enum @enum ||= Enumerator.new do |y| @listener = ChangesListener.new(y) yield(@listener) end.lazy end def stop @listener&.to_stop&.call end def map self.class.new(enum: @enum.map { |e| yield(e) }) end def to_enum @enum end def each @enum.each { |e| yield(e) } nil end end end nanoc-4.11.0/nanoc/lib/nanoc/base/contracts_support.rb000066400000000000000000000060671340050175000226740ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc::Int # @api private module ContractsSupport class Ignorer include Singleton def method_missing(*_args) # rubocop:disable Style/MethodMissingSuper self end def respond_to_missing?(*_args) true end end module DisabledContracts Any = Ignorer.instance Bool = Ignorer.instance Num = Ignorer.instance KeywordArgs = Ignorer.instance Args = 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 AbsolutePathString = 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 class AbsolutePathString < AbstractContract def self.valid?(val) val.is_a?(String) && Pathname.new(val).absolute? 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) ::Contracts.const_set('AbsolutePathString', EnabledContracts::AbsolutePathString) end @_contracts_support__should_enable end def self.enabled? setup_once 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.11.0/nanoc/lib/nanoc/base/core_ext.rb000066400000000000000000000002131340050175000206730ustar00rootroot00000000000000# frozen_string_literal: true require 'nanoc/base/core_ext/array' require 'nanoc/base/core_ext/hash' require 'nanoc/base/core_ext/string' nanoc-4.11.0/nanoc/lib/nanoc/base/core_ext/000077500000000000000000000000001340050175000203525ustar00rootroot00000000000000nanoc-4.11.0/nanoc/lib/nanoc/base/core_ext/array.rb000066400000000000000000000025621340050175000220220ustar00rootroot00000000000000# 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 def __nanoc_stringify_keys_recursively array = [] each do |element| array << (element.respond_to?(:__nanoc_stringify_keys_recursively) ? element.__nanoc_stringify_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.11.0/nanoc/lib/nanoc/base/core_ext/hash.rb000066400000000000000000000030371340050175000216250ustar00rootroot00000000000000# 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 def __nanoc_stringify_keys_recursively hash = {} each_pair do |key, value| new_key = key.is_a?(Symbol) ? key.to_s : key new_value = value.respond_to?(:__nanoc_stringify_keys_recursively) ? value.__nanoc_stringify_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.11.0/nanoc/lib/nanoc/base/core_ext/string.rb000066400000000000000000000005261340050175000222100ustar00rootroot00000000000000# 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.11.0/nanoc/lib/nanoc/base/entities.rb000066400000000000000000000020441340050175000207130ustar00rootroot00000000000000# 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.11.0/nanoc/lib/nanoc/base/entities/000077500000000000000000000000001340050175000203665ustar00rootroot00000000000000nanoc-4.11.0/nanoc/lib/nanoc/base/entities/action_sequence.rb000066400000000000000000000036521340050175000240660ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc::Int class ActionSequence include Nanoc::Int::ContractsSupport include Enumerable DDMemoize.activate(self) 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 memoized def serialize serialize_uncached end contract C::None => Array def serialize_uncached 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.11.0/nanoc/lib/nanoc/base/entities/checksum_collection.rb000066400000000000000000000013321340050175000247270ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc::Int class ChecksumCollection include Nanoc::Int::ContractsSupport 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.11.0/nanoc/lib/nanoc/base/entities/code_snippet.rb000066400000000000000000000025161340050175000233730ustar00rootroot00000000000000# 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.11.0/nanoc/lib/nanoc/base/entities/configuration-schema.json000066400000000000000000000046751340050175000254020ustar00rootroot00000000000000{ "$schema": "http://json-schema.org/draft-04/schema#", "title": "Nanoc configuration schema", "type": "object", "properties": { "text_extensions": { "type": "array", "items": { "type": "string" } }, "output_dir": { "type": "string" }, "index_filenames": { "type": "array", "items": { "type": "string" } }, "enable_output_diff": { "type": "boolean" }, "prune": { "type": "object", "additionalProperties": false, "properties": { "auto_prune": { "type": "boolean" }, "exclude": { "type": "array", "items": { "type": "string" } } } }, "commands_dirs": { "type": "array", "items": { "type": "string" } }, "lib_dirs": { "type": "array", "items": { "type": "string" } }, "data_sources": { "type": "array", "items": { "type": "object", "properties": { "type": { "type": "string" }, "items_root": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }, "layouts_root": { "anyOf": [ { "type": "string" }, { "type": "null" } ] } } } }, "string_pattern_type": { "type": "string", "enum": ["glob", "legacy"] }, "checks": { "type": "object", "properties": { "internal_links": { "type": "object", "additionalProperties": false, "properties": { "exclude": { "type": "array", "items": { "type": "string" } } } }, "external_links": { "type": "object", "additionalProperties": false, "properties": { "exclude": { "type": "array", "items": { "type": "string" } }, "exclude_files": { "type": "array", "items": { "type": "string" } } } } } }, "environments": { "type": "object", "patternProperties": { "^.*$": { "type": "object" } } } } } nanoc-4.11.0/nanoc/lib/nanoc/base/entities/configuration.rb000066400000000000000000000126211340050175000235640ustar00rootroot00000000000000# 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 tex 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', action_provider: 'rule_dsl', }.freeze # @return [String, nil] The active environment for the configuration attr_reader :env_name contract C::None => C::AbsolutePathString attr_reader :dir # 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], dir: C::AbsolutePathString] => C::Any def initialize(hash: {}, dir:, env_name: nil) @env_name = env_name @wrapped = hash.__nanoc_symbolize_keys_recursively @dir = dir validate 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, dir: @dir, 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, dir: @dir, 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::Args[C::Any] => C::Any def dig(*keys) @wrapped.dig(*keys) 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 contract C::Or[Hash, self] => self def merge(hash) self.class.new(hash: merge_recursively(@wrapped, hash.to_h), dir: @dir, env_name: @env_name) end contract C::Any => self def without(key) self.class.new(hash: @wrapped.reject { |k, _v| k == key }, dir: @dir, 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 => C::AbsolutePathString def output_dir make_absolute(self[:output_dir]).freeze end contract C::None => Symbol def action_provider self[:action_provider].to_sym end contract C::None => C::IterOf[C::AbsolutePathString] def output_dirs envs = @wrapped.fetch(ENVIRONMENTS_CONFIG_KEY, {}) res = [output_dir] + envs.values.map { |v| make_absolute(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 private def make_absolute(path) path && @dir && File.absolute_path(path, @dir).encode('UTF-8') end def merge_recursively(config1, config2) config1.merge(config2) do |_, value1, value2| if value1.is_a?(Hash) && value2.is_a?(Hash) merge_recursively(value1, value2) else value2 end end end def validate dir = File.dirname(__FILE__) schema_data = JSON.parse(File.read(dir + '/configuration-schema.json')) schema = JsonSchema.parse!(schema_data) schema.expand_references! schema.validate!(@wrapped.__nanoc_stringify_keys_recursively) end end end nanoc-4.11.0/nanoc/lib/nanoc/base/entities/content.rb000066400000000000000000000054601340050175000223720ustar00rootroot00000000000000# 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 contract C::None => Array def marshal_dump [filename, string] end contract Array => C::Any 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.11.0/nanoc/lib/nanoc/base/entities/context.rb000066400000000000000000000037371340050175000224110ustar00rootroot00000000000000# 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 Naming/AccessorMethodName def get_binding binding end # rubocop:enable Naming/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 valid_ivar_name = if defined?(Contracts) ivar_name =~ /\A@[A-Za-z_]+\z/ else true # probably good enough end (valid_ivar_name && 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.11.0/nanoc/lib/nanoc/base/entities/dependency.rb000066400000000000000000000015261340050175000230350ustar00rootroot00000000000000# 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.11.0/nanoc/lib/nanoc/base/entities/directed_graph.rb000066400000000000000000000121531340050175000236610ustar00rootroot00000000000000# 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 f g )) # # # Add edges # graph.add_edge('a', 'b') # graph.add_edge('b', 'c') # graph.add_edge('b', 'f') # graph.add_edge('b', 'g') # graph.add_edge('c', 'd') # graph.add_edge('d', 'e') # # # Get (direct) predecessors # graph.direct_predecessors_of('b').sort # # => %w( a ) # graph.predecessors_of('e').sort # # => %w( a b c d ) # # # Modify edges # graph.delete_edges_to('c') # # # Get (direct) predecessors again # graph.direct_predecessors_of('e').sort # # => %w( d ) # graph.predecessors_of('e').sort # # => %w( c d ) # # @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 @to_graph = {} @edge_props = {} invalidate_caches end def inspect s = [] @vertices.each_pair do |v2, _| direct_predecessors_of(v2).each do |v1| 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) @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 vertex The vertex to add to the graph # # @return [void] def add_vertex(vertex) return if @vertices.key?(vertex) @vertices[vertex] = @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| @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.fetch(to, Set.new) 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 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 |v2, i2| direct_predecessors_of(v2).map { |v1| [@vertices[v1], v1] }.each do |i1, v1| 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 = {} 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 end end end nanoc-4.11.0/nanoc/lib/nanoc/base/entities/document.rb000066400000000000000000000065021340050175000225340ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Int # @api private class Document include Nanoc::Int::ContractsSupport # @return [Nanoc::Int::Content] attr_reader :content # @return [Hash] def attributes @attributes.value end # @return [Nanoc::Identifier] attr_reader :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::Or[Nanoc::Identifier, String] => Nanoc::Identifier def identifier=(new_identifier) @identifier = Nanoc::Identifier.from(new_identifier) end contract Nanoc::Int::Content => C::Any def content=(new_content) @content = new_content @checksum_data = nil @content_checksum_data = nil end def set_attribute(key, value) attributes[key] = value @checksum_data = nil @attributes_checksum_data = nil 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.11.0/nanoc/lib/nanoc/base/entities/identifiable_collection.rb000066400000000000000000000064261340050175000255550ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc::Int # @api private class IdentifiableCollection DDMemoize.activate(self) include Nanoc::Int::ContractsSupport include Enumerable 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 => String def inspect "<#{self.class}>" 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.11.0/nanoc/lib/nanoc/base/entities/identifier.rb000066400000000000000000000116351340050175000230430ustar00rootroot00000000000000# 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 InvalidFullIdentifierError < ::Nanoc::Error def initialize(string) super("Invalid full identifier (ends 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 raise InvalidIdentifierError.new(string) if string !~ /\A\// raise InvalidFullIdentifierError.new(string) if string =~ /\/\z/ @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::Bool def match?(other) Nanoc::Int::Pattern.from(other).match?(to_s) 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) unless /\A\//.match?(string) 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.11.0/nanoc/lib/nanoc/base/entities/item.rb000066400000000000000000000002471340050175000216540ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc::Int # @api private class Item < ::Nanoc::Int::Document def reference "item:#{identifier}" end end end nanoc-4.11.0/nanoc/lib/nanoc/base/entities/item_collection.rb000066400000000000000000000004071340050175000240650ustar00rootroot00000000000000# 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.11.0/nanoc/lib/nanoc/base/entities/item_rep.rb000066400000000000000000000047661340050175000225340ustar00rootroot00000000000000# 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[C::AbsolutePathString]] => C::HashOf[Symbol => C::IterOf[C::AbsolutePathString]] def raw_paths=(val) @raw_paths = val end contract C::HashOf[Symbol => C::IterOf[String]] => C::HashOf[Symbol => C::IterOf[String]] def paths=(val) @paths = val 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 to_s "#{item.identifier} (rep name #{name.inspect})" end def inspect "<#{self.class} name=\"#{name}\" raw_path=\"#{raw_path}\" item.identifier=\"#{item.identifier}\">" end end end nanoc-4.11.0/nanoc/lib/nanoc/base/entities/layout.rb000066400000000000000000000002531340050175000222300ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc::Int # @api private class Layout < ::Nanoc::Int::Document def reference "layout:#{identifier}" end end end nanoc-4.11.0/nanoc/lib/nanoc/base/entities/layout_collection.rb000066400000000000000000000004151340050175000244430ustar00rootroot00000000000000# 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.11.0/nanoc/lib/nanoc/base/entities/lazy_value.rb000066400000000000000000000021221340050175000230630ustar00rootroot00000000000000# 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.11.0/nanoc/lib/nanoc/base/entities/outdatedness_reasons.rb000066400000000000000000000052541340050175000251550ustar00rootroot00000000000000# 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.11.0/nanoc/lib/nanoc/base/entities/outdatedness_status.rb000066400000000000000000000007501340050175000250220ustar00rootroot00000000000000# 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.11.0/nanoc/lib/nanoc/base/entities/pattern.rb000066400000000000000000000034761340050175000224020ustar00rootroot00000000000000# 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) when Symbol Nanoc::Int::StringPattern.new(obj.to_s) 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&.captures end contract C::None => String def to_s @regexp.to_s end end end nanoc-4.11.0/nanoc/lib/nanoc/base/entities/processing_action.rb000066400000000000000000000007571340050175000244350ustar00rootroot00000000000000# 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.11.0/nanoc/lib/nanoc/base/entities/processing_actions.rb000066400000000000000000000002501340050175000246040ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'processing_actions/filter' require_relative 'processing_actions/layout' require_relative 'processing_actions/snapshot' nanoc-4.11.0/nanoc/lib/nanoc/base/entities/processing_actions/000077500000000000000000000000001340050175000242625ustar00rootroot00000000000000nanoc-4.11.0/nanoc/lib/nanoc/base/entities/processing_actions/filter.rb000066400000000000000000000013641340050175000261000ustar00rootroot00000000000000# 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 def hash self.class.hash ^ filter_name.hash ^ params.hash end def ==(other) self.class == other.class && filter_name == other.filter_name && params == other.params end def eql?(other) self == other end end end nanoc-4.11.0/nanoc/lib/nanoc/base/entities/processing_actions/layout.rb000066400000000000000000000014611340050175000261260ustar00rootroot00000000000000# 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 def hash self.class.hash ^ layout_identifier.hash ^ params.hash end def ==(other) self.class == other.class && layout_identifier == other.layout_identifier && params == other.params end def eql?(other) self == other end end end nanoc-4.11.0/nanoc/lib/nanoc/base/entities/processing_actions/snapshot.rb000066400000000000000000000022771340050175000264560ustar00rootroot00000000000000# 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.to_a, @paths + paths.to_a) end contract C::None => String def to_s "snapshot #{@snapshot_names.inspect}, paths: #{@paths.inspect}" end def hash self.class.hash ^ snapshot_names.hash ^ paths.hash end def ==(other) self.class == other.class && snapshot_names == other.snapshot_names && paths == other.paths end def eql?(other) self == other end end end nanoc-4.11.0/nanoc/lib/nanoc/base/entities/props.rb000066400000000000000000000063761340050175000220720ustar00rootroot00000000000000# 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 Set attributes when Enumerable Set.new(attributes) else attributes end @raw_content = case raw_content when Set raw_content when Enumerable Set.new(raw_content) else raw_content end end contract C::None => String def inspect (+'').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 => String def to_s (+'').tap do |s| s << (raw_content? ? 'r' : '_') s << (attributes? ? 'a' : '_') s << (compiled_content? ? 'c' : '_') s << (path? ? 'p' : '_') 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.11.0/nanoc/lib/nanoc/base/entities/site.rb000066400000000000000000000030721340050175000216610ustar00rootroot00000000000000# 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 contract C::KeywordArgs[config: Nanoc::Int::Configuration, code_snippets: C::IterOf[Nanoc::Int::CodeSnippet], data_source: C::Named['Nanoc::DataSource']] => C::Any def initialize(config:, code_snippets:, data_source:) @config = config @code_snippets = code_snippets @data_source = data_source @preprocessed = false ensure_identifier_uniqueness(@data_source.items, 'item') ensure_identifier_uniqueness(@data_source.layouts, 'layout') end contract C::None => self def compile Nanoc::Int::Compiler.new_for(self).run_until_end self end def mark_as_preprocessed @preprocessed = true end def preprocessed? @preprocessed 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.11.0/nanoc/lib/nanoc/base/entities/snapshot_def.rb000066400000000000000000000006271340050175000233750ustar00rootroot00000000000000# 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.11.0/nanoc/lib/nanoc/base/error.rb000066400000000000000000000002231340050175000202150ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc # Generic error. Superclass for all Nanoc-specific errors. class Error < ::StandardError end end nanoc-4.11.0/nanoc/lib/nanoc/base/errors.rb000066400000000000000000000240641340050175000204110ustar00rootroot00000000000000# 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}") 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}.") 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 OutputNotWritten < Generic 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 class FilterReturnedNil < Generic def initialize(filter_name) super("The #{filter_name.inspect} filter returned nil, but is required to return a String.") end end class AmbiguousMetadataAssociation < Generic def initialize(content_filenames, meta_filename) super("There are multiple content files (#{content_filenames.join(', ')}) that could match the file containing metadata (#{meta_filename}).") end end class InternalInconsistency < Generic end end end nanoc-4.11.0/nanoc/lib/nanoc/base/feature.rb000066400000000000000000000050521340050175000205240ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc # @api private # # @example Defining a feature and checking its enabledness # # Nanoc::Feature.define('environments', version: '4.3') # Nanoc::Feature.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 # Tracking issue: # https://github.com/nanoc/features/issues/24 Nanoc::Feature.define('live_cmd', version: '4.11') # Tracking issue: # https://github.com/nanoc/features/issues/40 Nanoc::Feature.define('toml', version: '4.11') nanoc-4.11.0/nanoc/lib/nanoc/base/repos.rb000066400000000000000000000011441340050175000202170ustar00rootroot00000000000000# 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.11.0/nanoc/lib/nanoc/base/repos/000077500000000000000000000000001340050175000176725ustar00rootroot00000000000000nanoc-4.11.0/nanoc/lib/nanoc/base/repos/action_sequence_store.rb000066400000000000000000000024521340050175000246030ustar00rootroot00000000000000# 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 include Nanoc::Int::ContractsSupport contract C::KeywordArgs[config: Nanoc::Int::Configuration] => C::Any def initialize(config:) super(Nanoc::Int::Store.tmp_path_for(config: config, 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.11.0/nanoc/lib/nanoc/base/repos/aggregate_data_source.rb000066400000000000000000000013771340050175000245260ustar00rootroot00000000000000# 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 def item_changes SlowEnumeratorTools.merge(@data_sources.map(&:item_changes)) end def layout_changes SlowEnumeratorTools.merge(@data_sources.map(&:layout_changes)) end end end nanoc-4.11.0/nanoc/lib/nanoc/base/repos/checksum_store.rb000066400000000000000000000036231340050175000232410ustar00rootroot00000000000000# 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[config: Nanoc::Int::Configuration, objects: C::IterOf[c_obj]] => C::Any def initialize(config:, objects:) super(Nanoc::Int::Store.tmp_path_for(config: config, 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.11.0/nanoc/lib/nanoc/base/repos/compiled_content_cache.rb000066400000000000000000000034551340050175000246770ustar00rootroot00000000000000# 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[config: Nanoc::Int::Configuration] => C::Any def initialize(config:) super(Nanoc::Int::Store.tmp_path_for(config: config, store_name: 'compiled_content'), 2) @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] => C::HashOf[Symbol => Nanoc::Int::Content] # 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 end def prune(items:) item_identifiers = Set.new(items.map(&:identifier)) @cache.keys.each do |key| @cache.delete(key) unless item_identifiers.include?(key) end end protected def data @cache end def data=(new_data) @cache = {} new_data.each_pair do |item_identifier, content_per_rep| @cache[item_identifier] ||= content_per_rep end end end end nanoc-4.11.0/nanoc/lib/nanoc/base/repos/config_loader.rb000066400000000000000000000050601340050175000230130ustar00rootroot00000000000000# 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 = if Nanoc::Feature.enabled?(Nanoc::Feature::TOML) %w[nanoc.yaml config.yaml nanoc.toml] else %w[nanoc.yaml config.yaml] end 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: load_file(filename), dir: File.dirname(filename), ), [filename], ).with_defaults # Load environment config.with_environment end def load_file(filename) case File.extname(filename) when '.yaml' YAML.load_file(filename) when '.toml' Tomlrb.load_file(filename) else raise Nanoc::Int::Errors::InternalInconsistency, 'Unhandled config file extension' end 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: load_file(parent_path), dir: config.dir) 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.11.0/nanoc/lib/nanoc/base/repos/data_source.rb000066400000000000000000000147021340050175000225140ustar00rootroot00000000000000# 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 # 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 # @api private def item_changes warn "Caution: Data source #{self.class.identifier.inspect} does not implement #item_changes; live compilation will not pick up changes in this data source." Enumerator.new { |_y| sleep } end # @api private def layout_changes warn "Caution: Data source #{self.class.identifier.inspect} does not implement #layout_changes; live compilation will not pick up changes in this data source." Enumerator.new { |_y| sleep } 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.11.0/nanoc/lib/nanoc/base/repos/dependency_store.rb000066400000000000000000000144201340050175000235520ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc::Int # @api private class DependencyStore < ::Nanoc::Int::Store include Nanoc::Int::ContractsSupport attr_reader :items attr_reader :layouts contract Nanoc::Int::ItemCollection, Nanoc::Int::LayoutCollection, Nanoc::Int::Configuration => C::Any def initialize(items, layouts, config) super(Nanoc::Int::Store.tmp_path_for(config: config, 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(obj) @refs2objs[obj2ref(obj)] = obj 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&.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(from, to) props = @graph.props_for(obj2ref(from), obj2ref(to)) || {} 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.11.0/nanoc/lib/nanoc/base/repos/in_mem_data_source.rb000066400000000000000000000010251340050175000240320ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc::Int class InMemDataSource < Nanoc::DataSource attr_reader :items attr_reader :layouts def initialize(items, layouts, orig_data_source = nil) super({}, '/', '/', {}) @items = items @layouts = layouts @orig_data_source = orig_data_source end def item_changes @orig_data_source ? @orig_data_source.item_changes : super end def layout_changes @orig_data_source ? @orig_data_source.layout_changes : super end end end nanoc-4.11.0/nanoc/lib/nanoc/base/repos/item_rep_repo.rb000066400000000000000000000007741340050175000230600ustar00rootroot00000000000000# 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.11.0/nanoc/lib/nanoc/base/repos/outdatedness_store.rb000066400000000000000000000020541340050175000241360ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc::Int # @api private class OutdatednessStore < ::Nanoc::Int::Store include Nanoc::Int::ContractsSupport contract C::KeywordArgs[config: Nanoc::Int::Configuration] => C::Any def initialize(config:) super(Nanoc::Int::Store.tmp_path_for(config: config, store_name: 'outdatedness'), 1) @outdated_refs = Set.new end contract Nanoc::Int::ItemRep => C::Bool def include?(obj) @outdated_refs.include?(obj.reference) end contract Nanoc::Int::ItemRep => self def add(obj) @outdated_refs << obj.reference self end contract Nanoc::Int::ItemRep => self def remove(obj) @outdated_refs.delete(obj.reference) self end contract C::None => C::Bool def empty? @outdated_refs.empty? end contract C::None => self def clear @outdated_refs = Set.new self end protected def data @outdated_refs end def data=(new_data) @outdated_refs = Set.new(new_data) end end end nanoc-4.11.0/nanoc/lib/nanoc/base/repos/prefixed_data_source.rb000066400000000000000000000012101340050175000243700ustar00rootroot00000000000000# 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 def item_changes @data_source.item_changes end def layout_changes @data_source.layout_changes end end end nanoc-4.11.0/nanoc/lib/nanoc/base/repos/site_loader.rb000066400000000000000000000051221340050175000225110ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc::Int class SiteLoader 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 def gen_data_source_for_config(config) 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 Nanoc::Int::AggregateDataSource.new(data_sources_to_aggregate, config) end private def site_from_config(config) code_snippets = code_snippets_from_config(config) code_snippets.each(&:load) data_source = gen_data_source_for_config(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}/.freeze 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.11.0/nanoc/lib/nanoc/base/repos/snapshot_repo.rb000066400000000000000000000041651340050175000231110ustar00rootroot00000000000000# 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.11.0/nanoc/lib/nanoc/base/repos/store.rb000066400000000000000000000065451340050175000213650ustar00rootroot00000000000000# 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[config: Nanoc::Int::Configuration, store_name: String] => C::AbsolutePathString def self.tmp_path_for(store_name:, config:) File.absolute_path( File.join(tmp_path_prefix(config.output_dir), store_name), config.dir, ) 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.11.0/nanoc/lib/nanoc/base/services.rb000066400000000000000000000017501340050175000207150ustar00rootroot00000000000000# 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/instrumentor' 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.11.0/nanoc/lib/nanoc/base/services/000077500000000000000000000000001340050175000203655ustar00rootroot00000000000000nanoc-4.11.0/nanoc/lib/nanoc/base/services/action_provider.rb000066400000000000000000000010401340050175000240740ustar00rootroot00000000000000# 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(_obj) 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.11.0/nanoc/lib/nanoc/base/services/action_sequence_builder.rb000066400000000000000000000023611340050175000255670ustar00rootroot00000000000000# 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.11.0/nanoc/lib/nanoc/base/services/checksummer.rb000066400000000000000000000152451340050175000232270ustar00rootroot00000000000000# 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 = +'' 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::CompilationRuleContext 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.11.0/nanoc/lib/nanoc/base/services/compilation_context.rb000066400000000000000000000035501340050175000247770ustar00rootroot00000000000000# 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::ViewContextForCompilation.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::CompilationItemView.new(rep.item, view_context), rep: Nanoc::CompilationItemRepView.new(rep, view_context), item_rep: Nanoc::CompilationItemRepView.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.11.0/nanoc/lib/nanoc/base/services/compiler.rb000066400000000000000000000137501340050175000225320ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc::Int class Compiler include Nanoc::Int::ContractsSupport def initialize(site, compiled_content_cache:, checksum_store:, action_sequence_store:, action_provider:, dependency_store:, outdatedness_store:) @site = site # Needed because configuration is mutable :( @output_dirs = @site.config.output_dirs @compiled_content_cache = compiled_content_cache @checksum_store = checksum_store @action_sequence_store = action_sequence_store @dependency_store = dependency_store @action_provider = action_provider @outdatedness_store = outdatedness_store @snapshot_repo = Nanoc::Int::SnapshotRepo.new end contract Nanoc::Int::Site => Nanoc::Int::Compiler def self.new_for(site) Nanoc::Int::CompilerLoader.new.load(site) end def run_until_preprocessed @_res_preprocessed ||= begin preprocess_stage.call {} end end def run_until_reps_built @_res_reps_built ||= begin prev = run_until_preprocessed res = build_reps_stage.call prev.merge( reps: res.fetch(:reps), action_sequences: res.fetch(:action_sequences), ) end end def run_until_precompiled @_res_precompiled ||= begin prev = run_until_reps_built action_sequences = prev.fetch(:action_sequences) reps = prev.fetch(:reps) load_stores_stage.call checksums = calculate_checksums_stage.call outdatedness_checker = create_outdatedness_checker( checksums: checksums, action_sequences: action_sequences, reps: reps, ) outdated_items = determine_outdatedness_stage(outdatedness_checker, reps).call prev.merge( checksums: checksums, dependency_store: @dependency_store, outdatedness_checker: outdatedness_checker, outdated_items: outdated_items, ) end end def run_until_end res = run_until_precompiled action_sequences = res.fetch(:action_sequences) reps = res.fetch(:reps) checksums = res.fetch(:checksums) outdated_items = res.fetch(:outdated_items) forget_outdated_dependencies_stage.call(outdated_items) store_pre_compilation_state_stage(action_sequences, reps).call(checksums) prune_stage(reps).call compile_reps_stage(action_sequences, reps).call store_post_compilation_state_stage.call postprocess_stage.call(self) ensure cleanup_stage.call end def compilation_context(reps:) Nanoc::Int::CompilationContext.new( action_provider: @action_provider, reps: reps, site: @site, compiled_content_cache: @compiled_content_cache, snapshot_repo: @snapshot_repo, ) end private def create_outdatedness_checker(checksums:, action_sequences:, reps:) 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 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, ) end def prune_stage(reps) @_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(outdatedness_checker, reps) @_determine_outdatedness_stage ||= Stages::DetermineOutdatedness.new( reps: reps, outdatedness_checker: outdatedness_checker, outdatedness_store: @outdatedness_store, ) end def store_pre_compilation_state_stage(action_sequences, reps) @_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, reps) @_compile_reps_stage ||= Stages::CompileReps.new( reps: reps, outdatedness_store: @outdatedness_store, dependency_store: @dependency_store, action_sequences: action_sequences, compilation_context: compilation_context(reps: reps), 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, ) end def cleanup_stage @_cleanup_stage ||= Stages::Cleanup.new(@output_dirs) end def forget_outdated_dependencies_stage @_forget_outdated_dependencies_stage ||= Stages::ForgetOutdatedDependencies.new( dependency_store: @dependency_store, ) end end end nanoc-4.11.0/nanoc/lib/nanoc/base/services/compiler/000077500000000000000000000000001340050175000221775ustar00rootroot00000000000000nanoc-4.11.0/nanoc/lib/nanoc/base/services/compiler/phases.rb000066400000000000000000000004271340050175000240120ustar00rootroot00000000000000# 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.11.0/nanoc/lib/nanoc/base/services/compiler/phases/000077500000000000000000000000001340050175000234625ustar00rootroot00000000000000nanoc-4.11.0/nanoc/lib/nanoc/base/services/compiler/phases/abstract.rb000066400000000000000000000017561340050175000256230ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc::Int::Compiler::Phases class Abstract include Nanoc::Int::ContractsSupport def initialize(wrapped:) @wrapped = wrapped end def start @wrapped&.start end def stop @wrapped&.stop 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.11.0/nanoc/lib/nanoc/base/services/compiler/phases/cache.rb000066400000000000000000000024671340050175000250630ustar00rootroot00000000000000# 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.11.0/nanoc/lib/nanoc/base/services/compiler/phases/mark_done.rb000066400000000000000000000010161340050175000257440ustar00rootroot00000000000000# 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.11.0/nanoc/lib/nanoc/base/services/compiler/phases/recalculate.rb000066400000000000000000000031731340050175000262770ustar00rootroot00000000000000# 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.11.0/nanoc/lib/nanoc/base/services/compiler/phases/resume.rb000066400000000000000000000030411340050175000253050ustar00rootroot00000000000000# 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 DONE = Object.new 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) when DONE # rubocop:disable Lint/EmptyWhen # ignore else raise Nanoc::Int::Errors::InternalInconsistency.new( "Fiber yielded object of unexpected type #{res.class}", ) 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) DONE end @fibers[rep] end end end nanoc-4.11.0/nanoc/lib/nanoc/base/services/compiler/phases/write.rb000066400000000000000000000033411340050175000251420ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc::Int::Compiler::Phases class Write < Abstract include Nanoc::Int::ContractsSupport class Worker def initialize(queue:, snapshot_repo:) @queue = queue @snapshot_repo = snapshot_repo end def start @thread = Thread.new do Thread.current.abort_on_exception = true Thread.current.priority = -1 # schedule I/O work ASAP writer = Nanoc::Int::ItemRepWriter.new while rep = @queue.pop # rubocop:disable Lint/AssignmentInCondition writer.write_all(rep, @snapshot_repo) end end end def join @thread.join end end class WorkerPool def initialize(queue:, size:, snapshot_repo:) @workers = Array.new(size) { Worker.new(queue: queue, snapshot_repo: snapshot_repo) } end def start @workers.each(&:start) end def join @workers.each(&:join) end end QUEUE_SIZE = 40 WORKER_POOL_SIZE = 5 def initialize(snapshot_repo:, wrapped:) super(wrapped: wrapped) @snapshot_repo = snapshot_repo @queue = SizedQueue.new(QUEUE_SIZE) @worker_pool = WorkerPool.new(queue: @queue, size: WORKER_POOL_SIZE, snapshot_repo: @snapshot_repo) end def start super @worker_pool.start end def stop super @queue.close @worker_pool.join 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 @queue << rep Nanoc::Int::NotificationCenter.post(:rep_write_enqueued, rep) end end end nanoc-4.11.0/nanoc/lib/nanoc/base/services/compiler/stage.rb000066400000000000000000000003451340050175000236310ustar00rootroot00000000000000# frozen_string_literal: true class Nanoc::Int::Compiler::Stage def call(*args) Nanoc::Int::Instrumentor.call(:stage_ran, self.class) do run(*args) end end def run(*) raise NotImplementedError end end nanoc-4.11.0/nanoc/lib/nanoc/base/services/compiler/stages.rb000066400000000000000000000011441340050175000240120ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc::Int::Compiler::Stages end require_relative 'stage' 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.11.0/nanoc/lib/nanoc/base/services/compiler/stages/000077500000000000000000000000001340050175000234655ustar00rootroot00000000000000nanoc-4.11.0/nanoc/lib/nanoc/base/services/compiler/stages/build_reps.rb000066400000000000000000000012011340050175000261340ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc::Int::Compiler::Stages class BuildReps < Nanoc::Int::Compiler::Stage def initialize(site:, action_provider:) @site = site @action_provider = action_provider end def run reps = Nanoc::Int::ItemRepRepo.new 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 { reps: reps, action_sequences: action_sequences, } end end end nanoc-4.11.0/nanoc/lib/nanoc/base/services/compiler/stages/calculate_checksums.rb000066400000000000000000000021751340050175000300210ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc::Int::Compiler::Stages class CalculateChecksums < Nanoc::Int::Compiler::Stage 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.11.0/nanoc/lib/nanoc/base/services/compiler/stages/cleanup.rb000066400000000000000000000017771340050175000254550ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc::Int::Compiler::Stages class Cleanup < Nanoc::Int::Compiler::Stage def initialize(output_dirs) @output_dirs = output_dirs 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 = @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.11.0/nanoc/lib/nanoc/base/services/compiler/stages/compile_reps.rb000066400000000000000000000052241340050175000264760ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc::Int::Compiler::Stages class CompileReps < Nanoc::Int::Compiler::Stage include Nanoc::Int::ContractsSupport include Nanoc::Assertions::Mixin def initialize(reps:, outdatedness_store:, dependency_store:, action_sequences:, compilation_context:, compiled_content_cache:) @reps = reps @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 outdated_reps = @reps.select { |r| @outdatedness_store.include?(r) } selector = Nanoc::Int::ItemRepSelector.new(outdated_reps) run_phase_stack do |phase_stack| selector.each do |rep| handle_errors_while(rep) do compile_rep(rep, phase_stack: phase_stack, is_outdated: @outdatedness_store.include?(rep)) end end end assert Nanoc::Assertions::AllItemRepsHaveCompiledContent.new( compiled_content_cache: @compiled_content_cache, item_reps: @reps, ) ensure @outdatedness_store.store @compiled_content_cache.prune(items: @reps.map(&:item).uniq) @compiled_content_cache.store end private def handle_errors_while(item_rep) yield rescue Exception => e # rubocop:disable Lint/RescueException raise Nanoc::Int::Errors::CompilationError.new(e, item_rep) end def compile_rep(rep, phase_stack:, is_outdated:) phase_stack.call(rep, is_outdated: is_outdated) end def run_phase_stack phase_stack = build_phase_stack phase_stack.start yield(phase_stack) ensure phase_stack.stop end def build_phase_stack 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 nanoc-4.11.0/nanoc/lib/nanoc/base/services/compiler/stages/determine_outdatedness.rb000066400000000000000000000020261340050175000305500ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc::Int::Compiler::Stages class DetermineOutdatedness < Nanoc::Int::Compiler::Stage 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_items = select_outdated_items outdated_reps = reps_of_items(outdated_items) store_outdated_reps(outdated_reps) outdated_items end private def store_outdated_reps(reps) @outdatedness_store.clear reps.each { |r| @outdatedness_store.add(r) } end def select_outdated_items @reps .select { |r| outdated?(r) } .map(&:item) .uniq end def reps_of_items(items) Set.new(items.flat_map { |i| @reps[i] }) end def outdated?(rep) @outdatedness_store.include?(rep) || @outdatedness_checker.outdated?(rep) end end end nanoc-4.11.0/nanoc/lib/nanoc/base/services/compiler/stages/forget_outdated_dependencies.rb000066400000000000000000000006701340050175000317020ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc::Int::Compiler::Stages class ForgetOutdatedDependencies < Nanoc::Int::Compiler::Stage 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.11.0/nanoc/lib/nanoc/base/services/compiler/stages/load_stores.rb000066400000000000000000000016711340050175000263350ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc::Int::Compiler::Stages class LoadStores < Nanoc::Int::Compiler::Stage 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 load_store(@checksum_store) load_store(@compiled_content_cache) load_store(@dependency_store) load_store(@action_sequence_store) load_store(@outdatedness_store) end contract Nanoc::Int::Store => C::Any def load_store(store) Nanoc::Int::Instrumentor.call(:store_loaded, store.class) do store.load end end end end nanoc-4.11.0/nanoc/lib/nanoc/base/services/compiler/stages/postprocess.rb000066400000000000000000000006301340050175000263750ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc::Int::Compiler::Stages class Postprocess < Nanoc::Int::Compiler::Stage include Nanoc::Int::ContractsSupport def initialize(action_provider:, site:) @action_provider = action_provider @site = site end contract Nanoc::Int::Compiler => C::Any def run(compiler) @action_provider.postprocess(@site, compiler) end end end nanoc-4.11.0/nanoc/lib/nanoc/base/services/compiler/stages/preprocess.rb000066400000000000000000000015611340050175000262020ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc::Int::Compiler::Stages class Preprocess < Nanoc::Int::Compiler::Stage 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 return if @site.preprocessed? if @action_provider.need_preprocessing? @site.data_source = Nanoc::Int::InMemDataSource.new(@site.items, @site.layouts, @site.data_source) @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.mark_as_preprocessed @site.freeze end end end nanoc-4.11.0/nanoc/lib/nanoc/base/services/compiler/stages/prune.rb000066400000000000000000000007601340050175000251460ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc::Int::Compiler::Stages class Prune < Nanoc::Int::Compiler::Stage 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.11.0/nanoc/lib/nanoc/base/services/compiler/stages/store_post_compilation_state.rb000066400000000000000000000005421340050175000320120ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc::Int::Compiler::Stages class StorePostCompilationState < Nanoc::Int::Compiler::Stage 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.11.0/nanoc/lib/nanoc/base/services/compiler/stages/store_pre_compilation_state.rb000066400000000000000000000015221340050175000316120ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc::Int::Compiler::Stages class StorePreCompilationState < Nanoc::Int::Compiler::Stage 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.11.0/nanoc/lib/nanoc/base/services/compiler_loader.rb000066400000000000000000000022271340050175000240550ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc::Int # @api private class CompilerLoader def load(site, action_provider: nil) action_sequence_store = Nanoc::Int::ActionSequenceStore.new(config: site.config) dependency_store = Nanoc::Int::DependencyStore.new(site.items, site.layouts, site.config) objects = site.items.to_a + site.layouts.to_a + site.code_snippets + [site.config] checksum_store = Nanoc::Int::ChecksumStore.new(config: site.config, objects: objects) action_provider ||= Nanoc::Int::ActionProvider.named(site.config.action_provider).for(site) outdatedness_store = Nanoc::Int::OutdatednessStore.new(config: site.config) compiled_content_cache = Nanoc::Int::CompiledContentCache.new(config: site.config) params = { compiled_content_cache: compiled_content_cache, checksum_store: checksum_store, action_sequence_store: action_sequence_store, dependency_store: dependency_store, action_provider: action_provider, outdatedness_store: outdatedness_store, } Nanoc::Int::Compiler.new(site, params) end end end nanoc-4.11.0/nanoc/lib/nanoc/base/services/dependency_tracker.rb000066400000000000000000000036411340050175000245470ustar00rootroot00000000000000# 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 attr_reader :dependency_store 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.11.0/nanoc/lib/nanoc/base/services/executor.rb000066400000000000000000000100221340050175000225430ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Int class Executor 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) 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.11.0/nanoc/lib/nanoc/base/services/filter.rb000066400000000000000000000157121340050175000222050ustar00rootroot00000000000000# 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 # @api private def to_text? (@to || :text) == :text 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).tap { |res| verify(res) } 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 def verify(res) if self.class.to_binary? unless File.file?(output_filename) raise Nanoc::Int::Errors::OutputNotWritten.new(self.class.identifier, output_filename) end elsif self.class.to_text? unless res raise Nanoc::Int::Errors::FilterReturnedNil.new(self.class.identifier) end end 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) items.each(&:raw_filename) end end end nanoc-4.11.0/nanoc/lib/nanoc/base/services/instrumentor.rb000066400000000000000000000004761340050175000234720ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc::Int # @api private class Instrumentor def self.call(key, *args) stopwatch = DDMetrics::Stopwatch.new stopwatch.start yield ensure stopwatch.stop Nanoc::Int::NotificationCenter.post(key, stopwatch.duration, *args) end end end nanoc-4.11.0/nanoc/lib/nanoc/base/services/item_rep_builder.rb000066400000000000000000000012421340050175000242230ustar00rootroot00000000000000# 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.11.0/nanoc/lib/nanoc/base/services/item_rep_router.rb000066400000000000000000000057411340050175000241250ustar00rootroot00000000000000# 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} and #{rep_b} are both routed to #{output_path}.") end end class RouteWithoutSlashError < ::Nanoc::Error def initialize(output_path, rep) super("The item representation #{rep} 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 reps = [assigned_paths[path], rep].sort_by { |r| [r.item.identifier, r.name] } raise IdenticalRoutesError.new(path, *reps) 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.11.0/nanoc/lib/nanoc/base/services/item_rep_selector.rb000066400000000000000000000024121340050175000244150ustar00rootroot00000000000000# 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.11.0/nanoc/lib/nanoc/base/services/item_rep_writer.rb000066400000000000000000000041061340050175000241130ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc::Int # @api private class ItemRepWriter include Nanoc::Int::ContractsSupport include Nanoc::Assertions::Mixin 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) assert Nanoc::Assertions::PathIsAbsolute.new(path: raw_path) # 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( :rep_write_started, 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 if is_modified begin FileUtils.ln(temp_path, raw_path, force: true) rescue Errno::EXDEV FileUtils.cp(temp_path, raw_path) end end item_rep.modified = is_modified # Notify Nanoc::Int::NotificationCenter.post( :rep_write_ended, 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.11.0/nanoc/lib/nanoc/base/services/notification_center.rb000066400000000000000000000054011340050175000247400ustar00rootroot00000000000000# 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.11.0/nanoc/lib/nanoc/base/services/outdatedness_checker.rb000066400000000000000000000161031340050175000251010ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc::Int # Responsible for determining whether an item or a layout is outdated. # # @api private class OutdatednessChecker class Basic DDMemoize.activate(self) 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 DDMemoize.activate(self) 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.11.0/nanoc/lib/nanoc/base/services/outdatedness_rule.rb000066400000000000000000000013301340050175000244400ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc::Int # @api private class OutdatednessRule include Nanoc::Int::ContractsSupport include Singleton def call(obj, outdatedness_checker) Nanoc::Int::Instrumentor.call(:outdatedness_rule_ran, self.class) do apply(obj, outdatedness_checker) end 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.11.0/nanoc/lib/nanoc/base/services/outdatedness_rules.rb000066400000000000000000000010771340050175000246330ustar00rootroot00000000000000# 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.11.0/nanoc/lib/nanoc/base/services/outdatedness_rules/000077500000000000000000000000001340050175000243015ustar00rootroot00000000000000nanoc-4.11.0/nanoc/lib/nanoc/base/services/outdatedness_rules/attributes_modified.rb000066400000000000000000000026331340050175000306600ustar00rootroot00000000000000# 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.11.0/nanoc/lib/nanoc/base/services/outdatedness_rules/code_snippets_modified.rb000066400000000000000000000013651340050175000313320ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc::Int::OutdatednessRules class CodeSnippetsModified < Nanoc::Int::OutdatednessRule DDMemoize.activate(self) 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.11.0/nanoc/lib/nanoc/base/services/outdatedness_rules/content_modified.rb000066400000000000000000000010231340050175000301340ustar00rootroot00000000000000# 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.11.0/nanoc/lib/nanoc/base/services/outdatedness_rules/item_collection_extended.rb000066400000000000000000000010351340050175000316560ustar00rootroot00000000000000# 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.11.0/nanoc/lib/nanoc/base/services/outdatedness_rules/layout_collection_extended.rb000066400000000000000000000010531340050175000322350ustar00rootroot00000000000000# 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.11.0/nanoc/lib/nanoc/base/services/outdatedness_rules/not_written.rb000066400000000000000000000006031340050175000272010ustar00rootroot00000000000000# 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.11.0/nanoc/lib/nanoc/base/services/outdatedness_rules/rules_modified.rb000066400000000000000000000023361340050175000276240ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc::Int::OutdatednessRules class RulesModified < Nanoc::Int::OutdatednessRule affects_props :compiled_content, :path def apply(obj, outdatedness_checker) # Check rules of obj itself if rules_modified?(obj, outdatedness_checker) return Nanoc::Int::OutdatednessReasons::RulesModified end # Check rules of layouts used by obj layouts = layouts_touched_by(obj, outdatedness_checker) if layouts.any? { |layout| rules_modified?(layout, outdatedness_checker) } return Nanoc::Int::OutdatednessReasons::RulesModified end nil end private def rules_modified?(obj, outdatedness_checker) seq_old = outdatedness_checker.action_sequence_store[obj] seq_new = outdatedness_checker.action_sequence_for(obj).serialize !seq_old.eql?(seq_new) end def layouts_touched_by(obj, outdatedness_checker) actions = outdatedness_checker.action_sequence_store[obj] layout_actions = actions.select { |a| a.first == :layout } layout_actions.map do |layout_action| layout_pattern = layout_action[1] outdatedness_checker.site.layouts[layout_pattern] end.compact end end end nanoc-4.11.0/nanoc/lib/nanoc/base/services/outdatedness_rules/uses_always_outdated_filter.rb000066400000000000000000000012031340050175000324170ustar00rootroot00000000000000# 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.11.0/nanoc/lib/nanoc/base/services/pruner.rb000066400000000000000000000064411340050175000222320ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc # Responsible for finding and deleting files in the site’s output directory # that are not managed by Nanoc. # # @api private class Pruner include Nanoc::Int::ContractsSupport # @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 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 contract String => C::Bool def filename_excluded?(filename) pathname = Pathname.new(strip_output_dir(filename)) @exclude.any? { |e| pathname_components(pathname).include?(e) } end contract String => String def strip_output_dir(filename) if filename.start_with?(@config.output_dir) filename[@config.output_dir.size..-1] else filename end end contract Pathname => C::ArrayOf[String] 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 contract C::ArrayOf[String], C::ArrayOf[String] => self # @api private def remove_stray_files(present_files, compiled_files) (present_files - compiled_files).each do |f| delete_file(f) unless filename_excluded?(f) end self end contract C::ArrayOf[String] => self # @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 filename_excluded?(dir) delete_dir(dir) end self end contract String => C::ArrayOf[C::ArrayOf[String]] # @api private def files_and_dirs_in(dir) present_files = [] present_dirs = [] expanded_dir = File.expand_path(dir) Find.find(dir) do |f| case File.ftype(f) when 'file' unless filename_excluded?(f) present_files << f end when 'directory' if filename_excluded?(f) Find.prune elsif expanded_dir != File.expand_path(f) 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.11.0/nanoc/lib/nanoc/base/services/temp_filename_factory.rb000066400000000000000000000023611340050175000252500ustar00rootroot00000000000000# frozen_string_literal: true 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.11.0/nanoc/lib/nanoc/base/views.rb000066400000000000000000000026641340050175000202340ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'views/mixins/document_view_mixin' require_relative 'views/mixins/mutable_document_view_mixin' require_relative 'views/view_context_for_compilation' require_relative 'views/view_context_for_pre_compilation' require_relative 'views/view_context_for_shell' require_relative 'views/view' require_relative 'views/basic_item_rep_view' require_relative 'views/basic_item_rep_collection_view' require_relative 'views/basic_item_view' require_relative 'views/compilation_item_rep_view' require_relative 'views/compilation_item_rep_collection_view' require_relative 'views/compilation_item_view' require_relative 'views/config_view' require_relative 'views/identifiable_collection_view' require_relative 'views/item_collection_with_reps_view' require_relative 'views/item_collection_without_reps_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.11.0/nanoc/lib/nanoc/base/views/000077500000000000000000000000001340050175000176775ustar00rootroot00000000000000nanoc-4.11.0/nanoc/lib/nanoc/base/views/basic_item_rep_collection_view.rb000066400000000000000000000042101340050175000264330ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc class BasicItemRepCollectionView < ::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::BasicItemRepView 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::BasicItemRepView] 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 BasicItemRepCollectionView#[] to be called with a symbol (you likely want `.reps[:default]` rather than `.reps[#{rep_name}]`)" else raise ArgumentError, 'expected BasicItemRepCollectionView#[] 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::BasicItemRepView] # # @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.11.0/nanoc/lib/nanoc/base/views/basic_item_rep_view.rb000066400000000000000000000037641340050175000242350ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc class BasicItemRepView < ::Nanoc::View # @api private def initialize(item_rep, context) super(context) @item_rep = item_rep end # @abstract def item_view_class Nanoc::BasicItemView 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 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::CompilationItemView] def item item_view_class.new(@item_rep.item, @context) 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.11.0/nanoc/lib/nanoc/base/views/basic_item_view.rb000066400000000000000000000033111340050175000233530ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc class BasicItemView < ::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::CompilationItemView] 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 # @return [String, nil] The path to the file containing the uncompiled content of this item. def raw_filename @context.dependency_tracker.bounce(_unwrap, raw_content: true) _unwrap.content.filename end end end nanoc-4.11.0/nanoc/lib/nanoc/base/views/compilation_item_rep_collection_view.rb000066400000000000000000000003271340050175000276750ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc class CompilationItemRepCollectionView < ::Nanoc::BasicItemRepCollectionView # @api private def view_class Nanoc::CompilationItemRepView end end end nanoc-4.11.0/nanoc/lib/nanoc/base/views/compilation_item_rep_view.rb000066400000000000000000000030531340050175000254610ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc class CompilationItemRepView < ::Nanoc::BasicItemRepView # @abstract def item_view_class Nanoc::CompilationItemView end # Returns the item rep’s raw path. It includes the path to the output # directory and the full filename. # # @param [Symbol] snapshot The snapshot for which the path should be # returned. # # @return [String] The item rep’s raw path. 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 # Wait for file to exist if res start = Time.now sleep 0.05 until File.file?(res) || Time.now - start > 1.0 raise Nanoc::Int::Errors::InternalInconsistency, "File did not apear in time: #{res}" unless File.file?(res) end res 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 end end nanoc-4.11.0/nanoc/lib/nanoc/base/views/compilation_item_view.rb000066400000000000000000000032631340050175000246160ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc class CompilationItemView < ::Nanoc::BasicItemView # 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::BasicItemRepCollectionView] def reps Nanoc::CompilationItemRepCollectionView.new(@context.reps[_unwrap], @context) end end end nanoc-4.11.0/nanoc/lib/nanoc/base/views/config_view.rb000066400000000000000000000024361340050175000225300ustar00rootroot00000000000000# 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 # @api private def output_dir @config.output_dir 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 # @see Hash#dig def dig(*keys) @context.dependency_tracker.bounce(_unwrap, attributes: keys.take(1)) @config.dig(*keys) end end end nanoc-4.11.0/nanoc/lib/nanoc/base/views/identifiable_collection_view.rb000066400000000000000000000051361340050175000261150ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc class IdentifiableCollectionView < ::Nanoc::View include Enumerable NOTHING = Object.new # @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 = NOTHING, &block) if NOTHING.equal?(arg) @context.dependency_tracker.bounce(_unwrap, raw_content: true) return @objects.select(&block).map { |i| view_class.new(i, @context) } end 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.11.0/nanoc/lib/nanoc/base/views/item_collection_with_reps_view.rb000066400000000000000000000003161340050175000265130ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc class ItemCollectionWithRepsView < ::Nanoc::IdentifiableCollectionView # @api private def view_class Nanoc::CompilationItemView end end end nanoc-4.11.0/nanoc/lib/nanoc/base/views/item_collection_without_reps_view.rb000066400000000000000000000003131340050175000272400ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc class ItemCollectionWithoutRepsView < ::Nanoc::IdentifiableCollectionView # @api private def view_class Nanoc::BasicItemView end end end nanoc-4.11.0/nanoc/lib/nanoc/base/views/layout_collection_view.rb000066400000000000000000000002771340050175000250140ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc class LayoutCollectionView < ::Nanoc::IdentifiableCollectionView # @api private def view_class Nanoc::LayoutView end end end nanoc-4.11.0/nanoc/lib/nanoc/base/views/layout_view.rb000066400000000000000000000001761340050175000225770ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc class LayoutView < ::Nanoc::View include Nanoc::DocumentViewMixin end end nanoc-4.11.0/nanoc/lib/nanoc/base/views/mixins/000077500000000000000000000000001340050175000212065ustar00rootroot00000000000000nanoc-4.11.0/nanoc/lib/nanoc/base/views/mixins/document_view_mixin.rb000066400000000000000000000035171340050175000256150ustar00rootroot00000000000000# 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.11.0/nanoc/lib/nanoc/base/views/mixins/mutable_document_view_mixin.rb000066400000000000000000000025061340050175000273230ustar00rootroot00000000000000# 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::CompilationItemView, Nanoc::LayoutView, ]) if disallowed_value_classes.include?(value.class) raise DisallowedAttributeValueError.new(value) end _unwrap.set_attribute(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.set_attribute(k, v) } self end end end nanoc-4.11.0/nanoc/lib/nanoc/base/views/mutable_config_view.rb000066400000000000000000000004071340050175000242350ustar00rootroot00000000000000# 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.11.0/nanoc/lib/nanoc/base/views/mutable_identifiable_collection_view.rb000066400000000000000000000006531340050175000276250ustar00rootroot00000000000000# 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.11.0/nanoc/lib/nanoc/base/views/mutable_item_collection_view.rb000066400000000000000000000021111340050175000261330ustar00rootroot00000000000000# 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.11.0/nanoc/lib/nanoc/base/views/mutable_item_view.rb000066400000000000000000000002211340050175000237200ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc class MutableItemView < Nanoc::BasicItemView include Nanoc::MutableDocumentViewMixin end end nanoc-4.11.0/nanoc/lib/nanoc/base/views/mutable_layout_collection_view.rb000066400000000000000000000012631340050175000265210ustar00rootroot00000000000000# 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.11.0/nanoc/lib/nanoc/base/views/mutable_layout_view.rb000066400000000000000000000002201340050175000242760ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc class MutableLayoutView < Nanoc::LayoutView include Nanoc::MutableDocumentViewMixin end end nanoc-4.11.0/nanoc/lib/nanoc/base/views/post_compile_item_collection_view.rb000066400000000000000000000003171340050175000272050ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc class PostCompileItemCollectionView < Nanoc::IdentifiableCollectionView # @api private def view_class Nanoc::PostCompileItemView end end end nanoc-4.11.0/nanoc/lib/nanoc/base/views/post_compile_item_rep_collection_view.rb000066400000000000000000000003251340050175000300520ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc class PostCompileItemRepCollectionView < Nanoc::BasicItemRepCollectionView # @api private def view_class Nanoc::PostCompileItemRepView end end end nanoc-4.11.0/nanoc/lib/nanoc/base/views/post_compile_item_rep_view.rb000066400000000000000000000015231340050175000256400ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc class PostCompileItemRepView < ::Nanoc::BasicItemRepView def item_view_class Nanoc::PostCompileItemView end def compiled_content(snapshot: nil) compilation_context = @context.compilation_context snapshot_contents = 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.11.0/nanoc/lib/nanoc/base/views/post_compile_item_view.rb000066400000000000000000000006061340050175000247730ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc class PostCompileItemView < Nanoc::CompilationItemView 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.11.0/nanoc/lib/nanoc/base/views/view.rb000066400000000000000000000007661340050175000212070ustar00rootroot00000000000000# 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.11.0/nanoc/lib/nanoc/base/views/view_context_for_compilation.rb000066400000000000000000000014401340050175000262050ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc # @api private class ViewContextForCompilation include Nanoc::Int::ContractsSupport attr_reader :reps attr_reader :items attr_reader :dependency_tracker attr_reader :compilation_context attr_reader :snapshot_repo contract C::KeywordArgs[ reps: Nanoc::Int::ItemRepRepo, items: Nanoc::Int::IdentifiableCollection, dependency_tracker: C::Any, compilation_context: C::Any, snapshot_repo: C::Any, ] => C::Any 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.11.0/nanoc/lib/nanoc/base/views/view_context_for_pre_compilation.rb000066400000000000000000000006401340050175000270540ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc # @api private class ViewContextForPreCompilation include Nanoc::Int::ContractsSupport attr_reader :items attr_reader :dependency_tracker contract C::KeywordArgs[items: Nanoc::Int::IdentifiableCollection] => C::Any def initialize(items:) @items = items @dependency_tracker = Nanoc::Int::DependencyTracker::Null.new end end end nanoc-4.11.0/nanoc/lib/nanoc/base/views/view_context_for_shell.rb000066400000000000000000000007711340050175000250040ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc # @api private class ViewContextForShell include Nanoc::Int::ContractsSupport attr_reader :items attr_reader :reps attr_reader :dependency_tracker contract C::KeywordArgs[ items: Nanoc::Int::IdentifiableCollection, reps: Nanoc::Int::ItemRepRepo, ] => C::Any def initialize(items:, reps:) @items = items @reps = reps @dependency_tracker = Nanoc::Int::DependencyTracker::Null.new end end end nanoc-4.11.0/nanoc/lib/nanoc/checking.rb000066400000000000000000000004761340050175000177370ustar00rootroot00000000000000# 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' require_relative 'checking/loader' require_relative 'checking/issue' Nanoc::Check = Nanoc::Checking::Check nanoc-4.11.0/nanoc/lib/nanoc/checking/000077500000000000000000000000001340050175000174035ustar00rootroot00000000000000nanoc-4.11.0/nanoc/lib/nanoc/checking/check.rb000066400000000000000000000041251340050175000210070ustar00rootroot00000000000000# 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.define(ident, &block) klass = Class.new(::Nanoc::Checking::Check) { identifier(ident) } klass.send(:define_method, :run) do instance_exec(&block) end end 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 compiler = Nanoc::Int::Compiler.new_for(site) res = compiler.run_until_reps_built reps = res.fetch(:reps) compilation_context = compiler.compilation_context(reps: reps) view_context = 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) # Simplify subject # FIXME: do not depend on working directory if subject&.start_with?(Dir.getwd) subject = subject[(Dir.getwd.size + 1)..subject.size] end @issues << Issue.new(desc, subject, self.class) end # @private def output_html_filenames output_filenames.select { |f| File.extname(f) =~ /\A\.x?html?\z/ } end end end nanoc-4.11.0/nanoc/lib/nanoc/checking/checks.rb000066400000000000000000000005221340050175000211670ustar00rootroot00000000000000# 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.11.0/nanoc/lib/nanoc/checking/checks/000077500000000000000000000000001340050175000206435ustar00rootroot00000000000000nanoc-4.11.0/nanoc/lib/nanoc/checking/checks/css.rb000066400000000000000000000004211340050175000217550ustar00rootroot00000000000000# 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.11.0/nanoc/lib/nanoc/checking/checks/external_links.rb000066400000000000000000000072371340050175000242230ustar00rootroot00000000000000# frozen_string_literal: true 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_html_filenames.reject { |f| 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) ::Parallel.map(hrefs, in_threads: 10) { |href| validate(href) }.compact end def validate(href) # Parse url = nil begin url = URI.parse(href) rescue URI::Error 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 /^3..$/.match?(res.code) if i == 4 return Result.new(href, 'too many redirects') end location = extract_location(res, url) return Result.new(href, 'redirection without a target location') if location.nil? 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 extract_location(res, url) location = res['Location'] case location when nil nil when /^https?:\/\// location else base_url = url.dup base_url.path = (/^\//.match?(location) ? '' : '/') base_url.query = nil base_url.fragment = nil base_url.to_s + location 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.11.0/nanoc/lib/nanoc/checking/checks/html.rb000066400000000000000000000004311340050175000221320ustar00rootroot00000000000000# 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.11.0/nanoc/lib/nanoc/checking/checks/internal_links.rb000066400000000000000000000054761340050175000242200ustar00rootroot00000000000000# frozen_string_literal: true 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_html_filenames 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 = CGI.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) # FIXME: do not depend on current working directory origin = File.absolute_path(origin) 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.11.0/nanoc/lib/nanoc/checking/checks/mixed_content.rb000066400000000000000000000016571340050175000240410ustar00rootroot00000000000000# 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+):\/\//.freeze def run filenames = output_html_filenames 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.11.0/nanoc/lib/nanoc/checking/checks/stale.rb000066400000000000000000000015361340050175000223050ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc::Checking::Checks # @api private class Stale < ::Nanoc::Checking::Check identifier :stale def run 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.11.0/nanoc/lib/nanoc/checking/checks/w3c_validator.rb000066400000000000000000000014721340050175000237350ustar00rootroot00000000000000# 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.11.0/nanoc/lib/nanoc/checking/dsl.rb000066400000000000000000000012601340050175000205110ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc::Checking # @api private class DSL def self.from_file(filename, enabled_checks:) dsl = new(enabled_checks: enabled_checks) absolute_filename = File.expand_path(filename) dsl.instance_eval(File.read(filename), absolute_filename) dsl end def initialize(enabled_checks:) @enabled_checks = enabled_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| @enabled_checks << i } end end end nanoc-4.11.0/nanoc/lib/nanoc/checking/issue.rb000066400000000000000000000005061340050175000210610ustar00rootroot00000000000000# 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.11.0/nanoc/lib/nanoc/checking/loader.rb000066400000000000000000000020521340050175000211750ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc::Checking # @api private class Loader CHECKS_FILENAMES = ['Checks', 'Checks.rb', 'checks', 'checks.rb'].freeze def initialize(config:) @config = config end def run dsl end def enabled_checks (enabled_checks_from_dsl + enabled_checks_from_config).uniq end private def dsl_present? checks_filename && File.file?(checks_filename) end def enabled_checks_from_dsl dsl @enabled_checks_from_dsl end def enabled_checks_from_config @config.fetch(:checking, {}).fetch(:enabled_checks, []).map(&:to_sym) end def dsl @enabled_checks_from_dsl ||= [] @dsl ||= if dsl_present? Nanoc::Checking::DSL.from_file(checks_filename, enabled_checks: @enabled_checks_from_dsl) else Nanoc::Checking::DSL.new(enabled_checks: @enabled_checks_from_dsl) end end def checks_filename @_checks_filename ||= CHECKS_FILENAMES.find { |f| File.file?(f) } end end end nanoc-4.11.0/nanoc/lib/nanoc/checking/runner.rb000066400000000000000000000061371340050175000212500ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc::Checking # Runner is reponsible for running issue checks. # # @api private class Runner # @param [Nanoc::Int::Site] site The Nanoc site this runner is for def initialize(site) @site = site end def any_enabled_checks? enabled_checks.any? end # Lists all available checks on stdout. # # @return [void] def list_checks load_all 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_all run_check_classes(all_check_classes) end # Runs the checks marked for deployment. # # @return [Boolean] true if successful, false otherwise def run_for_deploy # TODO: rename to #run_enabled load_all run_check_classes(check_classes_named(enabled_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_all run_check_classes(check_classes_named(check_class_names)) end private def loader @loader ||= Nanoc::Checking::Loader.new(config: @site.config) end def load_all loader.run end def enabled_checks loader.enabled_checks 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(names) names.map do |name| name = name.to_s.tr('-', '_').to_sym klass = Nanoc::Checking::Check.named(name) raise Nanoc::Int::Errors::GenericTrivial, "Unknown check: #{name}" if klass.nil? klass end end def run_checks(classes) return [] if classes.empty? # TODO: remove me Nanoc::Int::Compiler.new_for(@site).run_until_reps_built 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(str) str || '(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.11.0/nanoc/lib/nanoc/cli.rb000066400000000000000000000143721340050175000167330ustar00rootroot00000000000000# 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' require_relative 'cli/stack_trace_writer' require_relative 'cli/transform' require_relative 'cli/commands/compile_listeners/abstract' require_relative 'cli/commands/compile_listeners/debug_printer' require_relative 'cli/commands/compile_listeners/diff_generator' require_relative 'cli/commands/compile_listeners/file_action_printer' require_relative 'cli/commands/compile_listeners/timing_recorder' require_relative 'cli/commands/compile_listeners/aggregate' # @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 = __dir__ + '/cli/commands/nanoc.rb' @root_command = Cri::Command.load_file(filename, infer_name: true) # Add help command help_cmd = Cri::Command.new_basic_help add_command(help_cmd) # Add other commands cmd_filenames = Dir[__dir__ + '/cli/commands/*.rb'] cmd_filenames.each do |cmd_filename| basename = File.basename(cmd_filename, '.rb') next if basename == 'nanoc' cmd = Cri::Command.load_file(cmd_filename, infer_name: true) 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 = Cri::Command.load_file(filename, infer_name: true) # 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 # # @deprecated def self.load_command_at(filename) # TODO: remove me one guard-nanoc is in this repo Cri::Command.load_file(filename, infer_name: true) 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? && !ENV.key?('NO_COLOR') 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.11.0/nanoc/lib/nanoc/cli/000077500000000000000000000000001340050175000163775ustar00rootroot00000000000000nanoc-4.11.0/nanoc/lib/nanoc/cli/ansi_string_colorizer.rb000066400000000000000000000013301340050175000233310ustar00rootroot00000000000000# 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] str The string to colorize # # @param [Array] attrs An array of attributes from `MAPPING` to colorize the # string with # # @return [String] A string colorized using the given attributes def self.c(str, *attrs) attrs.map { |a| MAPPING[a] }.join('') + str + "\e[0m" end end end nanoc-4.11.0/nanoc/lib/nanoc/cli/cleaning_stream.rb000066400000000000000000000063721340050175000220670ustar00rootroot00000000000000# 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(str) _nanoc_swallow_broken_pipe_errors_while do @stream.write(_nanoc_clean(str)) end end # @see IO#<< def <<(str) _nanoc_swallow_broken_pipe_errors_while do @stream.<<(_nanoc_clean(str)) 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(str) _nanoc_swallow_broken_pipe_errors_while do @stream.print(_nanoc_clean(str)) end end # @see IO#puts def puts(*str) _nanoc_swallow_broken_pipe_errors_while do @stream.puts(*str.map { |ss| _nanoc_clean(ss) }) end end # @see StringIO#string def string @stream.string end # @see IO#reopen def reopen(*args) @stream.reopen(*args) 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 Naming/AccessorMethodName def set_encoding(*args) @stream.set_encoding(*args) end # rubocop:enable Naming/AccessorMethodName protected def _nanoc_clean(str) @stream_cleaners.reduce(str.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.11.0/nanoc/lib/nanoc/cli/command_runner.rb000066400000000000000000000033071340050175000217360ustar00rootroot00000000000000# 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 do run end 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 def self.find_site_dir start_here = Dir.pwd here = start_here until Nanoc::Int::SiteLoader.cwd_is_nanoc_site? Dir.chdir('..') return nil if Dir.pwd == here here = Dir.pwd end here ensure Dir.chdir(start_here) end def self.enter_site_dir dir = find_site_dir if dir.nil? raise ::Nanoc::Int::Errors::GenericTrivial, 'The current working directory, nor any of its parents, seems to be a Nanoc site.' end return if Dir.getwd == dir $stderr.puts "Using Nanoc site in #{dir}" Dir.chdir(dir) end # Asserts that the current working directory contains a site and loads the site into memory. # # @return [void] def load_site self.class.enter_site_dir $stderr.print 'Loading site… ' $stderr.flush site = Nanoc::Int::SiteLoader.new.new_from_cwd $stderr.puts 'done' site 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.11.0/nanoc/lib/nanoc/cli/commands/000077500000000000000000000000001340050175000202005ustar00rootroot00000000000000nanoc-4.11.0/nanoc/lib/nanoc/cli/commands/check.rb000066400000000000000000000020221340050175000215760ustar00rootroot00000000000000# 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. By default, the issue checks marked for deployment will be run. " flag :a, :all, 'run all checks' flag :L, :list, 'list all checks' flag :d, :deploy, '(deprecated)' module Nanoc::CLI::Commands class Check < ::Nanoc::CLI::CommandRunner def run site = load_site 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 elsif arguments.any? runner.run_specific(arguments) else runner.run_for_deploy end unless success raise Nanoc::Int::Errors::GenericTrivial, 'One or more checks failed' end end end end runner Nanoc::CLI::Commands::Check nanoc-4.11.0/nanoc/lib/nanoc/cli/commands/compile.rb000066400000000000000000000023371340050175000221620ustar00rootroot00000000000000# frozen_string_literal: true usage 'compile [options]' summary 'compile items of this site' description <<~EOS Compile all items of the current site. EOS no_params flag nil, :diff, 'generate diff' if Nanoc::Feature.enabled?(Nanoc::Feature::LIVE_CMD) flag :w, :watch, 'watch for changes and recompile when needed' end module Nanoc::CLI::Commands class Compile < ::Nanoc::CLI::CommandRunner attr_accessor :listener_classes def run self.class.enter_site_dir if options[:watch] run_repeat else run_once end end def run_repeat require 'nanoc/live' Nanoc::Live::LiveRecompiler.new(command_runner: self).run end def run_once time_before = Time.now @site = load_site puts 'Compiling site…' compiler = Nanoc::Int::Compiler.new_for(@site) listener = Nanoc::CLI::Commands::CompileListeners::Aggregate.new( command_runner: self, site: @site, compiler: compiler, ) listener.run_while do compiler.run_until_end end time_after = Time.now puts puts "Site compiled in #{format('%.2f', time_after - time_before)}s." end end end runner Nanoc::CLI::Commands::Compile nanoc-4.11.0/nanoc/lib/nanoc/cli/commands/compile_listeners/000077500000000000000000000000001340050175000237205ustar00rootroot00000000000000nanoc-4.11.0/nanoc/lib/nanoc/cli/commands/compile_listeners/abstract.rb000066400000000000000000000013541340050175000260530ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc::CLI::Commands::CompileListeners class Abstract def initialize(*); end def self.enable_for?(command_runner, site) # rubocop:disable Lint/UnusedMethodArgument true end # @abstract def start raise NotImplementedError, "Subclasses of #{self.class} must implement #start" end # @abstract def stop; end def run_while start yield ensure 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.11.0/nanoc/lib/nanoc/cli/commands/compile_listeners/aggregate.rb000066400000000000000000000022301340050175000261700ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc::CLI::Commands::CompileListeners class Aggregate < Abstract def initialize(command_runner:, site:, compiler:) @site = site @compiler = compiler @command_runner = command_runner @listener_classes = self.class.default_listener_classes end def start setup_listeners end def stop teardown_listeners end def self.default_listener_classes [ Nanoc::CLI::Commands::CompileListeners::DiffGenerator, Nanoc::CLI::Commands::CompileListeners::DebugPrinter, Nanoc::CLI::Commands::CompileListeners::TimingRecorder, Nanoc::CLI::Commands::CompileListeners::FileActionPrinter, ] end protected def setup_listeners res = @compiler.run_until_reps_built reps = res.fetch(:reps) @listeners = @listener_classes .select { |klass| klass.enable_for?(@command_runner, @site) } .map { |klass| klass.new(reps: reps) } @listeners.each(&:start_safely) end def teardown_listeners return unless @listeners @listeners.reverse_each(&:stop_safely) end end end nanoc-4.11.0/nanoc/lib/nanoc/cli/commands/compile_listeners/debug_printer.rb000066400000000000000000000025521340050175000271020ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc::CLI::Commands::CompileListeners class DebugPrinter < Abstract # @see Listener#enable_for? def self.enable_for?(command_runner, _site) 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.11.0/nanoc/lib/nanoc/cli/commands/compile_listeners/diff_generator.rb000066400000000000000000000052241340050175000272260ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc::CLI::Commands::CompileListeners class DiffGenerator < Abstract # @see Listener#enable_for? def self.enable_for?(command_runner, site) site.config[:enable_output_diff] || command_runner.options[:diff] end # @see Listener#start def start setup_diffs old_contents = {} Nanoc::Int::NotificationCenter.on(:rep_write_started, self) do |rep, path| old_contents[rep] = File.file?(path) ? File.read(path) : nil end Nanoc::Int::NotificationCenter.on(:rep_write_ended, 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(:rep_write_started, self) Nanoc::Int::NotificationCenter.remove(:rep_write_ended, 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 # Simplify path # FIXME: do not depend on working directory if path.start_with?(Dir.getwd) path = path[(Dir.getwd.size + 1)..path.size] end # 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(str_a, str_b) # Create files Tempfile.open('old') do |old_file| Tempfile.open('new') do |new_file| # Write files old_file.write(str_a) old_file.flush new_file.write(str_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.11.0/nanoc/lib/nanoc/cli/commands/compile_listeners/file_action_printer.rb000066400000000000000000000047101340050175000302660ustar00rootroot00000000000000# 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 cached_reps = Set.new Nanoc::Int::NotificationCenter.on(:cached_content_used, self) do |rep| cached_reps << rep end Nanoc::Int::NotificationCenter.on(:rep_write_enqueued, self) do |rep| @acc_durations[rep] += Time.now - @start_times[rep] end Nanoc::Int::NotificationCenter.on(:rep_write_started, self) do |rep, _raw_path| @start_times[rep] = Time.now end Nanoc::Int::NotificationCenter.on(:rep_write_ended, 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 elsif cached_reps.include?(rep) then :cached else :identical end level = if is_created then :high elsif is_modified then :high else :low end # FIXME: do not depend on working directory if path.start_with?(Dir.getwd) path = path[(Dir.getwd.size + 1)..path.size] 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_write_enqueued, self) Nanoc::Int::NotificationCenter.remove(:rep_write_started, self) Nanoc::Int::NotificationCenter.remove(:rep_write_ended, 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.11.0/nanoc/lib/nanoc/cli/commands/compile_listeners/timing_recorder.rb000066400000000000000000000114101340050175000274160ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc::CLI::Commands::CompileListeners class TimingRecorder < Abstract attr_reader :stages_summary attr_reader :phases_summary attr_reader :outdatedness_rules_summary attr_reader :filters_summary # @see Listener#enable_for? def self.enable_for?(_command_runner, _site) Nanoc::CLI.verbosity >= 1 end # @param [Enumerable] reps def initialize(reps:) @reps = reps @stages_summary = DDMetrics::Summary.new @phases_summary = DDMetrics::Summary.new @outdatedness_rules_summary = DDMetrics::Summary.new @filters_summary = DDMetrics::Summary.new @load_stores_summary = DDMetrics::Summary.new end # @see Listener#start def start on(:stage_ran) do |duration, klass| @stages_summary.observe(duration, name: klass.to_s.sub(/.*::/, '')) end on(:outdatedness_rule_ran) do |duration, klass| @outdatedness_rules_summary.observe(duration, name: klass.to_s.sub(/.*::/, '')) end filter_stopwatches = {} on(:filtering_started) do |rep, _filter_name| stopwatch_stack = filter_stopwatches.fetch(rep) { filter_stopwatches[rep] = [] } stopwatch_stack << DDMetrics::Stopwatch.new stopwatch_stack.last.start end on(:filtering_ended) do |rep, filter_name| stopwatch = filter_stopwatches.fetch(rep).pop stopwatch.stop @filters_summary.observe(stopwatch.duration, name: filter_name.to_s) end on(:store_loaded) do |duration, klass| @load_stores_summary.observe(duration, name: klass.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] = DDMetrics::Stopwatch.new.tap(&:start) end on(:phase_ended) do |phase_name, rep| stopwatch = phase_stopwatches.fetch(rep).fetch(phase_name) stopwatch.stop @phases_summary.observe(stopwatch.duration, name: 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? @phases_summary.observe(stopwatch.duration, name: phase_name) end end # @see Listener#stop def stop print_profiling_feedback super end protected def table_for_summary(name, summary) headers = [name.to_s, 'count', 'min', '.50', '.90', '.95', 'max', 'tot'] rows = summary.map do |label, stats| name = label.fetch(:name) count = stats.count min = stats.min p50 = stats.quantile(0.50) p90 = stats.quantile(0.90) p95 = stats.quantile(0.95) tot = stats.sum max = stats.max [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, summary) headers = [name.to_s, 'tot'] rows = summary.map do |label, stats| name = label.fetch(:name) [name, "#{format('%4.2f', stats.sum)}s"] end [headers] + rows end def print_profiling_feedback print_table_for_summary(:filters, @filters_summary) print_table_for_summary(:phases, @phases_summary) if Nanoc::CLI.verbosity >= 2 print_table_for_summary_duration(:stages, @stages_summary) if Nanoc::CLI.verbosity >= 2 print_table_for_summary(:outdatedness_rules, @outdatedness_rules_summary) if Nanoc::CLI.verbosity >= 2 print_table_for_summary_duration(:load_stores, @load_stores_summary) if Nanoc::CLI.verbosity >= 2 print_ddmemoize_metrics if Nanoc::CLI.verbosity >= 2 end def print_table_for_summary(name, summary) return unless summary.any? puts print_table(table_for_summary(name, summary)) end def print_table_for_summary_duration(name, summary) return unless summary.any? puts print_table(table_for_summary_durations(name, summary)) end def print_ddmemoize_metrics puts DDMemoize.print_metrics end def print_table(rows) puts DDMetrics::Table.new(rows).to_s end end end nanoc-4.11.0/nanoc/lib/nanoc/cli/commands/create-site.rb000066400000000000000000000154131340050175000227360ustar00rootroot00000000000000# 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' param :path 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 # 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])} prune: auto_prune: true data_sources: - type: filesystem encoding: utf-8 EOS DEFAULT_RULES = <<~EOS unless defined? DEFAULT_RULES #!/usr/bin/env ruby compile '/index.html' do layout '/default.*' write '/index.html' end compile '/**/*.html' do layout '/default.*' write item.identifier.without_ext + '/index.html' 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.*' # write item.identifier.without_ext + '/index.html' #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 path = arguments[:path] # 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.11.0/nanoc/lib/nanoc/cli/commands/deploy.rb000066400000000000000000000072531340050175000220300ustar00rootroot00000000000000# 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 @site = load_site Nanoc::Int::Compiler.new_for(@site).run_until_preprocessed 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.each_key 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.any_enabled_checks? 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.11.0/nanoc/lib/nanoc/cli/commands/nanoc.rb000066400000000000000000000021161340050175000216230ustar00rootroot00000000000000# 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.11.0/nanoc/lib/nanoc/cli/commands/prune.rb000066400000000000000000000033041340050175000216560ustar00rootroot00000000000000# 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 no_params 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 @site = load_site res = Nanoc::Int::Compiler.new_for(@site).run_until_reps_built reps = res.fetch(:reps) if options.key?(:yes) Nanoc::Pruner.new(@site.config, reps, exclude: prune_config_exclude).run elsif options.key?(:'dry-run') Nanoc::Pruner.new(@site.config, 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.11.0/nanoc/lib/nanoc/cli/commands/shell.rb000066400000000000000000000027621340050175000216430ustar00rootroot00000000000000# frozen_string_literal: true usage 'shell' summary 'open a shell on the Nanoc environment' aliases 'console', 'sh' description " Open an IRB shell on a context that contains @items, @layouts, and @config. " flag :p, :preprocess, 'run preprocessor' no_params module Nanoc::CLI::Commands class Shell < ::Nanoc::CLI::CommandRunner def run require 'pry' # Needed to make pry behave properly sometimes -- see nanoc/nanoc#1309 Signal.trap('SIGINT') { raise Interrupt } @site = load_site Nanoc::Int::Compiler.new_for(@site).run_until_preprocessed if 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(site.config.action_provider).for(site) builder = Nanoc::Int::ItemRepBuilder.new(site, action_provider, reps) builder.run end end def self.view_context_for(site) Nanoc::ViewContextForShell.new( items: site.items, reps: reps_for(site), ) 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.11.0/nanoc/lib/nanoc/cli/commands/show-data.rb000066400000000000000000000121701340050175000224150ustar00rootroot00000000000000# 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 no_params module Nanoc::CLI::Commands class ShowData < ::Nanoc::CLI::CommandRunner def run site = load_site res = Nanoc::Int::Compiler.new_for(site).run_until_precompiled items = site.items layouts = site.layouts reps = res.fetch(:reps) dependency_store = res.fetch(:dependency_store) outdatedness_checker = res.fetch(:outdatedness_checker) # Print data print_item_dependencies(items, dependency_store) print_item_rep_paths(items, reps) print_item_rep_outdatedness(items, outdatedness_checker, reps) print_layouts(layouts, outdatedness_checker) 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, reps) prev = nil items.sort_by(&:identifier).each do |item| 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 when Nanoc::Int::IdentifiableCollection case dep.props.raw_content when true 'matching any' else "matching any of #{dep.props.raw_content.sort.join(', ')}" end else raise Nanoc::Int::Errors::InternalInconsistency, "unexpected pred type #{pred}" end if pred puts " [ #{format '%6s', type} ] (#{dep.props}) #{pred_identifier}" else puts ' ( removed item )' end end puts ' (nothing)' if dependencies.empty? end end def print_item_rep_paths(items, reps) print_header('Item representation paths') sorted_reps_with_prev(items, reps) 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, outdatedness_checker, reps) print_header('Item representation outdatedness') sorted_reps_with_prev(items, reps) do |rep, prev| puts if prev puts "item #{rep.item.identifier}, rep #{rep.name}:" print_outdatedness_reasons_for(rep, outdatedness_checker) end end def print_layouts(layouts, outdatedness_checker) print_header('Layouts') sorted_with_prev(layouts) do |layout, prev| puts if prev puts "layout #{layout.identifier}:" print_outdatedness_reasons_for(layout, outdatedness_checker) end end def print_outdatedness_reasons_for(obj, 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.11.0/nanoc/lib/nanoc/cli/commands/show-plugins.rb000066400000000000000000000050411340050175000231640ustar00rootroot00000000000000# 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 no_params module Nanoc::CLI::Commands class ShowPlugins < ::Nanoc::CLI::CommandRunner def run # Get list of plugins (before and after) plugins_before = PLUGIN_CLASSES.keys.each_with_object({}) { |c, acc| acc[c] = c.all } site = load_site 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.11.0/nanoc/lib/nanoc/cli/commands/show-rules.rb000066400000000000000000000031151340050175000226350ustar00rootroot00000000000000# 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. " no_params module Nanoc::CLI::Commands class ShowRules < ::Nanoc::CLI::CommandRunner def run site = load_site res = Nanoc::Int::Compiler.new_for(site).run_until_reps_built reps = res.fetch(:reps) action_provider = Nanoc::Int::ActionProvider.named(site.config.action_provider).for(site) rules = action_provider.rules_collection items = site.items.sort_by(&:identifier) layouts = site.layouts.sort_by(&:identifier) items.each { |e| explain_item(e, rules: rules, reps: reps) } layouts.each { |e| explain_layout(e, rules: rules) } end def explain_item(item, rules:, reps:) puts(fmt_heading("Item #{item.identifier}") + ':') 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, rules:) puts(fmt_heading("Layout #{layout.identifier}") + ':') found = false rules.layout_filter_mapping.each_key do |pattern| if pattern.match?(layout.identifier) puts " #{pattern}" found = true break end end unless found puts ' (none)' end puts end def fmt_heading(str) Nanoc::CLI::ANSIStringColorizer.c(str, :bold, :yellow) end end end runner Nanoc::CLI::Commands::ShowRules nanoc-4.11.0/nanoc/lib/nanoc/cli/commands/view.rb000066400000000000000000000035211340050175000215000ustar00rootroot00000000000000# 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: 127.0.0.1)', default: '127.0.0.1' required :p, :port, 'specify the port to listen on (default: 3000)', transform: Nanoc::CLI::Transform::Port, default: 3000 flag :L, :'live-reload', 'reload on changes' no_params module Nanoc::CLI::Commands class View < ::Nanoc::CLI::CommandRunner DEFAULT_HANDLER_NAME = :thin def run load_adsf config = Nanoc::Int::ConfigLoader.new.new_from_cwd # Create output dir so that viewer/watcher doesn’t explode. FileUtils.mkdir_p(config.output_dir) server = Adsf::Server.new( root: File.absolute_path(config.output_dir), live: options[:'live-reload'], index_filenames: config[:index_filenames], host: options.fetch(:host), port: options.fetch(:port), handler: options[:handler], ) server.run 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.11.0/nanoc/lib/nanoc/cli/error_handler.rb000066400000000000000000000250411340050175000215540ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc::CLI # Catches errors and prints nice diagnostic messages, then exits. # # @api private class ErrorHandler # Enables error handling in the given block. # # @return [void] def self.handle_while(exit_on_error: true) if @disabled yield else new.handle_while(exit_on_error: exit_on_error) { yield } 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(exit_on_error:) # 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 Interrupt exit(1) rescue StandardError, ScriptError => e handle_error(e, exit_on_error: exit_on_error) end def handle_error(error, exit_on_error:) if trivial?(error) $stderr.puts $stderr.puts "Error: #{error.message}" resolution = resolution_for(error) if resolution $stderr.puts $stderr.puts resolution end else print_error(error) end exit(1) if exit_on_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 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) stream.puts stream.puts 'Captain! We’ve been hit!' if forwards_stack_trace? write_stack_trace(stream, error) write_error_message(stream, error) write_item_rep(stream, error) else write_error_message(stream, error) write_item_rep(stream, error) write_stack_trace(stream, error) end stream.puts stream.puts 'A detailed crash log has been written to ./crash.log.' 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) stream.puts "Crashlog created at #{Time.now}" 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 # @api private def forwards_stack_trace? ruby_version.start_with?('2.5') end # @api private def trivial?(error) case error when Nanoc::Int::Errors::GenericTrivial, Errno::EADDRINUSE true when LoadError GEM_NAMES.key?(gem_name_from_load_error(error)) else false end end protected # @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', 'asciidoctor' => 'asciidoctor', 'bluecloth' => 'bluecloth', 'builder' => 'builder', 'coderay' => 'coderay', 'coffee-script' => 'coffee-script', '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', 'mustache' => 'mustache', 'nanoc/live' => 'nanoc-live', 'nokogiri' => 'nokogiri', 'nokogumbo' => 'nokogumbo', 'pandoc-ruby' => 'pandoc-ruby', 'pry' => 'pry', 'rack' => 'rack', 'rack/cache' => 'rack-cache', 'rainpress' => 'rainpress', 'rdiscount' => 'rdiscount', 'redcarpet' => 'redcarpet', 'redcloth' => 'RedCloth', 'rubypants' => 'rubypants', 'sass' => 'sass', 'slim' => 'slim', 'typogruby' => 'typogruby', 'uglifier' => 'uglifier', 'w3c_validators' => 'w3c_validators', 'yuicompressor' => 'yuicompressor', }.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 gem_name = gem_name_from_load_error(error) if gem_name if using_bundler? <<~RES 1. Add `gem '#{gem_name}'` to your Gemfile 2. Run `bundle install` 3. Re-run this command RES else "Install the '#{gem_name}' gem using `gem install #{gem_name}`." end end when RuntimeError if /^can't modify frozen/.match?(error.message) '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 when Errno::EADDRINUSE 'There already is a server running. Either shut down that one, or ' \ 'specify a different port to run this server on.' end end def gem_name_from_load_error(error) matches = error.message.match(/(no such file to load|cannot load such file) -- ([^\s]+)/) return nil if matches.nil? GEM_NAMES[matches[2]] end def using_bundler? defined?(Bundler) && Bundler::SharedHelpers.in_bundle? end def ruby_version RUBY_VERSION end def write_section_header(stream, title, verbose: false) stream.puts if verbose stream.puts '===== ' + title.upcase + ':' stream.puts end end def write_error_message(stream, error, verbose: false) write_section_header(stream, 'Message', verbose: verbose) error = unwrap_error(error) message = "#{error.class}: #{message_for_error(error)}" unless verbose message = "\e[1m\e[31m" + message + "\e[0m" end stream.puts message resolution = resolution_for(error) stream.puts resolution.to_s if resolution end def message_for_error(error) case error when JsonSchema::AggregateError "\n" + error.errors.map { |e| " * #{e.pointer}: #{e.message}" }.join("\n") else error.message end 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 "Current item: #{item_rep.item.identifier} (#{item_rep.name.inspect} representation)" end def write_stack_trace(stream, error, verbose: false) write_section_header(stream, 'Stack trace', verbose: verbose) writer = StackTraceWriter.new(stream, forwards: forwards_stack_trace?) writer.write(unwrap_error(error), verbose: verbose) 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.11.0/nanoc/lib/nanoc/cli/logger.rb000066400000000000000000000036661340050175000202160ustar00rootroot00000000000000# frozen_string_literal: true 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`, `:cached`, `:skip` and `:delete`) # onto their ANSI color codes. ACTION_COLORS = { create: "\e[32m", # green update: "\e[33m", # yellow identical: '', # (nothing) cached: '', # (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 @mutex = Mutex.new end # Logs a file-related action. # # @param [:high, :low] level The importance of this action # # @param [:create, :update, :identical, :cached, :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) return if @level == :off return if @level != :low && @level != level @mutex.synchronize do io.puts(message) end end end end nanoc-4.11.0/nanoc/lib/nanoc/cli/stack_trace_writer.rb000066400000000000000000000023661340050175000226120ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc::CLI # @api private class StackTraceWriter def initialize(stream, forwards:) @stream = stream @forwards = forwards end def write(error, verbose:) if @forwards write_forwards(error, verbose: verbose) else write_backwards(error, verbose: verbose) end end private def write_backwards(error, verbose:) 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} lines omitted (see crash.log for details)" end end def write_forwards(error, verbose:) count = 10 backtrace = verbose ? error.backtrace : error.backtrace.take(count) if !verbose && error.backtrace.size > count @stream.puts " ... #{error.backtrace.size - count} lines omitted (see crash.log for details)" end backtrace.each_with_index.to_a.reverse_each do |(item, index)| if index.zero? @stream.puts " #{item}" else @stream.puts " #{index}. from #{item}" end end end end end nanoc-4.11.0/nanoc/lib/nanoc/cli/stream_cleaners.rb000066400000000000000000000003311340050175000220700ustar00rootroot00000000000000# 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.11.0/nanoc/lib/nanoc/cli/stream_cleaners/000077500000000000000000000000001340050175000215465ustar00rootroot00000000000000nanoc-4.11.0/nanoc/lib/nanoc/cli/stream_cleaners/abstract.rb000066400000000000000000000012761340050175000237040ustar00rootroot00000000000000# 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] str The string to clean # # @return [String] The cleaned string def clean(str) # rubocop:disable Lint/UnusedMethodArgument raise NotImplementedError, 'Subclasses of Nanoc::CLI::StreamCleaners::Abstract must implement #clean' end end end nanoc-4.11.0/nanoc/lib/nanoc/cli/stream_cleaners/ansi_colors.rb000066400000000000000000000004271340050175000244110ustar00rootroot00000000000000# 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(str) str.gsub(/\e\[.+?m/, '') end end end nanoc-4.11.0/nanoc/lib/nanoc/cli/stream_cleaners/utf8.rb000066400000000000000000000007151340050175000227640ustar00rootroot00000000000000# 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(str) # FIXME: this decomposition is not generally usable str .unicode_normalize(:nfkd) .tr('─┼“”‘’', '-+""\'\'') .gsub('©', '(c)') end end end nanoc-4.11.0/nanoc/lib/nanoc/cli/transform.rb000066400000000000000000000004551340050175000207430ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc::CLI # @api private module Transform module Port RANGE = (0x0001..0xffff).freeze def self.call(data) Integer(data).tap do |int| raise 'not a valid port' unless RANGE.cover?(int) end end end end end nanoc-4.11.0/nanoc/lib/nanoc/data_sources.rb000066400000000000000000000001701340050175000206270ustar00rootroot00000000000000# frozen_string_literal: true # @api private module Nanoc::DataSources end require_relative 'data_sources/filesystem' nanoc-4.11.0/nanoc/lib/nanoc/data_sources/000077500000000000000000000000001340050175000203045ustar00rootroot00000000000000nanoc-4.11.0/nanoc/lib/nanoc/data_sources/filesystem.rb000066400000000000000000000330501340050175000230160ustar00rootroot00000000000000# 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 def item_changes changes_for_dir(content_dir_name) end def layout_changes changes_for_dir(layouts_dir_name) end def changes_for_dir(dir) require 'listen' Nanoc::ChangesStream.new do |cl| listener = Listen.to(dir, latency: 0.0, wait_for_delay: 0.0) do |_modifieds, _addeds, _deleteds| cl.unknown end listener.start cl.to_stop { listener.stop } sleep end 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? each_content_meta_pair_in(dir_name) do |content_filename, meta_filename| 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 res end # Enumerates each pair of content file and meta file. If there is ambiguity, it will raise an error. def each_content_meta_pair_in(dir_name) all_split_files_in(dir_name).each do |base_filename, (meta_ext, content_exts)| meta_filename = filename_for(base_filename, meta_ext) content_filenames = content_exts.map { |e| filename_for(base_filename, e) } have_possible_ambiguity = meta_filename && content_filenames.size > 1 if have_possible_ambiguity && content_filenames.count { |fn| !parser.frontmatter?(fn) } != 1 raise Nanoc::Int::Errors::AmbiguousMetadataAssociation.new(content_filenames, meta_filename) end content_filenames.each do |content_filename| real_meta_filename = if have_possible_ambiguity && parser.frontmatter?(content_filename) nil else meta_filename end yield(content_filename, real_meta_filename) end end end def content_checksum_data_for(proto_doc) data = proto_doc.content_checksum_data data ? Digest::SHA1.digest(data) : nil 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 /(^|\/)index(\.[^\/]+)?$/.match?(filename) 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 def parser @parser ||= Parser.new(config: @config) end def parse(content_filename, meta_filename) parser.call(content_filename, meta_filename) end end end require_relative 'filesystem/tools' require_relative 'filesystem/errors' require_relative 'filesystem/parser' nanoc-4.11.0/nanoc/lib/nanoc/data_sources/filesystem/000077500000000000000000000000001340050175000224705ustar00rootroot00000000000000nanoc-4.11.0/nanoc/lib/nanoc/data_sources/filesystem/errors.rb000066400000000000000000000033241340050175000243330ustar00rootroot00000000000000# 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.11.0/nanoc/lib/nanoc/data_sources/filesystem/parser.rb000066400000000000000000000044201340050175000243110ustar00rootroot00000000000000# frozen_string_literal: true # @api private class Nanoc::DataSources::Filesystem class Parser SEPARATOR = /(-{5}|-{3})/.source 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 initialize(config:) @config = config end # @return [ParseResult] def call(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 ? Tools.read_file(content_filename, config: @config) : '' meta_raw = Tools.read_file(meta_filename, config: @config) 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 = Tools.read_file(content_filename, config: @config) unless /\A#{SEPARATOR}\s*$/.match?(data) return ParseResult.new(content: data, attributes: {}, attributes_data: '') end pieces = data.split(/^#{SEPARATOR}[ \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].sub(/\A\n/, '') 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 def frontmatter?(filename) data = Tools.read_file(filename, config: @config) /\A#{SEPARATOR}\s*$/.match?(data) end def verify_meta(meta, filename) return if meta.is_a?(Hash) raise Errors::InvalidMetadata.new(filename, meta.class) end end end nanoc-4.11.0/nanoc/lib/nanoc/data_sources/filesystem/tools.rb000066400000000000000000000145061340050175000241630ustar00rootroot00000000000000# 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 module_function # 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 # 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 # 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 # 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_file(filename, config:) # 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 nanoc-4.11.0/nanoc/lib/nanoc/deploying.rb000066400000000000000000000002421340050175000201450ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc # @api private module Deploying end end require 'nanoc/deploying/deployer' require 'nanoc/deploying/deployers' nanoc-4.11.0/nanoc/lib/nanoc/deploying/000077500000000000000000000000001340050175000176225ustar00rootroot00000000000000nanoc-4.11.0/nanoc/lib/nanoc/deploying/deployer.rb000066400000000000000000000026011340050175000217710ustar00rootroot00000000000000# 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.11.0/nanoc/lib/nanoc/deploying/deployers.rb000066400000000000000000000002731340050175000221570ustar00rootroot00000000000000# 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.11.0/nanoc/lib/nanoc/deploying/deployers/000077500000000000000000000000001340050175000216305ustar00rootroot00000000000000nanoc-4.11.0/nanoc/lib/nanoc/deploying/deployers/fog.rb000066400000000000000000000123631340050175000227350ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc::Deploying::Deployers # A deployer that deploys a site using [fog](https://github.com/fog/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(str) if @is_dry_run puts "[dry run] #{str}" else puts str end end end # @see Nanoc::Deploying::Deployer#run def run require 'fog/core' src = File.expand_path(source_path) bucket = config[:bucket] || config[:bucket_name] path = config[:path] cdn_id = config[:cdn_id] if 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) rescue ArgumentError require "fog/#{config[:provider]}" ::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.11.0/nanoc/lib/nanoc/deploying/deployers/git.rb000066400000000000000000000062621340050175000227460ustar00rootroot00000000000000# 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.11.0/nanoc/lib/nanoc/deploying/deployers/rsync.rb000066400000000000000000000035001340050175000233110ustar00rootroot00000000000000# 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.11.0/nanoc/lib/nanoc/extra.rb000066400000000000000000000006431340050175000173030ustar00rootroot00000000000000# 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' require_relative 'extra/piper' require_relative 'extra/jruby_nokogiri_warner' require_relative 'extra/core_ext' nanoc-4.11.0/nanoc/lib/nanoc/extra/000077500000000000000000000000001340050175000167535ustar00rootroot00000000000000nanoc-4.11.0/nanoc/lib/nanoc/extra/core_ext.rb000066400000000000000000000001531340050175000211070ustar00rootroot00000000000000# frozen_string_literal: true require 'nanoc/extra/core_ext/pathname' require 'nanoc/extra/core_ext/time' nanoc-4.11.0/nanoc/lib/nanoc/extra/core_ext/000077500000000000000000000000001340050175000205635ustar00rootroot00000000000000nanoc-4.11.0/nanoc/lib/nanoc/extra/core_ext/pathname.rb000066400000000000000000000010221340050175000227000ustar00rootroot00000000000000# 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.11.0/nanoc/lib/nanoc/extra/core_ext/time.rb000066400000000000000000000006431340050175000220510ustar00rootroot00000000000000# 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.11.0/nanoc/lib/nanoc/extra/jruby_nokogiri_warner.rb000066400000000000000000000023501340050175000237120ustar00rootroot00000000000000# frozen_string_literal: true 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.11.0/nanoc/lib/nanoc/extra/link_collector.rb000066400000000000000000000037111340050175000223050ustar00rootroot00000000000000# frozen_string_literal: true 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.11.0/nanoc/lib/nanoc/extra/piper.rb000066400000000000000000000021271340050175000204210ustar00rootroot00000000000000# frozen_string_literal: true 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.11.0/nanoc/lib/nanoc/filters.rb000066400000000000000000000021321340050175000176230ustar00rootroot00000000000000# 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.11.0/nanoc/lib/nanoc/filters/000077500000000000000000000000001340050175000173005ustar00rootroot00000000000000nanoc-4.11.0/nanoc/lib/nanoc/filters/asciidoc.rb000066400000000000000000000011201340050175000213750ustar00rootroot00000000000000# 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.11.0/nanoc/lib/nanoc/filters/asciidoctor.rb000066400000000000000000000003621340050175000221310ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc::Filters class Asciidoctor < Nanoc::Filter identifier :asciidoctor requires 'asciidoctor' def run(content, params = {}) ::Asciidoctor.render(content, params) end end end nanoc-4.11.0/nanoc/lib/nanoc/filters/bluecloth.rb000066400000000000000000000007351340050175000216130ustar00rootroot00000000000000# 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.11.0/nanoc/lib/nanoc/filters/coffeescript.rb000066400000000000000000000007721340050175000223070ustar00rootroot00000000000000# 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.11.0/nanoc/lib/nanoc/filters/colorize_syntax.rb000066400000000000000000000106251340050175000230650ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc::Filters # @api private class ColorizeSyntax < Nanoc::Filter identifier :colorize_syntax requires 'nokogiri' 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'] || +'' 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 /can't modify frozen string/.match?(e.message) 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(str) str.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.11.0/nanoc/lib/nanoc/filters/colorize_syntax/000077500000000000000000000000001340050175000225345ustar00rootroot00000000000000nanoc-4.11.0/nanoc/lib/nanoc/filters/colorize_syntax/colorizers.rb000066400000000000000000000110541340050175000252550ustar00rootroot00000000000000# 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_key do |key| 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 params.fetch(:legacy, false) 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::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.11.0/nanoc/lib/nanoc/filters/erb.rb000066400000000000000000000020711340050175000203750ustar00rootroot00000000000000# frozen_string_literal: true

module Nanoc::Filters
  # @api private
  class ERB < Nanoc::Filter
    identifier :erb

    requires 'erb'

    # Runs the content through [ERB](http://ruby-doc.org/stdlib/libdoc/erb/rdoc/classes/ERB.html).
    #
    # @param [String] content The content to filter
    #
    # @option params [Integer] :safe_level (nil) The safe level (`$SAFE`) to
    #   use while running this filter
    #
    # @option params [String] :trim_mode (nil) The trim mode to use
    #
    # @return [String] The filtered content
    def run(content, params = {})
      # Add locals
      assigns.merge!(params[:locals] || {})

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

      # Get binding
      proc = assigns[:content] ? -> { 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.11.0/nanoc/lib/nanoc/filters/erubi.rb000066400000000000000000000016631340050175000207410ustar00rootroot00000000000000# 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
      engine_opts = { bufvar: '_erbout', filename: filename }.merge(params)
      engine = ::Erubi::Engine.new(content, engine_opts)
      eval(engine.src, assigns_binding, filename)
    end
  end
end
nanoc-4.11.0/nanoc/lib/nanoc/filters/erubis.rb000066400000000000000000000015551340050175000211240ustar00rootroot00000000000000# 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.11.0/nanoc/lib/nanoc/filters/haml.rb000066400000000000000000000013351340050175000205500ustar00rootroot00000000000000# 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.11.0/nanoc/lib/nanoc/filters/handlebars.rb000066400000000000000000000015741340050175000217370ustar00rootroot00000000000000# 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.11.0/nanoc/lib/nanoc/filters/kramdown.rb000066400000000000000000000017601340050175000214530ustar00rootroot00000000000000# 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.11.0/nanoc/lib/nanoc/filters/less.rb000066400000000000000000000045161340050175000206010ustar00rootroot00000000000000# 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| /\.(less|css)$/.match?(i[1]) ? 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.11.0/nanoc/lib/nanoc/filters/markaby.rb000066400000000000000000000007661340050175000212640ustar00rootroot00000000000000# 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.11.0/nanoc/lib/nanoc/filters/maruku.rb000066400000000000000000000010041340050175000211240ustar00rootroot00000000000000# 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.11.0/nanoc/lib/nanoc/filters/mustache.rb000066400000000000000000000010361340050175000214360ustar00rootroot00000000000000# 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.11.0/nanoc/lib/nanoc/filters/pandoc.rb000066400000000000000000000023111340050175000210660ustar00rootroot00000000000000# 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}, 'wrap=none', :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.11.0/nanoc/lib/nanoc/filters/rainpress.rb000066400000000000000000000010011340050175000216230ustar00rootroot00000000000000# 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.11.0/nanoc/lib/nanoc/filters/rdiscount.rb000066400000000000000000000011041340050175000216330ustar00rootroot00000000000000# 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.11.0/nanoc/lib/nanoc/filters/rdoc.rb000066400000000000000000000010761340050175000205600ustar00rootroot00000000000000# 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.11.0/nanoc/lib/nanoc/filters/redcarpet.rb000066400000000000000000000024641340050175000216040ustar00rootroot00000000000000# frozen_string_literal: true

module Nanoc::Filters
  # @api private
  class Redcarpet < Nanoc::Filter
    identifier :redcarpet

    requires 'redcarpet'

    def run(content, params = {})
      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)

      # 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
nanoc-4.11.0/nanoc/lib/nanoc/filters/redcloth.rb000066400000000000000000000031511340050175000214310ustar00rootroot00000000000000# 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.11.0/nanoc/lib/nanoc/filters/relativize_paths.rb000066400000000000000000000135171340050175000232110ustar00rootroot00000000000000# 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

    DDMemoize.activate(self)

    SELECTORS = ['*/@href', '*/@src', 'object/@data', 'param[@name="movie"]/@content', 'form/@action', 'comment()'].freeze

    GCSE_SEARCH_WORKAROUND = 'nanoc__gcse_search__f7ac3462f628a053f86fe6563c0ec98f1fe45cee'

    # 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, params)
      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, params)
      # 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]

        if exclude?(path, params)
          Regexp.last_match[0]
        else
          'url(' + quote + relative_path_to(path) + quote + ')'
        end
      end
    end

    memoized def excludes(params)
      raw = [params.fetch(:exclude, [])].flatten
      raw.map do |exclusion|
        case exclusion
        when Regexp
          exclusion
        when String
          /\A#{exclusion}(\z|\/)/
        end
      end
    end

    def exclude?(path, params)
      # TODO: Use #match? on newer Ruby versions
      excludes(params).any? { |ex| path =~ ex }
    end

    def relativize_html_like(content, params)
      selectors             = params.fetch(:select, SELECTORS)
      namespaces            = params.fetch(:namespaces, {})
      type                  = params.fetch(:type)
      nokogiri_save_options = params.fetch(:nokogiri_save_options, nil)

      parser = parser_for(type)
      content = fix_content(content, type)

      nokogiri_process(content, selectors, namespaces, parser, type, nokogiri_save_options, params)
    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, nokogiri_save_options, params)
      # Ensure that all prefixes are strings
      namespaces = namespaces.reduce({}) { |new, (prefix, uri)| new.merge(prefix.to_s => uri) }

      content = apply_gcse_search_workaround(content)

      doc = /]/.match?(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, params)
        elsif path_is_relativizable?(node.content, params)
          node.content = relative_path_to(node.content)
        end
      end

      output =
        case type
        when :html5
          doc.to_html(save_with: nokogiri_save_options)
        else
          doc.send("to_#{type}", save_with: nokogiri_save_options)
        end

      revert_gcse_search_workaround(output)
    end

    def apply_gcse_search_workaround(content)
      content.gsub('gcse:search', GCSE_SEARCH_WORKAROUND)
    end

    def revert_gcse_search_workaround(content)
      content.gsub(GCSE_SEARCH_WORKAROUND, 'gcse:search')
    end

    def nokogiri_process_comment(node, doc, selectors, namespaces, klass, type, params)
      content = node.content.dup.sub(%r{^(\s*\[.+?\]>\s*)(.+?)(\s* '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.11.0/nanoc/lib/nanoc/filters/yui_compressor.rb000066400000000000000000000012201340050175000227020ustar00rootroot00000000000000# 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.11.0/nanoc/lib/nanoc/helpers.rb000066400000000000000000000007201340050175000176160ustar00rootroot00000000000000# 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.11.0/nanoc/lib/nanoc/helpers/000077500000000000000000000000001340050175000172725ustar00rootroot00000000000000nanoc-4.11.0/nanoc/lib/nanoc/helpers/blogging.rb000066400000000000000000000175721340050175000214230ustar00rootroot00000000000000# 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 :alt_link
      attr_accessor :id
      attr_accessor :limit
      attr_accessor :relevant_articles
      attr_accessor :preserve_order
      attr_accessor :content_proc
      attr_accessor :excerpt_proc
      attr_accessor :title_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 = +''
        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(id || root_url)
          xml.title title

          # Add date
          xml.updated(updated.__nanoc_to_iso8601_time)

          # Add links
          xml.link(rel: 'alternate', href: (alt_link || 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(article, xml)
        # Get URL
        url = url_for(article)
        return if url.nil?

        xml.entry do
          # Add primary attributes
          xml.id atom_tag_for(article)
          xml.title title_proc.call(article), type: 'html'

          # Add dates
          xml.published attribute_to_time(article[:created_at]).__nanoc_to_iso8601_time
          xml.updated attribute_to_time(article[:updated_at] || article[:created_at]).__nanoc_to_iso8601_time

          # Add specific author information
          if article[:author_name] || article[:author_uri]
            xml.author do
              xml.name article[:author_name] || author_name
              xml.uri article[:author_uri] || author_uri
            end
          end

          # Add link
          xml.link(rel: 'alternate', href: url)

          # Add content
          summary = excerpt_proc.call(article)
          xml.content content_proc.call(article), 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 [Proc] :title_proc
    # @option params [String] :alt_link
    # @option params [String] :id
    # @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.alt_link          = params[:alt_link]
      builder.id                = params[:id]
      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_proc        = params[:title_proc] || ->(a) { a[:title] }
      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.11.0/nanoc/lib/nanoc/helpers/breadcrumbs.rb000066400000000000000000000111001340050175000221010ustar00rootroot00000000000000# frozen_string_literal: true

module Nanoc::Helpers
  # @see http://nanoc.ws/doc/reference/helpers/#breadcrumbs
  module Breadcrumbs
    class AmbiguousAncestorError < Nanoc::Int::Errors::Generic
      def initialize(pattern, items)
        @pattern = pattern
        @items = items
      end

      def message
        "expected only one item to match #{@pattern}, but found #{@items.size}"
      end
    end

    # @api private
    module Int
      DEFAULT_TIEBREAKER =
        lambda do |items, pattern|
          identifiers = items.map(&:identifier).sort
          $stderr.puts <<~WARNING
            Warning: The breadcrumbs trail (generated by #breadcrumbs_trail) found more than one potential parent item at #{pattern} (found #{identifiers.join(', ')}). Nanoc will pick the first item as the parent. Consider eliminating the ambiguity by making only one item match #{pattern}, or by passing a `:tiebreaker` option to `#breadcrumbs_trail`. (This situation will be an error in the next major version of Nanoc.)
          WARNING

          items.min_by(&:identifier)
        end

      ERROR_TIEBREAKER =
        lambda do |items, pattern|
          raise AmbiguousAncestorError.new(pattern, items)
        end

      # e.g. unfold(10.class, &:superclass)
      # => [Integer, Numeric, Object, BasicObject]
      def self.unfold(obj, &blk)
        acc = [obj]

        res = yield(obj)
        if res
          acc + unfold(res, &blk)
        else
          acc
        end
      end

      # e.g. patterns_for_prefix('/foo/1.0')
      # => ['/foo/1.0.*', '/foo/1.*']
      def self.patterns_for_prefix(prefix)
        prefixes =
          unfold(prefix) do |old_prefix|
            new_prefix = Nanoc::Identifier.new(old_prefix).without_ext
            new_prefix == old_prefix ? nil : new_prefix
          end

        prefixes.map { |pr| pr + '.*' }
      end

      def self.find_one(items, pat, tiebreaker)
        res = items.find_all(pat)
        case res.size
        when 0
          nil
        when 1
          res.first
        else
          if tiebreaker.arity == 1
            tiebreaker.call(res)
          else
            tiebreaker.call(res, pat)
          end
        end
      end
    end

    # @return [Array]
    def breadcrumbs_trail(tiebreaker: Int::DEFAULT_TIEBREAKER)
      # The design of this function is a little complicated.
      #
      # We can’t use #parent_of from the ChildParent helper, because the
      # breadcrumb trail can have gaps. For example, the breadcrumbs trail for
      # /software/oink.md might be /index.md -> nil -> /software/oink.md if
      # there is no item matching /software.* or /software/index.*.
      #
      # What this function does instead is something more complicated:
      #
      # 1.  It creates an ordered prefix list, based on the identifier of the
      #     item to create a breadcrumbs trail for. For example,
      #     /software/oink.md might have the prefix list
      #     ['', '/software', '/software/oink.md'].
      #
      # 2.  For each of the elements in that list, it will create a list of
      #     patterns could match zero or more items. For example, the element
      #     '/software' would correspond to the pattern '/software.*'.
      #
      # 3.  For each of the elements in that list, and for each pattern for that
      #     element, it will find any matching element. For example, the
      #     pattern '/software.*' (coming from the prefix /software) would match
      #     the item /software.md.
      #
      # 4.  Return the list of items, with the last element replaced by the item
      #     for which the breadcrumb is generated for -- while ancestral items
      #     in the breadcrumbs trail can have a bit of ambiguity, the item for
      #     which to generate the breadcrumbs trail is fixed.

      # e.g. ['', '/foo', '/foo/bar']
      components = item.identifier.components
      prefixes = components.inject(['']) { |acc, elem| acc + [acc.last + '/' + elem] }

      tiebreaker = Int::ERROR_TIEBREAKER if tiebreaker == :error

      if @item.identifier.legacy?
        prefixes.map { |pr| @items[Nanoc::Identifier.new('/' + pr, type: :legacy)] }
      else
        ancestral_prefixes = prefixes.reject { |pr| pr =~ /^\/index\./ }[0..-2]
        ancestral_items =
          ancestral_prefixes.map do |pr|
            if pr == ''
              @items['/index.*']
            else
              prefix_patterns = Int.patterns_for_prefix(pr)
              prefix_patterns.lazy.map { |pat| Int.find_one(@items, pat, tiebreaker) }.find(&:itself)
            end
          end
        ancestral_items + [item]
      end
    end
  end
end
nanoc-4.11.0/nanoc/lib/nanoc/helpers/capturing.rb000066400000000000000000000120761340050175000216210ustar00rootroot00000000000000# 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 = +'' # 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] = +''

      # 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.11.0/nanoc/lib/nanoc/helpers/child_parent.rb000066400000000000000000000011051340050175000222500ustar00rootroot00000000000000# 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.11.0/nanoc/lib/nanoc/helpers/filtering.rb000066400000000000000000000021561340050175000216060ustar00rootroot00000000000000# 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.11.0/nanoc/lib/nanoc/helpers/html_escape.rb000066400000000000000000000021631340050175000221050ustar00rootroot00000000000000# 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.11.0/nanoc/lib/nanoc/helpers/link_to.rb000066400000000000000000000052761340050175000212700ustar00rootroot00000000000000# 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::CompilationItemView, Nanoc::BasicItemView, Nanoc::BasicItemRepView
          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&.path == path
        # Create message
        "#{text}"
      else
        link_to(text, target, attributes)
      end
    end

    # @return [String]
    def relative_path_to(target)
      # 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.11.0/nanoc/lib/nanoc/helpers/rendering.rb000066400000000000000000000037671340050175000216110ustar00rootroot00000000000000# 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.11.0/nanoc/lib/nanoc/helpers/tagging.rb000066400000000000000000000016671340050175000212510ustar00rootroot00000000000000# 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.11.0/nanoc/lib/nanoc/helpers/text.rb000066400000000000000000000013061340050175000206030ustar00rootroot00000000000000# 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.11.0/nanoc/lib/nanoc/helpers/xml_sitemap.rb000066400000000000000000000030231340050175000221370ustar00rootroot00000000000000# 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 = +''
      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 Addressable::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.11.0/nanoc/lib/nanoc/rule_dsl.rb000066400000000000000000000011571340050175000177720ustar00rootroot00000000000000# frozen_string_literal: true

module Nanoc
  # @api private
  module RuleDSL
  end
end

require_relative 'rule_dsl/compiler_dsl'
require_relative 'rule_dsl/action_provider'
require_relative 'rule_dsl/action_recorder'
require_relative 'rule_dsl/action_sequence_calculator'
require_relative 'rule_dsl/rules_collection'
require_relative 'rule_dsl/rules_loader'

require_relative 'rule_dsl/rule_context'
require_relative 'rule_dsl/compilation_rule_context'
require_relative 'rule_dsl/routing_rule_context'

require_relative 'rule_dsl/rule'
require_relative 'rule_dsl/compilation_rule'
require_relative 'rule_dsl/routing_rule'
nanoc-4.11.0/nanoc/lib/nanoc/rule_dsl/000077500000000000000000000000001340050175000174415ustar00rootroot00000000000000nanoc-4.11.0/nanoc/lib/nanoc/rule_dsl/action_provider.rb000066400000000000000000000055001340050175000231550ustar00rootroot00000000000000# 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(obj)
      @action_sequence_calculator[obj]
    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, site.data_source)
    end

    def postprocess(site, compiler)
      dependency_tracker = Nanoc::Int::DependencyTracker::Null.new

      res = compiler.run_until_reps_built
      reps = res.fetch(:reps)

      view_context =
        Nanoc::ViewContextForCompilation.new(
          reps: reps,
          items: site.items,
          dependency_tracker: dependency_tracker,
          compilation_context: compiler.compilation_context(reps: reps),
          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)
      view_context =
        Nanoc::ViewContextForPreCompilation.new(items: site.items)

      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.11.0/nanoc/lib/nanoc/rule_dsl/action_recorder.rb000066400000000000000000000044711340050175000231360ustar00rootroot00000000000000# frozen_string_literal: true

module Nanoc
  module RuleDSL
    class ActionRecorder
      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
        @snapshots_for_which_to_skip_routing_rule = Set.new
      end

      def inspect
        "<#{self.class}>"
      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

      MaybePathlike = C::Or[nil, Nanoc::UNDEFINED, String, Nanoc::Identifier]
      contract Symbol, C::KeywordArgs[path: C::Optional[MaybePathlike]] => nil
      def snapshot(snapshot_name, path: Nanoc::UNDEFINED)
        unless Nanoc::UNDEFINED.equal?(path)
          @snapshots_for_which_to_skip_routing_rule << snapshot_name
        end

        path =
          if Nanoc::UNDEFINED.equal?(path) || path.nil?
            nil
          else
            path.to_s
          end

        @action_sequence_builder.add_snapshot(snapshot_name, path)
        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 => Set
      def snapshots_for_which_to_skip_routing_rule
        @snapshots_for_which_to_skip_routing_rule
      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.11.0/nanoc/lib/nanoc/rule_dsl/action_sequence_calculator.rb000066400000000000000000000115141340050175000253460ustar00rootroot00000000000000# frozen_string_literal: true

module Nanoc::RuleDSL
  class ActionSequenceCalculator
    DDMemoize.activate(self)

    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)
      view_context =
        Nanoc::ViewContextForPreCompilation.new(items: @site.items)

      recorder = Nanoc::RuleDSL::ActionRecorder.new(rep)
      rule = @rules_collection.compilation_rule_for(rep)

      unless rule
        raise NoActionSequenceForItemRepException.new(rep)
      end

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

      copy_paths_from_routing_rules(
        compact_snapshots(recorder.action_sequence),
        recorder.snapshots_for_which_to_skip_routing_rule,
        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, snapshots_for_which_to_skip_routing_rule, rep:)
      # NOTE: This assumes that `seq` is compacted, i.e. there are no two consecutive snapshot actions.

      seq.map do |action|
        # Only potentially modify snapshot actions
        next action unless action.is_a?(Nanoc::Int::ProcessingActions::Snapshot)

        # If any of the action’s snapshot are explicitly marked as excluded from
        # getting a path from a routing rule, then ignore routing rules.
        next action if snapshots_for_which_to_skip_routing_rule.intersect?(Set.new(action.snapshot_names))

        # If this action already has paths that don’t come from routing rules,
        # then don’t add more to them.
        next action unless action.paths.empty?

        # For each snapshot name, find a path from a routing rule. The routing
        # rule might return nil, so we need #compact.
        paths = action.snapshot_names.map { |sn| basic_path_from_rules_for(rep, sn) }.compact
        action.update(snapshot_names: [], paths: paths)
      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?

      view_context =
        Nanoc::ViewContextForPreCompilation.new(items: @site.items)

      basic_path =
        routing_rule.apply_to(
          rep,
          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.11.0/nanoc/lib/nanoc/rule_dsl/compilation_rule.rb000066400000000000000000000011761340050175000233400ustar00rootroot00000000000000# frozen_string_literal: true

module Nanoc::RuleDSL
  class CompilationRule < Rule
    include Nanoc::Int::ContractsSupport

    contract Nanoc::Int::ItemRep, C::KeywordArgs[
      site: Nanoc::Int::Site,
      recorder: Nanoc::RuleDSL::ActionRecorder,
      view_context: Nanoc::ViewContextForPreCompilation,
    ] => C::Any
    def apply_to(rep, site:, recorder:, view_context:)
      context = Nanoc::RuleDSL::CompilationRuleContext.new(
        rep: rep,
        recorder: recorder,
        site: site,
        view_context: view_context,
      )

      context.instance_exec(matches(rep.item.identifier), &@block)
    end
  end
end
nanoc-4.11.0/nanoc/lib/nanoc/rule_dsl/compilation_rule_context.rb000066400000000000000000000054141340050175000251030ustar00rootroot00000000000000# frozen_string_literal: true

module Nanoc::RuleDSL
  class CompilationRuleContext < RuleContext
    include Nanoc::Int::ContractsSupport

    contract C::KeywordArgs[
      rep: Nanoc::Int::ItemRep,
      site: Nanoc::Int::Site,
      recorder: Nanoc::RuleDSL::ActionRecorder,
      view_context: Nanoc::ViewContextForPreCompilation,
    ] => C::Any
    def initialize(rep:, site:, recorder:, view_context:)
      @_recorder = recorder

      super(rep: rep, site: site, view_context: 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 = {})
      @_recorder.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)
      @_recorder.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: Nanoc::UNDEFINED)
      @_recorder.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] arg
    #
    # @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, nil
        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.11.0/nanoc/lib/nanoc/rule_dsl/compiler_dsl.rb000066400000000000000000000254441340050175000224530ustar00rootroot00000000000000# frozen_string_literal: true

module Nanoc::RuleDSL
  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::CompilationRule.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::RoutingRule.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::CompilationRule.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::RoutingRule.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::CompilationRule.new(create_pattern(identifier), rep, proc {})
      @rules_collection.add_item_compilation_rule(compilation_rule)

      routing_rule = Nanoc::RuleDSL::RoutingRule.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.11.0/nanoc/lib/nanoc/rule_dsl/routing_rule.rb000066400000000000000000000015331340050175000225060ustar00rootroot00000000000000# frozen_string_literal: true

module Nanoc::RuleDSL
  class RoutingRule < Rule
    include Nanoc::Int::ContractsSupport

    contract C::None => C::Maybe[Symbol]
    attr_reader :snapshot_name

    contract Nanoc::Int::Pattern, Symbol, Proc, C::KeywordArgs[snapshot_name: C::Optional[Symbol]] => C::Any
    def initialize(pattern, rep_name, block, snapshot_name: nil)
      super(pattern, rep_name, block)

      @snapshot_name = snapshot_name
    end

    contract Nanoc::Int::ItemRep, C::KeywordArgs[
      site: Nanoc::Int::Site,
      view_context: Nanoc::ViewContextForPreCompilation,
    ] => C::Any
    def apply_to(rep, site:, view_context:)
      context = Nanoc::RuleDSL::RoutingRuleContext.new(
        rep: rep, site: site, view_context: view_context,
      )

      context.instance_exec(matches(rep.item.identifier), &@block)
    end
  end
end
nanoc-4.11.0/nanoc/lib/nanoc/rule_dsl/routing_rule_context.rb000066400000000000000000000001501340050175000242440ustar00rootroot00000000000000# frozen_string_literal: true

module Nanoc::RuleDSL
  class RoutingRuleContext < RuleContext
  end
end
nanoc-4.11.0/nanoc/lib/nanoc/rule_dsl/rule.rb000066400000000000000000000013071340050175000207360ustar00rootroot00000000000000# frozen_string_literal: true

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

    contract C::None => Symbol
    attr_reader :rep_name

    contract C::None => Nanoc::Int::Pattern
    attr_reader :pattern

    contract Nanoc::Int::Pattern, Symbol, Proc => C::Any
    def initialize(pattern, rep_name, block)
      @pattern = pattern
      @rep_name = rep_name.to_sym
      @block = block
    end

    contract Nanoc::Int::Item => C::Bool
    def applicable_to?(item)
      @pattern.match?(item.identifier)
    end

    # @api private
    contract Nanoc::Identifier => C::Or[nil, C::ArrayOf[String]]
    def matches(identifier)
      @pattern.captures(identifier)
    end
  end
end
nanoc-4.11.0/nanoc/lib/nanoc/rule_dsl/rule_context.rb000066400000000000000000000014471340050175000225070ustar00rootroot00000000000000# frozen_string_literal: true

module Nanoc::RuleDSL
  class RuleContext < Nanoc::Int::Context
    include Nanoc::Int::ContractsSupport

    contract C::KeywordArgs[
      rep: Nanoc::Int::ItemRep,
      site: Nanoc::Int::Site,
      view_context: Nanoc::ViewContextForPreCompilation,
    ] => C::Any
    def initialize(rep:, site:, view_context:)
      super({
        item: Nanoc::BasicItemView.new(rep.item, view_context),
        rep: Nanoc::BasicItemRepView.new(rep, view_context),
        item_rep: Nanoc::BasicItemRepView.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
  end
end
nanoc-4.11.0/nanoc/lib/nanoc/rule_dsl/rules_collection.rb000066400000000000000000000077221340050175000233430ustar00rootroot00000000000000# 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.11.0/nanoc/lib/nanoc/rule_dsl/rules_loader.rb000066400000000000000000000015031340050175000224450ustar00rootroot00000000000000# frozen_string_literal: true

module Nanoc::RuleDSL
  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.11.0/nanoc/lib/nanoc/spec.rb000066400000000000000000000151241340050175000171120ustar00rootroot00000000000000# frozen_string_literal: true

module Nanoc
  # @api private
  module Spec
    module Helper
      def chdir(dir)
        here = Dir.getwd
        Dir.chdir(dir)
        yield
      ensure
        Dir.chdir(here)
      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 skip_unless_have_command(cmd)
        skip "Could not find external command \"#{cmd}\"" unless command?(cmd)
      end

      def skip_unless_gem_available(gem)
        require gem
      rescue LoadError
        skip "Could not load gem \"#{gem}\""
      end

      def sleep_until(max: 3.0)
        start = Time.now
        loop do
          diff = (Time.now - start).to_f
          if diff > max
            raise "Waited for #{diff}s for condition to become true, but it never did"
          end

          break if yield

          sleep 0.1
        end
      end
    end

    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 = +''
        @action_sequence = {}
        @config = Nanoc::Int::Configuration.new(dir: Dir.getwd).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)
        @snapshot_repo = Nanoc::Int::SnapshotRepo.new
        @action_provider = new_action_provider
      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::CompilationItemView] 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::CompilationItemView] 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::CompilationItemView] 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::CompilationItemView, nil]
      def item
        assigns[:item]
      end

      # @return [Nanoc::BasicItemRepView, 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
        compilation_context =
          Nanoc::Int::CompilationContext.new(
            action_provider: @action_provider,
            reps: @reps,
            site: @site,
            compiled_content_cache: :__compiled_content_cache,
            snapshot_repo: @snapshot_repo,
          )

        Nanoc::ViewContextForCompilation.new(
          reps: @reps,
          items: @items,
          dependency_tracker: @dependency_tracker,
          compilation_context: compilation_context,
          snapshot_repo: @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: @action_provider)
      end

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

      def assigns
        {
          config: Nanoc::MutableConfigView.new(@config, view_context),
          item_rep: @item_rep ? Nanoc::CompilationItemRepView.new(@item_rep, view_context) : nil,
          item: @item ? Nanoc::CompilationItemView.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.11.0/nanoc/lib/nanoc/version.rb000066400000000000000000000001441340050175000176410ustar00rootroot00000000000000# frozen_string_literal: true

module Nanoc
  # The current Nanoc version.
  VERSION = '4.11.0'
end
nanoc-4.11.0/nanoc/nanoc.gemspec000066400000000000000000000026511340050175000164330ustar00rootroot00000000000000# 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+rubygems@denis.ws'
  s.license = 'MIT'

  s.files = Dir['*.md'] + ['LICENSE'] + Dir['bin/*'] + Dir['lib/**/*.rb'] + Dir['lib/**/*-schema.json']
  s.executables        = ['nanoc']
  s.require_paths      = ['lib']

  s.required_ruby_version = '~> 2.4'

  s.add_runtime_dependency('addressable', '~> 2.5')
  s.add_runtime_dependency('cri', '~> 2.15')
  s.add_runtime_dependency('ddmemoize', '~> 1.0')
  s.add_runtime_dependency('ddmetrics', '~> 1.0')
  s.add_runtime_dependency('ddplugin', '~> 1.0')
  s.add_runtime_dependency('hamster', '~> 3.0')
  s.add_runtime_dependency('json_schema', '~> 0.19')
  s.add_runtime_dependency('parallel', '~> 1.12')
  s.add_runtime_dependency('ref', '~> 2.0')
  s.add_runtime_dependency('slow_enumerator_tools', '~> 1.0')
  s.add_runtime_dependency('tomlrb', '~> 1.2')
end
nanoc-4.11.0/nanoc/nanoc.manifest000066400000000000000000000240461340050175000166200ustar00rootroot00000000000000LICENSE
NEWS.md
README.md

bin/nanoc

lib/nanoc.rb
lib/nanoc/base.rb
lib/nanoc/base/assertions.rb
lib/nanoc/base/changes_stream.rb
lib/nanoc/base/contracts_support.rb
lib/nanoc/base/core_ext.rb
lib/nanoc/base/core_ext/array.rb
lib/nanoc/base/core_ext/hash.rb
lib/nanoc/base/core_ext/string.rb
lib/nanoc/base/entities.rb
lib/nanoc/base/entities/action_sequence.rb
lib/nanoc/base/entities/checksum_collection.rb
lib/nanoc/base/entities/code_snippet.rb
lib/nanoc/base/entities/configuration.rb
lib/nanoc/base/entities/configuration-schema.json
lib/nanoc/base/entities/content.rb
lib/nanoc/base/entities/context.rb
lib/nanoc/base/entities/dependency.rb
lib/nanoc/base/entities/directed_graph.rb
lib/nanoc/base/entities/document.rb
lib/nanoc/base/entities/identifiable_collection.rb
lib/nanoc/base/entities/identifier.rb
lib/nanoc/base/entities/item_collection.rb
lib/nanoc/base/entities/item_rep.rb
lib/nanoc/base/entities/item.rb
lib/nanoc/base/entities/layout_collection.rb
lib/nanoc/base/entities/layout.rb
lib/nanoc/base/entities/lazy_value.rb
lib/nanoc/base/entities/outdatedness_reasons.rb
lib/nanoc/base/entities/outdatedness_status.rb
lib/nanoc/base/entities/pattern.rb
lib/nanoc/base/entities/processing_action.rb
lib/nanoc/base/entities/processing_actions.rb
lib/nanoc/base/entities/processing_actions/filter.rb
lib/nanoc/base/entities/processing_actions/layout.rb
lib/nanoc/base/entities/processing_actions/snapshot.rb
lib/nanoc/base/entities/props.rb
lib/nanoc/base/entities/site.rb
lib/nanoc/base/entities/snapshot_def.rb
lib/nanoc/base/error.rb
lib/nanoc/base/errors.rb
lib/nanoc/base/feature.rb
lib/nanoc/base/repos.rb
lib/nanoc/base/repos/action_sequence_store.rb
lib/nanoc/base/repos/aggregate_data_source.rb
lib/nanoc/base/repos/checksum_store.rb
lib/nanoc/base/repos/compiled_content_cache.rb
lib/nanoc/base/repos/config_loader.rb
lib/nanoc/base/repos/data_source.rb
lib/nanoc/base/repos/dependency_store.rb
lib/nanoc/base/repos/in_mem_data_source.rb
lib/nanoc/base/repos/item_rep_repo.rb
lib/nanoc/base/repos/outdatedness_store.rb
lib/nanoc/base/repos/prefixed_data_source.rb
lib/nanoc/base/repos/site_loader.rb
lib/nanoc/base/repos/snapshot_repo.rb
lib/nanoc/base/repos/store.rb
lib/nanoc/base/services.rb
lib/nanoc/base/services/action_provider.rb
lib/nanoc/base/services/action_sequence_builder.rb
lib/nanoc/base/services/checksummer.rb
lib/nanoc/base/services/compilation_context.rb
lib/nanoc/base/services/compiler_loader.rb
lib/nanoc/base/services/compiler.rb
lib/nanoc/base/services/compiler/phases.rb
lib/nanoc/base/services/compiler/phases/abstract.rb
lib/nanoc/base/services/compiler/phases/cache.rb
lib/nanoc/base/services/compiler/phases/mark_done.rb
lib/nanoc/base/services/compiler/phases/recalculate.rb
lib/nanoc/base/services/compiler/phases/resume.rb
lib/nanoc/base/services/compiler/phases/write.rb
lib/nanoc/base/services/compiler/stage.rb
lib/nanoc/base/services/compiler/stages.rb
lib/nanoc/base/services/compiler/stages/build_reps.rb
lib/nanoc/base/services/compiler/stages/calculate_checksums.rb
lib/nanoc/base/services/compiler/stages/cleanup.rb
lib/nanoc/base/services/compiler/stages/compile_reps.rb
lib/nanoc/base/services/compiler/stages/determine_outdatedness.rb
lib/nanoc/base/services/compiler/stages/forget_outdated_dependencies.rb
lib/nanoc/base/services/compiler/stages/load_stores.rb
lib/nanoc/base/services/compiler/stages/postprocess.rb
lib/nanoc/base/services/compiler/stages/preprocess.rb
lib/nanoc/base/services/compiler/stages/prune.rb
lib/nanoc/base/services/compiler/stages/store_post_compilation_state.rb
lib/nanoc/base/services/compiler/stages/store_pre_compilation_state.rb
lib/nanoc/base/services/dependency_tracker.rb
lib/nanoc/base/services/executor.rb
lib/nanoc/base/services/filter.rb
lib/nanoc/base/services/instrumentor.rb
lib/nanoc/base/services/item_rep_builder.rb
lib/nanoc/base/services/item_rep_router.rb
lib/nanoc/base/services/item_rep_selector.rb
lib/nanoc/base/services/item_rep_writer.rb
lib/nanoc/base/services/notification_center.rb
lib/nanoc/base/services/outdatedness_checker.rb
lib/nanoc/base/services/outdatedness_rule.rb
lib/nanoc/base/services/outdatedness_rules.rb
lib/nanoc/base/services/outdatedness_rules/attributes_modified.rb
lib/nanoc/base/services/outdatedness_rules/code_snippets_modified.rb
lib/nanoc/base/services/outdatedness_rules/content_modified.rb
lib/nanoc/base/services/outdatedness_rules/item_collection_extended.rb
lib/nanoc/base/services/outdatedness_rules/layout_collection_extended.rb
lib/nanoc/base/services/outdatedness_rules/not_written.rb
lib/nanoc/base/services/outdatedness_rules/rules_modified.rb
lib/nanoc/base/services/outdatedness_rules/uses_always_outdated_filter.rb
lib/nanoc/base/services/pruner.rb
lib/nanoc/base/services/temp_filename_factory.rb
lib/nanoc/base/views.rb
lib/nanoc/base/views/basic_item_rep_collection_view.rb
lib/nanoc/base/views/basic_item_rep_view.rb
lib/nanoc/base/views/basic_item_view.rb
lib/nanoc/base/views/compilation_item_rep_collection_view.rb
lib/nanoc/base/views/compilation_item_rep_view.rb
lib/nanoc/base/views/compilation_item_view.rb
lib/nanoc/base/views/config_view.rb
lib/nanoc/base/views/identifiable_collection_view.rb
lib/nanoc/base/views/item_collection_with_reps_view.rb
lib/nanoc/base/views/item_collection_without_reps_view.rb
lib/nanoc/base/views/layout_collection_view.rb
lib/nanoc/base/views/layout_view.rb
lib/nanoc/base/views/mixins/document_view_mixin.rb
lib/nanoc/base/views/mixins/mutable_document_view_mixin.rb
lib/nanoc/base/views/mutable_config_view.rb
lib/nanoc/base/views/mutable_identifiable_collection_view.rb
lib/nanoc/base/views/mutable_item_collection_view.rb
lib/nanoc/base/views/mutable_item_view.rb
lib/nanoc/base/views/mutable_layout_collection_view.rb
lib/nanoc/base/views/mutable_layout_view.rb
lib/nanoc/base/views/post_compile_item_collection_view.rb
lib/nanoc/base/views/post_compile_item_rep_collection_view.rb
lib/nanoc/base/views/post_compile_item_rep_view.rb
lib/nanoc/base/views/post_compile_item_view.rb
lib/nanoc/base/views/view_context_for_compilation.rb
lib/nanoc/base/views/view_context_for_pre_compilation.rb
lib/nanoc/base/views/view_context_for_shell.rb
lib/nanoc/base/views/view.rb
lib/nanoc/checking.rb
lib/nanoc/checking/check.rb
lib/nanoc/checking/checks.rb
lib/nanoc/checking/checks/css.rb
lib/nanoc/checking/checks/external_links.rb
lib/nanoc/checking/checks/html.rb
lib/nanoc/checking/checks/internal_links.rb
lib/nanoc/checking/checks/mixed_content.rb
lib/nanoc/checking/checks/stale.rb
lib/nanoc/checking/checks/w3c_validator.rb
lib/nanoc/checking/dsl.rb
lib/nanoc/checking/issue.rb
lib/nanoc/checking/loader.rb
lib/nanoc/checking/runner.rb
lib/nanoc/cli.rb
lib/nanoc/cli/ansi_string_colorizer.rb
lib/nanoc/cli/cleaning_stream.rb
lib/nanoc/cli/command_runner.rb
lib/nanoc/cli/commands/check.rb
lib/nanoc/cli/commands/compile_listeners/abstract.rb
lib/nanoc/cli/commands/compile_listeners/aggregate.rb
lib/nanoc/cli/commands/compile_listeners/debug_printer.rb
lib/nanoc/cli/commands/compile_listeners/diff_generator.rb
lib/nanoc/cli/commands/compile_listeners/file_action_printer.rb
lib/nanoc/cli/commands/compile_listeners/timing_recorder.rb
lib/nanoc/cli/commands/compile.rb
lib/nanoc/cli/commands/create-site.rb
lib/nanoc/cli/commands/deploy.rb
lib/nanoc/cli/commands/nanoc.rb
lib/nanoc/cli/commands/prune.rb
lib/nanoc/cli/commands/shell.rb
lib/nanoc/cli/commands/show-data.rb
lib/nanoc/cli/commands/show-plugins.rb
lib/nanoc/cli/commands/show-rules.rb
lib/nanoc/cli/commands/view.rb
lib/nanoc/cli/error_handler.rb
lib/nanoc/cli/logger.rb
lib/nanoc/cli/stack_trace_writer.rb
lib/nanoc/cli/stream_cleaners.rb
lib/nanoc/cli/stream_cleaners/abstract.rb
lib/nanoc/cli/stream_cleaners/ansi_colors.rb
lib/nanoc/cli/stream_cleaners/utf8.rb
lib/nanoc/cli/transform.rb
lib/nanoc/data_sources.rb
lib/nanoc/data_sources/filesystem.rb
lib/nanoc/data_sources/filesystem/errors.rb
lib/nanoc/data_sources/filesystem/tools.rb
lib/nanoc/data_sources/filesystem/parser.rb
lib/nanoc/deploying.rb
lib/nanoc/deploying/deployer.rb
lib/nanoc/deploying/deployers.rb
lib/nanoc/deploying/deployers/fog.rb
lib/nanoc/deploying/deployers/git.rb
lib/nanoc/deploying/deployers/rsync.rb
lib/nanoc/extra.rb
lib/nanoc/extra/core_ext.rb
lib/nanoc/extra/core_ext/pathname.rb
lib/nanoc/extra/core_ext/time.rb
lib/nanoc/extra/jruby_nokogiri_warner.rb
lib/nanoc/extra/link_collector.rb
lib/nanoc/extra/piper.rb
lib/nanoc/filters.rb
lib/nanoc/filters/asciidoc.rb
lib/nanoc/filters/asciidoctor.rb
lib/nanoc/filters/bluecloth.rb
lib/nanoc/filters/coffeescript.rb
lib/nanoc/filters/colorize_syntax.rb
lib/nanoc/filters/colorize_syntax/colorizers.rb
lib/nanoc/filters/erb.rb
lib/nanoc/filters/erubi.rb
lib/nanoc/filters/erubis.rb
lib/nanoc/filters/haml.rb
lib/nanoc/filters/handlebars.rb
lib/nanoc/filters/kramdown.rb
lib/nanoc/filters/less.rb
lib/nanoc/filters/markaby.rb
lib/nanoc/filters/maruku.rb
lib/nanoc/filters/mustache.rb
lib/nanoc/filters/pandoc.rb
lib/nanoc/filters/rainpress.rb
lib/nanoc/filters/rdiscount.rb
lib/nanoc/filters/rdoc.rb
lib/nanoc/filters/redcarpet.rb
lib/nanoc/filters/redcloth.rb
lib/nanoc/filters/relativize_paths.rb
lib/nanoc/filters/rubypants.rb
lib/nanoc/filters/sass.rb
lib/nanoc/filters/sass/functions.rb
lib/nanoc/filters/sass/importer.rb
lib/nanoc/filters/slim.rb
lib/nanoc/filters/typogruby.rb
lib/nanoc/filters/uglify_js.rb
lib/nanoc/filters/xsl.rb
lib/nanoc/filters/yui_compressor.rb
lib/nanoc/helpers.rb
lib/nanoc/helpers/blogging.rb
lib/nanoc/helpers/breadcrumbs.rb
lib/nanoc/helpers/capturing.rb
lib/nanoc/helpers/child_parent.rb
lib/nanoc/helpers/filtering.rb
lib/nanoc/helpers/html_escape.rb
lib/nanoc/helpers/link_to.rb
lib/nanoc/helpers/rendering.rb
lib/nanoc/helpers/tagging.rb
lib/nanoc/helpers/text.rb
lib/nanoc/helpers/xml_sitemap.rb
lib/nanoc/rule_dsl.rb
lib/nanoc/rule_dsl/action_provider.rb
lib/nanoc/rule_dsl/action_recorder.rb
lib/nanoc/rule_dsl/action_sequence_calculator.rb
lib/nanoc/rule_dsl/compiler_dsl.rb
lib/nanoc/rule_dsl/compilation_rule_context.rb
lib/nanoc/rule_dsl/compilation_rule.rb
lib/nanoc/rule_dsl/routing_rule_context.rb
lib/nanoc/rule_dsl/routing_rule.rb
lib/nanoc/rule_dsl/rule.rb
lib/nanoc/rule_dsl/rule_context.rb
lib/nanoc/rule_dsl/rules_collection.rb
lib/nanoc/rule_dsl/rules_loader.rb
lib/nanoc/spec.rb
lib/nanoc/version.rb
nanoc-4.11.0/nanoc/spec/000077500000000000000000000000001340050175000147165ustar00rootroot00000000000000nanoc-4.11.0/nanoc/spec/contributors_spec.rb000066400000000000000000000012241340050175000210110ustar00rootroot00000000000000# 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.11.0/nanoc/spec/gem_spec.rb000066400000000000000000000007601340050175000170300ustar00rootroot00000000000000# frozen_string_literal: true

describe 'nanoc.gem', chdir: false, stdio: true do
  around do |ex|
    Dir['*.gem'].each { |f| FileUtils.rm(f) }
    ex.run
    Dir['*.gem'].each { |f| FileUtils.rm(f) }
  end

  subject do
    piper = Nanoc::Extra::Piper.new(stdout: $stdout, stderr: $stderr)
    piper.run(%w[gem build nanoc.gemspec], nil)
  end

  it 'builds gem' do
    expect { subject }
      .to change { Dir['*.gem'] }
      .from([])
      .to(include(match(/^nanoc-.*\.gem$/)))
  end
end
nanoc-4.11.0/nanoc/spec/manifest_spec.rb000066400000000000000000000002101340050175000200540ustar00rootroot00000000000000# frozen_string_literal: true

describe 'manifest', chdir: false do
  example do
    expect('nanoc').to have_a_valid_manifest
  end
end
nanoc-4.11.0/nanoc/spec/nanoc/000077500000000000000000000000001340050175000160145ustar00rootroot00000000000000nanoc-4.11.0/nanoc/spec/nanoc/base/000077500000000000000000000000001340050175000167265ustar00rootroot00000000000000nanoc-4.11.0/nanoc/spec/nanoc/base/changes_stream_spec.rb000066400000000000000000000022741340050175000232550ustar00rootroot00000000000000# frozen_string_literal: true

describe Nanoc::ChangesStream do
  let(:simple_stream) do
    described_class.new do |cl|
      cl.unknown
      sleep
    end
  end

  it 'returns a stream of events generated by the listener' do
    buffered_stream = SlowEnumeratorTools.buffer(simple_stream, 1)
    expect(buffered_stream.take(1).to_a).to eq([:unknown])
  end

  describe '#map' do
    it 'returns a new maped enum' do
      stream = simple_stream.map { |e| e.to_s.upcase }
      buffered_stream = SlowEnumeratorTools.buffer(stream, 1)
      expect(buffered_stream.take(1).to_a).to eq(['UNKNOWN'])
    end
  end

  describe '#to_enum' do
    it 'returns an enumerator corresponding to itself' do
      buffered_stream = SlowEnumeratorTools.buffer(simple_stream.to_enum, 1)
      expect(buffered_stream.take(1).to_a).to eq([:unknown])
    end
  end

  describe '#stop' do
    let(:simple_stream) do
      described_class.new do |cl|
        cl.to_stop { $changes_stream_stopped = true }
        sleep
      end
    end

    example do
      SlowEnumeratorTools.buffer(simple_stream, 1)
      sleep 0.1
      expect { simple_stream.stop }.to change { $changes_stream_stopped }.from(nil).to(true)
    end
  end
end
nanoc-4.11.0/nanoc/spec/nanoc/base/checksummer_spec.rb000066400000000000000000000353531340050175000226040ustar00rootroot00000000000000# frozen_string_literal: true

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(dir: Dir.getwd, 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::CompilationItemView' do
    let(:obj) { Nanoc::CompilationItemView.new(item, nil) }
    let(:item) { Nanoc::Int::Item.new('asdf', {}, '/foo.md') }

    it { is_expected.to eql('Nanoc::CompilationItemView>,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::BasicItemRepView' do
    let(:obj) { Nanoc::BasicItemRepView.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::BasicItemRepView>,attributes=Hash<>,identifier=Nanoc::Identifier>>,name=Symbol>>') }
  end

  context 'Nanoc::CompilationItemRepView' do
    let(:obj) { Nanoc::CompilationItemRepView.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::CompilationItemRepView>,attributes=Hash<>,identifier=Nanoc::Identifier>>,name=Symbol>>') }
  end

  context 'Nanoc::BasicItemView' do
    let(:obj) { Nanoc::BasicItemView.new(item, nil) }
    let(:item) { Nanoc::Int::Item.new('asdf', {}, '/foo.md') }

    it { is_expected.to eql('Nanoc::BasicItemView>,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(dir: Dir.getwd, 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(dir: Dir.getwd, 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(dir: Dir.getwd, 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::CompilationRuleContext' do
    let(:obj) { Nanoc::RuleDSL::CompilationRuleContext.new(rep: rep, site: site, recorder: recorder, 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(dir: Dir.getwd, hash: { 'foo' => 'bar' }) }
    let(:code_snippets) { [Nanoc::Int::CodeSnippet.new('asdf', '/bob.rb')] }
    let(:items) { Nanoc::Int::ItemCollection.new(config, [item]) }
    let(:layouts) { [Nanoc::Int::Layout.new('asdf', {}, '/foo.md')] }

    let(:recorder) { Nanoc::RuleDSL::ActionRecorder.new(rep) }
    let(:view_context) { Nanoc::ViewContextForPreCompilation.new(items: items) }

    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::CompilationRuleContext<',
        'item=',
        'Nanoc::BasicItemView<' + expected_item_checksum + '>',
        ',rep=',
        'Nanoc::BasicItemRepView<' + 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 match(%r{\ASass::Importers::Filesystem\z}) }
  end

  context 'other marshal-able classes' do
    let(:obj) { klass.new('hello') }

    let(:klass) do
      Class.new do
        def initialize(arg)
          @arg = arg
        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.11.0/nanoc/spec/nanoc/base/compiler_spec.rb000066400000000000000000000132521340050175000221020ustar00rootroot00000000000000# 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,
      outdatedness_store: outdatedness_store,
    )
  end

  let(:checksum_store) { Nanoc::Int::ChecksumStore.new(config: config, objects: items) }
  let(:action_sequence_store) { Nanoc::Int::ActionSequenceStore.new(config: config) }

  let(:dependency_store) { Nanoc::Int::DependencyStore.new(items, layouts, config) }

  let(:outdatedness_store) { Nanoc::Int::OutdatednessStore.new(config: config) }
  let(:action_provider) { double(:action_provider) }

  let(:compiled_content_cache) do
    Nanoc::Int::CompiledContentCache.new(config: config)
  end

  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(dir: Dir.getwd).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
    allow(Nanoc::Int::NotificationCenter).to receive(:post)
  end

  describe '#compile_rep' do
    let(:stage) { compiler.send(:compile_reps_stage, action_sequences, reps) }

    subject { stage.send(:compile_rep, rep, phase_stack: phase_stack, is_outdated: is_outdated) }

    let(:is_outdated) { true }
    let(:phase_stack) { stage.send(:build_phase_stack) }

    let(:reps) do
      Nanoc::Int::ItemRepRepo.new.tap do |rs|
        rs << rep
        rs << other_rep

        rs.each do |rep|
          rep.snapshot_defs << Nanoc::Int::SnapshotDef.new(:last, binary: false)
        end
      end
    end

    it 'generates expected output' do
      reps = Nanoc::Int::ItemRepRepo.new
      expect { subject }
        .to change { compiler.compilation_context(reps: reps).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
        reps = Nanoc::Int::ItemRepRepo.new
        expect(compiler.compilation_context(reps: reps).snapshot_repo.get(rep, :last)).to be_nil

        expect { stage.send(:compile_rep, rep, phase_stack: phase_stack, is_outdated: true) }
          .to raise_error(Nanoc::Int::Errors::UnmetDependency)
        stage.send(:compile_rep, other_rep, phase_stack: phase_stack, is_outdated: true)
        stage.send(:compile_rep, rep, phase_stack: phase_stack, is_outdated: true)

        expect(compiler.compilation_context(reps: reps).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, phase_stack: phase_stack, is_outdated: true) }
          .to raise_error(Nanoc::Int::Errors::UnmetDependency)
        stage.send(:compile_rep, other_rep, phase_stack: phase_stack, is_outdated: true)
        stage.send(:compile_rep, rep, phase_stack: phase_stack, is_outdated: true)
      end
    end
  end
end
nanoc-4.11.0/nanoc/spec/nanoc/base/core_ext/000077500000000000000000000000001340050175000205365ustar00rootroot00000000000000nanoc-4.11.0/nanoc/spec/nanoc/base/core_ext/array_spec.rb000066400000000000000000000023321340050175000232130ustar00rootroot00000000000000# 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_stringify_keys_recursively' do
  it 'should convert keys to strings' do
    array_old = [:abc, 'xyz', { 'foo' => 'bar', baz: :qux }]
    array_new = [:abc, 'xyz', { 'foo' => 'bar', 'baz' => :qux }]
    expect(array_old.__nanoc_stringify_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.11.0/nanoc/spec/nanoc/base/core_ext/hash_spec.rb000066400000000000000000000027061340050175000230250ustar00rootroot00000000000000# 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_stringify_keys_recursively' do
  it 'should convert keys to strings' do
    hash_old = { foo: 'bar' }
    hash_new = { 'foo' => 'bar' }
    expect(hash_old.__nanoc_stringify_keys_recursively).to eql(hash_new)
  end

  it 'should not require symbol keys' do
    hash_old = { Time.now => 'abc' }
    hash_new = hash_old
    expect(hash_old.__nanoc_stringify_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.11.0/nanoc/spec/nanoc/base/core_ext/string_spec.rb000066400000000000000000000012761340050175000234110ustar00rootroot00000000000000# 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.11.0/nanoc/spec/nanoc/base/directed_graph_spec.rb000066400000000000000000000140351340050175000232340ustar00rootroot00000000000000# 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

  it 'has correct examples' do
    expect('Nanoc::Int::DirectedGraph')
      .to have_correct_yard_examples
      .in_file('nanoc/lib/nanoc/base/entities/directed_graph.rb')
  end

  describe '#vertices' do
    subject { graph.vertices }

    it { is_expected.to include('1') }
    it { is_expected.not_to include('4') }
  end

  describe '#add_edge' do
    subject { graph.add_edge('1', '4') }

    it 'adds vertex' do
      expect { subject }
        .to change { graph.vertices.include?('4') }
        .from(false)
        .to(true)
    end

    it 'changes direct predecessors' do
      expect { subject }
        .to change { graph.direct_predecessors_of('4') }
        .from([])
        .to(['1'])
    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 'requested for non-existant vertex' do
      subject { graph.direct_predecessors_of('12345') }

      it { is_expected.to be_empty }
      it { is_expected.to be_a(Set) }
    end

    context 'one edge to' do
      before { graph.add_edge('1', '2') }

      it { is_expected.to match_array(['1']) }
      it { is_expected.to be_a(Set) }
    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]) }
      it { is_expected.to be_a(Set) }
    end

    context 'edge from' do
      before { graph.add_edge('2', '3') }

      it { is_expected.to be_empty }
      it { is_expected.to be_a(Set) }
    end
  end

  describe '#predecessors_of' do
    subject { graph.predecessors_of('2') }

    context 'requested for non-existant vertex' do
      subject { graph.predecessors_of('12345') }

      it { is_expected.to be_empty }
      it { is_expected.to be_a(Set) }
    end

    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 '#delete_edges_to' do
    before do
      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')
    end

    subject { graph.delete_edges_to('1') }

    it 'deletes edges to 1' do
      expect { subject }
        .to change { graph.direct_predecessors_of('1') }
        .from(%w[2 3])
        .to([])
    end

    it 'keeps edges to 2' do
      expect { subject }
        .not_to change { graph.direct_predecessors_of('2') }
    end

    it 'keeps edges to 3' do
      expect { subject }
        .not_to change { graph.direct_predecessors_of('3') }
    end

    it 'keeps edges to 4' do
      expect { subject }
        .not_to change { graph.direct_predecessors_of('4') }
    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.11.0/nanoc/spec/nanoc/base/entities/000077500000000000000000000000001340050175000205525ustar00rootroot00000000000000nanoc-4.11.0/nanoc/spec/nanoc/base/entities/action_sequence_spec.rb000066400000000000000000000217271340050175000252670ustar00rootroot00000000000000# 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.11.0/nanoc/spec/nanoc/base/entities/code_snippet_spec.rb000066400000000000000000000027161340050175000245730ustar00rootroot00000000000000# 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 everywhere' 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 in helpers only' do
        expect { subject }
          .to change { [Nanoc::Int::Context.new({}).respond_to?(:e0f0c30b5e), Complex.respond_to?(:e0f0c30b5e)] }
          .from([false, false])
          .to([true, false])
      end
    end

    it 'defines at top level' do
      @foo = 'meow'

      code_snippet = Nanoc::Int::CodeSnippet.new("@foo = 'woof'", 'dog.rb')
      code_snippet.load

      expect(@foo).to eq('meow')
    end
  end
end
nanoc-4.11.0/nanoc/spec/nanoc/base/entities/configuration_spec.rb000066400000000000000000000315641340050175000247710ustar00rootroot00000000000000# frozen_string_literal: true

describe Nanoc::Int::Configuration do
  let(:hash) { { foo: 'bar' } }
  let(:config) { described_class.new(hash: hash, dir: Dir.getwd) }

  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, dir: Dir.getwd, 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(Dir.getwd + '/output') }
    end

    context 'explicitly defined, top-level' do
      let(:hash) { { foo: 'bar', output_dir: 'build' } }
      it { is_expected.to eql(Dir.getwd + '/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(Dir.getwd + '/output_toplevel')
      expect(subject).to include(Dir.getwd + '/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(Dir.getwd + '/output_staging')
      expect(subject).to include(Dir.getwd + '/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, dir: Dir.getwd) }
    let(:config2) { described_class.new(hash: hash2, dir: Dir.getwd) }

    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, dir: Dir.getwd, 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

  describe 'validation' do
    subject { config }

    context 'valid text_extensions' do
      let(:hash) { { text_extensions: ['md'] } }

      it 'passes' do
        expect { subject }.not_to raise_error
      end
    end

    context 'invalid text_extensions (not an array)' do
      let(:hash) { { text_extensions: 123 } }

      it 'fails' do
        expect { subject }.to raise_error(JsonSchema::Error)
      end
    end

    context 'invalid text_extensions (array, but with other things)' do
      let(:hash) { { text_extensions: [123] } }

      it 'fails' do
        expect { subject }.to raise_error(JsonSchema::Error)
      end
    end

    context 'valid output_dir' do
      let(:hash) { { output_dir: 'output' } }

      it 'passes' do
        expect { subject }.not_to raise_error
      end
    end

    context 'invalid output_dir' do
      let(:hash) { { output_dir: 123 } }

      it 'fails' do
        expect { subject }.to raise_error(JsonSchema::Error)
      end
    end

    context 'valid index_filenames' do
      let(:hash) { { index_filenames: ['index.html'] } }

      it 'passes' do
        expect { subject }.not_to raise_error
      end
    end

    context 'invalid index_filenames (not an array)' do
      let(:hash) { { index_filenames: 123 } }

      it 'fails' do
        expect { subject }.to raise_error(JsonSchema::Error)
      end
    end

    context 'invalid index_filenames (array, but with other things)' do
      let(:hash) { { index_filenames: [123] } }

      it 'fails' do
        expect { subject }.to raise_error(JsonSchema::Error)
      end
    end

    context 'valid enable_output_diff' do
      let(:hash) { { enable_output_diff: false } }

      it 'passes' do
        expect { subject }.not_to raise_error
      end
    end

    context 'invalid enable_output_diff' do
      let(:hash) { { enable_output_diff: 'nope' } }

      it 'fails' do
        expect { subject }.to raise_error(JsonSchema::Error)
      end
    end

    context 'valid prune (empty)' do
      let(:hash) { { prune: {} } }

      it 'passes' do
        expect { subject }.not_to raise_error
      end
    end

    context 'valid prune (full)' do
      let(:hash) { { prune: { auto_prune: true, exclude: ['oink'] } } }

      it 'passes' do
        expect { subject }.not_to raise_error
      end
    end

    context 'invalid prune (not a hash)' do
      let(:hash) { { prune: 'please' } }

      it 'passes' do
        expect { subject }.to raise_error(JsonSchema::Error)
      end
    end

    context 'invalid prune (auto_prune has incorrect type)' do
      let(:hash) { { prune: { auto_prune: 'please' } } }

      it 'passes' do
        expect { subject }.to raise_error(JsonSchema::Error)
      end
    end

    context 'invalid prune (exclude has incorrect type)' do
      let(:hash) { { prune: { exclude: 'nothing' } } }

      it 'passes' do
        expect { subject }.to raise_error(JsonSchema::Error)
      end
    end

    context 'invalid prune (exclude has items of incorrect type)' do
      let(:hash) { { prune: { exclude: [3000] } } }

      it 'passes' do
        expect { subject }.to raise_error(JsonSchema::Error)
      end
    end

    context 'valid commands_dirs' do
      let(:hash) { { commands_dirs: ['commands'] } }

      it 'passes' do
        expect { subject }.not_to raise_error
      end
    end

    context 'invalid commands_dirs (not an array)' do
      let(:hash) { { commands_dirs: 123 } }

      it 'fails' do
        expect { subject }.to raise_error(JsonSchema::Error)
      end
    end

    context 'invalid commands_dirs (array, but with other things)' do
      let(:hash) { { commands_dirs: [123] } }

      it 'fails' do
        expect { subject }.to raise_error(JsonSchema::Error)
      end
    end

    context 'valid lib_dirs' do
      let(:hash) { { lib_dirs: ['lib'] } }

      it 'passes' do
        expect { subject }.not_to raise_error
      end
    end

    context 'invalid lib_dirs (not an array)' do
      let(:hash) { { lib_dirs: 123 } }

      it 'fails' do
        expect { subject }.to raise_error(JsonSchema::Error)
      end
    end

    context 'invalid lib_dirs (array, but with other things)' do
      let(:hash) { { lib_dirs: [123] } }

      it 'fails' do
        expect { subject }.to raise_error(JsonSchema::Error)
      end
    end

    context 'valid data_sources (full)' do
      let(:hash) { { data_sources: [{ type: 'something', items_root: 'itemz/' }] } }

      it 'passes' do
        expect { subject }.not_to raise_error
      end
    end

    context 'valid data_sources (null items_root)' do
      let(:hash) { { data_sources: [{ type: 'something', items_root: nil }] } }

      it 'passes' do
        expect { subject }.not_to raise_error
      end
    end

    context 'valid data_sources (null layouts_root)' do
      let(:hash) { { data_sources: [{ type: 'something', layouts_root: nil }] } }

      it 'passes' do
        expect { subject }.not_to raise_error
      end
    end

    context 'valid data_sources (empty list)' do
      let(:hash) { { data_sources: [] } }

      it 'passes' do
        expect { subject }.not_to raise_error
      end
    end

    context 'valid data_sources (list with empty hashes)' do
      let(:hash) { { data_sources: [{}] } }

      it 'passes' do
        expect { subject }.not_to raise_error
      end
    end

    context 'invalid data_sources (not an array)' do
      let(:hash) { { data_sources: 'all of them please' } }

      it 'fails' do
        expect { subject }.to raise_error(JsonSchema::Error)
      end
    end

    context 'invalid data_sources (items have invalid type)' do
      let(:hash) { { data_sources: ['all of them please'] } }

      it 'fails' do
        expect { subject }.to raise_error(JsonSchema::Error)
      end
    end

    context 'invalid data_sources (items have invalid type field)' do
      let(:hash) { { data_sources: [{ type: 17 }] } }

      it 'fails' do
        expect { subject }.to raise_error(JsonSchema::Error)
      end
    end

    context 'invalid data_sources (items have invalid items_root field)' do
      let(:hash) { { data_sources: [{ items_root: 17 }] } }

      it 'fails' do
        expect { subject }.to raise_error(JsonSchema::Error)
      end
    end

    context 'invalid data_sources (items have invalid layouts_root field)' do
      let(:hash) { { data_sources: [{ layouts_root: 17 }] } }

      it 'fails' do
        expect { subject }.to raise_error(JsonSchema::Error)
      end
    end

    context 'valid string_pattern_type' do
      let(:hash) { { string_pattern_type: 'glob' } }

      it 'passes' do
        expect { subject }.not_to raise_error
      end
    end

    context 'invalid string_pattern_type (incorrect type)' do
      let(:hash) { { string_pattern_type: 16 } }

      it 'fails' do
        expect { subject }.to raise_error(JsonSchema::Error)
      end
    end

    context 'invalid string_pattern_type (not in enum)' do
      let(:hash) { { string_pattern_type: 'pretty' } }

      it 'fails' do
        expect { subject }.to raise_error(JsonSchema::Error)
      end
    end

    context 'valid checks (full)' do
      let(:hash) do
        {
          checks: {
            internal_links: {
              exclude: ['oink'],
            },
            external_links: {
              exclude: ['abc'],
              exclude_files: ['xyz'],
            },
          },
        }
      end

      it 'passes' do
        expect { subject }.not_to raise_error
      end
    end

    context 'invalid checks (invalid type)' do
      let(:hash) do
        { checks: 123 }
      end

      it 'passes' do
        expect { subject }.to raise_error(JsonSchema::Error)
      end
    end

    context 'invalid checks (internal_links has invalid type)' do
      let(:hash) do
        { checks: { internal_links: 123 } }
      end

      it 'passes' do
        expect { subject }.to raise_error(JsonSchema::Error)
      end
    end

    context 'invalid checks (internal_links.exclude has invalid type)' do
      let(:hash) do
        { checks: { internal_links: { exclude: 'everything' } } }
      end

      it 'passes' do
        expect { subject }.to raise_error(JsonSchema::Error)
      end
    end

    context 'invalid checks (external_links has invalid type)' do
      let(:hash) do
        { checks: { external_links: 123 } }
      end

      it 'passes' do
        expect { subject }.to raise_error(JsonSchema::Error)
      end
    end

    context 'invalid checks (external_links.exclude has invalid type)' do
      let(:hash) do
        { checks: { external_links: { exclude: 'everything' } } }
      end

      it 'passes' do
        expect { subject }.to raise_error(JsonSchema::Error)
      end
    end

    context 'invalid checks (external_links.exclude_files has invalid type)' do
      let(:hash) do
        { checks: { external_links: { exclude_files: 'everything' } } }
      end

      it 'passes' do
        expect { subject }.to raise_error(JsonSchema::Error)
      end
    end

    context 'valid environments' do
      let(:hash) { { environments: { production: {} } } }

      it 'passes' do
        expect { subject }.not_to raise_error
      end
    end

    context 'invalid environments (not an object)' do
      let(:hash) { { environments: nil } }

      it 'fails' do
        expect { subject }.to raise_error(JsonSchema::Error)
      end
    end

    context 'invalid environments (values are not objects)' do
      let(:hash) { { environments: { production: nil } } }

      it 'fails' do
        expect { subject }.to raise_error(JsonSchema::Error)
      end
    end
  end
end
nanoc-4.11.0/nanoc/spec/nanoc/base/entities/content_spec.rb000066400000000000000000000116021340050175000235630ustar00rootroot00000000000000# 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.11.0/nanoc/spec/nanoc/base/entities/context_spec.rb000066400000000000000000000012731340050175000236000ustar00rootroot00000000000000# frozen_string_literal: true

describe Nanoc::Int::Context do
  let(:context) do
    Nanoc::Int::Context.new(foo: 'bar', baz: 'quux')
  end

  it 'provides instance variables' do
    expect(eval('@foo', context.get_binding)).to eq('bar')
  end

  it 'provides instance methods' do
    expect(eval('foo', context.get_binding)).to eq('bar')
  end

  it 'supports #include' do
    eval('include Nanoc::Helpers::HTMLEscape', context.get_binding)
    expect(eval('h("<>")', context.get_binding)).to eq('<>')
  end

  it 'has correct examples' do
    expect('Nanoc::Int::Context#initialize')
      .to have_correct_yard_examples
      .in_file('nanoc/lib/nanoc/base/entities/context.rb')
  end
end
nanoc-4.11.0/nanoc/spec/nanoc/base/entities/document_spec.rb000066400000000000000000000225451340050175000237370ustar00rootroot00000000000000# 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

  describe '#identifier=' do
    let(:document) { described_class.new('stuff', {}, '/foo.md') }

    it 'allows changing to a string that contains a full identifier' do
      expect { document.identifier = '/thing' }.not_to raise_error

      expect(document.identifier).to eq('/thing')
      expect(document.identifier).to be_full
    end

    it 'refuses changing to a string that does not contain a full identifier' do
      expect { document.identifier = '/thing/' }
        .to raise_error(Nanoc::Identifier::InvalidFullIdentifierError)
    end

    it 'allos changing to a full identifier' do
      document.identifier = Nanoc::Identifier.new('/thing')

      expect(document.identifier.to_s).to eq('/thing')
      expect(document.identifier).to be_full
    end

    it 'allos changing to a legacy identifier' do
      document.identifier = Nanoc::Identifier.new('/thing/', type: :legacy)

      expect(document.identifier).to eq('/thing/')
      expect(document.identifier).to be_legacy
    end
  end

  describe '#content=' do
    let(:document) do
      described_class.new(
        content_arg,
        attributes_arg,
        '/foo.md',
        checksum_data: 'ch3cksum_d4t4',
        content_checksum_data: 'c0nt3nt_ch3cksum_d4t4',
        attributes_checksum_data: '4ttr_ch3cksum_d4t4',
      )
    end

    let(:content_arg) { 'Hallo' }
    let(:attributes_arg) { { foo: { bar: 'asdf' } } }

    subject { document.content = Nanoc::Int::TextualContent.new('New!') }

    it 'clears checksum' do
      expect { subject }
        .to change { document.checksum_data }
        .from('ch3cksum_d4t4')
        .to(nil)
    end

    it 'clears content checksum' do
      expect { subject }
        .to change { document.content_checksum_data }
        .from('c0nt3nt_ch3cksum_d4t4')
        .to(nil)
    end

    it 'does not clear attributes checksum data' do
      expect { subject }
        .not_to change { document.attributes_checksum_data }
    end
  end

  describe '#set_attribute' do
    let(:document) do
      described_class.new(
        content_arg,
        attributes_arg,
        '/foo.md',
        checksum_data: 'ch3cksum_d4t4',
        content_checksum_data: 'c0nt3nt_ch3cksum_d4t4',
        attributes_checksum_data: '4ttr_ch3cksum_d4t4',
      )
    end

    let(:content_arg) { 'Hallo' }
    let(:attributes_arg) { { foo: { bar: 'asdf' } } }

    subject { document.set_attribute(:key, 'value') }

    it 'clears checksum' do
      expect { subject }
        .to change { document.checksum_data }
        .from('ch3cksum_d4t4')
        .to(nil)
    end

    it 'clears attributes checksum' do
      expect { subject }
        .to change { document.attributes_checksum_data }
        .from('4ttr_ch3cksum_d4t4')
        .to(nil)
    end

    it 'does not clear content checksum data' do
      expect { subject }
        .not_to change { document.content_checksum_data }
    end
  end
end
nanoc-4.11.0/nanoc/spec/nanoc/base/entities/identifiable_collection_spec.rb000066400000000000000000000140661340050175000267520ustar00rootroot00000000000000# 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(dir: Dir.getwd) }
    let(:objects) { [] }

    describe '#reject' do
      subject { identifiable_collection.reject { |_| false } }

      it { is_expected.to be_a(described_class) }
    end

    describe '#inspect' do
      subject { identifiable_collection.inspect }

      it { is_expected.to eq("<#{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(dir: Dir.getwd).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(dir: Dir.getwd) }

        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

    describe 'changing identifiers' do
      let(:objects) do
        [
          Nanoc::Int::Item.new('Foo', {}, '/foo'),
        ]
      end

      subject { objects[0].identifier = '/bar' }

      it 'makes /foo nil' do
        expect { subject }
          .to change { identifiable_collection['/foo'] }
          .from(objects[0])
          .to(nil)
      end

      it 'makes /bar non-nil' do
        expect { subject }
          .to change { identifiable_collection['/bar'] }
          .from(nil)
          .to(objects[0])
      end
    end

    describe '#each' do
      let(:objects) do
        [
          Nanoc::Int::Item.new('Foo', {}, '/foo'),
          Nanoc::Int::Item.new('Bar', {}, '/bar'),
        ]
      end

      it 'loops' do
        res = []
        identifiable_collection.each { |i| res << i.identifier.to_s }
        expect(res).to match_array(['/foo', '/bar'])
      end
    end

    describe '#map' do
      let(:objects) do
        [
          Nanoc::Int::Item.new('Foo', {}, '/foo'),
          Nanoc::Int::Item.new('Bar', {}, '/bar'),
        ]
      end

      it 'loops' do
        res = identifiable_collection.map { |i| i.identifier.to_s }
        expect(res).to match_array(['/foo', '/bar'])
      end
    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.11.0/nanoc/spec/nanoc/base/entities/identifier_spec.rb000066400000000000000000000327611340050175000242440ustar00rootroot00000000000000# 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 'refuses string ending with a slash' do
        expect { described_class.new('/foo/') }
          .to raise_error(Nanoc::Identifier::InvalidFullIdentifierError)
      end

      it 'refuses string with only slash' do
        expect { described_class.new('/') }
          .to raise_error(Nanoc::Identifier::InvalidFullIdentifierError)
      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 '#match?' do
    let(:identifier) { described_class.new('/foo/bar') }

    subject { identifier.match?(pat) }

    context 'given a regex' do
      context 'matching regex' do
        let(:pat) { %r{\A/foo/bar} }
        it { is_expected.to be(true) }
        example { expect { subject }.not_to change { Regexp.last_match } }
      end

      context 'non-matching regex' do
        let(:pat) { %r{\A/qux/monkey} }
        it { is_expected.to be(false) }
        example { expect { subject }.not_to change { Regexp.last_match } }
      end
    end

    context 'given a string' do
      context 'matching string' do
        let(:pat) { '/foo/*' }
        it { is_expected.to be(true) }
        example { expect { subject }.not_to change { Regexp.last_match } }
      end

      context 'non-matching string' do
        let(:pat) { '/qux/*' }
        it { is_expected.to be(false) }
        example { expect { subject }.not_to change { Regexp.last_match } }
      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('/', type: :legacy) }
      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.11.0/nanoc/spec/nanoc/base/entities/item_rep_spec.rb000066400000000000000000000012121340050175000237110ustar00rootroot00000000000000# 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.11.0/nanoc/spec/nanoc/base/entities/item_spec.rb000066400000000000000000000004371340050175000230530ustar00rootroot00000000000000# 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.11.0/nanoc/spec/nanoc/base/entities/layout_spec.rb000066400000000000000000000004471340050175000234330ustar00rootroot00000000000000# 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.11.0/nanoc/spec/nanoc/base/entities/lazy_value_spec.rb000066400000000000000000000065611340050175000242740ustar00rootroot00000000000000# frozen_string_literal: true

describe Nanoc::Int::LazyValue do
  describe '#value' do
    let(:value_arg) { +'Hello world' }
    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) }

    context 'freeze before calling #value' do
      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

    context 'freeze after calling #value' do
      before do
        subject.value
        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
end
nanoc-4.11.0/nanoc/spec/nanoc/base/entities/outdatedness_status_spec.rb000066400000000000000000000055111340050175000262200ustar00rootroot00000000000000# 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.11.0/nanoc/spec/nanoc/base/entities/pattern_spec.rb000066400000000000000000000073541340050175000235770ustar00rootroot00000000000000# 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 'converts from symbol' 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 '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.11.0/nanoc/spec/nanoc/base/entities/processing_action_spec.rb000066400000000000000000000005251340050175000256240ustar00rootroot00000000000000# 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.11.0/nanoc/spec/nanoc/base/entities/processing_actions/000077500000000000000000000000001340050175000244465ustar00rootroot00000000000000nanoc-4.11.0/nanoc/spec/nanoc/base/entities/processing_actions/filter_spec.rb000066400000000000000000000057731340050175000273060ustar00rootroot00000000000000# frozen_string_literal: true

describe Nanoc::Int::ProcessingActions::Filter do
  let(:action) { described_class.new(:foo, awesome: true) }

  describe '#serialize' do
    subject { action.serialize }
    it { is_expected.to eql([:filter, :foo, 'sJYzLjHGo1e4ytuDfnOLkqrt9QE=']) }
  end

  describe '#to_s' do
    subject { action.to_s }
    it { is_expected.to eql('filter :foo, {:awesome=>true}') }
  end

  describe '#inspect' do
    subject { action.inspect }
    it { is_expected.to eql('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

  describe '#== and #eql?' do
    context 'other action is equal' do
      let(:action_a) { described_class.new([:erb], ['/foo.html']) }
      let(:action_b) { described_class.new([:erb], ['/foo.html']) }

      it 'is ==' do
        expect(action_a).to eq(action_b)
      end

      it 'is eql?' do
        expect(action_a).to eql(action_b)
      end
    end

    context 'other action has different name' do
      let(:action_a) { described_class.new([:erb], ['/foo.html']) }
      let(:action_b) { described_class.new([:haml], ['/foo.html']) }

      it 'is not ==' do
        expect(action_a).not_to eq(action_b)
      end

      it 'is not eql?' do
        expect(action_a).not_to eql(action_b)
      end
    end

    context 'other action has different paths' do
      let(:action_a) { described_class.new([:erb], ['/foo.html']) }
      let(:action_b) { described_class.new([:erb], ['/foo.htm']) }

      it 'is not ==' do
        expect(action_a).not_to eq(action_b)
      end

      it 'is not eql?' do
        expect(action_a).not_to eql(action_b)
      end
    end

    context 'other action is not a layout action' do
      let(:action_a) { described_class.new([:erb], ['/foo.html']) }
      let(:action_b) { :donkey }

      it 'is not ==' do
        expect(action_a).not_to eq(action_b)
      end

      it 'is not eql?' do
        expect(action_a).not_to eql(action_b)
      end
    end
  end

  describe '#hash' do
    context 'other action is equal' do
      let(:action_a) { described_class.new([:erb], ['/foo.html']) }
      let(:action_b) { described_class.new([:erb], ['/foo.html']) }

      it 'is the same' do
        expect(action_a.hash == action_b.hash).to eql(true)
      end
    end

    context 'other action has different name' do
      let(:action_a) { described_class.new([:erb], ['/foo.html']) }
      let(:action_b) { described_class.new([:haml], ['/foo.html']) }

      it 'is the same' do
        expect(action_a.hash == action_b.hash).to eql(false)
      end
    end

    context 'other action has different paths' do
      let(:action_a) { described_class.new([:erb], ['/foo.html']) }
      let(:action_b) { described_class.new([:erb], ['/foo.htm']) }

      it 'is the same' do
        expect(action_a.hash == action_b.hash).to eql(false)
      end
    end

    context 'other action is not a layout action' do
      let(:action_a) { described_class.new([:erb], ['/foo.html']) }
      let(:action_b) { :woof }

      it 'is the same' do
        expect(action_a.hash == action_b.hash).to eql(false)
      end
    end
  end
end
nanoc-4.11.0/nanoc/spec/nanoc/base/entities/props_spec.rb000066400000000000000000000275041340050175000232640ustar00rootroot00000000000000# 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 '#to_s' do
    subject { props.to_s }

    context 'nothing active' do
      it { is_expected.to eql('____') }
    end

    context 'attributes active' do
      let(:props) { described_class.new(attributes: true) }
      it { is_expected.to eql('_a__') }
    end

    context 'attributes and compiled_content active' do
      let(:props) { described_class.new(attributes: true, compiled_content: true) }
      it { is_expected.to eql('_ac_') }
    end

    context 'compiled_content active' do
      let(:props) { described_class.new(compiled_content: true) }
      it { is_expected.to eql('__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.11.0/nanoc/spec/nanoc/base/entities/site_spec.rb000066400000000000000000000034011340050175000230530ustar00rootroot00000000000000# 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(dir: Dir.getwd).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.11.0/nanoc/spec/nanoc/base/errors/000077500000000000000000000000001340050175000202425ustar00rootroot00000000000000nanoc-4.11.0/nanoc/spec/nanoc/base/errors/dependency_cycle_spec.rb000066400000000000000000000022171340050175000251000ustar00rootroot00000000000000# 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.11.0/nanoc/spec/nanoc/base/feature_spec.rb000066400000000000000000000055051340050175000217250ustar00rootroot00000000000000# 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'] = +''
    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'] = +''
    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.11.0/nanoc/spec/nanoc/base/filter_spec.rb000066400000000000000000000163041340050175000215560ustar00rootroot00000000000000# 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 '.named!' do
    it 'returns filter if exists' do
      expect(Nanoc::Filter.named!(:erb)).not_to be_nil
      expect(Nanoc::Filter.named!(:erb).identifier).to eq(:erb)
    end

    it 'raises if non-existent' do
      expect { Nanoc::Filter.named!(:ajklsdfklasjflkd) }
        .to raise_error(
          Nanoc::Int::Errors::UnknownFilter,
          'The requested filter, “ajklsdfklasjflkd”, does not exist.',
        )
    end
  end

  describe 'assigns' do
    context 'no assigns given' do
      subject { described_class.new }

      it 'has empty assigns' do
        expect(subject.instance_eval { @assigns }).to eq({})
      end
    end

    context 'assigns given' do
      subject { described_class.new(foo: 'bar') }

      it 'has assigns' do
        expect(subject.instance_eval { @assigns }).to eq(foo: 'bar')
      end

      it 'can access assigns with @' do
        expect(subject.instance_eval { @foo }).to eq('bar')
      end

      it 'can access assigns without @' do
        expect(subject.instance_eval { foo }).to eq('bar')
      end
    end
  end

  describe '#run' do
    context 'no subclass' do
      subject { described_class.new.run('stuff') }

      it 'errors' do
        expect { subject }.to raise_error(NotImplementedError)
      end
    end

    context 'subclass' do
      # TODO
    end
  end

  describe '#filename' do
    subject { described_class.new(assigns).filename }

    context 'assigns contains item + item rep' do
      let(:item) { Nanoc::Int::Item.new('asdf', {}, '/donkey.md') }
      let(:item_rep) { Nanoc::Int::ItemRep.new(item, :animal) }
      let(:assigns) { { item: item, item_rep: item_rep } }

      it { is_expected.to eq('item /donkey.md (rep animal)') }
    end

    context 'assigns contains layout' do
      let(:layout) { Nanoc::Int::Layout.new('asdf', {}, '/donkey.md') }
      let(:assigns) { { layout: layout } }

      it { is_expected.to eq('layout /donkey.md') }
    end

    context 'assigns contains neither' do
      let(:assigns) { {} }

      it { is_expected.to eq('?') }
    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::CompilationItemView.new(item, view_context) }
    let(:rep) { Nanoc::Int::ItemRep.new(item, :default) }

    let(:view_context) do
      Nanoc::ViewContextForCompilation.new(
        reps: reps,
        items: Nanoc::Int::ItemCollection.new(config),
        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(:empty_items) { Nanoc::Int::ItemCollection.new(config) }
    let(:empty_layouts) { Nanoc::Int::LayoutCollection.new(config) }

    let(:config) { Nanoc::Int::Configuration.new(dir: Dir.getwd).with_defaults }

    let(:reps) { Nanoc::Int::ItemRepRepo.new }

    let(:assigns) do
      {
        item: item_view,
      }
    end

    context 'reps exist' do
      before { reps << rep }

      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

        it 'creates dependency' do
          expect { subject }
            .to create_dependency_on(item_view)
        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

    context 'no reps exist' do
      context 'textual' do
        it 'creates dependency' do
          expect { subject }
            .to create_dependency_on(item_view)
        end
      end

      context 'binary' do
        let(:item) { Nanoc::Int::Item.new(content, {}, '/stuff.md') }

        let(:filename) { File.expand_path('foo.dat') }
        let(:content) { Nanoc::Int::BinaryContent.new(filename) }

        it 'creates dependency' do
          expect { subject }
            .to create_dependency_on(item_view)
        end
      end
    end
  end
end
nanoc-4.11.0/nanoc/spec/nanoc/base/item_rep_writer_spec.rb000066400000000000000000000103551340050175000234710ustar00rootroot00000000000000# frozen_string_literal: true

describe Nanoc::Int::ItemRepWriter do
  describe '#write' do
    let(:raw_path) { Dir.getwd + '/output/blah.dat' }

    let(:item) { Nanoc::Int::Item.new(orig_content, {}, '/foo') }

    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 contents' do
        expect(Nanoc::Int::NotificationCenter).to receive(:post)
          .with(:rep_write_started, item_rep, Dir.getwd + '/output/blah.dat')
        expect(Nanoc::Int::NotificationCenter).to receive(:post)
          .with(:rep_write_ended, item_rep, true, Dir.getwd + '/output/blah.dat', true, true)

        subject

        expect(File.read('output/blah.dat')).to eql('binary donkey stuff')
      end

      it 'uses hard links' do
        subject

        input = File.stat(snapshot_contents[:donkey].filename)
        output = File.stat('output/blah.dat')

        expect(input.ino).to eq(output.ino)
      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(:rep_write_started, item_rep, Dir.getwd + '/output/blah.dat')
        expect(Nanoc::Int::NotificationCenter).to receive(:post)
          .with(:rep_write_ended, item_rep, false, Dir.getwd + '/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.11.0/nanoc/spec/nanoc/base/repos/000077500000000000000000000000001340050175000200565ustar00rootroot00000000000000nanoc-4.11.0/nanoc/spec/nanoc/base/repos/aggregate_data_source_spec.rb000066400000000000000000000035011340050175000257130ustar00rootroot00000000000000# frozen_string_literal: true

describe Nanoc::Int::AggregateDataSource, stdio: true do
  let(:klass_1) do
    Class.new(Nanoc::DataSource) do
      def items
        [Nanoc::Int::Item.new('One', {}, '/one.md')]
      end

      def item_changes
        %i[one_foo one_bar]
      end

      def layouts
        [Nanoc::Int::Layout.new('One', {}, '/one.md')]
      end

      def layout_changes
        %i[one_foo one_bar]
      end
    end
  end

  let(:klass_2) do
    Class.new(Nanoc::DataSource) do
      def items
        [Nanoc::Int::Item.new('Two', {}, '/two.md')]
      end

      def item_changes
        %i[two_foo two_bar]
      end

      def layouts
        [Nanoc::Int::Layout.new('Two', {}, '/two.md')]
      end

      def layout_changes
        %i[two_foo two_bar]
      end
    end
  end

  let(:data_source_1) do
    klass_1.new({}, nil, nil, {})
  end

  let(:data_source_2) do
    klass_2.new({}, nil, nil, {})
  end

  subject(:data_source) do
    described_class.new([data_source_1, data_source_2], {})
  end

  describe '#items' do
    subject { data_source.items }

    it 'contains all items' do
      expect(subject).to match_array(data_source_1.items + data_source_2.items)
    end
  end

  describe '#layouts' do
    subject { data_source.layouts }

    it 'contains all layouts' do
      expect(subject).to match_array(data_source_1.layouts + data_source_2.layouts)
    end
  end

  describe '#item_changes' do
    subject { data_source.item_changes }

    it 'yields changes from both' do
      expect(subject).to match_array(data_source_1.item_changes + data_source_2.item_changes)
    end
  end

  describe '#layout_changes' do
    subject { data_source.layout_changes }

    it 'yields changes from both' do
      expect(subject).to match_array(data_source_1.layout_changes + data_source_2.layout_changes)
    end
  end
end
nanoc-4.11.0/nanoc/spec/nanoc/base/repos/checksum_store_spec.rb000066400000000000000000000076141340050175000244430ustar00rootroot00000000000000# frozen_string_literal: true

describe Nanoc::Int::ChecksumStore do
  let(:store) { described_class.new(config: config, objects: objects) }

  let(:config) { Nanoc::Int::Configuration.new(dir: Dir.getwd).with_defaults }

  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.11.0/nanoc/spec/nanoc/base/repos/compiled_content_cache_spec.rb000066400000000000000000000032031340050175000260640ustar00rootroot00000000000000# frozen_string_literal: true

describe Nanoc::Int::CompiledContentCache do
  let(:cache) { described_class.new(config: config) }

  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') }

  let(:config) { Nanoc::Int::Configuration.new(dir: Dir.getwd).with_defaults }

  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 content' do
        expect(cache[other_item_rep][:last].string).to eql('omg')
      end
    end

    context 'after pruning' do
      before do
        cache.prune(items: items)
      end

      it 'has no content' do
        expect(cache[other_item_rep]).to be_nil
      end
    end
  end
end
nanoc-4.11.0/nanoc/spec/nanoc/base/repos/config_loader_spec.rb000066400000000000000000000176111340050175000242160ustar00rootroot00000000000000# 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 'YAML 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 'TOML config file present' do
      around do |ex|
        Nanoc::Feature.enable(Nanoc::Feature::TOML) do
          ex.run
        end
      end

      before do
        File.write('nanoc.toml', '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 'YAML config file and YAML 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 'TOML config file and TOML parent present' do
      around do |ex|
        Nanoc::Feature.enable(Nanoc::Feature::TOML) do
          ex.run
        end
      end

      before do
        File.write('nanoc.toml', 'parent_config_file = "parent.toml"')
        File.write('parent.toml', '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(dir: Dir.getwd, 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(dir: Dir.getwd, 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.11.0/nanoc/spec/nanoc/base/repos/data_source_spec.rb000066400000000000000000000062371340050175000237160ustar00rootroot00000000000000# frozen_string_literal: true

describe Nanoc::DataSource, stdio: true do
  subject(:data_source) do
    described_class.new({}, nil, nil, {})
  end

  it 'has an empty #up implementation' do
    data_source.up
  end

  it 'has an empty #down implementation' do
    data_source.down
  end

  it 'returns empty #items' do
    expect(data_source.items).to be_empty
  end

  it 'returns empty #layouts' do
    expect(data_source.layouts).to be_empty
  end

  describe '#new_item' do
    it 'supports checksum data' do
      item = data_source.new_item('stuff', { title: 'Stuff!' }, '/asdf', checksum_data: 'abcdef')

      expect(item.content.string).to eql('stuff')
      expect(item.attributes[:title]).to eql('Stuff!')
      expect(item.identifier).to eql(Nanoc::Identifier.new('/asdf'))
      expect(item.checksum_data).to eql('abcdef')
    end

    it 'supports content/attributes checksum data' do
      item = data_source.new_item('stuff', { title: 'Stuff!' }, '/asdf', content_checksum_data: 'con-cs', attributes_checksum_data: 'attr-cs')

      expect(item.content.string).to eql('stuff')
      expect(item.attributes[:title]).to eql('Stuff!')
      expect(item.identifier).to eql(Nanoc::Identifier.new('/asdf'))
      expect(item.content_checksum_data).to eql('con-cs')
      expect(item.attributes_checksum_data).to eql('attr-cs')
    end
  end

  describe '#new_layout' do
    it 'supports checksum data' do
      layout = data_source.new_layout('stuff', { title: 'Stuff!' }, '/asdf', checksum_data: 'abcdef')

      expect(layout.content.string).to eql('stuff')
      expect(layout.attributes[:title]).to eql('Stuff!')
      expect(layout.identifier).to eql(Nanoc::Identifier.new('/asdf'))
      expect(layout.checksum_data).to eql('abcdef')
    end

    it 'supports content/attributes checksum data' do
      layout = data_source.new_layout('stuff', { title: 'Stuff!' }, '/asdf', content_checksum_data: 'con-cs', attributes_checksum_data: 'attr-cs')

      expect(layout.content.string).to eql('stuff')
      expect(layout.attributes[:title]).to eql('Stuff!')
      expect(layout.identifier).to eql(Nanoc::Identifier.new('/asdf'))
      expect(layout.content_checksum_data).to eql('con-cs')
      expect(layout.attributes_checksum_data).to eql('attr-cs')
    end
  end

  describe '#item_changes' do
    subject { data_source.item_changes }

    it 'warns' do
      expect { subject }.to output("Caution: Data source nil does not implement #item_changes; live compilation will not pick up changes in this data source.\n").to_stderr
    end

    it 'never yields anything' do
      q = SizedQueue.new(1)
      Thread.new { subject.each { |c| q << c } }
      sleep 0.1
      expect { q.pop(true) }.to raise_error(ThreadError)
    end
  end

  describe '#layout_changes' do
    subject { data_source.layout_changes }

    it 'warns' do
      expect { subject }.to output("Caution: Data source nil does not implement #layout_changes; live compilation will not pick up changes in this data source.\n").to_stderr
    end

    it 'never yields anything' do
      q = SizedQueue.new(1)
      Thread.new { subject.each { |c| q << c } }
      sleep 0.1
      expect { q.pop(true) }.to raise_error(ThreadError)
    end
  end
end
nanoc-4.11.0/nanoc/spec/nanoc/base/repos/dependency_store_spec.rb000066400000000000000000000417271340050175000247620ustar00rootroot00000000000000# 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(dir: Dir.getwd).with_defaults }

  it 'is empty by default' do
    expect(store.objects_causing_outdatedness_of(item_a)).to be_empty
    expect(store.objects_causing_outdatedness_of(item_b)).to be_empty
    expect(store.objects_causing_outdatedness_of(item_c)).to be_empty
    expect(store.objects_causing_outdatedness_of(layout_a)).to be_empty
    expect(store.objects_causing_outdatedness_of(layout_b)).to be_empty
  end

  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
    end

    let(:reloaded_store) do
      described_class.new(items_after, layouts, config).tap(&:load)
    end

    context 'no new items' do
      let(:items_after) { items }

      it 'has the right dependencies for item A' do
        deps = reloaded_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 = reloaded_store.dependencies_causing_outdatedness_of(item_b)
        expect(deps).to be_empty
      end

      it 'has the right dependencies for item C' do
        deps = reloaded_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(reloaded_store.objects_causing_outdatedness_of(item_a)).not_to include(item_d)
        expect(reloaded_store.objects_causing_outdatedness_of(item_b)).not_to include(item_d)
        expect(reloaded_store.objects_causing_outdatedness_of(item_c)).not_to include(item_d)
        expect(reloaded_store.objects_causing_outdatedness_of(item_d)).not_to include(item_d)
      end
    end

    context 'unrelated item removed' do
      let(:items_after) do
        Nanoc::Int::ItemCollection.new(config, [item_a, item_b])
      end

      it 'does not mark items as outdated' do
        expect(reloaded_store.objects_causing_outdatedness_of(item_a)).to eq([item_b])
        expect(reloaded_store.objects_causing_outdatedness_of(item_b)).to be_empty
        expect(reloaded_store.objects_causing_outdatedness_of(item_c)).to be_empty
      end
    end

    context 'related item removed' do
      let(:items_after) do
        Nanoc::Int::ItemCollection.new(config, [item_a, item_c])
      end

      it 'does not mark items as outdated' do
        expect(reloaded_store.objects_causing_outdatedness_of(item_a)).to eq([nil])
        expect(reloaded_store.objects_causing_outdatedness_of(item_b)).to be_empty
        expect(reloaded_store.objects_causing_outdatedness_of(item_c)).to be_empty
      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

      context 'dependency on self' do
        subject { store.record_dependency(source_obj, item_a) }

        it 'does not create dependency on self' do
          expect { subject }
            .not_to change { store.objects_causing_outdatedness_of(source_obj) }
        end
      end

      context 'two dependencies' do
        subject do
          store.record_dependency(source_obj, item_b)
          store.record_dependency(source_obj, item_b)
        end

        it 'does not create duplicate dependencies' do
          expect { subject }
            .to change { store.objects_causing_outdatedness_of(source_obj) }
            .from([])
            .to([item_b])
        end
      end

      context 'dependency to nil' do
        subject { store.record_dependency(source_obj, nil) }

        it 'creates a dependency to nil' do
          expect { subject }
            .to change { store.objects_causing_outdatedness_of(source_obj) }
            .from([])
            .to([nil])
        end
      end

      context 'dependency from nil' do
        subject { store.record_dependency(nil, item_b) }

        it 'does not create a dependency from nil' do
          expect { subject }
            .not_to change { store.objects_causing_outdatedness_of(item_b) }
        end
      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

  describe '#forget_dependencies_for' do
    before do
      store.record_dependency(item_a, item_b)
      store.record_dependency(item_a, item_c)
      store.record_dependency(item_b, item_a)
      store.record_dependency(item_b, item_c)
      store.record_dependency(item_c, item_a)
      store.record_dependency(item_c, item_b)
    end

    subject { store.forget_dependencies_for(item_b) }

    it 'removes dependencies from item_a' do
      expect { subject }
        .not_to change { store.objects_causing_outdatedness_of(item_a) }
    end

    it 'removes dependencies from item_b' do
      expect { subject }
        .to change { store.objects_causing_outdatedness_of(item_b) }
        .from(match_array([item_a, item_c]))
        .to(be_empty)
    end

    it 'removes dependencies from item_c' do
      expect { subject }
        .not_to change { store.objects_causing_outdatedness_of(item_c) }
    end
  end
end
nanoc-4.11.0/nanoc/spec/nanoc/base/repos/in_mem_data_source_spec.rb000066400000000000000000000015031340050175000252310ustar00rootroot00000000000000# frozen_string_literal: true

describe Nanoc::Int::InMemDataSource, stdio: true do
  let(:klass) do
    Class.new(Nanoc::DataSource) do
      def item_changes
        %i[one_foo one_bar]
      end

      def layout_changes
        %i[one_foo one_bar]
      end
    end
  end

  let(:original_data_source) do
    klass.new({}, nil, nil, {})
  end

  subject(:data_source) do
    described_class.new([], [], original_data_source)
  end

  describe '#item_changes' do
    subject { data_source.item_changes }

    it 'yields changes from the original' do
      expect(subject).to eq(original_data_source.item_changes)
    end
  end

  describe '#layout_changes' do
    subject { data_source.layout_changes }

    it 'yields changes from the original' do
      expect(subject).to eq(original_data_source.layout_changes)
    end
  end
end
nanoc-4.11.0/nanoc/spec/nanoc/base/repos/outdatedness_store_spec.rb000066400000000000000000000024201340050175000253310ustar00rootroot00000000000000# frozen_string_literal: true

describe Nanoc::Int::OutdatednessStore do
  subject(:store) { described_class.new(config: config) }

  let(:item) { Nanoc::Int::Item.new('foo', {}, '/foo.md') }
  let(:rep) { Nanoc::Int::ItemRep.new(item, :foo) }

  let(:config) { Nanoc::Int::Configuration.new(dir: Dir.getwd).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 'reloading' do
    subject do
      store.store
      store.load
      store.include?(rep)
    end

    context 'not added' do
      it { is_expected.not_to be }
    end

    context 'added' do
      before { store.add(rep) }
      it { is_expected.to be }
    end
  end
end
nanoc-4.11.0/nanoc/spec/nanoc/base/repos/prefixed_data_source_spec.rb000066400000000000000000000015241340050175000255760ustar00rootroot00000000000000# frozen_string_literal: true

describe Nanoc::Int::PrefixedDataSource, stdio: true do
  let(:klass) do
    Class.new(Nanoc::DataSource) do
      def item_changes
        %i[one_foo one_bar]
      end

      def layout_changes
        %i[one_foo one_bar]
      end
    end
  end

  let(:original_data_source) do
    klass.new({}, nil, nil, {})
  end

  subject(:data_source) do
    described_class.new(original_data_source, '/itemz', '/layoutz')
  end

  describe '#item_changes' do
    subject { data_source.item_changes }

    it 'yields changes from the original' do
      expect(subject).to eq(original_data_source.item_changes)
    end
  end

  describe '#layout_changes' do
    subject { data_source.layout_changes }

    it 'yields changes from the original' do
      expect(subject).to eq(original_data_source.layout_changes)
    end
  end
end
nanoc-4.11.0/nanoc/spec/nanoc/base/repos/site_loader_spec.rb000066400000000000000000000157441340050175000237220ustar00rootroot00000000000000# frozen_string_literal: true

describe Nanoc::Int::SiteLoader do
  let(:loader) { described_class.new }

  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(dir: Dir.getwd).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.11.0/nanoc/spec/nanoc/base/repos/snapshot_repo_spec.rb000066400000000000000000000237541340050175000243140ustar00rootroot00000000000000# 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, 'You cannot access the compiled content of a binary item representation (but you can access the path). The offending item rep is /foo.md (rep name :foo).')
          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, 'You cannot access the compiled content of a binary item representation (but you can access the path). The offending item rep is /foo.md (rep name :foo).')
                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, 'You cannot access the compiled content of a binary item representation (but you can access the path). The offending item rep is /foo.md (rep name :foo).')
                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.11.0/nanoc/spec/nanoc/base/repos/store_spec.rb000066400000000000000000000067241340050175000225620ustar00rootroot00000000000000# frozen_string_literal: true

describe Nanoc::Int::Store do
  describe '#tmp_path_for' do
    context 'passing config' do
      subject { described_class.tmp_path_for(config: config, store_name: 'giraffes') }

      let(:code_snippets) { [] }
      let(:items) { [] }
      let(:layouts) { [] }

      def gen_hash(path)
        Digest::SHA1.hexdigest(File.absolute_path(path))[0..12]
      end

      let(:hash_output) { gen_hash('output') }
      let(:hash_output_default) { gen_hash('output-default') }
      let(:hash_output_staging) { gen_hash('output-staging') }
      let(:hash_output_production) { gen_hash('output-production') }

      context 'no env specified' do
        let(:config) { Nanoc::Int::Configuration.new(dir: Dir.getwd, hash: config_hash).with_defaults.with_environment }

        context 'output dir is unspecified' do
          let(:config_hash) { {} }
          it { is_expected.to eql(Dir.getwd + "/tmp/nanoc/#{hash_output}/giraffes") }
        end

        context 'output dir at root is specified' do
          let(:config_hash) { { output_dir: 'output-default' } }
          it { is_expected.to eql(Dir.getwd + "/tmp/nanoc/#{hash_output_default}/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(Dir.getwd + "/tmp/nanoc/#{hash_output_default}/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(Dir.getwd + "/tmp/nanoc/#{hash_output}/giraffes") }
        end
      end

      context 'env specified' do
        let(:config) { Nanoc::Int::Configuration.new(env_name: 'staging', dir: Dir.getwd, hash: config_hash).with_defaults.with_environment }

        context 'output dir is unspecified' do
          let(:config_hash) { {} }
          it { is_expected.to eql(Dir.getwd + "/tmp/nanoc/#{hash_output}/giraffes") }
        end

        context 'output dir at root is specified' do
          let(:config_hash) { { output_dir: 'output-default' } }
          it { is_expected.to eql(Dir.getwd + "/tmp/nanoc/#{hash_output_default}/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(Dir.getwd + "/tmp/nanoc/#{hash_output_staging}/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(Dir.getwd + "/tmp/nanoc/#{hash_output}/giraffes") }
        end
      end
    end
  end

  let(:test_store_klass) do
    Class.new(Nanoc::Int::Store) do
      def data
        @data
      end

      def data=(new_data)
        @data = new_data
      end
    end
  end

  it 'deletes and reloads on error' do
    store = test_store_klass.new('test.db', 1)

    # Create
    store.load
    store.data = { fun: 'sure' }
    store.store

    # Test stored values
    store = test_store_klass.new('test.db', 1)
    store.load
    expect(store.data).to eq(fun: 'sure')

    # Mess up
    File.write('test.db', 'Damn {}#}%@}$^)@&$&*^#@ broken stores!!!')

    # Reload
    store = test_store_klass.new('test.db', 1)
    store.load
    expect(store.data).to be_nil
  end
end
nanoc-4.11.0/nanoc/spec/nanoc/base/services/000077500000000000000000000000001340050175000205515ustar00rootroot00000000000000nanoc-4.11.0/nanoc/spec/nanoc/base/services/action_sequence_builder_spec.rb000066400000000000000000000032071340050175000267650ustar00rootroot00000000000000# frozen_string_literal: true

describe Nanoc::Int::ActionSequenceBuilder do
  let(:builder) { described_class.new(item_rep) }

  let(:item_rep) { Nanoc::Int::ItemRep.new(item, :default) }
  let(:item) { Nanoc::Int::Item.new('some content', {}, '/foo.md') }

  describe '#add_filter' do
    subject { builder.add_filter(:erb, foo: :bar) }

    it 'adds an action' do
      expect { subject }
        .to change { builder.action_sequence.actions }
        .from([])
        .to([Nanoc::Int::ProcessingActions::Filter.new(:erb, foo: :bar)])
    end
  end

  describe '#add_layout' do
    subject { builder.add_layout('/oink.erb', foo: :bar) }

    it 'adds an action' do
      expect { subject }
        .to change { builder.action_sequence.actions }
        .from([])
        .to([Nanoc::Int::ProcessingActions::Layout.new('/oink.erb', foo: :bar)])
    end
  end

  describe '#add_snapshot' do
    context 'add one snapshot' do
      subject { builder.add_snapshot(:last, '/foo.html') }

      it 'adds an action' do
        expect { subject }
          .to change { builder.action_sequence.actions }
          .from([])
          .to([Nanoc::Int::ProcessingActions::Snapshot.new([:last], ['/foo.html'])])
      end
    end

    context 'add two snapshots with same name' do
      subject do
        builder.add_snapshot(:last, '/foo.html')
        builder.add_snapshot(:last, '/foo.htm')
      end

      it 'raises' do
        expect { subject }
          .to raise_error(Nanoc::Int::Errors::CannotCreateMultipleSnapshotsWithSameName, 'Attempted to create a snapshot with a duplicate name :last for the item rep /foo.md (rep name :default)')
      end
    end
  end
end
nanoc-4.11.0/nanoc/spec/nanoc/base/services/compiler/000077500000000000000000000000001340050175000223635ustar00rootroot00000000000000nanoc-4.11.0/nanoc/spec/nanoc/base/services/compiler/phases/000077500000000000000000000000001340050175000236465ustar00rootroot00000000000000nanoc-4.11.0/nanoc/spec/nanoc/base/services/compiler/phases/abstract_spec.rb000066400000000000000000000052271340050175000270160ustar00rootroot00000000000000# 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

  describe '#start' do
    subject { phase.start }

    context 'with wrapped' do
      let(:wrapped) { described_class.new(wrapped: nil) }

      it 'starts wrapped' do
        expect(wrapped).to receive(:start)
        subject
      end
    end

    context 'without wrapped' do
      let(:wrapped) { nil }

      it 'does not start wrapped' do
        subject
      end
    end
  end

  describe '#stop' do
    subject { phase.stop }

    context 'with wrapped' do
      let(:wrapped) { described_class.new(wrapped: nil) }

      it 'stops wrapped' do
        expect(wrapped).to receive(:stop)
        subject
      end
    end

    context 'without wrapped' do
      let(:wrapped) { nil }

      it 'does not stop wrapped' do
        subject
      end
    end
  end
end
nanoc-4.11.0/nanoc/spec/nanoc/base/services/compiler/phases/cache_spec.rb000066400000000000000000000111361340050175000262520ustar00rootroot00000000000000# 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(config: config)
  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) }

  let(:config) { Nanoc::Int::Configuration.new(dir: Dir.getwd).with_defaults }

  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.11.0/nanoc/spec/nanoc/base/services/compiler/stage_spec.rb000066400000000000000000000013161340050175000250260ustar00rootroot00000000000000# frozen_string_literal: true

describe Nanoc::Int::Compiler::Stage do
  subject(:stage) { klass.new }

  let(:klass) { described_class }

  before { Timecop.freeze(Time.local(2008, 9, 1, 10, 5, 0)) }
  after { Timecop.return }

  describe '#call' do
    subject { stage.call }

    it 'raises error' do
      expect { subject }.to raise_error(NotImplementedError)
    end

    context 'actual implementation' do
      let(:klass) do
        Class.new(described_class) do
          def run
            Timecop.freeze(Time.now + 13.57)
          end
        end
      end

      it 'sends notification' do
        expect { subject }
          .to send_notification(:stage_ran, 13.57, klass)
      end
    end
  end
end
nanoc-4.11.0/nanoc/spec/nanoc/base/services/compiler/stages/000077500000000000000000000000001340050175000236515ustar00rootroot00000000000000nanoc-4.11.0/nanoc/spec/nanoc/base/services/compiler/stages/calculate_checksums_spec.rb000066400000000000000000000037001340050175000312120ustar00rootroot00000000000000# 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(dir: Dir.getwd).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.11.0/nanoc/spec/nanoc/base/services/compiler/stages/cleanup_spec.rb000066400000000000000000000051511340050175000266410ustar00rootroot00000000000000# frozen_string_literal: true

describe Nanoc::Int::Compiler::Stages::Cleanup do
  let(:stage) { described_class.new(config.output_dirs) }

  let(:config) do
    Nanoc::Int::Configuration.new(dir: Dir.getwd).with_defaults
  end

  describe '#run' do
    subject { stage.run }

    def gen_hash(path)
      Digest::SHA1.hexdigest(File.absolute_path(path))[0..12]
    end

    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
      default_dir = "tmp/nanoc/#{gen_hash(Dir.getwd + '/output')}"
      prod_dir = "tmp/nanoc/#{gen_hash(Dir.getwd + '/output_production')}"
      staging_dir = "tmp/nanoc/#{gen_hash(Dir.getwd + '/output_staging')}"

      FileUtils.mkdir_p(default_dir)
      FileUtils.mkdir_p(prod_dir)
      FileUtils.mkdir_p(staging_dir)

      expect { subject }
        .to change { Dir.glob('tmp/nanoc/*').sort }
        .from([default_dir, prod_dir, staging_dir].sort)
        .to([default_dir])
    end
  end
end
nanoc-4.11.0/nanoc/spec/nanoc/base/services/compiler/stages/compile_reps_spec.rb000066400000000000000000000112311340050175000276670ustar00rootroot00000000000000# frozen_string_literal: true

describe Nanoc::Int::Compiler::Stages::CompileReps do
  let(:stage) do
    described_class.new(
      reps: reps,
      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(config: config) }
  let(:snapshot_repo) { Nanoc::Int::SnapshotRepo.new }

  let(:outdatedness_store) { Nanoc::Int::OutdatednessStore.new(config: config) }
  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(dir: Dir.getwd).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
      before do
        # Needed for consistency
        compiled_content_cache[rep] = { last: Nanoc::Int::TextualContent.new('asdf') }
        compiled_content_cache[other_rep] = { last: Nanoc::Int::TextualContent.new('asdf') }
      end

      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) }

      before do
        # Needed for consistency
        compiled_content_cache[other_rep] = { last: Nanoc::Int::TextualContent.new('asdf') }
      end

      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('<%= \'invalid_ruby %>', {}, '/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(SyntaxError)
            expect(err.unwrap.message).to start_with('item /hi.md (rep default):1: unterminated string meets end of file')
          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.11.0/nanoc/spec/nanoc/base/services/compiler/stages/determine_outdatedness_spec.rb000066400000000000000000000070271340050175000317540ustar00rootroot00000000000000# frozen_string_literal: true

describe Nanoc::Int::Compiler::Stages::DetermineOutdatedness do
  let(:stage) do
    described_class.new(
      reps: reps,
      outdatedness_checker: outdatedness_checker,
      outdatedness_store: outdatedness_store,
    )
  end

  let(:reps) do
    Nanoc::Int::ItemRepRepo.new
  end

  let(:outdatedness_checker) do
    double(:outdatedness_checker)
  end

  let(:outdatedness_store) do
    Nanoc::Int::OutdatednessStore.new(config: config)
  end

  let(:config) { Nanoc::Int::Configuration.new(dir: Dir.getwd).with_defaults }
  let(:code_snippets) { [] }

  describe '#run' do
    subject { stage.run }

    context 'outdatedness store is empty' do
      let(:item) do
        Nanoc::Int::Item.new('', {}, '/hi.md')
      end

      let(:rep) do
        Nanoc::Int::ItemRep.new(item, :woof)
      end

      let(:other_rep) do
        Nanoc::Int::ItemRep.new(item, :bark)
      end

      context 'outdatedness checker thinks rep is outdated' do
        before do
          reps << rep
          reps << other_rep

          expect(outdatedness_checker)
            .to receive(:outdated?).with(rep).and_return(true)

          expect(outdatedness_checker)
            .to receive(:outdated?).with(other_rep).and_return(false)
        end

        it 'adds the rep' do
          expect { subject }
            .to change { outdatedness_store.include?(rep) }
            .from(false)
            .to(true)
        end

        it 'also adds the other rep' do
          expect { subject }
            .to change { outdatedness_store.include?(other_rep) }
            .from(false)
            .to(true)
        end

        it 'returns a list with the known rep’s item' do
          expect(subject).to eq([rep.item])
        end
      end

      context 'outdatedness checker thinks rep is not outdated' do
        it 'keeps the outdatedness store empty' do
          expect { subject }
            .not_to change { outdatedness_store.empty? }
        end

        it 'returns an empty list' do
          expect(subject).to be_empty
        end
      end
    end

    context 'outdatedness store contains known rep' do
      let(:item) do
        Nanoc::Int::Item.new('', {}, '/hi.md')
      end

      let(:rep) do
        Nanoc::Int::ItemRep.new(item, :woof)
      end

      let(:other_rep) do
        Nanoc::Int::ItemRep.new(item, :bark)
      end

      before do
        reps << rep
        reps << other_rep

        outdatedness_store.add(rep)

        expect(outdatedness_checker)
          .to receive(:outdated?).with(other_rep).and_return(false)
      end

      it 'keeps the rep' do
        expect { subject }
          .not_to change { outdatedness_store.include?(rep) }
      end

      it 'also adds the other rep' do
        expect { subject }
          .to change { outdatedness_store.include?(other_rep) }
          .from(false)
          .to(true)
      end

      it 'returns a list with the known rep’s item' do
        expect(subject).to eq([rep.item])
      end
    end

    context 'outdatedness store contains unknown rep' do
      let(:item) do
        Nanoc::Int::Item.new('', {}, '/hi.md')
      end

      let(:unknown_rep) do
        Nanoc::Int::ItemRep.new(item, :woof)
      end

      before do
        outdatedness_store.add(unknown_rep)
      end

      it 'removes the unknown rep' do
        expect { subject }
          .to change { outdatedness_store.include?(unknown_rep) }
          .from(true)
          .to(false)
      end

      it 'returns an empty list' do
        expect(subject).to be_empty
      end
    end
  end
end
nanoc-4.11.0/nanoc/spec/nanoc/base/services/compiler/stages/preprocess_spec.rb000066400000000000000000000055521340050175000274040ustar00rootroot00000000000000# frozen_string_literal: true

describe Nanoc::Int::Compiler::Stages::Preprocess do
  let(:stage) do
    described_class.new(
      action_provider: action_provider,
      site: site,
      dependency_store: dependency_store,
      checksum_store: checksum_store,
    )
  end

  let(:action_provider) do
    double(:action_provider)
  end

  let(:site) do
    Nanoc::Int::Site.new(
      config: config,
      code_snippets: [],
      data_source: data_source,
    )
  end

  let(:data_source) { Nanoc::Int::InMemDataSource.new(items, layouts) }
  let(:config) { Nanoc::Int::Configuration.new(dir: Dir.getwd).with_defaults }
  let(:items) { Nanoc::Int::ItemCollection.new(config) }
  let(:layouts) { Nanoc::Int::LayoutCollection.new(config) }

  let(:dependency_store) do
    Nanoc::Int::DependencyStore.new(items, layouts, config)
  end

  let(:checksum_store) do
    Nanoc::Int::ChecksumStore.new(config: config, objects: items.to_a + layouts.to_a)
  end

  describe '#run' do
    subject { stage.run }

    context 'no preprocessing needed' do
      before do
        expect(action_provider).to receive(:need_preprocessing?).and_return(false)
      end

      it 'marks the site as preprocessed' do
        expect { subject }
          .to change { site.preprocessed? }
          .from(false)
          .to(true)
      end

      it 'freezes the site' do
        subject
      end
    end

    context 'preprocessing needed' do
      let(:new_item) { Nanoc::Int::Item.new('new item', {}, '/new.md') }
      let(:new_layout) { Nanoc::Int::Layout.new('new layout', {}, '/new.md') }

      before do
        expect(action_provider).to receive(:need_preprocessing?).and_return(true)

        expect(action_provider).to receive(:preprocess) do |site|
          site.data_source =
            Nanoc::Int::InMemDataSource.new(
              Nanoc::Int::ItemCollection.new(config, [new_item]),
              Nanoc::Int::LayoutCollection.new(config, [new_layout]),
            )
        end
      end

      it 'marks the site as preprocessed' do
        expect { subject }
          .to change { site.preprocessed? }
          .from(false)
          .to(true)
      end

      it 'freezes the site' do
        expect { subject }
          .to change { site.config.frozen? }
          .from(false)
          .to(true)
      end

      it 'sets items on dependency store' do
        expect { subject }
          .to change { dependency_store.items.to_a }
          .from([])
          .to([new_item])
      end

      it 'sets layouts on dependency store' do
        expect { subject }
          .to change { dependency_store.layouts.to_a }
          .from([])
          .to([new_layout])
      end

      it 'sets data on checksum store' do
        expect { subject }
          .to change { checksum_store.objects.to_a }
          .from([])
          .to(match_array([new_item, new_layout, config]))
      end
    end
  end
end
nanoc-4.11.0/nanoc/spec/nanoc/base/services/dependency_tracker_spec.rb000066400000000000000000000162661340050175000257540ustar00rootroot00000000000000# 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(dir: Dir.getwd).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.11.0/nanoc/spec/nanoc/base/services/executor_spec.rb000066400000000000000000000515201340050175000237510ustar00rootroot00000000000000# 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 (C:)?\/.*\/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 (C:)?\/.*\/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 (C:)?\/.*\/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 (C:)?\/.*\/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::Errors::OutputNotWritten)
      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::ViewContextForCompilation.new(
        reps: Nanoc::Int::ItemRepRepo.new,
        items: Nanoc::Int::ItemCollection.new(config),
        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::CompilationItemRepView.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: [Dir.getwd + '/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', {}, Nanoc::Identifier.new('/default/', type: :legacy))]
      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.11.0/nanoc/spec/nanoc/base/services/instrumentor_spec.rb000066400000000000000000000007461340050175000246700ustar00rootroot00000000000000# frozen_string_literal: true

describe(Nanoc::Int::Instrumentor) do
  subject { described_class }

  before { Timecop.freeze(Time.local(2008, 9, 1, 10, 5, 0)) }
  after { Timecop.return }

  it 'sends notification' do
    expect do
      subject.call(:sample_notification, 'garbage', 123) do
        # Go to a few seconds in the future
        Timecop.freeze(Time.local(2008, 9, 1, 10, 5, 5))
      end
    end.to send_notification(:sample_notification, 5.0, 'garbage', 123)
  end
end
nanoc-4.11.0/nanoc/spec/nanoc/base/services/item_rep_router_spec.rb000066400000000000000000000145531340050175000253240ustar00rootroot00000000000000# 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(dir: Dir.getwd).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: [Dir.getwd + '/output/foo/index.html'])
      expect(reps[0].paths).to eql(last: ['/foo/'])

      expect(reps[1].raw_paths).to eql(last: [Dir.getwd + '/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: [Dir.getwd + '/output/foo/index.html'])
      expect(reps[0].paths).to eql(last: ['/foo/'])

      expect(reps[1].raw_paths).to eql(last: [Dir.getwd + '/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, 'The item representations /foo.md (rep name :default) and /foo.md (rep name :other) are both routed to /foo/index.html.')
        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: [Dir.getwd + '/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, 'The item representation /foo.md (rep name :default) is routed to foo/index.html, which does not start with a slash, as required.')
            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: [Dir.getwd + '/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: [Dir.getwd + '/output/foo/index.html', Dir.getwd + '/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.11.0/nanoc/spec/nanoc/base/services/item_rep_selector_spec.rb000066400000000000000000000126541340050175000256240ustar00rootroot00000000000000# 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.11.0/nanoc/spec/nanoc/base/services/notification_center_spec.rb000066400000000000000000000012661340050175000261430ustar00rootroot00000000000000# frozen_string_literal: true

describe Nanoc::Int::NotificationCenter do
  it 'receives notification after subscribing' do
    ping_received = false
    Nanoc::Int::NotificationCenter.on :ping_received, :test do
      ping_received = true
    end

    Nanoc::Int::NotificationCenter.post :ping_received
    expect(ping_received).to be
  end

  it 'does not receive notification after unsubscribing' do
    ping_received = false
    Nanoc::Int::NotificationCenter.on :ping_received, :test do
      ping_received = true
    end

    Nanoc::Int::NotificationCenter.remove :ping_received, :test

    Nanoc::Int::NotificationCenter.post :ping_received
    expect(ping_received).not_to be
  end
end
nanoc-4.11.0/nanoc/spec/nanoc/base/services/outdatedness_checker_spec.rb000066400000000000000000000570241340050175000263060ustar00rootroot00000000000000# 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(config: config)
  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(config: config, objects: items.to_a + layouts.to_a) }

    let(:config) { Nanoc::Int::Configuration.new(dir: Dir.getwd).with_defaults }

    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(config: config, 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(dir: Dir.getwd).with_defaults }

    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.11.0/nanoc/spec/nanoc/base/services/outdatedness_rules_spec.rb000066400000000000000000000440641340050175000260340ustar00rootroot00000000000000# 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(:layout) { Nanoc::Int::Layout.new('layoutz', {}, '/page.erb') }

    let(:config) { Nanoc::Int::Configuration.new(dir: Dir.getwd).with_defaults }
    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(items, layouts),
      )
    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(config: config) }
    let(:checksum_store) { Nanoc::Int::ChecksumStore.new(config: config, 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, [layout]) }

    before do
      allow(site).to receive(:code_snippets).and_return(code_snippets)
      allow(site).to receive(:config).and_return(config)
    end

    describe '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

    describe '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) { Dir.getwd + '/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) { Dir.getwd + '/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

    describe '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

    describe '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

    describe '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

      context 'memory is the same, but refers to a layout' do
        let(:old_mem) do
          Nanoc::Int::ActionSequence.build(item_rep) do |b|
            b.add_layout('/page.*', {})
          end
        end

        let(:new_mem) { old_mem }

        let(:action_sequences) do
          {
            item_rep => new_mem,
            layout => new_layout_mem,
          }
        end

        before do
          action_sequence_store[layout] = old_layout_mem.serialize
        end

        context 'everything is the same' do
          let(:new_layout_mem) do
            Nanoc::Int::ActionSequence.build(layout) do |b|
              b.add_filter(:erb, {})
            end
          end

          let(:old_layout_mem) { new_layout_mem }

          it { is_expected.not_to be }
        end

        context 'referenced layout does not exist' do
          let(:new_layout_mem) do
            Nanoc::Int::ActionSequence.build(layout) do |b|
              b.add_filter(:erb, {})
            end
          end

          let(:old_layout_mem) do
            Nanoc::Int::ActionSequence.build(layout) do |b|
              b.add_filter(:haml, {})
            end
          end

          let(:old_mem) do
            Nanoc::Int::ActionSequence.build(item_rep) do |b|
              b.add_layout('/moo.*', {})
            end
          end

          # Something changed about the layout; the item-on-layout dependency
          # will ensure this item is marked as outdated.
          it { is_expected.not_to be }
        end

        context 'filter name is different' do
          let(:new_layout_mem) do
            Nanoc::Int::ActionSequence.build(layout) do |b|
              b.add_filter(:erb, {})
            end
          end

          let(:old_layout_mem) do
            Nanoc::Int::ActionSequence.build(layout) do |b|
              b.add_filter(:haml, {})
            end
          end

          it { is_expected.to be }
        end

        context 'params are different' do
          let(:new_layout_mem) do
            Nanoc::Int::ActionSequence.build(layout) do |b|
              b.add_filter(:erb, {})
            end
          end

          let(:old_layout_mem) do
            Nanoc::Int::ActionSequence.build(layout) do |b|
              b.add_filter(:erb, foo: 123)
            end
          end

          it { is_expected.to be }
        end
      end
    end

    describe 'ContentModified, AttributesModified' 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

    describe 'ItemCollectionExtended' do
      let(:rule_class) { Nanoc::Int::OutdatednessRules::ItemCollectionExtended }

      let(:obj) { items }

      context 'no new item added' do
        before do
          expect(dependency_store).to receive(:new_items).and_return([])
        end

        it { is_expected.not_to be }
      end

      context 'new item added' do
        before do
          expect(dependency_store).to receive(:new_items).and_return([item])
        end

        it { is_expected.to be }
      end
    end

    describe 'LayoutCollectionExtended' do
      let(:rule_class) { Nanoc::Int::OutdatednessRules::LayoutCollectionExtended }

      let(:obj) { layouts }

      context 'no new layout added' do
        before do
          expect(dependency_store).to receive(:new_layouts).and_return([])
        end

        it { is_expected.not_to be }
      end

      context 'new layout added' do
        before do
          expect(dependency_store).to receive(:new_layouts).and_return([layout])
        end

        it { is_expected.to be }
      end
    end
  end
end
nanoc-4.11.0/nanoc/spec/nanoc/base/services/pruner_spec.rb000066400000000000000000000253551340050175000234350ustar00rootroot00000000000000# frozen_string_literal: true

describe Nanoc::Pruner, stdio: true do
  subject(:pruner) { described_class.new(config, reps, dry_run: dry_run, exclude: exclude) }

  let(:config) { Nanoc::Int::Configuration.new(hash: {}, dir: Dir.getwd).with_defaults }
  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: [Dir.getwd + '/output/asdf.html'] }
      end

      reps << Nanoc::Int::ItemRep.new(item, :text).tap do |rep|
        rep.raw_paths = { last: [Dir.getwd + '/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 '#filename_excluded?' do
    subject { pruner.filename_excluded?(filename) }

    let(:filename) { Dir.getwd + '/output/foo/bar.html' }

    context 'nothing excluded' do
      it { is_expected.to be(false) }
    end

    context 'matching identifier component excluded' do
      let(:exclude) { ['foo'] }
      it { is_expected.to be(true) }
    end

    context 'non-matching identifier component excluded' do
      let(:exclude) { ['xyz'] }
      it { is_expected.to be(false) }
    end

    context 'output dir excluded' do
      let(:exclude) { ['output'] }
      it { is_expected.to be(false) }
    end
  end

  describe '#run' do
    subject { pruner.run }

    describe 'it removes stray files' do
      let(:present_files) do
        [
          'output/foo.html',
          'output/foo.txt',
          'output/bar.html',
          'output/foo/bar.html',
          'output/foo/bar.txt',
          'output/output/asdf.txt',
        ]
      end

      let(:reps) do
        Nanoc::Int::ItemRepRepo.new.tap do |reps|
          reps << Nanoc::Int::ItemRep.new(item, :a).tap do |rep|
            rep.raw_paths = { last: [Dir.getwd + '/output/foo.html'] }
          end

          reps << Nanoc::Int::ItemRep.new(item, :b).tap do |rep|
            rep.raw_paths = { last: [Dir.getwd + '/output/bar.html'] }
          end

          reps << Nanoc::Int::ItemRep.new(item, :c).tap do |rep|
            rep.raw_paths = { last: [Dir.getwd + '/output/foo/bar.html'] }
          end
        end
      end

      before do
        present_files.each do |fn|
          FileUtils.mkdir_p(File.dirname(fn))
          File.write(fn, 'asdf')
        end
      end

      context 'nothing excluded' do
        it 'removes /foo.txt' do
          expect { subject }
            .to change { File.file?('output/foo.txt') }
            .from(true)
            .to(false)
        end

        it 'removes /foo/bar.txt' do
          expect { subject }
            .to change { File.file?('output/foo/bar.txt') }
            .from(true)
            .to(false)
        end

        it 'removes /output/asdf.txt' do
          expect { subject }
            .to change { File.file?('output/output/asdf.txt') }
            .from(true)
            .to(false)
        end
      end

      context 'foo excluded' do
        let(:exclude) { ['foo'] }

        it 'removes /foo.txt' do
          expect { subject }
            .to change { File.file?('output/foo.txt') }
            .from(true)
            .to(false)
        end

        it 'keeps /foo/bar.txt' do
          expect { subject }
            .not_to change { File.file?('output/foo/bar.txt') }
            .from(true)
        end

        it 'removes /output/asdf.txt' do
          expect { subject }
            .to change { File.file?('output/output/asdf.txt') }
            .from(true)
            .to(false)
        end
      end

      context 'output excluded' do
        let(:exclude) { ['output'] }

        it 'removes /foo.txt' do
          expect { subject }
            .to change { File.file?('output/foo.txt') }
            .from(true)
            .to(false)
        end

        it 'removes /foo/bar.txt' do
          expect { subject }
            .to change { File.file?('output/foo/bar.txt') }
            .from(true)
            .to(false)
        end

        it 'keeps /output/asdf.txt' do
          expect { subject }
            .not_to change { File.file?('output/output/asdf.txt') }
            .from(true)
        end
      end
    end

    describe 'it removes empty directories' do
      let(:present_dirs) do
        [
          'output/.foo',
          'output/foo',
          'output/foo/bar',
          'output/bar',
          'output/output',
          'output/output/asdf',
        ]
      end

      before do
        present_dirs.each do |fn|
          FileUtils.mkdir_p(fn)
        end
      end

      context 'nothing excluded' do
        it 'removes /.foo' do
          expect { subject }
            .to change { File.directory?('output/.foo') }
            .from(true)
            .to(false)
        end

        it 'removes /foo' do
          expect { subject }
            .to change { File.directory?('output/foo') }
            .from(true)
            .to(false)
        end

        it 'removes /foo/bar' do
          expect { subject }
            .to change { File.directory?('output/foo/bar') }
            .from(true)
            .to(false)
        end

        it 'removes /bar' do
          expect { subject }
            .to change { File.directory?('output/bar') }
            .from(true)
            .to(false)
        end

        it 'removes /output' do
          expect { subject }
            .to change { File.directory?('output/output') }
            .from(true)
            .to(false)
        end

        it 'removes /output/asdf' do
          expect { subject }
            .to change { File.directory?('output/output/asdf') }
            .from(true)
            .to(false)
        end
      end

      context 'foo excluded' do
        let(:exclude) { ['foo'] }

        it 'removes /.foo' do
          expect { subject }
            .to change { File.directory?('output/.foo') }
            .from(true)
            .to(false)
        end

        it 'removes /bar' do
          expect { subject }
            .to change { File.directory?('output/bar') }
            .from(true)
            .to(false)
        end

        it 'keeps /foo' do
          expect { subject }
            .not_to change { File.directory?('output/foo') }
            .from(true)
        end

        it 'keeps /foo/bar' do
          expect { subject }
            .not_to change { File.directory?('output/foo/bar') }
            .from(true)
        end

        it 'removes /output' do
          expect { subject }
            .to change { File.directory?('output/output') }
            .from(true)
            .to(false)
        end

        it 'removes /output/asdf' do
          expect { subject }
            .to change { File.directory?('output/output/asdf') }
            .from(true)
            .to(false)
        end
      end

      context 'output excluded' do
        let(:exclude) { ['output'] }

        it 'removes /.foo' do
          expect { subject }
            .to change { File.directory?('output/.foo') }
            .from(true)
            .to(false)
        end

        it 'removes /bar' do
          expect { subject }
            .to change { File.directory?('output/bar') }
            .from(true)
            .to(false)
        end

        it 'removes /foo' do
          expect { subject }
            .to change { File.directory?('output/foo') }
            .from(true)
            .to(false)
        end

        it 'removes /foo/bar' do
          expect { subject }
            .to change { File.directory?('output/foo/bar') }
            .from(true)
            .to(false)
        end

        it 'keeps /output' do
          expect { subject }
            .not_to change { File.directory?('output/output') }
            .from(true)
        end

        it 'keeps /output/asdf' do
          expect { subject }
            .not_to change { File.directory?('output/output/asdf') }
            .from(true)
        end
      end
    end
  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/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/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/projects', 'output/.git', ] expect(subject[1]).to match_array(dirs) end end context 'output dir is a symlink' do before do FileUtils.mv('output', 'output-real') File.symlink('output-real', 'output') end before do if Nanoc.on_windows? skip 'Symlinks to output dirs are currently not supported on Windows.' end end 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/projects', 'output/.git', ] expect(subject[1]).to match_array(dirs) end end end end nanoc-4.11.0/nanoc/spec/nanoc/base/services/temp_filename_factory_spec.rb000066400000000000000000000041621340050175000264470ustar00rootroot00000000000000# 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(C:)?\//) 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.11.0/nanoc/spec/nanoc/base/views/000077500000000000000000000000001340050175000200635ustar00rootroot00000000000000nanoc-4.11.0/nanoc/spec/nanoc/base/views/basic_item_rep_collection_view_spec.rb000066400000000000000000000003671340050175000276420ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'support/item_rep_collection_view_examples' describe Nanoc::BasicItemRepCollectionView do it_behaves_like 'an item rep collection view' let(:expected_view_class) { Nanoc::BasicItemRepView } end nanoc-4.11.0/nanoc/spec/nanoc/base/views/basic_item_rep_view_spec.rb000066400000000000000000000003321340050175000254170ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'support/item_rep_view_examples' describe Nanoc::BasicItemRepView do let(:expected_item_view_class) { Nanoc::BasicItemView } it_behaves_like 'an item rep view' end nanoc-4.11.0/nanoc/spec/nanoc/base/views/compilation_item_rep_collection_view_spec.rb000066400000000000000000000004031340050175000310660ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'support/item_rep_collection_view_examples' describe Nanoc::CompilationItemRepCollectionView do it_behaves_like 'an item rep collection view' let(:expected_view_class) { Nanoc::CompilationItemRepView } end nanoc-4.11.0/nanoc/spec/nanoc/base/views/compilation_item_rep_view_spec.rb000066400000000000000000000104431340050175000266600ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'support/item_rep_view_examples' describe Nanoc::CompilationItemRepView do let(:expected_item_view_class) { Nanoc::CompilationItemView } it_behaves_like 'an item rep view' let(:view_context) do Nanoc::ViewContextForCompilation.new( reps: Nanoc::Int::ItemRepRepo.new, items: Nanoc::Int::ItemCollection.new(config), dependency_tracker: dependency_tracker, compilation_context: compilation_context, snapshot_repo: snapshot_repo, ) end 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(dir: Dir.getwd).with_defaults } before do dependency_tracker.enter(base_item) 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: [Dir.getwd + '/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 } context 'file does not exist' do it 'raises' do expect { subject }.to raise_error(Nanoc::Int::Errors::InternalInconsistency) end end context 'file exists' do before do FileUtils.mkdir_p('output/about') File.write('output/about/index.html', 'hi!') 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(Dir.getwd + '/output/about/index.html') } end 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 end nanoc-4.11.0/nanoc/spec/nanoc/base/views/config_view_spec.rb000066400000000000000000000064461340050175000237330ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::ConfigView do let(:config) do Nanoc::Int::Configuration.new(dir: Dir.getwd, hash: hash) end let(:hash) { { amount: 9000, animal: 'donkey', foo: { bar: :baz } } } let(:view) { described_class.new(config, view_context) } let(:view_context) do Nanoc::ViewContextForCompilation.new( reps: Nanoc::Int::ItemRepRepo.new, items: Nanoc::Int::ItemCollection.new(config), 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 existing key' do let(:key) { :animal } it { is_expected.to eql('donkey') } end context 'with non-existing 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 existing key' do let(:key) { :animal } subject { view.fetch(key) } it { is_expected.to eql('donkey') } end context 'with non-existing 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 existing key' do let(:key) { :animal } it { is_expected.to eql(true) } end context 'with non-existing 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'], [:foo, { bar: :baz }]]) end end describe '#dig' do subject { view.dig(*keys) } before do expect(dependency_tracker).to receive(:bounce).with(config, attributes: [:foo]) end context 'with existing keys' do let(:keys) { %i[foo bar] } it { is_expected.to eql(:baz) } end context 'with non-existing keys' do let(:keys) { %i[foo baz bar] } it { is_expected.to be_nil } end end describe '#inspect' do subject { view.inspect } it { is_expected.to eql('') } end end nanoc-4.11.0/nanoc/spec/nanoc/base/views/item_collection_with_reps_view_spec.rb000066400000000000000000000012441340050175000277120ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'support/identifiable_collection_view_examples' describe Nanoc::ItemCollectionWithRepsView do let(:view_class) { Nanoc::CompilationItemView } let(:collection_class) { Nanoc::Int::ItemCollection } it_behaves_like 'an identifiable collection view' 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.11.0/nanoc/spec/nanoc/base/views/item_collection_without_reps_view_spec.rb000066400000000000000000000012441340050175000304420ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'support/identifiable_collection_view_examples' describe Nanoc::ItemCollectionWithoutRepsView do let(:view_class) { Nanoc::BasicItemView } let(:collection_class) { Nanoc::Int::ItemCollection } it_behaves_like 'an identifiable collection view' 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.11.0/nanoc/spec/nanoc/base/views/item_view_spec.rb000066400000000000000000000304451340050175000234200ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'support/document_view_examples' describe Nanoc::CompilationItemView do let(:entity_class) { Nanoc::Int::Item } let(:other_view_class) { Nanoc::LayoutView } it_behaves_like 'a document view' let(:view_context) do Nanoc::ViewContextForCompilation.new( reps: reps, items: items, dependency_tracker: dependency_tracker, compilation_context: compilation_context, snapshot_repo: snapshot_repo, ) end let(:reps) { Nanoc::Int::ItemRepRepo.new } let(:items) { Nanoc::Int::ItemCollection.new(config) } 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(dir: Dir.getwd).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 context 'full identifier' do let(:identifier) do Nanoc::Identifier.new('/parent/me.md') end let(:parent_item) do Nanoc::Int::Item.new('parent', {}, '/parent.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 let(:parent_item) do Nanoc::Int::Item.new('parent', {}, Nanoc::Identifier.new('/parent/', type: :legacy)) end it 'returns a view for the parent' do expect(subject.class).to eql(Nanoc::CompilationItemView) 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', {}, parent_identifier) } let(:identifier) { Nanoc::Identifier.new('/me/', type: :legacy) } let(:parent_identifier) { Nanoc::Identifier.new('/', type: :legacy) } it 'returns a view for the parent' do expect(subject.class).to eql(Nanoc::CompilationItemView) 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(: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 let(:children) do [Nanoc::Int::Item.new('child', {}, '/me/child.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 let(:children) do [Nanoc::Int::Item.new('child', {}, Nanoc::Identifier.new('/me/child/', type: :legacy))] end it 'returns views for the children' do expect(subject.size).to eql(1) expect(subject[0].class).to eql(Nanoc::CompilationItemView) 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::CompilationItemRepCollectionView) 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::BasicItemRepCollectionView::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::BasicItemRepCollectionView::NoSuchItemRepError) end end end describe '#binary?' do # TODO: implement end describe '#raw_filename' do subject { view.raw_filename } let(:item) do Nanoc::Int::Item.new(content, { animal: 'donkey' }, '/foo') end let(:view) { described_class.new(item, view_context) } context 'textual content with no raw filename' do let(:content) { Nanoc::Int::TextualContent.new('asdf') } it { is_expected.to be_nil } 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.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 context 'textual content with raw filename' do let(:content) { Nanoc::Int::TextualContent.new('asdf', filename: filename) } let(:filename) { '/tmp/lol.txt' } it { is_expected.to eql('/tmp/lol.txt') } 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.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 context 'binary content' do let(:content) { Nanoc::Int::BinaryContent.new(filename) } let(:filename) { '/tmp/lol.txt' } it { is_expected.to eql('/tmp/lol.txt') } 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.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 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.11.0/nanoc/spec/nanoc/base/views/layout_collection_view_spec.rb000066400000000000000000000012231340050175000262020ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'support/identifiable_collection_view_examples' describe Nanoc::LayoutCollectionView do let(:view_class) { Nanoc::LayoutView } let(:collection_class) { Nanoc::Int::LayoutCollection } it_behaves_like 'an identifiable collection view' 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.11.0/nanoc/spec/nanoc/base/views/layout_view_spec.rb000066400000000000000000000007651340050175000240010ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'support/document_view_examples' describe Nanoc::LayoutView do let(:entity_class) { Nanoc::Int::Layout } let(:other_view_class) { Nanoc::CompilationItemView } 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.11.0/nanoc/spec/nanoc/base/views/mutable_config_view_spec.rb000066400000000000000000000006521340050175000254350ustar00rootroot00000000000000# 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.11.0/nanoc/spec/nanoc/base/views/mutable_item_collection_view_spec.rb000066400000000000000000000031611340050175000273370ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'support/identifiable_collection_view_examples' require_relative 'support/mutable_identifiable_collection_view_examples' describe Nanoc::MutableItemCollectionView do let(:view_class) { Nanoc::MutableItemView } let(:collection_class) { Nanoc::Int::ItemCollection } it_behaves_like 'an identifiable collection view' it_behaves_like 'a mutable identifiable collection view' 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.11.0/nanoc/spec/nanoc/base/views/mutable_item_view_spec.rb000066400000000000000000000013561340050175000251300ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'support/mutable_document_view_examples' 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.11.0/nanoc/spec/nanoc/base/views/mutable_layout_collection_view_spec.rb000066400000000000000000000032011340050175000277110ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'support/identifiable_collection_view_examples' require_relative 'support/mutable_identifiable_collection_view_examples' describe Nanoc::MutableLayoutCollectionView do let(:view_class) { Nanoc::MutableLayoutView } let(:collection_class) { Nanoc::Int::LayoutCollection } it_behaves_like 'an identifiable collection view' it_behaves_like 'a mutable identifiable collection view' 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.11.0/nanoc/spec/nanoc/base/views/mutable_layout_view_spec.rb000066400000000000000000000007311340050175000255030ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'support/mutable_document_view_examples' 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.11.0/nanoc/spec/nanoc/base/views/post_compile_item_rep_collection_view_spec.rb000066400000000000000000000004031340050175000312450ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'support/item_rep_collection_view_examples' describe Nanoc::PostCompileItemRepCollectionView do it_behaves_like 'an item rep collection view' let(:expected_view_class) { Nanoc::PostCompileItemRepView } end nanoc-4.11.0/nanoc/spec/nanoc/base/views/post_compile_item_rep_view_spec.rb000066400000000000000000000137741340050175000270510ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'support/item_rep_view_examples' describe Nanoc::PostCompileItemRepView do let(:expected_item_view_class) { Nanoc::PostCompileItemView } it_behaves_like 'an item rep view' 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::ViewContextForCompilation.new( reps: Nanoc::Int::ItemRepRepo.new, items: Nanoc::Int::ItemCollection.new(config), 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(dir: Dir.getwd).with_defaults } 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(config: config).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: [Dir.getwd + '/output/about/index.html'] } end it { is_expected.to eql(Dir.getwd + '/output/about/index.html') } end context 'path specified, but not for default snapshot' do before do item_rep.raw_paths = { pre: [Dir.getwd + '/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: [Dir.getwd + '/output/about/index.html'] } end it { is_expected.to eql(Dir.getwd + '/output/about/index.html') } end context 'path specified, but not for default snapshot' do before do item_rep.raw_paths = { pre: [Dir.getwd + '/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 expect { subject }.to raise_error(Nanoc::Int::Errors::CannotGetCompiledContentOfBinaryItem, 'You cannot access the compiled content of a binary item representation (but you can access the path). The offending item rep is /foo (rep name :jacques).') 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.11.0/nanoc/spec/nanoc/base/views/post_compile_item_view_spec.rb000066400000000000000000000031651340050175000261740ustar00rootroot00000000000000# 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.11.0/nanoc/spec/nanoc/base/views/support/000077500000000000000000000000001340050175000215775ustar00rootroot00000000000000nanoc-4.11.0/nanoc/spec/nanoc/base/views/support/document_view_examples.rb000066400000000000000000000231551340050175000267000ustar00rootroot00000000000000# frozen_string_literal: true shared_examples 'a document view' do let(:view) { described_class.new(document, view_context) } let(:view_context) do Nanoc::ViewContextForCompilation.new( reps: Nanoc::Int::ItemRepRepo.new, items: Nanoc::Int::ItemCollection.new(config), 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(dir: Dir.getwd).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.11.0/nanoc/spec/nanoc/base/views/support/identifiable_collection_view_examples.rb000066400000000000000000000172701340050175000317150ustar00rootroot00000000000000# frozen_string_literal: true # Needs :view_class shared_examples 'an identifiable collection view' do let(:view) { described_class.new(wrapped, view_context) } let(:view_context) do Nanoc::ViewContextForCompilation.new( reps: Nanoc::Int::ItemRepRepo.new, items: Nanoc::Int::ItemCollection.new(config), 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 context 'with string' do subject { view.find_all('/*.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 subject { view.find_all(%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 context 'with block' do subject { view.find_all { |iv| iv.identifier =~ /css/ } } it 'creates dependency' do expect(dependency_tracker).to receive(:bounce).with(wrapped, raw_content: true) 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.11.0/nanoc/spec/nanoc/base/views/support/item_rep_collection_view_examples.rb000066400000000000000000000062551340050175000311030ustar00rootroot00000000000000# 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 BasicItemRepCollectionView#[] 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 BasicItemRepCollectionView#[] 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::BasicItemRepCollectionView::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 nanoc-4.11.0/nanoc/spec/nanoc/base/views/support/item_rep_view_examples.rb000066400000000000000000000214731340050175000266670ustar00rootroot00000000000000# frozen_string_literal: true shared_examples 'an item rep view' do # needs expected_item_view_class let(:view_context) do Nanoc::ViewContextForCompilation.new( reps: Nanoc::Int::ItemRepRepo.new, items: Nanoc::Int::ItemCollection.new(config), dependency_tracker: dependency_tracker, compilation_context: compilation_context, snapshot_repo: snapshot_repo, ) end 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(dir: Dir.getwd).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 '#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 '#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(expected_item_view_class) 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('<' + described_class.to_s + ' item.identifier=/foo name=jacques>') } end end nanoc-4.11.0/nanoc/spec/nanoc/base/views/support/mutable_document_view_examples.rb000066400000000000000000000130651340050175000304100ustar00rootroot00000000000000# frozen_string_literal: true shared_examples 'a mutable document view' do let(:view) { described_class.new(document, view_context) } let(:view_context) do Nanoc::ViewContextForCompilation.new( reps: Nanoc::Int::ItemRepRepo.new, items: Nanoc::Int::ItemCollection.new(config), 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) } let(:config) { Nanoc::Int::Configuration.new(dir: Dir.getwd) } 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 context 'checksum_data set' do before do document.checksum_data = 'my checksum data' document.content_checksum_data = 'my content checksum data' document.attributes_checksum_data = 'my attributes checksum data' end it 'unsets checksum_data' do expect { view.raw_content = 'donkey' } .to change { document.checksum_data } .from('my checksum data') .to(nil) end it 'unsets content_checksum_data' do expect { view.raw_content = 'donkey' } .to change { document.content_checksum_data } .from('my content checksum data') .to(nil) end it 'keeps attributes_checksum_data' do expect { view.raw_content = 'donkey' } .not_to change { document.attributes_checksum_data } end 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::CompilationItemView.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 context 'checksum_data set' do before do document.checksum_data = 'my checksum data' document.content_checksum_data = 'my content checksum data' document.attributes_checksum_data = 'my attributes checksum data' end it 'unsets checksum_data' do expect { view[:title] = 'Donkey' } .to change { document.checksum_data } .from('my checksum data') .to(nil) end it 'unsets attributes_checksum_data' do expect { view[:title] = 'Donkey' } .to change { document.attributes_checksum_data } .from('my attributes checksum data') .to(nil) end it 'keeps content_checksum_data' do expect { view[:title] = 'Donkey' } .not_to change { document.content_checksum_data } end 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 context 'checksum_data set' do before do document.checksum_data = 'my checksum data' document.content_checksum_data = 'my content checksum data' document.attributes_checksum_data = 'my attributes checksum data' end it 'unsets checksum_data' do expect { subject } .to change { document.checksum_data } .from('my checksum data') .to(nil) end it 'unsets attributes_checksum_data' do expect { subject } .to change { document.attributes_checksum_data } .from('my attributes checksum data') .to(nil) end it 'keeps content_checksum_data' do expect { subject } .not_to change { document.content_checksum_data } end end end end nanoc-4.11.0/nanoc/spec/nanoc/base/views/support/mutable_identifiable_collection_view_examples.rb000066400000000000000000000020431340050175000334160ustar00rootroot00000000000000# frozen_string_literal: true shared_examples 'a mutable identifiable collection view' 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.11.0/nanoc/spec/nanoc/checking/000077500000000000000000000000001340050175000175675ustar00rootroot00000000000000nanoc-4.11.0/nanoc/spec/nanoc/checking/check_spec.rb000066400000000000000000000043061340050175000222060ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Check do it 'is an alias' do expect(described_class).to equal(Nanoc::Checking::Check) end end describe Nanoc::Checking::Check do describe '.define' do before do described_class.define(:spec_check_example_1) do add_issue('it’s totes bad') end end 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(dir: Dir.getwd).with_defaults } let(:code_snippets) { [] } let(:items) { Nanoc::Int::ItemCollection.new(config, []) } let(:layouts) { Nanoc::Int::LayoutCollection.new(config, []) } before do FileUtils.mkdir_p('output') File.write('Rules', 'passthrough "/**/*"') end it 'is discoverable' do expect(described_class.named(:spec_check_example_1)).not_to be_nil end it 'runs properly' do check = described_class.named(:spec_check_example_1).create(site) check.run expect(check.issues.size).to eq(1) expect(check.issues.first.description).to eq('it’s totes bad') end end describe '.named' do it 'finds checks that exist' do expect(described_class.named(:internal_links)).not_to be_nil end it 'is nil for non-existent checks' do expect(described_class.named(:asdfaskjlfdalhsgdjf)).to be_nil end end describe '#output_html_filenames' do let(:check) do described_class.new(output_filenames: output_filenames) end let(:output_filenames) do [ 'output/foo.html', 'output/foo.htm', 'output/foo.xhtml', 'output/foo.txt', 'output/foo.htmlx', 'output/foo.yhtml', ] end subject { check.output_html_filenames } it { is_expected.to include('output/foo.html') } it { is_expected.to include('output/foo.htm') } it { is_expected.to include('output/foo.xhtml') } it { is_expected.not_to include('output/foo.txt') } it { is_expected.not_to include('output/foo.htmlx') } it { is_expected.not_to include('output/foo.yhtml') } end end nanoc-4.11.0/nanoc/spec/nanoc/checking/checks/000077500000000000000000000000001340050175000210275ustar00rootroot00000000000000nanoc-4.11.0/nanoc/spec/nanoc/checking/checks/external_links_spec.rb000066400000000000000000000064541340050175000254210ustar00rootroot00000000000000# frozen_string_literal: true describe ::Nanoc::Checking::Checks::ExternalLinks do let(:check) do Nanoc::Checking::Checks::ExternalLinks.create(site).tap do |c| def c.request_url_once(_url) Net::HTTPResponse.new('1.1', '200', 'okay') end end end 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(dir: Dir.getwd).with_defaults } let(:code_snippets) { [] } let(:items) { Nanoc::Int::ItemCollection.new(config, []) } let(:layouts) { Nanoc::Int::LayoutCollection.new(config, []) } before do FileUtils.mkdir_p('output') File.write('output/hi.html', '
stuff') File.write('Rules', 'passthrough "/**/*"') end context 'found' do let(:check) do Nanoc::Checking::Checks::ExternalLinks.create(site).tap do |c| def c.request_url_once(_url) Net::HTTPResponse.new('1.1', '200', 'okay') end end end it 'has no issues' do check.run expect(check.issues).to be_empty end end context 'not found' do let(:check) do Nanoc::Checking::Checks::ExternalLinks.create(site).tap do |c| def c.request_url_once(_url) Net::HTTPResponse.new('1.1', '404', 'okay') end end end it 'has issues' do check.run expect(check.issues.size).to eq(1) end end context 'redirect' do before do skip 'Known failure on Windows' if Nanoc.on_windows? end let(:check) do Nanoc::Checking::Checks::ExternalLinks.create(site).tap do |c| def c.request_url_once(_url) @enum ||= Enumerator.new do |y| y << Net::HTTPResponse.new('1.1', '302', 'look elsewhere').tap do |h| h['Location'] = 'http://elsewhere.example.com/' end y << Net::HTTPResponse.new('1.1', '200', 'okay') end @enum.next end end end it 'has no issues' do check.run expect(check.issues).to be_empty end end context 'redirect without location' do before do skip 'Known failure on Windows' if Nanoc.on_windows? end let(:check) do Nanoc::Checking::Checks::ExternalLinks.create(site).tap do |c| def c.request_url_once(_url) @enum ||= Enumerator.new do |y| y << Net::HTTPResponse.new('1.1', '302', 'look elsewhere') end @enum.next end end end it 'has issues' do check.run expect(check.issues.size).to eq(1) expect(check.issues.first.description) .to eq('broken reference to http://example.com/x: redirection without a target location') end end context 'invalid URL component' do before do skip 'Known failure on Windows' if Nanoc.on_windows? end let(:check) do Nanoc::Checking::Checks::ExternalLinks.create(site) end before do File.write('output/hi.html', 'stuff') end it 'has issues' do check.run expect(check.issues.size).to eq(1) expect(check.issues.first.description) .to eq('broken reference to mailto:lol: invalid URI') end end end nanoc-4.11.0/nanoc/spec/nanoc/checking/runner_spec.rb000066400000000000000000000066731340050175000224530ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Checking::Runner, site: true do subject(:runner) { described_class.new(site) } let(:site) { Nanoc::Int::SiteLoader.new.new_from_cwd } describe '#any_enabled_checks?' do subject { runner.any_enabled_checks? } context 'no DSL' do context 'no deploy checks defined in config' do it { is_expected.to be(false) } end context 'deploy checks defined in config' do before do File.write('nanoc.yaml', "checking:\n enabled_checks:\n - elinks") end it { is_expected.to be(true) } end end context 'DSL without deploy checks defined' do before do File.write('Checks', '') end context 'no deploy checks defined in config' do it { is_expected.to be(false) } end context 'deploy checks defined in config' do before do File.write('nanoc.yaml', "checking:\n enabled_checks:\n - elinks") end it { is_expected.to be(true) } end end context 'DSL with deploy checks defined' do before do File.write('Checks', 'deploy_check :ilinks') end context 'no deploy checks defined in config' do it { is_expected.to be(true) } end context 'deploy checks defined in config' do before do File.write('nanoc.yaml', "checking:\n enabled_checks:\n - elinks") end it { is_expected.to be(true) } end end end describe '#enabled_checks' do subject { runner.send(:enabled_checks) } context 'no DSL' do context 'no deploy checks defined in config' do it { is_expected.to be_empty } end context 'deploy checks defined in config' do before do File.write('nanoc.yaml', "checking:\n enabled_checks:\n - elinks") end it { is_expected.to match_array([:elinks]) } end end context 'DSL without deploy checks defined' do before do File.write('Checks', '') end context 'no deploy checks defined in config' do it { is_expected.to be_empty } end context 'deploy checks defined in config' do before do File.write('nanoc.yaml', "checking:\n enabled_checks:\n - elinks") end it { is_expected.to match_array([:elinks]) } end end context 'DSL with deploy checks defined' do before do File.write('Checks', 'deploy_check :ilinks') end context 'no deploy checks defined in config' do it { is_expected.to match_array([:ilinks]) } end context 'deploy checks defined in config' do before do File.write('nanoc.yaml', "checking:\n enabled_checks:\n - elinks") end it { is_expected.to match_array(%i[ilinks elinks]) } end end end describe '#check_classes_named' do subject { runner.send(:check_classes_named, names) } context 'given one full name' do let(:names) { %w[internal_links] } it { is_expected.to eq([Nanoc::Checking::Checks::InternalLinks]) } end context 'given one full name with dash instead of underscore' do let(:names) { %w[internal-links] } it { is_expected.to eq([Nanoc::Checking::Checks::InternalLinks]) } end context 'given one abbreviated name' do let(:names) { %w[ilinks] } it { is_expected.to eq([Nanoc::Checking::Checks::InternalLinks]) } end end end nanoc-4.11.0/nanoc/spec/nanoc/cli/000077500000000000000000000000001340050175000165635ustar00rootroot00000000000000nanoc-4.11.0/nanoc/spec/nanoc/cli/command_runner_spec.rb000066400000000000000000000053021340050175000231310ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::CLI::CommandRunner, stdio: true do describe '.find_site_dir' do subject { described_class.find_site_dir } context 'config file in current dir' do before { File.write('nanoc.yaml', 'hi') } it 'returns the current dir' do expect(subject).to eq(File.expand_path(Dir.getwd)) end end context 'config file in parent dir' do around do |ex| FileUtils.mkdir_p('root/sub') File.write('root/nanoc.yaml', 'hi') chdir('root/sub') { ex.run } end it 'returns the parent dir' do expect(subject).to match(/root$/) end end context 'config file in grandparent dir' do around do |ex| FileUtils.mkdir_p('root/sub1/sub2') File.write('root/nanoc.yaml', 'hi') chdir('root/sub1/sub2') { ex.run } end it 'returns the parent dir' do expect(subject).to match(/root$/) end end context 'no config file in ancestral paths' do it 'returns nil' do expect(subject).to be_nil end end end describe '.enter_site_dir' do subject do described_class.enter_site_dir Dir.getwd end context 'config file in current dir' do before { File.write('nanoc.yaml', 'hi') } it 'returns the current dir' do expect(subject).to eq(File.expand_path(Dir.getwd)) end end context 'config file in parent dir' do around do |ex| FileUtils.mkdir_p('root/sub') File.write('root/nanoc.yaml', 'hi') chdir('root/sub') { ex.run } end it 'returns the parent dir' do expect(subject).to match(/root$/) end end context 'config file in grandparent dir' do around do |ex| FileUtils.mkdir_p('root/sub1/sub2') File.write('root/nanoc.yaml', 'hi') chdir('root/sub1/sub2') { ex.run } end it 'enters the parent dir' do expect(subject).to match(/root$/) end end context 'no config file in ancestral paths' do it 'raises' do expect { subject }.to raise_error(::Nanoc::Int::Errors::GenericTrivial, 'The current working directory, nor any of its parents, seems to be a Nanoc site.') end end end describe '#load_site' do let(:command_runner) { described_class.new(nil, nil, nil) } subject { command_runner.load_site } before { File.write('nanoc.yaml', '{}') } it 'does not set @site' do expect(command_runner.instance_variable_get(:@site)).to be_nil expect { subject }.not_to change { command_runner.instance_variable_get(:@site) } end it 'returns site' do expect(subject).to be_a(Nanoc::Int::Site) end end end nanoc-4.11.0/nanoc/spec/nanoc/cli/commands/000077500000000000000000000000001340050175000203645ustar00rootroot00000000000000nanoc-4.11.0/nanoc/spec/nanoc/cli/commands/check_spec.rb000066400000000000000000000022521340050175000230010ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::CLI::Commands::Check, site: true, stdio: true do describe '#run' do before do File.write('Checks', "deploy_check :stale\n") end context 'without options and arguments' do subject { Nanoc::CLI.run(['check']) } context 'no issues for any checks' do it 'succeeds' do subject end end context 'issues for deploy check' do before do FileUtils.mkdir_p('output') File.write('output/asdf.txt', 'staaale') end it 'fails' do expect { subject }.to raise_error(Nanoc::Int::Errors::GenericTrivial, 'One or more checks failed') end end context 'issues for non-deploy check' do before do FileUtils.mkdir_p('output') File.write('output/asdf.txt', 'staaale') File.write('Checks', '') end it 'succeeds' do subject end end end end describe 'help' do subject { Nanoc::CLI.run(%w[help check]) } it 'shows --deploy as deprecated' do expect { subject }.to output(/--deploy.*\(deprecated\)/).to_stdout end end end nanoc-4.11.0/nanoc/spec/nanoc/cli/commands/compile/000077500000000000000000000000001340050175000220145ustar00rootroot00000000000000nanoc-4.11.0/nanoc/spec/nanoc/cli/commands/compile/abstract_spec.rb000066400000000000000000000030251340050175000251560ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::CLI::Commands::CompileListeners::Abstract do subject { klass.new } context 'abstract class' do let(:klass) { described_class } it 'errors on starting' do expect { subject.start }.to raise_error(NotImplementedError) end it 'stops silently' do subject.stop end end context 'concrete subclass' 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 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 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.11.0/nanoc/spec/nanoc/cli/commands/compile/diff_generator_spec.rb000066400000000000000000000021661340050175000263360ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::CLI::Commands::CompileListeners::DiffGenerator do describe '.enable_for?' do subject { described_class.enable_for?(command_runner, site) } 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(dir: Dir.getwd, hash: config_hash).with_defaults } let(:items) { [] } let(:layouts) { [] } let(:code_snippets) { [] } let(:command_runner) do Nanoc::CLI::Commands::Compile.new(options, arguments, command) 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.11.0/nanoc/spec/nanoc/cli/commands/compile/file_action_printer_spec.rb000066400000000000000000000110101340050175000273630ustar00rootroot00000000000000# 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_write_ended' 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_write_ended, 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_write_ended, rep, false, '/foo.html', true, true) } .not_to output(/create/).to_stdout end it 'records from compilation_started over compilation_suspended to rep_write_ended' 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_write_ended, rep, false, '/foo.html', true, true) } .to output(/create.*\[4\.00s\]/).to_stdout end it 'records from compilation_started over rep_write_{enqueued,started} to rep_write_ended' 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(:rep_write_enqueued, rep) Timecop.freeze(Time.local(2008, 9, 1, 10, 5, 3)) Nanoc::Int::NotificationCenter.post(:rep_write_started, rep) Timecop.freeze(Time.local(2008, 9, 1, 10, 5, 6)) expect { Nanoc::Int::NotificationCenter.post(:rep_write_ended, 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 'does not print skipped (uncompiled) reps' do expect { listener.stop } .not_to output(/skip/).to_stdout end it 'prints nothing' do Nanoc::Int::NotificationCenter.post(:compilation_started, rep) Timecop.freeze(Time.local(2008, 9, 1, 10, 5, 1)) expect { Nanoc::Int::NotificationCenter.post(:rep_write_ended, rep, false, '/foo.html', false, false) } .not_to output(/identical/).to_stdout end it 'prints nothing' do Nanoc::Int::NotificationCenter.post(:compilation_started, rep) Nanoc::Int::NotificationCenter.post(:cached_content_used, rep) Timecop.freeze(Time.local(2008, 9, 1, 10, 5, 1)) expect { Nanoc::Int::NotificationCenter.post(:rep_write_ended, rep, false, '/foo.html', false, false) } .not_to output(/cached/).to_stdout end end context 'log level = low' do before { listener.start } before { Nanoc::CLI::Logger.instance.level = :low } it 'prints skipped (uncompiled) reps' do expect { listener.stop } .to output(/skip.*\/hi\.html/).to_stdout end it 'prints “identical” if not cached' do Nanoc::Int::NotificationCenter.post(:compilation_started, rep) Timecop.freeze(Time.local(2008, 9, 1, 10, 5, 1)) expect { Nanoc::Int::NotificationCenter.post(:rep_write_ended, rep, false, '/foo.html', false, false) } .to output(/identical/).to_stdout end it 'prints “cached” if cached' do Nanoc::Int::NotificationCenter.post(:compilation_started, rep) Nanoc::Int::NotificationCenter.post(:cached_content_used, rep) Timecop.freeze(Time.local(2008, 9, 1, 10, 5, 1)) expect { Nanoc::Int::NotificationCenter.post(:rep_write_ended, rep, false, '/foo.html', false, false) } .to output(/cached/).to_stdout end end end nanoc-4.11.0/nanoc/spec/nanoc/cli/commands/compile/timing_recorder_spec.rb000066400000000000000000000307241340050175000265350ustar00rootroot00000000000000# 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.filters_summary.get(name: 'erb').min).to eq(1.00) expect(listener.filters_summary.get(name: 'erb').avg).to eq(1.00) expect(listener.filters_summary.get(name: 'erb').max).to eq(1.00) expect(listener.filters_summary.get(name: 'erb').sum).to eq(1.00) expect(listener.filters_summary.get(name: '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.filters_summary.get(name: 'erb').min).to eq(1.00) expect(listener.filters_summary.get(name: 'erb').avg).to eq(1.50) expect(listener.filters_summary.get(name: 'erb').max).to eq(2.00) expect(listener.filters_summary.get(name: 'erb').sum).to eq(3.00) expect(listener.filters_summary.get(name: '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.filters_summary.get(name: 'inner').min).to eq(2.00) expect(listener.filters_summary.get(name: 'inner').avg).to eq(2.00) expect(listener.filters_summary.get(name: 'inner').max).to eq(2.00) expect(listener.filters_summary.get(name: 'inner').sum).to eq(2.00) expect(listener.filters_summary.get(name: 'inner').count).to eq(1.00) expect(listener.filters_summary.get(name: 'outer').min).to eq(6.00) expect(listener.filters_summary.get(name: 'outer').avg).to eq(6.00) expect(listener.filters_summary.get(name: 'outer').max).to eq(6.00) expect(listener.filters_summary.get(name: 'outer').sum).to eq(6.00) expect(listener.filters_summary.get(name: '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.filters_summary.get(name: 'outer').min).to eq(7.00) expect(listener.filters_summary.get(name: 'outer').avg).to eq(7.00) expect(listener.filters_summary.get(name: 'outer').max).to eq(7.00) expect(listener.filters_summary.get(name: 'outer').sum).to eq(7.00) expect(listener.filters_summary.get(name: '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.filters_summary.get(name: 'erb').min).to eq(5.00) expect(listener.filters_summary.get(name: 'erb').avg).to eq(5.00) expect(listener.filters_summary.get(name: 'erb').max).to eq(5.00) expect(listener.filters_summary.get(name: 'erb').sum).to eq(5.00) expect(listener.filters_summary.get(name: '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.phases_summary.get(name: 'donkey').min).to eq(1.00) expect(listener.phases_summary.get(name: 'donkey').avg).to eq(1.00) expect(listener.phases_summary.get(name: 'donkey').max).to eq(1.00) expect(listener.phases_summary.get(name: 'donkey').sum).to eq(1.00) expect(listener.phases_summary.get(name: '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.phases_summary.get(name: 'donkey').min).to eq(1.00) expect(listener.phases_summary.get(name: 'donkey').avg).to eq(1.50) expect(listener.phases_summary.get(name: 'donkey').max).to eq(2.00) expect(listener.phases_summary.get(name: 'donkey').sum).to eq(3.00) expect(listener.phases_summary.get(name: '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.phases_summary.get(name: 'donkey').min).to eq(3.00) expect(listener.phases_summary.get(name: 'donkey').avg).to eq(3.00) expect(listener.phases_summary.get(name: 'donkey').max).to eq(3.00) expect(listener.phases_summary.get(name: 'donkey').sum).to eq(3.00) expect(listener.phases_summary.get(name: '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.phases_summary.get(name: 'donkey').min).to eq(1.00) expect(listener.phases_summary.get(name: 'donkey').avg).to eq(2.00) expect(listener.phases_summary.get(name: 'donkey').max).to eq(3.00) expect(listener.phases_summary.get(name: 'donkey').sum).to eq(4.00) expect(listener.phases_summary.get(name: 'donkey').count).to eq(2.00) end it 'records stage duration' do Nanoc::Int::NotificationCenter.post(:stage_ran, 1.23, 'donkey_stage') expect(listener.stages_summary.get(name: 'donkey_stage').sum).to eq(1.23) expect(listener.stages_summary.get(name: 'donkey_stage').count).to eq(1) end it 'prints stage durations' do Nanoc::Int::NotificationCenter.post(:stage_ran, 1.23, 'donkey_stage') expect { listener.stop } .to output(/^\s*donkey_stage │ 1\.23s$/).to_stdout end it 'prints out outdatedness rule durations' do Nanoc::Int::NotificationCenter.post(:outdatedness_rule_ran, 1.0, Nanoc::Int::OutdatednessRules::CodeSnippetsModified) 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 Nanoc::Int::NotificationCenter.post(:outdatedness_rule_ran, 1.0, Nanoc::Int::OutdatednessRules::CodeSnippetsModified) expect(listener.outdatedness_rules_summary.get(name: 'CodeSnippetsModified').min).to eq(1.00) expect(listener.outdatedness_rules_summary.get(name: 'CodeSnippetsModified').avg).to eq(1.00) expect(listener.outdatedness_rules_summary.get(name: 'CodeSnippetsModified').max).to eq(1.00) expect(listener.outdatedness_rules_summary.get(name: 'CodeSnippetsModified').sum).to eq(1.00) expect(listener.outdatedness_rules_summary.get(name: 'CodeSnippetsModified').count).to eq(1.00) end it 'records multiple outdatedness rule duration' do Nanoc::Int::NotificationCenter.post(:outdatedness_rule_ran, 1.0, Nanoc::Int::OutdatednessRules::CodeSnippetsModified) Nanoc::Int::NotificationCenter.post(:outdatedness_rule_ran, 3.0, Nanoc::Int::OutdatednessRules::CodeSnippetsModified) expect(listener.outdatedness_rules_summary.get(name: 'CodeSnippetsModified').min).to eq(1.00) expect(listener.outdatedness_rules_summary.get(name: 'CodeSnippetsModified').avg).to eq(2.00) expect(listener.outdatedness_rules_summary.get(name: 'CodeSnippetsModified').max).to eq(3.00) expect(listener.outdatedness_rules_summary.get(name: 'CodeSnippetsModified').sum).to eq(4.00) expect(listener.outdatedness_rules_summary.get(name: 'CodeSnippetsModified').count).to eq(2.00) end it 'prints load store durations' do Nanoc::Int::NotificationCenter.post(:store_loaded, 1.23, Nanoc::Int::ChecksumStore) expect { listener.stop } .to output(/^\s*Nanoc::Int::ChecksumStore │ 1\.23s$/).to_stdout end it 'skips printing empty metrics' do expect { listener.stop } .not_to output(/filters|phases|stages/).to_stdout end end nanoc-4.11.0/nanoc/spec/nanoc/cli/commands/compile_spec.rb000066400000000000000000000045451340050175000233630ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::CLI::Commands::Compile, site: true, stdio: true do describe '#run' do it 'starts and stops listeners as needed' 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 expect(Nanoc::CLI::Commands::CompileListeners::Aggregate) .to receive(:default_listener_classes) .and_return([test_listener_class]) listener = test_listener_class.new expect(test_listener_class) .to receive(:new) .and_return(listener) options = {} arguments = [] cmd = nil cmd_runner = Nanoc::CLI::Commands::Compile.new(options, arguments, cmd) cmd_runner.run expect(listener).to be_started expect(listener).to be_stopped end describe '--watch', fork: true do it 'watches with --watch' do pipe_stdout_read, pipe_stdout_write = IO.pipe pid = fork do trap(:INT) { exit(0) } pipe_stdout_read.close $stdout = pipe_stdout_write # TODO: Use Nanoc::CLI.run instead (when --watch is no longer experimental) options = { watch: true } arguments = [] cmd = nil cmd_runner = Nanoc::CLI::Commands::Compile.new(options, arguments, cmd) cmd_runner.run end pipe_stdout_write.close # Wait until ready Timeout.timeout(5) do progress = 0 pipe_stdout_read.each_line do |line| progress += 1 if line.start_with?('Listening for lib/ changes') progress += 1 if line.start_with?('Listening for site changes') break if progress == 2 end end sleep 0.5 # Still needs time to warm up… File.write('content/lol.html', 'hej') sleep_until { File.file?('output/lol.html') } expect(File.read('output/lol.html')).to eq('hej') sleep 1.0 # HFS+ mtime resolution is 1s File.write('content/lol.html', 'bye') sleep_until { File.read('output/lol.html') == 'bye' } # Stop Process.kill('INT', pid) Process.waitpid(pid) end end end end nanoc-4.11.0/nanoc/spec/nanoc/cli/commands/deploy_spec.rb000066400000000000000000000217531340050175000232270ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::CLI::Commands::Deploy, site: true, stdio: true do before do skip_unless_have_command 'rsync' end 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.11.0/nanoc/spec/nanoc/cli/commands/shell_spec.rb000066400000000000000000000052561340050175000230420ustar00rootroot00000000000000# 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 'can be invoked as sh' 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(['sh']) end it 'can be invoked as console' 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(['console']) 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', '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.11.0/nanoc/spec/nanoc/cli/commands/show_data_spec.rb000066400000000000000000000246461340050175000237100ustar00rootroot00000000000000# 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(dir: Dir.getwd).with_defaults } 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 context 'dependency onto all items' do before do dependency_store.record_dependency(item_dog, items, raw_content: true) end it 'outputs dependencies for /dog.md' do expect { subject }.to output(%r{^item /dog.md depends on:\n \[ items \] \(r___\) matching any$}m).to_stdout end end context 'dependency onto one specific item' do before do dependency_store.record_dependency(item_dog, items, raw_content: ['/about.*']) end it 'outputs dependencies for /dog.md' do expect { subject }.to output(%r{^item /dog.md depends on:\n \[ items \] \(r___\) matching any of /about\.\*$}m).to_stdout end end context 'dependency onto multiple specific items' do before do dependency_store.record_dependency(item_dog, items, raw_content: ['/about.*']) dependency_store.record_dependency(item_dog, items, raw_content: ['/giraffe.*']) end it 'outputs dependencies for /dog.md' do expect { subject }.to output(%r{^item /dog.md depends on:\n \[ items \] \(r___\) matching any of /about\.\*, /giraffe\.\*$}m).to_stdout end end end describe '#print_item_rep_outdatedness' do subject { runner.send(:print_item_rep_outdatedness, items, outdatedness_checker, reps) } let(:runner) do described_class.new(options, arguments, command) end let(:options) { {} } let(:arguments) { [] } let(:command) { double(:command) } let(:config) { Nanoc::Int::Configuration.new(dir: Dir.getwd) } 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(:outdatedness_checker) { double(:outdatedness_checker) } let(:reps) do { item_about => [item_rep_about], item_dog => [item_rep_dog], } 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, outdatedness_checker) } let(:runner) do described_class.new(options, arguments, command) end let(:options) { {} } let(:arguments) { [] } let(:command) { double(:command) } let(:config) { Nanoc::Int::Configuration.new(dir: Dir.getwd) } let(:layouts) do Nanoc::Int::LayoutCollection.new(config, [layout]) end let(:layout) { Nanoc::Int::Layout.new('stuff', {}, '/default.erb') } let(:site) { double(:site) } let(:outdatedness_checker) { double(:outdatedness_checker) } 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.11.0/nanoc/spec/nanoc/cli/commands/show_plugins_spec.rb000066400000000000000000000010421340050175000244410ustar00rootroot00000000000000# 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.11.0/nanoc/spec/nanoc/cli/commands/show_rules_spec.rb000066400000000000000000000067721340050175000241310ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::CLI::Commands::ShowRules, stdio: true, site: true do describe '#run' do subject { runner.run } let(:runner) do described_class.new(options, arguments, command) end let(:options) { {} } let(:arguments) { [] } let(:command) { double(:command) } let(:site) do double( :site, items: items, layouts: layouts, compiler: compiler, config: config, ) 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(dir: Dir.getwd).with_defaults } let(:action_provider) { double(:action_provider, rules_collection: rules_collection) } let(:compiler) { double(:compiler) } let(:rules_collection) do Nanoc::RuleDSL::RulesCollection.new.tap do |rc| rc.add_item_compilation_rule( Nanoc::RuleDSL::CompilationRule.new(Nanoc::Int::Pattern.from('/dog.*'), :default, proc {}), ) rc.add_item_compilation_rule( Nanoc::RuleDSL::CompilationRule.new(Nanoc::Int::Pattern.from('/*.md'), :default, proc {}), ) rc.add_item_compilation_rule( Nanoc::RuleDSL::CompilationRule.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 it 'writes item and layout rules to stdout' do expect(runner).to receive(:load_site).and_return(site) expect(Nanoc::Int::Compiler).to receive(:new_for).with(site).and_return(compiler) expect(compiler).to receive(:run_until_reps_built).and_return(reps: reps) expect(Nanoc::RuleDSL::ActionProvider).to receive(:for).with(site).and_return(action_provider) expect { subject }.to output(expected_out).to_stdout end it 'writes status information to stderr' do expect { subject }.to output("Loading site… done\n").to_stderr end end end nanoc-4.11.0/nanoc/spec/nanoc/cli/commands/view_spec.rb000066400000000000000000000052431340050175000227010ustar00rootroot00000000000000# frozen_string_literal: true require 'net/http' describe Nanoc::CLI::Commands::View, site: true, stdio: true, fork: 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) break rescue Errno::ECONNREFUSED, Errno::ECONNRESET sleep(0.1 * 1.1**i) next end raise 'Server did not start up in time' 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 it 'does not crash when output dir does not exist and --live-reload is given' do FileUtils.rm_rf('output') run_nanoc_cmd(['view', '--port', '50385', '--live-reload']) do expect(Net::HTTP.get('127.0.0.1', '/', 50_385)).to eql("File not found: /\n") end end it 'does not listen on non-local interfaces' do addresses = Socket.getifaddrs.map(&:addr).select(&:ipv4?).map(&:ip_address) non_local_addresses = addresses - ['127.0.0.1'] if non_local_addresses.empty? skip 'Need non-local network interfaces for this spec' end run_nanoc_cmd(['view', '--port', '50385']) do expect { Net::HTTP.get(non_local_addresses[0], '/', 50_385) }.to raise_error(Errno::ECONNREFUSED) end end end end nanoc-4.11.0/nanoc/spec/nanoc/cli/error_handler_spec.rb000066400000000000000000000104341340050175000227520ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::CLI::ErrorHandler, stdio: true do subject(:error_handler) { described_class.new } describe '#forwards_stack_trace?' do subject { error_handler.forwards_stack_trace? } context 'Ruby 2.4' do before do expect(error_handler).to receive(:ruby_version).and_return('2.4.2') end it { is_expected.to be(false) } end context 'Ruby 2.5' do before do expect(error_handler).to receive(:ruby_version).and_return('2.5.0') end it { is_expected.to be(true) } end end describe '#trivial?' do subject { error_handler.trivial?(error) } context 'LoadError of known gem' do let(:error) do begin raise LoadError, 'cannot load such file -- nokogiri' rescue LoadError => e return e end end it { is_expected.to be(true) } end context 'LoadError of unknown gem' do let(:error) do begin raise LoadError, 'cannot load such file -- whatever' rescue LoadError => e return e end end it { is_expected.to be(false) } end context 'random error' do let(:error) do begin raise 'stuff' rescue => e return e end end it { is_expected.to be(false) } end context 'Errno::EADDRINUSE' do let(:error) do begin raise Errno::EADDRINUSE rescue => e return e end end it { is_expected.to be(true) } end context 'GenericTrivial' do let(:error) do begin raise Nanoc::Int::Errors::GenericTrivial, 'oh just a tiny thing' rescue => e return e end end it { is_expected.to be(true) } end end describe '#handle_error' do subject { error_handler.handle_error(error, exit_on_error: exit_on_error) } let(:error) do begin raise 'Bewm' rescue => e return e end end let(:exit_on_error) { false } describe 'exit behavior' do context 'exit on error' do let(:exit_on_error) { true } it 'exits on error' do expect { subject }.to raise_error(SystemExit) end end context 'no exit on error' do let(:exit_on_error) { false } it 'does not exit on error' do expect { subject }.not_to raise_error end end end describe 'printing behavior' do context 'trivial error with no resolution' do let(:error) do begin raise Nanoc::Int::Errors::GenericTrivial, 'asdf' rescue => e return e end end it 'prints summary' do expect { subject }.to output("\nError: asdf\n").to_stderr end end context 'LoadError' do let(:error) do begin raise LoadError, 'cannot load such file -- nokogiri' rescue LoadError => e return e end end it 'prints summary' do expected_output = "\n" + <<~OUT Error: cannot load such file -- nokogiri 1. Add `gem 'nokogiri'` to your Gemfile 2. Run `bundle install` 3. Re-run this command OUT expect { subject }.to output(expected_output).to_stderr end end context 'non-trivial error' do # … end end end describe '#write_error_message' do subject { error_handler.send(:write_error_message, $stdout, error, verbose: true) } let(:error) do begin Nanoc::Int::Configuration.new(dir: '/oink', hash: { enable_output_diff: 'yeah' }) rescue => e return e end end example do expect { subject }.to output("\n===== MESSAGE:\n\nJsonSchema::AggregateError: \n * #/enable_output_diff: For 'properties/enable_output_diff', \"yeah\" is not a boolean.\n").to_stdout end end describe 'GEM_NAMES' do example do requires = Nanoc::Filter.all.flat_map(&:requires) described = Nanoc::CLI::ErrorHandler::GEM_NAMES.keys + ['erb', 'rdoc', 'nanoc/filters/sass/importer', 'nanoc/filters/sass/functions'] missing = requires - described expect(missing).to be_empty end end end nanoc-4.11.0/nanoc/spec/nanoc/cli/stack_trace_writer_spec.rb000066400000000000000000000105751340050175000240110ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::CLI::StackTraceWriter do subject(:writer) do described_class.new(io, forwards: forwards) end let(:io) { StringIO.new } let(:forwards) { true } describe '#write' do let(:exception) do backtrace_generator = lambda do |af| if af.zero? raise 'finally!' else backtrace_generator.call(af - 1) end end begin backtrace_generator.call(3) rescue => e return e end end subject { writer.write(exception, verbose: verbose) } let(:verbose) { false } context 'backwards' do let(:forwards) { false } context 'verbose' do let(:verbose) { true } it 'starts with zero' do expect { subject } .to change { io.string } .from('') .to(start_with(' 0. ')) end it 'has more recent stack frames at the top' do expect { subject } .to change { io.string } .from('') .to(match(%r{^ 0\. (C:)?/.+/spec/nanoc/cli/stack_trace_writer_spec\.rb:\d+.*$\n 1\. (C:)?/.+/spec/nanoc/cli/stack_trace_writer_spec\.rb:\d}m)) end it 'has more than 10 stack frames' do expect { subject } .to change { io.string } .from('') .to(match(%r{^ 11\. })) end it 'does not contain a see-more explanation' do subject expect(io.string).not_to match(/crash\.log/) end end context 'not verbose' do let(:verbose) { false } it 'starts with zero' do expect { subject } .to change { io.string } .from('') .to(start_with(' 0. ')) end it 'has more recent stack frames at the top' do expect { subject } .to change { io.string } .from('') .to(match(%r{^ 0\. (C:)?/.+/spec/nanoc/cli/stack_trace_writer_spec\.rb:\d+.*$\n 1\. (C:)?/.+/spec/nanoc/cli/stack_trace_writer_spec\.rb:\d}m)) end it 'has not more than 10 stack frames' do subject expect(io.string).not_to match(/^ 11\. /) end it 'does not contain a see-more explanation' do subject expect(io.string).to include(" lines omitted (see crash.log for details)\n") end end end context 'forwards' do let(:forwards) { true } context 'verbose' do let(:verbose) { true } it 'ends with most recent line' do expect { subject } .to change { io.string } .from('') .to(match(%r{^ 1\. from (C:)?/.+/spec/nanoc/cli/stack_trace_writer_spec\.rb:\d+.*$\n (C:)?/.+/spec/nanoc/cli}m)) end it 'has more recent stack frames at the bottom' do expect { subject } .to change { io.string } .from('') .to(match(%r{^ 2\. from (C:)?/.+/spec/nanoc/cli/stack_trace_writer_spec\.rb:\d+.*$\n 1\. from (C:)?/.+/spec/nanoc/cli/stack_trace_writer_spec\.rb:\d}m)) end it 'has more than 10 stack frames' do expect { subject } .to change { io.string } .from('') .to(match(%r{^ 11\. from })) end it 'does not contain a see-more explanation' do subject expect(io.string).not_to match(/crash\.log/) end end context 'not verbose' do let(:verbose) { false } it 'ends with most recent line' do expect { subject } .to change { io.string } .from('') .to(match(%r{^ 1\. from (C:)?/.+/spec/nanoc/cli/stack_trace_writer_spec\.rb:\d+.*$\n (C:)?/.+/spec/nanoc/cli}m)) end it 'has more recent stack frames at the top' do expect { subject } .to change { io.string } .from('') .to(match(%r{^ 2\. from (C:)?/.+/spec/nanoc/cli/stack_trace_writer_spec\.rb:\d+.*$\n 1\. from (C:)?/.+/spec/nanoc/cli/stack_trace_writer_spec\.rb:\d}m)) end it 'has not more than 10 stack frames' do subject expect(io.string).not_to match(/^ 11\. from /) end it 'does not contain a see-more explanation' do subject expect(io.string).to include(" lines omitted (see crash.log for details)\n") end end end end end nanoc-4.11.0/nanoc/spec/nanoc/cli/stream_cleaners/000077500000000000000000000000001340050175000217325ustar00rootroot00000000000000nanoc-4.11.0/nanoc/spec/nanoc/cli/stream_cleaners/utf8_spec.rb000066400000000000000000000004201340050175000241530ustar00rootroot00000000000000# 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.11.0/nanoc/spec/nanoc/cli_spec.rb000066400000000000000000000036101340050175000201220ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::CLI do let(:all_commands) do ObjectSpace.each_object(Cri::Command) end let(:exceptions) do # FIXME: [Nanoc 5] Get rid of these exceptions [ ['deploy', ['C']], ['help', ['v']], ['check', ['d']], ] end def ancestors_of_command(command) if command.is_a?(Cri::Command) [command] + ancestors_of_command(command.supercommand) else [] end end def short_options_for_command(command) ancestors = ancestors_of_command(command) ancestors.flat_map { |a| a.option_definitions.to_a.map(&:short) }.compact end it 'has no commands that have conflicting options' do all_commands.each do |command| short_options = short_options_for_command(command) duplicate_options = short_options.select { |o| short_options.count(o) > 1 }.uniq next if exceptions.include?([command.name, duplicate_options]) expect(duplicate_options).to( be_empty, "The #{command.name} command’s option shorthands #{duplicate_options.uniq} are used by multiple options", ) end end describe '#enable_ansi_colors?' do subject { described_class.enable_ansi_colors?(io) } context 'TTY' do let(:io) { double(:io, tty?: true) } context 'NO_COLOR set' do before do allow(ENV).to receive(:key?).with('NO_COLOR').and_return(true) end it { is_expected.to be(false) } end context 'NO_COLOR not set' do it { is_expected.to be(true) } end end context 'no TTY' do let(:io) { double(:io, tty?: false) } context 'NO_COLOR set' do before do allow(ENV).to receive(:key?).with('NO_COLOR').and_return(true) end it { is_expected.to be(false) } end context 'NO_COLOR not set' do it { is_expected.to be(false) } end end end end nanoc-4.11.0/nanoc/spec/nanoc/data_sources/000077500000000000000000000000001340050175000204705ustar00rootroot00000000000000nanoc-4.11.0/nanoc/spec/nanoc/data_sources/filesystem/000077500000000000000000000000001340050175000226545ustar00rootroot00000000000000nanoc-4.11.0/nanoc/spec/nanoc/data_sources/filesystem/parser_spec.rb000066400000000000000000000232411340050175000255110ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::DataSources::Filesystem::Parser do subject(:parser) { described_class.new(config: config) } let(:config) do Nanoc::Int::Configuration.new(dir: Dir.getwd).with_defaults end describe '#call' do subject { parser.call(content_filename, meta_filename) } let(:content_filename) { nil } let(:meta_filename) { nil } context 'only meta file' do let(:meta_filename) { Tempfile.open('test') { |fn| fn << 'asdf' }.path } before do File.write(meta_filename, meta) end context 'simple metadata' do let(:meta) { "foo: bar\n" } it 'reads attributes' do expect(subject.attributes).to eq('foo' => 'bar') end it 'has no content' do expect(subject.content).to eq('') end end context 'UTF-8 bom' do let(:meta) { [0xEF, 0xBB, 0xBF].map(&:chr).join + "foo: bar\r\n" } it 'strips UTF-8 BOM' do expect(subject.attributes).to eq('foo' => 'bar') end it 'has no content' do expect(subject.content).to eq('') end end context 'CRLF' do let(:meta) { "foo: bar\r\n" } it 'handles CR+LF line endings' do expect(subject.attributes).to eq('foo' => 'bar') end it 'has no content' do expect(subject.content).to eq('') end end context 'metadata is empty' do let(:meta) { '' } it 'has no attributes' do expect(subject.attributes).to eq({}) end it 'has no content' do expect(subject.content).to eq('') end end context 'metadata is not hash' do let(:meta) { "- stuff\n" } it 'raises' do expect { subject } .to raise_error(Nanoc::DataSources::Filesystem::Errors::InvalidMetadata, /has invalid metadata \(expected key-value pairs, found Array instead\)/) end end end context 'only content file' do let(:content_filename) { Tempfile.open('test') { |fn| fn << 'asdf' }.path } before do File.write(content_filename, content) end context 'no metadata section' do context 'simple' do let(:content) { "Hello!\n" } it 'has no attributes' do expect(subject.attributes).to eq({}) end it 'has content' do expect(subject.content).to eq("Hello!\n") end end context 'UTF-8 bom' do let(:content) { [0xEF, 0xBB, 0xBF].map(&:chr).join + "Hello!\n" } it 'has no attributes' do expect(subject.attributes).to eq({}) end it 'strips UTF-8 BOM' do expect(subject.content).to eq("Hello!\n") end end context 'CRLF' do let(:content) { "Hello!\r\n" } it 'has no attributes' do expect(subject.attributes).to eq({}) end it 'retains CR+LF' do # FIXME: Is this the right thing to do? expect(subject.content).to eq("Hello!\r\n") end end end context 'metadata section' do context 'three dashes' do let(:content) { "---\ntitle: Welcome\n---\nHello!\n" } it 'has attributes' do expect(subject.attributes).to eq('title' => 'Welcome') end it 'has content' do expect(subject.content).to eq("Hello!\n") end end context 'five dashes' do let(:content) { "-----\ntitle: Welcome\n-----\nHello!\n" } it 'has attributes' do expect(subject.attributes).to eq('title' => 'Welcome') end it 'has content' do expect(subject.content).to eq("Hello!\n") end end context 'trailing spaces' do let(:content) { "--- \ntitle: Welcome \n--- \nHello! \n" } it 'has attributes' do expect(subject.attributes).to eq('title' => 'Welcome') end it 'has content' do expect(subject.content).to eq("Hello! \n") end end context 'diff' do let(:content) { "--- a/foo\n+++ b/foo\nblah blah\n" } it 'has no attributes' do expect(subject.attributes).to eq({}) end it 'has content' do expect(subject.content).to eq(content) end end context 'separator not at beginning' do let(:content) { "foo\n---\ntitle: Welcome\n---\nStuff\n" } it 'has no attributes' do expect(subject.attributes).to eq({}) end it 'has content' do expect(subject.content).to eq(content) end end context 'unterminated metadata section' do let(:content) { "---\ntitle: Welcome\n" } it 'raises' do expect { subject }.to raise_error(Nanoc::DataSources::Filesystem::Errors::InvalidFormat) end end context 'non-hash metadata section' do let(:content) { "---\nWelcome\n---\nHello!\n" } it 'raises' do expect { subject }.to raise_error(Nanoc::DataSources::Filesystem::Errors::InvalidMetadata) end end context 'empty metadata section' do let(:content) { "---\n---\nHello!\n" } it 'has no attributes' do expect(subject.attributes).to eq({}) end it 'has content' do expect(subject.content).to eq("Hello!\n") end end context 'leading newline' do let(:content) { "---\ntitle: Welcome\n---\n\nHello!\n" } it 'has attributes' do expect(subject.attributes).to eq('title' => 'Welcome') end it 'has content' do expect(subject.content).to eq("Hello!\n") end end context 'two leading newlines' do let(:content) { "---\ntitle: Welcome\n---\n\n\nHello!\n" } it 'has attributes' do expect(subject.attributes).to eq('title' => 'Welcome') end it 'has content with one leading newline' do expect(subject.content).to eq("\nHello!\n") end end context 'no content' do let(:content) { "---\ntitle: Welcome\n---\n" } it 'has attributes' do expect(subject.attributes).to eq('title' => 'Welcome') end it 'has no content' do expect(subject.content).to eq('') end end context 'UTF-8 bom' do let(:content) { [0xEF, 0xBB, 0xBF].map(&:chr).join + "---\ntitle: Welcome\n---\nHello!\n" } it 'has attributes' do expect(subject.attributes).to eq('title' => 'Welcome') end it 'strips UTF-8 BOM' do expect(subject.content).to eq("Hello!\n") end end context 'CRLF' do let(:content) { "---\r\ntitle: Welcome\r\n---\r\nHello!\r\n" } it 'has attributes' do expect(subject.attributes).to eq('title' => 'Welcome') end it 'retains CR+LF' do # FIXME: Is this the right thing to do? expect(subject.content).to eq("Hello!\r\n") end end context 'four dashes' do let(:content) { "----\ntitle: Welcome\n----\nHello!\n" } it 'has no attributes' do expect(subject.attributes).to eq({}) end it 'has unparsed content' do expect(subject.content).to eq(content) end end context 'additional separators' do let(:content) { "---\ntitle: Welcome\n---\nHello!\n---\nStuff\n" } it 'has attributes' do expect(subject.attributes).to eq('title' => 'Welcome') end it 'has content' do expect(subject.content).to eq("Hello!\n---\nStuff\n") end end end end context 'meta and content file' do let(:content_filename) { Tempfile.open('test') { |fn| fn << 'asdf' }.path } let(:meta_filename) { Tempfile.open('test') { |fn| fn << 'asdf' }.path } before do File.write(content_filename, content) File.write(meta_filename, meta) end context 'simple' do let(:content) { "Hello\n" } let(:meta) { "title: Welcome\n" } it 'has attributes' do expect(subject.attributes).to eq('title' => 'Welcome') end it 'has content' do expect(subject.content).to eq("Hello\n") end end context 'apparent metadata section' do let(:content) { "---\nauthor: Denis\n---\nHello!\n" } let(:meta) { "title: Welcome\n" } it 'has attributes' do expect(subject.attributes).to eq('title' => 'Welcome') end it 'does not parse content' do expect(subject.content).to eq(content) end end context 'CRLF' do let(:content) { "Hello!\r\n" } let(:meta) { "title: Welcome\r\n" } it 'has attributes' do expect(subject.attributes).to eq('title' => 'Welcome') end it 'has content' do # FIXME: Is this the right thing to do? expect(subject.content).to eq("Hello!\r\n") end end context 'UTF-8 bom' do let(:content) { [0xEF, 0xBB, 0xBF].map(&:chr).join + "Hello!\n" } let(:meta) { [0xEF, 0xBB, 0xBF].map(&:chr).join + "title: Welcome\n" } it 'has attributes' do expect(subject.attributes).to eq('title' => 'Welcome') end it 'has content' do expect(subject.content).to eq("Hello!\n") end end end end end nanoc-4.11.0/nanoc/spec/nanoc/data_sources/filesystem/tools_spec.rb000066400000000000000000000052171340050175000253600ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::DataSources::Filesystem::Tools do describe '.read_file' do subject { described_class.read_file(filename, config: config) } let(:filename) { 'foo.dat' } let(:config) { {} } context 'file does not exist' do it 'errors' do expect { subject } .to raise_error( Nanoc::DataSources::Filesystem::Errors::FileUnreadable, /^Could not read foo.dat:/, ) end end context 'file exists as ISO-8859-1' do before do File.write(filename, 'élève'.encode('ISO-8859-1')) end context 'no config' do it 'errors' do expect { subject } .to raise_error( Nanoc::DataSources::Filesystem::Errors::InvalidEncoding, 'Could not read foo.dat because the file is not valid UTF-8.', ) end end context 'config with correct encoding' do let(:config) do { encoding: 'ISO-8859-1' } end it { is_expected.to eq('élève') } its(:encoding) { is_expected.to eq(Encoding::UTF_8) } end context 'config with incorrect encoding' do let(:config) do { encoding: 'UTF-16' } end it 'errors' do expect { subject } .to raise_error( Nanoc::DataSources::Filesystem::Errors::InvalidEncoding, 'Could not read foo.dat because the file is not valid UTF-16.', ) end end end context 'file exists as UTF-8' do before do File.write(filename, 'élève'.encode('UTF-8')) end context 'no config' do it { is_expected.to eq('élève') } its(:encoding) { is_expected.to eq(Encoding::UTF_8) } end context 'config with correct encoding' do let(:config) do { encoding: 'UTF-8' } end it { is_expected.to eq('élève') } its(:encoding) { is_expected.to eq(Encoding::UTF_8) } end context 'config with incorrect encoding' do let(:config) do { encoding: 'UTF-16' } end it 'errors' do expect { subject } .to raise_error( Nanoc::DataSources::Filesystem::Errors::InvalidEncoding, 'Could not read foo.dat because the file is not valid UTF-16.', ) end end end context 'file exists as UTF-8 wit BOM' do before do File.write(filename, "\xEF\xBB\xBFélève".encode('UTF-8')) end it { is_expected.to eq('élève') } its(:encoding) { is_expected.to eq(Encoding::UTF_8) } end end end nanoc-4.11.0/nanoc/spec/nanoc/data_sources/filesystem_spec.rb000066400000000000000000000233101340050175000242120ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::DataSources::Filesystem, site: true do let(:data_source) { Nanoc::DataSources::Filesystem.new(site.config, nil, nil, params) } let(:params) { {} } let(:site) { Nanoc::Int::SiteLoader.new.new_from_cwd } 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 textual 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/', type: :legacy)) 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 context 'one binary file' do before do FileUtils.mkdir_p('foo') File.write('foo/bar.dat', "---\nnum: 1\n---\ntest 1") FileUtils.touch('foo/bar.dat', mtime: now) end let(:expected_attributes) do { content_filename: 'foo/bar.dat', extension: 'dat', filename: 'foo/bar.dat', meta_filename: nil, mtime: now, } end it 'loads that file' do expect(subject.size).to eq(1) expect(subject[0].content).to be_a(Nanoc::Int::BinaryContent) expect(subject[0].attributes).to eq(expected_attributes) expect(subject[0].identifier).to eq(Nanoc::Identifier.new('/bar/', type: :legacy)) 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) end it 'has no content checksum data' do expect(subject[0].content_checksum_data).to be_nil end end context 'two content files (no inline metadata) with one meta file' do let(:params) { { identifier_type: 'full' } } before do FileUtils.mkdir_p('foo') File.write('foo/a.txt', 'hi') File.write('foo/a.md', 'ho') File.write('foo/a.yaml', 'title: Aaah') end it 'errors' do expect { subject } .to raise_error( Nanoc::Int::Errors::AmbiguousMetadataAssociation, 'There are multiple content files (foo/a.txt, foo/a.md) that could match the file containing metadata (foo/a.yaml).', ) end end context 'two content files (one has inline metadata) with one meta file' do let(:params) { { identifier_type: 'full' } } before do FileUtils.mkdir_p('foo') File.write('foo/a.txt', "---\ntitle: Hi\n---\n\nhi") File.write('foo/a.md', 'ho') File.write('foo/a.yaml', 'title: Aaah') end it 'assigns metadata to the file that doesn’t have any yet' do expect(subject.size).to eq(2) items = subject.sort_by { |i| i.identifier.to_s } expect(items[0].content).to be_a(Nanoc::Int::TextualContent) expect(items[0].identifier).to eq(Nanoc::Identifier.new('/a.md', type: :full)) expect(items[0].attributes[:title]).to eq('Aaah') expect(items[1].content).to be_a(Nanoc::Int::TextualContent) expect(items[1].identifier).to eq(Nanoc::Identifier.new('/a.txt', type: :full)) expect(items[1].attributes[:title]).to eq('Hi') end end context 'two content files (both have inline metadata) with one meta file' do let(:params) { { identifier_type: 'full' } } before do FileUtils.mkdir_p('foo') File.write('foo/a.txt', "---\ntitle: Hi\n---\n\nhi") File.write('foo/a.md', "---\ntitle: Ho\n---\n\nho") File.write('foo/a.yaml', 'title: Aaah') end it 'errors' do expect { subject } .to raise_error( Nanoc::Int::Errors::AmbiguousMetadataAssociation, 'There are multiple content files (foo/a.txt, foo/a.md) that could match the file containing metadata (foo/a.yaml).', ) end end context 'two content files (both have inline metadata) with no meta file' do let(:params) { { identifier_type: 'full' } } before do FileUtils.mkdir_p('foo') File.write('foo/a.txt', "---\ntitle: Hi\n---\n\nhi") File.write('foo/a.md', "---\ntitle: Ho\n---\n\nho") end it 'uses inline metadata' do expect(subject.size).to eq(2) items = subject.sort_by { |i| i.identifier.to_s } expect(items[0].content).to be_a(Nanoc::Int::TextualContent) expect(items[0].identifier).to eq(Nanoc::Identifier.new('/a.md', type: :full)) expect(items[0].attributes[:title]).to eq('Ho') expect(items[1].content).to be_a(Nanoc::Int::TextualContent) expect(items[1].identifier).to eq(Nanoc::Identifier.new('/a.txt', type: :full)) expect(items[1].attributes[:title]).to eq('Hi') end end context 'two content files (neither have inline metadata) with no meta file' do let(:params) { { identifier_type: 'full' } } before do FileUtils.mkdir_p('foo') File.write('foo/a.txt', 'hi') File.write('foo/a.md', 'ho') end it 'uses no metadata' do expect(subject.size).to eq(2) items = subject.sort_by { |i| i.identifier.to_s } expect(items[0].content).to be_a(Nanoc::Int::TextualContent) expect(items[0].identifier).to eq(Nanoc::Identifier.new('/a.md', type: :full)) expect(items[0].attributes[:title]).to be_nil expect(items[1].content).to be_a(Nanoc::Int::TextualContent) expect(items[1].identifier).to eq(Nanoc::Identifier.new('/a.txt', type: :full)) expect(items[1].attributes[:title]).to be_nil end end context 'one content file (with inline metadata) and a meta file' do let(:params) { { identifier_type: 'full' } } before do FileUtils.mkdir_p('foo') File.write('foo/a.txt', "---\ntitle: Hi\n---\n\nhi") File.write('foo/a.yaml', 'author: Denis') end it 'uses only metadata from meta file' do expect(subject.size).to eq(1) expect(subject[0].content).to be_a(Nanoc::Int::TextualContent) expect(subject[0].content.string).to eq("---\ntitle: Hi\n---\n\nhi") expect(subject[0].identifier).to eq(Nanoc::Identifier.new('/a.txt', type: :full)) expect(subject[0].attributes[:title]).to be_nil expect(subject[0].attributes[:author]).to eq('Denis') end end end end describe '#item_changes' do subject { data_source.item_changes } before do if Nanoc.on_windows? skip 'nanoc-live is not currently supported on Windows' end end it 'returns a stream' do expect(subject).to be_a(Nanoc::ChangesStream) end it 'contains one element after changing' do FileUtils.mkdir_p('content') enum = SlowEnumeratorTools.buffer(subject.to_enum, 1) q = SizedQueue.new(1) Thread.new { q << enum.take(1).first } # FIXME: sleep is ugly sleep 0.3 File.write('content/wat.md', 'stuff') expect(q.pop).to eq(:unknown) subject.stop end end describe '#layout_changes' do subject { data_source.layout_changes } before do if Nanoc.on_windows? skip 'nanoc-live is not currently supported on Windows' end end it 'returns a stream' do expect(subject).to be_a(Nanoc::ChangesStream) end it 'contains one element after changing' do FileUtils.mkdir_p('layouts') enum = SlowEnumeratorTools.buffer(subject.to_enum, 1) q = SizedQueue.new(1) Thread.new { q << enum.take(1).first } # FIXME: sleep is ugly sleep 0.3 File.write('layouts/wat.md', 'stuff') expect(q.pop).to eq(:unknown) subject.stop end end end nanoc-4.11.0/nanoc/spec/nanoc/deploying/000077500000000000000000000000001340050175000200065ustar00rootroot00000000000000nanoc-4.11.0/nanoc/spec/nanoc/deploying/fog_spec.rb000066400000000000000000000127061340050175000221260ustar00rootroot00000000000000# 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 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.11.0/nanoc/spec/nanoc/deploying/git_spec.rb000066400000000000000000000207341340050175000221360ustar00rootroot00000000000000# 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 = +'' stderr = +'' 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.11.0/nanoc/spec/nanoc/filters/000077500000000000000000000000001340050175000174645ustar00rootroot00000000000000nanoc-4.11.0/nanoc/spec/nanoc/filters/asciidoc_spec.rb000066400000000000000000000004431340050175000226020ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Filters::AsciiDoc do before do skip_unless_have_command 'asciidoc' end subject { described_class.new } example do expect(subject.setup_and_run('== Blah blah')) .to match(%r{

Blah blah

}) end end nanoc-4.11.0/nanoc/spec/nanoc/filters/asciidoctor_spec.rb000066400000000000000000000004441340050175000233300ustar00rootroot00000000000000# 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.11.0/nanoc/spec/nanoc/filters/colorize_syntax/000077500000000000000000000000001340050175000227205ustar00rootroot00000000000000nanoc-4.11.0/nanoc/spec/nanoc/filters/colorize_syntax/rouge_spec.rb000066400000000000000000000111501340050175000253760ustar00rootroot00000000000000# frozen_string_literal: true require 'rouge' describe Nanoc::Filters::ColorizeSyntax, filter: 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 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 nanoc-4.11.0/nanoc/spec/nanoc/filters/erb_spec.rb000066400000000000000000000103511340050175000215730ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Filters::ERB do context 'no assigns' do let(:filter) { described_class.new } example do result = filter.setup_and_run('[<%= @foo %>]') expect(result).to eq('[]') end end context 'simple assigns' do let(:filter) { described_class.new(location: 'a cheap motel') } it 'can access assign through instance variable' do result = filter.setup_and_run( '<%= "I was hiding in #{@location}." %>', # rubocop:disable Lint/InterpolationCheck ) expect(result).to eq('I was hiding in a cheap motel.') end it 'can access assign through instance method' do result = filter.setup_and_run( '<%= "I was hiding in #{location}." %>', # rubocop:disable Lint/InterpolationCheck ) expect(result).to eq('I was hiding in a cheap motel.') end it 'does not accept yield' do expect { filter.setup_and_run('<%= yield %>') } .to raise_error(LocalJumpError) end end context 'content assigns' do let(:filter) { described_class.new(content: 'a cheap motel') } it 'can access assign through instance variable' do result = filter.setup_and_run( '<%= "I was hiding in #{@content}." %>', # rubocop:disable Lint/InterpolationCheck ) expect(result).to eq('I was hiding in a cheap motel.') end it 'can access assign through instance method' do result = filter.setup_and_run( '<%= "I was hiding in #{content}." %>', # rubocop:disable Lint/InterpolationCheck ) expect(result).to eq('I was hiding in a cheap motel.') end it 'can access assign through yield' do result = filter.setup_and_run( '<%= "I was hiding in #{yield}." %>', # rubocop:disable Lint/InterpolationCheck ) expect(result).to eq('I was hiding in a cheap motel.') end end context 'locals' do let(:filter) { described_class.new } let(:params) { { locals: { location: 'a cheap motel' } } } it 'can access assign through instance variable' do result = filter.setup_and_run( '<%= "I was hiding in #{@location}." %>', # rubocop:disable Lint/InterpolationCheck params, ) expect(result).to eq('I was hiding in a cheap motel.') end it 'can access assign through instance method' do result = filter.setup_and_run( '<%= "I was hiding in #{location}." %>', # rubocop:disable Lint/InterpolationCheck params, ) expect(result).to eq('I was hiding in a cheap motel.') end end context 'error' do let(:filter) { described_class.new(layout: layout) } let(:layout) { Nanoc::Int::Layout.new('asdf', {}, '/default.erb') } subject do filter.setup_and_run('<% raise "boom %>') end example do error = begin subject rescue SyntaxError => e e end expect(error.message).to start_with('layout /default.erb:1: unterminated string meets end of file') end end context 'with trim mode' do let(:filter) { described_class.new } let(:res) { { success: false } } subject do filter.setup_and_run('% res[:success] = true', params) end context 'trim mode unchanged' do let(:params) do { locals: { res: res }, } end it 'honors trim mode' do expect { subject }.not_to change { res[:success] } end end context 'trim mode set' do let(:params) do { trim_mode: '%', locals: { res: res }, } end it 'honors trim mode' do expect { subject }.to change { res[:success] }.from(false).to(true) end end end context 'safe level' do let(:filter) { described_class.new } let(:res) { { success: false } } subject do filter.setup_and_run('<%= eval File.read("moo") %>', params) end before do File.write('moo', '1+2') end context 'safe level unchanged' do let(:params) { {} } it 'honors safe level' do expect(subject).to eq('3') end end context 'safe level set' do let(:params) { { safe_level: 1 } } it 'honors safe level' do expect { subject }.to raise_error(SecurityError) end end end end nanoc-4.11.0/nanoc/spec/nanoc/filters/less_spec.rb000066400000000000000000000070621340050175000217760ustar00rootroot00000000000000# 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 skip_unless_gem_available('less') end 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.11.0/nanoc/spec/nanoc/filters/relativize_paths_spec.rb000066400000000000000000000127261340050175000244100ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Filters::RelativizePaths do subject(:filter) { described_class.new(assigns) } let(:assigns) do { item_rep: item_rep } end let(:item) do Nanoc::Int::Item.new('contentz', {}, '/sub/page.html') end let(:item_rep) do Nanoc::Int::ItemRep.new(item, :default).tap do |rep| rep.paths = { last: ['/sub/page.html'] } end end describe '#run' do subject { filter.setup_and_run(content, params) } let(:content) do 'Foo' end let(:params) do {} end context 'HTML' do let(:params) { { type: :html } } it { is_expected.to eq('Foo') } context 'full component excluded' do let(:params) { { type: :html, exclude: '/foo' } } it { is_expected.to eq('Foo') } end context 'full component excluded as list' do let(:params) { { type: :html, exclude: ['/foo'] } } it { is_expected.to eq('Foo') } end context 'partial component excluded' do let(:params) { { type: :html, exclude: ['/fo'] } } it { is_expected.to eq('Foo') } end context 'non-root component excluded' do let(:params) { { type: :html, exclude: ['/bar'] } } it { is_expected.to eq('Foo') } end context 'excluded with regexp' do let(:params) { { type: :html, exclude: /ar/ } } it { is_expected.to eq('Foo') } end context 'excluded with regexp list' do let(:params) { { type: :html, exclude: [/ar/] } } it { is_expected.to eq('Foo') } end end context 'HTML5' do let(:params) { { type: :html5 } } it { is_expected.to eq('Foo') } context 'full component excluded' do let(:params) { { type: :html5, exclude: '/foo' } } it { is_expected.to eq('Foo') } end context 'full component excluded as list' do let(:params) { { type: :html5, exclude: ['/foo'] } } it { is_expected.to eq('Foo') } end context 'partial component excluded' do let(:params) { { type: :html5, exclude: ['/fo'] } } it { is_expected.to eq('Foo') } end context 'non-root component excluded' do let(:params) { { type: :html5, exclude: ['/bar'] } } it { is_expected.to eq('Foo') } end context 'excluded with regexp' do let(:params) { { type: :html5, exclude: /ar/ } } it { is_expected.to eq('Foo') } end context 'excluded with regexp list' do let(:params) { { type: :html5, exclude: [/ar/] } } it { is_expected.to eq('Foo') } end end context 'XHTML' do let(:params) { { type: :xhtml } } it { is_expected.to eq('Foo') } context 'full component excluded' do let(:params) { { type: :xhtml, exclude: '/foo' } } it { is_expected.to eq('Foo') } end context 'full component excluded as list' do let(:params) { { type: :xhtml, exclude: ['/foo'] } } it { is_expected.to eq('Foo') } end context 'partial component excluded' do let(:params) { { type: :xhtml, exclude: ['/fo'] } } it { is_expected.to eq('Foo') } end context 'non-root component excluded' do let(:params) { { type: :xhtml, exclude: ['/bar'] } } it { is_expected.to eq('Foo') } end context 'excluded with regexp' do let(:params) { { type: :xhtml, exclude: /ar/ } } it { is_expected.to eq('Foo') } end context 'excluded with regexp list' do let(:params) { { type: :xhtml, exclude: [/ar/] } } it { is_expected.to eq('Foo') } end end context 'CSS' do let(:params) { { type: :css } } let(:content) do '.oink { background: url(/foo/bar.png) }' end it { is_expected.to eq('.oink { background: url(../foo/bar.png) }') } context 'full component excluded' do let(:params) { { type: :css, exclude: '/foo' } } it { is_expected.to eq('.oink { background: url(/foo/bar.png) }') } end context 'full component excluded as list' do let(:params) { { type: :css, exclude: ['/foo'] } } it { is_expected.to eq('.oink { background: url(/foo/bar.png) }') } end context 'partial component excluded' do let(:params) { { type: :css, exclude: ['/fo'] } } it { is_expected.to eq('.oink { background: url(../foo/bar.png) }') } end context 'non-root component excluded' do let(:params) { { type: :css, exclude: ['/bar'] } } it { is_expected.to eq('.oink { background: url(../foo/bar.png) }') } end context 'excluded with regexp' do let(:params) { { type: :css, exclude: /ar/ } } it { is_expected.to eq('.oink { background: url(/foo/bar.png) }') } end context 'excluded with regexp list' do let(:params) { { type: :css, exclude: [/ar/] } } it { is_expected.to eq('.oink { background: url(/foo/bar.png) }') } end end end end nanoc-4.11.0/nanoc/spec/nanoc/filters/sass_spec.rb000066400000000000000000000262261340050175000220040ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Filters::SassCommon do context 'with item, items, config context' do subject(:sass) { ::Nanoc::Filter.named(:sass).new(sass_params) } subject(:sass_sourcemap) { ::Nanoc::Filter.named(:sass_sourcemap).new(sass_sourcemap_params) } let(:sass_params) do { item: item_main_view, item_rep: item_main_default_rep_view, items: item_views, config: config, } end let(:sass_sourcemap_params) do { item: item_main_view, item_rep: item_main_sourcemap_rep_view, items: item_views, config: config, } end let(:item_main) do Nanoc::Int::Item.new( content_main, { content_filename: 'content/style/main.sass' }, '/style/main.sass', ) end let(:content_main) do Nanoc::Int::TextualContent.new( '/* irrelevant */', filename: File.expand_path('content/style/main.sass'), ) end let(:item_blue) do Nanoc::Int::Item.new( content_blue, { content_filename: 'content/style/colors/blue.sass' }, '/style/colors/blue.sass', ) end let(:content_blue) do Nanoc::Int::TextualContent.new( "\.blue\n color: blue", filename: File.expand_path('content/style/colors/blue.sass'), ) end let(:item_red) do Nanoc::Int::Item.new( content_red, { content_filename: 'content/style/colors/red.scss' }, '/style/colors/red.scss', ) end let(:content_red) do Nanoc::Int::TextualContent.new( '.red { color: red; }', filename: File.expand_path('content/style/colors/red.scss'), ) end let(:item_partial) do Nanoc::Int::Item.new( content_partial, { content_filename: 'content/style/_partial.scss' }, '/style/_partial.scss', ) end let(:content_partial) do Nanoc::Int::TextualContent.new( '* { margin: 0; }', filename: File.expand_path('content/style/_partial.scss'), ) end let(:item_main_default_rep) do Nanoc::Int::ItemRep.new(item_main, :default).tap do |rep| rep.raw_paths = rep.paths = { last: [Dir.getwd + '/output/style/main.sass'] } end end let(:item_main_sourcemap_rep) do Nanoc::Int::ItemRep.new(item_main, :sourcemap).tap do |rep| rep.raw_paths = rep.paths = { last: [Dir.getwd + '/output/style/main.sass.map'] } end end let(:item_main_view) { Nanoc::CompilationItemView.new(item_main, view_context) } let(:item_main_default_rep_view) { Nanoc::CompilationItemRepView.new(item_main_default_rep, view_context) } let(:item_main_sourcemap_rep_view) { Nanoc::CompilationItemRepView.new(item_main_sourcemap_rep, view_context) } let(:items) { Nanoc::Int::ItemCollection.new(config, [item_main, item_blue, item_red, item_partial]) } let(:item_views) { Nanoc::ItemCollectionWithRepsView.new(items, view_context) } let(:view_context) do Nanoc::ViewContextForCompilation.new( reps: reps, items: items, dependency_tracker: dependency_tracker, compilation_context: compilation_context, snapshot_repo: snapshot_repo, ) end let(:reps) do Nanoc::Int::ItemRepRepo.new.tap do |reps| [item_blue, item_red, item_partial].each do |item| reps << Nanoc::Int::ItemRep.new(item, :default).tap do |rep| rep.compiled = true rep.snapshot_defs = [Nanoc::Int::SnapshotDef.new(:last, binary: false)] end end reps << item_main_default_rep reps << item_main_sourcemap_rep end end 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) do Nanoc::Int::SnapshotRepo.new.tap do |repo| repo.set(reps[item_blue].first, :last, Nanoc::Int::TextualContent.new('.blue { color: blue }')) repo.set(reps[item_red].first, :last, Nanoc::Int::TextualContent.new('.red { color: red }')) repo.set(reps[item_partial].first, :last, Nanoc::Int::TextualContent.new('* { margin: 0 }')) end end let(:empty_items) { Nanoc::Int::ItemCollection.new(config) } let(:empty_layouts) { Nanoc::Int::LayoutCollection.new(config) } let(:config) { Nanoc::Int::Configuration.new(dir: Dir.getwd).with_defaults.merge(color: 'yellow') } before do items.each do |item| FileUtils.mkdir_p(File.dirname(item.attributes[:content_filename])) File.write(item.attributes[:content_filename], item.content) end end it 'can be called with content' do expect(sass.setup_and_run(".foo #bar\n color: #f00")) .to match(/.foo\s+#bar\s*\{\s*color:\s+(red|#f00);?\s*\}/) end it 'compacts when using style=compact' do expect(sass.setup_and_run(".foo #bar\n color: #f00", style: 'compact')) .to match(/^\.foo #bar[\s]*\{[\s]*color:\s*(red|#f00);?[\s]*\}/m) end it 'compacts when using style=compressed' do expect(sass.setup_and_run(".foo #bar\n color: #f00", style: 'compressed')) .to match(/^\.foo #bar[\s]*\{[\s]*color:\s*(red|#f00);?[\s]*\}/m) end it 'supports SCSS' do expect(sass.setup_and_run('.foo { color: #f00 }', syntax: :scss)) .to match(/^\.foo[\s]*\{[\s]*color:\s*(red|#f00);?[\s]*\}/m) end it 'raises proper error on failure' do expect { sass.setup_and_run('$*#&!@($') } .to raise_error(::Sass::SyntaxError, /Invalid variable/) end context 'importing a file for which an item exists' do it 'can import by relative path' do expect(sass.setup_and_run('@import colors/blue')) .to match(/\A\.blue\s+\{\s*color:\s+blue;?\s*\}\s*\z/) expect(sass.setup_and_run('@import colors/red')) .to match(/\A\.red\s+\{\s*color:\s+red;?\s*\}\s*\z/) end it 'cannot import by nested relative path' do expect { sass.setup_and_run('@import content/style/colors/blue') } .to raise_error(::Sass::SyntaxError, /File to import not found/) expect { sass.setup_and_run('@import content/style/colors/red') } .to raise_error(::Sass::SyntaxError, /File to import not found/) end it 'can import by relative path with extension' do expect(sass.setup_and_run('@import colors/blue.sass')) .to match(/\A\.blue\s+\{\s*color:\s+blue;?\s*\}\s*\z/) expect(sass.setup_and_run('@import colors/red.scss')) .to match(/\A\.red\s+\{\s*color:\s+red;?\s*\}\s*\z/) end it 'cannot import by nested relative path with extension' do expect { sass.setup_and_run('@import content/style/colors/blue.sass') } .to raise_error(::Sass::SyntaxError, /File to import not found/) expect { sass.setup_and_run('@import content/style/colors/red.scss') } .to raise_error(::Sass::SyntaxError, /File to import not found/) end it 'can import partials by relative path' do expect(sass.setup_and_run('@import partial')) .to match(/\A\*\s*\{\s*margin:\s+0;\s*\}\s*\z/) end it 'cannot import partials by nested relative path' do expect { sass.setup_and_run('@import content/style/_partial') } .to raise_error(::Sass::SyntaxError, /File to import not found/) end it 'can import partials by relative path with extension' do expect(sass.setup_and_run('@import partial.scss')) .to match(/\A\*\s*\{\s*margin:\s+0;\s*\}\s*\z/) end it 'cannot import partials by nested relative path with extension' do expect { sass.setup_and_run('@import content/style/partial.scss') } .to raise_error(::Sass::SyntaxError, /File to import not found/) end it 'creates a dependency' do expect { sass.setup_and_run('@import partial') } .to create_dependency_on(item_views[item_partial.identifier]) end end context 'importing a file for which an item does not exist' do before { File.write('_external.scss', 'body { font: 100%; }') } context 'load_path set' do it 'can import (using load paths) by relative path' do expect(sass.setup_and_run('@import external', load_paths: ['.'])) .to match(/\Abody\s+\{\s*font:\s+100%;?\s*\}\s*\z/) end it 'creates no dependency' do expect { sass.setup_and_run('@import external', load_paths: ['.']) } .to create_dependency_from(item_main_view).onto([instance_of(Nanoc::Int::ItemCollection)]) end end context 'load_path not set' do it 'cannot import (using load paths) by relative path' do expect { sass.setup_and_run('@import external') } .to raise_error(::Sass::SyntaxError, /File to import not found/) end it 'can import (using importer) by relative path' do expect(sass.setup_and_run('@import "../../_external"')) .to match(/\Abody\s+\{\s*font:\s+100%;?\s*\}\s*\z/) end end end context 'importing by identifier or pattern' do it 'can import by identifier' do expect(sass.setup_and_run('@import /style/colors/blue.*')) .to match(/\A\.blue\s+\{\s*color:\s+blue;?\s*\}\s*\z/) expect(sass.setup_and_run('@import /style/colors/red.*')) .to match(/\A\.red\s+\{\s*color:\s+red;?\s*\}\s*\z/) end it 'can import by pattern' do expect(sass.setup_and_run('@import /style/colors/*')) .to match(/\A\.blue\s+\{\s*color:\s+blue;?\s*\}\s*\.red\s+\{\s*color:\s+red;?\s*\}\s*\z/) end end context 'sourcemaps' do it 'generates proper sourcemaps' do expect(sass.setup_and_run(".foo #bar\n color: #f00", sourcemap_path: 'main.css.map')) .to match(/.foo\s+#bar\s*\{\s*color:\s+(red|#f00);?\s*\}\s*\/\*# sourceMappingURL=main.css.map \*\//) expect(sass_sourcemap.setup_and_run(".foo #bar\n color: #f00", css_path: 'main.css', sourcemap_path: 'main.css.map')) .to match(/{.*?"sources": \["#{item_main_default_rep.raw_path}"\].*?"file": "main\.css".*?}/m) expect(sass_sourcemap.setup_and_run(".foo #bar\n color: #f00", sourcemap_path: 'main.css.map')) .not_to match(/{.*?"sources": \["#{item_main_default_rep.raw_path}"\].*?"file": ".*?".*?}/m) end it 'generates inlined sourcemaps' do expect(sass.setup_and_run(".foo #bar\n color: #f00", css_path: 'main.css', sourcemap_path: :inline)) .to match(/.foo\s+#bar\s*\{\s*color:\s+(red|#f00);?\s*\}\s*\/\*# sourceMappingURL=data:application\/json;base64.*? \*\//) end end context 'nanoc() sass function' do it 'can inspect @config' do expect(sass.setup_and_run(".foo #bar\n color: nanoc('@config[:color]', $unquote: true)")) .to match(/.foo\s+#bar\s*\{\s*color:\s+yellow;?\s*\}/) end it 'can inspect @items' do expect(sass.setup_and_run(".foo\n content: nanoc('@items[\"/style/main.*\"][:content_filename]')")) .to match(/.foo\s*\{\s*content:\s+"content\/style\/main\.sass";?\s*\}/) end end end end nanoc-4.11.0/nanoc/spec/nanoc/helpers/000077500000000000000000000000001340050175000174565ustar00rootroot00000000000000nanoc-4.11.0/nanoc/spec/nanoc/helpers/blogging_spec.rb000066400000000000000000000135321340050175000226110ustar00rootroot00000000000000# 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) } # rubocop:disable Style/DateTime 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.11.0/nanoc/spec/nanoc/helpers/breadcrumbs_spec.rb000066400000000000000000000211521340050175000233070ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Helpers::Breadcrumbs, helper: true, stdio: 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 context 'item with version number component in path' do before do ctx.create_item('grandchild', {}, Nanoc::Identifier.new('/1.5/stuff.md')) ctx.create_item('child0', {}, Nanoc::Identifier.new('/1.4.md')) ctx.create_item('child1', {}, Nanoc::Identifier.new('/1.5.md')) ctx.create_item('child2', {}, Nanoc::Identifier.new('/1.6.md')) ctx.create_item('root', {}, Nanoc::Identifier.new('/index.md')) ctx.item = ctx.items['/1.5/stuff.md'] end it 'picks the closest parent' do expect(subject) .to eql( [ ctx.items['/index.md'], ctx.items['/1.5.md'], ctx.items['/1.5/stuff.md'], ], ) end end context 'item with multiple extensions in path' do before do ctx.create_item('grandchild', {}, Nanoc::Identifier.new('/foo/stuff.md')) ctx.create_item('child0', {}, Nanoc::Identifier.new('/foo.md.erb')) ctx.create_item('child1', {}, Nanoc::Identifier.new('/foo.md')) ctx.create_item('child2', {}, Nanoc::Identifier.new('/foo.erb')) ctx.create_item('root', {}, Nanoc::Identifier.new('/index.md')) ctx.item = ctx.items['/foo/stuff.md'] end context 'no tiebreaker specified' do it 'picks the first' do expect(subject) .to eql( [ ctx.items['/index.md'], ctx.items['/foo.erb'], ctx.items['/foo/stuff.md'], ], ) end it 'logs a warning' do expect { subject }.to output(Regexp.new(Regexp.escape('Warning: The breadcrumbs trail (generated by #breadcrumbs_trail) found more than one potential parent item at /foo.* (found /foo.erb, /foo.md, /foo.md.erb). Nanoc will pick the first item as the parent. Consider eliminating the ambiguity by making only one item match /foo.*, or by passing a `:tiebreaker` option to `#breadcrumbs_trail`. (This situation will be an error in the next major version of Nanoc.)'))).to_stderr end end context 'tiebreaker :error specified' do subject { helper.breadcrumbs_trail(tiebreaker: :error) } it 'errors because of ambiguity' do expect { subject } .to raise_error( Nanoc::Helpers::Breadcrumbs::AmbiguousAncestorError, 'expected only one item to match /foo.*, but found 3', ) end end context 'tiebreaker which picks the last' do subject { helper.breadcrumbs_trail(tiebreaker: tiebreaker) } let(:tiebreaker) do ->(items, _pattern) { items.max_by(&:identifier) } end it 'picks the last' do expect(subject) .to eql( [ ctx.items['/index.md'], ctx.items['/foo.md.erb'], ctx.items['/foo/stuff.md'], ], ) end end context 'tiebreaker without pattern arg which picks the last' do subject { helper.breadcrumbs_trail(tiebreaker: tiebreaker) } let(:tiebreaker) do ->(items) { items.max_by(&:identifier) } end it 'picks the last' do expect(subject) .to eql( [ ctx.items['/index.md'], ctx.items['/foo.md.erb'], ctx.items['/foo/stuff.md'], ], ) end end end context 'child with multiple extensions' do before do ctx.create_item('grandchild1', {}, Nanoc::Identifier.new('/foo/stuff.zip')) ctx.create_item('grandchild2', {}, Nanoc::Identifier.new('/foo/stuff.md')) ctx.create_item('grandchild3', {}, Nanoc::Identifier.new('/foo/stuff.png')) ctx.create_item('child', {}, Nanoc::Identifier.new('/foo.md')) ctx.create_item('root', {}, Nanoc::Identifier.new('/index.md')) ctx.item = ctx.items['/foo/stuff.md'] end it 'picks the best parent' do expect(subject) .to eql( [ ctx.items['/index.md'], ctx.items['/foo.md'], ctx.items['/foo/stuff.md'], ], ) end end end end end nanoc-4.11.0/nanoc/spec/nanoc/helpers/capturing_spec.rb000066400000000000000000000173701340050175000230210ustar00rootroot00000000000000# 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) { +'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) { +'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) { +'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 = +'' ex.run $OUTPUT_FIELD_SEPARATOR = orig_output_field_separator end include_examples 'returns properly joined output' end end end end nanoc-4.11.0/nanoc/spec/nanoc/helpers/child_parent_spec.rb000066400000000000000000000052221340050175000234520ustar00rootroot00000000000000# 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.11.0/nanoc/spec/nanoc/helpers/filtering_spec.rb000066400000000000000000000036031340050175000230020ustar00rootroot00000000000000# 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.11.0/nanoc/spec/nanoc/helpers/html_escape_spec.rb000066400000000000000000000016451340050175000233070ustar00rootroot00000000000000# 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) { +'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.11.0/nanoc/spec/nanoc/helpers/link_to_spec.rb000066400000000000000000000207711340050175000224630ustar00rootroot00000000000000# 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.11.0/nanoc/spec/nanoc/helpers/rendering_spec.rb000066400000000000000000000104701340050175000227740ustar00rootroot00000000000000# 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.11.0/nanoc/spec/nanoc/helpers/tagging_spec.rb000066400000000000000000000060171340050175000224410ustar00rootroot00000000000000# 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.11.0/nanoc/spec/nanoc/helpers/text_spec.rb000066400000000000000000000025361340050175000220070ustar00rootroot00000000000000# 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.11.0/nanoc/spec/nanoc/integration/000077500000000000000000000000001340050175000203375ustar00rootroot00000000000000nanoc-4.11.0/nanoc/spec/nanoc/integration/compile_command_spec.rb000066400000000000000000000033661340050175000250340ustar00rootroot00000000000000# 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 it 'recompiles when changing routes' do # 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" end # Compile site = Nanoc::Int::SiteLoader.new.new_from_cwd site.compile # Check expect(File.read('output/index.html')).to eq('

A

') # 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" end # Compile site = Nanoc::Int::SiteLoader.new.new_from_cwd site.compile # Check expect(File.read('output/index.html')).to eq('

B

') end end nanoc-4.11.0/nanoc/spec/nanoc/integration/outdatedness_integration_spec.rb000066400000000000000000000211421340050175000270030ustar00rootroot00000000000000# 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.11.0/nanoc/spec/nanoc/integration/partial_recompilation_spec.rb000066400000000000000000000034051340050175000262610ustar00rootroot00000000000000# 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.11.0/nanoc/spec/nanoc/integration/toml_config_spec.rb000066400000000000000000000010461340050175000241770ustar00rootroot00000000000000# frozen_string_literal: true describe 'TOML configuration', site: true, stdio: true do example do File.write('content/foo.md', '<%= @config[:animal] %>') File.write('Rules', <<~EOS) compile '/foo.*' do filter :erb write '/foo.html' end EOS FileUtils.rm_f('nanoc.yaml') File.write('nanoc.toml', <<~EOS) animal = "donkey" EOS Nanoc::Feature.enable(Nanoc::Feature::TOML) do Nanoc::CLI.run(%w[compile]) end expect(File.read('output/foo.html')).to eq('donkey') end end nanoc-4.11.0/nanoc/spec/nanoc/integration/write_nil_spec.rb000066400000000000000000000044341340050175000236770ustar00rootroot00000000000000# frozen_string_literal: true describe 'write nil (skip routing rule)', site: true, stdio: true do context 'write non-nil + write nil' do before do File.write('content/foo.md', 'foo') File.write('Rules', <<~EOS) compile '/foo.*' do write '/foo-via-compilation-rule.txt' write nil end route '/foo.*' do '/foo-via-routing-rule.txt' end EOS end it 'starts off empty' do expect(File.file?('output/foo-via-compilation-rule.txt')).not_to be expect(File.file?('output/foo-via-routing-rule.txt')).not_to be end it 'outputs creation of correct file' do expect { Nanoc::CLI.run(%w[compile --verbose]) rescue nil } .to output(/create.*output\/foo-via-compilation-rule\.txt/).to_stdout end it 'does not output creation of incorrect file' do expect { Nanoc::CLI.run(%w[compile --verbose]) rescue nil } .not_to output(/create.*output\/foo-via-routing-rule\.txt/).to_stdout end it 'creates correct file' do expect { Nanoc::CLI.run(%w[compile --verbose --debug]) rescue nil } .to change { File.file?('output/foo-via-compilation-rule.txt') } .from(false) .to(true) end it 'does not create incorrect file' do expect { Nanoc::CLI.run(%w[compile --verbose --debug]) rescue nil } .not_to change { File.file?('output/foo-via-routing-rule.txt') } end end context 'write nil only' do before do File.write('content/foo.md', 'foo') File.write('Rules', <<~EOS) compile '/foo.*' do write nil end route '/foo.*' do '/foo-via-routing-rule.txt' end EOS end it 'starts off empty' do expect(File.file?('output/foo-via-compilation-rule.txt')).not_to be expect(File.file?('output/foo-via-routing-rule.txt')).not_to be end it 'does not output creation of incorrect file' do expect { Nanoc::CLI.run(%w[compile --verbose]) rescue nil } .not_to output(/create.*output\/foo-via-routing-rule\.txt/).to_stdout end it 'does not create incorrect file' do expect { Nanoc::CLI.run(%w[compile --verbose --debug]) rescue nil } .not_to change { File.file?('output/foo-via-routing-rule.txt') } end end end nanoc-4.11.0/nanoc/spec/nanoc/regressions/000077500000000000000000000000001340050175000203575ustar00rootroot00000000000000nanoc-4.11.0/nanoc/spec/nanoc/regressions/gh_1015_spec.rb000066400000000000000000000007121340050175000227620ustar00rootroot00000000000000# frozen_string_literal: true describe 'GH-1015', site: true, stdio: true do before do File.write('content/foo.md', 'I am foo!') File.write('Rules', < / <%= yield %>') File.write('Rules', <]') File.write('content/bar.md', 'I am bar!') 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', <]') File.write('Rules', <') File.write('content/bar.txt', 'foo=<%= @items["/foo.*"].compiled_content %>') File.write('layouts/default.erb', '*<%= yield %>*') 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.11.0/nanoc/spec/nanoc/regressions/gh_1047_spec.rb000066400000000000000000000020111340050175000227610ustar00rootroot00000000000000# 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.11.0/nanoc/spec/nanoc/regressions/gh_1064_spec.rb000066400000000000000000000007311340050175000227670ustar00rootroot00000000000000# frozen_string_literal: true describe 'GH-1064', site: true, stdio: true do before do File.write('content/foo.erb', '*<%= @items["/bar.*"].compiled_content(snapshot: :pre) %>*') File.write('content/bar.erb', 'Bar!') File.write('Rules', <') File.write('Rules', <') File.write('content/b.erb', 'stuff') File.write('Rules', <') File.write('content/b.dat', 'stuff') File.write('Rules', <') File.write('content/b.erb', '<%= @items["/a.*"].reps[:default].binary? %>') File.write('Rules', <') File.write('Rules', <') File.write('content/z.dat', 'asdf') File.write('Rules', <') File.write('Rules', <') File.write('content/z.dat', 'quux') File.write('Rules', <') File.write('Rules', <') 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.11.0/nanoc/spec/nanoc/regressions/gh_1185_spec.rb000066400000000000000000000006331340050175000227740ustar00rootroot00000000000000# frozen_string_literal: true describe 'GH-1185', site: true, stdio: true do before do File.write('content/foo.html', 'stuff') File.write('Rules', <<~EOS) preprocess do @items['/foo.*'].identifier = '/bar.html' end compile '/**/*' do filter :erb write ext: 'html' end EOS end it 'does not crash' do Nanoc::CLI.run(%w[compile]) end end nanoc-4.11.0/nanoc/spec/nanoc/regressions/gh_1216_spec.rb000066400000000000000000000043601340050175000227700ustar00rootroot00000000000000# frozen_string_literal: true describe 'GH-1216', site: true, stdio: true do before do FileUtils.mkdir_p('content/talks') File.write('content/talks/aaa.html', 'A') File.write('content/talks/bbb.html', 'B') File.write('content/talks.html', '<%= @items.find_all("/talks/*").map { |i| i.raw_content + "=" + i[:status].to_s }.sort.join(" ") %>') File.write('Rules', <<~EOS) compile '/**/*' do filter :erb write ext: 'html' end EOS end before do Nanoc::CLI.run(%w[compile]) end context 'attributes changed using #[]=' do before do File.write('Rules', <<~EOS) preprocess do @items['/talks/aaa.*'][:status] = 'archived' @items['/talks/bbb.*'][:status] = 'archived' end compile '/**/*' do filter :erb write ext: 'html' end EOS end it 'changes output file' do expect { Nanoc::CLI.run(%w[compile]) } .to change { File.read('output/talks.html') } .from('A= B=') .to('A=archived B=archived') end end context 'attributes changed using update_attributes' do before do File.write('Rules', <<~EOS) preprocess do @items['/talks/aaa.*'].update_attributes(status: 'archived') @items['/talks/bbb.*'].update_attributes(status: 'archived') end compile '/**/*' do filter :erb write ext: 'html' end EOS end it 'changes output file' do expect { Nanoc::CLI.run(%w[compile]) } .to change { File.read('output/talks.html') } .from('A= B=') .to('A=archived B=archived') end end context 'raw content changed' do before do File.write('Rules', <<~EOS) preprocess do @items['/talks/aaa.*'][:status] = 'archived' @items['/talks/bbb.*'][:status] = 'current' @items['/talks/aaa.*'].raw_content = 'AAH' end compile '/**/*' do filter :erb write ext: 'html' end EOS end it 'changes output file' do expect { Nanoc::CLI.run(%w[compile]) } .to change { File.read('output/talks.html') } .from('A= B=') .to('AAH=archived B=current') end end end nanoc-4.11.0/nanoc/spec/nanoc/regressions/gh_1248_spec.rb000066400000000000000000000007311340050175000227730ustar00rootroot00000000000000# frozen_string_literal: true describe 'GH-1248', site: true, stdio: true do before do File.write('content/stuff.html', 'hi') File.write('Rules', <<~EOS) preprocess do @config[:output_dir] = 'ootpoot' end passthrough '/**/*' EOS end before do Nanoc::CLI.run(%w[compile]) end example do expect { Nanoc::CLI.run(%w[compile --verbose]) } .not_to output(/identical .* ootpoot\/stuff.html/).to_stdout end end nanoc-4.11.0/nanoc/spec/nanoc/regressions/gh_1313_spec.rb000066400000000000000000000010451340050175000227630ustar00rootroot00000000000000# frozen_string_literal: true describe 'GH-1313', site: true, stdio: true do before do File.write('nanoc.yaml', <<~CONFIG) output_dir: build/bin/web/bin prune: auto_prune: true exclude: - bin CONFIG end before do FileUtils.mkdir_p('build/bin/web/bin') File.write('build/bin/web/bin/should-be-pruned', 'asdf') end example do expect { Nanoc::CLI.run(%w[compile]) } .to change { File.file?('build/bin/web/bin/should-be-pruned') } .from(true) .to(false) end end nanoc-4.11.0/nanoc/spec/nanoc/regressions/gh_1319_spec.rb000066400000000000000000000007311340050175000227720ustar00rootroot00000000000000# frozen_string_literal: true describe 'GH-1319', site: true, stdio: true do before do File.write('content/stuff.html', 'abc') File.write('Rules', <<~EOS) compile '/**/*' do filter :relativize_paths, type: :html write ext: 'html' end EOS end before do Nanoc::CLI.run(%w[compile]) end example do expect(File.read('output/stuff.html')).to eq('abc') end end nanoc-4.11.0/nanoc/spec/nanoc/regressions/gh_1323_spec.rb000066400000000000000000000010321340050175000227600ustar00rootroot00000000000000# frozen_string_literal: true describe 'GH-1323', site: true, stdio: true do before do File.write('content/stuff.html', 'stuff') File.write('lib/stuff.rb', <<~EOS) Nanoc::Filter.define(:filter_gh1323) do |content, params = {}| nil end EOS File.write('Rules', <<~EOS) compile '/**/*' do filter :filter_gh1323 end EOS end example do expect { Nanoc::CLI.run(%w[compile]) } .to raise_error { |e| e.unwrap.is_a?(Nanoc::Int::Errors::FilterReturnedNil) } end end nanoc-4.11.0/nanoc/spec/nanoc/regressions/gh_1328_spec.rb000066400000000000000000000023001340050175000227640ustar00rootroot00000000000000# frozen_string_literal: true describe 'GH-1328', site: true, stdio: true do before do FileUtils.mkdir_p('content') File.write('content/foo.md', <<~EOS) hi bork bork EOS File.write('Rules', <<~EOS) compile '/*' do write ext: 'html' write ext: 'htm' write ext: 'xhtml' end EOS end before do Nanoc::CLI.run([]) end it 'fails check for foo.html' do expect { Nanoc::CLI.run(%w[check ilinks]) } .to raise_error(Nanoc::Int::Errors::GenericTrivial, 'One or more checks failed') .and output(%r{output/foo\.html:}).to_stdout end it 'fails check for foo.xhtml' do expect { Nanoc::CLI.run(%w[check ilinks]) } .to raise_error(Nanoc::Int::Errors::GenericTrivial, 'One or more checks failed') .and output(%r{output/foo\.xhtml:}).to_stdout end it 'fails check for foo.htm' do expect { Nanoc::CLI.run(%w[check ilinks]) } .to raise_error(Nanoc::Int::Errors::GenericTrivial, 'One or more checks failed') .and output(%r{output/foo\.htm:}).to_stdout end end nanoc-4.11.0/nanoc/spec/nanoc/regressions/gh_1338_spec.rb000066400000000000000000000007371340050175000230010ustar00rootroot00000000000000# frozen_string_literal: true describe 'GH-1338', site: true, stdio: true do before do File.write('lib/default.rb', <<~EOS) Nanoc::Filter.define(:gh_1338) do |content, params = {}| Dir.chdir('..') content.upcase end EOS File.write('Rules', <<~EOS) compile '/*' do filter :gh_1338 write ext: 'html' end EOS File.write('content/foo.txt', 'stuff') end example do Nanoc::CLI.run([]) end end nanoc-4.11.0/nanoc/spec/nanoc/regressions/gh_1342_spec.rb000066400000000000000000000006731340050175000227730ustar00rootroot00000000000000# frozen_string_literal: true describe 'GH-1342', site: true, stdio: true do before do File.write('Rules', <<~EOS) preprocess do items.create('<%= "hi!" %>', {}, '/hello.html') end compile '/*' do filter :erb write ext: 'html' end postprocess do @items.each(&:compiled_content) end EOS end example do Nanoc::CLI.run([]) Nanoc::CLI.run([]) end end nanoc-4.11.0/nanoc/spec/nanoc/regressions/gh_1352_spec.rb000066400000000000000000000004601340050175000227660ustar00rootroot00000000000000# frozen_string_literal: true describe 'GH-1352', site: true, stdio: true do before do File.write('nanoc.yaml', <<~EOS) environments: default: foo: 'bar' xxx: EOS end example do expect { Nanoc::CLI.run([]) }.to raise_error(JsonSchema::Error) end end nanoc-4.11.0/nanoc/spec/nanoc/regressions/gh_1358_spec.rb000066400000000000000000000011421340050175000227720ustar00rootroot00000000000000# frozen_string_literal: true describe 'GH-1358', site: true, stdio: true do before do FileUtils.mkdir_p('content') File.write('content/foo.dat', 'hi') File.write('content/home.erb', '<%= File.read(@items["/foo.*"].raw_filename) %>') File.write('Rules', <<~EOS) ignore '/*.dat' compile '/*' do filter :erb write ext: 'html' end EOS end example do Nanoc::CLI.run([]) File.write('content/foo.dat', 'hello') expect { Nanoc::CLI.run([]) } .to change { File.read('output/home.html') } .from('hi') .to('hello') end end nanoc-4.11.0/nanoc/spec/nanoc/regressions/gh_1372_spec.rb000066400000000000000000000014241340050175000227710ustar00rootroot00000000000000# frozen_string_literal: true describe 'GH-1372', site: true, stdio: true do before do FileUtils.mkdir_p('content') File.write('content/home.erb', 'hello') FileUtils.mkdir_p('layouts') File.write('layouts/default.haml', '#main= yield') File.write('Rules', <<~EOS) compile '/*' do layout '/default.*' write ext: 'html' end layout '/**/*', :haml, remove_whitespace: false EOS end example do Nanoc::CLI.run(['--verbose']) File.write('Rules', <<~EOS) compile '/*' do layout '/default.*' write ext: 'html' end layout '/**/*', :haml, remove_whitespace: true EOS expect { Nanoc::CLI.run(['--verbose']) } .to output(%r{update.*output/home\.html$}).to_stdout end end nanoc-4.11.0/nanoc/spec/nanoc/regressions/gh_1374_spec.rb000066400000000000000000000006421340050175000227740ustar00rootroot00000000000000# frozen_string_literal: true describe 'GH-1374', site: true, stdio: true do before do FileUtils.mkdir_p('content') File.write('content/test.md', 'hello') File.write('Rules', <<~EOS) compile '/*' do write nil end passthrough '/*' EOS end example do expect { Nanoc::CLI.run([]) } .not_to change { File.file?('output/test.md') } .from(false) end end nanoc-4.11.0/nanoc/spec/nanoc/regressions/gh_1378_spec.rb000066400000000000000000000011401340050175000227720ustar00rootroot00000000000000# frozen_string_literal: true describe 'GH-1378', site: true, stdio: true do before do FileUtils.mkdir_p('content') File.write('outside.scss', 'p { color: red; }') File.write('content/style.scss', '@import "../outside.scss";') File.write('Rules', <<~EOS) compile '/*' do filter :sass, syntax: :scss write ext: 'css' end EOS end example do expect { Nanoc::CLI.run([]) } .to change { File.file?('output/style.css') } .from(false) .to(true) expect(File.read('output/style.css')).to match(/p\s*{\s*color:\s*red;\s*}/) end end nanoc-4.11.0/nanoc/spec/nanoc/regressions/gh_761_spec.rb000066400000000000000000000010641340050175000227120ustar00rootroot00000000000000# frozen_string_literal: true describe 'GH-761', site: true do before do File.write('content/donkey.md', 'Compiled content donkey!') File.write('layouts/foo.erb', '[<%= @item.compiled_content %>]') 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('content/items-view.md', 'Frozen? <%= @items.frozen? %>!') 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.11.0/nanoc/spec/nanoc/regressions/gh_913_spec.rb000066400000000000000000000007301340050175000227100ustar00rootroot00000000000000# frozen_string_literal: true describe 'GH-913', site: true, stdio: true do before do File.write('content/hello.html', 'hi!') 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.11.0/nanoc/spec/nanoc/regressions/gh_928_spec.rb��������������������������������������������0000664�0000000�0000000�00000000265�13400501750�0022721�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# frozen_string_literal: true describe 'GH-928', site: true, stdio: true do example do expect { Nanoc::CLI.run(%w[check --list]) }.to output(%r{^ css$}).to_stdout end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nanoc-4.11.0/nanoc/spec/nanoc/regressions/gh_937_spec.rb��������������������������������������������0000664�0000000�0000000�00000001377�13400501750�0022726�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# 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.11.0/nanoc/spec/nanoc/regressions/gh_942_spec.rb��������������������������������������������0000664�0000000�0000000�00000001024�13400501750�0022707�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# frozen_string_literal: true describe 'GH-942', site: true, stdio: true do before do File.write('content/foo.md', 'Foo!') File.write('Rules', <<EOS) compile '/foo.*' do write '/parent/foo' end EOS File.open('nanoc.yaml', 'w') do |io| io << 'prune:' << "\n" io << ' auto_prune: true' << "\n" end end example do File.write('output/parent', 'Hahaaa! I am a file and not a directory!') Nanoc::CLI.run(%w[compile]) expect(File.read('output/parent/foo')).to eq('Foo!') end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nanoc-4.11.0/nanoc/spec/nanoc/regressions/gh_947_spec.rb��������������������������������������������0000664�0000000�0000000�00000001052�13400501750�0022715�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# frozen_string_literal: true describe 'GH-947', site: true, stdio: true do before do File.write('content/foo.md', 'Foo!') File.write('Rules', <<EOS) compile '/foo.*' do write '/foo' end EOS File.open('nanoc.yaml', 'w') do |io| io << 'prune:' << "\n" io << ' auto_prune: true' << "\n" end end example do File.write('output/foo', 'I am an older foo!') expect { Nanoc::CLI.run(%w[compile]) }.to output(%r{\s+update.* output/foo$}).to_stdout expect(File.read('output/foo')).to eq('Foo!') end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nanoc-4.11.0/nanoc/spec/nanoc/regressions/gh_948_spec.rb��������������������������������������������0000664�0000000�0000000�00000000602�13400501750�0022716�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# frozen_string_literal: true describe 'GH-948', site: true, stdio: true do before do File.write('content/foo.md', 'Foo!') File.open('nanoc.yaml', 'w') do |io| io << 'prune:' << "\n" io << ' auto_prune: true' << "\n" end FileUtils.rm_rf('output') end it 'does not crash when output dir is not present' do Nanoc::CLI.run(%w[compile]) end end ������������������������������������������������������������������������������������������������������������������������������nanoc-4.11.0/nanoc/spec/nanoc/regressions/gh_951_spec.rb��������������������������������������������0000664�0000000�0000000�00000000640�13400501750�0022712�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# frozen_string_literal: true describe 'GH-951', site: true, stdio: true do before do File.write('content/foo.md', 'Foo!') File.open('nanoc.yaml', 'w') do |io| io << 'string_pattern_type: legacy' << "\n" end File.write('Rules', <<EOS) passthrough '/foo.md' EOS end it 'copies foo.md' do Nanoc::CLI.run(%w[compile]) expect(File.file?('output/foo.md')).to eq(true) end end ������������������������������������������������������������������������������������������������nanoc-4.11.0/nanoc/spec/nanoc/regressions/gh_954_spec.rb��������������������������������������������0000664�0000000�0000000�00000001775�13400501750�0022727�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# frozen_string_literal: true describe 'GH-954', site: true, stdio: true do before do File.write('content/foo.md', 'foo <a href="/">root</a>') File.write('content/bar.md', 'bar <a href="/">root</a>') 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 <a href="./">root</a>') # Path is not relativized expect(File.read('output/bar-copy.html')).to eq('bar <a href="/">root</a>') end end ���nanoc-4.11.0/nanoc/spec/nanoc/regressions/gh_970a_spec.rb�������������������������������������������0000664�0000000�0000000�00000000650�13400501750�0023055�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# 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.11.0/nanoc/spec/nanoc/regressions/gh_970b_spec.rb�������������������������������������������0000664�0000000�0000000�00000003043�13400501750�0023055�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# 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.11.0/nanoc/spec/nanoc/regressions/gh_974_spec.rb��������������������������������������������0000664�0000000�0000000�00000000607�13400501750�0022722�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# frozen_string_literal: true describe 'GH-974', site: true, stdio: true do before do File.write('content/foo.md', 'foo') File.write('Rules', <<~EOS) compile '/foo.*' do write item.identifier end EOS end it 'writes to path corresponding to identifier' do Nanoc::CLI.run(%w[compile]) expect(File.file?('output/foo.md')).to eq(true) end end �������������������������������������������������������������������������������������������������������������������������nanoc-4.11.0/nanoc/spec/nanoc/regressions/gh_981_spec.rb��������������������������������������������0000664�0000000�0000000�00000001102�13400501750�0022707�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# frozen_string_literal: true describe 'GH-981', site: true, stdio: true do before do File.write('content/foo.md', 'I am foo!') File.write('Rules', <<EOS) compile '/foo.*' do filter :erb, stuff: self write '/foo.html' end EOS end it 'creates at first' do expect { Nanoc::CLI.run(%w[compile --verbose]) }.to output(%r{create.*output/foo\.html$}).to_stdout end it 'skips the item on second try' do Nanoc::CLI.run(%w[compile]) expect { Nanoc::CLI.run(%w[compile --verbose]) }.to output(%r{skip.*output/foo\.html$}).to_stdout end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nanoc-4.11.0/nanoc/spec/nanoc/rule_dsl/�������������������������������������������������������������0000775�0000000�0000000�00000000000�13400501750�0017625�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������nanoc-4.11.0/nanoc/spec/nanoc/rule_dsl/action_recorder_spec.rb��������������������������������������0000664�0000000�0000000�00000014560�13400501750�0024334�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# frozen_string_literal: true describe Nanoc::RuleDSL::ActionRecorder do let(:recorder) { described_class.new(rep) } let(:action_sequence) { recorder.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 recorder.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 recorder.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 recorder.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 recorder.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 { recorder.layout(:default, donkey: 123) }.to raise_error(ArgumentError) end end describe '#snapshot' do context 'snapshot already exists' do before do recorder.snapshot(:foo) end it 'raises when creating same snapshot' do expect { recorder.snapshot(:foo) } .to raise_error(Nanoc::Int::Errors::CannotCreateMultipleSnapshotsWithSameName) end end context 'no arguments' do subject { recorder.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 { recorder.snapshot(:foo, subject_params) } let(:subject_params) { {} } context 'routing rule does not exist' do context 'no explicit path given' do subject { recorder.snapshot(:foo, subject_params) } 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 it 'keeps skip_routing_rule' do expect { subject } .not_to change { recorder.snapshots_for_which_to_skip_routing_rule } .from(Set.new) end end context 'explicit path given as string' do let(:subject_params) { { 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 it 'sets skip_routing_rule' do expect { subject } .to change { recorder.snapshots_for_which_to_skip_routing_rule } .from(Set.new) .to(Set.new([:foo])) end end context 'explicit path given as identifier' do let(:subject_params) { { 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 it 'sets skip_routing_rule' do expect { subject } .to change { recorder.snapshots_for_which_to_skip_routing_rule } .from(Set.new) .to(Set.new([:foo])) end end context 'explicit path given as nil' do let(:subject_params) { { path: nil } } 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 it 'sets skip_routing_rule' do expect { subject } .to change { recorder.snapshots_for_which_to_skip_routing_rule } .from(Set.new) .to(Set.new([:foo])) end end end end it 'raises when given unknown arguments' do expect { recorder.snapshot(:foo, animal: 'giraffe') } .to raise_error(ArgumentError) end it 'can create multiple snapshots with different names' do recorder.snapshot(:foo) recorder.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.11.0/nanoc/spec/nanoc/rule_dsl/action_sequence_calculator_spec.rb���������������������������0000664�0000000�0000000�00000017176�13400501750�0026556�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# 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(:config) { Nanoc::Int::Configuration.new(dir: Dir.getwd).with_defaults } let(:items) { Nanoc::Int::ItemCollection.new(config) } let(:layouts) { Nanoc::Int::LayoutCollection.new(config) } let(:data_source_class) do Class.new(Nanoc::DataSource) do def items @config.fetch(:items) end def layouts @config.fetch(:layouts) end end end let(:data_source) do data_source_config = { items: items, layouts: layouts } data_source_class.new(config, '/', '/', data_source_config) end let(:site) do Nanoc::Int::Site.new(config: config, code_snippets: [], data_source: data_source) end 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')) } 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::CompilationRule.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::CompilationRule.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::CompilationRule.new(Nanoc::Int::Pattern.from('/list.*'), :csv, proc {}) rules_collection.add_item_compilation_rule(compilation_rule) # Add routing rule routing_rule = Nanoc::RuleDSL::RoutingRule.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::CompilationRule.new(Nanoc::Int::Pattern.from('/list.*'), :csv, proc {}) rules_collection.add_item_compilation_rule(compilation_rule) # Add routing rule routing_rule = Nanoc::RuleDSL::RoutingRule.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.11.0/nanoc/spec/nanoc/rule_dsl/rule_context_spec.rb�����������������������������������������0000664�0000000�0000000�00000025206�13400501750�0023704�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# frozen_string_literal: true shared_examples 'a rule context' do let(:item_identifier) { Nanoc::Identifier.new('/foo.md') } let(:item) { Nanoc::Int::Item.new('content', {}, item_identifier) } let(:config) { Nanoc::Int::Configuration.new(dir: Dir.getwd) } let(:items) { Nanoc::Int::ItemCollection.new(config) } let(:layouts) { Nanoc::Int::LayoutCollection.new(config) } let(:site) do Nanoc::Int::Site.new( config: config, code_snippets: [], data_source: data_source, ) end let(:data_source) do Nanoc::Int::InMemDataSource.new(items, layouts) end let(:rep) { Nanoc::Int::ItemRep.new(item, :default) } let(:reps) { double(:reps) } let(:compilation_context) { double(:compilation_context) } let(:view_context) do Nanoc::ViewContextForPreCompilation.new(items: items) end let(:dependency_tracker) { double(:dependency_tracker) } describe '#initialize' do it 'wraps objects in view classes' do expect(subject.rep.class).to eql(Nanoc::BasicItemRepView) expect(subject.item.class).to eql(Nanoc::BasicItemView) 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::BasicItemView) 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::BasicItemView) 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::BasicItemView]) 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 end describe(Nanoc::RuleDSL::RoutingRuleContext) do subject(:rule_context) do described_class.new(rep: rep, site: site, view_context: view_context) end let(:item_identifier) { Nanoc::Identifier.new('/foo.md') } let(:item) { Nanoc::Int::Item.new('content', {}, item_identifier) } let(:rep) { Nanoc::Int::ItemRep.new(item, :default) } let(:config) { Nanoc::Int::Configuration.new(dir: Dir.getwd) } let(:items) { Nanoc::Int::ItemCollection.new(config) } let(:site) do Nanoc::Int::Site.new( config: config, code_snippets: [], data_source: data_source, ) end let(:view_context) do Nanoc::ViewContextForPreCompilation.new(items: items) end it_behaves_like 'a rule context' end describe(Nanoc::RuleDSL::CompilationRuleContext) do subject(:rule_context) do described_class.new(rep: rep, site: site, recorder: recorder, view_context: view_context) end let(:item_identifier) { Nanoc::Identifier.new('/foo.md') } let(:item) { Nanoc::Int::Item.new('content', {}, item_identifier) } let(:rep) { Nanoc::Int::ItemRep.new(item, :default) } let(:config) { Nanoc::Int::Configuration.new(dir: Dir.getwd) } let(:items) { Nanoc::Int::ItemCollection.new(config) } let(:layouts) { Nanoc::Int::LayoutCollection.new(config) } let(:site) do Nanoc::Int::Site.new( config: config, code_snippets: [], data_source: data_source, ) end let(:data_source) do Nanoc::Int::InMemDataSource.new(items, layouts) end let(:rep) { Nanoc::Int::ItemRep.new(item, :default) } let(:view_context) do Nanoc::ViewContextForPreCompilation.new(items: items) end let(:recorder) { Nanoc::RuleDSL::ActionRecorder.new(rep) } it_behaves_like 'a rule context' 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 recorder' do expect(recorder).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 recorder' do expect(recorder).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 recorder' do expect(recorder).to receive(:snapshot).with(:for_snippet, path: '/foo.html') subject end end describe '#write' do context 'with string' do context 'calling once' do subject { rule_context.write('/foo.html') } it 'makes a request to the recorder' do expect(recorder).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 recorder with unique snapshot names' do expect(recorder).to receive(:snapshot).with(:_0, path: '/foo.html') expect(recorder).to receive(:snapshot).with(:_1, path: '/bar.html') subject end end end context 'with identifier' do context 'calling once' do subject { rule_context.write(identifier) } let(:identifier) { Nanoc::Identifier.new('/foo.html') } it 'makes a request to the recorder' do expect(recorder).to receive(:snapshot).with(:_0, path: '/foo.html') subject end end context 'calling twice' do subject do rule_context.write(identifier_a) rule_context.write(identifier_b) end let(:identifier_a) { Nanoc::Identifier.new('/foo.html') } let(:identifier_b) { Nanoc::Identifier.new('/bar.html') } it 'makes two requests to the recorder with unique snapshot names' do expect(recorder).to receive(:snapshot).with(:_0, path: '/foo.html') expect(recorder).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 recorder' do expect(recorder).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 recorder' do expect(recorder).to receive(:snapshot).with(:_0, path: '/foo.html') expect(recorder).to receive(:snapshot).with(:_1, path: '/foo.htm') subject end end end context 'with :ext, with period' do context 'calling once' do subject { rule_context.write(ext: '.html') } it 'makes a request to the recorder' do expect(recorder).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 recorder' do expect(recorder).to receive(:snapshot).with(:_0, path: '/foo.html') expect(recorder).to receive(:snapshot).with(:_1, path: '/foo.htm') subject end end end context 'with nil' do subject { rule_context.write(nil) } it 'makes a request to the recorder' do expect(recorder).to receive(:snapshot).with(:_0, path: nil) subject end end end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nanoc-4.11.0/nanoc/spec/nanoc/rule_dsl/rule_spec.rb�������������������������������������������������0000664�0000000�0000000�00000010366�13400501750�0022141�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# frozen_string_literal: true shared_examples 'a generic rule' do subject(:rule) do described_class.new(pattern, :xml, block) end let(:pattern) { Nanoc::Int::Pattern.from(%r{/(.*)/(.*)/}) } let(:block) { proc {} } describe '#matches' do subject { rule.matches(identifier) } context 'does not match' do let(:identifier) { Nanoc::Identifier.new('/moo/', type: :legacy) } it { is_expected.to be_nil } end context 'matches' do let(:identifier) { Nanoc::Identifier.new('/foo/bar/', type: :legacy) } it { is_expected.to eql(%w[foo bar]) } end end describe '#initialize' do subject { rule } its(:rep_name) { is_expected.to eql(:xml) } its(:pattern) { is_expected.to eql(pattern) } end describe '#applicable_to?' do subject { rule.applicable_to?(item) } let(:item) { Nanoc::Int::Item.new('', {}, '/foo.md') } context 'pattern matches' do let(:pattern) { Nanoc::Int::Pattern.from(%r{^/foo.*}) } it { is_expected.to be } end context 'pattern does not match' do let(:pattern) { Nanoc::Int::Pattern.from(%r{^/bar.*}) } it { is_expected.not_to be } end end end shared_examples 'Rule#apply_to' do let(:block) do proc { self } end let(:item) { Nanoc::Int::Item.new('', {}, '/foo.md') } let(:rep) { Nanoc::Int::ItemRep.new(item, :amazings) } let(:site) { Nanoc::Int::Site.new(config: config, data_source: data_source, code_snippets: []) } let(:data_source) { Nanoc::Int::InMemDataSource.new(items, layouts) } let(:config) { Nanoc::Int::Configuration.new(dir: Dir.getwd) } let(:view_context) { Nanoc::ViewContextForPreCompilation.new(items: items) } let(:items) { Nanoc::Int::ItemCollection.new(config, []) } let(:layouts) { Nanoc::Int::LayoutCollection.new(config, []) } it 'makes rep accessible' do expect(subject.instance_eval { rep }._unwrap).to eql(rep) expect(subject.instance_eval { @rep }._unwrap).to eql(rep) end it 'makes item_rep accessible' do expect(subject.instance_eval { item_rep }._unwrap).to eql(rep) expect(subject.instance_eval { @item_rep }._unwrap).to eql(rep) end it 'makes item accessible' do expect(subject.instance_eval { item }._unwrap).to eql(item) expect(subject.instance_eval { @item }._unwrap).to eql(item) end it 'makes items accessible' do expect(subject.instance_eval { items }._unwrap).to eql(items) expect(subject.instance_eval { @items }._unwrap).to eql(items) end it 'makes layouts accessible' do expect(subject.instance_eval { layouts }._unwrap).to eql(layouts) expect(subject.instance_eval { @layouts }._unwrap).to eql(layouts) end it 'makes config accessible' do expect(subject.instance_eval { config }._unwrap).to eql(config) expect(subject.instance_eval { @config }._unwrap).to eql(config) end end describe Nanoc::RuleDSL::RoutingRule do subject(:rule) do described_class.new(pattern, :xml, block) end let(:pattern) { Nanoc::Int::Pattern.from(%r{/(.*)/(.*)/}) } let(:block) { proc {} } it_behaves_like 'a generic rule' describe '#initialize' do context 'without snapshot_name' do subject { described_class.new(pattern, :xml, proc {}) } its(:rep_name) { is_expected.to eql(:xml) } its(:pattern) { is_expected.to eql(pattern) } its(:snapshot_name) { is_expected.to be_nil } end context 'with snapshot_name' do subject { described_class.new(pattern, :xml, proc {}, snapshot_name: :donkey) } its(:rep_name) { is_expected.to eql(:xml) } its(:pattern) { is_expected.to eql(pattern) } its(:snapshot_name) { is_expected.to eql(:donkey) } end end describe '#apply_to' do subject { rule.apply_to(rep, site: site, view_context: view_context) } it_behaves_like 'Rule#apply_to' end end describe Nanoc::RuleDSL::CompilationRule do subject(:rule) do described_class.new(pattern, :xml, block) end let(:pattern) { Nanoc::Int::Pattern.from(%r{/(.*)/(.*)/}) } let(:block) { proc {} } it_behaves_like 'a generic rule' describe '#apply_to' do subject { rule.apply_to(rep, site: site, recorder: recorder, view_context: view_context) } let(:recorder) { Nanoc::RuleDSL::ActionRecorder.new(rep) } let(:rep) { nil } it_behaves_like 'Rule#apply_to' end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nanoc-4.11.0/nanoc/spec/nanoc/rule_dsl/rules_collection_spec.rb�������������������������������������0000664�0000000�0000000�00000017316�13400501750�0024541�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# 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::CompilationRule.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::CompilationRule.new(Nanoc::Int::Pattern.from('/foo.*'), :default, proc {}) end let(:rule_b) do Nanoc::RuleDSL::CompilationRule.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::CompilationRule.new(Nanoc::Int::Pattern.from('/foo.*'), :default, proc {}) end let(:rule_b) do Nanoc::RuleDSL::CompilationRule.new(Nanoc::Int::Pattern.from('/*.*'), :default, proc {}) end let(:rule_c) do Nanoc::RuleDSL::CompilationRule.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::CompilationRule.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::CompilationRule.new(Nanoc::Int::Pattern.from('/foo.*'), :default, proc {}) end let(:rule_b) do Nanoc::RuleDSL::CompilationRule.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::CompilationRule.new(Nanoc::Int::Pattern.from('/foo.*'), :default, proc {}) end let(:rule_b) do Nanoc::RuleDSL::CompilationRule.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::RoutingRule.new( Nanoc::Int::Pattern.from('/foo.*'), :default, proc {}, snapshot_name: :a ), Nanoc::RuleDSL::RoutingRule.new( Nanoc::Int::Pattern.from('/foo.*'), :default, proc {}, snapshot_name: :b ), # Matching item, non-matching rep Nanoc::RuleDSL::RoutingRule.new( Nanoc::Int::Pattern.from('/foo.*'), :raw, proc {}, snapshot_name: :a ), Nanoc::RuleDSL::RoutingRule.new( Nanoc::Int::Pattern.from('/foo.*'), :raw, proc {}, snapshot_name: :b ), # Non-matching item, matching rep Nanoc::RuleDSL::RoutingRule.new( Nanoc::Int::Pattern.from('/bar.*'), :default, proc {}, snapshot_name: :a ), Nanoc::RuleDSL::RoutingRule.new( Nanoc::Int::Pattern.from('/bar.*'), :default, proc {}, snapshot_name: :b ), # Non-matching item, non-matching rep Nanoc::RuleDSL::RoutingRule.new( Nanoc::Int::Pattern.from('/bar.*'), :raw, proc {}, snapshot_name: :a ), Nanoc::RuleDSL::RoutingRule.new( Nanoc::Int::Pattern.from('/bar.*'), :raw, proc {}, snapshot_name: :b ), # Matching item, matching rep, but not the first Nanoc::RuleDSL::RoutingRule.new( Nanoc::Int::Pattern.from('/*.*'), :default, proc {}, snapshot_name: :a ), Nanoc::RuleDSL::RoutingRule.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.11.0/nanoc/spec/nanoc/spec_spec.rb����������������������������������������������������������0000664�0000000�0000000�00000002702�13400501750�0020306�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# 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.11.0/nanoc/spec/regression_filenames_spec.rb������������������������������������������������0000664�0000000�0000000�00000001046�13400501750�0022461�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# 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.11.0/nanoc/spec/spec_helper.rb��������������������������������������������������������������0000664�0000000�0000000�00000000213�13400501750�0017530�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# frozen_string_literal: true require_relative '../../common/spec/spec_helper_head' require_relative '../../common/spec/spec_helper_foot' �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nanoc-4.11.0/nanoc/test/����������������������������������������������������������������������������0000775�0000000�0000000�00000000000�13400501750�0014743�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������nanoc-4.11.0/nanoc/test/base/�����������������������������������������������������������������������0000775�0000000�0000000�00000000000�13400501750�0015655�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������nanoc-4.11.0/nanoc/test/base/test_compiler.rb�������������������������������������������������������0000664�0000000�0000000�00000016015�13400501750�0021056�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# 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_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_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 end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nanoc-4.11.0/nanoc/test/base/test_site.rb�����������������������������������������������������������0000664�0000000�0000000�00000010415�13400501750�0020206�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# 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 Dir.getwd + '/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 Dir.getwd + '/public_html', site.config.output_dir end def test_initialize_with_incomplete_data_source_config File.open('nanoc.yaml', 'w') { |io| io.write('data_sources: [{ items_root: "/bar/" }]') } site = Nanoc::Int::SiteLoader.new.new_from_cwd 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 Dir.getwd + '/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.11.0/nanoc/test/checking/�������������������������������������������������������������������0000775�0000000�0000000�00000000000�13400501750�0016516�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������nanoc-4.11.0/nanoc/test/checking/checks/������������������������������������������������������������0000775�0000000�0000000�00000000000�13400501750�0017756�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������nanoc-4.11.0/nanoc/test/checking/checks/test_css.rb�������������������������������������������������0000664�0000000�0000000�00000003607�13400501750�0022140�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# frozen_string_literal: true require 'helper' class Nanoc::Checking::Checks::CSSTest < Nanoc::TestCase def test_run_ok VCR.use_cassette('css_run_ok') do with_site do |site| # Create files FileUtils.mkdir_p('output') File.open('output/blah.html', 'w') { |io| io.write('<h1>Hi!</h1>') } File.open('output/style.css', 'w') { |io| io.write('h1 { color: red; }') } # Run check check = Nanoc::Checking::Checks::CSS.create(site) check.run # Check assert check.issues.empty? end end end def test_run_error VCR.use_cassette('css_run_error') do with_site do |site| # Create files FileUtils.mkdir_p('output') File.open('output/blah.html', 'w') { |io| io.write('<h1>Hi!</h1>') } File.open('output/style.css', 'w') { |io| io.write('h1 { coxlor: rxed; }') } # Run check check = Nanoc::Checking::Checks::CSS.create(site) check.run # Check refute check.issues.empty? assert_equal 1, check.issues.size assert_equal( "line 1: Property “coxlor” doesn't exist. The closest matching property name is “color”: h1 { coxlor: rxed; }", check.issues.to_a[0].description, ) end end end def test_run_parse_error VCR.use_cassette('css_run_parse_error') do with_site do |site| # Create files FileUtils.mkdir_p('output') File.open('output/blah.html', 'w') { |io| io.write('<h1>Hi!</h1>') } File.open('output/style.css', 'w') { |io| io.write('h1 { ; {') } # Run check check = Nanoc::Checking::Checks::CSS.create(site) check.run # Check refute check.issues.empty? assert_equal 1, check.issues.size assert_equal 'line 1: Parse Error: h1 { ; {', check.issues.to_a[0].description end end end end �������������������������������������������������������������������������������������������������������������������������nanoc-4.11.0/nanoc/test/checking/checks/test_external_links.rb��������������������������������������0000664�0000000�0000000�00000005726�13400501750�0024376�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# frozen_string_literal: true require 'helper' class Nanoc::Checking::Checks::ExternalLinksTest < Nanoc::TestCase def test_run with_site do |site| # Create files FileUtils.mkdir_p('output') File.open('output/foo.txt', 'w') { |io| io.write('<a href="http://example.com/404">broken</a>') } File.open('output/bar.html', 'w') { |io| io.write('<a href="http://example.com/">not broken</a>') } # Create check check = Nanoc::Checking::Checks::ExternalLinks.create(site) def check.request_url_once(url) Net::HTTPResponse.new('1.1', url.path == '/' ? '200' : '404', 'okay') end check.run # Test assert check.issues.empty? end end def test_valid_by_path with_site do |site| # Create check check = Nanoc::Checking::Checks::ExternalLinks.create(site) def check.request_url_once(url) Net::HTTPResponse.new('1.1', url.path == '/200' ? '200' : '404', 'okay') end # Test assert_nil check.validate('http://127.0.0.1:9204/200') assert_nil check.validate('foo://example.com/') refute_nil check.validate('http://127.0.0.1:9204">') end end def test_valid_by_query with_site do |site| # Create check check = Nanoc::Checking::Checks::ExternalLinks.create(site) def check.request_url_once(url) Net::HTTPResponse.new('1.1', url.query == 'status=200' ? '200' : '404', 'okay') end # Test assert_nil check.validate('http://example.com/?status=200') refute_nil check.validate('http://example.com/?status=404') end end def test_path_for_url with_site do |site| check = Nanoc::Checking::Checks::ExternalLinks.create(site) assert_equal '/', check.send(:path_for_url, URI.parse('http://example.com')) assert_equal '/', check.send(:path_for_url, URI.parse('http://example.com/')) assert_equal '/?foo=bar', check.send(:path_for_url, URI.parse('http://example.com?foo=bar')) assert_equal '/?foo=bar', check.send(:path_for_url, URI.parse('http://example.com/?foo=bar')) assert_equal '/meow?foo=bar', check.send(:path_for_url, URI.parse('http://example.com/meow?foo=bar')) end end def test_excluded with_site do |site| site.config.update(checks: { external_links: { exclude: ['^http://excluded.com$'] } }) check = Nanoc::Checking::Checks::ExternalLinks.create(site) assert check.send(:excluded?, 'http://excluded.com') refute check.send(:excluded?, 'http://excluded.com/notexcluded') refute check.send(:excluded?, 'http://notexcluded.com') end end def test_excluded_file with_site do |site| site.config.update(checks: { external_links: { exclude_files: ['blog/page'] } }) check = Nanoc::Checking::Checks::ExternalLinks.create(site) assert check.send(:excluded_file?, 'output/blog/page1/index.html') refute check.send(:excluded_file?, 'output/blog/pag1/index.html') end end end ������������������������������������������nanoc-4.11.0/nanoc/test/checking/checks/test_html.rb������������������������������������������������0000664�0000000�0000000�00000003231�13400501750�0022305�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# frozen_string_literal: true require 'helper' class Nanoc::Checking::Checks::HTMLTest < Nanoc::TestCase def test_run_ok require 'w3c_validators' VCR.use_cassette('html_run_ok') do with_site do |site| # Create files FileUtils.mkdir_p('output') File.open('output/blah.html', 'w') { |io| io.write('<!DOCTYPE html><html><head><meta charset="utf-8"><title>Hello

Hi!

') } File.open('output/style.css', 'w') { |io| io.write('h1 { coxlor: rxed; }') } # Run check check = Nanoc::Checking::Checks::HTML.create(site) check.run # Check assert check.issues.empty? end end end def test_run_error VCR.use_cassette('html_run_error') do with_site do |site| # Create files FileUtils.mkdir_p('output') File.open('output/blah.html', 'w') { |io| io.write('

Hi!

') } File.open('output/style.css', 'w') { |io| io.write('h1 { coxlor: rxed; }') } # Run check check = Nanoc::Checking::Checks::HTML.create(site) check.run # Check refute check.issues.empty? assert_equal 3, check.issues.size assert_equal 'line 1: Start tag seen without seeing a doctype first. Expected e.g. “”.:

Hi!

', check.issues.to_a[0].description assert_equal 'line 1: Element “head” is missing a required instance of child element “title”.:

Hi!

', check.issues.to_a[1].description assert_equal 'line 1: End tag “h1” seen, but there were open elements.:

Hi!

', check.issues.to_a[2].description end end end end nanoc-4.11.0/nanoc/test/checking/checks/test_internal_links.rb000066400000000000000000000073061340050175000243640ustar00rootroot00000000000000# 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| site.config.update(checks: { internal_links: { exclude: ['^/excluded\d+'] } }) check = Nanoc::Checking::Checks::InternalLinks.create(site) 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| site.config.update(checks: { internal_links: { exclude_targets: ['^/excluded\d+'] } }) check = Nanoc::Checking::Checks::InternalLinks.create(site) 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| site.config.update(checks: { internal_links: { exclude_origins: ['^/excluded'] } }) check = Nanoc::Checking::Checks::InternalLinks.create(site) 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.11.0/nanoc/test/checking/checks/test_mixed_content.rb000066400000000000000000000145021340050175000242040ustar00rootroot00000000000000# frozen_string_literal: true require 'helper' class Nanoc::Checking::Checks::MixedContentTest < Nanoc::TestCase def create_output_file(name, lines) FileUtils.mkdir_p('output') File.open('output/' + name, 'w') do |io| io.write(lines.join('\n')) end end def assert_include(haystack, needle) assert haystack.include?(needle), "Expected to find '#{needle}' in #{haystack}" end def test_https_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_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.11.0/nanoc/test/checking/checks/test_stale.rb000066400000000000000000000040701340050175000224530ustar00rootroot00000000000000# frozen_string_literal: true require 'helper' class Nanoc::Checking::Checks::StaleTest < Nanoc::TestCase def check_class Nanoc::Checking::Checks::Stale end def calc_issues site = Nanoc::Int::SiteLoader.new.new_from_cwd runner = Nanoc::Checking::Runner.new(site) runner.send(:run_checks, [check_class]) end def test_run_ok with_site do |_site| assert Dir['content/*'].empty? assert Dir['output/*'].empty? # Empty FileUtils.mkdir_p('output') assert calc_issues.empty? # One OK file File.open('content/index.html', 'w') { |io| io.write('stuff') } File.open('output/index.html', 'w') { |io| io.write('stuff') } assert calc_issues.empty? end end def test_run_error with_site do |_site| assert Dir['content/*'].empty? assert Dir['output/*'].empty? File.open('content/index.html', 'w') { |io| io.write('stuff') } File.open('output/WRONG.html', 'w') { |io| io.write('stuff') } assert_equal 1, calc_issues.count issue = calc_issues.to_a[0] assert_equal 'file without matching item', issue.description assert_equal 'output/WRONG.html', issue.subject end end def test_run_excluded with_site do |_site| assert Dir['content/*'].empty? assert Dir['output/*'].empty? File.open('nanoc.yaml', 'w') { |io| io.write "string_pattern_type: legacy\nprune:\n exclude: [ 'excluded.html' ]" } File.open('content/index.html', 'w') { |io| io.write('stuff') } File.open('output/excluded.html', 'w') { |io| io.write('stuff') } assert calc_issues.empty? end end def test_run_excluded_with_broken_config with_site do |_site| assert Dir['content/*'].empty? assert Dir['output/*'].empty? File.open('nanoc.yaml', 'w') { |io| io.write "string_pattern_type: legacy\nprune:\n blah: meh" } File.open('content/index.html', 'w') { |io| io.write('stuff') } File.open('output/excluded.html', 'w') { |io| io.write('stuff') } assert_raises(JsonSchema::Error) { calc_issues } end end end nanoc-4.11.0/nanoc/test/checking/test_check.rb000066400000000000000000000011171340050175000211570ustar00rootroot00000000000000# frozen_string_literal: true require 'helper' class Nanoc::Checking::CheckTest < Nanoc::TestCase def test_output_filenames with_site do |site| File.open('output/foo.html', 'w') { |io| io.write 'hello' } check = Nanoc::Checking::Check.create(site) assert_equal [Dir.getwd + '/output/foo.html'], check.output_filenames end end def test_no_output_dir with_site do |site| site.config[:output_dir] = 'non-existent' assert_raises Nanoc::Checking::OutputDirNotFoundError do Nanoc::Checking::Check.create(site) end end end end nanoc-4.11.0/nanoc/test/checking/test_dsl.rb000066400000000000000000000016751340050175000206750ustar00rootroot00000000000000# 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") } enabled_checks = [] Nanoc::Checking::DSL.from_file('Checks', enabled_checks: enabled_checks) refute Nanoc::Checking::Check.named(:foo).nil? assert_equal [:bar], enabled_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', enabled_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', enabled_checks: []) assert(Pathname.new($stuff).absolute?) end end end nanoc-4.11.0/nanoc/test/checking/test_runner.rb000066400000000000000000000021351340050175000214140ustar00rootroot00000000000000# 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.11.0/nanoc/test/cli/000077500000000000000000000000001340050175000155125ustar00rootroot00000000000000nanoc-4.11.0/nanoc/test/cli/commands/000077500000000000000000000000001340050175000173135ustar00rootroot00000000000000nanoc-4.11.0/nanoc/test/cli/commands/test_check.rb000066400000000000000000000007261340050175000217610ustar00rootroot00000000000000# 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.11.0/nanoc/test/cli/commands/test_compile.rb000066400000000000000000000136001340050175000223270ustar00rootroot00000000000000# 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_file_action_printer_normal # Create data item = Nanoc::Int::Item.new('content', {}, '/a') 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_write_ended, 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', {}, '/a') 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.11.0/nanoc/test/cli/commands/test_create_site.rb000066400000000000000000000065031340050175000231720ustar00rootroot00000000000000# 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.11.0/nanoc/test/cli/commands/test_help.rb000066400000000000000000000002741340050175000216320ustar00rootroot00000000000000# 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.11.0/nanoc/test/cli/commands/test_prune.rb000066400000000000000000000121661340050175000220360ustar00rootroot00000000000000# 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 if Nanoc.on_windows? skip 'Symlinks to output dirs are currently not supported on Windows.' 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.11.0/nanoc/test/cli/test_cleaning_stream.rb000066400000000000000000000040061340050175000222310ustar00rootroot00000000000000# frozen_string_literal: true require 'helper' class Nanoc::CLI::CleaningStreamTest < Nanoc::TestCase class Stream attr_accessor :called_methods def initialize @called_methods = [] end def method_missing(symbol, *_args) # rubocop:disable Style/MethodMissingSuper @called_methods << symbol end 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(_str) 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 = [128].pack('C').force_encoding('UTF-8') stream = StringIO.new cleaning_stream = Nanoc::CLI::CleaningStream.new(stream) cleaning_stream << s assert_equal "\xef\xbf\xbd", stream.string end end nanoc-4.11.0/nanoc/test/cli/test_cli.rb000066400000000000000000000124141340050175000176470ustar00rootroot00000000000000# 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(/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.11.0/nanoc/test/cli/test_error_handler.rb000066400000000000000000000060621340050175000217300ustar00rootroot00000000000000# 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(/^1\. Add.*to your 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(/ lines omitted \(see crash\.log for details\)/, stream.string) stream = StringIO.new @handler.send(:write_stack_trace, stream, error, verbose: false) assert_match(/ lines omitted \(see crash\.log for details\)/, stream.string) stream = StringIO.new @handler.send(:write_stack_trace, stream, error, verbose: true) refute_match(/ lines omitted \(see 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(/^Current item: \/about\.md \(:latex representation\)$/, 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(/^1\. Add.*to your 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 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.11.0/nanoc/test/cli/test_logger.rb000066400000000000000000000001711340050175000203540ustar00rootroot00000000000000# frozen_string_literal: true require 'helper' class Nanoc::CLI::LoggerTest < Nanoc::TestCase def test_stub; end end nanoc-4.11.0/nanoc/test/data_sources/000077500000000000000000000000001340050175000174175ustar00rootroot00000000000000nanoc-4.11.0/nanoc/test/data_sources/test_filesystem.rb000066400000000000000000000611221340050175000231710ustar00rootroot00000000000000# frozen_string_literal: true require 'helper' class Nanoc::DataSources::FilesystemTest < Nanoc::TestCase def new_data_source(params = nil) with_site do |site| Nanoc::DataSources::Filesystem.new(site.config, nil, nil, params) end 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 end nanoc-4.11.0/nanoc/test/data_sources/test_filesystem_tools.rb000066400000000000000000000113251340050175000244110ustar00rootroot00000000000000# 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.11.0/nanoc/test/deploying/000077500000000000000000000000001340050175000167355ustar00rootroot00000000000000nanoc-4.11.0/nanoc/test/deploying/test_fog.rb000066400000000000000000000043751340050175000211050ustar00rootroot00000000000000# frozen_string_literal: true require 'helper' class Nanoc::Deploying::Deployers::FogTest < Nanoc::TestCase def test_read_etags_with_local_provider fog = Nanoc::Deploying::Deployers::Fog.new( 'output/', provider: 'local' ) files = [ mock('file_a'), mock('file_b'), ] assert_equal({}, fog.send(:read_etags, files)) end def test_read_etags_with_aws_provider fog = Nanoc::Deploying::Deployers::Fog.new( 'output/', provider: 'aws' ) files = [ mock('file_a', key: 'key_a', etag: 'etag_a'), mock('file_b', key: 'key_b', etag: 'etag_b'), ] expected = { 'key_a' => 'etag_a', 'key_b' => 'etag_b', } assert_equal(expected, fog.send(:read_etags, files)) end def test_calc_local_etag_with_local_provider fog = Nanoc::Deploying::Deployers::Fog.new( 'output/', provider: 'local' ) file_path = 'blah.tmp' File.write(file_path, 'hallo') assert_nil fog.send(:calc_local_etag, file_path) end def test_calc_local_etag_with_aws_provider fog = Nanoc::Deploying::Deployers::Fog.new( 'output/', provider: 'aws' ) file_path = 'blah.tmp' File.write(file_path, 'hallo') assert_equal( '598d4c200461b81522a3328565c25f7c', fog.send(:calc_local_etag, file_path), ) end def test_needs_upload_with_missing_remote_etag fog = Nanoc::Deploying::Deployers::Fog.new( 'output/', provider: 'aws' ) file_path = 'blah.tmp' File.write(file_path, 'hallo') key = 'some_key' etags = {} assert fog.send(:needs_upload?, key, file_path, etags) end def test_needs_upload_with_different_etags fog = Nanoc::Deploying::Deployers::Fog.new( 'output/', provider: 'aws' ) file_path = 'blah.tmp' File.write(file_path, 'hallo') key = 'some_key' etags = { key => 'some_etag' } assert fog.send(:needs_upload?, key, file_path, etags) end def test_needs_upload_with_identical_etags fog = Nanoc::Deploying::Deployers::Fog.new( 'output/', provider: 'aws' ) file_path = 'blah.tmp' File.write(file_path, 'hallo') key = 'some_key' etags = { key => '598d4c200461b81522a3328565c25f7c' } refute fog.send(:needs_upload?, key, file_path, etags) end end nanoc-4.11.0/nanoc/test/deploying/test_git.rb000066400000000000000000000144331340050175000211110ustar00rootroot00000000000000# frozen_string_literal: true class Nanoc::Deploying::Deployers::GitTest < Nanoc::TestCase def test_run_with_defaults_options # Create deployer git = Nanoc::Deploying::Deployers::Git.new( 'output/', {} ) # Mock run_cmd def git.run_cmd(args, _opts = {}) @shell_cmd_args = [] unless defined? @shell_cmd_args @shell_cmd_args << args.join(' ') end # Mock clean_repo? def git.clean_repo? false end # Create output dir + repo FileUtils.mkdir_p('output') Dir.chdir('output') { system('git', 'init', '--quiet') } # Try running git.run commands = <<~EOS git config --get remote.origin.url git checkout master git add -A git commit -a --author Nanoc <> -m Automated commit at .+ by Nanoc \\d+\\.\\d+\\.\\d+\\w* git push origin master EOS assert_match Regexp.new(/^#{commands.chomp}$/), (git.instance_eval { @shell_cmd_args.join("\n") }) end def test_run_with_clean_repository # Create deployer git = Nanoc::Deploying::Deployers::Git.new( 'output/', {} ) # Mock run_cmd def git.run_cmd(args, _opts = {}) @shell_cmd_args = [] unless defined? @shell_cmd_args @shell_cmd_args << args.join(' ') end # Mock clean_repo? def git.clean_repo? true end # Create output dir + repo FileUtils.mkdir_p('output') Dir.chdir('output') { system('git', 'init', '--quiet') } # Try running git.run commands = <<~EOS git config --get remote.origin.url git checkout master EOS assert_match Regexp.new(/^#{commands.chomp}$/), (git.instance_eval { @shell_cmd_args.join("\n") }) end def test_run_with_custom_options # Create deployer git = Nanoc::Deploying::Deployers::Git.new( 'output/', remote: 'github', branch: 'gh-pages', forced: true, ) # Mock run_cmd def git.run_cmd(args, _opts = {}) @shell_cmd_args = [] unless defined? @shell_cmd_args @shell_cmd_args << args.join(' ') end # Mock clean_repo? def git.clean_repo? false end # Create output dir + repo FileUtils.mkdir_p('output') Dir.chdir('output') { system('git', 'init', '--quiet') } # Try running git.run commands = <<~EOS git config --get remote.github.url git checkout gh-pages git add -A git commit -a --author Nanoc <> -m Automated commit at .+ by Nanoc \\d+\\.\\d+\\.\\d+\\w* git push -f github gh-pages EOS assert_match Regexp.new(/^#{commands.chomp}$/), (git.instance_eval { @shell_cmd_args.join("\n") }) end def test_run_without_git_init # Create deployer git = Nanoc::Deploying::Deployers::Git.new( 'output/', {} ) # Mock run_cmd def git.run_cmd(args, _opts = {}) @shell_cmd_args = [] unless defined? @shell_cmd_args @shell_cmd_args << args.join(' ') end # Mock clean_repo? def git.clean_repo? false end # Create site FileUtils.mkdir_p('output/.git') # Try running git.run commands = <<~EOS git config --get remote.origin.url git checkout master git add -A git commit -a --author Nanoc <> -m Automated commit at .+ by Nanoc \\d+\\.\\d+\\.\\d+\\w* git push origin master EOS assert_match Regexp.new(/^#{commands.chomp}$/), (git.instance_eval { @shell_cmd_args.join("\n") }) end def test_run_with_ssh_url # Create deployer git = Nanoc::Deploying::Deployers::Git.new( 'output/', remote: 'git@github.com:myself/myproject.git', ) # Mock run_cmd def git.run_cmd(args, _opts = {}) @shell_cmd_args = [] unless defined? @shell_cmd_args @shell_cmd_args << args.join(' ') end # Mock clean_repo? def git.clean_repo? false end # Create output dir + repo FileUtils.mkdir_p('output') Dir.chdir('output') { system('git', 'init', '--quiet') } # Try running git.run commands = <<~EOS git checkout master git add -A git commit -a --author Nanoc <> -m Automated commit at .+ by Nanoc \\d+\\.\\d+\\.\\d+\\w* git push git@github.com:myself/myproject.git master EOS assert_match Regexp.new(/^#{commands.chomp}$/), (git.instance_eval { @shell_cmd_args.join("\n") }) end def test_run_with_http_url # Create deployer git = Nanoc::Deploying::Deployers::Git.new( 'output/', remote: 'https://github.com/nanoc/nanoc.git', ) # Mock run_cmd def git.run_cmd(args, _opts = {}) @shell_cmd_args = [] unless defined? @shell_cmd_args @shell_cmd_args << args.join(' ') end # Mock clean_repo? def git.clean_repo? false end # Create output dir + repo FileUtils.mkdir_p('output') Dir.chdir('output') { system('git', 'init', '--quiet') } # Try running git.run commands = <<~EOS git checkout master git add -A git commit -a --author Nanoc <> -m Automated commit at .+ by Nanoc \\d+\\.\\d+\\.\\d+\\w* git push https://github.com/nanoc/nanoc.git master EOS assert_match Regexp.new(/^#{commands.chomp}$/), (git.instance_eval { @shell_cmd_args.join("\n") }) end def test_clean_repo_on_a_clean_repo # Create deployer git = Nanoc::Deploying::Deployers::Git.new( 'output/', remote: 'https://github.com/nanoc/nanoc.git', ) FileUtils.mkdir_p('output') piper = Nanoc::Extra::Piper.new(stdout: $stdout, stderr: $stderr) Dir.chdir('output') do piper.run('git init', nil) assert git.send(:clean_repo?) end end def test_clean_repo_on_a_dirty_repo # Create deployer git = Nanoc::Deploying::Deployers::Git.new( 'output/', remote: 'https://github.com/nanoc/nanoc.git', ) FileUtils.mkdir_p('output') piper = Nanoc::Extra::Piper.new(stdout: $stdout, stderr: $stderr) Dir.chdir('output') do piper.run('git init', nil) FileUtils.touch('foobar') refute git.send(:clean_repo?) end end def test_clean_repo_not_git_repo # Create deployer git = Nanoc::Deploying::Deployers::Git.new( 'output/', remote: 'https://github.com/nanoc/nanoc.git', ) FileUtils.mkdir_p('output') Dir.chdir('output') do assert_raises Nanoc::Extra::Piper::Error do git.send(:clean_repo?) end end end end nanoc-4.11.0/nanoc/test/deploying/test_rsync.rb000066400000000000000000000037571340050175000214730ustar00rootroot00000000000000# frozen_string_literal: true require 'helper' class Nanoc::Deploying::Deployers::RsyncTest < Nanoc::TestCase def setup super skip_unless_have_command 'rsync' end def test_run_without_dst # Create deployer rsync = Nanoc::Deploying::Deployers::Rsync.new( 'output/', {}, ) # Mock run_shell_cmd def rsync.run_shell_cmd(args) @shell_cms_args = args end # Try running error = assert_raises(RuntimeError) do rsync.run end # Check error message assert_equal 'No dst found in deployment configuration', error.message end def test_run_with_erroneous_dst # Create deployer rsync = Nanoc::Deploying::Deployers::Rsync.new( 'output/', dst: 'asdf/', ) # Mock run_shell_cmd def rsync.run_shell_cmd(args) @shell_cms_args = args end # Try running error = assert_raises(RuntimeError) do rsync.run end # Check error message assert_equal 'dst requires no trailing slash', error.message end def test_run_everything_okay # Create deployer rsync = Nanoc::Deploying::Deployers::Rsync.new( 'output', dst: 'asdf', ) # Mock run_shell_cmd def rsync.run_shell_cmd(args) @shell_cms_args = args end # Run rsync.run # Check args opts = Nanoc::Deploying::Deployers::Rsync::DEFAULT_OPTIONS assert_equal( ['rsync', opts, 'output/', 'asdf'].flatten, rsync.instance_eval { @shell_cms_args }, ) end def test_run_everything_okay_dry # Create deployer rsync = Nanoc::Deploying::Deployers::Rsync.new( 'output', { dst: 'asdf' }, dry_run: true, ) # Mock run_shell_cmd def rsync.run_shell_cmd(args) @shell_cms_args = args end # Run rsync.run # Check args opts = Nanoc::Deploying::Deployers::Rsync::DEFAULT_OPTIONS assert_equal( ['echo', 'rsync', opts, 'output/', 'asdf'].flatten, rsync.instance_eval { @shell_cms_args }, ) end end nanoc-4.11.0/nanoc/test/extra/000077500000000000000000000000001340050175000160665ustar00rootroot00000000000000nanoc-4.11.0/nanoc/test/extra/core_ext/000077500000000000000000000000001340050175000176765ustar00rootroot00000000000000nanoc-4.11.0/nanoc/test/extra/core_ext/test_time.rb000066400000000000000000000010251340050175000222160ustar00rootroot00000000000000# frozen_string_literal: true require 'helper' class Nanoc::ExtraCoreExtTimeTest < Nanoc::TestCase def test___nanoc_to_iso8601_date_utc assert_equal('2008-05-19', Time.utc(2008, 5, 19, 14, 20, 0, 0).__nanoc_to_iso8601_date) end def test___nanoc_to_iso8601_date_non_utc assert_equal('2008-05-18', Time.new(2008, 5, 19, 0, 0, 0, '+02:00').__nanoc_to_iso8601_date) end def test___nanoc_to_iso8601_time assert_equal('2008-05-19T14:20:00Z', Time.utc(2008, 5, 19, 14, 20, 0, 0).__nanoc_to_iso8601_time) end end nanoc-4.11.0/nanoc/test/extra/test_link_collector.rb000066400000000000000000000061021340050175000224540ustar00rootroot00000000000000# frozen_string_literal: true require 'helper' class Nanoc::Extra::LinkCollectorTest < Nanoc::TestCase def test_all # Create dummy data File.open('file-a.html', 'w') do |io| io << %(A 1 ) io << %(A 2 ) io << %( ) io << %(A 4) io << %(A 5 ) end File.open('file-b.html', 'w') do |io| io << %(B 1 ) io << %(B 2 ) io << %(B 3 ) end # Create validator collector = Nanoc::Extra::LinkCollector.new(%w[file-a.html file-b.html]) # Test hrefs_with_filenames = collector.filenames_per_href hrefs = hrefs_with_filenames.keys assert_includes hrefs, 'http://example.com/' assert_includes hrefs, 'https://example.com/' assert_includes hrefs, 'stuff/' refute_includes hrefs, nil assert_includes hrefs, 'mailto:bob@example.com' assert_includes hrefs, '../stuff' assert_includes hrefs, '/stuff' refute_includes hrefs, 'https://example.com/with-fragment#moo' assert_includes hrefs, 'https://example.com/with-fragment' end def test_external # Create dummy data File.open('file-a.html', 'w') do |io| io << %(A 1 ) io << %(A 2 ) io << %( ) end File.open('file-b.html', 'w') do |io| io << %(B 1 ) io << %(B 2 ) io << %(B 3 ) end # Create validator collector = Nanoc::Extra::LinkCollector.new(%w[file-a.html file-b.html], :external) # Test hrefs_with_filenames = collector.filenames_per_href hrefs = hrefs_with_filenames.keys assert_includes hrefs, 'http://example.com/' assert_includes hrefs, 'https://example.com/' refute_includes hrefs, 'stuff/' assert_includes hrefs, 'mailto:bob@example.com' refute_includes hrefs, '../stuff' refute_includes hrefs, '/stuff' end def test_internal # Create dummy data File.open('file-a.html', 'w') do |io| io << %(A 1 ) io << %(A 2 ) io << %( ) end File.open('file-b.html', 'w') do |io| io << %(B 1 ) io << %(B 2 ) io << %(B 3 ) end # Create validator collector = Nanoc::Extra::LinkCollector.new(%w[file-a.html file-b.html], :internal) # Test hrefs_with_filenames = collector.filenames_per_href hrefs = hrefs_with_filenames.keys refute_includes hrefs, 'http://example.com/' refute_includes hrefs, 'https://example.com/' assert_includes hrefs, 'stuff/' refute_includes hrefs, 'mailto:bob@example.com' assert_includes hrefs, '../stuff' assert_includes hrefs, '/stuff' end end nanoc-4.11.0/nanoc/test/extra/test_piper.rb000066400000000000000000000021361340050175000205730ustar00rootroot00000000000000# frozen_string_literal: true require 'helper' class Nanoc::Extra::PiperTest < Nanoc::TestCase def test_basic stdout = StringIO.new stderr = StringIO.new cmd = %w[ls -l] File.open('foo.txt', 'w') { |io| io.write('hi') } File.open('bar.txt', 'w') { |io| io.write('ho') } piper = Nanoc::Extra::Piper.new(stdout: stdout, stderr: stderr) piper.run(cmd, nil) assert_match(/foo\.txt/, stdout.string) assert_match(/bar\.txt/, stdout.string) assert stderr.string.empty? end def test_stdin stdout = StringIO.new stderr = StringIO.new input = 'Hello World!' cmd = %w[cat] piper = Nanoc::Extra::Piper.new(stdout: stdout, stderr: stderr) piper.run(cmd, input) assert_equal(input, stdout.string) assert_equal('', stderr.string) end def test_no_such_command stdout = StringIO.new stderr = StringIO.new cmd = %w[cat kafhawilgoiwaejagoualjdsfilofiewaguihaifeowuiga] piper = Nanoc::Extra::Piper.new(stdout: stdout, stderr: stderr) assert_raises(Nanoc::Extra::Piper::Error) do piper.run(cmd, nil) end end end nanoc-4.11.0/nanoc/test/filters/000077500000000000000000000000001340050175000164135ustar00rootroot00000000000000nanoc-4.11.0/nanoc/test/filters/colorize_syntax/000077500000000000000000000000001340050175000216475ustar00rootroot00000000000000nanoc-4.11.0/nanoc/test/filters/colorize_syntax/test_coderay.rb000066400000000000000000000164021340050175000246640ustar00rootroot00000000000000# frozen_string_literal: true require 'helper' class Nanoc::Filters::ColorizeSyntax::CoderayTest < Nanoc::TestCase CODERAY_PRE = '
' CODERAY_POST = '
' def test_coderay_simple if_have '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 '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 '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 '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 '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 '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 '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 '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 '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 '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 '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 '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 '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.11.0/nanoc/test/filters/colorize_syntax/test_common.rb000066400000000000000000000065761340050175000245410ustar00rootroot00000000000000# 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 skip_unless_have 'nokogumbo' # 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.11.0/nanoc/test/filters/colorize_syntax/test_pygmentize.rb000066400000000000000000000024321340050175000254270ustar00rootroot00000000000000# 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.11.0/nanoc/test/filters/colorize_syntax/test_pygments.rb000066400000000000000000000013401340050175000250770ustar00rootroot00000000000000# 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.11.0/nanoc/test/filters/colorize_syntax/test_simon.rb000066400000000000000000000013061340050175000243600ustar00rootroot00000000000000# 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.11.0/nanoc/test/filters/test_bluecloth.rb000066400000000000000000000011061340050175000217560ustar00rootroot00000000000000# frozen_string_literal: true require 'helper' class Nanoc::Filters::BlueClothTest < Nanoc::TestCase def test_filter skip_unless_have 'bluecloth' # Skip if nonfunctional begin ::BlueCloth.new('# hi').to_html rescue ArgumentError => e skip 'BlueCloth is broken on this platform' if e.message.include?('wrong number of arguments') end # Create filter filter = ::Nanoc::Filters::BlueCloth.new # Run filter result = filter.setup_and_run('> Quote') assert_match %r{
\s*

Quote

\s*
}, result end end nanoc-4.11.0/nanoc/test/filters/test_coffeescript.rb000066400000000000000000000006361340050175000224600ustar00rootroot00000000000000# 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.11.0/nanoc/test/filters/test_erubi.rb000066400000000000000000000051151340050175000211070ustar00rootroot00000000000000# frozen_string_literal: true require 'helper' class Nanoc::Filters::ErubiTest < Nanoc::TestCase def test_filter_with_instance_variable # Create filter filter = ::Nanoc::Filters::Erubi.new(location: 'a cheap motel') # Run filter result = filter.setup_and_run('<%= "I was hiding in #{@location}." %>') # rubocop:disable Lint/InterpolationCheck assert_equal('I was hiding in a cheap motel.', result) end def test_filter_with_instance_method # Create filter filter = ::Nanoc::Filters::Erubi.new(location: 'a cheap motel') # Run filter result = filter.setup_and_run('<%= "I was hiding in #{location}." %>') # rubocop:disable Lint/InterpolationCheck assert_equal('I was hiding in a cheap motel.', result) end def test_filter_syntax_error # Create filter item = Nanoc::Int::Item.new('asdf', {}, '/about.md') item_rep = Nanoc::Int::ItemRep.new(item, :xml) filter = ::Nanoc::Filters::Erubi.new(item: item, item_rep: item_rep) # Run filter raised = false begin filter.setup_and_run('<%= this isn\'t really ruby so it\'ll break, muahaha %>') rescue SyntaxError => e assert_match('item /about.md (rep xml):1: syntax error, unexpected tIDENTIFIER', e.to_s) assert_match 'syntax error', e.message raised = true end assert raised end def test_filter_regular_error # Create filter item = Nanoc::Int::Item.new('asdf', {}, '/about.md') item_rep = Nanoc::Int::ItemRep.new(item, :xml) filter = ::Nanoc::Filters::Erubi.new(item: item, item_rep: item_rep) # Run filter raised = false begin filter.setup_and_run('<%= undefined_method_2ff04e22 %>') rescue => e assert_match 'item /about.md (rep xml):1', e.backtrace.join("\n") raised = true end assert raised end def test_filter_with_yield # Create filter filter = ::Nanoc::Filters::Erubi.new(content: 'a cheap motel') # Run filter result = filter.setup_and_run('<%= "I was hiding in #{yield}." %>') # rubocop:disable Lint/InterpolationCheck assert_equal('I was hiding in a cheap motel.', result) end def test_filter_with_yield_without_content # 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}." %>') # rubocop:disable Lint/InterpolationCheck end end def test_filter_with_erbout filter = ::Nanoc::Filters::Erubi.new result = filter.setup_and_run('stuff<% _erbout << _erbout %>') assert_equal 'stuffstuff', result end end nanoc-4.11.0/nanoc/test/filters/test_erubis.rb000066400000000000000000000036371340050175000213010ustar00rootroot00000000000000# frozen_string_literal: true require 'helper' class Nanoc::Filters::ErubisTest < Nanoc::TestCase def test_filter_with_instance_variable # Create filter filter = ::Nanoc::Filters::Erubis.new(location: 'a cheap motel') # Run filter result = filter.setup_and_run('<%= "I was hiding in #{@location}." %>') # rubocop:disable Lint/InterpolationCheck assert_equal('I was hiding in a cheap motel.', result) end def test_filter_with_instance_method # Create filter filter = ::Nanoc::Filters::Erubis.new(location: 'a cheap motel') # Run filter result = filter.setup_and_run('<%= "I was hiding in #{location}." %>') # rubocop:disable Lint/InterpolationCheck assert_equal('I was hiding in a cheap motel.', result) end def test_filter_error # Create filter filter = ::Nanoc::Filters::Erubis.new # 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 '?', Regexp.last_match[1] raised = true end assert raised end def test_filter_with_yield # Create filter filter = ::Nanoc::Filters::Erubis.new(content: 'a cheap motel') # Run filter result = filter.setup_and_run('<%= "I was hiding in #{yield}." %>') # rubocop:disable Lint/InterpolationCheck assert_equal('I was hiding in a cheap motel.', result) end def test_filter_with_yield_without_content # Create filter filter = ::Nanoc::Filters::Erubis.new(location: 'a cheap motel') # Run filter assert_raises LocalJumpError do filter.setup_and_run('<%= "I was hiding in #{yield}." %>') # rubocop:disable Lint/InterpolationCheck end end def test_filter_with_erbout filter = ::Nanoc::Filters::Erubis.new result = filter.setup_and_run('stuff<% _erbout << _erbout %>') assert_equal 'stuffstuff', result end end nanoc-4.11.0/nanoc/test/filters/test_haml.rb000066400000000000000000000043451340050175000207260ustar00rootroot00000000000000# frozen_string_literal: true require 'helper' class Nanoc::Filters::HamlTest < Nanoc::TestCase def test_filter # Create filter filter = ::Nanoc::Filters::Haml.new(question: 'Is this the Payne residence?') # Run filter (no assigns) result = filter.setup_and_run('%html') assert_match(/.*<\/html>/, result) # Run filter (assigns without @) result = filter.setup_and_run('%p= question') assert_equal("

Is this the Payne residence?

\n", result) # Run filter (assigns with @) result = filter.setup_and_run('%p= @question') assert_equal("

Is this the Payne residence?

\n", result) end def test_filter_with_params # Create filter filter = ::Nanoc::Filters::Haml.new(foo: 'bar') # Check with HTML5 result = filter.setup_and_run('%img', format: :html5) assert_match(//, result) # Check with XHTML result = filter.setup_and_run('%img', format: :xhtml) assert_match(//, result) end def test_filter_error # Create filter filter = ::Nanoc::Filters::Haml.new(foo: 'bar') # Run filter raised = false begin filter.setup_and_run('%p= this isn\'t really ruby so it\'ll break, muahaha') rescue SyntaxError, Haml::SyntaxError => e e.message =~ /(.+?):\d+: / assert_match '?', Regexp.last_match[1] raised = true end assert raised end def test_filter_with_yield # Create filter filter = ::Nanoc::Filters::Haml.new(content: 'Is this the Payne residence?') # Run filter result = filter.setup_and_run('%p= yield') assert_equal("

Is this the Payne residence?

\n", result) end def test_filter_with_yield_without_content # Create filter filter = ::Nanoc::Filters::Haml.new(location: 'Is this the Payne residence?') # Run filter assert_raises LocalJumpError do filter.setup_and_run('%p= yield') end end def test_filter_with_proper_indentation # Create file to include File.open('stuff', 'w') do |io| io.write("
Max Payne\nMona Sax
") end # Run filter filter = ::Nanoc::Filters::Haml.new result = filter.setup_and_run("%body\n ~ File.read('stuff')") assert_match(/Max Payne Mona Sax/, result) end end nanoc-4.11.0/nanoc/test/filters/test_handlebars.rb000066400000000000000000000034221340050175000221030ustar00rootroot00000000000000# frozen_string_literal: true require 'helper' class Nanoc::Filters::HandlebarsTest < Nanoc::TestCase def test_filter if_have 'handlebars' do # Create data item = Nanoc::Int::Item.new( 'content', { title: 'Max Payne', protagonist: 'Max Payne', location: 'here' }, '/games/max-payne', ) layout = Nanoc::Int::Layout.new( 'layout content', { name: 'Max Payne' }, '/default', ) config = { animals: 'cats and dogs' } # Create filter assigns = { item: item, layout: layout, config: config, content: 'No Payne No Gayne', } filter = ::Nanoc::Filters::Handlebars.new(assigns) # Run filter result = filter.setup_and_run('{{protagonist}} says: {{yield}}.') assert_equal('Max Payne says: No Payne No Gayne.', result) result = filter.setup_and_run('We can’t stop {{item.location}}! This is the {{layout.name}} layout!') assert_equal('We can’t stop here! This is the Max Payne layout!', result) result = filter.setup_and_run('It’s raining {{config.animals}} here!') assert_equal('It’s raining cats and dogs here!', result) end end def test_filter_without_layout if_have 'handlebars' do # Create data item = Nanoc::Int::Item.new( 'content', { title: 'Max Payne', protagonist: 'Max Payne', location: 'here' }, '/games/max-payne', ) # Create filter assigns = { item: item, content: 'No Payne No Gayne', } filter = ::Nanoc::Filters::Handlebars.new(assigns) # Run filter result = filter.setup_and_run('{{protagonist}} says: {{yield}}.') assert_equal('Max Payne says: No Payne No Gayne.', result) end end end nanoc-4.11.0/nanoc/test/filters/test_kramdown.rb000066400000000000000000000032651340050175000216270ustar00rootroot00000000000000# frozen_string_literal: true require 'helper' class Nanoc::Filters::KramdownTest < Nanoc::TestCase def test_filter # 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 def test_warnings # Create item item = Nanoc::Int::Item.new('foo', {}, '/foo.md') item_view = Nanoc::CompilationItemView.new(item, nil) item_rep = Nanoc::Int::ItemRep.new(item, :default) item_rep_view = Nanoc::CompilationItemRepView.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 def test_warning_filters # Create item item = Nanoc::Int::Item.new('foo', {}, '/foo.md') item_view = Nanoc::CompilationItemView.new(item, nil) item_rep = Nanoc::Int::ItemRep.new(item, :default) item_rep_view = Nanoc::CompilationItemRepView.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 nanoc-4.11.0/nanoc/test/filters/test_markaby.rb000066400000000000000000000004561340050175000214320ustar00rootroot00000000000000# frozen_string_literal: true require 'helper' class Nanoc::Filters::MarkabyTest < Nanoc::TestCase def test_filter # Create filter filter = ::Nanoc::Filters::Markaby.new # Run filter result = filter.setup_and_run("html do\nend") assert_equal('', result) end end nanoc-4.11.0/nanoc/test/filters/test_maruku.rb000066400000000000000000000005261340050175000213060ustar00rootroot00000000000000# frozen_string_literal: true require 'helper' class Nanoc::Filters::MarukuTest < Nanoc::TestCase def test_filter # 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 nanoc-4.11.0/nanoc/test/filters/test_mustache.rb000066400000000000000000000017271340050175000216170ustar00rootroot00000000000000# frozen_string_literal: true require 'helper' class Nanoc::Filters::MustacheTest < Nanoc::TestCase def test_filter # 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 def test_filter_with_yield # 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 nanoc-4.11.0/nanoc/test/filters/test_pandoc.rb000066400000000000000000000022611340050175000212440ustar00rootroot00000000000000# 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 }, 'wrap=none', :toc] result = filter.setup_and_run("# Heading\n", args: args) assert_match '