pax_global_header00006660000000000000000000000064147203333460014517gustar00rootroot0000000000000052 comment=bbdf2727979471515b83bb5cb1fe2d1ed1f09174 nanoc-4.13.3/000077500000000000000000000000001472033334600127055ustar00rootroot00000000000000nanoc-4.13.3/.github/000077500000000000000000000000001472033334600142455ustar00rootroot00000000000000nanoc-4.13.3/.github/CONTRIBUTING.md000066400000000000000000000021271472033334600165000ustar00rootroot00000000000000Contributing ============ 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](https://nanoc.app/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](https://nanoc.app/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 https://nanoc.app/contributing/#contributor-code-of-conduct. nanoc-4.13.3/.github/FUNDING.yml000066400000000000000000000000531472033334600160600ustar00rootroot00000000000000ko_fi: denisdefreyne github: denisdefreyne nanoc-4.13.3/.github/ISSUE_TEMPLATE.md000066400000000000000000000006541472033334600167570ustar00rootroot00000000000000### 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.13.3/.github/PULL_REQUEST_TEMPLATE.md000066400000000000000000000003761472033334600200540ustar00rootroot00000000000000### 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.13.3/.github/dependabot.yml000066400000000000000000000002371472033334600170770ustar00rootroot00000000000000# Set update schedule for GitHub Actions version: 2 updates: - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" nanoc-4.13.3/.github/workflows/000077500000000000000000000000001472033334600163025ustar00rootroot00000000000000nanoc-4.13.3/.github/workflows/ruby.yml000066400000000000000000000035301472033334600200070ustar00rootroot00000000000000name: Nanoc on: push: branches: - main pull_request: permissions: contents: read jobs: test: runs-on: ubuntu-latest strategy: fail-fast: false matrix: ruby-version: ["3.1", "3.2", "3.3"] steps: - uses: actions/checkout@v4 - uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby-version }} rubygems: 3.4.9 # https://github.com/ruby/psych/discussions/607 bundler-cache: true - name: Test style if: ${{ matrix.ruby-version == '3.3' }} run: bundle exec rake rubocop - name: Test nanoc-core run: bundle exec rake nanoc_core:test timeout-minutes: 3 - name: Test nanoc run: bundle exec rake nanoc:test timeout-minutes: 3 - name: Test nanoc-cli run: bundle exec rake nanoc_cli:test timeout-minutes: 3 - name: Test nanoc-checking run: bundle exec rake nanoc_checking:test timeout-minutes: 3 - name: Test nanoc-dart-sass run: bundle exec rake nanoc_dart_sass:test timeout-minutes: 3 - name: Test nanoc-deploying run: bundle exec rake nanoc_deploying:test timeout-minutes: 3 - name: Test nanoc-external run: bundle exec rake nanoc_external:test timeout-minutes: 3 - name: Test nanoc-org-mode run: bundle exec rake nanoc_org_mode:test timeout-minutes: 3 - name: Test nanoc-live run: bundle exec rake nanoc_live:test timeout-minutes: 3 - name: Test nanoc-tilt run: bundle exec rake nanoc_tilt:test timeout-minutes: 3 - name: Test nanoc-spec run: bundle exec rake nanoc_spec:test timeout-minutes: 3 - name: Test guard-nanoc run: bundle exec rake guard_nanoc:test timeout-minutes: 3 nanoc-4.13.3/.gitignore000066400000000000000000000001341472033334600146730ustar00rootroot00000000000000.DS_Store *.gem *.gemfile.lock *~ coverage/ /.yardoc/ /Gemfile.lock /doc/yardoc/ /.vscode/ nanoc-4.13.3/.rubocop.yml000066400000000000000000000445751472033334600151760ustar00rootroot00000000000000inherit_from: .rubocop_todo.yml # ----- CONFIGURED ----- AllCops: TargetRubyVersion: 3.1 require: - rubocop-minitest - rubocop-rake - rubocop-rspec # We use filenames such as “create-site.rb” that translate to method names. Naming/FileName: Exclude: - "nanoc/lib/nanoc/orig_cli/commands/*.rb" - "nanoc-cli/lib/nanoc/cli/commands/*.rb" - "nanoc-*/lib/nanoc-*.rb" - "nanoc-dart-sass/lib/nanoc/dart-sass.rb" - "nanoc-org-mode/lib/nanoc/org-mode.rb" - "guard-nanoc/lib/guard-nanoc.rb" - "*/*.gemspec" Style/TrailingCommaInArguments: EnforcedStyleForMultiline: comma Style/TrailingCommaInArrayLiteral: EnforcedStyleForMultiline: comma Style/TrailingCommaInHashLiteral: EnforcedStyleForMultiline: comma Layout/FirstArrayElementIndentation: EnforcedStyle: consistent # Having the leading underscore makes it clear that the instance variable is not # intended to be used directly. Naming/MemoizedInstanceVariableName: EnforcedStyleForLeadingUnderscores: required Naming/MethodParameterName: AllowedNames: - "to" # destination - "e" # element - "fn" # filename - "id" # identifier - "io" # IO instance - "y" # enumerator yielder # Some specs deal with things that aren’t classes. RSpec/DescribeClass: Exclude: - "*/spec/gem_spec.rb" - "*/spec/manifest_spec.rb" - "*/spec/meta_spec.rb" - "nanoc/spec/contributors_spec.rb" - "nanoc/spec/nanoc/integration/*_spec.rb" - "nanoc/spec/nanoc/regressions/*_spec.rb" - "nanoc/spec/regression_filenames_spec.rb" # ----- CONFIGURED (enabled non-default cops) ----- Layout/ClassStructure: Enabled: true Include: - "nanoc-core/**/*.rb" Lint/RaiseException: Enabled: true Lint/StructNewOverride: Enabled: true Style/AccessorGrouping: Enabled: true EnforcedStyle: separated Style/HashEachMethods: Enabled: true Style/HashTransformKeys: Enabled: true Style/HashTransformValues: Enabled: true # ----- 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" # This interferes with variables named `focus`. RSpec/Focus: Enabled: false # ----- ENABLED (new cops not enabled by default) ----- Layout/EmptyLinesAroundAttributeAccessor: Enabled: true Layout/SpaceAroundMethodCallOperator: Enabled: true Lint/DeprecatedOpenSSLConstant: Enabled: true Style/ExponentialNotation: Enabled: true Style/SlicingWithRange: Enabled: true Style/RedundantRegexpCharacterClass: Enabled: true Style/RedundantRegexpEscape: Enabled: true Lint/BinaryOperatorWithIdenticalOperands: Enabled: true Lint/DuplicateElsifCondition: Enabled: true Lint/DuplicateRescueException: Enabled: true Lint/EmptyConditionalBody: Enabled: true Lint/FloatComparison: Enabled: true Lint/MissingSuper: Enabled: true Lint/OutOfRangeRegexpRef: Enabled: true Lint/SelfAssignment: Enabled: true Lint/TopLevelReturnWithArgument: Enabled: true Lint/UnreachableLoop: Enabled: true Style/ArrayCoercion: Enabled: true Style/BisectedAttrAccessor: Enabled: true Style/CaseLikeIf: Enabled: true Style/ExplicitBlockArgument: Enabled: true Style/GlobalStdStream: Enabled: true Style/HashAsLastArrayItem: Enabled: true Style/HashLikeCase: Enabled: true Style/OptionalBooleanParameter: Enabled: true Style/RedundantAssignment: Enabled: true Style/RedundantFetchBlock: Enabled: true Style/RedundantFileExtensionInRequire: Enabled: true Style/SingleArgumentDig: Enabled: true Style/StringConcatenation: Enabled: true Layout/BeginEndAlignment: Enabled: true Lint/ConstantDefinitionInBlock: Enabled: true Lint/DuplicateRequire: Enabled: true Lint/EmptyFile: Enabled: true Lint/HashCompareByIdentity: Enabled: true Lint/IdentityComparison: Enabled: true Lint/RedundantSafeNavigation: Enabled: true Lint/TrailingCommaInAttributeDeclaration: Enabled: true Lint/UselessMethodDefinition: Enabled: true Lint/UselessTimes: Enabled: true Style/ClassEqualityComparison: Enabled: true Style/CombinableLoops: Enabled: true Style/KeywordParametersOrder: Enabled: true Style/RedundantSelfAssignment: Enabled: true Style/SoleNestedConditional: Enabled: true Lint/DuplicateBranch: # (new in 1.3) Enabled: true Lint/DuplicateRegexpCharacterClassElement: # (new in 1.1) Enabled: true Lint/EmptyBlock: # (new in 1.1) Enabled: true Lint/EmptyClass: # (new in 1.3) Enabled: true Lint/NoReturnInBeginEndBlocks: # (new in 1.2) Enabled: true Lint/ToEnumArguments: # (new in 1.1) Enabled: true Lint/UnexpectedBlockArity: # (new in 1.5) Enabled: true Lint/UnmodifiedReduceAccumulator: # (new in 1.1) Enabled: true Style/ArgumentsForwarding: # (new in 1.1) Enabled: true Style/CollectionCompact: # (new in 1.2) Enabled: true Style/DocumentDynamicEvalDefinition: # (new in 1.1) Enabled: true Style/NegatedIfElseCondition: # (new in 1.2) Enabled: true Style/NilLambda: # (new in 1.3) Enabled: true Style/RedundantArgument: # (new in 1.4) Enabled: true Style/SwapValues: # (new in 1.1) Enabled: true Minitest/AssertInDelta: # (new in 0.10) Enabled: true Minitest/AssertionInLifecycleHook: # (new in 0.10) Enabled: true Minitest/AssertKindOf: # (new in 0.10) Enabled: true Minitest/AssertOutput: # (new in 0.10) Enabled: true Minitest/AssertPathExists: # (new in 0.10) Enabled: true Minitest/AssertSilent: # (new in 0.10) Enabled: true Minitest/LiteralAsActualArgument: # (new in 0.10) Enabled: true Minitest/MultipleAssertions: # (new in 0.10) Enabled: true Minitest/RefuteInDelta: # (new in 0.10) Enabled: true Minitest/RefuteKindOf: # (new in 0.10) Enabled: true Minitest/RefutePathExists: # (new in 0.10) Enabled: true Minitest/TestMethodName: # (new in 0.10) Enabled: true Minitest/UnspecifiedException: # (new in 0.10) Enabled: true Layout/SpaceBeforeBrackets: # (new in 1.7) Enabled: true Lint/AmbiguousAssignment: # (new in 1.7) Enabled: true Style/HashExcept: # (new in 1.7) Enabled: true Lint/DeprecatedConstants: # (new in 1.8) Enabled: true Lint/LambdaWithoutLiteralBlock: # (new in 1.8) Enabled: true Lint/RedundantDirGlobSort: # (new in 1.8) Enabled: true Style/EndlessMethod: # (new in 1.8) Enabled: true Lint/NumberedParameterAssignment: # (new in 1.9) Enabled: true Lint/OrAssignmentToConstant: # (new in 1.9) Enabled: true Lint/SymbolConversion: # (new in 1.9) Enabled: true Lint/TripleQuotes: # (new in 1.9) Enabled: true Style/HashConversion: # (new in 1.10) Enabled: true Style/IfWithBooleanLiteralBranches: # (new in 1.9) Enabled: true Style/StringChars: # (new in 1.12) Enabled: true Minitest/AssertWithExpectedArgument: # (new in 0.11) Enabled: true Gemspec/RequireMFA: # new in 1.23 Enabled: true Lint/UselessRuby2Keywords: # new in 1.23 Enabled: true Naming/BlockForwarding: # new in 1.24 Enabled: true Style/FileRead: # new in 1.24 Enabled: true Style/FileWrite: # new in 1.24 Enabled: true Style/MapToHash: # new in 1.24 Enabled: true Style/OpenStructUse: # new in 1.23 Enabled: true Gemspec/DeprecatedAttributeAssignment: # new in 1.30 Enabled: true Lint/RefinementImportMethods: # new in 1.27 Enabled: true Security/CompoundHash: # new in 1.28 Enabled: true Style/EnvHome: # new in 1.29 Enabled: true Style/FetchEnvVar: # new in 1.28 Enabled: true Style/MapCompactWithConditionalBlock: # new in 1.30 Enabled: true Style/NestedFileDirname: # new in 1.26 Enabled: true Style/ObjectThen: # new in 1.28 Enabled: true Style/RedundantInitialize: # new in 1.27 Enabled: true Minitest/AssertPredicate: # new in 0.18 Enabled: true Minitest/DuplicateTestRun: # new in 0.19 Enabled: true Minitest/RefutePredicate: # new in 0.18 Enabled: true Minitest/SkipEnsure: # new in 0.20 Enabled: true RSpec/BeEq: # new in 2.9.0 Enabled: true RSpec/BeNil: # new in 2.9.0 Enabled: true RSpec/ChangeByZero: # new in 2.11.0 Enabled: true RSpec/VerifiedDoubleReference: # new in 2.10.0 Enabled: true Layout/LineContinuationLeadingSpace: # new in 1.31 Enabled: true Layout/LineContinuationSpacing: # new in 1.31 Enabled: true Lint/ConstantOverwrittenInRescue: # new in 1.31 Enabled: true Lint/NonAtomicFileOperation: # new in 1.31 Enabled: true Lint/RequireRangeParentheses: # new in 1.32 Enabled: true Style/EmptyHeredoc: # new in 1.32 Enabled: true Style/MagicCommentFormat: # new in 1.35 Enabled: true Minitest/AssertRaisesCompoundBody: # new in 0.21 Enabled: true Minitest/AssertRaisesWithRegexpArgument: # new in 0.22 Enabled: true RSpec/ClassCheck: # new in 2.13 Enabled: true RSpec/NoExpectationExample: # new in 2.13 Enabled: true Lint/DuplicateMagicComment: # new in 1.37 Enabled: true Style/OperatorMethodCall: # new in 1.37 Enabled: true Style/RedundantStringEscape: # new in 1.37 Enabled: true RSpec/SortMetadata: # new in 2.14 Enabled: true Minitest/EmptyLineBeforeAssertionMethods: # new in 0.23 Enabled: true Style/RedundantEach: # new in 1.38 Enabled: true Minitest/SkipWithoutReason: # new in 0.24 Enabled: true Style/ArrayIntersect: # new in 1.40 Enabled: true Style/RedundantConstantBase: # new in 1.40 Enabled: true RSpec/DuplicatedMetadata: # new in 2.16 Enabled: true RSpec/PendingWithoutReason: # new in 2.16 Enabled: true Gemspec/DevelopmentDependencies: # new in 1.44 Enabled: true Lint/UselessRescue: # new in 1.43 Enabled: true Style/ComparableClamp: # new in 1.44 Enabled: true Style/ConcatArrayLiterals: # new in 1.41 Enabled: true Style/MapToSet: # new in 1.42 Enabled: true Style/MinMaxComparison: # new in 1.42 Enabled: true Style/RedundantDoubleSplatHashBraces: # new in 1.41 Enabled: true Minitest/AssertSame: # new in 0.26 Enabled: true Minitest/RefuteSame: # new in 0.26 Enabled: true Minitest/TestFileName: # new in 0.26 Enabled: true Minitest/UselessAssertion: # new in 0.26 Enabled: true Minitest/NonPublicTestMethod: # new in 0.27 Enabled: true Lint/DuplicateMatchPattern: # new in 1.50 Enabled: true Metrics/CollectionLiteralLength: # new in 1.47 Enabled: true Style/DataInheritance: # new in 1.49 Enabled: true Style/DirEmpty: # new in 1.48 Enabled: true Style/ExactRegexpMatch: # new in 1.51 Enabled: true Style/FileEmpty: # new in 1.48 Enabled: true Style/RedundantArrayConstructor: # new in 1.52 Enabled: true Style/RedundantFilterChain: # new in 1.52 Enabled: true Style/RedundantHeredocDelimiterQuotes: # new in 1.45 Enabled: true Style/RedundantLineContinuation: # new in 1.49 Enabled: true Style/RedundantRegexpConstructor: # new in 1.52 Enabled: true Minitest/LifecycleHooksOrder: # new in 0.28 Enabled: true Minitest/ReturnInTestMethod: # new in 0.31 Enabled: true RSpec/BeEmpty: # new in 2.20 Enabled: true RSpec/ContainExactly: # new in 2.19 Enabled: true RSpec/IndexedLet: # new in 2.20 Enabled: true RSpec/MatchArray: # new in 2.19 Enabled: true RSpec/RedundantAround: # new in 2.19 Enabled: true RSpec/SkipBlockInsideExample: # new in 2.19 Enabled: true Lint/MixedCaseRange: # new in 1.53 Enabled: true Lint/RedundantRegexpQuantifiers: # new in 1.53 Enabled: true Style/RedundantCurrentDirectoryInPath: # new in 1.53 Enabled: true Style/RedundantRegexpArgument: # new in 1.53 Enabled: true Style/ReturnNilInPredicateMethodDefinition: # new in 1.53 Enabled: true Style/YAMLFileRead: # new in 1.53 Enabled: true RSpec/ReceiveMessages: # new in 2.23 Enabled: true Style/SingleLineDoEndBlock: # new in 1.57 Enabled: true RSpec/EmptyMetadata: # new in 2.24 Enabled: true RSpec/Eq: # new in 2.24 Enabled: true RSpec/SpecFilePathSuffix: # new in 2.24 Enabled: true Lint/LiteralAssignmentInCondition: # new in 1.58 Enabled: true Style/SuperWithArgsParentheses: # new in 1.58 Enabled: true Lint/UselessNumericOperation: # new in 1.66 Enabled: true Style/RedundantInterpolationUnfreeze: # new in 1.66 Enabled: true RSpec/StringAsInstanceDoubleConstant: # new in 3.1 Enabled: true # ----- DISABLED (new cops not enabled by default; to enable later) ----- Layout/LineEndStringConcatenationIndentation: # new in 1.18 Enabled: false Lint/AmbiguousOperatorPrecedence: # new in 1.21 Enabled: false Lint/AmbiguousRange: # new in 1.19 Enabled: false Lint/EmptyInPattern: # new in 1.16 Enabled: false Lint/IncompatibleIoSelectWithFiberScheduler: # new in 1.21 Enabled: false Lint/RequireRelativeSelfPath: # new in 1.22 Enabled: false Security/IoMethods: # new in 1.22 Enabled: false Style/InPatternThen: # new in 1.16 Enabled: false Style/MultilineInPatternThen: # new in 1.16 Enabled: false Style/NumberedParameters: # new in 1.22 Enabled: false Style/NumberedParametersLimit: # new in 1.22 Enabled: false Style/QuotedSymbols: # new in 1.16 Enabled: false Style/RedundantSelfAssignmentBranch: # new in 1.19 Enabled: false Style/SelectByRegexp: # new in 1.22 Enabled: false Minitest/UnreachableAssertion: # new in 0.14 Enabled: false RSpec/ExcessiveDocstringSpacing: # new in 2.5 Enabled: false RSpec/IdenticalEqualityAssertion: # new in 2.4 Enabled: false RSpec/SubjectDeclaration: # new in 2.5 Enabled: false RSpec/MetadataStyle: # new in 2.24 Enabled: false Minitest/AssertOperator: # new in 0.32 Enabled: false RSpec/SpecFilePathFormat: # new in 2.24 Enabled: false Minitest/RefuteOperator: # new in 0.32 Enabled: false Lint/ItWithoutArgumentsInBlock: # new in 1.59 Enabled: true Minitest/NonExecutableTestMethod: # new in 0.34 Enabled: true Minitest/RedundantMessageArgument: # new in 0.34 Enabled: true RSpec/RedundantPredicateMatcher: # new in 2.26 Enabled: true RSpec/RemoveConst: # new in 2.26 Enabled: true Minitest/Focus: # new in 0.35 Enabled: true RSpec/IsExpectedSpecify: # new in 2.27 Enabled: true RSpec/RepeatedSubjectCall: # new in 2.27 Enabled: true RSpec/EmptyOutput: # new in 2.29 Enabled: true RSpec/UndescriptiveLiteralsDescription: # new in 2.29 Enabled: true Style/SendWithLiteralMethodName: # new in 1.64 Enabled: true Style/SuperArguments: # new in 1.64 Enabled: true RSpec/ExpectInLet: # new in 2.30 Enabled: true Gemspec/AddRuntimeDependency: # new in 1.65 Enabled: true Lint/DuplicateSetElement: # new in 1.67 Enabled: true Lint/UnescapedBracketInRegexp: # new in 1.68 Enabled: true Style/AmbiguousEndlessMethodDefinition: # new in 1.68 Enabled: true Style/BitwisePredicate: # new in 1.68 Enabled: true Style/CombinableDefined: # new in 1.68 Enabled: true Style/KeywordArgumentsMerging: # new in 1.68 Enabled: true Style/SafeNavigationChainLength: # new in 1.68 Enabled: true # ----- TO ENABLE LATER ----- # Valid cops, but fixing the offenses they report is non-trivial. Style/RegexpLiteral: Enabled: false Style/EmptyElse: Enabled: false Style/Next: Enabled: false Naming/HeredocDelimiterNaming: Enabled: false Style/EvalWithLocation: Enabled: false Layout/LineLength: Enabled: false Lint/MixedRegexpCaptureTypes: 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/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/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/SuppressedException: 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 # Both {} and [] are considered empty, but it’s still important to check whether # something is {} rather than [] or vice versa. Minitest/AssertEmptyLiteral: Enabled: false # ----- DISABLED (buggy) ----- # This cop attempts to replace Nanoc::VERSION with described_class, which makes # the spec fail. RSpec/DescribedClass: Exclude: - "**/version_spec.rb" # This turns # # expect(File.directory?('output')).to be_falsy # # into # # expect(File).not_to be_directory('output') # # … which does not look right. RSpec/PredicateMatcher: Exclude: - "nanoc-core/spec/nanoc/core/item_rep_writer_spec.rb" # This yields false positives; a class that responds to #each does not # necessarily also respond to #map. Style/MapIntoArray: Enabled: false nanoc-4.13.3/.rubocop_todo.yml000066400000000000000000001105271472033334600162120ustar00rootroot00000000000000# This configuration was generated by # `rubocop --auto-gen-config --exclude-limit 300` # on 2024-06-06 09:17:53 UTC using RuboCop version 1.64.1. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. # Offense count: 1 Lint/BinaryOperatorWithIdenticalOperands: Exclude: - 'nanoc-core/spec/nanoc/core/checksummer_spec.rb' # Offense count: 5 # Configuration parameters: IgnoreLiteralBranches, IgnoreConstantBranches. Lint/DuplicateBranch: Exclude: - 'nanoc-checking/lib/nanoc/checking/command_runners/check.rb' - 'nanoc-cli/lib/nanoc/cli/compile_listeners/file_action_printer.rb' - 'nanoc-core/lib/nanoc/core/dependency_props.rb' - 'nanoc/lib/nanoc/filters/relativize_paths.rb' # Offense count: 15 # Configuration parameters: AllowComments, AllowEmptyLambdas. Lint/EmptyBlock: Exclude: - 'common/spec/spec_helper_foot_core.rb' - 'nanoc-core/spec/nanoc/core/action_sequence_spec.rb' - 'nanoc-core/spec/nanoc/core/compilation_phases/abstract_spec.rb' - 'nanoc-core/spec/nanoc/core/item_rep_builder_spec.rb' - 'nanoc-core/spec/nanoc/core/support/identifiable_collection_view_examples.rb' - 'nanoc-core/spec/nanoc/core/support/item_rep_collection_view_examples.rb' - 'nanoc-spec/spec/nanoc/spec/helper_context_spec.rb' - 'nanoc/spec/nanoc/spec_spec.rb' - 'nanoc/test/helper.rb' - 'nanoc/test/rule_dsl/test_compiler_dsl.rb' # Offense count: 1 # Configuration parameters: AllowComments. Lint/EmptyClass: Exclude: - 'guard-nanoc/spec/spec_helper.rb' # Offense count: 5 Lint/FormatParameterMismatch: Exclude: - 'Rakefile' - 'nanoc-cli/lib/nanoc/cli/commands/show-data.rb' - 'nanoc-cli/lib/nanoc/cli/commands/show-plugins.rb' # Offense count: 1 Lint/HashCompareByIdentity: Exclude: - 'nanoc/lib/nanoc/filters/sass/importer.rb' # Offense count: 58 # Configuration parameters: AllowedParentClasses. Lint/MissingSuper: Exclude: - 'nanoc-checking/spec/nanoc/checking/check_spec.rb' - 'nanoc-cli/lib/nanoc/cli/compile_listeners/aggregate.rb' - 'nanoc-cli/lib/nanoc/cli/compile_listeners/file_action_printer.rb' - 'nanoc-cli/lib/nanoc/cli/compile_listeners/timing_recorder.rb' - 'nanoc-cli/spec/nanoc/cli/compile_listeners/abstract_spec.rb' - 'nanoc-core/lib/nanoc/core/assertions.rb' - 'nanoc-core/lib/nanoc/core/compilation_stages/build_reps.rb' - 'nanoc-core/lib/nanoc/core/compilation_stages/calculate_checksums.rb' - 'nanoc-core/lib/nanoc/core/compilation_stages/cleanup.rb' - 'nanoc-core/lib/nanoc/core/compilation_stages/compile_reps.rb' - 'nanoc-core/lib/nanoc/core/compilation_stages/determine_outdatedness.rb' - 'nanoc-core/lib/nanoc/core/compilation_stages/forget_outdated_dependencies.rb' - 'nanoc-core/lib/nanoc/core/compilation_stages/load_stores.rb' - 'nanoc-core/lib/nanoc/core/compilation_stages/postprocess.rb' - 'nanoc-core/lib/nanoc/core/compilation_stages/preprocess.rb' - 'nanoc-core/lib/nanoc/core/compilation_stages/prune.rb' - 'nanoc-core/lib/nanoc/core/compilation_stages/store_post_compilation_state.rb' - 'nanoc-core/lib/nanoc/core/compilation_stages/store_pre_compilation_state.rb' - 'nanoc-core/lib/nanoc/core/compiled_content_cache.rb' - 'nanoc-core/lib/nanoc/core/contracts_support.rb' - 'nanoc-core/lib/nanoc/core/dependency_tracker.rb' - 'nanoc-core/lib/nanoc/core/errors.rb' - 'nanoc-core/lib/nanoc/core/item_collection.rb' - 'nanoc-core/lib/nanoc/core/layout_collection.rb' - 'nanoc-core/lib/nanoc/core/mutable_document_view_mixin.rb' - 'nanoc-core/lib/nanoc/core/processing_actions/filter.rb' - 'nanoc-core/lib/nanoc/core/processing_actions/layout.rb' - 'nanoc-core/lib/nanoc/core/processing_actions/snapshot.rb' - 'nanoc-core/lib/nanoc/core/regexp_pattern.rb' - 'nanoc-core/lib/nanoc/core/string_pattern.rb' - 'nanoc-core/spec/nanoc/core/compilation_item_rep_view_spec.rb' - 'nanoc-core/spec/nanoc/core/compilation_item_view_spec.rb' - 'nanoc-core/spec/nanoc/core/compilation_phases/cache_spec.rb' - 'nanoc-core/spec/nanoc/core/compilation_stages/compile_reps_spec.rb' - 'nanoc-core/spec/nanoc/core/compilation_stages/preprocess_spec.rb' - 'nanoc-core/spec/nanoc/core/compiler_spec.rb' - 'nanoc-core/spec/nanoc/core/config_view_spec.rb' - 'nanoc-core/spec/nanoc/core/executor_spec.rb' - 'nanoc-core/spec/nanoc/core/filter_spec.rb' - 'nanoc-core/spec/nanoc/core/item_rep_router_spec.rb' - 'nanoc-core/spec/nanoc/core/post_compile_item_rep_view_spec.rb' - 'nanoc-core/spec/nanoc/core/post_compile_item_view_spec.rb' - 'nanoc-core/spec/nanoc/core/support/document_view_examples.rb' - 'nanoc-core/spec/nanoc/core/support/identifiable_collection_view_examples.rb' - 'nanoc-core/spec/nanoc/core/support/item_rep_view_examples.rb' - 'nanoc-core/spec/nanoc/core/support/mutable_document_view_examples.rb' - 'nanoc-spec/lib/nanoc/spec.rb' - 'nanoc/lib/nanoc/helpers/breadcrumbs.rb' - 'nanoc/lib/nanoc/rule_dsl/action_provider.rb' - 'nanoc/spec/nanoc/filters/sass_spec.rb' - 'nanoc/spec/nanoc/orig_cli/commands/show_rules_spec.rb' - 'nanoc/test/filters/test_slim.rb' - 'nanoc/test/filters/test_xsl.rb' - 'nanoc/test/helpers/test_blogging.rb' - 'nanoc/test/helpers/test_capturing.rb' - 'nanoc/test/helpers/test_xml_sitemap.rb' # Offense count: 8 # This cop supports unsafe autocorrection (--autocorrect-all). Lint/RedundantDirGlobSort: Exclude: - 'nanoc-cli/lib/nanoc/cli.rb' - 'nanoc-core/lib/nanoc/core/site_loader.rb' - 'nanoc-core/spec/nanoc/core/compilation_stages/cleanup_spec.rb' - 'nanoc-deploying/spec/nanoc/deploying/deployers/fog_spec.rb' # Offense count: 1 # This cop supports unsafe autocorrection (--autocorrect-all). Lint/RedundantRequireStatement: Exclude: - 'nanoc-core/lib/nanoc/core.rb' # Offense count: 2 # Configuration parameters: AllowedPatterns. # AllowedPatterns: (?-mix:(exactly|at_least|at_most)\(\d+\)\.times) Lint/UnreachableLoop: Exclude: - 'nanoc-core/spec/nanoc/core/item_rep_selector_spec.rb' # Offense count: 40 Minitest/MultipleAssertions: Max: 18 # Offense count: 1 # Configuration parameters: Severity. Minitest/SkipEnsure: Exclude: - 'nanoc/test/orig_cli/commands/test_create_site.rb' # Offense count: 9 # Configuration parameters: EnforcedStyle, CheckMethodNames, CheckSymbols, AllowedIdentifiers, AllowedPatterns. # SupportedStyles: snake_case, normalcase, non_integer # AllowedIdentifiers: capture3, iso8601, rfc1123_date, rfc822, rfc2822, rfc3339, x86_64 Naming/VariableNumber: Exclude: - 'nanoc-checking/spec/nanoc/checking/check_spec.rb' - 'nanoc-core/spec/nanoc/core/aggregate_data_source_spec.rb' - 'nanoc/spec/nanoc/rule_dsl/action_sequence_calculator_spec.rb' # Offense count: 5 RSpec/AnyInstance: Exclude: - 'nanoc-cli/spec/nanoc/cli/commands/shell_spec.rb' - 'nanoc/spec/nanoc/regressions/gh_1572_spec.rb' # Offense count: 55 RSpec/Be: Exclude: - 'nanoc-cli/spec/nanoc/cli/compile_listeners/abstract_spec.rb' - 'nanoc-cli/spec/nanoc/cli/compile_listeners/diff_generator_spec.rb' - 'nanoc-cli/spec/nanoc/cli_spec.rb' - 'nanoc-deploying/spec/nanoc/deploying/deployers/git_spec.rb' - 'nanoc/spec/nanoc/integration/compile_command_spec.rb' - 'nanoc/spec/nanoc/integration/partial_recompilation_spec.rb' - 'nanoc/spec/nanoc/integration/write_nil_spec.rb' - 'nanoc/spec/nanoc/regressions/gh_1015_spec.rb' - 'nanoc/spec/nanoc/regressions/gh_1022_spec.rb' - 'nanoc/spec/nanoc/regressions/gh_1031_spec.rb' - 'nanoc/spec/nanoc/regressions/gh_1045_spec.rb' - 'nanoc/spec/nanoc/regressions/gh_1134_spec.rb' - 'nanoc/spec/nanoc/regressions/gh_828_spec.rb' - 'nanoc/spec/nanoc/rule_dsl/rule_spec.rb' # Offense count: 1172 # Configuration parameters: Prefixes, AllowedPatterns. # Prefixes: when, with, without RSpec/ContextWording: Exclude: - 'nanoc-checking/spec/nanoc/checking/check_spec.rb' - 'nanoc-checking/spec/nanoc/checking/checks/external_links_spec.rb' - 'nanoc-checking/spec/nanoc/checking/command_runners/check_spec.rb' - 'nanoc-checking/spec/nanoc/checking/runner_spec.rb' - 'nanoc-cli/spec/nanoc/cli/command_runner_spec.rb' - 'nanoc-cli/spec/nanoc/cli/commands/show_data_spec.rb' - 'nanoc-cli/spec/nanoc/cli/commands/show_plugins_spec.rb' - 'nanoc-cli/spec/nanoc/cli/commands/view_spec.rb' - 'nanoc-cli/spec/nanoc/cli/compile_listeners/abstract_spec.rb' - 'nanoc-cli/spec/nanoc/cli/compile_listeners/diff_generator_spec.rb' - 'nanoc-cli/spec/nanoc/cli/compile_listeners/file_action_printer_spec.rb' - 'nanoc-cli/spec/nanoc/cli/error_handler_spec.rb' - 'nanoc-cli/spec/nanoc/cli/stack_trace_writer_spec.rb' - 'nanoc-cli/spec/nanoc/cli_spec.rb' - 'nanoc-core/spec/nanoc/core/action_sequence_builder_spec.rb' - 'nanoc-core/spec/nanoc/core/action_sequence_spec.rb' - 'nanoc-core/spec/nanoc/core/action_sequence_store_spec.rb' - 'nanoc-core/spec/nanoc/core/basic_outdatedness_checker_spec.rb' - 'nanoc-core/spec/nanoc/core/binary_compiled_content_cache_spec.rb' - 'nanoc-core/spec/nanoc/core/checksum_store_spec.rb' - 'nanoc-core/spec/nanoc/core/checksummer_spec.rb' - 'nanoc-core/spec/nanoc/core/compilation_item_rep_view_spec.rb' - 'nanoc-core/spec/nanoc/core/compilation_item_view_spec.rb' - 'nanoc-core/spec/nanoc/core/compilation_phases/cache_spec.rb' - 'nanoc-core/spec/nanoc/core/compilation_phases/resume_spec.rb' - 'nanoc-core/spec/nanoc/core/compilation_stage_spec.rb' - 'nanoc-core/spec/nanoc/core/compilation_stages/cleanup_spec.rb' - 'nanoc-core/spec/nanoc/core/compilation_stages/compile_reps_spec.rb' - 'nanoc-core/spec/nanoc/core/compilation_stages/determine_outdatedness_spec.rb' - 'nanoc-core/spec/nanoc/core/compilation_stages/preprocess_spec.rb' - 'nanoc-core/spec/nanoc/core/compiled_content_cache_spec.rb' - 'nanoc-core/spec/nanoc/core/compiled_content_store_spec.rb' - 'nanoc-core/spec/nanoc/core/compiler_spec.rb' - 'nanoc-core/spec/nanoc/core/config_loader_spec.rb' - 'nanoc-core/spec/nanoc/core/config_view_spec.rb' - 'nanoc-core/spec/nanoc/core/configuration_spec.rb' - 'nanoc-core/spec/nanoc/core/content_spec.rb' - 'nanoc-core/spec/nanoc/core/dependency_props_spec.rb' - 'nanoc-core/spec/nanoc/core/dependency_store_spec.rb' - 'nanoc-core/spec/nanoc/core/dependency_tracker_spec.rb' - 'nanoc-core/spec/nanoc/core/directed_graph_spec.rb' - 'nanoc-core/spec/nanoc/core/document_spec.rb' - 'nanoc-core/spec/nanoc/core/executor_spec.rb' - 'nanoc-core/spec/nanoc/core/feature_spec.rb' - 'nanoc-core/spec/nanoc/core/filter_spec.rb' - 'nanoc-core/spec/nanoc/core/identifier_spec.rb' - 'nanoc-core/spec/nanoc/core/item_rep_builder_spec.rb' - 'nanoc-core/spec/nanoc/core/item_rep_router_spec.rb' - 'nanoc-core/spec/nanoc/core/item_rep_selector_spec.rb' - 'nanoc-core/spec/nanoc/core/item_rep_spec.rb' - 'nanoc-core/spec/nanoc/core/item_rep_writer_spec.rb' - 'nanoc-core/spec/nanoc/core/lazy_value_spec.rb' - 'nanoc-core/spec/nanoc/core/outdatedness_rules_spec.rb' - 'nanoc-core/spec/nanoc/core/outdatedness_status_spec.rb' - 'nanoc-core/spec/nanoc/core/outdatedness_store_spec.rb' - 'nanoc-core/spec/nanoc/core/post_compile_item_rep_view_spec.rb' - 'nanoc-core/spec/nanoc/core/processing_actions/filter_spec.rb' - 'nanoc-core/spec/nanoc/core/processing_actions/layout_spec.rb' - 'nanoc-core/spec/nanoc/core/processing_actions/snapshot_spec.rb' - 'nanoc-core/spec/nanoc/core/pruner_spec.rb' - 'nanoc-core/spec/nanoc/core/site_loader_spec.rb' - 'nanoc-core/spec/nanoc/core/store_spec.rb' - 'nanoc-core/spec/nanoc/core/support/document_view_examples.rb' - 'nanoc-core/spec/nanoc/core/support/identifiable_collection_view_examples.rb' - 'nanoc-core/spec/nanoc/core/support/item_rep_collection_view_examples.rb' - 'nanoc-core/spec/nanoc/core/support/item_rep_view_examples.rb' - 'nanoc-core/spec/nanoc/core/support/mutable_document_view_examples.rb' - 'nanoc-core/spec/nanoc/core/temp_filename_factory_spec.rb' - 'nanoc-core/spec/nanoc/core/textual_compiled_content_cache_spec.rb' - 'nanoc-deploying/spec/nanoc/deploying/command_runners/deploy_spec.rb' - 'nanoc-deploying/spec/nanoc/deploying/deployers/fog_spec.rb' - 'nanoc-deploying/spec/nanoc/deploying/deployers/git_spec.rb' - 'nanoc-deploying/spec/nanoc/deploying/deployers/rsync_spec.rb' - 'nanoc/spec/nanoc/core/checksummer_spec.rb' - 'nanoc/spec/nanoc/data_sources/filesystem/parser_spec.rb' - 'nanoc/spec/nanoc/data_sources/filesystem/tools_spec.rb' - 'nanoc/spec/nanoc/data_sources/filesystem_spec.rb' - 'nanoc/spec/nanoc/filters/erb_spec.rb' - 'nanoc/spec/nanoc/filters/less_spec.rb' - 'nanoc/spec/nanoc/filters/relativize_paths_spec.rb' - 'nanoc/spec/nanoc/filters/sass_spec.rb' - 'nanoc/spec/nanoc/helpers/blogging_spec.rb' - 'nanoc/spec/nanoc/helpers/breadcrumbs_spec.rb' - 'nanoc/spec/nanoc/helpers/capturing_spec.rb' - 'nanoc/spec/nanoc/helpers/child_parent_spec.rb' - 'nanoc/spec/nanoc/helpers/filtering_spec.rb' - 'nanoc/spec/nanoc/helpers/html_escape_spec.rb' - 'nanoc/spec/nanoc/helpers/link_to_spec.rb' - 'nanoc/spec/nanoc/helpers/rendering_spec.rb' - 'nanoc/spec/nanoc/helpers/tagging_spec.rb' - 'nanoc/spec/nanoc/helpers/text_spec.rb' - 'nanoc/spec/nanoc/integration/outdatedness_integration_spec.rb' - 'nanoc/spec/nanoc/integration/write_nil_spec.rb' - 'nanoc/spec/nanoc/regressions/gh_1171_spec.rb' - 'nanoc/spec/nanoc/regressions/gh_1216_spec.rb' - 'nanoc/spec/nanoc/rule_dsl/action_recorder_spec.rb' - 'nanoc/spec/nanoc/rule_dsl/action_sequence_calculator_spec.rb' - 'nanoc/spec/nanoc/rule_dsl/rule_context_spec.rb' - 'nanoc/spec/nanoc/rule_dsl/rule_spec.rb' - 'nanoc/spec/nanoc/rule_dsl/rules_collection_spec.rb' # Offense count: 8 # This cop supports unsafe autocorrection (--autocorrect-all). RSpec/EmptyExampleGroup: Exclude: - 'nanoc-cli/spec/nanoc/cli/error_handler_spec.rb' - 'nanoc-core/spec/nanoc/core/basic_outdatedness_checker_spec.rb' - 'nanoc-core/spec/nanoc/core/compilation_item_view_spec.rb' - 'nanoc-core/spec/nanoc/core/dependency_props_spec.rb' - 'nanoc-core/spec/nanoc/core/filter_spec.rb' - 'nanoc-core/spec/nanoc/core/outdatedness_rules_spec.rb' - 'nanoc/spec/nanoc/helpers/text_spec.rb' # Offense count: 198 # Configuration parameters: CountAsOne. RSpec/ExampleLength: Max: 29 # Offense count: 41 RSpec/ExpectInHook: Exclude: - 'nanoc-cli/spec/nanoc/cli/commands/shell_spec.rb' - 'nanoc-core/spec/nanoc/core/compilation_stages/determine_outdatedness_spec.rb' - 'nanoc-core/spec/nanoc/core/compilation_stages/preprocess_spec.rb' - 'nanoc-core/spec/nanoc/core/config_loader_spec.rb' - 'nanoc-core/spec/nanoc/core/config_view_spec.rb' - 'nanoc-core/spec/nanoc/core/executor_spec.rb' - 'nanoc-core/spec/nanoc/core/item_rep_writer_spec.rb' - 'nanoc-core/spec/nanoc/core/site_loader_spec.rb' - 'nanoc-core/spec/nanoc/core/support/identifiable_collection_view_examples.rb' - 'nanoc-deploying/spec/nanoc/deploying/command_runners/deploy_spec.rb' - 'nanoc-deploying/spec/nanoc/deploying/deployers/fog_spec.rb' # Offense count: 3 RSpec/ExpectOutput: Exclude: - 'nanoc-cli/spec/nanoc/cli/commands/compile_spec.rb' - 'nanoc-live/spec/nanoc/live/live_recompiler_spec.rb' # Offense count: 8 # Configuration parameters: Max, AllowedIdentifiers, AllowedPatterns. RSpec/IndexedLet: Exclude: - 'nanoc-core/spec/nanoc/core/aggregate_data_source_spec.rb' - 'nanoc-core/spec/nanoc/core/configuration_spec.rb' # Offense count: 12 # Configuration parameters: AssignmentOnly. RSpec/InstanceVariable: Exclude: - 'guard-nanoc/spec/lib/guard/nanoc_spec.rb' - 'nanoc-core/spec/nanoc/core/code_snippet_spec.rb' - 'nanoc-core/spec/nanoc/core/filter_spec.rb' - 'nanoc/spec/nanoc/rule_dsl/rule_spec.rb' # Offense count: 140 # Configuration parameters: . # SupportedStyles: have_received, receive RSpec/MessageSpies: EnforcedStyle: receive # Offense count: 3 RSpec/MultipleDescribes: Exclude: - 'nanoc-core/spec/nanoc/core/checksummer_spec.rb' - 'nanoc/spec/nanoc/rule_dsl/rule_context_spec.rb' - 'nanoc/spec/nanoc/rule_dsl/rule_spec.rb' # Offense count: 408 RSpec/MultipleExpectations: Max: 19 # Offense count: 806 # Configuration parameters: AllowSubject. RSpec/MultipleMemoizedHelpers: Max: 58 # Offense count: 868 # Configuration parameters: EnforcedStyle, IgnoreSharedExamples. # SupportedStyles: always, named_only RSpec/NamedSubject: Exclude: - 'guard-nanoc/spec/gem_spec.rb' - 'guard-nanoc/spec/lib/guard/nanoc_spec.rb' - 'nanoc-checking/spec/gem_spec.rb' - 'nanoc-checking/spec/nanoc/checking/command_runners/check_spec.rb' - 'nanoc-cli/spec/gem_spec.rb' - 'nanoc-cli/spec/nanoc/cli/command_runner_spec.rb' - 'nanoc-cli/spec/nanoc/cli/commands/shell_spec.rb' - 'nanoc-cli/spec/nanoc/cli/commands/show_data_spec.rb' - 'nanoc-cli/spec/nanoc/cli/compile_listeners/abstract_spec.rb' - 'nanoc-cli/spec/nanoc/cli/compile_listeners/diff_generator_spec.rb' - 'nanoc-cli/spec/nanoc/cli/error_handler_spec.rb' - 'nanoc-cli/spec/nanoc/cli/stack_trace_writer_spec.rb' - 'nanoc-cli/spec/nanoc/cli/stream_cleaners/utf8_spec.rb' - 'nanoc-core/spec/nanoc/core/action_sequence_builder_spec.rb' - 'nanoc-core/spec/nanoc/core/action_sequence_spec.rb' - 'nanoc-core/spec/nanoc/core/aggregate_data_source_spec.rb' - 'nanoc-core/spec/nanoc/core/basic_outdatedness_checker_spec.rb' - 'nanoc-core/spec/nanoc/core/binary_compiled_content_cache_spec.rb' - 'nanoc-core/spec/nanoc/core/code_snippet_spec.rb' - 'nanoc-core/spec/nanoc/core/compilation_item_rep_view_spec.rb' - 'nanoc-core/spec/nanoc/core/compilation_item_view_spec.rb' - 'nanoc-core/spec/nanoc/core/compilation_phases/abstract_spec.rb' - 'nanoc-core/spec/nanoc/core/compilation_phases/cache_spec.rb' - 'nanoc-core/spec/nanoc/core/compilation_phases/resume_spec.rb' - 'nanoc-core/spec/nanoc/core/compilation_stage_spec.rb' - 'nanoc-core/spec/nanoc/core/compilation_stages/calculate_checksums_spec.rb' - 'nanoc-core/spec/nanoc/core/compilation_stages/cleanup_spec.rb' - 'nanoc-core/spec/nanoc/core/compilation_stages/compile_reps_spec.rb' - 'nanoc-core/spec/nanoc/core/compilation_stages/determine_outdatedness_spec.rb' - 'nanoc-core/spec/nanoc/core/compilation_stages/preprocess_spec.rb' - 'nanoc-core/spec/nanoc/core/compiled_content_store_spec.rb' - 'nanoc-core/spec/nanoc/core/compiler_spec.rb' - 'nanoc-core/spec/nanoc/core/config_loader_spec.rb' - 'nanoc-core/spec/nanoc/core/config_view_spec.rb' - 'nanoc-core/spec/nanoc/core/configuration_spec.rb' - 'nanoc-core/spec/nanoc/core/content_spec.rb' - 'nanoc-core/spec/nanoc/core/data_source_spec.rb' - 'nanoc-core/spec/nanoc/core/dependency_store_spec.rb' - 'nanoc-core/spec/nanoc/core/dependency_tracker_spec.rb' - 'nanoc-core/spec/nanoc/core/directed_graph_spec.rb' - 'nanoc-core/spec/nanoc/core/executor_spec.rb' - 'nanoc-core/spec/nanoc/core/filter_spec.rb' - 'nanoc-core/spec/nanoc/core/identifier_spec.rb' - 'nanoc-core/spec/nanoc/core/in_memory_data_source_spec.rb' - 'nanoc-core/spec/nanoc/core/instrumentor_spec.rb' - 'nanoc-core/spec/nanoc/core/item_rep_router_spec.rb' - 'nanoc-core/spec/nanoc/core/item_rep_selector_spec.rb' - 'nanoc-core/spec/nanoc/core/item_rep_writer_spec.rb' - 'nanoc-core/spec/nanoc/core/lazy_value_spec.rb' - 'nanoc-core/spec/nanoc/core/outdatedness_rules_spec.rb' - 'nanoc-core/spec/nanoc/core/outdatedness_status_spec.rb' - 'nanoc-core/spec/nanoc/core/post_compile_item_rep_view_spec.rb' - 'nanoc-core/spec/nanoc/core/post_compile_item_view_spec.rb' - 'nanoc-core/spec/nanoc/core/prefixed_data_source_spec.rb' - 'nanoc-core/spec/nanoc/core/pruner_spec.rb' - 'nanoc-core/spec/nanoc/core/regexp_pattern_spec.rb' - 'nanoc-core/spec/nanoc/core/site_loader_spec.rb' - 'nanoc-core/spec/nanoc/core/string_pattern_spec.rb' - 'nanoc-core/spec/nanoc/core/temp_filename_factory_spec.rb' - 'nanoc-deploying/spec/gem_spec.rb' - 'nanoc-deploying/spec/nanoc/deploying/deployers/fog_spec.rb' - 'nanoc-deploying/spec/nanoc/deploying/deployers/git_spec.rb' - 'nanoc-deploying/spec/nanoc/deploying/deployers/rsync_spec.rb' - 'nanoc-external/spec/gem_spec.rb' - 'nanoc-live/spec/gem_spec.rb' - 'nanoc-spec/spec/gem_spec.rb' - 'nanoc-spec/spec/nanoc/spec/helper_context_spec.rb' - 'nanoc/spec/gem_spec.rb' - 'nanoc/spec/nanoc/data_sources/filesystem/parser_spec.rb' - 'nanoc/spec/nanoc/data_sources/filesystem/tools_spec.rb' - 'nanoc/spec/nanoc/data_sources/filesystem_spec.rb' - 'nanoc/spec/nanoc/filters/asciidoc_spec.rb' - 'nanoc/spec/nanoc/filters/erb_spec.rb' - 'nanoc/spec/nanoc/helpers/blogging_spec.rb' - 'nanoc/spec/nanoc/helpers/breadcrumbs_spec.rb' - 'nanoc/spec/nanoc/helpers/capturing_spec.rb' - 'nanoc/spec/nanoc/helpers/child_parent_spec.rb' - 'nanoc/spec/nanoc/helpers/filtering_spec.rb' - 'nanoc/spec/nanoc/helpers/html_escape_spec.rb' - 'nanoc/spec/nanoc/helpers/link_to_spec.rb' - 'nanoc/spec/nanoc/helpers/rendering_spec.rb' - 'nanoc/spec/nanoc/helpers/text_spec.rb' - 'nanoc/spec/nanoc/orig_cli/commands/show_rules_spec.rb' - 'nanoc/spec/nanoc/rule_dsl/action_recorder_spec.rb' - 'nanoc/spec/nanoc/rule_dsl/action_sequence_calculator_spec.rb' - 'nanoc/spec/nanoc/rule_dsl/rule_context_spec.rb' - 'nanoc/spec/nanoc/rule_dsl/rules_collection_spec.rb' - 'nanoc/spec/nanoc/spec_spec.rb' # Offense count: 483 # Configuration parameters: AllowedGroups. RSpec/NestedGroups: Max: 7 # Offense count: 1 RSpec/OverwritingSetup: Exclude: - 'nanoc/spec/nanoc/rule_dsl/rule_context_spec.rb' # Offense count: 6 RSpec/RepeatedDescription: Exclude: - 'nanoc-cli/spec/nanoc/cli/compile_listeners/file_action_printer_spec.rb' - 'nanoc-core/spec/nanoc/core/binary_compiled_content_cache_spec.rb' - 'nanoc-live/spec/nanoc/live/live_recompiler_spec.rb' # Offense count: 2 RSpec/RepeatedExample: Exclude: - 'nanoc/spec/nanoc/filters/sass_spec.rb' # Offense count: 12 RSpec/RepeatedExampleGroupDescription: Exclude: - 'nanoc-core/spec/nanoc/core/dependency_props_spec.rb' - 'nanoc-core/spec/nanoc/core/dependency_store_spec.rb' - 'nanoc-core/spec/nanoc/core/identifiable_collection_spec.rb' - 'nanoc-core/spec/nanoc/core/support/item_rep_view_examples.rb' # Offense count: 28 RSpec/StubbedMock: Exclude: - 'nanoc-cli/spec/nanoc/cli/commands/compile_spec.rb' - 'nanoc-core/spec/nanoc/core/compilation_stages/determine_outdatedness_spec.rb' - 'nanoc-core/spec/nanoc/core/compilation_stages/preprocess_spec.rb' - 'nanoc-core/spec/nanoc/core/config_loader_spec.rb' - 'nanoc-core/spec/nanoc/core/executor_spec.rb' - 'nanoc-core/spec/nanoc/core/site_loader_spec.rb' - 'nanoc-deploying/spec/nanoc/deploying/deployers/fog_spec.rb' - 'nanoc/spec/nanoc/orig_cli/commands/show_rules_spec.rb' # Offense count: 73 # Configuration parameters: IgnoreNameless, IgnoreSymbolicNames. RSpec/VerifiedDoubles: Exclude: - 'nanoc-cli/spec/nanoc/cli/commands/show_data_spec.rb' - 'nanoc-cli/spec/nanoc/cli/compile_listeners/diff_generator_spec.rb' - 'nanoc-cli/spec/nanoc/cli_spec.rb' - 'nanoc-core/spec/nanoc/core/basic_outdatedness_checker_spec.rb' - 'nanoc-core/spec/nanoc/core/compilation_stages/compile_reps_spec.rb' - 'nanoc-core/spec/nanoc/core/compilation_stages/determine_outdatedness_spec.rb' - 'nanoc-core/spec/nanoc/core/executor_spec.rb' - 'nanoc-core/spec/nanoc/core/identifiable_collection_spec.rb' - 'nanoc-core/spec/nanoc/core/post_compile_item_rep_view_spec.rb' - 'nanoc-core/spec/nanoc/core/support/document_view_examples.rb' - 'nanoc-core/spec/nanoc/core/support/identifiable_collection_view_examples.rb' - 'nanoc-core/spec/nanoc/core/support/item_rep_collection_view_examples.rb' - 'nanoc-core/spec/nanoc/core/support/item_rep_view_examples.rb' - 'nanoc-core/spec/nanoc/core/support/mutable_document_view_examples.rb' - 'nanoc-core/spec/nanoc/core/support/mutable_identifiable_collection_view_examples.rb' - 'nanoc-deploying/spec/nanoc/deploying/deployers/fog_spec.rb' - 'nanoc/spec/nanoc/orig_cli/commands/show_rules_spec.rb' - 'nanoc/spec/nanoc/rule_dsl/rule_context_spec.rb' # Offense count: 1 # This cop supports unsafe autocorrection (--autocorrect-all). Style/ArrayIntersect: Exclude: - 'nanoc-core/lib/nanoc/core/outdatedness_checker.rb' # Offense count: 127 # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: EnforcedStyle. # SupportedStyles: nested, compact Style/ClassAndModuleChildren: Exclude: - 'nanoc-cli/lib/nanoc/cli/commands/compile.rb' - 'nanoc-cli/lib/nanoc/cli/commands/create-site.rb' - 'nanoc-cli/lib/nanoc/cli/commands/prune.rb' - 'nanoc-cli/lib/nanoc/cli/commands/shell.rb' - 'nanoc-cli/lib/nanoc/cli/commands/show-data.rb' - 'nanoc-cli/lib/nanoc/cli/commands/show-plugins.rb' - 'nanoc-cli/lib/nanoc/cli/commands/view.rb' - 'nanoc-cli/lib/nanoc/cli/compile_listeners/abstract.rb' - 'nanoc-cli/lib/nanoc/cli/compile_listeners/aggregate.rb' - 'nanoc-cli/lib/nanoc/cli/compile_listeners/debug_printer.rb' - 'nanoc-cli/lib/nanoc/cli/compile_listeners/diff_generator.rb' - 'nanoc-cli/lib/nanoc/cli/compile_listeners/file_action_printer.rb' - 'nanoc-cli/lib/nanoc/cli/compile_listeners/timing_recorder.rb' - 'nanoc-cli/lib/nanoc/cli/error_handler.rb' - 'nanoc/lib/nanoc/data_sources.rb' - 'nanoc/lib/nanoc/data_sources/filesystem.rb' - 'nanoc/lib/nanoc/data_sources/filesystem/errors.rb' - 'nanoc/lib/nanoc/data_sources/filesystem/parser.rb' - 'nanoc/lib/nanoc/data_sources/filesystem/tools.rb' - 'nanoc/lib/nanoc/extra.rb' - 'nanoc/lib/nanoc/extra/core_ext/time.rb' - 'nanoc/lib/nanoc/filters.rb' - 'nanoc/lib/nanoc/filters/asciidoc.rb' - 'nanoc/lib/nanoc/filters/asciidoctor.rb' - 'nanoc/lib/nanoc/filters/bluecloth.rb' - 'nanoc/lib/nanoc/filters/coffeescript.rb' - 'nanoc/lib/nanoc/filters/colorize_syntax.rb' - 'nanoc/lib/nanoc/filters/colorize_syntax/colorizers.rb' - 'nanoc/lib/nanoc/filters/erb.rb' - 'nanoc/lib/nanoc/filters/erubi.rb' - 'nanoc/lib/nanoc/filters/erubis.rb' - 'nanoc/lib/nanoc/filters/haml.rb' - 'nanoc/lib/nanoc/filters/handlebars.rb' - 'nanoc/lib/nanoc/filters/kramdown.rb' - 'nanoc/lib/nanoc/filters/less.rb' - 'nanoc/lib/nanoc/filters/markaby.rb' - 'nanoc/lib/nanoc/filters/maruku.rb' - 'nanoc/lib/nanoc/filters/mustache.rb' - 'nanoc/lib/nanoc/filters/pandoc.rb' - 'nanoc/lib/nanoc/filters/rainpress.rb' - 'nanoc/lib/nanoc/filters/rdiscount.rb' - 'nanoc/lib/nanoc/filters/rdoc.rb' - 'nanoc/lib/nanoc/filters/redcarpet.rb' - 'nanoc/lib/nanoc/filters/redcloth.rb' - 'nanoc/lib/nanoc/filters/relativize_paths.rb' - 'nanoc/lib/nanoc/filters/rubypants.rb' - 'nanoc/lib/nanoc/filters/sass.rb' - 'nanoc/lib/nanoc/filters/sass/functions.rb' - 'nanoc/lib/nanoc/filters/sass/importer.rb' - 'nanoc/lib/nanoc/filters/slim.rb' - 'nanoc/lib/nanoc/filters/terser.rb' - 'nanoc/lib/nanoc/filters/typogruby.rb' - 'nanoc/lib/nanoc/filters/xsl.rb' - 'nanoc/lib/nanoc/filters/yui_compressor.rb' - 'nanoc/lib/nanoc/helpers.rb' - 'nanoc/lib/nanoc/helpers/blogging.rb' - 'nanoc/lib/nanoc/helpers/breadcrumbs.rb' - 'nanoc/lib/nanoc/helpers/capturing.rb' - 'nanoc/lib/nanoc/helpers/child_parent.rb' - 'nanoc/lib/nanoc/helpers/filtering.rb' - 'nanoc/lib/nanoc/helpers/html_escape.rb' - 'nanoc/lib/nanoc/helpers/link_to.rb' - 'nanoc/lib/nanoc/helpers/rendering.rb' - 'nanoc/lib/nanoc/helpers/tagging.rb' - 'nanoc/lib/nanoc/helpers/text.rb' - 'nanoc/lib/nanoc/helpers/xml_sitemap.rb' - 'nanoc/lib/nanoc/orig_cli.rb' - 'nanoc/lib/nanoc/orig_cli/commands/show-rules.rb' - 'nanoc/lib/nanoc/rule_dsl/action_provider.rb' - 'nanoc/lib/nanoc/rule_dsl/action_sequence_calculator.rb' - 'nanoc/lib/nanoc/rule_dsl/compilation_rule.rb' - 'nanoc/lib/nanoc/rule_dsl/compilation_rule_context.rb' - 'nanoc/lib/nanoc/rule_dsl/compiler_dsl.rb' - 'nanoc/lib/nanoc/rule_dsl/routing_rule.rb' - 'nanoc/lib/nanoc/rule_dsl/routing_rule_context.rb' - 'nanoc/lib/nanoc/rule_dsl/rule.rb' - 'nanoc/lib/nanoc/rule_dsl/rule_context.rb' - 'nanoc/lib/nanoc/rule_dsl/rules_collection.rb' - 'nanoc/lib/nanoc/rule_dsl/rules_loader.rb' - 'nanoc/test/base/test_compiler.rb' - 'nanoc/test/base/test_site.rb' - 'nanoc/test/data_sources/test_filesystem.rb' - 'nanoc/test/data_sources/test_filesystem_tools.rb' - 'nanoc/test/extra/core_ext/test_time.rb' - 'nanoc/test/filters/colorize_syntax/test_coderay.rb' - 'nanoc/test/filters/colorize_syntax/test_common.rb' - 'nanoc/test/filters/colorize_syntax/test_pygmentize.rb' - 'nanoc/test/filters/colorize_syntax/test_pygments.rb' - 'nanoc/test/filters/colorize_syntax/test_simon.rb' - 'nanoc/test/filters/test_bluecloth.rb' - 'nanoc/test/filters/test_coffeescript.rb' - 'nanoc/test/filters/test_erubi.rb' - 'nanoc/test/filters/test_erubis.rb' - 'nanoc/test/filters/test_haml.rb' - 'nanoc/test/filters/test_handlebars.rb' - 'nanoc/test/filters/test_kramdown.rb' - 'nanoc/test/filters/test_markaby.rb' - 'nanoc/test/filters/test_maruku.rb' - 'nanoc/test/filters/test_mustache.rb' - 'nanoc/test/filters/test_pandoc.rb' - 'nanoc/test/filters/test_rainpress.rb' - 'nanoc/test/filters/test_rdiscount.rb' - 'nanoc/test/filters/test_rdoc.rb' - 'nanoc/test/filters/test_redcarpet.rb' - 'nanoc/test/filters/test_redcloth.rb' - 'nanoc/test/filters/test_relativize_paths.rb' - 'nanoc/test/filters/test_rubypants.rb' - 'nanoc/test/filters/test_slim.rb' - 'nanoc/test/filters/test_terser.rb' - 'nanoc/test/filters/test_typogruby.rb' - 'nanoc/test/filters/test_xsl.rb' - 'nanoc/test/filters/test_yui_compressor.rb' - 'nanoc/test/helper.rb' - 'nanoc/test/helpers/test_blogging.rb' - 'nanoc/test/helpers/test_capturing.rb' - 'nanoc/test/helpers/test_xml_sitemap.rb' - 'nanoc/test/orig_cli/commands/test_compile.rb' - 'nanoc/test/orig_cli/commands/test_create_site.rb' - 'nanoc/test/orig_cli/commands/test_help.rb' - 'nanoc/test/orig_cli/commands/test_prune.rb' - 'nanoc/test/orig_cli/test_cli.rb' - 'nanoc/test/orig_cli/test_error_handler.rb' - 'nanoc/test/orig_cli/test_logger.rb' - 'nanoc/test/rule_dsl/test_action_provider.rb' - 'nanoc/test/rule_dsl/test_compiler_dsl.rb' - 'nanoc/test/rule_dsl/test_rules_collection.rb' # Offense count: 2 # This cop supports unsafe autocorrection (--autocorrect-all). Style/CombinableLoops: Exclude: - 'nanoc-core/lib/nanoc/core/item_rep_router.rb' # Offense count: 1 # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: AllowedReceivers. # AllowedReceivers: Thread.current Style/HashEachMethods: Exclude: - 'nanoc-core/spec/nanoc/core/post_compile_item_rep_view_spec.rb' # Offense count: 2 # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: EnforcedStyle. # SupportedStyles: literals, strict Style/MutableConstant: Exclude: - 'nanoc-cli/lib/nanoc/cli/commands/create-site.rb' # Offense count: 7 # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: Methods. Style/RedundantArgument: Exclude: - 'nanoc-cli/lib/nanoc/cli/ansi_string_colorizer.rb' - 'nanoc-core/lib/nanoc/core/errors.rb' - 'nanoc/lib/nanoc/filters/relativize_paths.rb' - 'nanoc/lib/nanoc/helpers/capturing.rb' - 'nanoc/spec/contributors_spec.rb' - 'nanoc/spec/nanoc/core/checksummer_spec.rb' - 'nanoc/spec/nanoc/helpers/capturing_spec.rb' # Offense count: 22 # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: AutoCorrect, AllowComments. Style/RedundantInitialize: Exclude: - 'nanoc-checking/spec/nanoc/checking/check_spec.rb' - 'nanoc-core/lib/nanoc/core/dependency_tracker.rb' - 'nanoc-core/spec/nanoc/core/compilation_item_rep_view_spec.rb' - 'nanoc-core/spec/nanoc/core/compilation_item_view_spec.rb' - 'nanoc-core/spec/nanoc/core/compilation_stages/compile_reps_spec.rb' - 'nanoc-core/spec/nanoc/core/compilation_stages/preprocess_spec.rb' - 'nanoc-core/spec/nanoc/core/compiler_spec.rb' - 'nanoc-core/spec/nanoc/core/config_view_spec.rb' - 'nanoc-core/spec/nanoc/core/executor_spec.rb' - 'nanoc-core/spec/nanoc/core/filter_spec.rb' - 'nanoc-core/spec/nanoc/core/post_compile_item_rep_view_spec.rb' - 'nanoc-core/spec/nanoc/core/post_compile_item_view_spec.rb' - 'nanoc-core/spec/nanoc/core/support/document_view_examples.rb' - 'nanoc-core/spec/nanoc/core/support/identifiable_collection_view_examples.rb' - 'nanoc-core/spec/nanoc/core/support/item_rep_view_examples.rb' - 'nanoc-core/spec/nanoc/core/support/mutable_document_view_examples.rb' - 'nanoc/spec/nanoc/filters/sass_spec.rb' - 'nanoc/test/filters/test_slim.rb' - 'nanoc/test/filters/test_xsl.rb' - 'nanoc/test/helpers/test_blogging.rb' - 'nanoc/test/helpers/test_capturing.rb' - 'nanoc/test/helpers/test_xml_sitemap.rb' # Offense count: 136 # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: Mode. Style/StringConcatenation: Exclude: - 'Rakefile' - 'common/spec/spec_helper_foot_core.rb' - 'nanoc-checking/lib/nanoc/checking/check.rb' - 'nanoc-checking/lib/nanoc/checking/checks/external_links.rb' - 'nanoc-checking/lib/nanoc/checking/checks/w3c_validator.rb' - 'nanoc-checking/lib/nanoc/checking/runner.rb' - 'nanoc-checking/spec/nanoc/checking/checks/mixed_content_spec.rb' - 'nanoc-cli/lib/nanoc/cli.rb' - 'nanoc-cli/lib/nanoc/cli/ansi_string_colorizer.rb' - 'nanoc-cli/lib/nanoc/cli/commands/create-site.rb' - 'nanoc-cli/lib/nanoc/cli/compile_listeners/file_action_printer.rb' - 'nanoc-cli/lib/nanoc/cli/error_handler.rb' - 'nanoc-cli/spec/nanoc/cli/error_handler_spec.rb' - 'nanoc-core/lib/nanoc/core.rb' - 'nanoc-core/lib/nanoc/core/basic_item_view.rb' - 'nanoc-core/lib/nanoc/core/binary_compiled_content_cache.rb' - 'nanoc-core/lib/nanoc/core/checksummer.rb' - 'nanoc-core/lib/nanoc/core/configuration.rb' - 'nanoc-core/lib/nanoc/core/context.rb' - 'nanoc-core/lib/nanoc/core/directed_graph.rb' - 'nanoc-core/lib/nanoc/core/errors.rb' - 'nanoc-core/lib/nanoc/core/item_rep_router.rb' - 'nanoc-core/lib/nanoc/core/pruner.rb' - 'nanoc-core/spec/nanoc/core/compilation_item_rep_view_spec.rb' - 'nanoc-core/spec/nanoc/core/compilation_stages/cleanup_spec.rb' - 'nanoc-core/spec/nanoc/core/configuration_spec.rb' - 'nanoc-core/spec/nanoc/core/executor_spec.rb' - 'nanoc-core/spec/nanoc/core/item_rep_router_spec.rb' - 'nanoc-core/spec/nanoc/core/item_rep_writer_spec.rb' - 'nanoc-core/spec/nanoc/core/outdatedness_rules_spec.rb' - 'nanoc-core/spec/nanoc/core/post_compile_item_rep_view_spec.rb' - 'nanoc-core/spec/nanoc/core/pruner_spec.rb' - 'nanoc-core/spec/nanoc/core/support/item_rep_collection_view_examples.rb' - 'nanoc-core/spec/nanoc/core/support/item_rep_view_examples.rb' - 'nanoc-core/spec/nanoc/core/temp_filename_factory_spec.rb' - 'nanoc-deploying/lib/nanoc/deploying/deployers/fog.rb' - 'nanoc-deploying/lib/nanoc/deploying/deployers/rsync.rb' - 'nanoc/lib/nanoc/data_sources/filesystem.rb' - 'nanoc/lib/nanoc/filters/less.rb' - 'nanoc/lib/nanoc/filters/relativize_paths.rb' - 'nanoc/lib/nanoc/filters/sass/importer.rb' - 'nanoc/lib/nanoc/helpers/blogging.rb' - 'nanoc/lib/nanoc/helpers/breadcrumbs.rb' - 'nanoc/lib/nanoc/helpers/child_parent.rb' - 'nanoc/lib/nanoc/helpers/link_to.rb' - 'nanoc/lib/nanoc/orig_cli/commands/show-rules.rb' - 'nanoc/lib/nanoc/rule_dsl/compilation_rule_context.rb' - 'nanoc/lib/nanoc/rule_dsl/compiler_dsl.rb' - 'nanoc/spec/nanoc/core/checksummer_spec.rb' - 'nanoc/spec/nanoc/data_sources/filesystem/parser_spec.rb' - 'nanoc/spec/nanoc/filters/sass_spec.rb' - 'nanoc/test/base/test_site.rb' - 'nanoc/test/filters/colorize_syntax/test_coderay.rb' - 'nanoc/test/helper.rb' - 'nanoc/test/orig_cli/commands/test_create_site.rb' - 'nanoc/test/rule_dsl/test_action_provider.rb' - 'scripts/release' nanoc-4.13.3/.ruby-version000066400000000000000000000000051472033334600153450ustar00rootroot000000000000003.3.6nanoc-4.13.3/CODE_OF_CONDUCT.md000066400000000000000000000125341472033334600155110ustar00rootroot00000000000000# Contributor Covenant Code of Conduct ## Our Pledge We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation. We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. ## Our Standards Examples of behavior that contributes to a positive environment for our community include: - Demonstrating empathy and kindness toward other people - Being respectful of differing opinions, viewpoints, and experiences - Giving and gracefully accepting constructive feedback - Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience - Focusing on what is best not just for us as individuals, but for the overall community Examples of unacceptable behavior include: - The use of sexualized language or imagery, and sexual attention or advances of any kind - Trolling, insulting or derogatory comments, and personal or political attacks - Public or private harassment - Publishing others' private information, such as a physical or email address, without their explicit permission - Other conduct which could reasonably be considered inappropriate in a professional setting ## Enforcement Responsibilities Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. Community leaders 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, and will communicate reasons for moderation decisions when appropriate. ## Scope This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at coc@nanoc.app. All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the reporter of any incident. ## Enforcement Guidelines Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: ### 1. Correction **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. ### 2. Warning **Community Impact**: A violation through a single incident or series of actions. **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. ### 3. Temporary Ban **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. ### 4. Permanent Ban **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. **Consequence**: A permanent ban from any sort of public interaction within the community. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.1, available at [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder][mozilla coc]. For answers to common questions about this code of conduct, see the FAQ at [https://www.contributor-covenant.org/faq][faq]. Translations are available at [https://www.contributor-covenant.org/translations][translations]. [homepage]: https://www.contributor-covenant.org [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html [mozilla coc]: https://github.com/mozilla/diversity [faq]: https://www.contributor-covenant.org/faq [translations]: https://www.contributor-covenant.org/translations nanoc-4.13.3/Gemfile000066400000000000000000000040751472033334600142060ustar00rootroot00000000000000# frozen_string_literal: true source 'https://rubygems.org' gemspec path: 'nanoc' gemspec path: 'nanoc-core' gemspec path: 'nanoc-cli' gemspec path: 'nanoc-checking' gemspec path: 'nanoc-dart-sass' gemspec path: 'nanoc-deploying' gemspec path: 'nanoc-external' gemspec path: 'nanoc-org-mode' gemspec path: 'nanoc-live' gemspec path: 'nanoc-spec' gemspec path: 'nanoc-tilt' gemspec path: 'guard-nanoc' group :devel do gem 'addressable', '~> 2.8' gem 'contracts', '~> 0.16' gem 'debug', '~> 1.9' 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', '~> 1.31' gem 'rubocop-minitest' gem 'rubocop-rake' gem 'rubocop-rspec' gem 'simplecov', '~> 0.22.0' gem 'timecop' gem 'tty-command', '~> 0.8' gem 'vcr' gem 'webmock' gem 'webrick', '~> 1.7' gem 'yard' gem 'yard-contracts' end group :plugins do gem 'adsf' gem 'adsf-live' gem 'asciidoctor' gem 'bluecloth', platforms: :ruby gem 'builder' gem 'clonefile', '~> 0.5.2' gem 'coderay' gem 'coffee-script' gem 'erubi' gem 'erubis' gem 'execjs', '~> 2.7' gem 'fog-aws' gem 'fog-local', '~> 0.6' gem 'haml', '~> 6.0' gem 'kramdown' gem 'less', '~> 2.6', platforms: :ruby gem 'listen', '~> 3.1' gem 'markaby' gem 'maruku' gem 'mime-types' gem 'mini_racer' gem 'mustache', '~> 1.0' gem 'nokogiri', '~> 1.12' gem 'pandoc-ruby' gem 'pygments.rb', '~> 2.0', platforms: :ruby gem 'rack' gem 'rainpress' gem 'redcarpet', '~> 3.4', platforms: :ruby gem 'RedCloth', platforms: :ruby gem 'rouge', '~> 4.1' gem 'ruby-handlebars' gem 'rubypants' gem 'sass' gem 'slim', '~> 5.0' gem 'terser' gem 'typogruby' gem 'w3c_validators' gem 'wdm', '>= 0.1.0' if Gem.win_platform? gem 'yuicompressor' # TODO: remove # See https://github.com/davidfstr/rdiscount/issues/155 unless `clang --version`.match?(/clang version 16/) gem 'rdiscount', '~> 2.2', platforms: :ruby end end nanoc-4.13.3/LICENSE000066400000000000000000000020711472033334600137120ustar00rootroot00000000000000Copyright (c) 2007–… 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.13.3/README.md000066400000000000000000000047411472033334600141720ustar00rootroot00000000000000[![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) [![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](https://nanoc.app) for more information. Contributions are greatly appreciated! Consult the [guidelines](https://nanoc.app/contributing/) for information on how you can contribute. 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, Antar, 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 Mathieu, Damien Pollet, Dan Callahan, Daniel Aleksandersen, Daniel Hofstetter, Daniel Mendler, Daniel Wollschlaeger, David Alexander, David Everitt, Denis Defreyne, Dennis Sutch, Devon Luke Buchanan, Dmitry Bilunov, Eric Sunshine, Erik Hollensbe, Ethan Crawford, Fabian Buch, Farley Reynolds, Felix Hanley, Garen Torikian, Go Maeda, Grégory Karékinian, Gregory Pakosz, Guilherme Garnier, Hendrik Jäger, Hideaki Nagamine, Hugo Peixoto, Ian Young, Jack Chu, Jake Benilov, Jamie McCarthy, Jan M. Faber, Jasper Van der Jeugt, Jeff Forcier, Jim Mendenhall, John Nishinaga, Johnny Willemsen, Junichi Sato, Justin Clift, Justin Hileman, Kevin Lynagh, Lorin Werthen, Louis T., Lucas Vuotto, Masayuki Higashino, 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, ntkme, Oliver Byford, Paul Boone, Peter Aronoff, Raphael von der Grün, Rémi Barraquand, Remko Tronçon, Rien Maertens, Riley Goodside, Romain Goyet, Ruben Verborgh, Scott Vokes, Seiichi Yonezawa, Sergio Durigan Junior, Š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 Noria, Xavier Shay, Yannick Ihmels, Zaiste de Grengolada, zor-el nanoc-4.13.3/Rakefile000066400000000000000000000071071472033334600143570ustar00rootroot00000000000000# frozen_string_literal: true require 'rubocop/rake_task' RuboCop::RakeTask.new(:rubocop) def sub_sh(dir, cmd) Bundler.with_original_env do Dir.chdir(dir) do puts "======= entering ./#{dir}/" puts sh(cmd) puts puts "======= exiting ./#{dir}/" end end end packages = %w[ nanoc nanoc-core nanoc-cli nanoc-checking nanoc-dart-sass nanoc-deploying nanoc-external nanoc-live nanoc-org-mode nanoc-spec nanoc-tilt guard-nanoc ] def name_sets(packages) packages .partition { |name| %w[nanoc nanoc-core nanoc-cli].include?(name) } .map(&:sort) end packages.each do |package| namespace(package.tr('-', '_')) do desc "Build gem for #{package}" task(:gem) { sub_sh(package, 'bundle exec rake gem') } desc "Run tests for #{package}" task(:test) { sub_sh(package, 'bundle exec rake test') } desc "Run style checks for #{package}" task(:rubocop) { sub_sh(package, 'bundle exec rake rubocop') } end end desc 'Run tests for all packages' task test: packages.map { |p| p.tr('-', '_') + ':test' } desc 'Build gems all packages' task gem: packages.map { |p| p.tr('-', '_') + ':gem' } desc 'Print overview of which packages need a release' task :needs_release do tags = `git tags`.lines.map(&:chomp).map { |t| t.match?(/\A\d/) ? 'nanoc-v' + t : t } tags_by_base_name = tags.group_by { |t| t[/\A.*(?=-v\d)/] }.select { |(base_name, _tags)| base_name } versions_by_base_name = tags_by_base_name.transform_values { |list| list.map { |nv| nv.match(/\A.*-v(\d.*)/) }.compact.map { |m| Gem::Version.new(m[1]) } } last_version_by_base_name = versions_by_base_name.transform_values(&:max) name_length = name_sets(packages).flatten.map(&:size).max name_sets(packages).each_with_index do |names, idx| puts if idx.positive? names.each do |name| last_version = last_version_by_base_name[name] if last_version dir = name tag = name == 'nanoc' ? last_version.to_s : name + '-v' + last_version.to_s diff = `git diff --stat #{tag} #{dir}` needs_release = diff.match?(/\d+ files changed/) text = needs_release ? 'needs release' : 'up to date' color = needs_release ? "\e[33m" : "\e[32m" else text = 'needs initial release' color = "\e[31m" end puts( format( "%-#{name_length}s \e[1m%s%s\e[0m", name, color, text, ), ) end end end desc 'Print overview of all packages and their versions' task :summary do versions = {} dependencies = {} packages.each do |name| gemspec = Bundler.rubygems.find_name(name).first dependencies[name] = gemspec .dependencies .select { |d| d.name.match?(/nanoc/) } versions[name] = gemspec.version end puts '━━━ VERSIONS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━' puts name_length = versions.keys.map(&:size).max name_sets(packages).each_with_index do |names, idx| puts if idx.positive? names.each do |name| puts(format(" %#{name_length}s %s", name, versions[name])) end end puts puts '━━━ DEPENDENCIES ━━━━━━━━━━━━━━━━━━━━━━━━━━━━' puts name_sets(packages).flatten.each do |name| puts(format(' %-s %s', name, '╌' * (40 - name.length))) dependencies[name].sort_by(&:name).each do |dependency| puts(format(" %-#{name_length}s %s", dependency.name, dependency.requirement)) end puts end end task default: %i[test rubocop] nanoc-4.13.3/common/000077500000000000000000000000001472033334600141755ustar00rootroot00000000000000nanoc-4.13.3/common/spec/000077500000000000000000000000001472033334600151275ustar00rootroot00000000000000nanoc-4.13.3/common/spec/spec_helper_foot.rb000066400000000000000000000020631472033334600207750ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'spec_helper_foot_core' Nanoc::CLI.setup RSpec.configure do |c| c.around do |example| Nanoc::CLI::ErrorHandler.disable example.run Nanoc::CLI::ErrorHandler.enable end c.before(:each, fork: true) do skip 'fork() is not supported on Windows' if Nanoc::Core.on_windows? end end RSpec::Matchers.define :raise_wrapped_error do |expected| supports_block_expectations include RSpec::Matchers::Composable match do |actual| actual.call rescue Nanoc::Core::Errors::CompilationError => e values_match?(expected, e.unwrap) 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.alias_matcher :some_textual_content, :be_some_textual_content RSpec::Matchers.alias_matcher :some_binary_content, :be_some_binary_content nanoc-4.13.3/common/spec/spec_helper_foot_core.rb000066400000000000000000000246211472033334600220110ustar00rootroot00000000000000# frozen_string_literal: true require 'nanoc/spec' def __nanoc_core_chdir(dir) here = Dir.getwd Dir.chdir(dir) yield ensure Dir.chdir(here) end def __nanoc_core_with_env_vars(hash, &) orig_env_hash = ENV.to_hash hash.each_pair { |k, v| ENV[k] = v } yield ensure orig_env_hash.each_pair { |k, v| ENV[k] = v } end RSpec.configure do |c| c.include(Nanoc::Spec::Helper) # TODO: Now that HelperHelper is used for filters too, maybe it is worth # renaming it to DataHelper or so. c.include(Nanoc::Spec::HelperHelper, helper: true) c.include(Nanoc::Spec::HelperHelper, filter: true) c.threadsafe = false # TODO: Only really relevant when using the filesystem data source 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.fuubar_progress_bar_options = { format: '%c/%C |<%b>%i| %p%%', } c.before(:each, fork: true) do skip 'fork() is not supported on Windows' if Nanoc::Core.on_windows? end c.before do Nanoc::Core::NotificationCenter.reset end c.around do |example| should_chdir = !example.metadata.key?(:chdir) || example.metadata[:chdir] if should_chdir Dir.mktmpdir('nanoc-test') do |dir| __nanoc_core_chdir(dir) { example.run } end else example.run end 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 end RSpec::Matchers.define_negated_matcher :not_match, :match RSpec::Matchers.define :send_notification do |name, *expected_args| supports_block_expectations include RSpec::Matchers::Composable match do |actual| @actual_notifications = [] Nanoc::Core::NotificationCenter.on(name, self) do |*actual_args| @actual_notifications << actual_args end actual.call Nanoc::Core::NotificationCenter.sync @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 :raise_frozen_error do |_expected| match do |actual| 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 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 :be_some_textual_content do |expected| include RSpec::Matchers::Composable match do |actual| actual.is_a?(Nanoc::Core::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::Core::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 :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 :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 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::Core::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 nanoc-4.13.3/common/spec/spec_helper_head.rb000066400000000000000000000001621472033334600207250ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'spec_helper_head_core' require 'nanoc' require 'nanoc/orig_cli' nanoc-4.13.3/common/spec/spec_helper_head_core.rb000066400000000000000000000003001472033334600217270ustar00rootroot00000000000000# frozen_string_literal: true require 'simplecov' SimpleCov.start ENV['NANOC_DEV_MODE'] = 'true' require 'fuubar' require 'rspec/its' require 'timecop' require 'tty-command' require 'yard' nanoc-4.13.3/ddenv.yaml000066400000000000000000000003211472033334600146650ustar00rootroot00000000000000# This file defines the dependencies so that ddenv # (https://github.com/denisdefreyne/ddenv) can install and # set them up automatically. up: - homebrew: openjdk - homebrew: asciidoc - ruby - bundle nanoc-4.13.3/guard-nanoc/000077500000000000000000000000001472033334600151035ustar00rootroot00000000000000nanoc-4.13.3/guard-nanoc/.rspec000066400000000000000000000000611472033334600162150ustar00rootroot00000000000000-r ./spec/spec_helper.rb --format Fuubar --color nanoc-4.13.3/guard-nanoc/LICENSE000066400000000000000000000020711472033334600161100ustar00rootroot00000000000000Copyright (c) 2013–… 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.13.3/guard-nanoc/NEWS.md000066400000000000000000000024001472033334600161750ustar00rootroot00000000000000# guard-nanoc Release Notes ## 2.1.9 (2020-03-08) - Fixed an incompatibility with Nanoc 4.11.15. ## 2.1.8 (2020-03-07) - Fixed an incompatibility with Nanoc 4.11.14. ## 2.1.7 (2019-11-16) - Fixed an incompatibility with Nanoc 4.11.13. ## 2.1.6 (2019-02-16) - Fixed another incompatibility with Nanoc 4.11.1. ## 2.1.5 (2019-02-14) - Fixed an incompatibility with Nanoc 4.11.1. ## 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.13.3/guard-nanoc/README.md000066400000000000000000000002211472033334600163550ustar00rootroot00000000000000# Guard::Nanoc This project is deprecated in favor of [Nanoc’s built-in live recompilation](https://nanoc.app/doc/sites/#live-recompilation). nanoc-4.13.3/guard-nanoc/Rakefile000066400000000000000000000004341472033334600165510ustar00rootroot00000000000000# 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.13.3/guard-nanoc/guard-nanoc.gemspec000066400000000000000000000016571472033334600206570ustar00rootroot00000000000000# 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 = 'https://nanoc.app/' 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.required_ruby_version = '>= 3.1' s.add_dependency 'guard', '~> 2.8' s.add_dependency 'guard-compat', '~> 1.0' s.add_dependency 'nanoc-cli', '~> 4.11', '>= 4.11.14' s.add_dependency 'nanoc-core', '~> 4.11', '>= 4.11.14' s.files = Dir['[A-Z]*'] + Dir['lib/**/*'] + ['guard-nanoc.gemspec'] s.require_paths = ['lib'] s.metadata = { 'rubygems_mfa_required' => 'true', 'source_code_uri' => "https://github.com/nanoc/nanoc/tree/#{s.name}-v#{s.version}/#{s.name}", } end nanoc-4.13.3/guard-nanoc/lib/000077500000000000000000000000001472033334600156515ustar00rootroot00000000000000nanoc-4.13.3/guard-nanoc/lib/guard-nanoc.rb000066400000000000000000000000651472033334600203750ustar00rootroot00000000000000# frozen_string_literal: true require 'guard/nanoc' nanoc-4.13.3/guard-nanoc/lib/guard/000077500000000000000000000000001472033334600167535ustar00rootroot00000000000000nanoc-4.13.3/guard-nanoc/lib/guard/nanoc.rb000066400000000000000000000035051472033334600204010ustar00rootroot00000000000000# frozen_string_literal: true require 'guard/compat/plugin' require 'nanoc' require 'nanoc/orig_cli' module Guard class Nanoc < Plugin def self.live_cmd @_live_cmd ||= begin path = File.join(File.dirname(__FILE__), 'nanoc', 'live_command.rb') Cri::Command.load_file(path, infer_name: true) 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::CompileListeners::FileActionPrinter .new(reps: []) .start_safely end def recompile_in_subprocess if Process.respond_to?(:fork) pid = Process.fork { recompile } Process.waitpid(pid) else recompile end end def recompile # Necessary, because forking and threading don’t work together. ::Nanoc::Core::NotificationCenter.force_reset Dir.chdir(@dir) do site = ::Nanoc::Core::SiteLoader.new.new_from_cwd ::Nanoc::Core::Compiler.compile(site) 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.13.3/guard-nanoc/lib/guard/nanoc/000077500000000000000000000000001472033334600200515ustar00rootroot00000000000000nanoc-4.13.3/guard-nanoc/lib/guard/nanoc/live_command.rb000066400000000000000000000035401472033334600230350ustar00rootroot00000000000000# 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 option :H, :handler, 'specify the handler to use (webrick/puma/...)', argument: :required option :o, :host, 'specify the host to listen on', default: '127.0.0.1', argument: :required option :p, :port, 'specify the port to listen on', transform: Nanoc::CLI::Transform::Port, default: 3000, argument: :required flag :L, :'live-reload', 'reload on changes' module Guard class Nanoc class LiveCommand < ::Nanoc::CLI::CommandRunner def run require 'guard' require 'guard/commander' if defined?(Nanoc::Live) $stderr.puts '-' * 40 $stderr.puts 'NOTE:' $stderr.puts 'You are using the `nanoc live` command provided by `guard-nanoc`, but the `nanoc-live` gem is also installed, which also provides a `nanoc live` command.' if defined?(Bundler) $stderr.puts 'Recommendation: Remove `guard-nanoc` from your Gemfile.' else $stderr.puts 'Recommendation: Uninstall `guard-nanoc`.' end $stderr.puts '-' * 40 end Thread.new do break if ENV['__NANOC_DEV_LIVE_DISABLE_VIEW'] # 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 end runner Guard::Nanoc::LiveCommand nanoc-4.13.3/guard-nanoc/lib/guard/nanoc/templates/000077500000000000000000000000001472033334600220475ustar00rootroot00000000000000nanoc-4.13.3/guard-nanoc/lib/guard/nanoc/templates/Guardfile000066400000000000000000000003041472033334600236710ustar00rootroot00000000000000# 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.13.3/guard-nanoc/lib/guard/nanoc/version.rb000066400000000000000000000001201472033334600220540ustar00rootroot00000000000000# frozen_string_literal: true module Guard GUARD_NANOC_VERSION = '2.1.9' end nanoc-4.13.3/guard-nanoc/spec/000077500000000000000000000000001472033334600160355ustar00rootroot00000000000000nanoc-4.13.3/guard-nanoc/spec/gem_spec.rb000066400000000000000000000007001472033334600201410ustar00rootroot00000000000000# frozen_string_literal: true describe 'guard-nanoc.gem', chdir: false, stdio: true do subject do TTY::Command.new.run('gem build guard-nanoc.gemspec') end around do |ex| Dir['*.gem'].each { |f| FileUtils.rm(f) } ex.run Dir['*.gem'].each { |f| FileUtils.rm(f) } end it 'builds gem' do expect { subject } .to change { Dir['*.gem'] } .from([]) .to(include(match(/^guard-nanoc-.*\.gem$/))) end end nanoc-4.13.3/guard-nanoc/spec/lib/000077500000000000000000000000001472033334600166035ustar00rootroot00000000000000nanoc-4.13.3/guard-nanoc/spec/lib/guard/000077500000000000000000000000001472033334600177055ustar00rootroot00000000000000nanoc-4.13.3/guard-nanoc/spec/lib/guard/nanoc/000077500000000000000000000000001472033334600210035ustar00rootroot00000000000000nanoc-4.13.3/guard-nanoc/spec/lib/guard/nanoc/live_command_spec.rb000066400000000000000000000006701472033334600250020ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe Guard::Nanoc do around do |example| Dir.mktmpdir('nanoc-test') do |dir| __nanoc_core_chdir(dir) do Nanoc::CLI.run(%w[create-site foo]) __nanoc_core_chdir('foo') do example.run end end end end it 'loads the command properly' do expect { Nanoc::CLI.run(%w[live]) }.to raise_error(/No Guardfile found, please create one/) end end nanoc-4.13.3/guard-nanoc/spec/lib/guard/nanoc_spec.rb000066400000000000000000000033261472033334600223460ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe Guard::Nanoc do around do |example| Dir.mktmpdir('nanoc-test') do |dir| __nanoc_core_chdir(dir) do Nanoc::CLI.run(%w[create-site foo]) __nanoc_core_chdir('foo') do example.run end end end end 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 = described_class.live_cmd expect(live_cmd.option_definitions).not_to eq(view_cmd.option_definitions) end end end nanoc-4.13.3/guard-nanoc/spec/spec_helper.rb000066400000000000000000000014061472033334600206540ustar00rootroot00000000000000# frozen_string_literal: true require_relative '../../common/spec/spec_helper_head' module Guard # `guard-compat` defines `Notifier` as a module rather than a class. We define # `Notifier` as a class here, before requiring guard-compat and guard, so that # `guard` does not break. class Notifier end end require 'guard/compat/test/helper' require 'guard/nanoc' require_relative '../../common/spec/spec_helper_foot' ENV['__NANOC_DEV_LIVE_DISABLE_VIEW'] = '1' RSpec.configure do |config| # Swallow stdout/stderr config.around 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 end nanoc-4.13.3/nanoc-checking/000077500000000000000000000000001472033334600155545ustar00rootroot00000000000000nanoc-4.13.3/nanoc-checking/.rspec000066400000000000000000000000611472033334600166660ustar00rootroot00000000000000-r ./spec/spec_helper.rb --format Fuubar --color nanoc-4.13.3/nanoc-checking/LICENSE000066400000000000000000000020711472033334600165610ustar00rootroot00000000000000Copyright (c) 2014–… 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.13.3/nanoc-checking/NEWS.md000066400000000000000000000007731472033334600166610ustar00rootroot00000000000000# nanoc-checking news ## 1.0.5 (2024-04-21) Fixes: - Fixed support for paths with spaces in internal_links check (#1702) ## 1.0.4 (2024-04-19) Enhancements: - Added support for `javascript:` pseudo-URLs (#1698) ## 1.0.3 (2024-03-15) Fixes: - Restore compatibility with Nanoc 4.12.20 ## 1.0.2 (2022-01-15) Fixes: - Ensure compatibility with latest version of Nanoc ## 1.0.1 (2021-01-01) Enhancements: - Added support for Ruby 3.x ## 1.0.0 (2020-03-07) Initial release (extracted from nanoc) nanoc-4.13.3/nanoc-checking/README.md000066400000000000000000000003611472033334600170330ustar00rootroot00000000000000# nanoc-checking This provides the `check` command and associated functionality for [Nanoc](https://nanoc.app). For details, see the [Checking correctness of Nanoc sites](https://nanoc.app/doc/testing/) chapter of the Nanoc documentation. nanoc-4.13.3/nanoc-checking/Rakefile000066400000000000000000000006731472033334600172270ustar00rootroot00000000000000# frozen_string_literal: true require 'rake/testtask' require 'rspec/core/rake_task' require 'rubocop/rake_task' RuboCop::RakeTask.new(:rubocop) RSpec::Core::RakeTask.new(:spec) do |t| t.verbose = false end Rake::TestTask.new(:test_all) do |t| t.test_files = Dir['test/**/test_*.rb'] t.libs << 'test' 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.13.3/nanoc-checking/lib/000077500000000000000000000000001472033334600163225ustar00rootroot00000000000000nanoc-4.13.3/nanoc-checking/lib/nanoc-checking.rb000066400000000000000000000000701472033334600215130ustar00rootroot00000000000000# frozen_string_literal: true require 'nanoc/checking' nanoc-4.13.3/nanoc-checking/lib/nanoc/000077500000000000000000000000001472033334600174205ustar00rootroot00000000000000nanoc-4.13.3/nanoc-checking/lib/nanoc/checking.rb000066400000000000000000000012751472033334600215250ustar00rootroot00000000000000# frozen_string_literal: true require 'nanoc-core' require 'nanoc-cli' module Nanoc module Checking end end require_relative 'checking/version' require_relative 'checking/check' require_relative 'checking/checks' require_relative 'checking/command_runners' require_relative 'checking/dsl' require_relative 'checking/link_collector' require_relative 'checking/runner' require_relative 'checking/loader' require_relative 'checking/issue' root = File.dirname(__FILE__) checking_command_path = File.join(root, 'checking', 'commands', 'check.rb') check_command = Cri::Command.load_file(checking_command_path, infer_name: true) Nanoc::CLI.after_setup do Nanoc::CLI.add_command(check_command) end nanoc-4.13.3/nanoc-checking/lib/nanoc/checking/000077500000000000000000000000001472033334600211735ustar00rootroot00000000000000nanoc-4.13.3/nanoc-checking/lib/nanoc/checking/check.rb000066400000000000000000000050351472033334600226000ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Checking # @api private class OutputDirNotFoundError < ::Nanoc::Core::Error 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::Core::Context extend DDPlugin::Plugin prepend MemoWise attr_reader :issues def self.define(ident, &block) klass = Class.new(self) { 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::Core::Compiler.new_for(site) res = compiler.run_until_reps_built reps = res.fetch(:reps) view_context = Nanoc::Core::ViewContextForShell.new( items: site.items, reps:, ) context = { items: Nanoc::Core::PostCompileItemCollectionView.new(site.items, view_context), layouts: Nanoc::Core::LayoutCollectionView.new(site.layouts, view_context), config: Nanoc::Core::ConfigView.new(site.config, view_context), output_filenames:, } new(context) end def initialize(context) super @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_filenames super.reject { |f| excluded_patterns.any? { |pat| pat.match?(f) } } end # @private def excluded_patterns @config .fetch(:checks, {}) .fetch(:all, {}) .fetch(:exclude_files, []) .map { |pattern| Regexp.new(pattern) } end memo_wise :excluded_patterns # @private def output_html_filenames output_filenames.select { |f| File.extname(f) =~ /\A\.x?html?\z/ } end end end end nanoc-4.13.3/nanoc-checking/lib/nanoc/checking/checks.rb000066400000000000000000000005621472033334600227630ustar00rootroot00000000000000# frozen_string_literal: true # @api private module Nanoc module Checking module Checks end end 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.13.3/nanoc-checking/lib/nanoc/checking/checks/000077500000000000000000000000001472033334600224335ustar00rootroot00000000000000nanoc-4.13.3/nanoc-checking/lib/nanoc/checking/checks/css.rb000066400000000000000000000005271472033334600235540ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Checking module Checks # @api private class CSS < ::Nanoc::Checking::Checks::W3CValidator identifier :css def extension 'css' end def validator_class ::W3CValidators::CSSValidator end end end end end nanoc-4.13.3/nanoc-checking/lib/nanoc/checking/checks/external_links.rb000066400000000000000000000114641472033334600260100ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Checking module 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::Checking::LinkCollector.new(filenames, :external).filenames_per_href results = select_invalid(hrefs_with_filenames.keys.shuffle) # 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) # Skip javascript: URLs # # This needs to be handled explicitly, because URI.parse does not # like `javascript:` URLs -- presumably because those are not # technically valid URLs. return nil if href.start_with?('javascript:') # 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 case res.code when /^3..$/ 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? # ignore redirects back onto self (misused to set HTTP cookies) return nil if href == location if /^30[18]$/.match?(res.code) return Result.new(href, "link has moved permanently to '#{location}'") end url = URI.parse(location) when '200' return nil else return Result.new(href, res.code) end end if last_err Result.new(href, last_err.message) else raise Nanoc::Core::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)) req['User-Agent'] = "Mozilla/5.0 Nanoc/#{Nanoc::VERSION} (link rot checker)" 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 end end nanoc-4.13.3/nanoc-checking/lib/nanoc/checking/checks/html.rb000066400000000000000000000005371472033334600237310ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Checking module Checks # @api private class HTML < ::Nanoc::Checking::Checks::W3CValidator identifier :html def extension '{htm,html}' end def validator_class ::W3CValidators::NuValidator end end end end end nanoc-4.13.3/nanoc-checking/lib/nanoc/checking/checks/internal_links.rb000066400000000000000000000063051472033334600260000ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Checking module 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 uris = ::Nanoc::Checking::LinkCollector.new(filenames, :internal).filenames_per_href 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 == '.' # Turn file: into output_dir-as-root relative paths output_dir = @config.output_dir output_dir += '/' unless output_dir.end_with?('/') # FIXME: escape is hacky base_uri = URI("file://#{output_dir.gsub(' ', '%20')}") path = href.sub(/#{base_uri}/, '').sub(/file:\/{1,3}/, '') path = "/#{path}" unless path.start_with?('/') # Skip hrefs that are specified in the exclude configuration return true if excluded?(path, origin) # Make an absolute path path = ::File.join(output_dir, path[1..path.length]) # Remove fragment path = path.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) # 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..] excludes = config.fetch(:exclude_origins, []) excludes.any? { |pattern| Regexp.new(pattern).match(relative_origin) } end end end end end nanoc-4.13.3/nanoc-checking/lib/nanoc/checking/checks/mixed_content.rb000066400000000000000000000020631472033334600256210ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Checking module Checks # A check that verifies HTML files do not reference external resources with # URLs that would trigger "mixed content" warnings. # # @api private class MixedContent < ::Nanoc::Checking::Check identifier :mixed_content PROTOCOL_PATTERN = /^(\w+):\/\// def run filenames = output_html_filenames resource_uris_with_filenames = ::Nanoc::Checking::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 end end nanoc-4.13.3/nanoc-checking/lib/nanoc/checking/checks/stale.rb000066400000000000000000000021571472033334600240750ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Checking module 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 @_pruner ||= begin exclude_config = @config.fetch(:prune, {}).fetch(:exclude, []) # FIXME: specifying reps this way is icky reps = Nanoc::Core::ItemRepRepo.new Nanoc::Core::Pruner.new(@config._unwrap, reps, exclude: exclude_config) end end end end end end nanoc-4.13.3/nanoc-checking/lib/nanoc/checking/checks/w3c_validator.rb000066400000000000000000000016701472033334600255250ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Checking module 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 end end nanoc-4.13.3/nanoc-checking/lib/nanoc/checking/command_runners.rb000066400000000000000000000002431472033334600247110ustar00rootroot00000000000000# frozen_string_literal: true # @api private module Nanoc module Checking module CommandRunners end end end require_relative 'command_runners/check' nanoc-4.13.3/nanoc-checking/lib/nanoc/checking/command_runners/000077500000000000000000000000001472033334600243655ustar00rootroot00000000000000nanoc-4.13.3/nanoc-checking/lib/nanoc/checking/command_runners/check.rb000066400000000000000000000014101472033334600257630ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Checking module CommandRunners 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::Core::TrivialError, 'One or more checks failed' end end end end end end nanoc-4.13.3/nanoc-checking/lib/nanoc/checking/commands/000077500000000000000000000000001472033334600227745ustar00rootroot00000000000000nanoc-4.13.3/nanoc-checking/lib/nanoc/checking/commands/check.rb000066400000000000000000000006611472033334600244010ustar00rootroot00000000000000# 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)' runner Nanoc::Checking::CommandRunners::Check nanoc-4.13.3/nanoc-checking/lib/nanoc/checking/dsl.rb000066400000000000000000000013151472033334600223020ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Checking # @api private class DSL def self.from_file(filename, enabled_checks:) dsl = new(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, &) klass = Class.new(::Nanoc::Checking::Check) klass.send(:define_method, :run, &) klass.send(:identifier, identifier) end def deploy_check(*identifiers) identifiers.each { |i| @enabled_checks << i } end end end end nanoc-4.13.3/nanoc-checking/lib/nanoc/checking/issue.rb000066400000000000000000000005521472033334600226520ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module 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 end nanoc-4.13.3/nanoc-checking/lib/nanoc/checking/link_collector.rb000066400000000000000000000071751472033334600245350ustar00rootroot00000000000000# frozen_string_literal: true module ::Nanoc module Checking class LinkCollector # HTML5 element attributes URI_ATTRS = { 'a' => %i[href ping], 'area' => %i[href ping], 'audio' => %i[src], 'base' => %i[href], 'blockquote' => %i[cite], 'form' => %i[action], 'iframe' => %i[src], 'img' => %i[src srcset], 'link' => %i[href], 'object' => %i[data], 'script' => %i[src], 'source' => %i[src srcset], 'video' => %i[poster src], }.freeze # HTML+RDFa global URI attributes GLOBAL_ATTRS = %i[about resource].freeze def initialize(filenames, mode = nil) @filenames = filenames @filter = case mode when nil ->(_h) { true } when :external ->(h) { external_href?(h) } when :internal ->(h) { internal_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) return false if internal_href?(href) href =~ %r{^(//|[a-z-]+:)} end def internal_href?(href) return false if href.nil? href.start_with?('file:/') end # all links def hrefs_in_file(filename) uris_in_file filename, nil end # embedded resources, used by the mixed-content checker def resource_uris_in_file(filename) uris_in_file filename, %w[audio base form iframe img link object script source 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 # FIXME: escape is hacky base_uri = URI("file://#{filename.gsub(' ', '%20')}") doc = Nokogiri::HTML(::File.read(filename)) doc.traverse do |tag| next unless tag_names.nil? || tag_names.include?(tag.name) attrs = [] attrs += URI_ATTRS[tag.name] unless URI_ATTRS[tag.name].nil? attrs += GLOBAL_ATTRS if tag_names.nil? next if attrs.nil? attrs.each do |attr_name| next if tag[attr_name].nil? if attr_name == :srcset uris = uris.merge(tag[attr_name].split(',').map { |v| v.strip.split[0].strip }.compact) elsif %i[about ping resource].include?(attr_name) uris = uris.merge(tag[attr_name].split.map(&:strip).compact) else uris << tag[attr_name.to_s] end end end # Strip fragment uris.map! { |uri| uri.gsub(/#.*$/, '') } # Resolve paths relative to the filename, return invalid URIs as-is uris.map! do |uri| if uri.start_with?('//') # Don’t modify protocol-relative URLs. They’re absolute! uri else begin # FIXME: escape is hacky URI.join(base_uri, uri.gsub(' ', '%20')).to_s rescue uri end end end uris.select(&@filter) end end end end nanoc-4.13.3/nanoc-checking/lib/nanoc/checking/loader.rb000066400000000000000000000022011472033334600227610ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module 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 end nanoc-4.13.3/nanoc-checking/lib/nanoc/checking/runner.rb000066400000000000000000000064541472033334600230420ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Checking # Runner is reponsible for running issue checks. # # @api private class Runner # @param [Nanoc::Core::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? 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::Core::TrivialError, "Unknown check: #{name}" if klass.nil? klass end end def run_checks(classes) return [] if classes.empty? # TODO: remove me Nanoc::Core::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 end nanoc-4.13.3/nanoc-checking/lib/nanoc/checking/version.rb000066400000000000000000000001361472033334600232050ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Checking VERSION = '1.0.5' end end nanoc-4.13.3/nanoc-checking/nanoc-checking.gemspec000066400000000000000000000015161472033334600217730ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'lib/nanoc/checking/version' Gem::Specification.new do |s| s.name = 'nanoc-checking' s.version = Nanoc::Checking::VERSION s.homepage = 'https://nanoc.app/' s.summary = 'Checking support for Nanoc' s.description = 'Provides checking functionality 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 = '>= 3.1' s.add_dependency('nanoc-cli', '~> 4.12', '>= 4.12.5') s.add_dependency('nanoc-core', '~> 4.12', '>= 4.12.5') s.metadata = { 'rubygems_mfa_required' => 'true', 'source_code_uri' => "https://github.com/nanoc/nanoc/tree/#{s.name}-v#{s.version}/#{s.name}", } end nanoc-4.13.3/nanoc-checking/nanoc-checking.manifest000066400000000000000000000012661472033334600221600ustar00rootroot00000000000000NEWS.md README.md lib/nanoc-checking.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/command_runners.rb lib/nanoc/checking/command_runners/check.rb lib/nanoc/checking/commands/check.rb lib/nanoc/checking/dsl.rb lib/nanoc/checking/issue.rb lib/nanoc/checking/link_collector.rb lib/nanoc/checking/loader.rb lib/nanoc/checking/runner.rb lib/nanoc/checking/version.rb nanoc-4.13.3/nanoc-checking/spec/000077500000000000000000000000001472033334600165065ustar00rootroot00000000000000nanoc-4.13.3/nanoc-checking/spec/fixtures/000077500000000000000000000000001472033334600203575ustar00rootroot00000000000000nanoc-4.13.3/nanoc-checking/spec/fixtures/vcr_cassettes/000077500000000000000000000000001472033334600232275ustar00rootroot00000000000000nanoc-4.13.3/nanoc-checking/spec/fixtures/vcr_cassettes/css_run_error.yml000066400000000000000000000102221472033334600266340ustar00rootroot00000000000000--- http_interactions: - request: method: post uri: https://jigsaw.w3.org/css-validator/validator body: encoding: UTF-8 string: "--349832898984244898448024464570528145\r\nContent-Disposition: form-data; name=\"profile\"\r\n\r\ncss3\r\n--349832898984244898448024464570528145\r\nContent-Disposition: form-data; name=\"text\"\r\n\r\nh1 { coxlor: rxed; }\r\n--349832898984244898448024464570528145\r\nContent-Disposition: form-data; name=\"output\"\r\n\r\nsoap12\r\n--349832898984244898448024464570528145--\r\n" headers: Content-Type: - multipart/form-data; boundary=349832898984244898448024464570528145 Accept-Encoding: - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 Accept: - "*/*" User-Agent: - Ruby response: status: code: 200 message: OK headers: Cache-Control: - no-cache Date: - Fri, 17 Aug 2018 08:55:44 GMT Pragma: - no-cache Transfer-Encoding: - chunked Content-Language: - en Content-Type: - application/soap+xml;charset=utf-8 Server: - Jigsaw/2.3.0-beta4 Vary: - Accept-Language Access-Control-Allow-Origin: - "*" Access-Control-Allow-Headers: - content-type,accept-charset Access-Control-Allow-Methods: - GET, HEAD, POST, OPTIONS Access-Control-Max-Age: - '600' X-W3c-Validator-Errors: - '1' X-W3c-Validator-Status: - Invalid Strict-Transport-Security: - max-age=15552015; includeSubDomains; preload Public-Key-Pins: - pin-sha256="cN0QSpPIkuwpT6iP2YjEo1bEwGpH/yiUn6yhdy+HNto="; pin-sha256="WGJkyYjx1QMdMe0UqlyOKXtydPDVrk7sl2fV+nNm1r4="; pin-sha256="LrKdTxZLRTvyHM4/atX2nquX9BeHRZMCxg3cf4rhc2I="; max-age=864000 X-Frame-Options: - deny X-Xss-Protection: - 1; mode=block body: encoding: UTF-8 string: "\n\n \ \n \n TextArea\n \ http://jigsaw.w3.org/css-validator/\n \ css3\n 2018-08-17T08:55:44Z\n \ false\n \n \n 1\n \n \ \n file://localhost/TextArea\n \ \n \n 1\n \ parse-error\n h1 \ \n \n exp\n \ \n \n \ rxed\n \n \ noexistence-typo\n \n \ \n \n Property “coxlor” doesn't exist. The closest matching property name is “color” : \n \n \n \ \n \n \n \n \ \n 0\n \ \n \n \n \ \n\n\n" http_version: recorded_at: Fri, 17 Aug 2018 08:55:44 GMT recorded_with: VCR 4.0.0 nanoc-4.13.3/nanoc-checking/spec/fixtures/vcr_cassettes/css_run_ok.yml000066400000000000000000000057761472033334600261360ustar00rootroot00000000000000--- http_interactions: - request: method: post uri: https://jigsaw.w3.org/css-validator/validator body: encoding: UTF-8 string: "--349832898984244898448024464570528145\r\nContent-Disposition: form-data; name=\"profile\"\r\n\r\ncss3\r\n--349832898984244898448024464570528145\r\nContent-Disposition: form-data; name=\"text\"\r\n\r\nh1 { color: red; }\r\n--349832898984244898448024464570528145\r\nContent-Disposition: form-data; name=\"output\"\r\n\r\nsoap12\r\n--349832898984244898448024464570528145--\r\n" headers: Content-Type: - multipart/form-data; boundary=349832898984244898448024464570528145 Accept-Encoding: - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 Accept: - "*/*" User-Agent: - Ruby response: status: code: 200 message: OK headers: Cache-Control: - no-cache Date: - Fri, 17 Aug 2018 08:55:44 GMT Pragma: - no-cache Transfer-Encoding: - chunked Content-Language: - en Content-Type: - application/soap+xml;charset=utf-8 Server: - Jigsaw/2.3.0-beta4 Vary: - Accept-Language Access-Control-Allow-Origin: - "*" Access-Control-Allow-Headers: - content-type,accept-charset Access-Control-Allow-Methods: - GET, HEAD, POST, OPTIONS Access-Control-Max-Age: - '600' X-W3c-Validator-Errors: - '0' X-W3c-Validator-Status: - Valid Strict-Transport-Security: - max-age=15552015; includeSubDomains; preload Public-Key-Pins: - pin-sha256="cN0QSpPIkuwpT6iP2YjEo1bEwGpH/yiUn6yhdy+HNto="; pin-sha256="WGJkyYjx1QMdMe0UqlyOKXtydPDVrk7sl2fV+nNm1r4="; pin-sha256="LrKdTxZLRTvyHM4/atX2nquX9BeHRZMCxg3cf4rhc2I="; max-age=864000 X-Frame-Options: - deny X-Xss-Protection: - 1; mode=block body: encoding: UTF-8 string: "\n\n \ \n \n TextArea\n \ http://jigsaw.w3.org/css-validator/\n \ css3\n 2018-08-17T08:55:44Z\n \ true\n \n \n 0\n \n \ \n \n \ 0\n \n \ \n \n \n\n\n" http_version: recorded_at: Fri, 17 Aug 2018 08:55:44 GMT recorded_with: VCR 4.0.0 nanoc-4.13.3/nanoc-checking/spec/fixtures/vcr_cassettes/css_run_parse_error.yml000066400000000000000000000100501472033334600300250ustar00rootroot00000000000000--- http_interactions: - request: method: post uri: https://jigsaw.w3.org/css-validator/validator body: encoding: UTF-8 string: "--349832898984244898448024464570528145\r\nContent-Disposition: form-data; name=\"profile\"\r\n\r\ncss3\r\n--349832898984244898448024464570528145\r\nContent-Disposition: form-data; name=\"text\"\r\n\r\nh1 { ; {\r\n--349832898984244898448024464570528145\r\nContent-Disposition: form-data; name=\"output\"\r\n\r\nsoap12\r\n--349832898984244898448024464570528145--\r\n" headers: Content-Type: - multipart/form-data; boundary=349832898984244898448024464570528145 Accept-Encoding: - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 Accept: - "*/*" User-Agent: - Ruby response: status: code: 200 message: OK headers: Cache-Control: - no-cache Date: - Fri, 17 Aug 2018 08:55:45 GMT Pragma: - no-cache Transfer-Encoding: - chunked Content-Language: - en Content-Type: - application/soap+xml;charset=utf-8 Server: - Jigsaw/2.3.0-beta3 Vary: - Accept-Language Access-Control-Allow-Origin: - "*" Access-Control-Allow-Headers: - content-type,accept-charset Access-Control-Allow-Methods: - GET, HEAD, POST, OPTIONS Access-Control-Max-Age: - '600' X-W3c-Validator-Errors: - '1' X-W3c-Validator-Status: - Invalid Strict-Transport-Security: - max-age=15552015; includeSubDomains; preload Public-Key-Pins: - pin-sha256="cN0QSpPIkuwpT6iP2YjEo1bEwGpH/yiUn6yhdy+HNto="; pin-sha256="WGJkyYjx1QMdMe0UqlyOKXtydPDVrk7sl2fV+nNm1r4="; pin-sha256="LrKdTxZLRTvyHM4/atX2nquX9BeHRZMCxg3cf4rhc2I="; max-age=864000 X-Frame-Options: - deny X-Xss-Protection: - 1; mode=block body: encoding: UTF-8 string: "\n\n \ \n \n TextArea\n \ http://jigsaw.w3.org/css-validator/\n \ css3\n 2018-08-17T08:55:45Z\n \ false\n \n \n 1\n \n \ \n file://localhost/TextArea\n \ \n \n 1\n \ parse-error\n h1 \ \n \n unrecognized\n \ \n \n \ ; {\n \n \ generator.unrecognize\n \n \ \n \n Parse Error\n \n \n \ \n \n \n \n \ \n 0\n \ \n \n \n \ \n\n\n" http_version: recorded_at: Fri, 17 Aug 2018 08:55:45 GMT recorded_with: VCR 4.0.0 nanoc-4.13.3/nanoc-checking/spec/fixtures/vcr_cassettes/external_links_some_files_excluded.yml000066400000000000000000000111161472033334600330560ustar00rootroot00000000000000--- http_interactions: - request: method: get uri: http://example.com/x body: encoding: US-ASCII string: '' headers: Accept-Encoding: - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 Accept: - "*/*" User-Agent: - Mozilla/5.0 Nanoc/4.11.14 (link rot checker) response: status: code: 404 message: Not Found headers: Accept-Ranges: - bytes Cache-Control: - max-age=604800 Content-Type: - text/html; charset=UTF-8 Date: - Mon, 25 Nov 2019 20:47:21 GMT Expires: - Mon, 02 Dec 2019 20:47:21 GMT Last-Modified: - Fri, 22 Nov 2019 21:50:34 GMT Server: - ECS (nyb/1D23) Vary: - Accept-Encoding X-Cache: - 404-HIT Content-Length: - '648' body: encoding: ASCII-8BIT string: "\n\n\n Example Domain\n\n \ \n \n \n \n\n\n\n
\n

Example Domain

\n \

This domain is for use in illustrative examples in documents. You may use this\n domain in literature without prior coordination or asking for permission.

\n

More information...

\n
\n\n\n" http_version: recorded_at: Mon, 25 Nov 2019 20:47:21 GMT - request: method: get uri: http://example.com/ink_luded body: encoding: US-ASCII string: '' headers: Accept-Encoding: - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 Accept: - "*/*" User-Agent: - Mozilla/5.0 Nanoc/4.11.14 (link rot checker) response: status: code: 404 message: Not Found headers: Cache-Control: - max-age=604800 Content-Type: - text/html; charset=UTF-8 Date: - Mon, 25 Nov 2019 20:47:21 GMT Expires: - Mon, 02 Dec 2019 20:47:21 GMT Server: - EOS (vny006/0450) Vary: - Accept-Encoding Content-Length: - '648' body: encoding: ASCII-8BIT string: "\n\n\n Example Domain\n\n \ \n \n \n \n\n\n\n
\n

Example Domain

\n \

This domain is for use in illustrative examples in documents. You may use this\n domain in literature without prior coordination or asking for permission.

\n

More information...

\n
\n\n\n" http_version: recorded_at: Mon, 25 Nov 2019 20:47:21 GMT recorded_with: VCR 5.0.0 nanoc-4.13.3/nanoc-checking/spec/fixtures/vcr_cassettes/html_run_error.yml000066400000000000000000000044501472033334600270160ustar00rootroot00000000000000--- http_interactions: - request: method: post uri: https://validator.w3.org/nu/?out=json&parser=html&showsource=yes body: encoding: UTF-8 string: "

Hi!

" headers: Content-Type: - text/html; charset=utf-8 Accept-Encoding: - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 Accept: - "*/*" User-Agent: - Ruby response: status: code: 200 message: OK headers: Date: - Fri, 30 Jun 2017 20:45:18 GMT Accept-Encoding: - gzip Access-Control-Allow-Origin: - "*" Access-Control-Allow-Headers: - content-type Expires: - Thu, 01 Jan 1970 00:00:00 GMT Cache-Control: - no-cache Content-Type: - application/json; charset=utf-8 Transfer-Encoding: - chunked Server: - Jetty(9.2.9.v20150224) Strict-Transport-Security: - max-age=15552015; preload Public-Key-Pins: - pin-sha256="cN0QSpPIkuwpT6iP2YjEo1bEwGpH/yiUn6yhdy+HNto="; pin-sha256="WGJkyYjx1QMdMe0UqlyOKXtydPDVrk7sl2fV+nNm1r4="; pin-sha256="LrKdTxZLRTvyHM4/atX2nquX9BeHRZMCxg3cf4rhc2I="; max-age=864000 X-Frame-Options: - deny X-Xss-Protection: - 1; mode=block body: encoding: ASCII-8BIT string: !binary |- eyJtZXNzYWdlcyI6W3sidHlwZSI6ImVycm9yIiwibGFzdExpbmUiOjEsImxhc3RDb2x1bW4iOjQsImZpcnN0Q29sdW1uIjoxLCJtZXNzYWdlIjoiU3RhcnQgdGFnIHNlZW4gd2l0aG91dCBzZWVpbmcgYSBkb2N0eXBlIGZpcnN0LiBFeHBlY3RlZCBlLmcuIOKAnDwhRE9DVFlQRSBodG1sPuKAnS4iLCJleHRyYWN0IjoiPGgyPkhpITwvaCIsImhpbGl0ZVN0YXJ0IjowLCJoaWxpdGVMZW5ndGgiOjR9LHsidHlwZSI6ImVycm9yIiwibGFzdExpbmUiOjEsImxhc3RDb2x1bW4iOjQsImZpcnN0Q29sdW1uIjoxLCJtZXNzYWdlIjoiRWxlbWVudCDigJxoZWFk4oCdIGlzIG1pc3NpbmcgYSByZXF1aXJlZCBpbnN0YW5jZSBvZiBjaGlsZCBlbGVtZW50IOKAnHRpdGxl4oCdLiIsImV4dHJhY3QiOiI8aDI+SGkhPC9oIiwiaGlsaXRlU3RhcnQiOjAsImhpbGl0ZUxlbmd0aCI6NH0seyJ0eXBlIjoiZXJyb3IiLCJsYXN0TGluZSI6MSwibGFzdENvbHVtbiI6MTIsImZpcnN0Q29sdW1uIjo4LCJtZXNzYWdlIjoiRW5kIHRhZyDigJxoMeKAnSBzZWVuLCBidXQgdGhlcmUgd2VyZSBvcGVuIGVsZW1lbnRzLiIsImV4dHJhY3QiOiI8aDI+SGkhPC9oMT4iLCJoaWxpdGVTdGFydCI6NywiaGlsaXRlTGVuZ3RoIjo1fV0sInNvdXJjZSI6eyJ0eXBlIjoidGV4dC9odG1sIiwiZW5jb2RpbmciOiJVVEYtOCIsImNvZGUiOiI8aDI+SGkhPC9oMT4ifX0K http_version: recorded_at: Fri, 30 Jun 2017 20:45:19 GMT recorded_with: VCR 3.0.3 nanoc-4.13.3/nanoc-checking/spec/fixtures/vcr_cassettes/html_run_ok.yml000066400000000000000000000032101472033334600262670ustar00rootroot00000000000000--- http_interactions: - request: method: post uri: https://validator.w3.org/nu/?out=json&parser=html&showsource=yes body: encoding: UTF-8 string: Hello

Hi!

headers: Content-Type: - text/html; charset=utf-8 Accept-Encoding: - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 Accept: - "*/*" User-Agent: - Ruby response: status: code: 200 message: OK headers: Date: - Fri, 30 Jun 2017 20:45:19 GMT Accept-Encoding: - gzip Access-Control-Allow-Origin: - "*" Access-Control-Allow-Headers: - content-type Expires: - Thu, 01 Jan 1970 00:00:00 GMT Cache-Control: - no-cache Content-Type: - application/json; charset=utf-8 Transfer-Encoding: - chunked Server: - Jetty(9.2.9.v20150224) Strict-Transport-Security: - max-age=15552015; preload Public-Key-Pins: - pin-sha256="cN0QSpPIkuwpT6iP2YjEo1bEwGpH/yiUn6yhdy+HNto="; pin-sha256="WGJkyYjx1QMdMe0UqlyOKXtydPDVrk7sl2fV+nNm1r4="; pin-sha256="LrKdTxZLRTvyHM4/atX2nquX9BeHRZMCxg3cf4rhc2I="; max-age=864000 X-Frame-Options: - deny X-Xss-Protection: - 1; mode=block body: encoding: UTF-8 string: '{"messages":[],"source":{"type":"text/html","encoding":"UTF-8","code":"Hello

Hi!

"}} ' http_version: recorded_at: Fri, 30 Jun 2017 20:45:19 GMT recorded_with: VCR 3.0.3 nanoc-4.13.3/nanoc-checking/spec/gem_spec.rb000066400000000000000000000007111472033334600206140ustar00rootroot00000000000000# frozen_string_literal: true describe 'nanoc-checking.gem', chdir: false, stdio: true do subject do TTY::Command.new.run('gem build nanoc-checking.gemspec') end around do |ex| Dir['*.gem'].each { |f| FileUtils.rm(f) } ex.run Dir['*.gem'].each { |f| FileUtils.rm(f) } end it 'builds gem' do expect { subject } .to change { Dir['*.gem'] } .from([]) .to(include(match(/^nanoc-checking-.*\.gem$/))) end end nanoc-4.13.3/nanoc-checking/spec/manifest_spec.rb000066400000000000000000000002211472033334600216460ustar00rootroot00000000000000# frozen_string_literal: true describe 'manifest', chdir: false do example do expect('nanoc-checking').to have_a_valid_manifest end end nanoc-4.13.3/nanoc-checking/spec/nanoc/000077500000000000000000000000001472033334600176045ustar00rootroot00000000000000nanoc-4.13.3/nanoc-checking/spec/nanoc/checking/000077500000000000000000000000001472033334600213575ustar00rootroot00000000000000nanoc-4.13.3/nanoc-checking/spec/nanoc/checking/check_spec.rb000066400000000000000000000125651472033334600240040ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Checking::Check do let(:config) do Nanoc::Core::Configuration.new( dir: Dir.getwd, hash: config_hash, ).with_defaults end let(:config_hash) { {} } let(:reps) { Nanoc::Core::ItemRepRepo.new } let(:site) do Nanoc::Core::Site.new( config:, code_snippets: [], data_source: Nanoc::Core::InMemoryDataSource.new(items, layouts), ) end let(:items) { Nanoc::Core::ItemCollection.new(config, []) } let(:layouts) { Nanoc::Core::LayoutCollection.new(config, []) } let(:view_context) do Nanoc::Core::ViewContextForCompilation.new( reps:, items:, dependency_tracker:, compilation_context:, compiled_content_store:, ) end let(:compilation_context) do Nanoc::Core::CompilationContext.new( action_provider:, reps:, site:, compiled_content_cache:, compiled_content_store:, ) end let(:action_provider) do Class.new(Nanoc::Core::ActionProvider) do def self.for(_context) raise NotImplementedError end def initialize; end end.new end let(:compiled_content_cache) { Nanoc::Core::CompiledContentCache.new(config:) } let(:compiled_content_store) { Nanoc::Core::CompiledContentStore.new } let(:dependency_tracker) { Nanoc::Core::DependencyTracker::Null.new } describe '.create' do let(:check_class) { described_class.named(:internal_links) } before do File.write('Rules', 'passthrough "/**/*"') end context 'output dir exists' do before do FileUtils.mkdir_p('output') end it 'does nor raise' do expect { check_class.create(site) }.not_to raise_error end end context 'output dir does not exist' do it 'raises' do expect { check_class.create(site) } .to raise_error(Nanoc::Checking::OutputDirNotFoundError) end end end describe '.define' do before do described_class.define(:spec_check_example_1) do add_issue('it’s totes bad') end 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_filenames' do subject { check.output_filenames } let(:check) do described_class.new( output_filenames:, config: Nanoc::Core::ConfigView.new(config, view_context), ) end let(:output_filenames) do [ 'output/foo.html', 'output/foo.htm', 'output/foo.xhtml', 'output/foo.txt', 'output/foo.htmlx', 'output/foo.yhtml', ] end context 'when exclude_files is unset' do it { is_expected.to include('output/foo.htm') } it { is_expected.to include('output/foo.html') } it { is_expected.to include('output/foo.htmlx') } it { is_expected.to include('output/foo.txt') } it { is_expected.to include('output/foo.xhtml') } it { is_expected.to include('output/foo.yhtml') } end context 'when exclude_files is set' do let(:config_hash) do { checks: { all: { exclude_files: ['foo.xhtml'] } } } end it { is_expected.to include('output/foo.htm') } it { is_expected.to include('output/foo.html') } it { is_expected.to include('output/foo.htmlx') } it { is_expected.to include('output/foo.txt') } it { is_expected.to include('output/foo.yhtml') } it { is_expected.not_to include('output/foo.xhtml') } end end describe '#output_html_filenames' do subject { check.output_html_filenames } let(:check) do described_class.new( output_filenames:, config: Nanoc::Core::ConfigView.new(config, view_context), ) end let(:output_filenames) do [ 'output/foo.html', 'output/foo.htm', 'output/foo.xhtml', 'output/foo.txt', 'output/foo.htmlx', 'output/foo.yhtml', ] end context 'when exclude_files is unset' do 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 context 'when exclude_files is set' do let(:config_hash) do { checks: { all: { exclude_files: ['foo.xhtml'] } } } end it { is_expected.to include('output/foo.html') } it { is_expected.to include('output/foo.htm') } it { is_expected.not_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 end nanoc-4.13.3/nanoc-checking/spec/nanoc/checking/checks/000077500000000000000000000000001472033334600226175ustar00rootroot00000000000000nanoc-4.13.3/nanoc-checking/spec/nanoc/checking/checks/css_spec.rb000066400000000000000000000034121472033334600247460ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Checking::Checks::CSS do let(:check) { described_class.create(site) } let(:site) do Nanoc::Core::Site.new( config:, code_snippets:, data_source: Nanoc::Core::InMemoryDataSource.new(items, layouts), ) end let(:config) { Nanoc::Core::Configuration.new(dir: Dir.getwd).with_defaults } let(:code_snippets) { [] } let(:items) { Nanoc::Core::ItemCollection.new(config, []) } let(:layouts) { Nanoc::Core::LayoutCollection.new(config, []) } before do FileUtils.mkdir_p('output') File.write('Rules', 'passthrough "/**/*"') end it 'handles good CSS' do VCR.use_cassette('css_run_ok') do FileUtils.mkdir_p('output') File.write('output/blah.html', '

Hi!

') File.write('output/style.css', 'h1 { color: red; }') check.run expect(check.issues).to be_empty end end it 'handles bad CSS' do VCR.use_cassette('css_run_error') do FileUtils.mkdir_p('output') File.write('output/blah.html', '

Hi!

') File.write('output/style.css', 'h1 { coxlor: rxed; }') check.run expect(check.issues.length).to be(1) expect(check.issues.to_a[0].description).to eq( "line 1: Property “coxlor” doesn't exist. The closest matching property name is “color”: h1 { coxlor: rxed; }", ) end end it 'handles parse errors' do VCR.use_cassette('css_run_parse_error') do FileUtils.mkdir_p('output') File.write('output/blah.html', '

Hi!

') File.write('output/style.css', 'h1 { ; {') check.run expect(check.issues.length).to be(1) expect(check.issues.to_a[0].description).to eq( 'line 1: Parse Error: h1 { ; {', ) end end end nanoc-4.13.3/nanoc-checking/spec/nanoc/checking/checks/external_links_spec.rb000066400000000000000000000132411472033334600272010ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Checking::Checks::ExternalLinks do let(:check) do described_class.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::Core::Site.new( config:, code_snippets:, data_source: Nanoc::Core::InMemoryDataSource.new(items, layouts), ) end let(:config) { Nanoc::Core::Configuration.new(dir: Dir.getwd).with_defaults } let(:code_snippets) { [] } let(:items) { Nanoc::Core::ItemCollection.new(config, []) } let(:layouts) { Nanoc::Core::LayoutCollection.new(config, []) } around do |ex| FileUtils.mkdir('site with spaces') Dir.chdir('site with spaces') do ex.run end end before do FileUtils.mkdir_p('output') File.write('Rules', 'passthrough "/**/*"') end context 'found' do before do File.write('output/hi.html', 'stuff') end let(:check) do described_class.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 before do File.write('output/hi.html', 'stuff') end let(:check) do described_class.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::Core.on_windows? File.write('output/hi.html', 'stuff') end let(:check) do described_class.create(site).tap do |c| # rubocop:disable RSpec/InstanceVariable 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 # rubocop:enable RSpec/InstanceVariable 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::Core.on_windows? File.write('output/hi.html', 'stuff') end let(:check) do described_class.create(site).tap do |c| # rubocop:disable RSpec/InstanceVariable def c.request_url_once(_url) @enum ||= Enumerator.new do |y| y << Net::HTTPResponse.new('1.1', '302', 'look elsewhere') end @enum.next end # rubocop:enable RSpec/InstanceVariable 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 : redirection without a target location') end end context 'invalid URL component' do before do skip 'Known failure on Windows' if Nanoc::Core.on_windows? File.write('output/hi.html', 'stuff') end let(:check) do described_class.create(site) end it 'has issues' do check.run expect(check.issues.size).to eq(1) expect(check.issues.first.description) .to eq('broken reference to : invalid URI') end end context 'javascript URL' do before do File.write('output/hi.html', %[scroll to top]) end let(:check) do described_class.create(site) end it 'has no issues' do check.run expect(check.issues.size).to eq(0) end end context 'with some patterns excluded' do let(:config) do super().merge( checks: { external_links: { exclude: ['^http://excluded.com'] } }, ) end let(:check) do described_class.create(site) end before do File.write('output/hi.html', <<~CONTENT) eggs clused ink luded CONTENT end it 'has only issues for non-excluded links' do VCR.use_cassette('external_links_some_patterns_excluded') do check.run end expect(check.issues.size).to eq(1) expect(check.issues.first.description) .to match(%r{broken reference to : Failed to open TCP connection}) end end context 'with some files excluded' do let(:config) do super().merge( checks: { external_links: { exclude_files: ['excluded'] } }, ) end let(:check) do described_class.create(site) end before do File.write( 'output/excluded.html', 'eggs cluded', ) File.write( 'output/included.html', 'ink luded', ) end it 'has only issues for non-excluded files' do VCR.use_cassette('external_links_some_files_excluded') do check.run end expect(check.issues.size).to eq(1) expect(check.issues.first.description) .to match(%r{broken reference to : 404}) end end end nanoc-4.13.3/nanoc-checking/spec/nanoc/checking/checks/html_spec.rb000066400000000000000000000030561472033334600251260ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Checking::Checks::HTML do let(:check) { described_class.create(site) } let(:site) do Nanoc::Core::Site.new( config:, code_snippets:, data_source: Nanoc::Core::InMemoryDataSource.new(items, layouts), ) end let(:config) { Nanoc::Core::Configuration.new(dir: Dir.getwd).with_defaults } let(:code_snippets) { [] } let(:items) { Nanoc::Core::ItemCollection.new(config, []) } let(:layouts) { Nanoc::Core::LayoutCollection.new(config, []) } before do FileUtils.mkdir_p('output') File.write('Rules', 'passthrough "/**/*"') end it 'handles good HTML' do VCR.use_cassette('html_run_ok') do FileUtils.mkdir_p('output') File.write('output/blah.html', '

Hi!

') check.run expect(check.issues).to be_empty end end it 'handles bad HTML' do VCR.use_cassette('html_run_error') do FileUtils.mkdir_p('output') File.write('output/blah.html', '

Hi!

') check.run expect(check.issues.length).to be(3) expect(check.issues.to_a[0].description) .to eq('line 1: Start tag seen without seeing a doctype first. Expected e.g. “”.:

Hi!

') expect(check.issues.to_a[1].description) .to eq('line 1: Element “head” is missing a required instance of child element “title”.:

Hi!

') expect(check.issues.to_a[2].description) .to eq('line 1: End tag “h1” seen, but there were open elements.:

Hi!

') end end end nanoc-4.13.3/nanoc-checking/spec/nanoc/checking/checks/internal_links_spec.rb000066400000000000000000000115231472033334600271740ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Checking::Checks::InternalLinks do let(:check) { described_class.create(site) } let(:site) do Nanoc::Core::Site.new( config:, code_snippets:, data_source: Nanoc::Core::InMemoryDataSource.new(items, layouts), ) end let(:config) { Nanoc::Core::Configuration.new(dir: Dir.getwd).with_defaults } let(:code_snippets) { [] } let(:items) { Nanoc::Core::ItemCollection.new(config, []) } let(:layouts) { Nanoc::Core::LayoutCollection.new(config, []) } around do |ex| FileUtils.mkdir('site with spaces') Dir.chdir('site with spaces') do ex.run end end before do FileUtils.mkdir_p('output') File.write('Rules', 'passthrough "/**/*"') end # FIXME: deduplicate def path_to_file_uri(path, dir) output_dir = dir.is_a?(String) ? dir : dir.config.output_dir output_dir += '/' unless output_dir.end_with?('/') uri = Addressable::URI.convert_path(output_dir) + Addressable::URI.convert_path(path) uri.to_s end it 'detects non-broken links' do File.write('output/foo.xhtml', 'not broken') File.write('output/bar.html', 'not broken') check.run expect(check.issues).to be_empty end it 'detects broken links' do File.write('output/foo.html', 'broken') check.run expect(check.issues).not_to be_empty end it 'detects broken links in s' do File.write('output/bar.html', '') check.run expect(check.issues.size).to eq(1) end it 'handles all sorts of path types' do FileUtils.mkdir_p('output/stuff') File.write('output/origin', 'hi') File.write('output/foo', 'hi') File.write('output/stuff/blah', 'hi') expect(check.send(:valid?, path_to_file_uri('foo', site), 'output/origin')).to be(true) expect(check.send(:valid?, path_to_file_uri('origin', site), 'output/origin')).to be(true) expect(check.send(:valid?, path_to_file_uri('stuff/blah', site), 'output/origin')).to be(true) expect(check.send(:valid?, path_to_file_uri('/foo', site), 'output/origin')).to be(true) expect(check.send(:valid?, path_to_file_uri('/origin', site), 'output/origin')).to be(true) expect(check.send(:valid?, path_to_file_uri('/stuff/blah', site), 'output/origin')).to be(true) end it 'ignores query strings' do FileUtils.mkdir_p('output/stuff') File.write('output/stuff/right', 'hi') expect(check.send(:valid?, '/stuff/right?foo=123', 'output/origin')).to be(true) expect(check.send(:valid?, 'stuff/right?foo=456', 'output/origin')).to be(true) expect(check.send(:valid?, 'stuff/wrong?foo=123', 'output/origin')).to be(false) end it 'handles excludes' do site.config.update(checks: { internal_links: { exclude: ['^/excluded\d+'] } }) expect(check.send(:valid?, path_to_file_uri('/excluded1', site), 'output/origin')).to be(true) expect(check.send(:valid?, path_to_file_uri('/excluded2', site), 'output/origin')).to be(true) expect(check.send(:valid?, path_to_file_uri('/excluded_not', site), 'output/origin')).to be(false) end it 'handles exclude targets' do site.config.update(checks: { internal_links: { exclude_targets: ['^/excluded\d+'] } }) expect(check.send(:valid?, path_to_file_uri('/excluded1', site), 'output/origin')).to be(true) expect(check.send(:valid?, path_to_file_uri('/excluded2/two', site), 'output/origin')).to be(true) expect(check.send(:valid?, path_to_file_uri('/excluded_not', site), 'output/origin')).to be(false) end it 'handles exclude origins' do site.config.update(checks: { internal_links: { exclude_origins: ['^/excluded'] } }) expect(check.send(:valid?, path_to_file_uri('/foo', site), 'output/excluded')).to be(true) expect(check.send(:valid?, path_to_file_uri('/foo', site), 'output/not_excluded')).to be(false) end it 'unescapes properly' do FileUtils.mkdir_p('output/stuff') File.write('output/stuff/right foo', 'hi') expect(check.send(:valid?, path_to_file_uri('stuff/right%20foo', site), 'output/origin')).to be(true) expect(check.send(:valid?, path_to_file_uri('stuff/wrong%20foo', site), 'output/origin')).to be(false) end it 'handles nested paths' do FileUtils.mkdir_p('output/one/two/three') File.write('output/one/two/three/a.html', 'b') File.write('output/one/b.html', 'a') File.write('output/one/c.html', 'c') check.run expect(check.issues).to be_empty end it 'ignores protocol-relative URLs' do # Protocol-relative URLs are not internal links. File.write('output/a.html', 'broken') check.run expect(check.issues).to be_empty end end nanoc-4.13.3/nanoc-checking/spec/nanoc/checking/checks/mixed_content_spec.rb000066400000000000000000000131351472033334600270210ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Checking::Checks::MixedContent do let(:check) { described_class.create(site) } let(:site) do Nanoc::Core::Site.new( config:, code_snippets:, data_source: Nanoc::Core::InMemoryDataSource.new(items, layouts), ) end let(:config) { Nanoc::Core::Configuration.new(dir: Dir.getwd).with_defaults } let(:code_snippets) { [] } let(:items) { Nanoc::Core::ItemCollection.new(config, []) } let(:layouts) { Nanoc::Core::LayoutCollection.new(config, []) } before do FileUtils.mkdir_p('output') File.write('Rules', 'passthrough "/**/*"') end def create_output_file(name, lines) FileUtils.mkdir_p('output') File.write('output/' + name, lines.join('\n')) end it 'handles HTTPS URLs' do create_output_file('foo.html', [ '', '', '', '', '
', '', '', '', ]) check.run expect(check.issues).to be_empty end it 'handles absolute paths' do create_output_file('foo.html', [ '', '', '', '
', '', '', '', ]) check.run expect(check.issues).to be_empty end it 'handles protocol-relative paths' do create_output_file('foo.html', [ '', '', '', '
', '', '', '', ]) check.run expect(check.issues).to be_empty end it 'handles relative paths' do create_output_file('foo.html', [ '', '', '', '
', '', '', '', ]) check.run expect(check.issues).to be_empty end it 'ignores query strings' do create_output_file('foo.html', [ '', '', '', '
', '', '', '', ]) check.run expect(check.issues).to be_empty end it 'ignores fragments' do create_output_file('foo.html', [ '', '', '', '
', '', '', '', ]) check.run expect(check.issues).to be_empty end it 'handles HTTP URLs' do create_output_file('foo.html', [ '', '', '', '
', '', '', '', ]) check.run issues = check.issues.to_a expect(issues.count).to eq(7) descriptions = issues.map(&:description) expect(issues.map(&:subject)).to all(eq('output/foo.html')) expect(descriptions).to include('mixed content include: http://nanoc.ws/logo.png') expect(descriptions).to include('mixed content include: http://nanoc.ws/style.css') expect(descriptions).to include('mixed content include: http://nanoc.ws/app.js') expect(descriptions).to include('mixed content include: http://nanoc.ws/process.cgi') expect(descriptions).to include('mixed content include: http://nanoc.ws/preview.html') expect(descriptions).to include('mixed content include: http://nanoc.ws/theme-song.flac') expect(descriptions).to include('mixed content include: http://nanoc.ws/screencast.mkv') expect(descriptions).not_to include('mixed content include: HTTP://nanoc.ws/logo.png') end it 'ignores inert content' do create_output_file('foo.html', [ 'The homepage', 'Content', '', '', '', '', '
', '', '', '', '

http://nanoc.ws/harmless-text

', ]) check.run expect(check.issues).to be_empty end end nanoc-4.13.3/nanoc-checking/spec/nanoc/checking/checks/stale_spec.rb000066400000000000000000000033701472033334600252710ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Checking::Checks::Stale do let(:check) { described_class.create(site) } let(:site) do Nanoc::Core::Site.new( config:, code_snippets:, data_source: Nanoc::Core::InMemoryDataSource.new(items, layouts), ) end let(:config) { Nanoc::Core::Configuration.new(dir: Dir.getwd).with_defaults } let(:code_snippets) { [] } let(:layouts) { Nanoc::Core::LayoutCollection.new(config, []) } let(:items) do Nanoc::Core::ItemCollection.new( config, Dir['content/*'].map { |fn| Nanoc::Core::Item.new('stuff', {}, fn.sub(/^content/, '')) }, ) end before do FileUtils.mkdir_p('content') FileUtils.mkdir_p('output') File.write('Rules', 'passthrough "/**/*"') end it 'does not error when there are no files' do check.run expect(check.issues).to be_empty end it 'does not error when input matches output' do File.write('content/index.html', 'stuff') File.write('output/index.html', 'stuff') check.run expect(check.issues).to be_empty end it 'errors when there is an output file with no matching input file' do File.write('content/index.html', 'stuff') File.write('output/WRONG.html', 'stuff') check.run expect(check.issues.size).to eq(1) expect(check.issues.to_a[0].description).to eq('file without matching item') expect(check.issues.to_a[0].subject).to eq('output/WRONG.html') end context 'with excludes' do let(:config) do super().merge(prune: { exclude: ['excluded.html'] }) end it 'honors excludes' do File.write('content/index.html', 'stuff') File.write('output/excluded.html', 'stuff') check.run expect(check.issues).to be_empty end end end nanoc-4.13.3/nanoc-checking/spec/nanoc/checking/command_runners/000077500000000000000000000000001472033334600245515ustar00rootroot00000000000000nanoc-4.13.3/nanoc-checking/spec/nanoc/checking/command_runners/check_spec.rb000066400000000000000000000024061472033334600271670ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Checking::CommandRunners::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 # rubocop:disable RSpec/NoExpectationExample 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::Core::TrivialError, '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 # rubocop:disable RSpec/NoExpectationExample 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.13.3/nanoc-checking/spec/nanoc/checking/dsl_spec.rb000066400000000000000000000014511472033334600235010ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Checking::DSL do it 'can read from file' do File.write('Checks', "check :foo do\n\nend\ndeploy_check :bar\n") enabled_checks = [] described_class.from_file('Checks', enabled_checks:) expect(Nanoc::Checking::Check.named(:foo)).not_to be_nil expect(enabled_checks).to eq([:bar]) end it 'can load relative files' do File.write('stuff.rb', '$greeting = "hello"') File.write('Checks', 'require "./stuff"') described_class.from_file('Checks', enabled_checks: []) expect($greeting).to eq('hello') end it 'has an absolute path' do File.write('Checks', '$stuff = __FILE__') described_class.from_file('Checks', enabled_checks: []) pathname = Pathname.new($stuff) expect(pathname).to be_absolute end end nanoc-4.13.3/nanoc-checking/spec/nanoc/checking/runner_spec.rb000066400000000000000000000102631472033334600242310ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Checking::Runner, site: true do subject(:runner) { described_class.new(site) } let(:site) { Nanoc::Core::SiteLoader.new.new_from_cwd } describe '#run_specific' do it 'can run a predefined check' do File.write('output/blah', 'I am stale! Haha!') expect { runner.run_specific(%w[stale]) } .to output(%r{output/blah:.*stale - file without matching item}m).to_stdout end it 'can run custom checks' do File.write('Checks', 'check :my_foo_check do ; puts "I AM FOO!" ; end') expect { runner.run_specific(%w[my_foo_check]) }.to output(/I AM FOO!/).to_stdout end end describe '#list_checks' do before do File.write('Checks', 'check :my_foo_check do ; end') end it 'lists all checks' do expect { runner.list_checks } .to output(%r{Available checks:$.*^ css$.*^ my_foo_check$}m).to_stdout end end 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 contain_exactly(: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 contain_exactly(: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 contain_exactly(: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.13.3/nanoc-checking/spec/spec_helper.rb000066400000000000000000000004371472033334600213300ustar00rootroot00000000000000# frozen_string_literal: true require_relative '../../common/spec/spec_helper_head' require 'nanoc/checking' require 'vcr' VCR.configure do |c| c.cassette_library_dir = 'spec/fixtures/vcr_cassettes' c.hook_into :webmock end require_relative '../../common/spec/spec_helper_foot' nanoc-4.13.3/nanoc-checking/test/000077500000000000000000000000001472033334600165335ustar00rootroot00000000000000nanoc-4.13.3/nanoc-checking/test/helper.rb000066400000000000000000000135161472033334600203450ustar00rootroot00000000000000# frozen_string_literal: true $VERBOSE = false require 'simplecov' SimpleCov.start require 'minitest/autorun' require 'mocha/minitest' require 'vcr' require 'debug' require 'tmpdir' require 'stringio' require 'yard' VCR.configure do |c| c.cassette_library_dir = 'test/fixtures/vcr_cassettes' c.hook_into :webmock end require 'nanoc' require 'nanoc/orig_cli' Nanoc::CLI.setup module Nanoc module TestHelpers LIB_DIR = File.expand_path("#{__dir__}/../lib") def disable_nokogiri? ENV.key?('DISABLE_NOKOGIRI') end def if_have(*libs) libs.each do |lib| if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby' && lib == 'nokogiri' && disable_nokogiri? skip 'Pure Java Nokogiri has issues that cause problems with Nanoc (see https://github.com/nanoc/nanoc/pull/422) -- run without DISABLE_NOKOGIRI to enable Nokogiri tests' end begin require lib rescue LoadError skip "requiring #{lib} failed" end end yield end def with_site(params = {}) # Build site name site_name = params[:name] if site_name.nil? @site_num ||= 0 site_name = "site-#{@site_num}" @site_num += 1 end # Build rules rules_content = <<~EOS compile '*' do {{compilation_rule_content}} end route '*' do if item.binary? item.identifier.chop + (item[:extension] ? '.' + item[:extension] : '') else item.identifier + 'index.html' end end layout '*', :erb EOS rules_content = rules_content.gsub( '{{compilation_rule_content}}', params[:compilation_rule_content] || '', ) # Create site unless File.directory?(site_name) FileUtils.mkdir_p(site_name) FileUtils.cd(site_name) do FileUtils.mkdir_p('content') FileUtils.mkdir_p('layouts') FileUtils.mkdir_p('lib') FileUtils.mkdir_p('output') if params[:has_layout] File.write('layouts/default.html', '... <%= @yield %> ...') end File.open('nanoc.yaml', 'w') do |io| io << 'string_pattern_type: legacy' << "\n" if params.fetch(:legacy, true) io << 'data_sources:' << "\n" io << ' -' << "\n" io << ' type: filesystem' << "\n" io << ' identifier_type: legacy' << "\n" if params.fetch(:legacy, true) end File.write('Rules', rules_content) end end # Yield site FileUtils.cd(site_name) do site = Nanoc::Core::SiteLoader.new.new_from_cwd return yield(site) end end def setup # Clean up GC.start # Go quiet unless ENV['QUIET'] == 'false' @orig_stdout = $stdout @orig_stderr = $stderr $stdout = StringIO.new $stderr = StringIO.new end # Enter tmp @tmp_dir = Dir.mktmpdir('nanoc-test') @orig_wd = FileUtils.pwd FileUtils.cd(@tmp_dir) # Let us get to the raw errors Nanoc::CLI::ErrorHandler.disable end def teardown # Restore normal error handling Nanoc::CLI::ErrorHandler.enable # Exit tmp FileUtils.cd(@orig_wd) FileUtils.rm_rf(@tmp_dir) # Go unquiet unless ENV['QUIET'] == 'false' $stdout = @orig_stdout $stderr = @orig_stderr end end def capturing_stdio(&) # Store orig_stdout = $stdout orig_stderr = $stderr # Run $stdout = StringIO.new $stderr = StringIO.new yield { stdout: $stdout.string, stderr: $stderr.string } ensure # Restore $stdout = orig_stdout $stderr = orig_stderr end # Adapted from https://github.com/lsegal/yard-examples/tree/master/doctest def assert_examples_correct(object) P(object).tags(:example).each do |example| # Classify lines = example.text.lines.map do |line| [/^\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) # Test b = binding lines.each_slice(2) do |pair| actual_out = eval(pair.first, b) expected_out = eval(pair.last.match(/# ?=>(.*)/)[1], b) assert_equal( expected_out, actual_out, "Incorrect example:\n#{pair.first}", ) end end end def command?(cmd) TTY::Which.exist?(cmd) end def symlinks_supported? File.symlink nil, nil rescue NotImplementedError false rescue true end def skip_unless_have_command(cmd) skip "Could not find external command \"#{cmd}\"" unless command?(cmd) end def skip_unless_symlinks_supported skip 'Symlinks are not supported by Ruby on Windows' unless symlinks_supported? end def root_dir File.absolute_path("#{__dir__}/..") end # FIXME: deduplicate def path_to_file_uri(path, dir) output_dir = dir.is_a?(String) ? dir : dir.config.output_dir output_dir += '/' unless output_dir.end_with?('/') uri = Addressable::URI.convert_path(output_dir) + Addressable::URI.convert_path(path) uri.to_s end end end module Nanoc class TestCase < Minitest::Test include Nanoc::TestHelpers end end # Unexpected system exit is unexpected Minitest::Test::PASSTHROUGH_EXCEPTIONS.delete(SystemExit) # A more precise inspect method for Time improves assert failure messages. # class Time def inspect strftime("%a %b %d %H:%M:%S.#{format('%06d', usec)} %Z %Y") end end nanoc-4.13.3/nanoc-checking/test/nanoc/000077500000000000000000000000001472033334600176315ustar00rootroot00000000000000nanoc-4.13.3/nanoc-checking/test/nanoc/checking/000077500000000000000000000000001472033334600214045ustar00rootroot00000000000000nanoc-4.13.3/nanoc-checking/test/nanoc/checking/test_link_collector.rb000066400000000000000000000166001472033334600257760ustar00rootroot00000000000000# frozen_string_literal: true require 'helper' module Nanoc module Checking class LinkCollectorTest < Nanoc::TestCase def test_all # Create dummy data FileUtils.mkdir_p('test dir') file_a = File.join(Dir.pwd, 'file-a.html') file_b = File.join(Dir.pwd, 'test dir', 'file-b.html') File.open(file_a, 'w') do |io| io << %(A 1) io << %(A 2) io << %(A 3) io << %(A 3b) io << %(A 4) io << %(A 5) end File.open(file_b, 'w') do |io| io << %(B 1) io << %(B 2) io << %(B 2) end # Create validator collector = Nanoc::Checking::LinkCollector.new([file_a, file_b]) # 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, path_to_file_uri('stuff/', Dir.pwd) assert_includes hrefs, path_to_file_uri('stuff with spaces/', Dir.pwd) refute_includes hrefs, 'https://example.com/with-fragment#moo' assert_includes hrefs, 'https://example.com/with-fragment' refute_includes hrefs, nil assert_includes hrefs, 'mailto:bob@example.com' assert_includes hrefs, 'file:///stuff' assert_includes hrefs, path_to_file_uri('stuff', Dir.pwd) end def test_external # Create dummy data file_a = File.join(Dir.pwd, 'file-a.html') file_b = File.join(Dir.pwd, 'file-b.html') File.open(file_a, 'w') do |io| io << %(A 1) io << %(A 2) io << %() end File.open(file_b, 'w') do |io| io << %(B 1) io << %(B 2) io << %(B 3) end # Create validator collector = Nanoc::Checking::LinkCollector.new([file_a, file_b], :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, path_to_file_uri('/', Dir.pwd) assert_includes hrefs, 'mailto:bob@example.com' refute_includes hrefs, path_to_file_uri('/stuff', Dir.pwd) refute_includes hrefs, path_to_file_uri('/stuff/', Dir.pwd) end def test_internal_excludes_external # Create dummy data output_dir = Dir.pwd file_a = File.join(output_dir, 'file-a.html') file_b = File.join(output_dir, 'file-b.html') File.open(file_a, 'w') do |io| io << %(A 1) io << %(A 2) end File.open(file_b, 'w') do |io| io << %(B 1) io << %(B 2) end # Create validator collector = Nanoc::Checking::LinkCollector.new([file_a, file_b], :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/' refute_includes hrefs, 'https://nanoc.app' refute_includes hrefs, 'mailto:bob@example.com' end def test_collect_links_from_space_separated_lists # The white-space variations in this file’s attributes are intentional File.open('file-a.html', 'w') do |io| io << %() io << %() io << %(A 1) end file_a = File.join(Dir.pwd, 'file-a.html') collector = Nanoc::Checking::LinkCollector.new([file_a], :internal) # Test hrefs_with_filenames = collector.filenames_per_href hrefs = hrefs_with_filenames.keys assert_includes hrefs, path_to_file_uri('image.jpeg', Dir.pwd) assert_includes hrefs, path_to_file_uri('image-large.jpeg', Dir.pwd) assert_includes hrefs, path_to_file_uri('image-medium.jpeg', Dir.pwd) assert_includes hrefs, path_to_file_uri('image-small.jpeg', Dir.pwd) assert_includes hrefs, path_to_file_uri('image-large.webp', Dir.pwd) assert_includes hrefs, path_to_file_uri('image-medium.webp', Dir.pwd) assert_includes hrefs, path_to_file_uri('image-small.webp', Dir.pwd) assert_includes hrefs, path_to_file_uri('ping1', Dir.pwd) assert_includes hrefs, path_to_file_uri('ping2', Dir.pwd) refute_includes hrefs, 'http://example.com/ping3' refute_includes hrefs, nil refute_includes hrefs, path_to_file_uri('/', Dir.pwd) end def test_collects_exotic_links file_a = File.join(Dir.pwd, 'file-a.html') File.open(file_a, 'w') do |io| io << %(
A 1
) io << %(A 2) io << %(
A 3
) io << %() end collector = Nanoc::Checking::LinkCollector.new([file_a], :external) # Test hrefs_with_filenames = collector.filenames_per_href hrefs = hrefs_with_filenames.keys assert_includes hrefs, 'urn:uuid:6650eb58-86e6-416c-906a-35336e5ac8b2' assert_includes hrefs, 'ms-settings:windows-update' assert_includes hrefs, 'https://tracking.nanoc.ws/ping' refute_includes hrefs, 'https://nanoc.app/#static-generator' assert_includes hrefs, 'https://nanoc.app/' assert_includes hrefs, 'https://nanoc.app/all-your-base-are-belong-to-us' end def test_protocol_relative_urls File.write('a.html', 'broken') external_collector = Nanoc::Checking::LinkCollector.new(['a.html'], :external) internal_collector = Nanoc::Checking::LinkCollector.new(['a.html'], :internal) hrefs = external_collector.filenames_per_href.keys assert_includes hrefs, '//example.com/broken' refute_includes hrefs, 'http://example.com/broken' refute_includes hrefs, 'file:///example.com/broken' refute_includes hrefs, 'file://example.com/broken' hrefs = internal_collector.filenames_per_href.keys refute_includes hrefs, '//example.com/broken' refute_includes hrefs, 'http://example.com/broken' refute_includes hrefs, 'file:///example.com/broken' refute_includes hrefs, 'file://example.com/broken' end end end end nanoc-4.13.3/nanoc-cli/000077500000000000000000000000001472033334600145505ustar00rootroot00000000000000nanoc-4.13.3/nanoc-cli/.rspec000066400000000000000000000000611472033334600156620ustar00rootroot00000000000000-r ./spec/spec_helper.rb --format Fuubar --color nanoc-4.13.3/nanoc-cli/LICENSE000066400000000000000000000020711472033334600155550ustar00rootroot00000000000000Copyright (c) 2014–… 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.13.3/nanoc-cli/NEWS.md000066400000000000000000000000771472033334600156520ustar00rootroot00000000000000# nanoc-cli news See the release notes for Nanoc for details. nanoc-4.13.3/nanoc-cli/README.md000066400000000000000000000001371472033334600160300ustar00rootroot00000000000000# nanoc-cli This package contains the command-line interface for [Nanoc](https://nanoc.app/). nanoc-4.13.3/nanoc-cli/Rakefile000066400000000000000000000004341472033334600162160ustar00rootroot00000000000000# 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.13.3/nanoc-cli/lib/000077500000000000000000000000001472033334600153165ustar00rootroot00000000000000nanoc-4.13.3/nanoc-cli/lib/nanoc-cli.rb000066400000000000000000000000741472033334600175070ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'nanoc/cli' nanoc-4.13.3/nanoc-cli/lib/nanoc/000077500000000000000000000000001472033334600164145ustar00rootroot00000000000000nanoc-4.13.3/nanoc-cli/lib/nanoc/cli.rb000066400000000000000000000141311472033334600175100ustar00rootroot00000000000000# frozen_string_literal: true require 'nanoc/core' require 'diff/lcs' require 'diff/lcs/hunk' require 'logger' 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 module Nanoc # @api private module 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 # 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 # 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 # @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.fetch(e, nil) =~ /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 # 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 Nanoc::CLI.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::Core::SiteLoader.cwd_is_nanoc_site? config = Nanoc::Core::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&.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 # @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 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 end inflector_class = Class.new(Zeitwerk::Inflector) do def camelize(basename, abspath) case basename when 'version', 'cli', 'utf8' basename.upcase when 'ansi_colors' 'ANSIColors' when 'ansi_string_colorizer' 'ANSIStringColorizer' else super end end end loader = Zeitwerk::Loader.new loader.inflector = inflector_class.new loader.push_dir(__dir__ + '/..') loader.ignore(__dir__ + '/../nanoc-cli.rb') loader.ignore(__dir__ + '/cli/commands') loader.setup loader.eager_load nanoc-4.13.3/nanoc-cli/lib/nanoc/cli/000077500000000000000000000000001472033334600171635ustar00rootroot00000000000000nanoc-4.13.3/nanoc-cli/lib/nanoc/cli/ansi_string_colorizer.rb000066400000000000000000000014241472033334600241210ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module 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 end nanoc-4.13.3/nanoc-cli/lib/nanoc/cli/cleaning_stream.rb000066400000000000000000000071551472033334600226530ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module CLI # An output stream that passes output through stream cleaners. This can be # used to strip ANSI color sequences, for instance. 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.instance_of?(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? @_tty_eh ||= @stream.tty? # rubocop:disable Naming/MemoizedInstanceVariableName 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#printf def printf(*args) @stream.printf(*args) 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 IO#closed? def closed? @stream.closed? 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 def set_encoding(*args) @stream.set_encoding(*args) end 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 end nanoc-4.13.3/nanoc-cli/lib/nanoc/cli/command_runner.rb000066400000000000000000000035011472033334600225160ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module 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::Core::SiteLoader.cwd_is_nanoc_site? end def self.find_site_dir start_here = Dir.pwd here = start_here until Nanoc::Core::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::Core::TrivialError, '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::Core::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 end nanoc-4.13.3/nanoc-cli/lib/nanoc/cli/commands/000077500000000000000000000000001472033334600207645ustar00rootroot00000000000000nanoc-4.13.3/nanoc-cli/lib/nanoc/cli/commands/compile.rb000066400000000000000000000027571472033334600227540ustar00rootroot00000000000000# 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' flag :W, :watch, 'watch for changes and recompile when needed' option nil, :focus, 'compile only items matching the given pattern', argument: :required, multiple: true 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, focus: options[:focus]).run end def run_once time_before = Time.now @site = load_site puts 'Compiling site…' compiler = Nanoc::Core::Compiler.new_for(@site, focus: options[:focus]) listener = Nanoc::CLI::CompileListeners::Aggregate.new( command_runner: self, site: @site, 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." if options[:focus] warn 'CAUTION: A --focus option is specified. Not the entire site has been compiled.' warn 'Re-run without --focus to compile the entire site.' end end end end runner Nanoc::CLI::Commands::Compile nanoc-4.13.3/nanoc-cli/lib/nanoc/cli/commands/create-site.rb000066400000000000000000000157021472033334600235230ustar00rootroot00000000000000# 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_GEMFILE = <<~EOS unless defined? DEFAULT_GEMFILE # frozen_string_literal: true source 'https://rubygems.org' gem 'nanoc', '~> #{Nanoc::CLI::VERSION.split('.').take(2).join('.')}' EOS 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::Core::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 '/**/*.html' do layout '/default.*' if item.identifier =~ '**/index.*' write item.identifier.to_s else write item.identifier.without_ext + '/index.html' end 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.*' # # if item.identifier =~ '**/index.*' # write item.identifier.without_ext + '.html' # else # write item.identifier.without_ext + '/index.html' # end #end passthrough '/**/*' 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::Core::TrivialError, "The site was not created because '#{path}' already exists. " \ 'Re-run the command using --force to create the site anyway.', ) 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('Gemfile', DEFAULT_GEMFILE) 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::CLI::Logger.instance.file(:high, :create, filename) end end end runner Nanoc::CLI::Commands::CreateSite nanoc-4.13.3/nanoc-cli/lib/nanoc/cli/commands/nanoc.rb000066400000000000000000000021241472033334600224060ustar00rootroot00000000000000# 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::Core.version_information exit 0 end opt :w, :warn, 'enable warnings' do $-w = true end nanoc-4.13.3/nanoc-cli/lib/nanoc/cli/commands/prune.rb000066400000000000000000000036071472033334600224500ustar00rootroot00000000000000# 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::Core::Compiler.new_for(@site).run_until_reps_built reps = res.fetch(:reps) listener_class = Nanoc::CLI::CompileListeners::FileActionPrinter listener = listener_class.new(reps:) listener.start_safely if options.key?(:yes) Nanoc::Core::Pruner.new(@site.config, reps, exclude: prune_config_exclude).run elsif options.key?(:'dry-run') Nanoc::Core::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 ensure listener&.stop_safely 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.13.3/nanoc-cli/lib/nanoc/cli/commands/shell.rb000066400000000000000000000030171472033334600224210ustar00rootroot00000000000000# 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::Core::Compiler.new_for(@site).run_until_preprocessed if options[:preprocess] Nanoc::Core::Context.new(env).pry end def env self.class.env_for_site(@site) end def self.reps_for(site) Nanoc::Core::ItemRepRepo.new.tap do |reps| action_provider = Nanoc::Core::ActionProvider.named(site.config.action_provider).for(site) builder = Nanoc::Core::ItemRepBuilder.new(site, action_provider, reps) builder.run end end def self.view_context_for(site) Nanoc::Core::ViewContextForShell.new( items: site.items, reps: reps_for(site), ) end def self.env_for_site(site) view_context = view_context_for(site) { items: Nanoc::Core::ItemCollectionWithRepsView.new(site.items, view_context), layouts: Nanoc::Core::LayoutCollectionView.new(site.layouts, view_context), config: Nanoc::Core::ConfigView.new(site.config, view_context), } end end end runner Nanoc::CLI::Commands::Shell nanoc-4.13.3/nanoc-cli/lib/nanoc/cli/commands/show-data.rb000066400000000000000000000144401472033334600232030ustar00rootroot00000000000000# 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::Core::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::Core::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::Core::Layout 'layout' when Nanoc::Core::Item 'item' when Nanoc::Core::Configuration 'config' when Nanoc::Core::ItemCollection 'items' when Nanoc::Core::LayoutCollection 'layouts' when nil '(unknown)' else raise Nanoc::Core::Errors::InternalInconsistency, "unexpected pred type #{pred.inspect}" end details = case pred when Nanoc::Core::Document [pred.identifier.to_s] when Nanoc::Core::Configuration, nil [] when Nanoc::Core::IdentifiableCollection describe_identifiable_collection_dependency(dep) else raise Nanoc::Core::Errors::InternalInconsistency, "unexpected pred type #{pred.inspect}" end if pred print " [ #{format '%7s', type} ] (#{dep.props})" if details.any? print ' ' end puts details.join("\n ") else puts ' ( removed )' end end puts ' (nothing)' if dependencies.empty? end end def describe_identifiable_collection_dependency(dep) outcome = [] case dep.props.raw_content when true outcome << 'matching any identifier' when Set dep.props.raw_content.sort.each do |x| outcome << "matching identifier #{x}" end end if dep.props.attributes case dep.props.attributes when true outcome << 'matching any attribute' when Set dep.props.attributes.each do |elem| case elem when Symbol outcome << "matching attribute #{elem.inspect} (any value)" when Array outcome << "matching attribute pair #{elem[0].inspect} => #{elem[1].inspect}" else raise( Nanoc::Core::Errors::InternalInconsistency, "unexpected prop attribute element #{elem.inspect}", ) end end else raise( Nanoc::Core::Errors::InternalInconsistency, "unexpected prop attribute #{dep.props.attributes.inspect}", ) end end outcome 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.13.3/nanoc-cli/lib/nanoc/cli/commands/show-plugins.rb000066400000000000000000000053741472033334600237610ustar00rootroot00000000000000# 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 # rubocop:disable Style/MutableConstant # These constants are intended to be mutated (through #add_plugin_class) PLUGIN_CLASS_ORDER = [ Nanoc::Core::Filter, Nanoc::Core::DataSource, ] PLUGIN_CLASSES = { Nanoc::Core::Filter => 'Filters', Nanoc::Core::DataSource => 'Data Sources', } # rubocop:enable Style/MutableConstant 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 def self.add_plugin_class(klass, name) PLUGIN_CLASS_ORDER << klass PLUGIN_CLASSES[klass] = name end private def name_for_plugin_class(klass) PLUGIN_CLASSES[klass.to_s] end end end runner Nanoc::CLI::Commands::ShowPlugins nanoc-4.13.3/nanoc-cli/lib/nanoc/cli/commands/view.rb000066400000000000000000000034771472033334600222760ustar00rootroot00000000000000# 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 127.0.0.1. Running this static web server requires `adsf` (not `asdf`!). EOS option :H, :handler, 'specify the handler to use (webrick/puma/...)', argument: :required option :o, :host, 'specify the host to listen on', default: '127.0.0.1', argument: :required option :p, :port, 'specify the port to listen on', transform: Nanoc::CLI::Transform::Port, default: 3000, argument: :required flag :L, :'live-reload', 'reload on changes' no_params module Nanoc::CLI::Commands class View < ::Nanoc::CLI::CommandRunner def run load_adsf config = Nanoc::Core::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[:host], port: options[: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.13.3/nanoc-cli/lib/nanoc/cli/compile_listeners/000077500000000000000000000000001472033334600227035ustar00rootroot00000000000000nanoc-4.13.3/nanoc-cli/lib/nanoc/cli/compile_listeners/abstract.rb000066400000000000000000000020471472033334600250360ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc::CLI::CompileListeners class Abstract def initialize(*) super() 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 wrapped_start @_notification_names = [] start end def wrapped_stop stop Nanoc::Core::NotificationCenter.sync @_notification_names.each do |name| Nanoc::Core::NotificationCenter.remove(name, self) end end def run_while wrapped_start yield ensure wrapped_stop end def start_safely wrapped_start @_started = true end def stop_safely wrapped_stop if @_started @_started = false end def on(sym) @_notification_names << sym Nanoc::Core::NotificationCenter.on(sym, self) { |*args| yield(*args) } end end end nanoc-4.13.3/nanoc-cli/lib/nanoc/cli/compile_listeners/aggregate.rb000066400000000000000000000021411472033334600251540ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc::CLI::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::CompileListeners::DiffGenerator, Nanoc::CLI::CompileListeners::DebugPrinter, Nanoc::CLI::CompileListeners::TimingRecorder, Nanoc::CLI::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:) } @listeners.each(&:start_safely) end def teardown_listeners return unless @listeners @listeners.reverse_each(&:stop_safely) end end end nanoc-4.13.3/nanoc-cli/lib/nanoc/cli/compile_listeners/debug_printer.rb000066400000000000000000000055171472033334600260710ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc::CLI::CompileListeners class DebugPrinter < Abstract # @see Listener#enable_for? def self.enable_for?(command_runner, _site) command_runner.debug? end COLOR_MAP = { 'compilation' => "\e[31m", 'content' => "\e[32m", 'filtering' => "\e[33m", 'dependency_tracking' => "\e[34m", 'phase' => "\e[35m", 'stage' => "\e[36m", }.freeze # @see Listener#start def start on(:compilation_started) do |rep| log('compilation', "Started compilation of #{rep}") end on(:compilation_ended) do |rep| log('compilation', "Ended compilation of #{rep}") log('compilation', '') end on(:compilation_suspended) do |rep, target_rep, snapshot_name| log('compilation', "Suspended compilation of #{rep}: depends on #{target_rep}, snapshot #{snapshot_name}") end on(:cached_content_used) do |rep| log('content', "Used cached compiled content for #{rep} instead of recompiling") end on(:snapshot_created) do |rep, snapshot_name| log('content', "Snapshot #{snapshot_name} created for #{rep}") end on(:filtering_started) do |rep, filter_name| log('filtering', "Started filtering #{rep} with #{filter_name}") end on(:filtering_ended) do |rep, filter_name| log('filtering', "Ended filtering #{rep} with #{filter_name}") end on(:dependency_created) do |src, dst| log('dependency_tracking', "Dependency created from #{src.inspect} onto #{dst.inspect}") end on(:phase_started) do |phase_name, rep| log('phase', "Phase started: #{phase_name} (rep: #{rep})") end on(:phase_yielded) do |phase_name, rep| log('phase', "Phase yielded: #{phase_name} (rep: #{rep})") end on(:phase_resumed) do |phase_name, rep| log('phase', "Phase resumed: #{phase_name} (rep: #{rep})") end on(:phase_ended) do |phase_name, rep| log('phase', "Phase ended: #{phase_name} (rep: #{rep})") end on(:phase_aborted) do |phase_name, rep| log('phase', "Phase aborted: #{phase_name} (rep: #{rep})") end on(:stage_started) do |stage_name| log('stage', "Stage started: #{stage_name}") end on(:stage_ended) do |stage_name| log('stage', "Stage ended: #{stage_name}") end on(:stage_aborted) do |stage_name| log('stage', "Stage aborted: #{stage_name}") end end def log(progname, msg) logger.info(progname) { msg } end def logger @_logger ||= Logger.new($stdout).tap do |l| l.formatter = proc do |_severity, datetime, progname, msg| "*** #{datetime.strftime('%H:%M:%S.%L')} #{COLOR_MAP[progname]}#{msg}\e[0m\n" end end end end end nanoc-4.13.3/nanoc-cli/lib/nanoc/cli/compile_listeners/diff_generator.rb000066400000000000000000000044011472033334600262050ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc::CLI::CompileListeners class DiffGenerator < Abstract class Differ def initialize(path, str_a, str_b) @path = path @str_a = str_a @str_b = str_b end def call run end private def run lines_a = @str_a.lines.map(&:chomp) lines_b = @str_b.lines.map(&:chomp) diffs = Diff::LCS.diff(lines_a, lines_b) output = +'' output << "--- #{@path}\n" output << "+++ #{@path}\n" prev_hunk = hunk = nil file_length_difference = 0 diffs.each do |piece| hunk = Diff::LCS::Hunk.new(lines_a, lines_b, piece, 3, file_length_difference) file_length_difference = hunk.file_length_difference next unless prev_hunk next if hunk.merge(prev_hunk) output << prev_hunk.diff(:unified) << "\n" ensure prev_hunk = hunk end last = prev_hunk.diff(:unified) output << last << "\n" output end end # @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 on(:rep_ready_for_diff) do |raw_path, old_content, new_content| generate_diff_for(raw_path, old_content, new_content) end end # @see Listener#stop def stop 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 = Differ.new(path, old_content, new_content).call # Write diff @diff_lock.synchronize do File.open('output.diff', 'a') { |io| io.write(diff) } end end end end end nanoc-4.13.3/nanoc-cli/lib/nanoc/cli/compile_listeners/file_action_printer.rb000066400000000000000000000050541472033334600272530ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc::CLI::CompileListeners class FileActionPrinter < Abstract def initialize(reps:) @reps = reps @stopwatches = {} end # @see Listener#start def start on(:compilation_started) do |rep| @stopwatches[rep] ||= DDMetrics::Stopwatch.new @stopwatches[rep].start end on(:compilation_suspended) do |rep| @stopwatches[rep].stop end cached_reps = Set.new on(:cached_content_used) do |rep| cached_reps << rep end on(:rep_write_enqueued) do |rep| @stopwatches[rep].stop end on(:rep_write_started) do |rep, _raw_path| @stopwatches[rep].start end on(:rep_write_ended) do |rep, _binary, path, is_created, is_modified| @stopwatches[rep].stop duration = @stopwatches[rep].duration action = if is_created :create elsif is_modified :update elsif cached_reps.include?(rep) :cached else :identical end level = if is_created :high elsif is_modified :high else :low end # Make path relative (to current working directory) # 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 on(:file_pruned) do |path| # Make path relative (to current working directory) # FIXME: do not depend on working directory if path.start_with?(Dir.getwd) path = path[(Dir.getwd.size + 1)..path.size] end Nanoc::CLI::Logger.instance.file(:high, :delete, path) end on(:file_listed_for_pruning) do |path| # Make path relative (to current working directory) # FIXME: do not depend on working directory if path.start_with?(Dir.getwd) path = path[(Dir.getwd.size + 1)..path.size] end Nanoc::CLI::Logger.instance.file(:high, :delete, '(dry run) ' + path) end end # @see Listener#stop def stop @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.13.3/nanoc-cli/lib/nanoc/cli/compile_listeners/timing_recorder.rb000066400000000000000000000122221472033334600264030ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc::CLI::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 @store_stores_summary = DDMetrics::Summary.new end # @see Listener#start def start Nanoc::Core::Instrumentor.enable 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(:store_stored) do |duration, klass| @store_stores_summary.observe(duration, name: klass.to_s) end on(:compilation_suspended) do |rep, _target_rep, _snapshot_name| filter_stopwatches.fetch(rep).each(&:stop) end on(:compilation_started) do |rep| filter_stopwatches.fetch(rep, []).each(&:start) end setup_phase_notifications end # @see Listener#stop def stop Nanoc::Core::Instrumentor.disable print_profiling_feedback end protected def setup_phase_notifications stopwatches = {} on(:phase_started) do |phase_name, rep| stopwatch = stopwatches[[phase_name, rep]] = DDMetrics::Stopwatch.new stopwatch.start end on(:phase_ended) do |phase_name, rep| stopwatch = stopwatches[[phase_name, rep]] stopwatch.stop @phases_summary.observe(stopwatch.duration, name: phase_name) end on(:phase_yielded) do |phase_name, rep| stopwatch = stopwatches[[phase_name, rep]] stopwatch.stop end on(:phase_resumed) do |phase_name, rep| # It probably looks weird that a phase can be resumed even though it was not suspended earlier. This can happen when compilation is suspended, where you’d get the sequence started -> suspended -> started -> resumed. stopwatch = stopwatches[[phase_name, rep]] stopwatch.start unless stopwatch.running? end on(:phase_aborted) do |phase_name, rep| stopwatch = stopwatches[[phase_name, rep]] stopwatch.stop if stopwatch.running? @phases_summary.observe(stopwatch.duration, name: phase_name) end end 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_table_for_summary_duration(:store_stores, @store_stores_summary) 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_table(rows) puts DDMetrics::Table.new(rows) end end end nanoc-4.13.3/nanoc-cli/lib/nanoc/cli/error_handler.rb000066400000000000000000000253101472033334600223370ustar00rootroot00000000000000# 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, &block) if @disabled yield else new.handle_while(exit_on_error:, &block) end end # Disables error handling. This is used by the test cases to prevent error # from being handled by the CLI while tests are running. def self.disable @disabled = true end # Re-enables error handling after it was disabled. This is used by the test # cases to prevent error from being handled by the CLI while tests are # running. def self.enable @disabled = false end # Enables error handling in the given block. This method should not be # called directly; use {Nanoc::CLI::ErrorHandler.handle_while} instead. # # @return [void] def handle_while(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 Exception => e # rubocop:disable Lint/RescueException # The exception could be wrapped in a # Nanoc::Core::Errors::CompilationError, so find the # underlying exception and handle that one instead. e = unwrap_error(e) case e when Interrupt puts exit(1) when StandardError, ScriptError handle_error(e, exit_on_error:) else raise e end 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!' write_error_message(stream, error) write_error_detail(stream, error) write_item_rep(stream, error) write_stack_trace(stream, error) 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 "Crash log created at #{Time.now}" write_error_message(stream, error, verbose: true) write_error_detail(stream, error) 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 trivial?(error) case error when Nanoc::Core::TrivialError, 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', 'json' => 'json', 'kramdown' => 'kramdown', 'less' => 'less', 'listen' => 'listen', 'markaby' => 'markaby', 'maruku' => 'maruku', 'mime/types' => 'mime-types', 'mustache' => 'mustache', 'nanoc/live' => 'nanoc-live', 'nokogiri' => 'nokogiri', 'pandoc-ruby' => 'pandoc-ruby', 'pry' => 'pry', 'rack' => 'rack', 'rack/cache' => 'rack-cache', 'rainpress' => 'rainpress', 'rdiscount' => 'rdiscount', 'redcarpet' => 'redcarpet', 'redcloth' => 'RedCloth', 'ruby-handlebars' => 'hbs', 'rubypants' => 'rubypants', 'sass' => 'sass', 'slim' => 'slim', 'typogruby' => 'typogruby', 'terser' => 'terser', '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:) 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_error_detail(stream, error) error = unwrap_error(error) if error.respond_to?(:full_message) stream.puts stream.puts error.full_message end end def write_item_rep(stream, error, verbose: false) return unless error.is_a?(Nanoc::Core::Errors::CompilationError) write_section_header(stream, 'Item being compiled', 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:) writer = Nanoc::CLI::StackTraceWriter.new(stream) writer.write(unwrap_error(error), verbose:) end def write_version_information(stream, verbose: false) write_section_header(stream, 'Version information', verbose:) stream.puts Nanoc::Core.version_information end def write_system_information(stream, verbose: false) uname = `uname -a` write_section_header(stream, 'System information', verbose:) stream.puts uname rescue Errno::ENOENT end def write_installed_gems(stream, verbose: false) write_section_header(stream, 'Installed gems', 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:) stream.puts File.read('Gemfile.lock') end end def write_load_paths(stream, verbose: false) write_section_header(stream, 'Load paths', verbose:) $LOAD_PATH.each_with_index do |i, index| stream.puts " #{index}. #{i}" end end def unwrap_error(e) case e when Nanoc::Core::Errors::CompilationError e.unwrap else e end end end end nanoc-4.13.3/nanoc-cli/lib/nanoc/cli/logger.rb000066400000000000000000000041061472033334600207700ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module 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 end nanoc-4.13.3/nanoc-cli/lib/nanoc/cli/stack_trace_writer.rb000066400000000000000000000010341472033334600233650ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module CLI class StackTraceWriter def initialize(stream) @stream = stream end def write(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 end end end nanoc-4.13.3/nanoc-cli/lib/nanoc/cli/stream_cleaners/000077500000000000000000000000001472033334600223325ustar00rootroot00000000000000nanoc-4.13.3/nanoc-cli/lib/nanoc/cli/stream_cleaners/abstract.rb000066400000000000000000000013311472033334600244600ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module CLI module 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} 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) raise NotImplementedError, 'Subclasses of Nanoc::CLI::StreamCleaners::Abstract must implement #clean' end end end end end nanoc-4.13.3/nanoc-cli/lib/nanoc/cli/stream_cleaners/ansi_colors.rb000066400000000000000000000004761472033334600252010ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module CLI module StreamCleaners # Removes ANSI color escape sequences. class ANSIColors < Abstract # @see Nanoc::CLI::StreamCleaners::Abstract#clean def clean(str) str.gsub(/\e\[.+?m/, '') end end end end end nanoc-4.13.3/nanoc-cli/lib/nanoc/cli/stream_cleaners/utf8.rb000066400000000000000000000011061472033334600235430ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module CLI module StreamCleaners # Simplifies output by replacing UTF-8 characters with their ASCII decompositions. # class UTF8 < Abstract # @see Nanoc::CLI::StreamCleaners::Abstract#clean def clean(str) return str unless str.encoding.name == 'UTF-8' # FIXME: this decomposition is not generally usable str .unicode_normalize(:nfkd) .tr('─┼“”‘’', '-+""\'\'') .gsub('©', '(c)') end end end end end nanoc-4.13.3/nanoc-cli/lib/nanoc/cli/transform.rb000066400000000000000000000005121472033334600215210ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module CLI # @api private module Transform module Port RANGE = (0x0001..0xffff) def self.call(data) Integer(data).tap do |int| raise 'not a valid port' unless RANGE.cover?(int) end end end end end end nanoc-4.13.3/nanoc-cli/lib/nanoc/cli/version.rb000066400000000000000000000001321472033334600211710ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module CLI VERSION = '4.13.3' end end nanoc-4.13.3/nanoc-cli/nanoc-cli.gemspec000066400000000000000000000016101472033334600177560ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'lib/nanoc/cli/version' Gem::Specification.new do |s| s.name = 'nanoc-cli' s.version = Nanoc::CLI::VERSION s.homepage = 'https://nanoc.app/' s.summary = 'CLI for Nanoc' s.description = 'Provides the CLI 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 = '>= 3.1' s.add_dependency('cri', '~> 2.15') s.add_dependency('diff-lcs', '~> 1.3') s.add_dependency('nanoc-core', "= #{Nanoc::CLI::VERSION}") s.add_dependency('pry', '>= 0') s.add_dependency('zeitwerk', '~> 2.1') s.metadata = { 'rubygems_mfa_required' => 'true', 'source_code_uri' => "https://github.com/nanoc/nanoc/tree/#{s.name}-v#{s.version}/#{s.name}", } end nanoc-4.13.3/nanoc-cli/nanoc-cli.manifest000066400000000000000000000017411472033334600201460ustar00rootroot00000000000000NEWS.md README.md lib/nanoc-cli.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/compile.rb lib/nanoc/cli/commands/create-site.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/view.rb lib/nanoc/cli/compile_listeners/abstract.rb lib/nanoc/cli/compile_listeners/aggregate.rb lib/nanoc/cli/compile_listeners/debug_printer.rb lib/nanoc/cli/compile_listeners/diff_generator.rb lib/nanoc/cli/compile_listeners/file_action_printer.rb lib/nanoc/cli/compile_listeners/timing_recorder.rb lib/nanoc/cli/error_handler.rb lib/nanoc/cli/logger.rb lib/nanoc/cli/stack_trace_writer.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/cli/version.rb nanoc-4.13.3/nanoc-cli/spec/000077500000000000000000000000001472033334600155025ustar00rootroot00000000000000nanoc-4.13.3/nanoc-cli/spec/gem_spec.rb000066400000000000000000000006721472033334600176160ustar00rootroot00000000000000# frozen_string_literal: true describe 'nanoc-cli.gem', chdir: false, stdio: true do subject do TTY::Command.new.run('gem build nanoc-cli.gemspec') end around do |ex| Dir['*.gem'].each { |f| FileUtils.rm(f) } ex.run Dir['*.gem'].each { |f| FileUtils.rm(f) } end it 'builds gem' do expect { subject } .to change { Dir['*.gem'] } .from([]) .to(include(match(/^nanoc-cli-.*\.gem$/))) end end nanoc-4.13.3/nanoc-cli/spec/manifest_spec.rb000066400000000000000000000002141472033334600206440ustar00rootroot00000000000000# frozen_string_literal: true describe 'manifest', chdir: false do example do expect('nanoc-cli').to have_a_valid_manifest end end nanoc-4.13.3/nanoc-cli/spec/meta_spec.rb000066400000000000000000000020671472033334600177740ustar00rootroot00000000000000# frozen_string_literal: true describe 'meta', chdir: false do it 'is covered by specs' do regular_files = Dir['lib/nanoc/cli/**/*.rb'] regular_file_base_names = regular_files.map { |fn| fn.gsub(/^lib\/nanoc\/cli\/|\.rb$/, '').tr('-', '_') } spec_files = Dir['spec/nanoc/cli/**/*_spec.rb'] spec_file_base_names = spec_files.map { |fn| fn.gsub(/^spec\/nanoc\/cli\/|_spec\.rb$/, '').tr('-', '_') } # TODO: don’t ignore anything ignored_regular_file_base_names = %w[ stream_cleaners/abstract stream_cleaners/ansi_colors ansi_string_colorizer commands/create_site commands/nanoc commands/prune compile_listeners/aggregate logger transform ] ignored_spec_file_base_names = %w[] effective_regular_file_base_names = regular_file_base_names - ignored_regular_file_base_names effective_spec_file_base_names = spec_file_base_names - ignored_spec_file_base_names expect(effective_regular_file_base_names) .to match_array(effective_spec_file_base_names) end end nanoc-4.13.3/nanoc-cli/spec/nanoc/000077500000000000000000000000001472033334600166005ustar00rootroot00000000000000nanoc-4.13.3/nanoc-cli/spec/nanoc/cli/000077500000000000000000000000001472033334600173475ustar00rootroot00000000000000nanoc-4.13.3/nanoc-cli/spec/nanoc/cli/cleaning_stream_spec.rb000066400000000000000000000040251472033334600240420ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::CLI::CleaningStream do let(:stream_class) do Class.new do attr_accessor :called_methods def initialize @called_methods = [] end def method_missing(symbol, *_args) @called_methods << symbol end def respond_to_missing?(*_args) true end end end it 'forwards methods' do methods = %i[write << flush tell print printf puts string reopen exist? exists? close closed?] s = stream_class.new cs = described_class.new(s) cs.write('aaa') cs << 'bb' cs.flush cs.tell cs.print('cc') cs.printf('cc') cs.puts('dd') cs.string cs.reopen('/dev/null', 'r') cs.exist? cs.exists? cs.close cs.closed? expect(s.called_methods).to eq(methods) end it 'forwards #tty? and #isatty' do s = stream_class.new cs = described_class.new(s) cs.tty? cs.isatty expect(s.called_methods).to eq([:tty?]) end it 'works with Logger' do # rubocop:disable RSpec/NoExpectationExample require 'logger' stream = StringIO.new cleaning_stream = described_class.new(stream) logger = Logger.new(cleaning_stream) logger.info('Some info') logger.warn('Something could start going wrong!') end it 'handles broken pipes' do # rubocop:disable RSpec/NoExpectationExample stream = StringIO.new def stream.write(_str) raise Errno::EPIPE.new end cleaning_stream = described_class.new(stream) cleaning_stream.write('lol') end it 'handles non-string objects' do obj = Object.new def obj.to_s 'Hello… world!' end stream = StringIO.new cleaning_stream = described_class.new(stream) cleaning_stream << obj expect(stream.string).to eq('Hello… world!') end it 'handles invalid strings' do s = [128].pack('C').force_encoding('UTF-8') stream = StringIO.new cleaning_stream = described_class.new(stream) cleaning_stream << s expect(stream.string).to eq("\xef\xbf\xbd") end end nanoc-4.13.3/nanoc-cli/spec/nanoc/cli/command_runner_spec.rb000066400000000000000000000052701472033334600237210ustar00rootroot00000000000000# 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::Core::TrivialError, 'The current working directory, nor any of its parents, seems to be a Nanoc site.') end end end describe '#load_site' do subject { command_runner.load_site } let(:command_runner) { described_class.new(nil, nil, nil) } 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::Core::Site) end end end nanoc-4.13.3/nanoc-cli/spec/nanoc/cli/commands/000077500000000000000000000000001472033334600211505ustar00rootroot00000000000000nanoc-4.13.3/nanoc-cli/spec/nanoc/cli/commands/compile_spec.rb000066400000000000000000000053461472033334600241470ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::CLI::Commands::Compile, site: true, stdio: true do describe '#run' do let(:site) do Nanoc::Core::Site.new( config:, code_snippets:, data_source: Nanoc::Core::InMemoryDataSource.new(items, layouts), ) end let(:config) { Nanoc::Core::Configuration.new(dir: Dir.getwd, hash: {}).with_defaults } let(:items) { Nanoc::Core::ItemCollection.new(config, []) } let(:layouts) { Nanoc::Core::LayoutCollection.new(config, []) } let(:code_snippets) { [] } it 'starts and stops listeners as needed' do test_listener_class = Class.new(Nanoc::CLI::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::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 = described_class.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 = described_class.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.13.3/nanoc-cli/spec/nanoc/cli/commands/shell_spec.rb000066400000000000000000000053021472033334600236160ustar00rootroot00000000000000# 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::Core::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::Core::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::Core::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 'preprocesses if requested' do expect_any_instance_of(Nanoc::Core::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::Core::SiteLoader.new.new_from_cwd end it 'returns views' do expect(subject[:items]).to be_a(Nanoc::Core::ItemCollectionWithRepsView) expect(subject[:layouts]).to be_a(Nanoc::Core::LayoutCollectionView) expect(subject[:config]).to be_a(Nanoc::Core::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.13.3/nanoc-cli/spec/nanoc/cli/commands/show_data_spec.rb000066400000000000000000000241511472033334600244630ustar00rootroot00000000000000# 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::Core::ItemCollection.new( config, [ item_about, item_dog, item_other, ], ) end let(:item_about) { Nanoc::Core::Item.new('About Me', {}, '/about.md') } let(:item_dog) { Nanoc::Core::Item.new('About My Dog', {}, '/dog.md') } let(:item_other) { Nanoc::Core::Item.new('Raw Data', {}, '/other.dat') } let(:config) { Nanoc::Core::Configuration.new(dir: Dir.getwd).with_defaults } let(:dependency_store) do Nanoc::Core::DependencyStore.new(items, layouts, config) end let(:layouts) do Nanoc::Core::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 (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 identifier$}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 identifier /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 identifier /about\.\*\n matching identifier /giraffe\.\*$}m).to_stdout end end context 'dependency onto one specific layout' do before do dependency_store.record_dependency(item_dog, layouts, raw_content: ['/about.*']) end it 'outputs dependencies for /dog.md' do expect { subject }.to output(%r{^item /dog.md depends on:\n \[ layouts \] \(r___\) matching identifier /about\.\*$}m).to_stdout end end context 'dependency onto multiple specific layouts' do before do dependency_store.record_dependency(item_dog, layouts, raw_content: ['/about.*']) dependency_store.record_dependency(item_dog, layouts, raw_content: ['/giraffe.*']) end it 'outputs dependencies for /dog.md' do expect { subject }.to output(%r{^item /dog.md depends on:\n \[ layouts \] \(r___\) matching identifier /about\.\*\n matching identifier /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::Core::Configuration.new(dir: Dir.getwd) } let(:items) do Nanoc::Core::ItemCollection.new( config, [ item_about, item_dog, ], ) end let(:item_about) { Nanoc::Core::Item.new('About Me', {}, '/about.md') } let(:item_dog) { Nanoc::Core::Item.new('About My Dog', {}, '/dog.md') } let(:item_rep_about) { Nanoc::Core::ItemRep.new(item_about, :default) } let(:item_rep_dog) { Nanoc::Core::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::Core::OutdatednessReasons::ContentModified, Nanoc::Core::OutdatednessReasons::AttributesModified.new([:title]), ] reasons_dog = [Nanoc::Core::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::Core::Configuration.new(dir: Dir.getwd) } let(:layouts) do Nanoc::Core::LayoutCollection.new(config, [layout]) end let(:layout) { Nanoc::Core::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::Core::OutdatednessReasons::ContentModified, Nanoc::Core::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.13.3/nanoc-cli/spec/nanoc/cli/commands/show_plugins_spec.rb000066400000000000000000000011251472033334600252270ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::CLI::Commands::ShowPlugins, site: true, stdio: true do describe '#run' do it 'can be invoked' do # rubocop:disable RSpec/NoExpectationExample Nanoc::CLI.run(['show-plugins']) end context 'site with plugins' do before do File.write('lib/default.rb', 'Nanoc::Core::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.13.3/nanoc-cli/spec/nanoc/cli/commands/view_spec.rb000066400000000000000000000055261472033334600234710ustar00rootroot00000000000000# frozen_string_literal: true require 'net/http' describe Nanoc::CLI::Commands::View, fork: true, site: true, stdio: true do describe '#run' do def run_nanoc_cmd(cmd) pid = fork { Nanoc::CLI.run(cmd) } # Wait for server to start up 20.times do |i| begin Net::HTTP.get('127.0.0.1', '/', 50_385) 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).compact.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 do Net::HTTP.start(non_local_addresses[0], 50_385, open_timeout: 0.2) do |http| request = Net::HTTP::Get.new('/') http.request(request) end end.to raise_error(/Failed to open TCP connection|execution expired/) end end end end nanoc-4.13.3/nanoc-cli/spec/nanoc/cli/compile_listeners/000077500000000000000000000000001472033334600230675ustar00rootroot00000000000000nanoc-4.13.3/nanoc-cli/spec/nanoc/cli/compile_listeners/abstract_spec.rb000066400000000000000000000041741472033334600262370ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::CLI::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 # rubocop:disable RSpec/NoExpectationExample 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 context 'listener that notifies' do let!(:notifications) { [] } let(:klass) do Class.new(described_class) do def start; end end end before do Nanoc::Core::NotificationCenter.on(:sah8sem0jaiw1phi4bai) do sleep 0.1 notifications << :notified end end it 'waits for notifications to be processed' do subject.run_while do Nanoc::Core::NotificationCenter.post(:sah8sem0jaiw1phi4bai) end expect(notifications).to eq([:notified]) end 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.13.3/nanoc-cli/spec/nanoc/cli/compile_listeners/debug_printer_spec.rb000066400000000000000000000032621472033334600272620ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::CLI::CompileListeners::DebugPrinter, stdio: true do let(:listener) { described_class.new(reps:) } let(:reps) do Nanoc::Core::ItemRepRepo.new end let(:item) { Nanoc::Core::Item.new('item content', {}, '/donkey.md') } let(:rep) { Nanoc::Core::ItemRep.new(item, :latex) } it 'records snapshot_created' do listener.start_safely expect { Nanoc::Core::NotificationCenter.post(:snapshot_created, rep, :last).sync } .to output(%r{Snapshot last created for /donkey.md \(rep name :latex\)}).to_stdout end it 'prints with timestamp' do listener.start_safely expect { Nanoc::Core::NotificationCenter.post(:snapshot_created, rep, :last).sync } .to output(%r{^\*\*\* \d{2}:\d{2}:\d{2}\.\d{3} .*Snapshot last created for /donkey.md \(rep name :latex\)}).to_stdout end it 'records cached_content_used' do listener.start_safely expect { Nanoc::Core::NotificationCenter.post(:cached_content_used, rep).sync } .to output(%r{Used cached compiled content for /donkey.md \(rep name :latex\) instead of recompiling}).to_stdout end it 'records stage_started' do listener.start_safely expect { Nanoc::Core::NotificationCenter.post(:stage_started, 'Moo').sync } .to output(/Stage started: Moo/).to_stdout end it 'records stage_ended' do listener.start_safely expect { Nanoc::Core::NotificationCenter.post(:stage_ended, 'Moo').sync } .to output(/Stage ended: Moo/).to_stdout end it 'records stage_aborted' do listener.start_safely expect { Nanoc::Core::NotificationCenter.post(:stage_aborted, 'Moo').sync } .to output(/Stage aborted: Moo/).to_stdout end end nanoc-4.13.3/nanoc-cli/spec/nanoc/cli/compile_listeners/diff_generator_spec.rb000066400000000000000000000050241472033334600274050ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::CLI::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::Core::Site.new( config:, code_snippets:, data_source: Nanoc::Core::InMemoryDataSource.new(items, layouts), ) end let(:config) { Nanoc::Core::Configuration.new(dir: Dir.getwd, hash: config_hash).with_defaults } let(:items) { Nanoc::Core::ItemCollection.new(config, []) } let(:layouts) { Nanoc::Core::LayoutCollection.new(config, []) } 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 describe Nanoc::CLI::CompileListeners::DiffGenerator::Differ do subject { differ.call } let(:differ) { described_class.new('content/foo.md', str_a, str_b) } let(:str_a) do %w[a b c d e f g h i j k l m n o p q r s].join("\n") end let(:str_b) do # remove c, d # add !!! %w[a b e f g h i j k l m !!! n o p q r s].join("\n") end it 'generates the proper diff' do expect(subject).to eq(<<~EOS) --- content/foo.md +++ content/foo.md @@ -1,7 +1,5 @@ a b -c -d e f g @@ -11,6 +9,7 @@ k l m +!!! n o p EOS end context 'when hunks are overlapping' do let(:str_a) do <<~EOS A B MOVED D E F DELETED EOS end let(:str_b) do <<~EOS A B D E F MOVED EOS end it 'correctly merges hunks' do expect(subject).to eq(<<~EOS) --- content/foo.md +++ content/foo.md @@ -1,8 +1,7 @@ A B -MOVED D E F -DELETED +MOVED EOS end end end end nanoc-4.13.3/nanoc-cli/spec/nanoc/cli/compile_listeners/file_action_printer_spec.rb000066400000000000000000000107021472033334600304450ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::CLI::CompileListeners::FileActionPrinter, stdio: true do let(:listener) { described_class.new(reps:) } let(:reps) do Nanoc::Core::ItemRepRepo.new.tap do |reps| reps << rep end end let(:item) { Nanoc::Core::Item.new('<%= 1 + 2 %>', {}, '/hi.md') } let(:rep) do Nanoc::Core::ItemRep.new(item, :default).tap do |rep| rep.raw_paths = { default: ['/hi.html'] } end end let(:original_timestamp) { Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond) } def mock_time(seconds) allow(Process) .to receive(:clock_gettime) .with(Process::CLOCK_MONOTONIC, :nanosecond) .and_return(original_timestamp + seconds * 1_000_000_000) end after do listener.stop_safely end it 'records from compilation_started to rep_write_ended' do listener.start_safely mock_time(0) Nanoc::Core::NotificationCenter.post(:compilation_started, rep).sync mock_time(1) expect { Nanoc::Core::NotificationCenter.post(:rep_write_ended, rep, false, '/foo.html', true, true).sync } .to output(/create.*\[1\.00s\]/).to_stdout end it 'stops listening after #stop' do listener.start_safely listener.stop_safely Nanoc::Core::NotificationCenter.post(:compilation_started, rep).sync expect { Nanoc::Core::NotificationCenter.post(:rep_write_ended, rep, false, '/foo.html', true, true).sync } .not_to output(/create/).to_stdout end it 'records from compilation_started over compilation_suspended to rep_write_ended' do listener.start_safely mock_time(0) Nanoc::Core::NotificationCenter.post(:compilation_started, rep).sync mock_time(1) Nanoc::Core::NotificationCenter.post(:compilation_suspended, rep, :__irrelevant__).sync mock_time(3) Nanoc::Core::NotificationCenter.post(:compilation_started, rep).sync mock_time(6) expect { Nanoc::Core::NotificationCenter.post(:rep_write_ended, rep, false, '/foo.html', true, true).sync } .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_safely mock_time(0) Nanoc::Core::NotificationCenter.post(:compilation_started, rep).sync mock_time(1) Nanoc::Core::NotificationCenter.post(:rep_write_enqueued, rep).sync mock_time(3) Nanoc::Core::NotificationCenter.post(:rep_write_started, rep).sync mock_time(6) expect { Nanoc::Core::NotificationCenter.post(:rep_write_ended, rep, false, '/foo.html', true, true).sync } .to output(/create.*\[4\.00s\]/).to_stdout end context 'log level = high' do before do listener.start_safely Nanoc::CLI::Logger.instance.level = :high end it 'does not print skipped (uncompiled) reps' do expect { listener.stop_safely } .not_to output(/skip/).to_stdout end it 'prints nothing' do Nanoc::Core::NotificationCenter.post(:compilation_started, rep).sync mock_time(1) expect { Nanoc::Core::NotificationCenter.post(:rep_write_ended, rep, false, '/foo.html', false, false).sync } .not_to output(/identical/).to_stdout end it 'prints nothing' do Nanoc::Core::NotificationCenter.post(:compilation_started, rep).sync Nanoc::Core::NotificationCenter.post(:cached_content_used, rep).sync mock_time(1) expect { Nanoc::Core::NotificationCenter.post(:rep_write_ended, rep, false, '/foo.html', false, false).sync } .not_to output(/cached/).to_stdout end end context 'log level = low' do before do listener.start_safely Nanoc::CLI::Logger.instance.level = :low end it 'prints skipped (uncompiled) reps' do expect { listener.stop_safely } .to output(/skip.*\/hi\.html/).to_stdout end it 'prints “identical” if not cached' do Nanoc::Core::NotificationCenter.post(:compilation_started, rep).sync mock_time(1) expect { Nanoc::Core::NotificationCenter.post(:rep_write_ended, rep, false, '/foo.html', false, false).sync } .to output(/identical/).to_stdout end it 'prints “cached” if cached' do Nanoc::Core::NotificationCenter.post(:compilation_started, rep).sync Nanoc::Core::NotificationCenter.post(:cached_content_used, rep).sync mock_time(1) expect { Nanoc::Core::NotificationCenter.post(:rep_write_ended, rep, false, '/foo.html', false, false).sync } .to output(/cached/).to_stdout end end end nanoc-4.13.3/nanoc-cli/spec/nanoc/cli/compile_listeners/timing_recorder_spec.rb000066400000000000000000000276361472033334600276200ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::CLI::CompileListeners::TimingRecorder, stdio: true do let(:listener) { described_class.new(reps:) } let(:reps) do Nanoc::Core::ItemRepRepo.new.tap do |reps| reps << rep end end let(:item) { Nanoc::Core::Item.new('<%= 1 + 2 %>', {}, '/hi.md') } let(:rep) do Nanoc::Core::ItemRep.new(item, :default).tap do |rep| rep.raw_paths = { default: ['/hi.html'] } end end let(:other_rep) do Nanoc::Core::ItemRep.new(item, :other).tap do |rep| rep.raw_paths = { default: ['/bye.html'] } end end let(:original_timestamp) { Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond) } def mock_time(seconds) allow(Process) .to receive(:clock_gettime) .with(Process::CLOCK_MONOTONIC, :nanosecond) .and_return(original_timestamp + seconds * 1_000_000_000) end before do Nanoc::CLI.verbosity = 2 listener.start_safely end after do listener.stop_safely end it 'prints filters table' do mock_time(0) Nanoc::Core::NotificationCenter.post(:filtering_started, rep, :erb).sync mock_time(1) Nanoc::Core::NotificationCenter.post(:filtering_ended, rep, :erb).sync mock_time(100) Nanoc::Core::NotificationCenter.post(:filtering_started, rep, :erb).sync mock_time(102) Nanoc::Core::NotificationCenter.post(:filtering_ended, rep, :erb).sync expect { listener.stop_safely } .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 mock_time(0) Nanoc::Core::NotificationCenter.post(:filtering_started, rep, :erb).sync mock_time(1) Nanoc::Core::NotificationCenter.post(:filtering_ended, rep, :erb).sync 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 mock_time(0) Nanoc::Core::NotificationCenter.post(:filtering_started, rep, :erb).sync mock_time(1) Nanoc::Core::NotificationCenter.post(:filtering_ended, rep, :erb).sync mock_time(100) Nanoc::Core::NotificationCenter.post(:filtering_started, rep, :erb).sync mock_time(102) Nanoc::Core::NotificationCenter.post(:filtering_ended, rep, :erb).sync 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 mock_time(0) Nanoc::Core::NotificationCenter.post(:filtering_started, rep, :outer).sync mock_time(1) Nanoc::Core::NotificationCenter.post(:filtering_started, rep, :inner).sync mock_time(3) Nanoc::Core::NotificationCenter.post(:filtering_ended, rep, :inner).sync mock_time(6) Nanoc::Core::NotificationCenter.post(:filtering_ended, rep, :outer).sync 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 mock_time(0) Nanoc::Core::NotificationCenter.post(:compilation_started, rep).sync Nanoc::Core::NotificationCenter.post(:filtering_started, rep, :outer).sync mock_time(1) Nanoc::Core::NotificationCenter.post(:filtering_started, rep, :inner).sync mock_time(3) Nanoc::Core::NotificationCenter.post(:compilation_suspended, rep, :__anything__).sync mock_time(6) Nanoc::Core::NotificationCenter.post(:compilation_started, rep).sync mock_time(10) Nanoc::Core::NotificationCenter.post(:filtering_ended, rep, :inner).sync Nanoc::Core::NotificationCenter.post(:filtering_ended, rep, :outer).sync 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::Core::NotificationCenter.post(:compilation_started, rep).sync mock_time(0) Nanoc::Core::NotificationCenter.post(:filtering_started, rep, :erb).sync mock_time(1) Nanoc::Core::NotificationCenter.post(:compilation_suspended, rep, :__anything__).sync mock_time(3) Nanoc::Core::NotificationCenter.post(:compilation_started, rep).sync mock_time(7) Nanoc::Core::NotificationCenter.post(:filtering_ended, rep, :erb).sync 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 mock_time(0) Nanoc::Core::NotificationCenter.post(:phase_started, 'donkey', rep).sync mock_time(1) Nanoc::Core::NotificationCenter.post(:phase_ended, 'donkey', rep).sync 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 mock_time(0) Nanoc::Core::NotificationCenter.post(:phase_started, 'donkey', rep).sync mock_time(1) Nanoc::Core::NotificationCenter.post(:phase_ended, 'donkey', rep).sync mock_time(100) Nanoc::Core::NotificationCenter.post(:phase_started, 'donkey', rep).sync mock_time(102) Nanoc::Core::NotificationCenter.post(:phase_ended, 'donkey', rep).sync 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 mock_time(0) Nanoc::Core::NotificationCenter.post(:phase_started, 'donkey', rep).sync mock_time(1) Nanoc::Core::NotificationCenter.post(:phase_yielded, 'donkey', rep).sync mock_time(100) Nanoc::Core::NotificationCenter.post(:phase_resumed, 'donkey', rep).sync mock_time(102) Nanoc::Core::NotificationCenter.post(:phase_ended, 'donkey', rep).sync 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 mock_time(0) Nanoc::Core::NotificationCenter.post(:phase_started, 'donkey', rep).sync mock_time(1) Nanoc::Core::NotificationCenter.post(:phase_yielded, 'donkey', rep).sync mock_time(100) Nanoc::Core::NotificationCenter.post(:phase_aborted, 'donkey', rep).sync mock_time(200) Nanoc::Core::NotificationCenter.post(:phase_started, 'donkey', rep).sync mock_time(203) Nanoc::Core::NotificationCenter.post(:phase_ended, 'donkey', rep).sync 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::Core::NotificationCenter.post(:stage_ran, 1.23, 'donkey_stage').sync 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::Core::NotificationCenter.post(:stage_ran, 1.23, 'donkey_stage').sync expect { listener.stop_safely } .to output(/^\s*donkey_stage │ 1\.23s$/).to_stdout end it 'prints out outdatedness rule durations' do Nanoc::Core::NotificationCenter.post(:outdatedness_rule_ran, 1.0, Nanoc::Core::OutdatednessRules::CodeSnippetsModified).sync expect { listener.stop_safely } .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::Core::NotificationCenter.post(:outdatedness_rule_ran, 1.0, Nanoc::Core::OutdatednessRules::CodeSnippetsModified).sync 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::Core::NotificationCenter.post(:outdatedness_rule_ran, 1.0, Nanoc::Core::OutdatednessRules::CodeSnippetsModified).sync Nanoc::Core::NotificationCenter.post(:outdatedness_rule_ran, 3.0, Nanoc::Core::OutdatednessRules::CodeSnippetsModified).sync 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::Core::NotificationCenter.post(:store_loaded, 1.23, Nanoc::Core::ChecksumStore).sync expect { listener.stop_safely } .to output(/^\s*Nanoc::Core::ChecksumStore │ 1\.23s$/).to_stdout end it 'prints store store durations' do Nanoc::Core::NotificationCenter.post(:store_stored, 2.34, Nanoc::Core::ChecksumStore).sync expect { listener.stop_safely } .to output(/^\s*Nanoc::Core::ChecksumStore │ 2\.34s$/).to_stdout end it 'skips printing empty metrics' do expect { listener.stop_safely } .not_to output(/filters|phases|stages/).to_stdout end end nanoc-4.13.3/nanoc-cli/spec/nanoc/cli/error_handler_spec.rb000066400000000000000000000107241472033334600235400ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::CLI::ErrorHandler, stdio: true do subject(:error_handler) { described_class.new } describe '#trivial?' do subject { error_handler.trivial?(error) } context 'LoadError of known gem' do let(:error) do raise LoadError, 'cannot load such file -- nokogiri' rescue LoadError => e return e end it { is_expected.to be(true) } end context 'LoadError of unknown gem' do let(:error) do raise LoadError, 'cannot load such file -- whatever' rescue LoadError => e return e end it { is_expected.to be(false) } end context 'random error' do let(:error) do raise 'stuff' rescue => e return e end it { is_expected.to be(false) } end context 'Errno::EADDRINUSE' do let(:error) do raise Errno::EADDRINUSE rescue => e return e end it { is_expected.to be(true) } end context 'TrivialError' do let(:error) do raise Nanoc::Core::TrivialError, 'oh just a tiny thing' rescue => e return e end it { is_expected.to be(true) } end end describe '#handle_error' do subject { error_handler.handle_error(error, exit_on_error:) } let(:error) do raise 'Bewm' rescue => e return e 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 raise Nanoc::Core::TrivialError, 'asdf' rescue => e return e end it 'prints summary' do expect { subject }.to output("\nError: asdf\n").to_stderr end end context 'LoadError' do let(:error) do raise LoadError, 'cannot load such file -- nokogiri' rescue LoadError => e return e 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 'when error implements #full_message', stdio: true do let(:klass) do Class.new(StandardError) do def self.to_s 'SubclassOfStandardError' end def full_message "okay so what I mean is that #{message}" end end end let(:error) do raise klass.new('it is broken') rescue => e return e end it 'prints error message followed by error detail' do subject expect($stderr.string).to match( %r{SubclassOfStandardError: it is broken.*okay so what I mean is that it is broken}m, ) 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 Nanoc::Core::Configuration.new(dir: '/oink', hash: { enable_output_diff: 'yeah' }) rescue => e return e 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::Core::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 describe '#handle_while' do it 'makes #exit bubble up a SystemExit' do expect do error_handler.handle_while(exit_on_error: false) do exit(0) end end.to raise_error(SystemExit) end end end nanoc-4.13.3/nanoc-cli/spec/nanoc/cli/stack_trace_writer_spec.rb000066400000000000000000000042421472033334600245670ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::CLI::StackTraceWriter do subject(:writer) do described_class.new(io) end let(:io) { StringIO.new } describe '#write' do subject { writer.write(exception, verbose:) } 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 let(:verbose) { 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 end nanoc-4.13.3/nanoc-cli/spec/nanoc/cli/stream_cleaners/000077500000000000000000000000001472033334600225165ustar00rootroot00000000000000nanoc-4.13.3/nanoc-cli/spec/nanoc/cli/stream_cleaners/utf8_spec.rb000066400000000000000000000010631472033334600247430ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::CLI::StreamCleaners::UTF8 do subject { described_class.new } context 'when passed a string that is not UTF-8 encoded' do let(:str) { String.new('Not UTF-8', encoding: 'ASCII-8BIT') } it 'does not attempt to clean the string' do expect(str).not_to receive(:unicode_normalize) expect(subject.clean(str)).to eq(str) end end it 'handles all cases' do expect(subject.clean('┼─ “© Denis” ‘and others…’ ─┼')).to eq('+- "(c) Denis" \'and others...\' -+') end end nanoc-4.13.3/nanoc-cli/spec/nanoc/cli/version_spec.rb000066400000000000000000000002631472033334600223740ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::CLI::VERSION do it 'is the same as Nanoc::Core::VERSION' do expect(Nanoc::CLI::VERSION).to eq(Nanoc::Core::VERSION) end end nanoc-4.13.3/nanoc-cli/spec/nanoc/cli_spec.rb000066400000000000000000000052321472033334600207100ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::CLI do it 'enables UTF-8 only on TTYs' do new_env_diff = { 'LC_ALL' => 'en_US.ISO-8859-1', 'LC_CTYPE' => 'en_US.ISO-8859-1', 'LANG' => 'en_US.ISO-8859-1', } __nanoc_core_with_env_vars(new_env_diff) do io = StringIO.new def io.tty? true end expect(described_class.enable_utf8?(io)).not_to be io = StringIO.new def io.tty? false end expect(described_class.enable_utf8?(io)).to be end end it 'enables UTF-8 when appropriate' do 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', } __nanoc_core_with_env_vars(new_env_diff) do expect(described_class.enable_utf8?(io)).not_to be __nanoc_core_with_env_vars('LC_ALL' => 'en_US.UTF-8') { expect(described_class.enable_utf8?(io)).to be } __nanoc_core_with_env_vars('LC_CTYPE' => 'en_US.UTF-8') { expect(described_class.enable_utf8?(io)).to be } __nanoc_core_with_env_vars('LANG' => 'en_US.UTF-8') { expect(described_class.enable_utf8?(io)).to be } __nanoc_core_with_env_vars('LC_ALL' => 'en_US.utf-8') { expect(described_class.enable_utf8?(io)).to be } __nanoc_core_with_env_vars('LC_CTYPE' => 'en_US.utf-8') { expect(described_class.enable_utf8?(io)).to be } __nanoc_core_with_env_vars('LANG' => 'en_US.utf-8') { expect(described_class.enable_utf8?(io)).to be } __nanoc_core_with_env_vars('LC_ALL' => 'en_US.utf8') { expect(described_class.enable_utf8?(io)).to be } __nanoc_core_with_env_vars('LC_CTYPE' => 'en_US.utf8') { expect(described_class.enable_utf8?(io)).to be } __nanoc_core_with_env_vars('LANG' => 'en_US.utf8') { expect(described_class.enable_utf8?(io)).to be } 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.13.3/nanoc-cli/spec/spec_helper.rb000066400000000000000000000024101472033334600203150ustar00rootroot00000000000000# frozen_string_literal: true require_relative '../../common/spec/spec_helper_head_core' require 'nanoc/cli' require_relative '../../common/spec/spec_helper_foot_core' Nanoc::CLI.setup Class.new(Nanoc::Core::DataSource) do # fake identifier :filesystem def items Dir['content/*'].map do |filename| Nanoc::Core::Item.new(File.read(filename), {}, filename.sub(/^content\//, '/')) end end def layouts Dir['layouts/*'].map do |filename| Nanoc::Core::Layout.new(File.read(filename), {}, filename.sub(/^layouts\//, '/')) end end end Class.new(Nanoc::Core::ActionProvider) do # fake identifier :rule_dsl def self.for(_site) new end def need_preprocessing? true end def preprocess(site) item = site.items.object_matching_glob('/hello.*') if item item.content = Nanoc::Core::TextualContent.new('Better hello!') end end def postprocess(_site, _reps); end def rep_names_for(_item) [:default] end def action_sequence_for(rep) Nanoc::Core::ActionSequence.new( actions: [ Nanoc::Core::ProcessingActions::Snapshot.new([:last], [rep.item.identifier.to_s]), ], ) end def snapshots_defs_for(_rep) [Nanoc::Core::SnapshotDef.new(:last, binary: false)] end end nanoc-4.13.3/nanoc-core/000077500000000000000000000000001472033334600147315ustar00rootroot00000000000000nanoc-4.13.3/nanoc-core/.rspec000066400000000000000000000000751472033334600160500ustar00rootroot00000000000000-r ./spec/spec_helper.rb --format Fuubar --color --backtrace nanoc-4.13.3/nanoc-core/LICENSE000066400000000000000000000020711472033334600157360ustar00rootroot00000000000000Copyright (c) 2019–… 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.13.3/nanoc-core/NEWS.md000066400000000000000000000001001472033334600160160ustar00rootroot00000000000000# nanoc-core news See the release notes for Nanoc for details. nanoc-4.13.3/nanoc-core/README.md000066400000000000000000000002551472033334600162120ustar00rootroot00000000000000# nanoc-core This repository contains the core of Nanoc. It is designed to have no dependencies (neither gems nor external applications), and be operating system-agnostic. nanoc-4.13.3/nanoc-core/Rakefile000066400000000000000000000004341472033334600163770ustar00rootroot00000000000000# 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.13.3/nanoc-core/lib/000077500000000000000000000000001472033334600154775ustar00rootroot00000000000000nanoc-4.13.3/nanoc-core/lib/nanoc-core.rb000066400000000000000000000000641472033334600200500ustar00rootroot00000000000000# frozen_string_literal: true require 'nanoc/core' nanoc-4.13.3/nanoc-core/lib/nanoc/000077500000000000000000000000001472033334600165755ustar00rootroot00000000000000nanoc-4.13.3/nanoc-core/lib/nanoc/core.rb000066400000000000000000000036701472033334600200600ustar00rootroot00000000000000# frozen_string_literal: true # Ruby stdlib require 'base64' require 'fiber' require 'find' require 'singleton' require 'tmpdir' require 'yaml' require 'zlib' # External gems require 'concurrent-ruby' require 'json_schema' require 'ddmetrics' require 'ddplugin' require 'immutable' require 'memo_wise' require 'slow_enumerator_tools' require 'tty-platform' require 'zeitwerk' # External gems (optional) begin require 'clonefile' rescue LoadError # ignore end module Nanoc module Core # 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 def UNDEFINED.inspect '' end def UNDEFINED.to_s inspect end # @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::Core::VERSION} © 2007–… 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? @_on_windows ||= TTY::Platform.new.windows? end end end inflector_class = Class.new(Zeitwerk::Inflector) do def camelize(basename, abspath) case basename when 'version' 'VERSION' else super end end end loader = Zeitwerk::Loader.new loader.inflector = inflector_class.new loader.push_dir(__dir__ + '/..') loader.ignore(__dir__ + '/../nanoc-core.rb') loader.ignore(__dir__ + '/core/core_ext') loader.setup loader.eager_load require_relative 'core/core_ext/array' require_relative 'core/core_ext/hash' require_relative 'core/core_ext/string' nanoc-4.13.3/nanoc-core/lib/nanoc/core/000077500000000000000000000000001472033334600175255ustar00rootroot00000000000000nanoc-4.13.3/nanoc-core/lib/nanoc/core/action_provider.rb000066400000000000000000000011331472033334600232370ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core # @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 end nanoc-4.13.3/nanoc-core/lib/nanoc/core/action_sequence.rb000066400000000000000000000024441472033334600232230ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core class ActionSequence # include Nanoc::Core::ContractsSupport include Enumerable prepend MemoWise attr_reader :actions def initialize(actions: []) @actions = actions end # contract C::None => Numeric def size @actions.size end # contract Numeric => C::Maybe[Nanoc::Core::ProcessingAction] def [](idx) @actions[idx] end # contract C::None => C::ArrayOf[Nanoc::Core::ProcessingAction] def snapshot_actions @actions.select { |a| a.is_a?(Nanoc::Core::ProcessingActions::Snapshot) } end # contract C::None => Array def paths snapshot_actions.map { |a| [a.snapshot_names, a.paths] } end def serialize serialize_uncached end memo_wise :serialize # contract C::None => Array def serialize_uncached to_a.map(&:serialize) end # contract C::Func[Nanoc::Core::ProcessingAction => C::Any] => self def each(&) @actions.each(&) self end # contract C::Func[Nanoc::Core::ProcessingAction => C::Any] => self def map(&) self.class.new( actions: @actions.map(&), ) end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/action_sequence_builder.rb000066400000000000000000000034111472033334600247240ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core class ActionSequenceBuilder include Nanoc::Core::ContractsSupport # Error that is raised when a snapshot with an existing name is made. class CannotCreateMultipleSnapshotsWithSameNameError < ::Nanoc::Core::Error include Nanoc::Core::ContractsSupport contract Nanoc::Core::ItemRep, Symbol => C::Any def initialize(rep, snapshot) super("Attempted to create a snapshot with a duplicate name #{snapshot.inspect} for the item rep #{rep}") end end def self.build builder = new yield(builder) builder.action_sequence end def initialize @actions = [] end contract Symbol, Hash => self def add_filter(filter_name, params) @actions << Nanoc::Core::ProcessingActions::Filter.new(filter_name, params) self end contract String, C::Maybe[Hash] => self def add_layout(layout_identifier, params) @actions << Nanoc::Core::ProcessingActions::Layout.new(layout_identifier, params) self end def add_snapshot(snapshot_name, path, rep) will_add_snapshot(snapshot_name, rep) @actions << Nanoc::Core::ProcessingActions::Snapshot.new([snapshot_name], path ? [path] : []) self end contract C::None => Nanoc::Core::ActionSequence def action_sequence Nanoc::Core::ActionSequence.new(actions: @actions) end private def will_add_snapshot(name, rep) @_snapshot_names ||= Set.new if @_snapshot_names.include?(name) raise CannotCreateMultipleSnapshotsWithSameNameError.new(rep, name) else @_snapshot_names << name end end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/action_sequence_store.rb000066400000000000000000000026061472033334600244370ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core # Stores action sequences for objects that can be run through a rule (item # representations and layouts). # # @api private class ActionSequenceStore < ::Nanoc::Core::Store include Nanoc::Core::ContractsSupport contract C::KeywordArgs[config: Nanoc::Core::Configuration] => C::Any def initialize(config:) super(Nanoc::Core::Store.tmp_path_for(config:, store_name: 'rule_memory'), 2) @action_sequences = {} end # @param [Nanoc::Core::ItemRep, Nanoc::Core::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::Core::ItemRep, Nanoc::Core::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::Core::Store#data def data @action_sequences end # @see Nanoc::Core::Store#data= def data=(new_data) @action_sequences = new_data end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/aggregate_data_source.rb000066400000000000000000000015121472033334600243500ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core class AggregateDataSource < ::Nanoc::Core::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::Core::ItemCollection.new(@config, objs) end end def layouts @_layouts ||= begin objs = @data_sources.flat_map(&:layouts) Nanoc::Core::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 end nanoc-4.13.3/nanoc-core/lib/nanoc/core/assertions.rb000066400000000000000000000027241472033334600222510ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core module Assertions class AssertionFailure < Nanoc::Core::Error end module Mixin def assert(assertion) return unless Nanoc::Core::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::Core::Assertions::Base include Nanoc::Core::ContractsSupport contract C::KeywordArgs[compiled_content_cache: C::Or[Nanoc::Core::CompiledContentCache, Nanoc::Core::TextualCompiledContentCache], item_reps: Nanoc::Core::ItemRepRepo] => C::Any def initialize(compiled_content_cache:, item_reps:) @compiled_content_cache = compiled_content_cache @item_reps = item_reps end contract C::None => C::Bool def call @item_reps.all? do |rep| @compiled_content_cache[rep] end end end class PathIsAbsolute < Nanoc::Core::Assertions::Base include Nanoc::Core::ContractsSupport contract C::KeywordArgs[path: String] => C::Any def initialize(path:) @path = path end contract C::None => C::Bool def call Pathname.new(@path).absolute? end end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/basic_item_rep_collection_view.rb000066400000000000000000000045141472033334600262700ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core class BasicItemRepCollectionView < ::Nanoc::Core::View include Enumerable class NoSuchItemRepError < ::Nanoc::Core::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::Core::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::Core::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::Core::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 end nanoc-4.13.3/nanoc-core/lib/nanoc/core/basic_item_rep_view.rb000066400000000000000000000042121472033334600240500ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core class BasicItemRepView < ::Nanoc::Core::View # @api private def initialize(item_rep, context) super(context) @item_rep = item_rep end # @abstract def item_view_class Nanoc::Core::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, item.identifier, 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:) end # Returns the item that this item rep belongs to. # # @return [Nanoc::Core::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::Core::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 end nanoc-4.13.3/nanoc-core/lib/nanoc/core/basic_item_view.rb000066400000000000000000000035351472033334600232110ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core class BasicItemView < ::Nanoc::Core::View include Nanoc::Core::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::Core::Errors::CannotGetParentOrChildrenOfNonLegacyItem.new(_unwrap.identifier) end children_pattern = Nanoc::Core::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::Core::CompilationItemView] if the item has a parent # # @return [nil] if the item has no parent def parent unless _unwrap.identifier.legacy? raise Nanoc::Core::Errors::CannotGetParentOrChildrenOfNonLegacyItem.new(_unwrap.identifier) end parent_identifier = '/' + _unwrap.identifier.components[0..-2].join('/') + '/' parent_identifier = '/' if parent_identifier == '//' parent = @context.items.object_with_identifier(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 end nanoc-4.13.3/nanoc-core/lib/nanoc/core/basic_outdatedness_checker.rb000066400000000000000000000101661472033334600254050ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core class BasicOutdatednessChecker include Nanoc::Core::ContractsSupport attr_reader :site attr_reader :checksum_store attr_reader :checksums attr_reader :dependency_store attr_reader :action_sequence_store attr_reader :action_sequences Rules = Nanoc::Core::OutdatednessRules RULES_FOR_ITEM_REP = [ Rules::ItemAdded, Rules::RulesModified, Rules::ContentModified, Rules::AttributesModified, Rules::NotWritten, Rules::CodeSnippetsModified, Rules::UsesAlwaysOutdatedFilter, ].freeze RULES_FOR_LAYOUT = [ Rules::LayoutAdded, Rules::RulesModified, Rules::ContentModified, Rules::AttributesModified, Rules::UsesAlwaysOutdatedFilter, ].freeze RULES_FOR_CONFIG = [ Rules::AttributesModified, ].freeze C_OBJ = C::Or[ Nanoc::Core::Item, Nanoc::Core::ItemRep, Nanoc::Core::Configuration, Nanoc::Core::Layout, Nanoc::Core::ItemCollection, ] C_OBJ_MAYBE_REP = C::Or[ Nanoc::Core::Item, Nanoc::Core::ItemRep, Nanoc::Core::Configuration, Nanoc::Core::Layout, Nanoc::Core::ItemCollection, Nanoc::Core::LayoutCollection, ] C_ACTION_SEQUENCES = C::HashOf[C_OBJ => Nanoc::Core::ActionSequence] contract C::KeywordArgs[ site: Nanoc::Core::Site, checksum_store: Nanoc::Core::ChecksumStore, checksums: Nanoc::Core::ChecksumCollection, dependency_store: Nanoc::Core::DependencyStore, action_sequence_store: Nanoc::Core::ActionSequenceStore, action_sequences: C_ACTION_SEQUENCES, reps: Nanoc::Core::ItemRepRepo, ] => C::Any def initialize(site:, checksum_store:, checksums:, dependency_store:, action_sequence_store:, action_sequences:, reps:) @reps = reps @site = site @checksum_store = checksum_store @checksums = checksums @dependency_store = dependency_store @action_sequence_store = action_sequence_store @action_sequences = action_sequences # Memoize @_outdatedness_status_for = {} end contract C_OBJ_MAYBE_REP => C::Maybe[Nanoc::Core::OutdatednessStatus] def outdatedness_status_for(obj) # TODO: remove memoization (no longer needed) @_outdatedness_status_for[obj] ||= case obj when Nanoc::Core::ItemRep apply_rules(RULES_FOR_ITEM_REP, obj) when Nanoc::Core::Item apply_rules_multi(RULES_FOR_ITEM_REP, @reps[obj]) when Nanoc::Core::Layout apply_rules(RULES_FOR_LAYOUT, obj) when Nanoc::Core::Configuration apply_rules(RULES_FOR_CONFIG, obj) when Nanoc::Core::ItemCollection, Nanoc::Core::LayoutCollection # Collections are never outdated. Objects inside them might be, # however. apply_rules([], obj) else raise Nanoc::Core::Errors::InternalInconsistency, "do not know how to check outdatedness of #{obj.inspect}" end end def action_sequence_for(rep) @action_sequences.fetch(rep) end private contract C::ArrayOf[Class], C_OBJ_MAYBE_REP, Nanoc::Core::OutdatednessStatus => C::Maybe[Nanoc::Core::OutdatednessStatus] def apply_rules(rules, obj, status = Nanoc::Core::OutdatednessStatus.new) rules.inject(status) do |acc, rule| if acc.useful_to_apply?(rule) reason = rule.instance.call(obj, self) if reason acc.update(reason) else acc end else acc end end end contract C::ArrayOf[Class], C::ArrayOf[C_OBJ_MAYBE_REP] => C::Maybe[Nanoc::Core::OutdatednessStatus] def apply_rules_multi(rules, objs) objs.inject(Nanoc::Core::OutdatednessStatus.new) do |acc, elem| apply_rules(rules, elem, acc) end end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/binary_compiled_content_cache.rb000066400000000000000000000113071472033334600260710ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core # Represents a cache than can be used to store already compiled content, # to prevent it from being needlessly recompiled. # # @api private class BinaryCompiledContentCache < ::Nanoc::Core::Store include Nanoc::Core::ContractsSupport contract C::KeywordArgs[config: Nanoc::Core::Configuration] => C::Any def initialize(config:) super(Nanoc::Core::Store.tmp_path_for(config:, store_name: 'binary_content'), 3) @cache = {} end contract Nanoc::Core::ItemRep => C::Maybe[C::HashOf[Symbol => Nanoc::Core::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] || {} rep_cache = item_cache[rep.name] return nil if rep_cache.nil? rep_cache.transform_values do |filename| Nanoc::Core::Content.create(filename, binary: true) end end contract Nanoc::Core::ItemRep => C::Bool def include?(rep) item_cache = @cache[rep.item.identifier] || {} item_cache.key?(rep.name) end contract Nanoc::Core::ItemRep, C::HashOf[Symbol => Nanoc::Core::BinaryContent] => C::HashOf[Symbol => Nanoc::Core::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] ||= {} rep_cache = @cache[rep.item.identifier][rep.name] content.each do |snapshot, binary_content| # Check if Nanoc::Core::ContractsSupport.enabled? && !File.file?(binary_content.filename) raise Nanoc::Core::Errors::InternalInconsistency, "Binary content at #{binary_content.filename.inspect} does not exist, but is expected to." end filename = build_filename(rep, snapshot) rep_cache[snapshot] = filename # Avoid reassigning the same content if this binary cached content was # already used, because it was available and the item wasn’t oudated. next if binary_content.filename == filename # Copy FileUtils.mkdir_p(File.dirname(filename)) smart_cp(binary_content.filename, filename) end end def prune(items:) item_identifiers = Set.new(items.map(&:identifier)) @cache.each_key do |key| # TODO: remove unused item reps next if item_identifiers.include?(key) @cache.delete(key) path = dirname_for_item_identifier(key) FileUtils.rm_rf(path) end end 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 def use_clonefile? defined?(Clonefile) end private def dirname filename + '_data' end def string_to_path_component(string) string.gsub(/[^a-zA-Z0-9]+/, '_') + '-' + Digest::SHA1.hexdigest(string)[0..9] end def dirname_for_item_identifier(item_identifier) File.join( dirname, string_to_path_component(item_identifier.to_s), ) end def dirname_for_item_rep(rep) File.join( dirname_for_item_identifier(rep.item.identifier), string_to_path_component(rep.name.to_s), ) end def build_filename(rep, snapshot_name) File.join( dirname_for_item_rep(rep), string_to_path_component(snapshot_name.to_s), ) end # NOTE: Similar to ItemRepWriter#smart_cp (but without hardlinking) def smart_cp(from, to) # NOTE: hardlinking is not an option in this case, because hardlinking # would make it possible for the content to be (inadvertently) # changed outside of Nanoc. # Try clonefile if use_clonefile? FileUtils.rm_f(to) begin res = Clonefile.always(from, to) return if res rescue Clonefile::UnsupportedPlatform, Errno::ENOTSUP, Errno::EXDEV, Errno::EINVAL end end # Fall back to old-school copy if File.file?(to) FileUtils.rm_f(to) end FileUtils.cp(from, to) end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/binary_content.rb000066400000000000000000000002711472033334600230700ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core class BinaryContent < Content contract C::None => C::Bool def binary? true end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/changes_stream.rb000066400000000000000000000015741472033334600230440ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core 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(&)) end def to_enum @enum end def each(&) @enum.each(&) nil end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/checksum_collection.rb000066400000000000000000000015351472033334600240730ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core class ChecksumCollection include Nanoc::Core::ContractsSupport c_obj = C::Or[Nanoc::Core::Item, Nanoc::Core::Layout, Nanoc::Core::Configuration, Nanoc::Core::CodeSnippet] def initialize(checksums) @checksums = checksums @_attribute_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) @_attribute_checksums[obj] ||= @checksums[[obj.reference, :each_attribute]] end def to_h @checksums end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/checksum_store.rb000066400000000000000000000043531472033334600230750ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core # 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::Core::Store include Nanoc::Core::ContractsSupport attr_writer :checksums attr_accessor :objects c_obj = C::Or[Nanoc::Core::Item, Nanoc::Core::Layout, Nanoc::Core::Configuration, Nanoc::Core::CodeSnippet] contract C::KeywordArgs[config: Nanoc::Core::Configuration, objects: C::IterOf[c_obj]] => C::Any def initialize(config:, objects:) super(Nanoc::Core::Store.tmp_path_for(config:, store_name: 'checksums'), 3) @objects = objects @checksums = {} invalidate_memoization 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::Core::Document) @checksums[[obj.reference, :content]] = Nanoc::Core::Checksummer.calc_for_content_of(obj) end if obj.is_a?(Nanoc::Core::Document) || obj.is_a?(Nanoc::Core::Configuration) @checksums[[obj.reference, :each_attribute]] = Nanoc::Core::Checksummer.calc_for_each_attribute_of(obj) end @checksums[obj.reference] = Nanoc::Core::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) @_attribute_checksums[obj] ||= @checksums[[obj.reference, :each_attribute]] end protected def data @checksums end def data=(new_data) invalidate_memoization references = Set.new(@objects.map(&:reference)) @checksums = {} new_data.each_pair do |key, checksum| if references.include?(key) || (key.respond_to?(:first) && references.include?(key.first)) @checksums[key] = checksum end end end private def invalidate_memoization @_attribute_checksums = {} end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/checksummer.rb000066400000000000000000000211401472033334600223560ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core # 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. 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::Core::Checksummer.calc(obj.content) end def calc_for_each_attribute_of(obj, digest_class = CompactDigest) obj.attributes.transform_values do |value| Nanoc::Core::Checksummer.calc(value, digest_class) end end def define_behavior(klass, behavior) behaviors[klass] = behavior end private def update(obj, digest, visited = {}) num = visited[obj] if num # If there already is an entry for this object, refer to it by its number. digest.update("@#{num}") else # This object isn’t known yet. Assign it a new number. num = visited.length visited[obj] = num digest.update(obj.class.to_s) digest.update("##{num}<") behavior_for(obj).update(obj, digest) { |o| update(o, digest, visited) } digest.update('>') end end def behaviors return @behaviors if @behaviors @behaviors = {} # NOTE: Other behaviors are registered elsewhere # (search for `define_behavior`). define_behavior(Array, CollectionUpdateBehavior) define_behavior(Set, SetUpdateBehavior) define_behavior(FalseClass, NoUpdateBehavior) define_behavior(Hash, HashUpdateBehavior) define_behavior(NilClass, NoUpdateBehavior) define_behavior(Numeric, RawUpdateBehavior) define_behavior(Pathname, PathnameUpdateBehavior) define_behavior(String, RawUpdateBehavior) define_behavior(Symbol, RawUpdateBehavior) define_behavior(Time, ToIToSUpdateBehavior) define_behavior(TrueClass, NoUpdateBehavior) define_behavior(Nanoc::Core::BinaryContent, BinaryContentUpdateBehavior) define_behavior(Nanoc::Core::Configuration, HashUpdateBehavior) define_behavior(Nanoc::Core::Context, ContextUpdateBehavior) define_behavior(Nanoc::Core::CodeSnippet, DataUpdateBehavior) define_behavior(Nanoc::Core::IdentifiableCollection, CollectionUpdateBehavior) define_behavior(Nanoc::Core::Identifier, ToSUpdateBehavior) define_behavior(Nanoc::Core::Item, DocumentUpdateBehavior) define_behavior(Nanoc::Core::ItemRep, ItemRepUpdateBehavior) define_behavior(Nanoc::Core::Layout, DocumentUpdateBehavior) define_behavior(Nanoc::Core::TextualContent, StringUpdateBehavior) define_behavior(Nanoc::Core::View, UnwrapUpdateBehavior) @behaviors end def behavior_for_class(klass) behaviors.fetch(klass) do if Object.equal?(klass.superclass) RescueUpdateBehavior else behavior_for_class(klass.superclass) end end end def behavior_for(obj) behavior_for_class(obj.class) 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 CollectionUpdateBehavior < UpdateBehavior def self.update(obj, digest) obj.each do |el| yield(el) digest.update(',') end end end class SetUpdateBehavior < CollectionUpdateBehavior def self.update(obj, digest) # Similar to CollectionUpdateBehavior, but sorted for consistency. super(obj.sort { |a, b| (a <=> b) || 0 }, digest) 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) # rubocop:disable Style/ClassEqualityComparison # This Rubocop rule is disabled because the class # itself might not be loaded (yet). if obj.class.to_s == 'Sass::Importers::Filesystem' digest.update('root=') digest.update(obj.root) return end # rubocop:enable Style/ClassEqualityComparison data = begin Marshal.dump(obj) rescue obj.inspect end digest.update(data) end end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/code_snippet.rb000066400000000000000000000031641472033334600225320ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core # Nanoc::Core::CodeSnippet represent a piece of custom code of a Nanoc site. # # @api private class CodeSnippet include Nanoc::Core::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 # rubocop:disable Security/Eval eval(<<~CODE, TOPLEVEL_BINDING) unless respond_to?(:use_helper) def self.use_helper(mod) Nanoc::Core::Context.instance_eval { include mod } end end CODE eval(@data, TOPLEVEL_BINDING, @filename) # rubocop:enable Security/Eval 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 end nanoc-4.13.3/nanoc-core/lib/nanoc/core/compilation_context.rb000066400000000000000000000037641472033334600241460ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core class CompilationContext class FilterNameAndArgs include Nanoc::Core::ContractsSupport attr_reader :name attr_reader :args contract C::KeywordArgs[name: C::Maybe[Symbol], args: Hash] => C::Any def initialize(name:, args:) @name = name @args = args end end class UndefinedFilterForLayoutError < ::Nanoc::Core::Error def initialize(layout) super("There is no filter defined for the layout #{layout.identifier}") end end include Nanoc::Core::ContractsSupport attr_reader :site attr_reader :reps attr_reader :compiled_content_cache attr_reader :compiled_content_store C_COMPILED_CONTENT_CACHE = C::Or[ Nanoc::Core::CompiledContentCache, Nanoc::Core::TextualCompiledContentCache, Nanoc::Core::BinaryCompiledContentCache, ] contract C::KeywordArgs[ action_provider: Nanoc::Core::ActionProvider, reps: Nanoc::Core::ItemRepRepo, site: Nanoc::Core::Site, compiled_content_cache: C_COMPILED_CONTENT_CACHE, compiled_content_store: Nanoc::Core::CompiledContentStore, ] => C::Any def initialize(action_provider:, reps:, site:, compiled_content_cache:, compiled_content_store:) @action_provider = action_provider @reps = reps @site = site @compiled_content_cache = compiled_content_cache @compiled_content_store = compiled_content_store end contract Nanoc::Core::Layout => FilterNameAndArgs 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::Core::ProcessingActions::Filter) raise UndefinedFilterForLayoutError.new(layout) end FilterNameAndArgs.new(name: seq[0].filter_name, args: seq[0].params) end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/compilation_item_rep_collection_view.rb000066400000000000000000000004031472033334600275160ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core class CompilationItemRepCollectionView < ::Nanoc::Core::BasicItemRepCollectionView # @api private def view_class Nanoc::Core::CompilationItemRepView end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/compilation_item_rep_view.rb000066400000000000000000000037061472033334600253140ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core class CompilationItemRepView < ::Nanoc::Core::BasicItemRepView # How long to wait before the requested file appears. # # This is a bit of a hack -- ideally, Nanoc would know that the file is # being generated, and wait the appropriate amount of time. FILE_APPEAR_TIMEOUT = 10.0 # @abstract def item_view_class Nanoc::Core::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:) unless @item_rep.compiled? Fiber.yield(Nanoc::Core::Errors::UnmetDependency.new(@item_rep, snapshot)) end # Wait for file to exist if res start = Time.now sleep 0.05 until File.file?(res) || Time.now - start > FILE_APPEAR_TIMEOUT raise Nanoc::Core::Errors::InternalInconsistency, "File raw_path did not appear in time (#{FILE_APPEAR_TIMEOUT}s): #{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.compiled_content_store.compiled_content(rep: _unwrap, snapshot:) end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/compilation_item_view.rb000066400000000000000000000034251472033334600244440ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core class CompilationItemView < ::Nanoc::Core::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:) 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:) end # Returns the representations of this item. # # @return [Nanoc::Core::BasicItemRepCollectionView] def reps Nanoc::Core::CompilationItemRepCollectionView.new(@context.reps[_unwrap], @context) end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/compilation_phases/000077500000000000000000000000001472033334600234065ustar00rootroot00000000000000nanoc-4.13.3/nanoc-core/lib/nanoc/core/compilation_phases/abstract.rb000066400000000000000000000021231472033334600255340ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core module CompilationPhases class Abstract include Nanoc::Core::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:) do notify(:phase_yielded, rep) @wrapped.call(rep, is_outdated:) notify(:phase_resumed, rep) end notify(:phase_ended, rep) rescue notify(:phase_aborted, rep) raise end contract Nanoc::Core::ItemRep, C::KeywordArgs[is_outdated: C::Bool], C::Func[C::None => C::Any] => C::Any def run(_rep, is_outdated:) raise NotImplementedError end private def notify(sym, rep) name = self.class.to_s.sub(/^.*::/, '') Nanoc::Core::NotificationCenter.post(sym, name, rep) end end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/compilation_phases/cache.rb000066400000000000000000000034301472033334600247760ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core module CompilationPhases # 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::Core::ContractsSupport def initialize(wrapped:, compiled_content_cache:, compiled_content_store:) super(wrapped:) @compiled_content_cache = compiled_content_cache @compiled_content_store = compiled_content_store end contract Nanoc::Core::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:) # If cached content can be used for this item rep, do so, and skip # recalculation of the item rep compiled content. Nanoc::Core::NotificationCenter.post(:cached_content_used, rep) @compiled_content_store.set_all(rep, @compiled_content_cache[rep]) else # Cached content couldn’t be used for this rep. Continue as usual with # recalculation of the item rep compiled content. yield # Update compiled content cache, now that the item rep is compiled. @compiled_content_cache[rep] = @compiled_content_store.get_all(rep) end rep.compiled = true end contract Nanoc::Core::ItemRep, C::KeywordArgs[is_outdated: C::Bool] => C::Bool def can_reuse_content_for_rep?(rep, is_outdated:) if is_outdated false else @compiled_content_cache.full_cache_available?(rep) end end end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/compilation_phases/mark_done.rb000066400000000000000000000011321472033334600256670ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core module CompilationPhases class MarkDone < Abstract include Nanoc::Core::ContractsSupport def initialize(wrapped:, outdatedness_store:) super(wrapped:) @outdatedness_store = outdatedness_store end contract Nanoc::Core::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 end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/compilation_phases/notify.rb000066400000000000000000000011721472033334600252440ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core module CompilationPhases # Provides functionality for notifying start and end of compilation. class Notify < Abstract include Nanoc::Core::ContractsSupport contract Nanoc::Core::ItemRep, C::KeywordArgs[is_outdated: C::Bool], C::Func[C::None => C::Any] => C::Any def run(rep, is_outdated:) # rubocop:disable Lint/UnusedMethodArgument Nanoc::Core::NotificationCenter.post(:compilation_started, rep) yield Nanoc::Core::NotificationCenter.post(:compilation_ended, rep) end end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/compilation_phases/recalculate.rb000066400000000000000000000034731472033334600262260ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core module CompilationPhases # Provides functionality for (re)calculating the content of an item rep, without caching or # outdatedness checking. class Recalculate < Abstract include Nanoc::Core::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::Core::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::Core::DependencyTracker.new(@dependency_store) dependency_tracker.enter(rep.item) executor = Nanoc::Core::Executor.new(rep, @compilation_context, dependency_tracker) @compilation_context.compiled_content_store.set_current(rep, rep.item.content) actions = @action_sequences[rep] actions.each do |action| case action when Nanoc::Core::ProcessingActions::Filter executor.filter(action.filter_name, action.params) when Nanoc::Core::ProcessingActions::Layout executor.layout(action.layout_identifier, action.params) when Nanoc::Core::ProcessingActions::Snapshot action.snapshot_names.each do |snapshot_name| executor.snapshot(snapshot_name) end else raise Nanoc::Core::Errors::InternalInconsistency, "unknown action #{action.inspect}" end end ensure dependency_tracker.exit end end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/compilation_phases/resume.rb000066400000000000000000000027761472033334600252470ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core module CompilationPhases # Provides functionality for suspending and resuming item rep compilation (using fibers). class Resume < Abstract include Nanoc::Core::ContractsSupport DONE = Object.new contract Nanoc::Core::ItemRep, C::KeywordArgs[is_outdated: C::Bool], C::Func[C::None => C::Any] => C::Any def run(rep, is_outdated:, &block) fiber = fiber_for(rep, is_outdated:, &block) while fiber.alive? res = fiber.resume case res when Nanoc::Core::Errors::UnmetDependency Nanoc::Core::NotificationCenter.post(:compilation_suspended, rep, res.rep, res.snapshot_name) raise(res) when Proc fiber.resume(res.call) when DONE # ignore else raise Nanoc::Core::Errors::InternalInconsistency.new( "Fiber yielded object of unexpected type #{res.class}", ) end end end private contract Nanoc::Core::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 end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/compilation_phases/write.rb000066400000000000000000000022761472033334600250740ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core module CompilationPhases class Write < Abstract include Nanoc::Core::ContractsSupport WORKER_POOL_SIZE = 5 def initialize(compiled_content_store:, wrapped:) super(wrapped:) @compiled_content_store = compiled_content_store @pool = Concurrent::FixedThreadPool.new(WORKER_POOL_SIZE) @writer = Nanoc::Core::ItemRepWriter.new end def stop @pool.shutdown @pool.wait_for_termination super end contract Nanoc::Core::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 # Caution: Notification must be posted before enqueueing the rep, # or we risk a race condition where the :rep_write_ended # notification happens before the :rep_write_enqueued one. Nanoc::Core::NotificationCenter.post(:rep_write_enqueued, rep) @pool.post do @writer.write_all(rep, @compiled_content_store) end end end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/compilation_stage.rb000066400000000000000000000011101472033334600235440ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core class CompilationStage def call(*args) notify(:stage_started) res = Nanoc::Core::Instrumentor.call(:stage_ran, self.class) do run(*args) end notify(:stage_ended) res rescue notify(:stage_aborted) raise end def run(*) raise NotImplementedError end private def notify(sym) name = self.class.to_s.sub(/^.*::/, '') Nanoc::Core::NotificationCenter.post(sym, name) end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/compilation_stages/000077500000000000000000000000001472033334600234115ustar00rootroot00000000000000nanoc-4.13.3/nanoc-core/lib/nanoc/core/compilation_stages/build_reps.rb000066400000000000000000000016011472033334600260640ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core module CompilationStages class BuildReps < Nanoc::Core::CompilationStage include Nanoc::Core::ContractsSupport contract C::KeywordArgs[site: Nanoc::Core::Site, action_provider: Nanoc::Core::ActionProvider] => C::Any def initialize(site:, action_provider:) @site = site @action_provider = action_provider end def run reps = Nanoc::Core::ItemRepRepo.new builder = Nanoc::Core::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:, action_sequences:, } end end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/compilation_stages/calculate_checksums.rb000066400000000000000000000024331472033334600277420ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core module CompilationStages class CalculateChecksums < Nanoc::Core::CompilationStage 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::Core::Checksummer.calc_for_content_of(document) checksums[[document.reference, :each_attribute]] = Nanoc::Core::Checksummer.calc_for_each_attribute_of(document) end end [@items, @layouts, @code_snippets].each do |objs| objs.each do |obj| checksums[obj.reference] = Nanoc::Core::Checksummer.calc(obj) end end checksums[@config.reference] = Nanoc::Core::Checksummer.calc(@config) checksums[[@config.reference, :each_attribute]] = Nanoc::Core::Checksummer.calc_for_each_attribute_of(@config) Nanoc::Core::ChecksumCollection.new(checksums) end end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/compilation_stages/cleanup.rb000066400000000000000000000022441472033334600253670ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core module CompilationStages class Cleanup < Nanoc::Core::CompilationStage def initialize(output_dirs) @output_dirs = output_dirs end def run cleanup_temps(Nanoc::Core::Filter::TMP_BINARY_ITEMS_DIR) cleanup_temps(Nanoc::Core::ItemRepWriter::TMP_TEXT_ITEMS_DIR) cleanup_unused_stores cleanup_old_stores end private def cleanup_temps(dir) Nanoc::Core::TempFilenameFactory.instance.cleanup(dir) end def cleanup_unused_stores used_paths = @output_dirs.map { |d| Nanoc::Core::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 end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/compilation_stages/compile_reps.rb000066400000000000000000000072751472033334600264320ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core module CompilationStages class CompileReps < Nanoc::Core::CompilationStage include Nanoc::Core::ContractsSupport include Nanoc::Core::Assertions::Mixin def initialize(reps:, outdatedness_store:, dependency_store:, action_sequences:, compilation_context:, compiled_content_cache:, focus:) @reps = reps @outdatedness_store = outdatedness_store @dependency_store = dependency_store @action_sequences = action_sequences @compilation_context = compilation_context @compiled_content_cache = compiled_content_cache @focus = focus end def run outdated_reps = @reps.select { |r| @outdatedness_store.include?(r) } # If a focus is specified, only compile reps that match this focus. # (If no focus is specified, `@focus` will be `nil`, not an empty array.) if @focus focus_patterns = @focus.map { |f| Nanoc::Core::Pattern.from(f) } # Find reps for which at least one focus pattern matches. outdated_reps = outdated_reps.select do |irep| focus_patterns.any? do |focus_pattern| focus_pattern.match?(irep.item.identifier) end end end selector = Nanoc::Core::ItemRepSelector.new(outdated_reps) run_phase_stack do |phase_stack| selector.each do |rep| handle_errors_while(rep) do compile_rep(rep, phase_stack:, is_outdated: @outdatedness_store.include?(rep)) end end end unless @focus assert Nanoc::Core::Assertions::AllItemRepsHaveCompiledContent.new( compiled_content_cache: @compiled_content_cache, item_reps: @reps, ) end 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::Core::Errors::CompilationError.new(e, item_rep) end def compile_rep(rep, phase_stack:, is_outdated:) phase_stack.call(rep, 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::Core::CompilationPhases::Recalculate.new( action_sequences: @action_sequences, dependency_store: @dependency_store, compilation_context: @compilation_context, ) cache_phase = Nanoc::Core::CompilationPhases::Cache.new( compiled_content_cache: @compiled_content_cache, compiled_content_store: @compilation_context.compiled_content_store, wrapped: recalculate_phase, ) resume_phase = Nanoc::Core::CompilationPhases::Resume.new( wrapped: cache_phase, ) write_phase = Nanoc::Core::CompilationPhases::Write.new( compiled_content_store: @compilation_context.compiled_content_store, wrapped: resume_phase, ) mark_done_phase = Nanoc::Core::CompilationPhases::MarkDone.new( wrapped: write_phase, outdatedness_store: @outdatedness_store, ) Nanoc::Core::CompilationPhases::Notify.new( wrapped: mark_done_phase, ) end end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/compilation_stages/determine_outdatedness.rb000066400000000000000000000025161472033334600305000ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core module CompilationStages class DetermineOutdatedness < Nanoc::Core::CompilationStage include Nanoc::Core::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) if @outdatedness_store.include?(rep) # We determined previously that this rep is outdated. true else !@outdatedness_checker.outdatedness_reasons_for(rep).empty? end end end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/compilation_stages/forget_outdated_dependencies.rb000066400000000000000000000010061472033334600316200ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core module CompilationStages class ForgetOutdatedDependencies < Nanoc::Core::CompilationStage include Nanoc::Core::ContractsSupport def initialize(dependency_store:) @dependency_store = dependency_store end contract C::IterOf[Nanoc::Core::Item] => C::Any def run(outdated_items) outdated_items.each { |i| @dependency_store.forget_dependencies_for(i) } end end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/compilation_stages/load_stores.rb000066400000000000000000000015271472033334600262610ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core module CompilationStages class LoadStores < Nanoc::Core::CompilationStage include Nanoc::Core::ContractsSupport def initialize(checksum_store:, compiled_content_cache:, dependency_store:, action_sequence_store:, outdatedness_store:) @checksum_store = checksum_store @compiled_content_cache = compiled_content_cache @dependency_store = dependency_store @action_sequence_store = action_sequence_store @outdatedness_store = outdatedness_store end contract C::None => C::Any def run @checksum_store.load @compiled_content_cache.load @dependency_store.load @action_sequence_store.load @outdatedness_store.load end end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/compilation_stages/postprocess.rb000066400000000000000000000007521472033334600263260ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core module CompilationStages class Postprocess < Nanoc::Core::CompilationStage include Nanoc::Core::ContractsSupport def initialize(action_provider:, site:) @action_provider = action_provider @site = site end contract Nanoc::Core::Compiler => C::Any def run(compiler) @action_provider.postprocess(@site, compiler) end end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/compilation_stages/preprocess.rb000066400000000000000000000017511472033334600261270ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core module CompilationStages class Preprocess < Nanoc::Core::CompilationStage 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::Core::InMemoryDataSource.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 end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/compilation_stages/prune.rb000066400000000000000000000011421472033334600250650ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core module CompilationStages class Prune < Nanoc::Core::CompilationStage def initialize(config:, reps:) @config = config @reps = reps end def run if @config[:prune][:auto_prune] Nanoc::Core::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 end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/compilation_stages/store_post_compilation_state.rb000066400000000000000000000006571472033334600317450ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core module CompilationStages class StorePostCompilationState < Nanoc::Core::CompilationStage include Nanoc::Core::ContractsSupport def initialize(dependency_store:) @dependency_store = dependency_store end contract C::None => C::Any def run @dependency_store.store end end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/compilation_stages/store_pre_compilation_state.rb000066400000000000000000000017141472033334600315410ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core module CompilationStages class StorePreCompilationState < Nanoc::Core::CompilationStage include Nanoc::Core::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::Core::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 end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/compiled_content_cache.rb000066400000000000000000000047341472033334600245330ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core # Represents a cache than can be used to store already compiled content, # to prevent it from being needlessly recompiled. # # @api private class CompiledContentCache < ::Nanoc::Core::Store include Nanoc::Core::ContractsSupport contract C::KeywordArgs[config: Nanoc::Core::Configuration] => C::Any def initialize(config:) @textual_cache = Nanoc::Core::TextualCompiledContentCache.new(config:) @binary_cache = Nanoc::Core::BinaryCompiledContentCache.new(config:) @wrapped_caches = [@textual_cache, @binary_cache] end contract Nanoc::Core::ItemRep => C::Maybe[C::HashOf[Symbol => Nanoc::Core::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) textual_content_map = @textual_cache[rep] binary_content_map = @binary_cache[rep] # If either the textual or the binary content cache is nil, assume the # cache is entirely absent. # # This is necessary to support the case where only textual content is # cached (which was the case in older versions of Nanoc). return nil if [textual_content_map, binary_content_map].any?(&:nil?) textual_content_map.merge(binary_content_map) end contract Nanoc::Core::ItemRep, C::HashOf[Symbol => Nanoc::Core::Content] => C::Any # 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) @textual_cache[rep] = content.select { |_key, c| c.textual? } @binary_cache[rep] = content.select { |_key, c| c.binary? } end def prune(items:) @wrapped_caches.each { |w| w.prune(items:) } end # True if there is cached compiled content available for this item, and # all entries are present (either textual or binary). def full_cache_available?(rep) @textual_cache.include?(rep) && @binary_cache.include?(rep) end def load(*args) @wrapped_caches.each { |w| w.load(*args) } end def store(*args) @wrapped_caches.each { |w| w.store(*args) } end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/compiled_content_store.rb000066400000000000000000000047631472033334600246260ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core # @api private class CompiledContentStore include Nanoc::Core::ContractsSupport def initialize @contents = Hash.new { |hash, rep| hash[rep] = {} } @current_content = {} end contract Nanoc::Core::ItemRep, Symbol => C::Maybe[Nanoc::Core::Content] def get(rep, snapshot_name) @contents[rep][snapshot_name] end contract Nanoc::Core::ItemRep => C::Maybe[Nanoc::Core::Content] def get_current(rep) @current_content[rep] end contract Nanoc::Core::ItemRep, Symbol, Nanoc::Core::Content => C::Any def set(rep, snapshot_name, contents) @contents[rep][snapshot_name] = contents end contract Nanoc::Core::ItemRep, Nanoc::Core::Content => C::Any def set_current(rep, content) @current_content[rep] = content end contract Nanoc::Core::ItemRep => C::HashOf[Symbol => Nanoc::Core::Content] def get_all(rep) @contents[rep] end contract Nanoc::Core::ItemRep, C::HashOf[Symbol => Nanoc::Core::Content] => C::Any def set_all(rep, contents_per_snapshot) @contents[rep] = contents_per_snapshot end contract C::KeywordArgs[rep: Nanoc::Core::ItemRep, snapshot: C::Optional[C::Maybe[Symbol]]] => Nanoc::Core::Content def raw_compiled_content(rep:, snapshot: nil) # Get name of last pre-layout snapshot has_pre = rep.snapshot_defs.any? { |sd| sd.name == :pre } snapshot_name = snapshot || (has_pre ? :pre : :last) # Check existance of snapshot snapshot_def = rep.snapshot_defs.reverse.find { |sd| sd.name == snapshot_name } unless snapshot_def raise Nanoc::Core::Errors::NoSuchSnapshot.new(rep, snapshot_name) end # Return content if it is available content = get(rep, snapshot_name) return content if content # Content is unavailable; notify and try again Fiber.yield(Nanoc::Core::Errors::UnmetDependency.new(rep, snapshot_name)) get(rep, snapshot_name) end contract C::KeywordArgs[rep: Nanoc::Core::ItemRep, snapshot: C::Optional[C::Maybe[Symbol]]] => String def compiled_content(rep:, snapshot: nil) snapshot_content = raw_compiled_content(rep:, snapshot:) if snapshot_content.binary? raise Nanoc::Core::Errors::CannotGetCompiledContentOfBinaryItem.new(rep) end snapshot_content.string end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/compiler.rb000066400000000000000000000153541472033334600216740ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core class Compiler include Nanoc::Core::ContractsSupport contract Nanoc::Core::Site => C::Any def self.compile(site, focus: nil) new_for(site, focus:).run_until_end end contract Nanoc::Core::Site => Nanoc::Core::Compiler def self.new_for(site, focus: nil) Nanoc::Core::CompilerLoader.new.load(site, focus:) end def initialize(site, compiled_content_cache:, checksum_store:, action_sequence_store:, action_provider:, dependency_store:, outdatedness_store:, focus:) @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 @focus = focus @compiled_content_store = Nanoc::Core::CompiledContentStore.new end def run_until_preprocessed @_run_until_preprocessed ||= begin preprocess_stage.call {} end end def run_until_reps_built @_run_until_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 @_run_until_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:, action_sequences:, reps:, ) outdated_items = determine_outdatedness_stage(outdatedness_checker, reps).call prev.merge( checksums:, dependency_store: @dependency_store, outdatedness_checker:, 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::Core::CompilationContext.new( action_provider: @action_provider, reps:, site: @site, compiled_content_cache: @compiled_content_cache, compiled_content_store: @compiled_content_store, ) end private def create_outdatedness_checker(checksums:, action_sequences:, reps:) Nanoc::Core::OutdatednessChecker.new( site: @site, checksum_store: @checksum_store, dependency_store: @dependency_store, action_sequence_store: @action_sequence_store, action_sequences:, checksums:, reps:, ) end def preprocess_stage @_preprocess_stage ||= ::Nanoc::Core::CompilationStages::Preprocess.new( action_provider: @action_provider, site: @site, dependency_store: @dependency_store, checksum_store: @checksum_store, ) end def build_reps_stage @_build_reps_stage ||= ::Nanoc::Core::CompilationStages::BuildReps.new( site: @site, action_provider: @action_provider, ) end def prune_stage(reps) @_prune_stage ||= ::Nanoc::Core::CompilationStages::Prune.new( config: @site.config, reps:, ) end def load_stores_stage @_load_stores_stage ||= ::Nanoc::Core::CompilationStages::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 ||= ::Nanoc::Core::CompilationStages::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 ||= ::Nanoc::Core::CompilationStages::DetermineOutdatedness.new( reps:, outdatedness_checker:, outdatedness_store: @outdatedness_store, ) end def store_pre_compilation_state_stage(action_sequences, reps) @_store_pre_compilation_state_stage ||= ::Nanoc::Core::CompilationStages::StorePreCompilationState.new( reps:, layouts: @site.layouts, checksum_store: @checksum_store, action_sequence_store: @action_sequence_store, action_sequences:, ) end def compile_reps_stage(action_sequences, reps) @_compile_reps_stage ||= ::Nanoc::Core::CompilationStages::CompileReps.new( reps:, outdatedness_store: @outdatedness_store, dependency_store: @dependency_store, action_sequences:, compilation_context: compilation_context(reps:), compiled_content_cache: @compiled_content_cache, focus: @focus, ) end def store_post_compilation_state_stage @_store_post_compilation_state_stage ||= ::Nanoc::Core::CompilationStages::StorePostCompilationState.new( dependency_store: @dependency_store, ) end def postprocess_stage @_postprocess_stage ||= ::Nanoc::Core::CompilationStages::Postprocess.new( action_provider: @action_provider, site: @site, ) end def cleanup_stage @_cleanup_stage ||= ::Nanoc::Core::CompilationStages::Cleanup.new(@output_dirs) end def forget_outdated_dependencies_stage @_forget_outdated_dependencies_stage ||= ::Nanoc::Core::CompilationStages::ForgetOutdatedDependencies.new( dependency_store: @dependency_store, ) end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/compiler_loader.rb000066400000000000000000000023361472033334600232160ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core # @api private class CompilerLoader def load(site, focus: nil, action_provider: nil) action_sequence_store = Nanoc::Core::ActionSequenceStore.new(config: site.config) dependency_store = Nanoc::Core::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::Core::ChecksumStore.new(config: site.config, objects:) action_provider ||= Nanoc::Core::ActionProvider.named(site.config.action_provider).for(site) outdatedness_store = Nanoc::Core::OutdatednessStore.new(config: site.config) compiled_content_cache = compiled_content_cache_class.new(config: site.config) params = { compiled_content_cache:, checksum_store:, action_sequence_store:, dependency_store:, action_provider:, outdatedness_store:, focus:, } Nanoc::Core::Compiler.new(site, **params) end def compiled_content_cache_class Nanoc::Core::CompiledContentCache end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/config_loader.rb000066400000000000000000000045621472033334600226540ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core # @api private class ConfigLoader class NoConfigFileFoundError < ::Nanoc::Core::Error def initialize super('No configuration file found') end end class NoParentConfigFileFoundError < ::Nanoc::Core::Error def initialize(filename) super("There is no parent configuration file at #{filename}") end end class CyclicalConfigFileError < ::Nanoc::Core::Error def initialize(filename) super("The parent configuration file at #{filename} includes one of its descendants") end end # @return [Boolean] def self.cwd_is_nanoc_site? !config_filename_for_cwd.nil? end # @return [String] def self.config_filename_for_cwd filenames = %w[nanoc.yaml config.yaml] candidate = filenames.find { |f| File.file?(f) } candidate && File.expand_path(candidate) end def new_from_cwd # Determine path filename = self.class.config_filename_for_cwd raise NoConfigFileFoundError if filename.nil? # Read config = apply_parent_config( Nanoc::Core::Configuration.new( hash: load_file(filename), dir: File.dirname(filename), ), [filename], ).with_defaults # Load environment config.with_environment end def load_file(filename) YamlLoader.load_file(filename) 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::Core::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 end nanoc-4.13.3/nanoc-core/lib/nanoc/core/config_view.rb000066400000000000000000000030341472033334600223510ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core class ConfigView < ::Nanoc::Core::View # @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 = Nanoc::Core::UNDEFINED, &) @context.dependency_tracker.bounce(_unwrap, attributes: [key]) @config.fetch(key) do if !Nanoc::Core::UNDEFINED.equal?(fallback) 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(&) @context.dependency_tracker.bounce(_unwrap, attributes: true) @config.each(&) end # @see Configuration#env_name def env_name @context.dependency_tracker.bounce(_unwrap, attributes: true) @config.env_name end # @see Hash#dig def dig(*keys) @context.dependency_tracker.bounce(_unwrap, attributes: keys.take(1)) @config.dig(*keys) end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/configuration-schema.json000066400000000000000000000053321472033334600245300ustar00rootroot00000000000000{ "$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": { "all": { "type": "object", "additionalProperties": false, "properties": { "exclude_files": { "type": "array", "items": { "type": "string" } } } }, "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.13.3/nanoc-core/lib/nanoc/core/configuration.rb000066400000000000000000000140031472033334600227170ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core # Represents the site configuration. class Configuration include Nanoc::Core::ContractsSupport # 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::Core::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 org 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(dir:, hash: {}, 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:).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 = Nanoc::Core::UNDEFINED, &) @wrapped.fetch(key) do if !Nanoc::Core::UNDEFINED.equal?(fallback) 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(&) 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 contract C::None => C::Num def hash [@dir, @env_name].hash end contract C::Any => C::Bool def ==(other) eql?(other) end contract C::Any => C::Bool def eql?(other) other.is_a?(self.class) && @dir == other.dir && @env_name == other.env_name 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 end nanoc-4.13.3/nanoc-core/lib/nanoc/core/content.rb000066400000000000000000000023741472033334600215320ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core class Content include Nanoc::Core::ContractsSupport contract C::Or[self, String, Proc], C::KeywordArgs[binary: C::Optional[C::Bool], filename: C::Optional[C::Maybe[String]]] => self def self.create(content, binary: false, filename: nil) if content.nil? raise ArgumentError, 'Cannot create nil content' elsif content.is_a?(Nanoc::Core::Content) content elsif binary Nanoc::Core::BinaryContent.new(content) else Nanoc::Core::TextualContent.new(content, filename:) end end contract C::None => C::Maybe[String] attr_reader :filename contract C::Maybe[String] => C::Any def initialize(filename) if filename && Pathname.new(filename).relative? raise ArgumentError, "Content filename #{filename} is not absolute" end @filename = filename end contract C::None => self def freeze super @filename.freeze self end contract C::None => C::Bool def binary? raise NotImplementedError end contract C::None => C::Bool def textual? !binary? end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/context.rb000066400000000000000000000041131472033334600215350ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core # Provides a context and a binding for use in filters such as the ERB and # Haml ones. 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::Core::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, &) 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 end nanoc-4.13.3/nanoc-core/lib/nanoc/core/contracts_support.rb000066400000000000000000000100751472033334600236510ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core module ContractsSupport class Ignorer include Singleton def method_missing(*_args) 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 SetOf = 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) warn_about_performance 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 def self.warn_about_performance return if ENV.key?('CI') return if ENV.key?('NANOC_DEV_MODE') puts '-' * 78 puts 'A NOTE ABOUT PERFORMANCE:' puts 'The `contracts` gem is loaded, which enables extra run-time checks, but can drastically reduce Nanoc’s performance. The `contracts` gem is intended for development purposes, and is not recommended for day-to-day Nanoc usage.' puts if defined?(Bundler) puts 'To speed up compilation, remove `contracts` from the Gemfile and run `bundle install`.' else puts 'To speed up compilation, either uninstall the `contracts` gem, or use Bundler (https://bundler.io/) with a Gemfile that doesn’t include `contracts`.' end puts '-' * 78 end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/core_ext/000077500000000000000000000000001472033334600213355ustar00rootroot00000000000000nanoc-4.13.3/nanoc-core/lib/nanoc/core/core_ext/array.rb000066400000000000000000000032341472033334600230020ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core module CoreExt module 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 map do |element| if element.respond_to?(:__nanoc_symbolize_keys_recursively) element.__nanoc_symbolize_keys_recursively else element end end end def __nanoc_stringify_keys_recursively map do |element| if element.respond_to?(:__nanoc_stringify_keys_recursively) element.__nanoc_stringify_keys_recursively else element end end 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 end end end class Array include Nanoc::Core::CoreExt::ArrayExtensions end nanoc-4.13.3/nanoc-core/lib/nanoc/core/core_ext/hash.rb000066400000000000000000000035131472033334600226070ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core module CoreExt module 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 end end end class Hash include Nanoc::Core::CoreExt::HashExtensions end nanoc-4.13.3/nanoc-core/lib/nanoc/core/core_ext/string.rb000066400000000000000000000006601472033334600231720ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core module CoreExt module StringExtensions # Transforms string into an actual identifier # # @return [String] The identifier generated from the receiver def __nanoc_cleaned_identifier "/#{self}/".gsub(/^\/+|\/+$/, '/') end end end end end class String include Nanoc::Core::CoreExt::StringExtensions end nanoc-4.13.3/nanoc-core/lib/nanoc/core/data_source.rb000066400000000000000000000152041472033334600223450ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core # 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::Core::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::Core::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::Core::Content.create(content, binary:) Nanoc::Core::Item.new(content, attributes, identifier, checksum_data:, content_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::Core::Layout.new(raw_content, attributes, identifier, checksum_data:, content_checksum_data:, attributes_checksum_data:) end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/dependency.rb000066400000000000000000000016571472033334600222010ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core # @api private # A dependency between two items/layouts. class Dependency include Nanoc::Core::ContractsSupport C_OBJ_FROM = C::Or[Nanoc::Core::Item, Nanoc::Core::Layout, Nanoc::Core::Configuration, Nanoc::Core::IdentifiableCollection] C_OBJ_TO = Nanoc::Core::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::Core::DependencyProps attr_reader :props contract C::Maybe[C_OBJ_FROM], C::Maybe[C_OBJ_TO], Nanoc::Core::DependencyProps => 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 end nanoc-4.13.3/nanoc-core/lib/nanoc/core/dependency_props.rb000066400000000000000000000111071472033334600234130ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core # @api private class DependencyProps include Nanoc::Core::ContractsSupport attr_reader :attributes attr_reader :raw_content # TODO: Split raw_content for documents and collections C_RAW_CONTENT = C::Or[ C::SetOf[C::Or[String, Regexp]], C::ArrayOf[C::Or[String, Regexp]], C::Bool ] C_ATTR = C::Or[ C::SetOf[ C::Or[ Symbol, # any value [Symbol, C::Any] # pair (specific value) ], ], C::ArrayOf[ C::Or[ Symbol, # any value [Symbol, C::Any] # pair (specific value) ], ], 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] ] contract C_ARGS => 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 Array Set.new(attributes) else attributes end @raw_content = case raw_content when Set raw_content when Array 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' : '_') if @raw_content.is_a?(Set) @raw_content.each do |elem| s << '; raw_content(' s << elem.inspect s << ')' end end if @attributes.is_a?(Set) @attributes.each do |elem| s << '; attr(' s << elem.inspect s << ')' end end 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 Set !@raw_content.empty? else @raw_content end end contract C::None => C::Bool def attributes? case @attributes when Set !@attributes.empty? 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::Core::DependencyProps => Nanoc::Core::DependencyProps def merge(other) DependencyProps.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 def attribute_keys case @attributes when Enumerable @attributes.map { |a| a.is_a?(Array) ? a.first : a } else [] end end contract C::None => Hash def to_h { raw_content:, attributes:, compiled_content: compiled_content?, path: path?, } end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/dependency_store.rb000066400000000000000000000162761472033334600234200ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core # @api private class DependencyStore < ::Nanoc::Core::Store include Nanoc::Core::ContractsSupport C_RAW_CONTENT = C::Or[ C::ArrayOf[C::Or[String, Regexp]], C::Bool ] C_ATTR = C::Or[ C::ArrayOf[Symbol], C::HashOf[Symbol => C::Any], 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] ] C_OBJ_SRC = Nanoc::Core::Item C_OBJ_DST = C::Or[ Nanoc::Core::Item, Nanoc::Core::Layout, Nanoc::Core::Configuration, Nanoc::Core::IdentifiableCollection ] attr_reader :items attr_reader :layouts contract Nanoc::Core::ItemCollection, Nanoc::Core::LayoutCollection, Nanoc::Core::Configuration => C::Any def initialize(items, layouts, config) super(Nanoc::Core::Store.tmp_path_for(config:, store_name: 'dependencies'), 6) @config = config @items = items @layouts = layouts rebuild_refs2objs @new_objects = [] @graph = Nanoc::Core::DirectedGraph.new([nil] + objs2refs(@items) + objs2refs(@layouts)) end def rebuild_refs2objs @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) end contract C_OBJ_SRC => C::ArrayOf[Nanoc::Core::Dependency] def dependencies_causing_outdatedness_of(object) objects_causing_outdatedness_of(object).map do |other_object| props = props_for(other_object, object) Nanoc::Core::Dependency.new( other_object, object, props, ) end end def items=(items) @items = items rebuild_refs2objs end def layouts=(layouts) @layouts = layouts rebuild_refs2objs end def new_items @new_objects.select { |o| o.is_a?(Nanoc::Core::Item) } end def new_layouts @new_objects.select { |o| o.is_a?(Nanoc::Core::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::Core::Item, Nanoc::Core::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 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::Core::Item, Nanoc::Core::Layout] src The source of the dependency, # i.e. the object that will become outdated if dst is outdated # # @param [Nanoc::Core::Item, Nanoc::Core::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) # Convert attributes into key-value pairs, if necessary if attributes.is_a?(Hash) attributes = attributes.to_a end existing_props = @graph.props_for(dst_ref, src_ref) new_props = Nanoc::Core::DependencyProps.new(raw_content:, attributes:, compiled_content:, path:) props = existing_props ? existing_props.merge(new_props) : new_props @graph.add_edge(dst_ref, src_ref, props:) 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::Core::Item, Nanoc::Core::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)) return props if props # This is for backwards compatibility, in case there are no dependency # props available yet. Pretend everything is set to `true`; it’ll be # recompiled and correct props will be available in the next run. # # NOTE: This isn’t covered by tests, yet. (Not trivial to test because # it’s not a condition that can arise in the current Nanoc version.) Nanoc::Core::DependencyProps.new( raw_content: true, attributes: true, compiled_content: true, path: true, ) end def data { edges: @graph.edges.map { |arr| [arr[0], arr[1], arr[2].to_h] }, 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::Core::DirectedGraph.new([nil] + refs) # Load vertices previous_objects = refs2objs(new_data[:vertices]) previous_refs = objs2refs(previous_objects) # 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] props = Nanoc::Core::DependencyProps.new(**props) @graph.add_edge(from, to, props:) end # Record dependency from all items on new items @new_objects = objects - previous_objects end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/dependency_tracker.rb000066400000000000000000000043261472033334600237100ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core # @api private class DependencyTracker include Nanoc::Core::ContractsSupport C_OBJ = C::Or[ Nanoc::Core::Item, Nanoc::Core::Layout, Nanoc::Core::Configuration, Nanoc::Core::IdentifiableCollection ] C_RAW_CONTENT = C::Or[ C::ArrayOf[C::Or[String, Regexp]], C::Bool ] C_ATTR = C::Or[ C::ArrayOf[Symbol], C::HashOf[Symbol => C::Any], 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] ] 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::Core::NotificationCenter.post(:dependency_created, @stack.last, obj) @dependency_store.record_dependency( @stack.last, obj, raw_content:, attributes:, compiled_content:, 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:, attributes:, compiled_content:, path:) exit end class Null < DependencyTracker include Nanoc::Core::ContractsSupport def initialize; end 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 end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/directed_graph.rb000066400000000000000000000126401472033334600230210ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core # 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::Core::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 ) 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 end nanoc-4.13.3/nanoc-core/lib/nanoc/core/document.rb000066400000000000000000000066551472033334600217040ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core class Document include Nanoc::Core::ContractsSupport # @return [Nanoc::Core::Content] attr_reader :content # @return [Nanoc::Core::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::Core::Content] c_attributes = C::Or[Hash, Proc] c_identifier = C::Or[String, Nanoc::Core::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::Core::Content] content # # @param [Hash, Proc] attributes # # @param [String, Nanoc::Core::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::Core::Content.create(content) @attributes = Nanoc::Core::LazyValue.new(attributes).map(&:__nanoc_symbolize_keys_recursively) @identifier = Nanoc::Core::Identifier.from(identifier) @checksum_data = checksum_data @content_checksum_data = content_checksum_data @attributes_checksum_data = attributes_checksum_data # Precalculate for performance @hash = [self.class, identifier].hash reference end # @return [Hash] def attributes @attributes.value 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::Core::Identifier, String] => Nanoc::Core::Identifier def identifier=(new_identifier) @identifier = Nanoc::Core::Identifier.from(new_identifier) end contract Nanoc::Core::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 @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.13.3/nanoc-core/lib/nanoc/core/document_view_mixin.rb000066400000000000000000000037321472033334600241330ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core module DocumentViewMixin # @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, identifier].hash end # @return [Nanoc::Core::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 = Nanoc::Core::UNDEFINED, &) @context.dependency_tracker.bounce(_unwrap, attributes: [key]) if _unwrap.attributes.key?(key) _unwrap.attributes[key] elsif !Nanoc::Core::UNDEFINED.equal?(fallback) 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 end nanoc-4.13.3/nanoc-core/lib/nanoc/core/error.rb000066400000000000000000000002551472033334600212050ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core # Generic error. Superclass for all Nanoc-specific errors. class Error < ::StandardError end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/errors.rb000066400000000000000000000164161472033334600213760ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core module Errors # Error that is raised when the compiled content at a non-existing snapshot # is requested. class NoSuchSnapshot < ::Nanoc::Core::Error # @return [Nanoc::Core::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::Core::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 compilation of an item rep fails. The # underlying error is available by calling `#unwrap`. class CompilationError < ::Nanoc::Core::Error 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 < ::Nanoc::Core::Error # @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 when an rep cannot be compiled because it depends # on other representations. class UnmetDependency < ::Nanoc::Core::Error # @return [Nanoc::Core::ItemRep] The item representation that cannot yet be # compiled attr_reader :rep # @return [Symbol] The name of the snapshot that cannot yet be compiled attr_reader :snapshot_name # @param [Nanoc::Core::ItemRep] rep The item representation that cannot yet be # compiled def initialize(rep, snapshot_name) @rep = rep @snapshot_name = snapshot_name super("The current item cannot be compiled yet because of an unmet dependency on the “#{rep.item.identifier}” item (rep “#{rep.name}”, snapshot “#{snapshot_name}”).") 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 < ::Nanoc::Core::Error def initialize(cycle) 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 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 < ::Nanoc::Core::Error # @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 when the compiled content of a binary item is attempted to be accessed. class CannotGetCompiledContentOfBinaryItem < ::Nanoc::Core::Error # @param [Nanoc::Core::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 attempting to call #parent or #children on an item with a legacy identifier. class CannotGetParentOrChildrenOfNonLegacyItem < ::Nanoc::Core::Error 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 # Error that is raised during site compilation when an item uses a layout # that is not present in the site. class UnknownLayout < ::Nanoc::Core::Error # @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 when a binary item is attempted to be laid out. class CannotLayoutBinaryItem < ::Nanoc::Core::Error # @param [Nanoc::Core::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 < ::Nanoc::Core::Error # @param [Nanoc::Core::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 < ::Nanoc::Core::Error # @param [Nanoc::Core::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 class InternalInconsistency < ::Nanoc::Core::Error end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/executor.rb000066400000000000000000000111451472033334600217120ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core 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(filter_name) begin Nanoc::Core::NotificationCenter.post(:filtering_started, @rep, filter_name) # Run filter last = @compilation_context.compiled_content_store.get_current(@rep) 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::Core::BinaryContent.new(filter.output_filename).tap(&:freeze) else Nanoc::Core::TextualContent.new(result).tap(&:freeze) end # Store @compilation_context.compiled_content_store.set_current(@rep, last) ensure Nanoc::Core::NotificationCenter.post(:filtering_ended, @rep, filter_name) end end def layout(layout_identifier, extra_filter_args = nil) layout = find_layout(layout_identifier) filter_name_and_args = @compilation_context.filter_name_and_args_for_layout(layout) filter_name = filter_name_and_args.name if filter_name.nil? raise Nanoc::Core::Errors::CannotDetermineFilter.new(layout_identifier) end filter_args = filter_name_and_args.args filter_args = filter_args.merge(extra_filter_args || {}) filter_args.freeze # Check whether item can be laid out last = @compilation_context.compiled_content_store.get_current(@rep) raise Nanoc::Core::Errors::CannotLayoutBinaryItem.new(@rep) if last.binary? # Create filter klass = Nanoc::Core::Filter.named!(filter_name) layout_view = Nanoc::Core::LayoutView.new(layout, view_context) filter = klass.new(assigns.merge(layout: layout_view)) # Visit @dependency_tracker.bounce(layout, raw_content: true) begin Nanoc::Core::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::Core::TextualContent.new(res).tap(&:freeze) @compilation_context.compiled_content_store.set_current(@rep, last) ensure Nanoc::Core::NotificationCenter.post(:filtering_ended, @rep, filter_name) end end def snapshot(snapshot_name) last = @compilation_context.compiled_content_store.get_current(@rep) @compilation_context.compiled_content_store.set(@rep, snapshot_name, last) Nanoc::Core::NotificationCenter.post(:snapshot_created, @rep, snapshot_name) end def assigns view_context.assigns_for(@rep, site: @compilation_context.site) end def layouts @compilation_context.site.layouts end def find_layout(arg) req_id = arg.__nanoc_cleaned_identifier layout = layouts.object_with_identifier(req_id) return layout if layout if use_globs? # TODO: use object_matching_glob instead pat = Nanoc::Core::Pattern.from(arg) layout = layouts.find { |l| pat.match?(l.identifier) } return layout if layout end raise Nanoc::Core::Errors::UnknownLayout.new(arg) end def filter_for_filtering(filter_name) klass = Nanoc::Core::Filter.named!(filter_name) last = @compilation_context.compiled_content_store.get_current(@rep) if klass.from_binary? && !last.binary? raise Nanoc::Core::Errors::CannotUseBinaryFilter.new(@rep, klass) elsif !klass.from_binary? && last.binary? raise Nanoc::Core::Errors::CannotUseTextualFilter.new(@rep, klass) end klass.new(assigns) end def use_globs? @compilation_context.site.config[:string_pattern_type] == 'glob' end def view_context @_view_context ||= Nanoc::Core::ViewContextForCompilation.new( reps: @compilation_context.reps, items: @compilation_context.site.items, dependency_tracker: @dependency_tracker, compilation_context: @compilation_context, compiled_content_store: @compilation_context.compiled_content_store, ) end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/feature.rb000066400000000000000000000051051472033334600215060ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core # @example Defining a feature and checking its enabledness # # Nanoc::Core::Feature.define('environments', version: '4.3') # Nanoc::Core::Feature.enabled?(Nanoc::Core::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::Core::Feature module. # # @example Defining Nanoc::Core::Feature::ENVIRONMENTS # # Nanoc::Core::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::Core::VERSION.start_with?(version) end end end end end Nanoc::Core::Feature.define('where', version: '4.13') nanoc-4.13.3/nanoc-core/lib/nanoc/core/filter.rb000066400000000000000000000210141472033334600213350ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core # Nanoc::Core::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::Core::Context extend DDPlugin::Plugin include Nanoc::Core::ContractsSupport # @api private TMP_BINARY_ITEMS_DIR = 'binary_items' class UnknownFilterError < Nanoc::Core::Error include Nanoc::Core::ContractsSupport contract C::Or[String, Symbol] => self def initialize(filter_name) super("The requested filter, “#{filter_name}”, does not exist.") end end class OutputNotWrittenError < Nanoc::Core::Error include Nanoc::Core::ContractsSupport contract C::Or[String, Symbol], String => self 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 FilterReturnedNilError < Nanoc::Core::Error include Nanoc::Core::ContractsSupport contract C::Or[String, Symbol] => self def initialize(filter_name) super("The #{filter_name.inspect} filter returned nil, but is required to return a String.") end end class << self def define(ident, &block) filter_class = Class.new(::Nanoc::Core::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 UnknownFilterError.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::Core::Filter subclasses must implement #run') end def verify(res) if self.class.to_binary? unless File.file?(output_filename) raise Nanoc::Core::Filter::OutputNotWrittenError.new(self.class.identifier, output_filename) end elsif self.class.to_text? unless res raise Nanoc::Core::Filter::FilterReturnedNilError.new(self.class.identifier) end end end contract C::None => String # 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::Core::TempFilenameFactory.instance.create(TMP_BINARY_ITEMS_DIR) end contract C::None => String # 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 contract C::ArrayOf[C::Named['Nanoc::Core::BasicItemView']] => C::Any # 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 end nanoc-4.13.3/nanoc-core/lib/nanoc/core/identifiable_collection.rb000066400000000000000000000056611472033334600247140ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core class IdentifiableCollection prepend MemoWise # include Nanoc::Core::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::Core::Configuration']], C::IterOf[C::RespondTo[:identifier]], C::Maybe[String] => C::Any def initialize_basic(config, objects = [], name = nil) @config = config @objects = Immutable::Vector.new(objects) @name = name end # contract C::None => String def inspect "<#{self.class}>" end # contract C::None => self def freeze @objects.freeze each(&:freeze) build_mapping super 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(&) self.class.new(@config, @objects.reject(&)) end def object_with_identifier(identifier) if frozen? @mapping[identifier.to_s] else find { |i| i.identifier == identifier } end end def object_matching_glob(glob) if frozen? object_matching_glob_memoized(glob) else object_matching_glob_unmemoized(glob) end end protected def object_matching_glob_memoized(glob) object_matching_glob_unmemoized(glob) end memo_wise :object_matching_glob_memoized def object_matching_glob_unmemoized(glob) if use_globs? pat = Pattern.from(glob) find { |i| pat.match?(i.identifier) } end end # contract C::Any => C::IterOf[C::RespondTo[:identifier]] def find_all_unmemoized(arg) pat = Pattern.from(arg) select { |i| pat.match?(i.identifier) } end # contract C::Any => C::IterOf[C::RespondTo[:identifier]] def find_all_memoized(arg) find_all_unmemoized(arg) end memo_wise :find_all_memoized def build_mapping @mapping = {} each do |object| @mapping[object.identifier.to_s] = object end end # contract C::None => C::Bool def use_globs? @config[:string_pattern_type] == 'glob' end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/identifiable_collection_view.rb000066400000000000000000000136641472033334600257500ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core class IdentifiableCollectionView < ::Nanoc::Core::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, &) if NOTHING.equal?(arg) @context.dependency_tracker.bounce(_unwrap, raw_content: true) return @objects.map { |i| view_class.new(i, @context) }.select(&) end prop_attribute = case arg when String, Nanoc::Core::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 # Finds all objects that have the given attribute key/value pair. # # @example # # @items.where(kind: 'article') # @items.where(kind: 'article', year: 2020) # # @return [Enumerable] def where(**hash) unless Nanoc::Core::Feature.enabled?(Nanoc::Core::Feature::WHERE) raise( Nanoc::Core::TrivialError, '#where is experimental, and not yet available unless the corresponding feature flag is turned on. Set the `NANOC_FEATURES` environment variable to `where` to enable its usage. (Alternatively, set the environment variable to `all` to turn on all feature flags.)', ) end @context.dependency_tracker.bounce(_unwrap, attributes: hash) # IDEA: Nanoc could remember (from the previous compilation) how many # times #where is called with a given attribute key, and memoize the # key-to-identifiers list. found_objects = @objects.select do |i| hash.all? { |k, v| i.attributes[k] == v } end found_objects.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) # The argument to #[] fall in two categories: exact matches, and # patterns. An example of an exact match is '/about.md', while an # example of a pattern is '/about.*'. # # If a Nanoc::Core::Identifier is given, we know it will need to be an # exact match. If a String is given, it could be either. If a Regexp is # given, we know it’s a pattern match. # # If we have a pattern match, create a dependency on the item # collection, with a `raw_content` property that contains the pattern. # If we have an exact match, do nothing -- there is no reason to create # a dependency on the item itself, because accessing that item # (attributes, compiled content, …) will create the dependency later. object_from_exact_match = nil object_from_pattern_match = nil case arg when Nanoc::Core::Identifier # Can only be an exact match object_from_exact_match = @objects.object_with_identifier(arg) when String # Can be an exact match, or a pattern match tmp = @objects.object_with_identifier(arg) if tmp object_from_exact_match = tmp else object_from_pattern_match = @objects.object_matching_glob(arg) end when Regexp # Can only be a pattern match object_from_pattern_match = @objects.find { |i| i.identifier.to_s =~ arg } else raise ArgumentError, "Unexpected argument #{arg.class} to []: Can only pass strings, identfiers, and regular expressions" end unless object_from_exact_match # We got this object from a pattern match. Create a dependency with # this pattern, because if the objects matching this pattern change, # then the result of #[] will change too. # # NOTE: object_from_exact_match can also be nil, but in that case # we still need to create a dependency. prop_attribute = case arg when Identifier [arg.to_s] when String, Regexp [arg] end @context.dependency_tracker.bounce(_unwrap, raw_content: prop_attribute) end object = object_from_exact_match || object_from_pattern_match object && view_class.new(object, @context) end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/identifier.rb000066400000000000000000000123521472033334600221770ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core class Identifier include Comparable include Nanoc::Core::ContractsSupport class InvalidIdentifierError < ::Nanoc::Core::Error def initialize(string) super("Invalid identifier (does not start with a slash): #{string.inspect}") end end class InvalidFullIdentifierError < ::Nanoc::Core::Error def initialize(string) super("Invalid full identifier (ends with a slash): #{string.inspect}") end end class InvalidTypeError < ::Nanoc::Core::Error def initialize(type) super("Invalid type for identifier: #{type.inspect} (can be :full or :legacy)") end end class InvalidPrefixError < ::Nanoc::Core::Error def initialize(string) super("Invalid prefix (does not start with a slash): #{string.inspect}") end end class UnsupportedLegacyOperationError < ::Nanoc::Core::Error def initialize super('Cannot use this method on legacy identifiers') end end class NonCoercibleObjectError < ::Nanoc::Core::Error def initialize(obj) super("#{obj.inspect} cannot be converted into a Nanoc::Core::Identifier") end end contract C::Any => self def self.from(obj) case obj when Nanoc::Core::Identifier obj when String Nanoc::Core::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::Core::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, to_s].hash end contract C::Any => C::Maybe[C::Num] def =~(other) Nanoc::Core::Pattern.from(other).match?(to_s) ? 0 : nil end contract C::Any => C::Bool def match?(other) Nanoc::Core::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::Core::Identifier] def prefix(string) unless /\A\//.match?(string) raise InvalidPrefixError.new(string) end Nanoc::Core::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 else @string[0..-extname.size - 1] 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..] end contract C::None => String # The identifier, as string, with all extensions removed def without_exts extname = exts.join('.') if extname.empty? @string else @string[0..-extname.size - 2] 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..] 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 end nanoc-4.13.3/nanoc-core/lib/nanoc/core/in_memory_data_source.rb000066400000000000000000000013611472033334600244220ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core class InMemoryDataSource < Nanoc::Core::DataSource include Nanoc::Core::ContractsSupport attr_reader :items attr_reader :layouts contract Nanoc::Core::ItemCollection, Nanoc::Core::LayoutCollection, C::Maybe[Nanoc::Core::DataSource] => C::Any 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 end nanoc-4.13.3/nanoc-core/lib/nanoc/core/instrumentor.rb000066400000000000000000000013271472033334600226260ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core # @api private class Instrumentor @enabled = false def self.enable if block_given? begin enable yield ensure disable end else @enabled = true end end def self.disable @enabled = false end def self.call(key, *args) return yield unless @enabled begin stopwatch = DDMetrics::Stopwatch.new stopwatch.start yield ensure stopwatch.stop Nanoc::Core::NotificationCenter.post(key, stopwatch.duration, *args) end end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/item.rb000066400000000000000000000005011472033334600210040ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core class Item < ::Nanoc::Core::Document def reference @_reference ||= "item:#{identifier}" end def identifier=(new_identifier) super # Invalidate memoization cache @_reference = nil end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/item_collection.rb000066400000000000000000000004551472033334600232270ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core class ItemCollection < IdentifiableCollection prepend MemoWise def initialize(config, objects = []) initialize_basic(config, objects, 'items') end def reference 'items' end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/item_collection_with_reps_view.rb000066400000000000000000000003721472033334600263430ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core class ItemCollectionWithRepsView < ::Nanoc::Core::IdentifiableCollectionView # @api private def view_class Nanoc::Core::CompilationItemView end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/item_collection_without_reps_view.rb000066400000000000000000000003671472033334600270770ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core class ItemCollectionWithoutRepsView < ::Nanoc::Core::IdentifiableCollectionView # @api private def view_class Nanoc::Core::BasicItemView end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/item_rep.rb000066400000000000000000000060011472033334600216530ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core class ItemRep include Nanoc::Core::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::Core::Item attr_reader :item contract C::None => Symbol attr_reader :name contract C::None => C::IterOf[C::Named['Nanoc::Core::SnapshotDef']] attr_accessor :snapshot_defs contract C::None => C::Bool attr_accessor :modified alias modified? modified contract Nanoc::Core::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 # Precalculate for performance @hash = [self.class, item, name].hash 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 contract C::None => C::Num def hash @hash end contract C::Any => C::Bool def ==(other) eql?(other) end contract C::Any => C::Bool def eql?(other) other.is_a?(self.class) && item == other.item && name == other.name 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 end nanoc-4.13.3/nanoc-core/lib/nanoc/core/item_rep_builder.rb000066400000000000000000000031141472033334600233630ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core # @api private class ItemRepBuilder include Nanoc::Core::ContractsSupport contract Nanoc::Core::ActionSequence, Nanoc::Core::ItemRep => C::ArrayOf[Nanoc::Core::SnapshotDef] def self.snapshot_defs_for(action_sequence, rep) is_binary = rep.item.content.binary? snapshot_defs = [] action_sequence.each do |action| case action when Nanoc::Core::ProcessingActions::Snapshot action.snapshot_names.each do |snapshot_name| snapshot_defs << Nanoc::Core::SnapshotDef.new(snapshot_name, binary: is_binary) end when Nanoc::Core::ProcessingActions::Filter is_binary = Nanoc::Core::Filter.named!(action.filter_name).to_binary? end end snapshot_defs end attr_reader :reps contract Nanoc::Core::Site, Nanoc::Core::ActionProvider, Nanoc::Core::ItemRepRepo => C::Any 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::Core::ItemRep.new(item, rep_name) end end action_sequences = Nanoc::Core::ItemRepRouter.new(@reps, @action_provider, @site).run @reps.each do |rep| rep.snapshot_defs = self.class.snapshot_defs_for(action_sequences[rep], rep) end action_sequences end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/item_rep_repo.rb000066400000000000000000000010631472033334600227030ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core # 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(&) @reps.each(&) self end def [](item) @reps_by_item.fetch(item, []) end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/item_rep_router.rb000066400000000000000000000063751472033334600232710ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core # Assigns paths to reps. # # @api private class ItemRepRouter include Nanoc::Core::ContractsSupport class IdenticalRoutesError < ::Nanoc::Core::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::Core::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 contract Nanoc::Core::ItemRepRepo, Nanoc::Core::ActionProvider, Nanoc::Core::Site => C::Any 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::Core::ItemRep, C::IterOf[String], C::IterOf[Symbol], C::HashOf[String => Nanoc::Core::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 end nanoc-4.13.3/nanoc-core/lib/nanoc/core/item_rep_selector.rb000066400000000000000000000066721472033334600235710ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core # Yields item reps to compile. class ItemRepSelector def initialize(reps) @reps = reps end # A priority queue that tracks dependencies and can detect circular # dependencies. class ItemRepPriorityQueue def initialize(reps) # Prio A: most important; prio C: least important. @prio_a = nil @prio_b = reps.dup @prio_c = [] # List of reps that we’ve already seen. Reps from `reps` will end up # in here. Reps can end up in here even *before* they come from # `reps`, when they are part of a dependency. @seen = Set.new # List of reps that have already been completed (yielded followed by # `#mark_ok`). @completed = Set.new # Record (hard) dependencies. Used for detecting cycles. @dependencies = Hash.new { |hash, key| hash[key] = Set.new } end def next # Read prio A @this = @prio_a if @this @prio_a = nil return @this end # Read prio B @this = @prio_b.shift @this = @prio_b.shift while @seen.include?(@this) if @this return @this end # Read prio C @this = @prio_c.pop @this = @prio_c.pop while @completed.include?(@this) if @this return @this end nil end def mark_ok @completed << @this end def mark_failed(dep) record_dependency(dep) # `@this` depends on `dep`, so `dep` has to be compiled first. Thus, # move `@this` into priority C, and `dep` into priority A. # Put `@this` (item rep that needs `dep` to be compiled first) into # priority C (lowest prio). @prio_c.push(@this) unless @prio_c.include?(@this) # Put `dep` (item rep that needs to be compiled first, before # `@this`) into priority A (highest prio). @prio_a = dep # Remember that we’ve prioritised `dep`. This particular element will # come from @prio_b at some point in the future, so we’ll have to skip # it then. @seen << dep end private def record_dependency(dep) @dependencies[@this] << dep find_cycle(@this, [@this]) end def find_cycle(dep, path) @dependencies[dep].each do |dep1| # Check whether this dependency path ends in `@this` again. If it # does, we have a cycle (because we started from `@this`). if dep1 == @this raise Nanoc::Core::Errors::DependencyCycle.new(path) end # Continue checking, starting from `dep1` this time. find_cycle(dep1, [*path, dep1]) end end end def each pq = ItemRepPriorityQueue.new(@reps) loop do rep = pq.next break if rep.nil? begin yield(rep) pq.mark_ok rescue => e actual_error = e.is_a?(Nanoc::Core::Errors::CompilationError) ? e.unwrap : e if actual_error.is_a?(Nanoc::Core::Errors::UnmetDependency) pq.mark_failed(actual_error.rep) else raise(e) end end end end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/item_rep_writer.rb000066400000000000000000000056771472033334600232710ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core class ItemRepWriter include Nanoc::Core::ContractsSupport include Nanoc::Core::Assertions::Mixin TMP_TEXT_ITEMS_DIR = 'text_items' def write_all(item_rep, compiled_content_store) written_paths = Set.new item_rep.snapshot_defs.map(&:name).each do |snapshot_name| write(item_rep, compiled_content_store, snapshot_name, written_paths) end end def write(item_rep, compiled_content_store, snapshot_name, written_paths) item_rep.raw_paths.fetch(snapshot_name, []).each do |raw_path| write_single(item_rep, compiled_content_store, snapshot_name, raw_path, written_paths) end end def write_single(item_rep, compiled_content_store, snapshot_name, raw_path, written_paths) assert Nanoc::Core::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::Core::NotificationCenter.post( :rep_write_started, item_rep, raw_path ) content = compiled_content_store.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) # Notify ready for diff generation if !is_created && is_modified && !content.binary? Nanoc::Core::NotificationCenter.post( :rep_ready_for_diff, raw_path, File.read(raw_path, encoding: 'UTF-8'), content.string ) end # Write if is_modified smart_cp(temp_path, raw_path) end item_rep.modified = is_modified # Notify Nanoc::Core::NotificationCenter.post( :rep_write_ended, item_rep, content.binary?, raw_path, is_created, is_modified ) end def temp_filename Nanoc::Core::TempFilenameFactory.instance.create(TMP_TEXT_ITEMS_DIR) end def smart_cp(from, to) # Try clonefile if defined?(Clonefile) FileUtils.rm_f(to) begin res = Clonefile.always(from, to) return if res rescue Clonefile::UnsupportedPlatform, Errno::ENOTSUP, Errno::EXDEV, Errno::EINVAL end end # Try with hardlink begin FileUtils.ln(from, to, force: true) return rescue Errno::EXDEV, Errno::EACCES end # Fall back to old-school copy FileUtils.cp(from, to) end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/layout.rb000066400000000000000000000005051472033334600213670ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core class Layout < ::Nanoc::Core::Document def reference @_reference ||= "layout:#{identifier}" end def identifier=(new_identifier) super # Invalidate memoization cache @_reference = nil end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/layout_collection.rb000066400000000000000000000004631472033334600236050ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core class LayoutCollection < IdentifiableCollection prepend MemoWise def initialize(config, objects = []) initialize_basic(config, objects, 'layouts') end def reference 'layouts' end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/layout_collection_view.rb000066400000000000000000000003531472033334600246350ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core class LayoutCollectionView < ::Nanoc::Core::IdentifiableCollectionView # @api private def view_class Nanoc::Core::LayoutView end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/layout_view.rb000066400000000000000000000002441472033334600224210ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core class LayoutView < ::Nanoc::Core::View include Nanoc::Core::DocumentViewMixin end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/lazy_value.rb000066400000000000000000000017601472033334600222310ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core # Holds a value that might be generated lazily. class LazyValue include Nanoc::Core::ContractsSupport # Takes a value or a proc to generate the value def initialize(value_or_proc) @value = { raw: value_or_proc } end # Returns 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. def map self.class.new(-> { yield(value) }) end contract C::None => self def freeze super @value.__nanoc_freeze_recursively unless @value[:raw] self end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/mutable_config_view.rb000066400000000000000000000004651472033334600240670ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core class MutableConfigView < Nanoc::Core::ConfigView # Sets the value for the given attribute. # # @param [Symbol] key # # @see Hash#[]= def []=(key, value) @config[key] = value end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/mutable_document_view_mixin.rb000066400000000000000000000034501472033334600256410ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core module MutableDocumentViewMixin # @api private class DisallowedAttributeValueError < Nanoc::Core::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::Core::Content.create(arg) end # Sets the value for the given attribute. # # @param [Symbol] key # # @see Hash#[]= def []=(key, value) if disallowed_value_class?(value.class) raise DisallowedAttributeValueError.new(value) end _unwrap.set_attribute(key, value) end # Sets the identifier to the given argument. # # @param [String, Nanoc::Core::Identifier] arg def identifier=(arg) _unwrap.identifier = Nanoc::Core::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 private def disallowed_value_class?(klass) # NOTE: We’re explicitly disabling Style/MultipleComparison, because # the suggested alternative (Array#include?) carries a measurable # performance penatly. # # rubocop:disable Style/MultipleComparison klass == Nanoc::Core::Item || klass == Nanoc::Core::Layout || klass == Nanoc::Core::CompilationItemView || klass == Nanoc::Core::LayoutView # rubocop:enable Style/MultipleComparison end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/mutable_identifiable_collection_view.rb000066400000000000000000000007311472033334600274500ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core class MutableIdentifiableCollectionView < Nanoc::Core::IdentifiableCollectionView # Deletes every object for which the block evaluates to true. # # @yieldparam [#identifier] object # # @yieldreturn [Boolean] # # @return [self] def delete_if(&) @objects = @objects.reject { |o| yield(view_class.new(o, @context)) } self end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/mutable_item_collection_view.rb000066400000000000000000000022271472033334600257710ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core class MutableItemCollectionView < Nanoc::Core::MutableIdentifiableCollectionView # @api private def view_class Nanoc::Core::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::Core::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::Core::Content.create(content, binary:, filename:) @objects = @objects.add(Nanoc::Core::Item.new(content, attributes, identifier)) self end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/mutable_item_view.rb000066400000000000000000000002671472033334600235600ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core class MutableItemView < Nanoc::Core::BasicItemView include Nanoc::Core::MutableDocumentViewMixin end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/mutable_layout_collection_view.rb000066400000000000000000000014001472033334600263400ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core class MutableLayoutCollectionView < Nanoc::Core::MutableIdentifiableCollectionView # @api private def view_class Nanoc::Core::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::Core::Identifier, String] identifier This layout's identifier. # # @return [self] def create(content, attributes, identifier) @objects = @objects.add(Nanoc::Core::Layout.new(content, attributes, identifier)) self end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/mutable_layout_view.rb000066400000000000000000000002661472033334600241360ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core class MutableLayoutView < Nanoc::Core::LayoutView include Nanoc::Core::MutableDocumentViewMixin end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/notification_center.rb000066400000000000000000000044451472033334600241070ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core # 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. class NotificationCenter DONE = Object.new SYNC = Object.new def initialize @thread = nil # name => observers dictionary @notifications = Hash.new { |hash, name| hash[name] = [] } @queue = Queue.new @sync_queue = Queue.new on(SYNC, self) { @sync_queue << true } end def start @thread ||= Thread.new do # rubocop:disable Naming/MemoizedInstanceVariableName Thread.current.abort_on_exception = true loop do elem = @queue.pop break if DONE.equal?(elem) name = elem[0] args = elem[1] @notifications[name].each do |observer| observer[:block].call(*args) end end end end def stop @queue << DONE @thread.join end def force_stop @queue << DONE end def on(name, id = nil, &block) @notifications[name] << { id:, block: } end def remove(name, id) @notifications[name].reject! { |i| i[:id] == id } end def post(name, *args) @queue << [name, args] self end def sync post(SYNC) @sync_queue.pop end class << self def instance @_instance ||= new.tap(&:start) end def on(name, id = nil, &) instance.on(name, id, &) end def post(name, *args) instance.post(name, *args) end def remove(name, id) instance.remove(name, id) end def reset instance.stop @_instance = nil end def force_reset instance.force_stop @_instance = nil end def sync instance.sync end end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/outdatedness_checker.rb000066400000000000000000000204341472033334600242430ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core # Responsible for determining whether an item or a layout is outdated. # # @api private class OutdatednessChecker include Nanoc::Core::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::Core::OutdatednessReasons C_OBJ = C::Or[Nanoc::Core::Item, Nanoc::Core::ItemRep, Nanoc::Core::Configuration, Nanoc::Core::Layout, Nanoc::Core::ItemCollection] C_ITEM_OR_REP = C::Or[Nanoc::Core::Item, Nanoc::Core::ItemRep] C_ACTION_SEQUENCES = C::HashOf[C_OBJ => Nanoc::Core::ActionSequence] contract C::KeywordArgs[ site: Nanoc::Core::Site, checksum_store: Nanoc::Core::ChecksumStore, checksums: Nanoc::Core::ChecksumCollection, dependency_store: Nanoc::Core::DependencyStore, action_sequence_store: Nanoc::Core::ActionSequenceStore, action_sequences: C_ACTION_SEQUENCES, reps: Nanoc::Core::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 contract C_OBJ => C::IterOf[Reasons::Generic] def outdatedness_reasons_for(obj) basic_reasons = basic_outdatedness_statuses.fetch(obj).reasons if basic_reasons.any? basic_reasons elsif outdated_due_to_dependencies?(obj) [Reasons::DependenciesOutdated] else [] end end private def basic_outdatedness_statuses @_basic_outdatedness_statuses ||= {}.tap do |tmp| collections = [[@site.config], @site.layouts, @site.items, @reps] collections.each do |collection| collection.each do |obj| tmp[obj] = basic.outdatedness_status_for(obj) end end end end contract C::None => BasicOutdatednessChecker def basic @_basic ||= BasicOutdatednessChecker.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 contract C_OBJ, Immutable::Set => C::Bool def outdated_due_to_dependencies?(obj, processed = Immutable::Set.new) # Convert from rep to item if necessary obj = obj.item if obj.is_a?(Nanoc::Core::ItemRep) # Only items can have dependencies return false unless obj.is_a?(Nanoc::Core::Item) # 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::Core::Dependency => C::Bool def dependency_causes_outdatedness?(dependency) case dependency.from when nil true when Nanoc::Core::ItemCollection, Nanoc::Core::LayoutCollection all_objects = dependency.from raw_content_prop_causes_outdatedness?(all_objects, dependency.props.raw_content) || attributes_prop_causes_outdatedness?(all_objects, dependency.props.attributes) else status = basic_outdatedness_statuses.fetch(dependency.from) active = status.props.active & dependency.props.active active.delete(:attributes) if attributes_unaffected?(status, dependency) !active.empty? end end def attributes_unaffected?(status, dependency) reason = status.reasons.find { |r| r.is_a?(Nanoc::Core::OutdatednessReasons::AttributesModified) } reason && dependency.props.attribute_keys.any? && (dependency.props.attribute_keys & reason.attributes).empty? end def raw_content_prop_causes_outdatedness?(objects, raw_content_prop) return false unless raw_content_prop matching_objects = case raw_content_prop when true # If the `raw_content` dependency prop is `true`, then this is a # dependency on all *objects* (items or layouts). objects when Enumerable # If the `raw_content` dependency prop is a collection, then this # is a dependency on specific objects, given by the patterns. raw_content_prop.flat_map { |pat| objects.find_all(pat) } else raise( Nanoc::Core::Errors::InternalInconsistency, "Unexpected type of raw_content: #{raw_content_prop.inspect}", ) end # For all objects matching the `raw_content` dependency prop: # If the object is outdated because it is newly added, # then this dependency causes outdatedness. # # Note that these objects might be modified but *not* newly added, # in which case this dependency will *not* cause outdatedness. # However, when the object is used later (e.g. attributes are # accessed), then another dependency will exist that will cause # outdatedness. matching_objects.any? do |obj| status = basic_outdatedness_statuses.fetch(obj) status.reasons.any? { |r| r == Nanoc::Core::OutdatednessReasons::DocumentAdded } end end def attributes_prop_causes_outdatedness?(objects, attributes_prop) return false unless attributes_prop unless attributes_prop.is_a?(Set) raise( Nanoc::Core::Errors::InternalInconsistency, 'expected attributes_prop to be a Set', ) end pairs = attributes_prop.select { |a| a.is_a?(Array) }.to_h unless pairs.any? raise( Nanoc::Core::Errors::InternalInconsistency, 'expected attributes_prop not to be empty', ) end dep_checksums = pairs.transform_values { |value| Nanoc::Core::Checksummer.calc(value) } objects.any? do |object| # Find old and new attribute checksums for the object old_object_checksums = checksum_store.attributes_checksum_for(object) new_object_checksums = checksums.attributes_checksum_for(object) dep_checksums.any? do |key, dep_value| if old_object_checksums # Get old and new checksum for this particular attribute old_value = old_object_checksums[key] new_value = new_object_checksums[key] # If the old and new checksums are identical, then the attribute # is unchanged and can’t cause outdatedness. next false unless old_value != new_value # We already know that the old value and new value are different. # This attribute will cause outdatedness if either of those # checksums is identical to the one in the prop. old_value == dep_value || new_value == dep_value else # We don’t have the previous checksums, which means this item is # newly added. In this case, we can compare the value in the # dependency with the new checksum. new_value = new_object_checksums[key] new_value == dep_value end end end end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/outdatedness_reasons.rb000066400000000000000000000057071472033334600243170ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core # 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::Core::DependencyProps] attr_reader :props # @param [String] message The descriptive message for this outdatedness # reason def initialize(message, props = Nanoc::Core::DependencyProps.new) # TODO: Replace `DependencyProps` with its own `OutdatednessProps` # type. For `OutdatednessProps`, the only values are true/false; # giving a collection for `raw_content` makes no sense (anymore). @message = message @props = props end end CodeSnippetsModified = Generic.new( 'The code snippets have been modified since the last time the site was compiled.', Nanoc::Core::DependencyProps.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).', Nanoc::Core::DependencyProps.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.', Nanoc::Core::DependencyProps.new(compiled_content: true, path: true), ) DocumentAdded = Generic.new( 'The item or layout is newly added to the site.', Nanoc::Core::DependencyProps.new, # NOTE: empty props, because they’re not relevant ) ContentModified = Generic.new( 'The content of this item has been modified since the last time the site was compiled.', Nanoc::Core::DependencyProps.new(raw_content: true, compiled_content: true), ) 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.', Nanoc::Core::DependencyProps.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.', Nanoc::Core::DependencyProps.new(raw_content: true, attributes: true, compiled_content: true), ) end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/outdatedness_rule.rb000066400000000000000000000026771472033334600236170ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core # @api private class OutdatednessRule include Nanoc::Core::ContractsSupport include Singleton def self.affects_path? @affects_path end def self.affects_attributes? @affects_attributes end def self.affects_compiled_content? @affects_compiled_content end def self.affects_props(*names) @affects_raw_content = false @affects_attributes = false @affects_compiled_content = false @affects_path = false names.each do |name| case name when :raw_content @affects_raw_content = true when :attributes @affects_attributes = true when :compiled_content @affects_compiled_content = true when :path @affects_path = true end end end def self.affects_raw_content? @affects_raw_content end def call(obj, outdatedness_checker) Nanoc::Core::Instrumentor.call(:outdatedness_rule_ran, self.class) do apply(obj, outdatedness_checker) end end def apply(_obj, _outdatedness_checker) raise NotImplementedError.new('Nanoc::Core::OutdatednessRule subclasses must implement #apply') end contract C::None => String def inspect "#{self.class.name}(#{reason})" end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/outdatedness_rules/000077500000000000000000000000001472033334600234415ustar00rootroot00000000000000nanoc-4.13.3/nanoc-core/lib/nanoc/core/outdatedness_rules/attributes_modified.rb000066400000000000000000000031371472033334600300200ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core module OutdatednessRules class AttributesModified < Nanoc::Core::OutdatednessRule include Nanoc::Core::ContractsSupport affects_props :attributes, :compiled_content contract C::Or[Nanoc::Core::ItemRep, Nanoc::Core::Item, Nanoc::Core::Configuration, Nanoc::Core::Layout], C::Named['Nanoc::Core::BasicOutdatednessChecker'] => C::Maybe[Nanoc::Core::OutdatednessReasons::Generic] def apply(obj, basic_outdatedness_checker) case obj when Nanoc::Core::ItemRep apply(obj.item, basic_outdatedness_checker) when Nanoc::Core::Item, Nanoc::Core::Layout, Nanoc::Core::Configuration if basic_outdatedness_checker.checksum_store[obj] == basic_outdatedness_checker.checksums.checksum_for(obj) return nil end old_checksums = basic_outdatedness_checker.checksum_store.attributes_checksum_for(obj) unless old_checksums return Nanoc::Core::OutdatednessReasons::AttributesModified.new(true) end new_checksums = basic_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::Core::OutdatednessReasons::AttributesModified.new(changed_attributes) end else raise ArgumentError end end end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/outdatedness_rules/code_snippets_modified.rb000066400000000000000000000016401472033334600304660ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core module OutdatednessRules class CodeSnippetsModified < Nanoc::Core::OutdatednessRule prepend MemoWise # include Nanoc::Core::ContractsSupport affects_props :raw_content, :attributes, :compiled_content, :path def apply(_obj, basic_outdatedness_checker) if any_snippets_modified?(basic_outdatedness_checker) Nanoc::Core::OutdatednessReasons::CodeSnippetsModified end end private def any_snippets_modified?(basic_outdatedness_checker) basic_outdatedness_checker.site.code_snippets.any? do |cs| ch_old = basic_outdatedness_checker.checksum_store[cs] ch_new = basic_outdatedness_checker.checksums.checksum_for(cs) ch_old != ch_new end end memo_wise :any_snippets_modified? end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/outdatedness_rules/content_modified.rb000066400000000000000000000011651472033334600273030ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core module OutdatednessRules class ContentModified < Nanoc::Core::OutdatednessRule affects_props :raw_content, :compiled_content def apply(obj, basic_outdatedness_checker) obj = obj.item if obj.is_a?(Nanoc::Core::ItemRep) ch_old = basic_outdatedness_checker.checksum_store.content_checksum_for(obj) ch_new = basic_outdatedness_checker.checksums.content_checksum_for(obj) if ch_old != ch_new Nanoc::Core::OutdatednessReasons::ContentModified end end end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/outdatedness_rules/item_added.rb000066400000000000000000000010741472033334600260470ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core module OutdatednessRules class ItemAdded < Nanoc::Core::OutdatednessRule affects_props :raw_content contract Nanoc::Core::ItemRep, C::Named['Nanoc::Core::BasicOutdatednessChecker'] => C::Maybe[Nanoc::Core::OutdatednessReasons::Generic] def apply(obj, basic_outdatedness_checker) if basic_outdatedness_checker.dependency_store.new_items.include?(obj.item) Nanoc::Core::OutdatednessReasons::DocumentAdded end end end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/outdatedness_rules/layout_added.rb000066400000000000000000000010721472033334600264240ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core module OutdatednessRules class LayoutAdded < Nanoc::Core::OutdatednessRule affects_props :raw_content contract Nanoc::Core::Layout, C::Named['Nanoc::Core::BasicOutdatednessChecker'] => C::Maybe[Nanoc::Core::OutdatednessReasons::Generic] def apply(obj, basic_outdatedness_checker) if basic_outdatedness_checker.dependency_store.new_layouts.include?(obj) Nanoc::Core::OutdatednessReasons::DocumentAdded end end end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/outdatedness_rules/not_written.rb000066400000000000000000000021271472033334600263440ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core module OutdatednessRules class NotWritten < Nanoc::Core::OutdatednessRule affects_props :raw_content, :attributes, :compiled_content, :path def apply(obj, basic_outdatedness_checker) if obj.raw_paths.values.flatten.compact.any? { |fn| !exist?(fn, basic_outdatedness_checker) } Nanoc::Core::OutdatednessReasons::NotWritten end end private def exist?(fn, basic_outdatedness_checker) all(basic_outdatedness_checker).include?(fn) end def all(basic_outdatedness_checker) # NOTE: Cached per outdatedness checker, so that unrelated invocations # later on don’t reuse an old cache. @all ||= {} @all[basic_outdatedness_checker] ||= Set.new( Dir.glob("#{site_root(basic_outdatedness_checker)}/**/*", File::FNM_DOTMATCH), ) end def site_root(basic_outdatedness_checker) basic_outdatedness_checker.site.config.output_dir end end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/outdatedness_rules/rules_modified.rb000066400000000000000000000030261472033334600267610ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core module OutdatednessRules class RulesModified < Nanoc::Core::OutdatednessRule affects_props :compiled_content, :path def apply(obj, basic_outdatedness_checker) # Check rules of obj itself if rules_modified?(obj, basic_outdatedness_checker) return Nanoc::Core::OutdatednessReasons::RulesModified end # Check rules of layouts used by obj layouts = layouts_touched_by(obj, basic_outdatedness_checker) if layouts.any? { |layout| rules_modified?(layout, basic_outdatedness_checker) } return Nanoc::Core::OutdatednessReasons::RulesModified end nil end private def rules_modified?(obj, basic_outdatedness_checker) seq_old = basic_outdatedness_checker.action_sequence_store[obj] seq_new = basic_outdatedness_checker.action_sequence_for(obj).serialize !seq_old.eql?(seq_new) end def layouts_touched_by(obj, basic_outdatedness_checker) actions = basic_outdatedness_checker.action_sequence_store[obj] layout_actions = actions.select { |a| a.first == :layout } layouts = basic_outdatedness_checker.site.layouts layout_actions.map do |layout_action| layout_pattern = layout_action[1] layouts.object_with_identifier(layout_pattern) || layouts.object_matching_glob(layout_pattern) end.compact end end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/outdatedness_rules/uses_always_outdated_filter.rb000066400000000000000000000013711472033334600315650ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core module OutdatednessRules class UsesAlwaysOutdatedFilter < Nanoc::Core::OutdatednessRule affects_props :raw_content, :attributes, :path def apply(obj, basic_outdatedness_checker) seq = basic_outdatedness_checker.action_sequence_for(obj) if any_always_outdated?(seq) Nanoc::Core::OutdatednessReasons::UsesAlwaysOutdatedFilter end end def any_always_outdated?(seq) seq .select { |a| a.is_a?(Nanoc::Core::ProcessingActions::Filter) } .map { |a| Nanoc::Core::Filter.named(a.filter_name) } .compact .any?(&:always_outdated?) end end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/outdatedness_status.rb000066400000000000000000000016271472033334600241650ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core # @api private class OutdatednessStatus attr_reader :reasons attr_reader :props def initialize(reasons: [], props: Nanoc::Core::DependencyProps.new) @reasons = reasons @props = props end def useful_to_apply?(rule) return true if rule.affects_raw_content? && !@props.raw_content? return true if rule.affects_attributes? && !@props.attributes? return true if rule.affects_compiled_content? && !@props.compiled_content? return true if rule.affects_path? && !@props.path? false end def update(reason) self.class.new( reasons: @reasons + [reason], props: @props.merge(reason.props), ) end def inspect "<#{self.class} reasons=#{@reasons.inspect} props=#{@props.inspect}>" end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/outdatedness_store.rb000066400000000000000000000022111472033334600237640ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core # @api private class OutdatednessStore < ::Nanoc::Core::Store include Nanoc::Core::ContractsSupport contract C::KeywordArgs[config: Nanoc::Core::Configuration] => C::Any def initialize(config:) super(Nanoc::Core::Store.tmp_path_for(config:, store_name: 'outdatedness'), 2) @outdated_refs = Set.new end contract Nanoc::Core::ItemRep => C::Bool def include?(obj) @outdated_refs.include?(obj.reference) end contract Nanoc::Core::ItemRep => self def add(obj) @outdated_refs << obj.reference self end contract Nanoc::Core::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 end nanoc-4.13.3/nanoc-core/lib/nanoc/core/pattern.rb000066400000000000000000000015261472033334600215330ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core class Pattern include Nanoc::Core::ContractsSupport contract C::Any => self def self.from(obj) case obj when Nanoc::Core::StringPattern, Nanoc::Core::RegexpPattern obj when String Nanoc::Core::StringPattern.new(obj) when Regexp Nanoc::Core::RegexpPattern.new(obj) when Symbol Nanoc::Core::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 end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/post_compile_item_collection_view.rb000066400000000000000000000003731472033334600270350ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core class PostCompileItemCollectionView < Nanoc::Core::IdentifiableCollectionView # @api private def view_class Nanoc::Core::PostCompileItemView end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/post_compile_item_rep_collection_view.rb000066400000000000000000000004011472033334600276730ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core class PostCompileItemRepCollectionView < Nanoc::Core::BasicItemRepCollectionView # @api private def view_class Nanoc::Core::PostCompileItemRepView end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/post_compile_item_rep_view.rb000066400000000000000000000016341472033334600254710ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core class PostCompileItemRepView < ::Nanoc::Core::BasicItemRepView def item_view_class Nanoc::Core::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::Core::Errors::NoSuchSnapshot.new(_unwrap, snapshot_name) end content = snapshot_contents[snapshot_name] if content.binary? raise Nanoc::Core::Errors::CannotGetCompiledContentOfBinaryItem.new(_unwrap) end content.string end def raw_path(snapshot: :last) @item_rep.raw_path(snapshot:) end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/post_compile_item_view.rb000066400000000000000000000006761472033334600246300ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core class PostCompileItemView < Nanoc::Core::CompilationItemView def reps Nanoc::Core::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 end nanoc-4.13.3/nanoc-core/lib/nanoc/core/prefixed_data_source.rb000066400000000000000000000013051472033334600242300ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core class PrefixedDataSource < Nanoc::Core::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 end nanoc-4.13.3/nanoc-core/lib/nanoc/core/processing_action.rb000066400000000000000000000010321472033334600235570ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core 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..].map(&:inspect).join(', '), ) end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/processing_actions.rb000066400000000000000000000003711472033334600237470ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core module ProcessingActions end end end require 'nanoc/core/processing_actions/filter' require 'nanoc/core/processing_actions/layout' require 'nanoc/core/processing_actions/snapshot' nanoc-4.13.3/nanoc-core/lib/nanoc/core/processing_actions/000077500000000000000000000000001472033334600234215ustar00rootroot00000000000000nanoc-4.13.3/nanoc-core/lib/nanoc/core/processing_actions/filter.rb000066400000000000000000000016301472033334600252330ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core module ProcessingActions class Filter < Nanoc::Core::ProcessingAction # TODO: rename params to args # 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::Core::Checksummer.calc(@params)] end def to_s "filter #{@filter_name.inspect}, #{@params.inspect}" end def hash [self.class, filter_name, 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 end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/processing_actions/layout.rb000066400000000000000000000016561472033334600252730ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core module ProcessingActions class Layout < Nanoc::Core::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::Core::Checksummer.calc(@params)] end def to_s "layout #{@layout_identifier.inspect}, #{@params.inspect}" end def hash [self.class, layout_identifier, 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 end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/processing_actions/snapshot.rb000066400000000000000000000025341472033334600256110ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core module ProcessingActions class Snapshot < Nanoc::Core::ProcessingAction # snapshot :before_layout # snapshot :before_layout, path: '/about.md' include Nanoc::Core::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, snapshot_names, 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 end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/pruner.rb000066400000000000000000000065741472033334600214010ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core # Responsible for finding and deleting files in the site’s output directory # that are not managed by Nanoc. class Pruner include Nanoc::Core::ContractsSupport contract Nanoc::Core::Configuration, Nanoc::Core::ItemRepRepo, C::KeywordArgs[dry_run: C::Optional[C::Bool], exclude: C::Optional[C::IterOf[String]]] => C::Any 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..] 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 Nanoc::Core::NotificationCenter.post(:file_listed_for_pruning, thing) else Nanoc::Core::NotificationCenter.post(:file_pruned, thing) yield end end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/regexp_pattern.rb000066400000000000000000000011761472033334600231060ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core class RegexpPattern < Pattern contract Regexp => C::Any def initialize(regexp) @regexp = regexp end contract C::Or[Nanoc::Core::Identifier, String] => C::Bool def match?(identifier) (identifier.to_s =~ @regexp) != nil end contract C::Or[Nanoc::Core::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 end nanoc-4.13.3/nanoc-core/lib/nanoc/core/site.rb000066400000000000000000000035251472033334600210230ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core # @api private class Site # Error that is raised when multiple items or layouts with the same identifier exist. class DuplicateIdentifierError < ::Nanoc::Core::Error def initialize(identifier, type) super("There are multiple #{type}s with the #{identifier} identifier.") end end include Nanoc::Core::ContractsSupport attr_reader :code_snippets attr_reader :config attr_accessor :data_source contract C::KeywordArgs[config: Nanoc::Core::Configuration, code_snippets: C::IterOf[Nanoc::Core::CodeSnippet], data_source: C::Named['Nanoc::Core::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 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::Core::Item, Nanoc::Core::Layout]], String => self def ensure_identifier_uniqueness(objects, type) seen = Set.new objects.each do |obj| if seen.include?(obj.identifier) raise DuplicateIdentifierError.new(obj.identifier, type) end seen << obj.identifier end self end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/site_loader.rb000066400000000000000000000053351472033334600223520ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core class SiteLoader ENCODING_REGEX = /\A#\s+(-\*-\s+)?(en)?coding: (?[^\s]+)(\s+-\*-\s*)?\n{0,2}/ # @return [Boolean] def self.cwd_is_nanoc_site? Nanoc::Core::ConfigLoader.cwd_is_nanoc_site? end def new_from_cwd site_from_config(Nanoc::Core::ConfigLoader.new.new_from_cwd) 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::Core::PrefixedDataSource.new(ds, ds.items_root, ds.layouts_root) end end Nanoc::Core::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::Core::Site.new( config:, code_snippets:, data_source:, ) end def with_data_sources(config, &) 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::Core::DataSource.named(data_source_hash[:type].to_sym) if data_source_class.nil? raise Nanoc::Core::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::Core::CodeSnippet.new( read_code_snippet_contents(filename), filename, ) end end end 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 end nanoc-4.13.3/nanoc-core/lib/nanoc/core/snapshot_def.rb000066400000000000000000000006311472033334600225270ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core class SnapshotDef include Nanoc::Core::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.13.3/nanoc-core/lib/nanoc/core/store.rb000066400000000000000000000130101472033334600212010ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core # 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. # # @api private class Store include Nanoc::Core::ContractsSupport # Logic for building tmp path from active environment and store name # @api private contract C::KeywordArgs[config: Nanoc::Core::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 contract String => String def self.tmp_path_prefix(output_dir) dir = Digest::SHA1.hexdigest(output_dir)[0..12] File.join('tmp', 'nanoc', dir) end # @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 # @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::Core::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::Core::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 Nanoc::Core::Instrumentor.call(:store_loaded, self.class) do load_uninstrumented 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 # NOTE: Yes, the “store stored” name is a little silly. Maybe stores # need to be renamed to databases or so. Nanoc::Core::Instrumentor.call(:store_stored, self.class) do store_uninstrumented end end private def load_uninstrumented unsafe_load_uninstrumented rescue # An error occurred! Remove the database and try again FileUtils.rm_f(version_filename) FileUtils.rm_f(data_filename) # Try again unsafe_load_uninstrumented end def store_uninstrumented FileUtils.mkdir_p(File.dirname(filename)) write_obj_to_file(version_filename, version) write_obj_to_file(data_filename, data) # Remove old file (back from the PStore days), if there are any. FileUtils.rm_f(filename) end # Unsafe, because it can throw exceptions. def unsafe_load_uninstrumented # If there is no database, no point in loading anything return unless File.file?(version_filename) # Check if store version is the expected version. If it is not, don’t # load. read_version = read_obj_from_file(version_filename) return if read_version != version # Load data self.data = read_obj_from_file(data_filename) end def write_obj_to_file(filename, obj) data = Marshal.dump(obj) compressed_data = Zlib::Deflate.deflate(data, Zlib::BEST_SPEED) write_data_to_file(filename, compressed_data) end def read_obj_from_file(fn) compressed_data = File.binread(fn) data = Zlib::Inflate.inflate(compressed_data) Marshal.load(data) end def version_filename "#{filename}.version.db" end def data_filename "#{filename}.data.db" end def write_data_to_file(filename, data) basename = File.basename(filename) dirname = File.dirname(filename) # Write to a temporary file first, and then (atomically) move it into # place. Tempfile.open(".#{basename}", dirname) do |temp_file| temp_file.binmode # Write the data as a stream, because File.binwrite can’t # necessarily deal with writing that much data all at once. # # See https://github.com/nanoc/nanoc/issues/1635. reader = StringIO.new(data) IO.copy_stream(reader, temp_file) temp_file.close # Rename (atomic) File.rename(temp_file.path, filename) end end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/string_pattern.rb000066400000000000000000000011541472033334600231160ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core 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::Core::Identifier, String] => C::Bool def match?(identifier) File.fnmatch(@string, identifier.to_s, MATCH_OPTS) end contract C::Or[Nanoc::Core::Identifier, String] => nil def captures(_identifier) nil end contract C::None => String def to_s @string end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/temp_filename_factory.rb000066400000000000000000000025601472033334600244110ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core class TempFilenameFactory # @return [String] The root directory for all temporary filenames attr_reader :root_dir # @return [Nanoc::Core::TempFilenameFactory] A common instance def self.instance @_instance ||= new end def initialize @counts = {} @root_dir = Dir.mktmpdir('nanoc') @mutex = Mutex.new 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 = nil @mutex.synchronize do count = @counts.fetch(prefix, 0) @counts[prefix] = count + 1 end 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) FileUtils.rm_rf(path) @counts.delete(prefix) if @counts.empty? && File.directory?(@root_dir) FileUtils.rm_rf(@root_dir) end end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/textual_compiled_content_cache.rb000066400000000000000000000050371472033334600262760ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core # Represents a cache than can be used to store already compiled content, # to prevent it from being needlessly recompiled. # # @api private class TextualCompiledContentCache < ::Nanoc::Core::Store include Nanoc::Core::ContractsSupport contract C::KeywordArgs[config: Nanoc::Core::Configuration] => C::Any def initialize(config:) super(Nanoc::Core::Store.tmp_path_for(config:, store_name: 'compiled_content'), 4) @cache = {} end contract Nanoc::Core::ItemRep => C::Maybe[C::HashOf[Symbol => Nanoc::Core::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::Core::ItemRep => C::Bool def include?(rep) item_cache = @cache[rep.item.identifier] || {} item_cache.key?(rep.name) end contract Nanoc::Core::ItemRep, C::HashOf[Symbol => Nanoc::Core::Content] => C::Any # 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) # FIXME: once the binary content cache is properly enabled (no longer # behind a feature flag), change contract to be TextualContent, rather # than Content. @cache[rep.item.identifier] ||= {} @cache[rep.item.identifier][rep.name] = content end def prune(items:) item_identifiers = Set.new(items.map(&:identifier)) @cache.each_key do |key| # TODO: remove unused item reps next if item_identifiers.include?(key) @cache.delete(key) end end # True if there is cached compiled content available for this item, and # all entries are textual. def full_cache_available?(rep) cache = self[rep] cache ? cache.none? { |_snapshot_name, content| content.binary? } : false 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 end nanoc-4.13.3/nanoc-core/lib/nanoc/core/textual_content.rb000066400000000000000000000015361472033334600232770ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core class TextualContent < Content contract C::Or[String, Proc], C::KeywordArgs[filename: C::Optional[C::Maybe[String]]] => C::Any def initialize(string, filename: nil) super(filename) @string = Nanoc::Core::LazyValue.new(string) end contract C::None => String def string @string.value 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::Core::LazyValue.new(array[1]) end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/trivial_error.rb000066400000000000000000000004321472033334600227340ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core # 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 TrivialError < ::Nanoc::Core::Error end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/utils.rb000066400000000000000000000020341472033334600212110ustar00rootroot00000000000000# frozen_string_literal: true # .sub(/^[A-Z]:/,'') module Nanoc module Core # Utilities that don’t fit anywhere else. # # @api private module Utils # Same as File.expand_path, but does not add a drive identifier on # Windows. This is necessary in case the path is a Nanoc path, rather than # a filesystem path. def self.expand_path_without_drive_identifier(file_name, dir_string) res = File.expand_path(file_name, dir_string) if windows_fs? # On Windows, strip the drive identifier, e.g. `C:`. res = res.sub(/^[A-Z]:/, '') end res end # Returns `true` if absolute file paths start with a drive identifier, like `C:`. def self.windows_fs? # NOTE: This isn’t memoized with ||= because @_windows_fs is a boolean. return @_windows_fs if defined?(@_windows_fs) absolute_path = File.expand_path('/a') @_windows_fs = absolute_path.start_with?(/^[A-Z]:/) @_windows_fs end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/version.rb000066400000000000000000000001331472033334600215340ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core VERSION = '4.13.3' end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/view.rb000066400000000000000000000015021472033334600210220ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core class View include Nanoc::Core::ContractsSupport # @api private # TODO: disallow nil contract C::Maybe[C::Or[ Nanoc::Core::ViewContextForCompilation, Nanoc::Core::ViewContextForPreCompilation, Nanoc::Core::ViewContextForShell ]] => C::Any 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 end nanoc-4.13.3/nanoc-core/lib/nanoc/core/view_context_for_compilation.rb000066400000000000000000000034661472033334600260450ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core class ViewContextForCompilation include Nanoc::Core::ContractsSupport attr_reader :reps attr_reader :items attr_reader :dependency_tracker attr_reader :compilation_context attr_reader :compiled_content_store contract C::KeywordArgs[ reps: Nanoc::Core::ItemRepRepo, items: Nanoc::Core::IdentifiableCollection, dependency_tracker: Nanoc::Core::DependencyTracker, compilation_context: Nanoc::Core::CompilationContext, compiled_content_store: Nanoc::Core::CompiledContentStore, ] => C::Any def initialize(reps:, items:, dependency_tracker:, compilation_context:, compiled_content_store:) @reps = reps @items = items @dependency_tracker = dependency_tracker @compilation_context = compilation_context @compiled_content_store = compiled_content_store end contract Nanoc::Core::ItemRep, C::KeywordArgs[site: Nanoc::Core::Site] => Hash def assigns_for(rep, site:) last_content = @compiled_content_store.get_current(rep) content_or_filename_assigns = if last_content.binary? { filename: last_content.filename } else { content: last_content.string } end content_or_filename_assigns.merge( item: Nanoc::Core::CompilationItemView.new(rep.item, self), rep: Nanoc::Core::CompilationItemRepView.new(rep, self), item_rep: Nanoc::Core::CompilationItemRepView.new(rep, self), items: Nanoc::Core::ItemCollectionWithRepsView.new(site.items, self), layouts: Nanoc::Core::LayoutCollectionView.new(site.layouts, self), config: Nanoc::Core::ConfigView.new(site.config, self), ) end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/view_context_for_pre_compilation.rb000066400000000000000000000006721472033334600267070ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core class ViewContextForPreCompilation include Nanoc::Core::ContractsSupport attr_reader :items attr_reader :dependency_tracker contract C::KeywordArgs[items: Nanoc::Core::IdentifiableCollection] => C::Any def initialize(items:) @items = items @dependency_tracker = Nanoc::Core::DependencyTracker::Null.new end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/view_context_for_shell.rb000066400000000000000000000010361472033334600246250ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core class ViewContextForShell include Nanoc::Core::ContractsSupport attr_reader :items attr_reader :reps attr_reader :dependency_tracker contract C::KeywordArgs[ items: Nanoc::Core::IdentifiableCollection, reps: Nanoc::Core::ItemRepRepo, ] => C::Any def initialize(items:, reps:) @items = items @reps = reps @dependency_tracker = Nanoc::Core::DependencyTracker::Null.new end end end end nanoc-4.13.3/nanoc-core/lib/nanoc/core/yaml_loader.rb000066400000000000000000000007001472033334600223370ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core # @api private module YamlLoader OPTIONS = { permitted_classes: [Symbol, Date, Time].freeze, aliases: true, }.freeze private_constant :OPTIONS def self.load(yaml_string) YAML.safe_load(yaml_string, **OPTIONS) end def self.load_file(filename) YAML.safe_load_file(filename, **OPTIONS) end end end end nanoc-4.13.3/nanoc-core/nanoc-core.gemspec000066400000000000000000000022201472033334600203160ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'lib/nanoc/core/version' Gem::Specification.new do |s| s.name = 'nanoc-core' s.version = Nanoc::Core::VERSION s.homepage = 'https://nanoc.app/' s.summary = 'Core of Nanoc' s.description = 'Contains the core of Nanoc' s.author = 'Denis Defreyne' s.email = 'denis+rubygems@denis.ws' s.license = 'MIT' s.files = ['NEWS.md', 'README.md'] + Dir['lib/**/*.rb'] + Dir['lib/**/*-schema.json'] s.require_paths = ['lib'] s.required_ruby_version = '>= 3.1' s.add_dependency('base64', '~> 0.2') s.add_dependency('concurrent-ruby', '~> 1.1') s.add_dependency('ddmetrics', '~> 1.0') s.add_dependency('ddplugin', '~> 1.0') s.add_dependency('immutable-ruby', '~> 0.1') s.add_dependency('json_schema', '~> 0.19') s.add_dependency('memo_wise', '~> 1.5') s.add_dependency('slow_enumerator_tools', '~> 1.0') s.add_dependency('tty-platform', '~> 0.2') s.add_dependency('zeitwerk', '~> 2.1') s.metadata = { 'rubygems_mfa_required' => 'true', 'source_code_uri' => "https://github.com/nanoc/nanoc/tree/#{s.name}-v#{s.version}/#{s.name}", } end nanoc-4.13.3/nanoc-core/nanoc-core.manifest000066400000000000000000000125211472033334600205060ustar00rootroot00000000000000NEWS.md README.md lib/nanoc-core.rb lib/nanoc/core.rb lib/nanoc/core/version.rb lib/nanoc/core/action_provider.rb lib/nanoc/core/action_sequence_builder.rb lib/nanoc/core/action_sequence_store.rb lib/nanoc/core/action_sequence.rb lib/nanoc/core/aggregate_data_source.rb lib/nanoc/core/assertions.rb lib/nanoc/core/basic_outdatedness_checker.rb lib/nanoc/core/basic_item_rep_collection_view.rb lib/nanoc/core/basic_item_rep_view.rb lib/nanoc/core/basic_item_view.rb lib/nanoc/core/binary_compiled_content_cache.rb lib/nanoc/core/binary_content.rb lib/nanoc/core/changes_stream.rb lib/nanoc/core/checksum_collection.rb lib/nanoc/core/checksum_store.rb lib/nanoc/core/checksummer.rb lib/nanoc/core/code_snippet.rb lib/nanoc/core/compilation_context.rb lib/nanoc/core/compilation_item_rep_collection_view.rb lib/nanoc/core/compilation_item_rep_view.rb lib/nanoc/core/compilation_item_view.rb lib/nanoc/core/compilation_phases/abstract.rb lib/nanoc/core/compilation_phases/cache.rb lib/nanoc/core/compilation_phases/mark_done.rb lib/nanoc/core/compilation_phases/notify.rb lib/nanoc/core/compilation_phases/recalculate.rb lib/nanoc/core/compilation_phases/resume.rb lib/nanoc/core/compilation_phases/write.rb lib/nanoc/core/compilation_stage.rb lib/nanoc/core/compilation_stages/build_reps.rb lib/nanoc/core/compilation_stages/calculate_checksums.rb lib/nanoc/core/compilation_stages/cleanup.rb lib/nanoc/core/compilation_stages/compile_reps.rb lib/nanoc/core/compilation_stages/determine_outdatedness.rb lib/nanoc/core/compilation_stages/forget_outdated_dependencies.rb lib/nanoc/core/compilation_stages/load_stores.rb lib/nanoc/core/compilation_stages/postprocess.rb lib/nanoc/core/compilation_stages/preprocess.rb lib/nanoc/core/compilation_stages/prune.rb lib/nanoc/core/compilation_stages/store_post_compilation_state.rb lib/nanoc/core/compilation_stages/store_pre_compilation_state.rb lib/nanoc/core/compiled_content_cache.rb lib/nanoc/core/compiled_content_store.rb lib/nanoc/core/compiler_loader.rb lib/nanoc/core/compiler.rb lib/nanoc/core/config_loader.rb lib/nanoc/core/config_view.rb lib/nanoc/core/configuration-schema.json lib/nanoc/core/configuration.rb lib/nanoc/core/content.rb lib/nanoc/core/context.rb lib/nanoc/core/contracts_support.rb lib/nanoc/core/core_ext/array.rb lib/nanoc/core/core_ext/hash.rb lib/nanoc/core/core_ext/string.rb lib/nanoc/core/data_source.rb lib/nanoc/core/dependency_props.rb lib/nanoc/core/dependency_store.rb lib/nanoc/core/dependency_tracker.rb lib/nanoc/core/dependency.rb lib/nanoc/core/directed_graph.rb lib/nanoc/core/document_view_mixin.rb lib/nanoc/core/document.rb lib/nanoc/core/error.rb lib/nanoc/core/errors.rb lib/nanoc/core/executor.rb lib/nanoc/core/feature.rb lib/nanoc/core/filter.rb lib/nanoc/core/identifiable_collection_view.rb lib/nanoc/core/identifiable_collection.rb lib/nanoc/core/identifier.rb lib/nanoc/core/in_memory_data_source.rb lib/nanoc/core/instrumentor.rb lib/nanoc/core/item_collection_with_reps_view.rb lib/nanoc/core/item_collection_without_reps_view.rb lib/nanoc/core/item_collection.rb lib/nanoc/core/item_rep_builder.rb lib/nanoc/core/item_rep_repo.rb lib/nanoc/core/item_rep_router.rb lib/nanoc/core/item_rep_selector.rb lib/nanoc/core/item_rep_writer.rb lib/nanoc/core/item_rep.rb lib/nanoc/core/item.rb lib/nanoc/core/layout_collection_view.rb lib/nanoc/core/layout_collection.rb lib/nanoc/core/layout_view.rb lib/nanoc/core/layout.rb lib/nanoc/core/lazy_value.rb lib/nanoc/core/mutable_config_view.rb lib/nanoc/core/mutable_document_view_mixin.rb lib/nanoc/core/mutable_identifiable_collection_view.rb lib/nanoc/core/mutable_item_collection_view.rb lib/nanoc/core/mutable_item_view.rb lib/nanoc/core/mutable_layout_collection_view.rb lib/nanoc/core/mutable_layout_view.rb lib/nanoc/core/notification_center.rb lib/nanoc/core/outdatedness_checker.rb lib/nanoc/core/outdatedness_reasons.rb lib/nanoc/core/outdatedness_rule.rb lib/nanoc/core/outdatedness_rules/attributes_modified.rb lib/nanoc/core/outdatedness_rules/code_snippets_modified.rb lib/nanoc/core/outdatedness_rules/content_modified.rb lib/nanoc/core/outdatedness_rules/item_added.rb lib/nanoc/core/outdatedness_rules/layout_added.rb lib/nanoc/core/outdatedness_rules/not_written.rb lib/nanoc/core/outdatedness_rules/rules_modified.rb lib/nanoc/core/outdatedness_rules/uses_always_outdated_filter.rb lib/nanoc/core/outdatedness_status.rb lib/nanoc/core/outdatedness_store.rb lib/nanoc/core/pattern.rb lib/nanoc/core/post_compile_item_collection_view.rb lib/nanoc/core/post_compile_item_rep_collection_view.rb lib/nanoc/core/post_compile_item_rep_view.rb lib/nanoc/core/post_compile_item_view.rb lib/nanoc/core/prefixed_data_source.rb lib/nanoc/core/processing_action.rb lib/nanoc/core/processing_actions.rb lib/nanoc/core/processing_actions/filter.rb lib/nanoc/core/processing_actions/layout.rb lib/nanoc/core/processing_actions/snapshot.rb lib/nanoc/core/pruner.rb lib/nanoc/core/regexp_pattern.rb lib/nanoc/core/site_loader.rb lib/nanoc/core/site.rb lib/nanoc/core/snapshot_def.rb lib/nanoc/core/store.rb lib/nanoc/core/string_pattern.rb lib/nanoc/core/temp_filename_factory.rb lib/nanoc/core/textual_compiled_content_cache.rb lib/nanoc/core/textual_content.rb lib/nanoc/core/trivial_error.rb lib/nanoc/core/utils.rb lib/nanoc/core/view_context_for_compilation.rb lib/nanoc/core/view_context_for_pre_compilation.rb lib/nanoc/core/view_context_for_shell.rb lib/nanoc/core/view.rb lib/nanoc/core/yaml_loader.rb nanoc-4.13.3/nanoc-core/spec/000077500000000000000000000000001472033334600156635ustar00rootroot00000000000000nanoc-4.13.3/nanoc-core/spec/manifest_spec.rb000066400000000000000000000002151472033334600210260ustar00rootroot00000000000000# frozen_string_literal: true describe 'manifest', chdir: false do example do expect('nanoc-core').to have_a_valid_manifest end end nanoc-4.13.3/nanoc-core/spec/meta_spec.rb000066400000000000000000000055141472033334600201550ustar00rootroot00000000000000# frozen_string_literal: true describe 'meta', chdir: false do # rubocop:disable RSpec/ExampleLength it 'is covered by specs' do regular_files = Dir['lib/nanoc/core/**/*.rb'] regular_file_base_names = regular_files.map { |fn| fn.gsub(/^lib\/nanoc\/core\/|\.rb$/, '') } spec_files = Dir['spec/nanoc/core/**/*_spec.rb'] spec_file_base_names = spec_files.map { |fn| fn.gsub(/^spec\/nanoc\/core\/|_spec\.rb$/, '') } # TODO: don’t ignore anything ignored_regular_file_base_names = %w[ action_provider assertions basic_item_view checksum_collection compilation_context compilation_phases/mark_done compilation_phases/notify compilation_phases/recalculate compilation_phases/write compiler_loader contracts_support dependency document_view_mixin error errors identifiable_collection_view item_collection item_rep_repo layout_collection mutable_document_view_mixin mutable_identifiable_collection_view outdatedness_reasons outdatedness_rule outdatedness_rules/attributes_modified outdatedness_rules/code_snippets_modified outdatedness_rules/content_modified outdatedness_rules/item_added outdatedness_rules/layout_added outdatedness_rules/not_written outdatedness_rules/rules_modified outdatedness_rules/uses_always_outdated_filter post_compile_item_collection_view processing_actions snapshot_def trivial_error version view view_context_for_compilation view_context_for_pre_compilation view_context_for_shell compilation_stages/build_reps compilation_stages/forget_outdated_dependencies compilation_stages/load_stores compilation_stages/postprocess compilation_stages/prune compilation_stages/store_post_compilation_state compilation_stages/store_pre_compilation_state ] ignored_spec_file_base_names = %w[ errors/dependency_cycle outdatedness_rules item_rep_selector/item_rep_priority_queue ] effective_regular_file_base_names = regular_file_base_names - ignored_regular_file_base_names effective_spec_file_base_names = spec_file_base_names - ignored_spec_file_base_names expect(effective_regular_file_base_names) .to match_array(effective_spec_file_base_names) end it 'doesn’t log anything' do # TODO: don’t have any exceptions regular_files = Dir['lib/nanoc/core/**/*.rb'] - [ 'lib/nanoc/core/data_source.rb', 'lib/nanoc/core/contracts_support.rb', ] expect(regular_files).to all(satisfy do |fn| content = File.read(fn) !content.match?(/\b(puts|warn)\b/) && !content.match?(/\$std(err|out)/) end) end # rubocop:enable RSpec/ExampleLength end nanoc-4.13.3/nanoc-core/spec/nanoc/000077500000000000000000000000001472033334600167615ustar00rootroot00000000000000nanoc-4.13.3/nanoc-core/spec/nanoc/core/000077500000000000000000000000001472033334600177115ustar00rootroot00000000000000nanoc-4.13.3/nanoc-core/spec/nanoc/core/action_sequence_builder_spec.rb000066400000000000000000000032661472033334600261320ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Core::ActionSequenceBuilder do let(:builder) { described_class.new } let(:item_rep) { Nanoc::Core::ItemRep.new(item, :default) } let(:item) { Nanoc::Core::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::Core::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::Core::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', item_rep) } it 'adds an action' do expect { subject } .to change { builder.action_sequence.actions } .from([]) .to([Nanoc::Core::ProcessingActions::Snapshot.new([:last], ['/foo.html'])]) end end context 'add two snapshots with same name' do subject do builder.add_snapshot(:last, '/foo.html', item_rep) builder.add_snapshot(:last, '/foo.htm', item_rep) end it 'raises' do expect { subject } .to raise_error(Nanoc::Core::ActionSequenceBuilder::CannotCreateMultipleSnapshotsWithSameNameError, '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.13.3/nanoc-core/spec/nanoc/core/action_sequence_spec.rb000066400000000000000000000075411472033334600244240ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Core::ActionSequence do let(:action_sequence) { raise 'override me' } let(:item) { Nanoc::Core::Item.new('foo', {}, '/foo.md') } let(:rep) { Nanoc::Core::ItemRep.new(item, :default) } describe '#initialize' do context 'with actions' do subject { described_class.new(actions:) } let(:actions) do [ Nanoc::Core::ProcessingActions::Filter.new(:erb, {}), ] end its(:actions) { is_expected.to be(actions) } end context 'without actions' do subject { described_class.new } its(:actions) { is_expected.to be_empty } end end describe '#size' do subject { action_sequence.size } context 'no actions' do let(:action_sequence) do Nanoc::Core::ActionSequenceBuilder.build do |b| end end it { is_expected.to be(0) } end context 'some actions' do let(:action_sequence) do Nanoc::Core::ActionSequenceBuilder.build do |b| b.add_filter(:foo, {}) end end it { is_expected.to be(1) } end end describe '#[]' do subject { action_sequence[index] } let(:index) { 0 } context 'no actions' do let(:action_sequence) do Nanoc::Core::ActionSequenceBuilder.build do |b| end end it { is_expected.to be_nil } end context 'some actions' do let(:action_sequence) do Nanoc::Core::ActionSequenceBuilder.build do |b| b.add_filter(:foo, {}) end end it { is_expected.to be_a(Nanoc::Core::ProcessingActions::Filter) } end end describe '#snapshot_actions' do subject { action_sequence.snapshot_actions } let(:action_sequence) do Nanoc::Core::ActionSequenceBuilder.build do |b| b.add_filter(:foo, {}) b.add_snapshot(:pre, '/page-pre.html', rep) b.add_layout('/default.erb', {}) end end it { is_expected.to contain_exactly(Nanoc::Core::ProcessingActions::Snapshot.new([:pre], ['/page-pre.html'])) } end describe '#paths' do subject { action_sequence.paths } let(:action_sequence) do Nanoc::Core::ActionSequenceBuilder.build do |b| b.add_snapshot(:pre, '/pre.html', rep) b.add_snapshot(:post, '/post.html', rep) end end it { is_expected.to contain_exactly([[:pre], ['/pre.html']], [[:post], ['/post.html']]) } end describe '#each' do let(:action_sequence) do Nanoc::Core::ActionSequenceBuilder.build do |b| b.add_filter(:erb, awesomeness: 'high') b.add_snapshot(:bar, '/foo.md', rep) b.add_layout('/default.erb', somelayoutparam: 'yes') end end example do actions = action_sequence.map { _1 } expect(actions.size).to eq(3) end end describe '#map' do let(:action_sequence) do Nanoc::Core::ActionSequenceBuilder.build do |b| b.add_filter(:erb, awesomeness: 'high') b.add_snapshot(:bar, '/foo.md', rep) b.add_layout('/default.erb', somelayoutparam: 'yes') end end example do res = action_sequence.map { Nanoc::Core::ProcessingActions::Filter.new(:donkey, {}) } expect(res.to_a.size).to eq(3) expect(res.to_a).to all(be_a(Nanoc::Core::ProcessingActions::Filter)) end end describe '#serialize' do subject { action_sequence.serialize } let(:action_sequence) do Nanoc::Core::ActionSequenceBuilder.build do |b| b.add_filter(:erb, awesomeness: 'high') b.add_snapshot(:bar, '/foo.md', rep) b.add_layout('/default.erb', somelayoutparam: 'yes') end end example do expect(subject).to eql( [ [:filter, :erb, 'B1gmzMdP+iEDgTz7SylLoB6yLNw='], [:snapshot, [:bar], true, ['/foo.md']], [:layout, '/default.erb', 'QQW0vu/3fP4Ihc5xhQKuPer3xUc='], ], ) end end end nanoc-4.13.3/nanoc-core/spec/nanoc/core/action_sequence_store_spec.rb000066400000000000000000000026301472033334600256320ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Core::ActionSequenceStore do let(:store) { described_class.new(config:) } let(:config) { Nanoc::Core::Configuration.new(dir: Dir.getwd).with_defaults } describe '#[]' do subject { store[obj] } let(:known_obj) { Nanoc::Core::Item.new('asdf', {}, '/asdf.md') } let(:unknown_obj) { Nanoc::Core::Item.new('fdsa', {}, '/fdsa.md') } let(:some_action_sequence) do Nanoc::Core::ActionSequenceBuilder.build do |b| b.add_filter(:foo, {}) end end before do store[known_obj] = some_action_sequence end context 'obj is not known' do let(:obj) { unknown_obj } it { is_expected.to be_nil } end context 'obj is known' do let(:obj) { known_obj } it { is_expected.to eq(some_action_sequence) } end end describe 'load and store' do subject do store.store store.load end let(:known_obj) { Nanoc::Core::Item.new('asdf', {}, '/asdf.md') } let(:unknown_obj) { Nanoc::Core::Item.new('fdsa', {}, '/fdsa.md') } let(:some_action_sequence) do Nanoc::Core::ActionSequenceBuilder.build do |b| b.add_filter(:foo, {}) end end before do store[known_obj] = some_action_sequence end it 'retains its data' do expect(store[known_obj]).to eq(some_action_sequence) expect(store[unknown_obj]).to be_nil end end end nanoc-4.13.3/nanoc-core/spec/nanoc/core/aggregate_data_source_spec.rb000066400000000000000000000035221472033334600255510ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Core::AggregateDataSource, stdio: true do subject(:data_source) do described_class.new([data_source_1, data_source_2], {}) end let(:klass_1) do Class.new(Nanoc::Core::DataSource) do def items [Nanoc::Core::Item.new('One', {}, '/one.md')] end def item_changes %i[one_foo one_bar] end def layouts [Nanoc::Core::Layout.new('One', {}, '/one.md')] end def layout_changes %i[one_foo one_bar] end end end let(:klass_2) do Class.new(Nanoc::Core::DataSource) do def items [Nanoc::Core::Item.new('Two', {}, '/two.md')] end def item_changes %i[two_foo two_bar] end def layouts [Nanoc::Core::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 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.13.3/nanoc-core/spec/nanoc/core/basic_item_rep_collection_view_spec.rb000066400000000000000000000004041472033334600274600ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'support/item_rep_collection_view_examples' describe Nanoc::Core::BasicItemRepCollectionView do let(:expected_view_class) { Nanoc::Core::BasicItemRepView } it_behaves_like 'an item rep collection view' end nanoc-4.13.3/nanoc-core/spec/nanoc/core/basic_item_rep_view_spec.rb000066400000000000000000000003461472033334600252520ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'support/item_rep_view_examples' describe Nanoc::Core::BasicItemRepView do let(:expected_item_view_class) { Nanoc::Core::BasicItemView } it_behaves_like 'an item rep view' end nanoc-4.13.3/nanoc-core/spec/nanoc/core/basic_outdatedness_checker_spec.rb000066400000000000000000000071621472033334600266050ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Core::BasicOutdatednessChecker do let(:basic_outdatedness_checker) do described_class.new( reps:, site:, checksum_store:, checksums:, dependency_store:, action_sequence_store:, action_sequences:, ) end let(:checksum_store) { double(:checksum_store) } let(:checksums) do checksums = {} [items, layouts].each do |documents| documents.each do |document| checksums[[document.reference, :content]] = Nanoc::Core::Checksummer.calc_for_content_of(document) checksums[[document.reference, :each_attribute]] = Nanoc::Core::Checksummer.calc_for_each_attribute_of(document) end end [items, layouts, code_snippets].each do |objs| objs.each do |obj| checksums[obj.reference] = Nanoc::Core::Checksummer.calc(obj) end end checksums[config.reference] = Nanoc::Core::Checksummer.calc(config) checksums[[config.reference, :each_attribute]] = Nanoc::Core::Checksummer.calc_for_each_attribute_of(config) Nanoc::Core::ChecksumCollection.new(checksums) end let(:dependency_store) do Nanoc::Core::DependencyStore.new(items, layouts, config) end let(:items) { Nanoc::Core::ItemCollection.new(config, [item]) } let(:layouts) { Nanoc::Core::LayoutCollection.new(config) } let(:code_snippets) { [] } let(:site) do Nanoc::Core::Site.new( config:, code_snippets:, data_source: Nanoc::Core::InMemoryDataSource.new(items, layouts), ) end let(:action_sequence_store) do Nanoc::Core::ActionSequenceStore.new(config:) end let(:old_action_sequence_for_item_rep) do Nanoc::Core::ActionSequenceBuilder.build 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::Core::ItemRepRepo.new end let(:item_rep) { Nanoc::Core::ItemRep.new(item, :default) } let(:item) { Nanoc::Core::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 { basic_outdatedness_checker.outdatedness_status_for(obj).reasons.first } let(:checksum_store) { Nanoc::Core::ChecksumStore.new(config:, objects: items.to_a + layouts.to_a) } let(:config) { Nanoc::Core::Configuration.new(dir: Dir.getwd).with_defaults } before do checksum_store.add(item) allow(site).to receive_messages(code_snippets: [], config:) end context 'with item' do let(:obj) { item } context 'action sequence differs' do let(:new_action_sequence_for_item_rep) do Nanoc::Core::ActionSequenceBuilder.build do |b| b.add_filter(:super_erb, {}) end end it 'is outdated due to rule differences' do expect(subject).to eql(Nanoc::Core::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::Core::ActionSequenceBuilder.build do |b| b.add_filter(:super_erb, {}) end end it 'is outdated due to rule differences' do expect(subject).to eql(Nanoc::Core::OutdatednessReasons::RulesModified) end end # … end context 'with layout' do # … end end end nanoc-4.13.3/nanoc-core/spec/nanoc/core/binary_compiled_content_cache_spec.rb000066400000000000000000000100401472033334600272600ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Core::BinaryCompiledContentCache do let(:cache) { described_class.new(config:) } let(:items) { [item] } let(:item) { Nanoc::Core::Item.new('asdf', {}, '/foo.md') } let(:item_rep) { Nanoc::Core::ItemRep.new(item, :default) } let(:other_item) { Nanoc::Core::Item.new('asdf', {}, '/sneaky.md') } let(:other_item_rep) { Nanoc::Core::ItemRep.new(other_item, :default) } let(:content) do Nanoc::Core::Content.create(File.join(Dir.getwd, 'bin.dat'), binary: true).tap do |c| File.write(c.filename, 'b1n4ry') end end let(:config) { Nanoc::Core::Configuration.new(dir: Dir.getwd).with_defaults } it 'has no content by default' do expect(cache[item_rep]).to be_nil end describe 'setting new content' do subject do cache[item_rep] = { last: content } end it 'sets content' do expect { subject } .to change { cache[item_rep] } .from(nil) .to(be_a(Hash)) end it 'has correct content in cache' do subject expect(File.read(cache[item_rep][:last].filename)).to eql('b1n4ry') end it 'has correct filename in cache' do subject expect(cache[item_rep][:last].filename) .to match(%r{tmp/nanoc/[a-z0-9]+/binary_content_data/_foo_md-299726ada7/default-7505d64a54/last-213ed3ea45\z}) end end describe 'setting empty content' do subject do cache[item_rep] = {} end it 'sets content' do expect { subject } .to change { cache[item_rep] } .from(nil) .to({}) end end describe 'replacing existing content with itself' do subject do cache[item_rep] = cache[item_rep] end before do cache[item_rep] = { last: content } end it 'does not crash' do expect { subject } .not_to change { cache[item_rep][:last].filename } end end describe '#prune' do subject { cache.prune(items:) } before do cache[item_rep] = { last: content } cache[other_item_rep] = { last: content } end it 'empties content for unknown item' do expect { subject } .to change { cache[other_item_rep].nil? } .from(false) .to(true) end it 'retains content for known item' do expect { subject } .not_to change { cache[item_rep].nil? } .from(false) end it 'deletes content for unknown item' do pattern = 'tmp/nanoc/*/binary_content_data/*' is_sneaky_md = ->(fn) { fn.end_with?('/binary_content_data/_sneaky_md-e0111278e4') } expect { subject } .to change { Dir[pattern].any? { |e| is_sneaky_md.call(e) } } .from(true) .to(false) end it 'retains content for known item' do pattern = 'tmp/nanoc/*/binary_content_data/*' is_foo_md = ->(fn) { fn.end_with?('/binary_content_data/_foo_md-299726ada7') } expect { subject } .not_to change { Dir[pattern].any? { |e| is_foo_md.call(e) } } .from(true) end end context 'nested items' do let(:keep) { Nanoc::Core::Item.new('keep', {}, '/articles/keep/foo.md') } let(:keep_item_rep) { Nanoc::Core::ItemRep.new(keep, :default) } let(:remove) { Nanoc::Core::Item.new('remove', {}, '/articles/remove/foo.md') } let(:remove_item_rep) { Nanoc::Core::ItemRep.new(remove, :default) } let(:remove_dotted) { Nanoc::Core::Item.new('remove dotted', {}, '/articles/remove-dotted/foo.md') } let(:remove_dotted_item_rep) { Nanoc::Core::ItemRep.new(remove_dotted, :default) } before do cache[keep_item_rep] = { last: content } cache[remove_item_rep] = { last: content } cache[remove_dotted_item_rep] = { '.last': content } cache.prune(items: [keep]) end it 'has content for kept items' do expect(cache[keep_item_rep]).not_to be_nil end it 'has no content for removed items' do expect(cache[remove_item_rep]).to be_nil end it 'has no content for removed items with snapshots starting with a dot' do expect(cache[remove_dotted_item_rep]).to be_nil end end end nanoc-4.13.3/nanoc-core/spec/nanoc/core/binary_content_spec.rb000066400000000000000000000015501472033334600242670ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Core::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 be(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.13.3/nanoc-core/spec/nanoc/core/changes_stream_spec.rb000066400000000000000000000023021472033334600242300ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Core::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.13.3/nanoc-core/spec/nanoc/core/checksum_store_spec.rb000066400000000000000000000110431472033334600242650ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Core::ChecksumStore do let(:store) { described_class.new(config:, objects:) } let(:config) { Nanoc::Core::Configuration.new(dir: Dir.getwd).with_defaults } let(:objects) { [item, code_snippet] } let(:item) { Nanoc::Core::Item.new('asdf', item_attributes, '/foo.md') } let(:other_item) { Nanoc::Core::Item.new('asdf', other_item_attributes, '/sneaky.md') } let(:item_attributes) { {} } let(:other_item_attributes) { {} } let(:code_snippet) { Nanoc::Core::CodeSnippet.new('def hi ; end', 'lib/foo.rb') } let(:other_code_snippet) { Nanoc::Core::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 'after storing and loading missing data' do let(:code_snippet_a) { Nanoc::Core::CodeSnippet.new('aaa', 'lib/aaa.rb') } let(:code_snippet_b) { Nanoc::Core::CodeSnippet.new('bbb', 'lib/bbb.rb') } let(:code_snippet_c) { Nanoc::Core::CodeSnippet.new('ccc', 'lib/ccc.rb') } before do store.add(code_snippet_a) store.add(code_snippet_b) store.add(code_snippet_c) store.store # remove B store.objects = [code_snippet_a, code_snippet_c] store.load end it 'has checksums for A and C' do expect(store[code_snippet_a]).not_to be_nil expect(store[code_snippet_c]).not_to be_nil 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.13.3/nanoc-core/spec/nanoc/core/checksummer_spec.rb000066400000000000000000000325471472033334600235710ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Core::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::Core::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::Core::Checksummer do subject { described_class.calc(obj, Nanoc::Core::Checksummer::VerboseDigest) } describe '.calc_for_each_attribute_of' do let(:obj) { Nanoc::Core::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::Core::Checksummer::VerboseDigest) end it { is_expected.to eq(foo: 'String#0') } end end context 'String' do let(:obj) { 'hello' } it { is_expected.to eql('String#0') } end context 'Symbol' do let(:obj) { :hello } it { is_expected.to eql('Symbol#0') } end context 'nil' do let(:obj) { nil } it { is_expected.to eql('NilClass#0<>') } end context 'true' do let(:obj) { true } it { is_expected.to eql('TrueClass#0<>') } end context 'false' do let(:obj) { false } it { is_expected.to eql('FalseClass#0<>') } end context 'Array' do let(:obj) { %w[hello goodbye] } it { is_expected.to eql('Array#0,String#2,>') } context 'different order' do let(:obj) { %w[goodbye hello] } it { is_expected.to eql('Array#0,String#2,>') } end context 'recursive' do let(:obj) { [].tap { |arr| arr << ['hello', arr] } } it { is_expected.to eql('Array#0,@0,>,>') } end context 'non-serializable' do let(:obj) { [-> {}] } it { is_expected.to match(/\AArray#0>,>\z/) } end end context 'Set' do let(:obj) { Set.new(%w[hello goodbye]) } it { is_expected.to eql('Set#0,String#2,>') } context 'different order' do let(:obj) { Set.new(%w[goodbye hello]) } it { is_expected.to eql('Set#0,String#2,>') } end context 'recursive' do let(:obj) { Set.new.tap { |set| set << Set.new.add('hello').add(set) } } it { is_expected.to eql('Set#0,@0,>,>') } end context 'non-serializable' do let(:obj) { Set.new.add(-> {}) } it { is_expected.to match(/\ASet#0>,>\z/) } end end context 'Hash' do let(:obj) { { 'a' => 'foo', 'b' => 'bar' } } it { is_expected.to eql('Hash#0=String#2,String#3=String#4,>') } context 'different order' do let(:obj) { { 'b' => 'bar', 'a' => 'foo' } } it { is_expected.to eql('Hash#0=String#2,String#3=String#4,>') } end context 'non-serializable' do let(:obj) { { 'a' => -> {} } } it { is_expected.to match(/\AHash#0=Proc#2<#>,>\z/) } end context 'recursive values' do let(:obj) { {}.tap { |hash| hash['a'] = hash } } it { is_expected.to eql('Hash#0=@0,>') } end context 'recursive keys' do let(:obj) { {}.tap { |hash| hash[hash] = 'hello' } } it { is_expected.to eql('Hash#0<@0=String#1,>') } 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#0<6-200>') } context 'does not exist' do before do FileUtils.rm_rf(filename) end it { is_expected.to eql('Pathname#0') } end context 'different data' do let(:data) { 'other stuffs :o' } it { is_expected.to eql('Pathname#0<15-200>') } end end context 'Time' do let(:obj) { Time.at(111_223) } it { is_expected.to eql('Time#0<111223>') } end context 'Float' do let(:obj) { 3.14 } it { is_expected.to eql('Float#0<3.14>') } end context 'Fixnum/Integer' do let(:obj) { 3 } it { is_expected.to match(/\A(Integer|Fixnum)#0<3>\z/) } end context 'Nanoc::Core::Identifier' do let(:obj) { Nanoc::Core::Identifier.new('/foo.md') } it { is_expected.to eql('Nanoc::Core::Identifier#0>') } end context 'Nanoc::Core::Configuration' do let(:obj) { Nanoc::Core::Configuration.new(dir: Dir.getwd, hash: { 'foo' => 'bar' }) } it { is_expected.to eql('Nanoc::Core::Configuration#0=String#2,>') } end context 'Nanoc::Core::Item' do let(:obj) { Nanoc::Core::Item.new('asdf', { 'foo' => 'bar' }, '/foo.md') } it { is_expected.to eql('Nanoc::Core::Item#0>,attributes=Hash#3=String#5,>,identifier=Nanoc::Core::Identifier#6>>') } context 'binary' do let(:filename) { File.expand_path('foo.dat') } let(:content) { Nanoc::Core::BinaryContent.new(filename) } let(:obj) { Nanoc::Core::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::Core::Item#0>,attributes=Hash#3=String#5,>,identifier=Nanoc::Core::Identifier#6>>') } end context 'recursive attributes' do before do obj.attributes[:foo] = obj end it { is_expected.to eql('Nanoc::Core::Item#0>,attributes=Hash#3=@0,>,identifier=Nanoc::Core::Identifier#5>>') } end context 'with checksum' do let(:obj) { Nanoc::Core::Item.new('asdf', { 'foo' => 'bar' }, '/foo.md', checksum_data: 'abcdef') } it { is_expected.to eql('Nanoc::Core::Item#0') } end context 'with content checksum' do let(:obj) { Nanoc::Core::Item.new('asdf', { 'foo' => 'bar' }, '/foo.md', content_checksum_data: 'con-cs') } it { is_expected.to eql('Nanoc::Core::Item#0=String#3,>,identifier=Nanoc::Core::Identifier#4>>') } end context 'with attributes checksum' do let(:obj) { Nanoc::Core::Item.new('asdf', { 'foo' => 'bar' }, '/foo.md', attributes_checksum_data: 'attr-cs') } it { is_expected.to eql('Nanoc::Core::Item#0>,attributes_checksum_data=attr-cs,identifier=Nanoc::Core::Identifier#3>>') } end end context 'Nanoc::Core::Layout' do let(:obj) { Nanoc::Core::Layout.new('asdf', { 'foo' => 'bar' }, '/foo.md') } it { is_expected.to eql('Nanoc::Core::Layout#0>,attributes=Hash#3=String#5,>,identifier=Nanoc::Core::Identifier#6>>') } context 'recursive attributes' do before do obj.attributes[:foo] = obj end it { is_expected.to eql('Nanoc::Core::Layout#0>,attributes=Hash#3=@0,>,identifier=Nanoc::Core::Identifier#5>>') } end context 'with checksum' do let(:obj) { Nanoc::Core::Layout.new('asdf', { 'foo' => 'bar' }, '/foo.md', checksum_data: 'abcdef') } it { is_expected.to eql('Nanoc::Core::Layout#0') } end end context 'Nanoc::Core::ItemRep' do let(:obj) { Nanoc::Core::ItemRep.new(item, :pdf) } let(:item) { Nanoc::Core::Item.new('asdf', {}, '/foo.md') } it { is_expected.to eql('Nanoc::Core::ItemRep#0>,attributes=Hash#4<>,identifier=Nanoc::Core::Identifier#5>>,name=Symbol#7>') } end context 'Nanoc::Core::Context' do let(:obj) { Nanoc::Core::Context.new(foo: 123) } it { is_expected.to match(/\ANanoc::Core::Context#0<@foo=(Fixnum|Integer)#1<123>,>\z/) } end context 'Nanoc::Core::CodeSnippet' do let(:obj) { Nanoc::Core::CodeSnippet.new('asdf', '/bob.rb') } it { is_expected.to eql('Nanoc::Core::CodeSnippet#0>') } end context 'Nanoc::Core::CompilationItemView' do let(:obj) { Nanoc::Core::CompilationItemView.new(item, nil) } let(:item) { Nanoc::Core::Item.new('asdf', {}, '/foo.md') } it { is_expected.to eql('Nanoc::Core::CompilationItemView#0>,attributes=Hash#4<>,identifier=Nanoc::Core::Identifier#5>>>') } end context 'Nanoc::Core::BasicItemRepView' do let(:obj) { Nanoc::Core::BasicItemRepView.new(rep, nil) } let(:rep) { Nanoc::Core::ItemRep.new(item, :pdf) } let(:item) { Nanoc::Core::Item.new('asdf', {}, '/foo.md') } it { is_expected.to eql('Nanoc::Core::BasicItemRepView#0>,attributes=Hash#5<>,identifier=Nanoc::Core::Identifier#6>>,name=Symbol#8>>') } end context 'Nanoc::Core::CompilationItemRepView' do let(:obj) { Nanoc::Core::CompilationItemRepView.new(rep, nil) } let(:rep) { Nanoc::Core::ItemRep.new(item, :pdf) } let(:item) { Nanoc::Core::Item.new('asdf', {}, '/foo.md') } it { is_expected.to eql('Nanoc::Core::CompilationItemRepView#0>,attributes=Hash#5<>,identifier=Nanoc::Core::Identifier#6>>,name=Symbol#8>>') } end context 'Nanoc::Core::BasicItemView' do let(:obj) { Nanoc::Core::BasicItemView.new(item, nil) } let(:item) { Nanoc::Core::Item.new('asdf', {}, '/foo.md') } it { is_expected.to eql('Nanoc::Core::BasicItemView#0>,attributes=Hash#4<>,identifier=Nanoc::Core::Identifier#5>>>') } end context 'Nanoc::Core::LayoutView' do let(:obj) { Nanoc::Core::LayoutView.new(layout, nil) } let(:layout) { Nanoc::Core::Layout.new('asdf', {}, '/foo.md') } it { is_expected.to eql('Nanoc::Core::LayoutView#0>,attributes=Hash#4<>,identifier=Nanoc::Core::Identifier#5>>>') } end context 'Nanoc::Core::ConfigView' do let(:obj) { Nanoc::Core::ConfigView.new(config, nil) } let(:config) { Nanoc::Core::Configuration.new(dir: Dir.getwd, hash: { 'foo' => 'bar' }) } it { is_expected.to eql('Nanoc::Core::ConfigView#0=String#3,>>') } end context 'Nanoc::Core::ItemCollectionWithRepsView' do let(:obj) { Nanoc::Core::ItemCollectionWithRepsView.new(wrapped, nil) } let(:config) { Nanoc::Core::Configuration.new(dir: Dir.getwd, hash: { 'foo' => 'bar' }) } let(:wrapped) do Nanoc::Core::ItemCollection.new( config, [ Nanoc::Core::Item.new('foo', {}, '/foo.md'), Nanoc::Core::Item.new('bar', {}, '/bar.md'), ], ) end it { is_expected.to eql('Nanoc::Core::ItemCollectionWithRepsView#0>,attributes=Hash#5<>,identifier=Nanoc::Core::Identifier#6>>,Nanoc::Core::Item#8>,attributes=@5,identifier=Nanoc::Core::Identifier#11>>,>>') } end context 'Nanoc::Core::ItemCollectionWithoutRepsView' do let(:obj) { Nanoc::Core::ItemCollectionWithoutRepsView.new(wrapped, nil) } let(:config) { Nanoc::Core::Configuration.new(dir: Dir.getwd, hash: { 'foo' => 'bar' }) } let(:wrapped) do Nanoc::Core::ItemCollection.new( config, [ Nanoc::Core::Item.new('foo', {}, '/foo.md'), Nanoc::Core::Item.new('bar', {}, '/bar.md'), ], ) end it { is_expected.to eql('Nanoc::Core::ItemCollectionWithoutRepsView#0>,attributes=Hash#5<>,identifier=Nanoc::Core::Identifier#6>>,Nanoc::Core::Item#8>,attributes=@5,identifier=Nanoc::Core::Identifier#11>>,>>') } 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##0<.*>\z/) } end context 'other non-marshal-able classes' do let(:obj) { proc {} } it { is_expected.to match(/\AProc#0<#>\z/) } end end nanoc-4.13.3/nanoc-core/spec/nanoc/core/code_snippet_spec.rb000066400000000000000000000036631472033334600237340ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Core::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::Core::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::Core::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 = described_class.new("@foo = 'woof'", 'dog.rb') code_snippet.load expect(@foo).to eq('meow') end describe 'calling twice' do subject do 2.times { code_snippet.load } end let(:data) { 'def v5yqq2zmfcjr; "ok"; end' } it 'does not write warnings to stdout' do expect { subject }.not_to output(/warning: method redefined; discarding old use_helper/).to_stdout end it 'does not write warnings to stderr' do expect { subject }.not_to output(/warning: method redefined; discarding old use_helper/).to_stderr end end end end nanoc-4.13.3/nanoc-core/spec/nanoc/core/compilation_item_rep_collection_view_spec.rb000066400000000000000000000004201472033334600307130ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'support/item_rep_collection_view_examples' describe Nanoc::Core::CompilationItemRepCollectionView do let(:expected_view_class) { Nanoc::Core::CompilationItemRepView } it_behaves_like 'an item rep collection view' end nanoc-4.13.3/nanoc-core/spec/nanoc/core/compilation_item_rep_view_spec.rb000066400000000000000000000120631472033334600265060ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'support/item_rep_view_examples' describe Nanoc::Core::CompilationItemRepView do let(:expected_item_view_class) { Nanoc::Core::CompilationItemView } let(:config) { Nanoc::Core::Configuration.new(dir: Dir.getwd).with_defaults } let(:empty_layouts) { Nanoc::Core::LayoutCollection.new(config) } let(:empty_items) { Nanoc::Core::ItemCollection.new(config) } let(:base_item) { Nanoc::Core::Item.new('base', {}, '/base.md') } let(:dependency_store) { Nanoc::Core::DependencyStore.new(empty_items, empty_layouts, config) } let(:dependency_tracker) { Nanoc::Core::DependencyTracker.new(dependency_store) } let(:items) { Nanoc::Core::ItemCollection.new(config) } let(:layouts) { Nanoc::Core::LayoutCollection.new(config) } let(:reps) { Nanoc::Core::ItemRepRepo.new } let(:view_context) do Nanoc::Core::ViewContextForCompilation.new( reps: Nanoc::Core::ItemRepRepo.new, items: Nanoc::Core::ItemCollection.new(config), dependency_tracker:, compilation_context:, compiled_content_store:, ) end let(:compilation_context) do Nanoc::Core::CompilationContext.new( action_provider:, reps:, site:, compiled_content_cache:, compiled_content_store:, ) end let(:compiled_content_store) { Nanoc::Core::CompiledContentStore.new } let(:compiled_content_cache) { Nanoc::Core::CompiledContentCache.new(config:) } let(:site) do Nanoc::Core::Site.new( config:, code_snippets: [], data_source: Nanoc::Core::InMemoryDataSource.new(items, layouts), ) end let(:action_provider) do Class.new(Nanoc::Core::ActionProvider) do def self.for(_context) raise NotImplementedError end def initialize; end end.new end before do dependency_tracker.enter(base_item) end it_behaves_like 'an item rep view' describe '#raw_path' do subject { Fiber.new { view.raw_path }.resume } let(:view) { described_class.new(rep, view_context) } let(:rep) do Nanoc::Core::ItemRep.new(item, :default).tap do |ir| ir.raw_paths = { last: [Dir.getwd + '/output/about/index.html'], } end end let(:item) do Nanoc::Core::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 be(true) expect(dep.props.raw_content?).to be(false) expect(dep.props.attributes?).to be(false) expect(dep.props.path?).to be(false) end it { is_expected.to be_a(Nanoc::Core::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::Core::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 be(true) expect(dep.props.raw_content?).to be(false) expect(dep.props.attributes?).to be(false) expect(dep.props.path?).to be(false) end it { is_expected.to 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::Core::ItemRep.new(item, :default).tap do |ir| ir.compiled = true ir.snapshot_defs = [ Nanoc::Core::SnapshotDef.new(:last, binary: false), ] end end let(:item) do Nanoc::Core::Item.new('content', {}, '/asdf.md') end before do compiled_content_store.set(rep, :last, Nanoc::Core::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 be(true) expect(dep.props.raw_content?).to be(false) expect(dep.props.attributes?).to be(false) expect(dep.props.path?).to be(false) end it { is_expected.to eq('Hallo') } end end nanoc-4.13.3/nanoc-core/spec/nanoc/core/compilation_item_view_spec.rb000066400000000000000000000320171472033334600256410ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'support/document_view_examples' describe Nanoc::Core::CompilationItemView do let(:entity_class) { Nanoc::Core::Item } let(:other_view_class) { Nanoc::Core::LayoutView } let(:config) { Nanoc::Core::Configuration.new(dir: Dir.getwd).with_defaults } let(:empty_layouts) { Nanoc::Core::LayoutCollection.new(config) } let(:empty_items) { Nanoc::Core::ItemCollection.new(config) } let(:base_item) { Nanoc::Core::Item.new('base', {}, '/base.md') } let(:dependency_store) { Nanoc::Core::DependencyStore.new(empty_items, empty_layouts, config) } let(:dependency_tracker) { Nanoc::Core::DependencyTracker.new(dependency_store) } let(:items) { Nanoc::Core::ItemCollection.new(config) } let(:layouts) { Nanoc::Core::LayoutCollection.new(config) } let(:reps) { Nanoc::Core::ItemRepRepo.new } let(:view_context) do Nanoc::Core::ViewContextForCompilation.new( reps:, items:, dependency_tracker:, compilation_context:, compiled_content_store:, ) end let(:compilation_context) do Nanoc::Core::CompilationContext.new( action_provider:, reps:, site:, compiled_content_cache:, compiled_content_store:, ) end let(:compiled_content_store) { Nanoc::Core::CompiledContentStore.new } let(:compiled_content_cache) { Nanoc::Core::CompiledContentCache.new(config:) } let(:site) do Nanoc::Core::Site.new( config:, code_snippets: [], data_source: Nanoc::Core::InMemoryDataSource.new(items, layouts), ) end let(:action_provider) do Class.new(Nanoc::Core::ActionProvider) do def self.for(_context) raise NotImplementedError end def initialize; end end.new end before do dependency_tracker.enter(base_item) end it_behaves_like 'a document view' describe '#parent' do subject { view.parent } let(:item) do Nanoc::Core::Item.new('me', {}, identifier) end let(:view) { described_class.new(item, view_context) } let(:items) do Nanoc::Core::ItemCollection.new( {}, [ item, parent_item, ].compact, ) end context 'with parent' do context 'full identifier' do let(:identifier) do Nanoc::Core::Identifier.new('/parent/me.md') end let(:parent_item) do Nanoc::Core::Item.new('parent', {}, '/parent.md') end it 'raises' do expect { subject }.to raise_error(Nanoc::Core::Errors::CannotGetParentOrChildrenOfNonLegacyItem) end end context 'legacy identifier' do let(:identifier) do Nanoc::Core::Identifier.new('/parent/me/', type: :legacy) end let(:parent_item) do Nanoc::Core::Item.new('parent', {}, Nanoc::Core::Identifier.new('/parent/', type: :legacy)) end it 'returns a view for the parent' do expect(subject.class).to eql(described_class) 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::Core::Item.new('parent', {}, parent_identifier) } let(:identifier) { Nanoc::Core::Identifier.new('/me/', type: :legacy) } let(:parent_identifier) { Nanoc::Core::Identifier.new('/', type: :legacy) } it 'returns a view for the parent' do expect(subject.class).to eql(described_class) 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::Core::Identifier.new('/me.md') end it 'raises' do expect { subject }.to raise_error(Nanoc::Core::Errors::CannotGetParentOrChildrenOfNonLegacyItem) end end context 'legacy identifier' do let(:identifier) do Nanoc::Core::Identifier.new('/me/', type: :legacy) end it { is_expected.to be_nil } it { is_expected.to be_frozen } end end end describe '#children' do subject { view.children } let(:item) do Nanoc::Core::Item.new('me', {}, identifier) end let(:view) { described_class.new(item, view_context) } let(:items) do Nanoc::Core::ItemCollection.new( {}, [ item, *children, ], ) end context 'full identifier' do let(:identifier) do Nanoc::Core::Identifier.new('/me.md') end let(:children) do [Nanoc::Core::Item.new('child', {}, '/me/child.md')] end it 'raises' do expect { subject }.to raise_error(Nanoc::Core::Errors::CannotGetParentOrChildrenOfNonLegacyItem) end end context 'legacy identifier' do let(:identifier) do Nanoc::Core::Identifier.new('/me/', type: :legacy) end let(:children) do [Nanoc::Core::Item.new('child', {}, Nanoc::Core::Identifier.new('/me/child/', type: :legacy))] end it 'returns views for the children' do expect(subject.size).to be(1) expect(subject[0].class).to eql(described_class) expect(subject[0]._unwrap).to eql(children[0]) end it { is_expected.to be_frozen } end end describe '#reps' do subject { view.reps } let(:item) { Nanoc::Core::Item.new('blah', {}, '/foo.md') } let(:rep_a) { Nanoc::Core::ItemRep.new(item, :a) } let(:rep_b) { Nanoc::Core::ItemRep.new(item, :b) } let(:reps) do Nanoc::Core::ItemRepRepo.new.tap do |reps| reps << rep_a reps << rep_b end end let(:view) { described_class.new(item, view_context) } it 'returns a proper item rep collection' do expect(subject.size).to eq(2) expect(subject.class).to eql(Nanoc::Core::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::Core::Item.new('content', {}, '/asdf') end let(:reps) do Nanoc::Core::ItemRepRepo.new.tap do |reps| reps << rep end end let(:rep) do Nanoc::Core::ItemRep.new(item, :default).tap do |ir| ir.compiled = true ir.snapshot_defs = [ Nanoc::Core::SnapshotDef.new(:last, binary: false), Nanoc::Core::SnapshotDef.new(:pre, binary: false), Nanoc::Core::SnapshotDef.new(:post, binary: false), Nanoc::Core::SnapshotDef.new(:specific, binary: false), ] end end before do compiled_content_store.set(rep, :last, Nanoc::Core::TextualContent.new('Last Hallo')) compiled_content_store.set(rep, :pre, Nanoc::Core::TextualContent.new('Pre Hallo')) compiled_content_store.set(rep, :post, Nanoc::Core::TextualContent.new('Post Hallo')) compiled_content_store.set(rep, :specific, Nanoc::Core::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::Core::BasicItemRepCollectionView::NoSuchItemRepError) end end end describe '#path' do subject { view.path(**params) } let(:view) { described_class.new(item, view_context) } let(:item) do Nanoc::Core::Item.new('content', {}, '/asdf.md') end let(:reps) do Nanoc::Core::ItemRepRepo.new.tap do |reps| reps << rep end end let(:rep) do Nanoc::Core::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::Core::BasicItemRepCollectionView::NoSuchItemRepError) end end end describe '#binary?' do # TODO: implement end describe '#raw_filename' do subject { view.raw_filename } let(:item) do Nanoc::Core::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::Core::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 be(true) expect(dep.props.attributes?).to be(false) expect(dep.props.compiled_content?).to be(false) expect(dep.props.path?).to be(false) end end context 'textual content with raw filename' do let(:content) { Nanoc::Core::TextualContent.new('asdf', 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 be(true) expect(dep.props.attributes?).to be(false) expect(dep.props.compiled_content?).to be(false) expect(dep.props.path?).to be(false) end end context 'binary content' do let(:content) { Nanoc::Core::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 be(true) expect(dep.props.attributes?).to be(false) expect(dep.props.compiled_content?).to be(false) expect(dep.props.path?).to be(false) end end end describe '#inspect' do subject { view.inspect } let(:item) { Nanoc::Core::Item.new('content', {}, '/asdf') } let(:view) { described_class.new(item, nil) } it { is_expected.to eql('') } end end nanoc-4.13.3/nanoc-core/spec/nanoc/core/compilation_phases/000077500000000000000000000000001472033334600235725ustar00rootroot00000000000000nanoc-4.13.3/nanoc-core/spec/nanoc/core/compilation_phases/abstract_spec.rb000066400000000000000000000053531472033334600267420ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Core::CompilationPhases::Abstract do subject(:phase) do described_class.new(wrapped:) end let(:item) { Nanoc::Core::Item.new('foo', {}, '/stuff.md') } let(:rep) { Nanoc::Core::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:) } 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::Core::NotificationCenter).to receive(:post).with(:phase_started, 'MyTestingPhaseClass', rep).ordered expect(Nanoc::Core::NotificationCenter).to receive(:post).with(:phase_yielded, 'MyTestingPhaseClass', rep).ordered expect(Nanoc::Core::NotificationCenter).to receive(:post).with(:phase_started, 'MyTestingWrappedPhaseClass', rep).ordered expect(Nanoc::Core::NotificationCenter).to receive(:post).with(:phase_ended, 'MyTestingWrappedPhaseClass', rep).ordered expect(Nanoc::Core::NotificationCenter).to receive(:post).with(:phase_resumed, 'MyTestingPhaseClass', rep).ordered expect(Nanoc::Core::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 # rubocop:disable RSpec/NoExpectationExample 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 # rubocop:disable RSpec/NoExpectationExample subject end end end end nanoc-4.13.3/nanoc-core/spec/nanoc/core/compilation_phases/cache_spec.rb000066400000000000000000000121571472033334600262020ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Core::CompilationPhases::Cache do subject(:phase) do described_class.new( compiled_content_cache:, compiled_content_store:, wrapped:, ) end let(:compiled_content_cache) do Nanoc::Core::CompiledContentCache.new(config:) end let(:compiled_content_store) { Nanoc::Core::CompiledContentStore.new } let(:wrapped_class) do Class.new(Nanoc::Core::CompilationPhases::Abstract) do def initialize(compiled_content_store) @compiled_content_store = compiled_content_store end def run(rep, is_outdated:) # rubocop:disable Lint/UnusedMethodArgument @compiled_content_store.set(rep, :last, Nanoc::Core::TextualContent.new('wrapped content')) end end end let(:wrapped) { wrapped_class.new(compiled_content_store) } let(:item) { Nanoc::Core::Item.new('item content', {}, '/donkey.md') } let(:rep) { Nanoc::Core::ItemRep.new(item, :latex) } let(:config) { Nanoc::Core::Configuration.new(dir: Dir.getwd).with_defaults } describe '#run' do subject { phase.call(rep, is_outdated:) } let(:is_outdated) { raise 'override me' } before do rep.snapshot_defs = [Nanoc::Core::SnapshotDef.new(:last, binary: false)] allow(Nanoc::Core::NotificationCenter).to receive(:post).with(:phase_started, anything, anything) allow(Nanoc::Core::NotificationCenter).to receive(:post).with(:phase_yielded, anything, anything) allow(Nanoc::Core::NotificationCenter).to receive(:post).with(:phase_resumed, anything, anything) allow(Nanoc::Core::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:) 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 # rubocop:disable RSpec/NoExpectationExample 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 rep.snapshot_defs = [Nanoc::Core::SnapshotDef.new(:last, binary: false)] compiled_content_cache[rep] = { last: Nanoc::Core::TextualContent.new('cached') } end it 'reads content from cache' do expect(Nanoc::Core::NotificationCenter).to receive(:post).with(:cached_content_used, rep) expect { subject } .to change { compiled_content_store.get(rep, :last) } .from(nil) .to(some_textual_content('cached')) end it 'marks rep as compiled' do expect(Nanoc::Core::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::Core::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!(:temp_dir) do Dir.mktmpdir('nanoc-phase-cache-spec') end let(:binary_content) { 'b1n4ry' } let(:binary_filename) { File.join(temp_dir, 'test') } before do File.write(binary_filename, binary_content) rep.snapshot_defs = [Nanoc::Core::SnapshotDef.new(:last, binary: true)] compiled_content_cache[rep] = { last: Nanoc::Core::BinaryContent.new(binary_filename) } end after do FileUtils.rm_rf(temp_dir) end it 'reads content from cache' do expect(Nanoc::Core::NotificationCenter).to receive(:post).with(:cached_content_used, rep) expect { subject } .to change { compiled_content_store.get(rep, :last) } .from(nil) .to(some_binary_content(binary_content)) end it 'marks rep as compiled' do expect(Nanoc::Core::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::Core::NotificationCenter).to receive(:post).with(:cached_content_used, rep) expect { subject } .not_to change { compiled_content_cache[rep][:last].filename } end end context 'no cached compiled content available' do include_examples 'calls wrapped' end end end end nanoc-4.13.3/nanoc-core/spec/nanoc/core/compilation_phases/resume_spec.rb000066400000000000000000000146141472033334600264370ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Core::CompilationPhases::Resume do subject(:phase) do described_class.new( wrapped:, ) end let(:wrapped_class) do Class.new(Nanoc::Core::CompilationPhases::Abstract) do attr_reader :count def self.to_s 'MyPhaseClass' end def initialize(other_rep) super(wrapped: nil) @other_rep = other_rep @count = 0 end def run(_rep, is_outdated:) # rubocop:disable Lint/UnusedMethodArgument @count += 1 Fiber.yield(Nanoc::Core::Errors::UnmetDependency.new(@other_rep, :last)) @count += 1 Fiber.yield(Nanoc::Core::Errors::UnmetDependency.new(@other_rep, :last)) @count += 1 end end end let(:wrapped) { wrapped_class.new(other_rep) } let(:item) { Nanoc::Core::Item.new('item content', {}, '/donkey.md') } let(:rep) { Nanoc::Core::ItemRep.new(item, :latex) } let(:other_item) { Nanoc::Core::Item.new('other item content', {}, '/other.md') } let(:other_rep) { Nanoc::Core::ItemRep.new(other_item, :latex) } let(:config) { Nanoc::Core::Configuration.new(dir: Dir.getwd).with_defaults } describe '#call' do context 'one run' do subject do phase.call(rep, is_outdated: true) end it 'delegates to wrapped' do expect { subject rescue nil }.to change(wrapped, :count).from(0).to(1) end it 'raises' do expect { subject }.to raise_error(Nanoc::Core::Errors::UnmetDependency) end it 'posts correct notifications' do msgs = [] Nanoc::Core::NotificationCenter.on(:compilation_suspended, self) { msgs << :compilation_suspended } subject rescue nil Nanoc::Core::NotificationCenter.sync expect(msgs).to eq([:compilation_suspended]) ensure Nanoc::Core::NotificationCenter.remove(:compilation_suspended, self) end context 'wrapped in Notify' do let(:phase) do Nanoc::Core::CompilationPhases::Notify.new(wrapped: super()) end it 'posts correct notifications' do msgs = [] Nanoc::Core::NotificationCenter.on(:compilation_started, self) { msgs << :compilation_started } Nanoc::Core::NotificationCenter.on(:compilation_suspended, self) { msgs << :compilation_suspended } Nanoc::Core::NotificationCenter.on(:compilation_ended, self) { msgs << :compilation_ended } subject rescue nil Nanoc::Core::NotificationCenter.sync expect(msgs).to eq(%i[compilation_started compilation_suspended]) ensure Nanoc::Core::NotificationCenter.remove(:compilation_ended, self) Nanoc::Core::NotificationCenter.remove(:compilation_suspended, self) Nanoc::Core::NotificationCenter.remove(:compilation_started, self) end end end context 'two runs' do subject do phase.call(rep, is_outdated: true) rescue nil phase.call(rep, is_outdated: true) end it 'delegates to wrapped' do expect { subject rescue nil }.to change(wrapped, :count).from(0).to(2) end it 'raises' do expect { subject }.to raise_error(Nanoc::Core::Errors::UnmetDependency) end it 'posts correct notifications' do msgs = [] Nanoc::Core::NotificationCenter.on(:compilation_suspended, self) { msgs << :compilation_suspended } subject rescue nil Nanoc::Core::NotificationCenter.sync expect(msgs).to eq(%i[compilation_suspended compilation_suspended]) ensure Nanoc::Core::NotificationCenter.remove(:compilation_suspended, self) end context 'wrapped in Notify' do let(:phase) do Nanoc::Core::CompilationPhases::Notify.new(wrapped: super()) end it 'posts correct notifications' do msgs = [] Nanoc::Core::NotificationCenter.on(:compilation_started, self) { msgs << :compilation_started } Nanoc::Core::NotificationCenter.on(:compilation_suspended, self) { msgs << :compilation_suspended } Nanoc::Core::NotificationCenter.on(:compilation_ended, self) { msgs << :compilation_ended } subject rescue nil Nanoc::Core::NotificationCenter.sync expect(msgs).to eq(%i[compilation_started compilation_suspended compilation_started compilation_suspended]) ensure Nanoc::Core::NotificationCenter.remove(:compilation_ended, self) Nanoc::Core::NotificationCenter.remove(:compilation_suspended, self) Nanoc::Core::NotificationCenter.remove(:compilation_started, self) end end end context 'three runs' do subject do phase.call(rep, is_outdated: true) rescue nil phase.call(rep, is_outdated: true) rescue nil phase.call(rep, is_outdated: true) end it 'delegates to wrapped' do expect { subject }.to change(wrapped, :count).from(0).to(3) end it 'does not raise' do expect { subject }.not_to raise_error end it 'posts correct notifications' do msgs = [] Nanoc::Core::NotificationCenter.on(:compilation_suspended, self) { msgs << :compilation_suspended } subject Nanoc::Core::NotificationCenter.sync expect(msgs).to eq(%i[compilation_suspended compilation_suspended]) ensure Nanoc::Core::NotificationCenter.remove(:compilation_suspended, self) end context 'wrapped in Notify' do let(:phase) do Nanoc::Core::CompilationPhases::Notify.new(wrapped: super()) end it 'posts correct notifications' do msgs = [] Nanoc::Core::NotificationCenter.on(:compilation_started, self) { msgs << :compilation_started } Nanoc::Core::NotificationCenter.on(:compilation_suspended, self) { msgs << :compilation_suspended } Nanoc::Core::NotificationCenter.on(:compilation_ended, self) { msgs << :compilation_ended } subject Nanoc::Core::NotificationCenter.sync expect(msgs).to eq(%i[compilation_started compilation_suspended compilation_started compilation_suspended compilation_started compilation_ended]) ensure Nanoc::Core::NotificationCenter.remove(:compilation_ended, self) Nanoc::Core::NotificationCenter.remove(:compilation_suspended, self) Nanoc::Core::NotificationCenter.remove(:compilation_started, self) end end end end end nanoc-4.13.3/nanoc-core/spec/nanoc/core/compilation_stage_spec.rb000066400000000000000000000047171472033334600247620ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Core::CompilationStage do subject(:stage) { klass.new } let(:klass) { described_class } before { Timecop.freeze(Time.local(2008, 9, 1, 10, 5, 0)) } after { Timecop.return } around do |ex| Nanoc::Core::Instrumentor.enable { ex.run } end describe '#call' do subject { stage.call } it 'raises error' do expect { subject }.to raise_error(NotImplementedError) end context 'actual implementation' do before do a = Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond) # Go to a few seconds in the future allow(Process) .to receive(:clock_gettime) .with(Process::CLOCK_MONOTONIC, :nanosecond) .and_return(a, a + 13_570_000_000) end let(:klass) do Class.new(described_class) do def self.to_s 'My::Klazz' end def run :i_like_donkeys end end end it 'sends timing notification' do expect { subject } .to send_notification(:stage_ran, 13.57, klass) end it 'sends stage_started' do expect { subject } .to send_notification(:stage_started, 'Klazz') end it 'sends stage_ended' do expect { subject } .to send_notification(:stage_ended, 'Klazz') end it 'does not send stage_aborted' do expect { subject } .not_to send_notification(:stage_aborted, 'Klazz') end it 'returns what #run returns' do expect(subject).to be :i_like_donkeys end context 'erroring' do let(:klass) do Class.new(described_class) do def self.to_s 'My::Klazz' end def run raise 'boom' end end end it 'sends timing notification' do expect { subject rescue nil } .to send_notification(:stage_ran, 13.57, klass) end it 'sends stage_started' do expect { subject rescue nil } .to send_notification(:stage_started, 'Klazz') end it 'does not send stage_ended' do expect { subject rescue nil } .not_to send_notification(:stage_ended, 'Klazz') end it 'sends stage_aborted' do expect { subject rescue nil } .to send_notification(:stage_aborted, 'Klazz') end end end end end nanoc-4.13.3/nanoc-core/spec/nanoc/core/compilation_stages/000077500000000000000000000000001472033334600235755ustar00rootroot00000000000000nanoc-4.13.3/nanoc-core/spec/nanoc/core/compilation_stages/calculate_checksums_spec.rb000066400000000000000000000036561472033334600311500ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Core::CompilationStages::CalculateChecksums do let(:stage) do described_class.new(items:, layouts:, code_snippets:, config:) end let(:config) do Nanoc::Core::Configuration.new(dir: Dir.getwd).with_defaults end let(:code_snippets) do [code_snippet] end let(:items) do Nanoc::Core::ItemCollection.new(config, [item]) end let(:layouts) do Nanoc::Core::LayoutCollection.new(config, [layout]) end let(:code_snippet) do Nanoc::Core::CodeSnippet.new('woof!', 'dog.rb') end let(:item) do Nanoc::Core::Item.new('hello there', {}, '/hi.md') end let(:layout) do Nanoc::Core::Layout.new('t3mpl4t3', {}, '/page.erb') end describe '#run' do subject { stage.run } it 'checksums items' do expect(subject.checksum_for(item)) .to eq(Nanoc::Core::Checksummer.calc(item)) expect(subject.content_checksum_for(item)) .to eq(Nanoc::Core::Checksummer.calc_for_content_of(item)) expect(subject.attributes_checksum_for(item)) .to eq(Nanoc::Core::Checksummer.calc_for_each_attribute_of(item)) end it 'checksums layouts' do expect(subject.checksum_for(layout)) .to eq(Nanoc::Core::Checksummer.calc(layout)) expect(subject.content_checksum_for(layout)) .to eq(Nanoc::Core::Checksummer.calc_for_content_of(layout)) expect(subject.attributes_checksum_for(layout)) .to eq(Nanoc::Core::Checksummer.calc_for_each_attribute_of(layout)) end it 'checksums config' do expect(subject.checksum_for(config)) .to eq(Nanoc::Core::Checksummer.calc(config)) expect(subject.attributes_checksum_for(config)) .to eq(Nanoc::Core::Checksummer.calc_for_each_attribute_of(config)) end it 'checksums code snippets' do expect(subject.checksum_for(code_snippet)) .to eq(Nanoc::Core::Checksummer.calc(code_snippet)) end end end nanoc-4.13.3/nanoc-core/spec/nanoc/core/compilation_stages/cleanup_spec.rb000066400000000000000000000051721472033334600265700ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Core::CompilationStages::Cleanup do let(:stage) { described_class.new(config.output_dirs) } let(:config) do Nanoc::Core::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::Core::TempFilenameFactory.instance.create(Nanoc::Core::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::Core::TempFilenameFactory.instance.create(Nanoc::Core::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.13.3/nanoc-core/spec/nanoc/core/compilation_stages/compile_reps_spec.rb000066400000000000000000000146351472033334600276260ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Core::CompilationStages::CompileReps do Class.new(Nanoc::Core::Filter) do identifier :simple_erb_vcn3np2ayqmv6ayqp8su2crbusonmgwh def run(content, _params = {}) context = Nanoc::Core::Context.new(assigns) ERB.new(content).result(context.get_binding) end end let(:stage) do described_class.new( reps:, outdatedness_store:, dependency_store:, action_sequences:, compilation_context:, compiled_content_cache:, focus:, ) end let(:focus) { nil } let(:compilation_context) do Nanoc::Core::CompilationContext.new( action_provider:, reps:, site:, compiled_content_cache:, compiled_content_store:, ) end let(:action_provider) do Class.new(Nanoc::Core::ActionProvider) do def self.for(_context) raise NotImplementedError end def initialize; end end.new end let(:action_sequences) { double(:action_sequences) } let(:reps) { Nanoc::Core::ItemRepRepo.new } let(:compiled_content_cache) { Nanoc::Core::CompiledContentCache.new(config:) } let(:compiled_content_store) { Nanoc::Core::CompiledContentStore.new } let(:outdatedness_store) { Nanoc::Core::OutdatednessStore.new(config:) } let(:dependency_store) { Nanoc::Core::DependencyStore.new(items, layouts, config) } let(:rep) { Nanoc::Core::ItemRep.new(item, :default) } let(:item) { Nanoc::Core::Item.new('<%= 1 + 2 %>', {}, '/hi.md') } let(:other_rep) { Nanoc::Core::ItemRep.new(other_item, :default) } let(:other_item) { Nanoc::Core::Item.new('other content', {}, '/other.md') } let(:site) do Nanoc::Core::Site.new( config:, code_snippets:, data_source: Nanoc::Core::InMemoryDataSource.new(items, layouts), ) end let(:config) { Nanoc::Core::Configuration.new(dir: Dir.getwd).with_defaults } let(:code_snippets) { [] } let(:layouts) do Nanoc::Core::LayoutCollection.new(config) end let(:items) do Nanoc::Core::ItemCollection.new( config, [item, other_item], ) end let(:memory) do actions = [ Nanoc::Core::ProcessingActions::Filter.new(:simple_erb_vcn3np2ayqmv6ayqp8su2crbusonmgwh, {}), Nanoc::Core::ProcessingActions::Snapshot.new([:last], []), ] Nanoc::Core::ActionSequence.new(actions:) end before do reps << rep reps << other_rep reps.each do |rep| rep.snapshot_defs << Nanoc::Core::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::Core::SnapshotDef.new(:last, binary: false)] end let(:snapshot_defs_for_other_rep) do [Nanoc::Core::SnapshotDef.new(:last, binary: false)] end context 'rep not in outdatedness store' do before do # Needed for consistency compiled_content_cache[rep] = { last: Nanoc::Core::TextualContent.new('asdf') } compiled_content_cache[other_rep] = { last: Nanoc::Core::TextualContent.new('asdf') } end it 'keeps the item rep out of the outdatedness store' do expect(outdatedness_store.include?(rep)).to be(false) expect { subject }.not_to change { outdatedness_store.include?(rep) } end end context 'rep in outdatedness store' do before do outdatedness_store.add(rep) # Needed for consistency compiled_content_cache[other_rep] = { last: Nanoc::Core::TextualContent.new('asdf') } end context 'when focus is not specified' do let(:focus) { nil } it 'compiles individual reps' do expect { subject }.to change { compiled_content_store.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 end context 'when in focus with one entry' do let(:focus) { ['/hi.*'] } it 'compiles individual reps' do expect { subject }.to change { compiled_content_store.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 end context 'when in focus with multiple entries' do let(:focus) { ['/hi.*', '/unrelated.*'] } it 'compiles individual reps' do expect { subject }.to change { compiled_content_store.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 end context 'when not in focus' do let(:focus) { ['/other.*'] } it 'does not compile individual reps' do expect { subject }.not_to change { compiled_content_store.get(rep, :last) } .from(nil) end it 'does not remove the item rep from the outdatedness store' do expect { subject }.not_to change { outdatedness_store.include?(rep) }.from(true) end end context 'exception' do let(:item) { Nanoc::Core::Item.new('<%= \'invalid_ruby %>', {}, '/hi.md') } it 'wraps exception' do expect { subject }.to raise_error(Nanoc::Core::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('(erb):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(true) expect { subject rescue nil }.not_to change { outdatedness_store.include?(rep) } end end end end end nanoc-4.13.3/nanoc-core/spec/nanoc/core/compilation_stages/determine_outdatedness_spec.rb000066400000000000000000000070321472033334600316740ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Core::CompilationStages::DetermineOutdatedness do let(:stage) do described_class.new( reps:, outdatedness_checker:, outdatedness_store:, ) end let(:reps) do Nanoc::Core::ItemRepRepo.new end let(:outdatedness_checker) do double(:outdatedness_checker) end let(:outdatedness_store) do Nanoc::Core::OutdatednessStore.new(config:) end let(:config) { Nanoc::Core::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::Core::Item.new('', {}, '/hi.md') end let(:rep) do Nanoc::Core::ItemRep.new(item, :woof) end let(:other_rep) do Nanoc::Core::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(:outdatedness_reasons_for).with(rep).and_return([double]) expect(outdatedness_checker) .to receive(:outdatedness_reasons_for).with(other_rep).and_return([]) 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::Core::Item.new('', {}, '/hi.md') end let(:rep) do Nanoc::Core::ItemRep.new(item, :woof) end let(:other_rep) do Nanoc::Core::ItemRep.new(item, :bark) end before do reps << rep reps << other_rep outdatedness_store.add(rep) expect(outdatedness_checker) .to receive(:outdatedness_reasons_for).with(other_rep).and_return([]) 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::Core::Item.new('', {}, '/hi.md') end let(:unknown_rep) do Nanoc::Core::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.13.3/nanoc-core/spec/nanoc/core/compilation_stages/preprocess_spec.rb000066400000000000000000000057351472033334600273330ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Core::CompilationStages::Preprocess do let(:stage) do described_class.new( action_provider:, site:, dependency_store:, checksum_store:, ) end let(:action_provider) do Class.new(Nanoc::Core::ActionProvider) do def self.for(_context) raise NotImplementedError end def initialize; end end.new end let(:site) do Nanoc::Core::Site.new( config:, code_snippets: [], data_source:, ) end let(:data_source) { Nanoc::Core::InMemoryDataSource.new(items, layouts) } let(:config) { Nanoc::Core::Configuration.new(dir: Dir.getwd).with_defaults } let(:items) { Nanoc::Core::ItemCollection.new(config) } let(:layouts) { Nanoc::Core::LayoutCollection.new(config) } let(:dependency_store) do Nanoc::Core::DependencyStore.new(items, layouts, config) end let(:checksum_store) do Nanoc::Core::ChecksumStore.new(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 # rubocop:disable RSpec/NoExpectationExample subject end end context 'preprocessing needed' do let(:new_item) { Nanoc::Core::Item.new('new item', {}, '/new.md') } let(:new_layout) { Nanoc::Core::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::Core::InMemoryDataSource.new( Nanoc::Core::ItemCollection.new(config, [new_item]), Nanoc::Core::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(contain_exactly(new_item, new_layout, config)) end end end end nanoc-4.13.3/nanoc-core/spec/nanoc/core/compiled_content_cache_spec.rb000066400000000000000000000105031472033334600257200ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Core::CompiledContentCache do let(:cache) { described_class.new(config:) } let(:items) { [item] } let(:item) { Nanoc::Core::Item.new('asdf', {}, '/foo.md') } let(:item_rep) { Nanoc::Core::ItemRep.new(item, :default) } let(:other_item) { Nanoc::Core::Item.new('asdf', {}, '/sneaky.md') } let(:other_item_rep) { Nanoc::Core::ItemRep.new(other_item, :default) } let(:textual_content) do Nanoc::Core::Content.create('text') end let(:binary_content) do Nanoc::Core::Content.create(File.join(Dir.getwd, 'bin.dat'), binary: true).tap do |c| File.write(c.filename, 'b1n4ry') end end let(:config) { Nanoc::Core::Configuration.new(dir: Dir.getwd).with_defaults } it 'has no content by default' do expect(cache[item_rep]).to be_nil end shared_examples 'properly-functioning compiled content cache' do context 'setting only textual content' do before do cache[target_item_rep] = { aaa: textual_content, } end it 'has content' do expect(cache[target_item_rep][:aaa].string).to eql(textual_content.string) end context 'after storing and loading' do before do cache.store cache.load end it 'has content' do expect(cache[target_item_rep][:aaa].string).to eql(textual_content.string) end end context 'after pruning all items' do before do cache.prune(items: []) end it 'has no content' do expect(cache[target_item_rep]).to be_nil end end context 'after pruning no items' do before do cache.prune(items: [target_item_rep.item]) end it 'has no content' do expect(cache[target_item_rep]).not_to be_nil end end end context 'setting textual and binary content' do before do cache[target_item_rep] = { aaa: textual_content, bbb: binary_content, } end it 'has content' do expect(cache[target_item_rep][:aaa].string).to eql(textual_content.string) expect(File.read(cache[target_item_rep][:bbb].filename)) .to eql('b1n4ry') end context 'after storing and loading' do before do cache.store cache.load end it 'has content' do expect(cache[target_item_rep][:aaa].string).to eql(textual_content.string) expect(File.read(cache[target_item_rep][:bbb].filename)) .to eql('b1n4ry') end end context 'after pruning all items' do before do cache.prune(items: []) end it 'has no content' do expect(cache[target_item_rep]).to be_nil end end context 'after pruning no items' do before do cache.prune(items: [target_item_rep.item]) end it 'has no content' do expect(cache[target_item_rep]).not_to be_nil end end end context 'setting only binary content' do before do cache[target_item_rep] = { bbb: binary_content, } end it 'has content' do expect(File.read(cache[target_item_rep][:bbb].filename)) .to eql('b1n4ry') end context 'after storing and loading' do before do cache.store cache.load end it 'has content' do expect(File.read(cache[target_item_rep][:bbb].filename)) .to eql('b1n4ry') end end context 'after pruning all items' do before do cache.prune(items: []) end it 'has no content' do expect(cache[target_item_rep]).to be_nil end end context 'after pruning no items' do before do cache.prune(items: [target_item_rep.item]) end it 'has no content' do expect(cache[target_item_rep]).not_to be_nil end end end end context 'setting content on known item' do let(:target_item_rep) { item_rep } include_examples 'properly-functioning compiled content cache' end context 'setting content on unknown item' do let(:target_item_rep) { other_item_rep } include_examples 'properly-functioning compiled content cache' end end nanoc-4.13.3/nanoc-core/spec/nanoc/core/compiled_content_store_spec.rb000066400000000000000000000154471472033334600260250ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Core::CompiledContentStore do subject(:repo) { described_class.new } describe '#get' do subject { repo.get(rep, snapshot_name) } let(:item) { Nanoc::Core::Item.new('contentz', {}, '/foo.md') } let(:rep) { Nanoc::Core::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::Core::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::Core::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::Core::Item.new('contentz', {}, '/foo.md') } let(:rep) { Nanoc::Core::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::Core::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::Core::Item.new('contentz', {}, '/foo.md') } let(:rep) { Nanoc::Core::ItemRep.new(item, :foo) } let(:snapshot_name) { :donkey } let(:contents) { Nanoc::Core::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::Core::Item.new('contentz', {}, '/foo2.md') } let(:other_rep) { Nanoc::Core::ItemRep.new(other_item, :foo) } let(:item) { Nanoc::Core::Item.new('contentz', {}, '/foo.md') } let(:rep) { Nanoc::Core::ItemRep.new(item, :foo) } let(:contents_by_snapshot) { { donkey: Nanoc::Core::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 let(:item) { Nanoc::Core::Item.new('contentz', {}, '/foo.md') } let(:rep) { Nanoc::Core::ItemRep.new(item, :foo) } shared_examples 'a snapshot' do context 'no snapshot def' do it 'raises' do expect { subject }.to raise_error(Nanoc::Core::Errors::NoSuchSnapshot) end end context 'snapshot def exists' do context 'content is missing' do before do rep.snapshot_defs = [Nanoc::Core::SnapshotDef.new(expected_snapshot_name, binary: false)] repo.set_all(rep, {}) end it 'errors' do expect { subject }.to yield_from_fiber(an_instance_of(Nanoc::Core::Errors::UnmetDependency)) end end context 'content is present' do before do rep.snapshot_defs = [Nanoc::Core::SnapshotDef.new(expected_snapshot_name, binary: false)] repo.set_all(rep, expected_snapshot_name => content) end context 'content is textual' do let(:content) { Nanoc::Core::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::Core::BinaryContent.new(File.expand_path('donkey.dat')) } it 'raises' do expect { subject }.to raise_error(Nanoc::Core::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 nil' do subject { repo.compiled_content(rep:, snapshot: nil) } let(:expected_snapshot_name) { :last } include_examples 'a snapshot' context 'when :pre and :last snapshot definitions exist' do before do rep.snapshot_defs = [ Nanoc::Core::SnapshotDef.new(:pre, binary: false), Nanoc::Core::SnapshotDef.new(:last, binary: false), ] end context 'when :last, but no :pre content is available' do before do content = Nanoc::Core::TextualContent.new('hellos') repo.set_all(rep, last: content) end it 'does not use :last' do expect { subject }.to yield_from_fiber(an_instance_of(Nanoc::Core::Errors::UnmetDependency)) end end end end context 'snapshot not specified' do subject { repo.compiled_content(rep:) } let(:expected_snapshot_name) { :last } include_examples 'a snapshot' context 'when :pre and :last snapshot definitions exist' do before do rep.snapshot_defs = [ Nanoc::Core::SnapshotDef.new(:pre, binary: false), Nanoc::Core::SnapshotDef.new(:last, binary: false), ] end context 'when :last, but no :pre content is available' do before do content = Nanoc::Core::TextualContent.new('hellos') repo.set_all(rep, last: content) end it 'does not use :last' do expect { subject }.to yield_from_fiber(an_instance_of(Nanoc::Core::Errors::UnmetDependency)) end end end end context 'snapshot :pre specified' do subject { repo.compiled_content(rep:, snapshot: :pre) } let(:expected_snapshot_name) { :pre } include_examples 'a snapshot' end context 'snapshot :post specified' do subject { repo.compiled_content(rep:, snapshot: :post) } let(:expected_snapshot_name) { :post } include_examples 'a snapshot' end context 'snapshot :last specified' do subject { repo.compiled_content(rep:, snapshot: :last) } let(:expected_snapshot_name) { :last } include_examples 'a snapshot' end context 'snapshot :donkey specified' do subject { repo.compiled_content(rep:, snapshot: :donkey) } let(:expected_snapshot_name) { :donkey } include_examples 'a snapshot' end end end nanoc-4.13.3/nanoc-core/spec/nanoc/core/compiler_spec.rb000066400000000000000000000137361472033334600230740ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Core::Compiler do Class.new(Nanoc::Core::Filter) do identifier :simple_erb_ob3rqra0yc def run(content, _params = {}) context = Nanoc::Core::Context.new(assigns) ERB.new(content).result(context.get_binding) end end let(:compiler) do described_class.new( site, compiled_content_cache:, checksum_store:, action_sequence_store:, action_provider:, dependency_store:, outdatedness_store:, focus: nil, ) end let(:checksum_store) { Nanoc::Core::ChecksumStore.new(config:, objects: items) } let(:action_sequence_store) { Nanoc::Core::ActionSequenceStore.new(config:) } let(:dependency_store) { Nanoc::Core::DependencyStore.new(items, layouts, config) } let(:outdatedness_store) { Nanoc::Core::OutdatednessStore.new(config:) } let(:action_provider) do Class.new(Nanoc::Core::ActionProvider) do def self.for(_context) raise NotImplementedError end def initialize; end end.new end let(:compiled_content_cache) do Nanoc::Core::CompiledContentCache.new(config:) end let(:rep) { Nanoc::Core::ItemRep.new(item, :default) } let(:item) { Nanoc::Core::Item.new('<%= 1 + 2 %>', {}, '/hi.md') } let(:other_rep) { Nanoc::Core::ItemRep.new(other_item, :default) } let(:other_item) { Nanoc::Core::Item.new('other content', {}, '/other.md') } let(:site) do Nanoc::Core::Site.new( config:, code_snippets:, data_source: Nanoc::Core::InMemoryDataSource.new(items, layouts), ) end let(:config) { Nanoc::Core::Configuration.new(dir: Dir.getwd).with_defaults } let(:code_snippets) { [] } let(:items) do Nanoc::Core::ItemCollection.new(config, [item, other_item]) end let(:layouts) do Nanoc::Core::LayoutCollection.new(config) end let(:memory) do actions = [ Nanoc::Core::ProcessingActions::Filter.new(:simple_erb_ob3rqra0yc, {}), Nanoc::Core::ProcessingActions::Snapshot.new([:last], []), ] Nanoc::Core::ActionSequence.new(actions:) end let(:action_sequences) do { rep => memory, other_rep => memory } end before do allow(Nanoc::Core::NotificationCenter).to receive(:post) end describe '#compile_rep' do subject { stage.send(:compile_rep, rep, phase_stack:, is_outdated:) } let(:stage) { compiler.send(:compile_reps_stage, action_sequences, reps) } let(:is_outdated) { true } let(:phase_stack) { stage.send(:build_phase_stack) } let(:reps) do Nanoc::Core::ItemRepRepo.new.tap do |rs| rs << rep rs << other_rep rs.each do |rep| rep.snapshot_defs << Nanoc::Core::SnapshotDef.new(:last, binary: false) end end end it 'generates expected output' do reps = Nanoc::Core::ItemRepRepo.new expect { subject } .to change { compiler.compilation_context(reps:).compiled_content_store.get_current(rep) } .from(nil) .to(some_textual_content('3')) end it 'generates notifications in the proper order' do expect(Nanoc::Core::NotificationCenter).to receive(:post).with(:compilation_started, rep).ordered expect(Nanoc::Core::NotificationCenter).to receive(:post).with(:filtering_started, rep, :simple_erb_ob3rqra0yc).ordered expect(Nanoc::Core::NotificationCenter).to receive(:post).with(:filtering_ended, rep, :simple_erb_ob3rqra0yc).ordered expect(Nanoc::Core::NotificationCenter).to receive(:post).with(:compilation_ended, rep).ordered subject end context 'interrupted compilation' do let(:item) { Nanoc::Core::Item.new('other=<%= @items["/other.*"].compiled_content %>', {}, '/hi.md') } it 'generates expected output' do reps = Nanoc::Core::ItemRepRepo.new expect(compiler.compilation_context(reps:).compiled_content_store.get_current(rep)).to be_nil expect { stage.send(:compile_rep, rep, phase_stack:, is_outdated: true) } .to raise_error(Nanoc::Core::Errors::UnmetDependency) stage.send(:compile_rep, other_rep, phase_stack:, is_outdated: true) stage.send(:compile_rep, rep, phase_stack:, is_outdated: true) expect(compiler.compilation_context(reps:).compiled_content_store.get_current(rep).string).to eql('other=other content') end it 'generates notifications in the proper order' do # rep 1 expect(Nanoc::Core::NotificationCenter).to receive(:post).with(:compilation_started, rep).ordered expect(Nanoc::Core::NotificationCenter).to receive(:post).with(:filtering_started, rep, :simple_erb_ob3rqra0yc).ordered expect(Nanoc::Core::NotificationCenter).to receive(:post).with(:dependency_created, item, other_item).ordered expect(Nanoc::Core::NotificationCenter).to receive(:post).with(:compilation_suspended, rep, anything, anything).ordered # rep 2 expect(Nanoc::Core::NotificationCenter).to receive(:post).with(:compilation_started, other_rep).ordered expect(Nanoc::Core::NotificationCenter).to receive(:post).with(:filtering_started, other_rep, :simple_erb_ob3rqra0yc).ordered expect(Nanoc::Core::NotificationCenter).to receive(:post).with(:filtering_ended, other_rep, :simple_erb_ob3rqra0yc).ordered expect(Nanoc::Core::NotificationCenter).to receive(:post).with(:compilation_ended, other_rep).ordered # rep 1 (again) expect(Nanoc::Core::NotificationCenter).to receive(:post).with(:compilation_started, rep).ordered expect(Nanoc::Core::NotificationCenter).to receive(:post).with(:filtering_ended, rep, :simple_erb_ob3rqra0yc).ordered expect(Nanoc::Core::NotificationCenter).to receive(:post).with(:compilation_ended, rep).ordered expect { stage.send(:compile_rep, rep, phase_stack:, is_outdated: true) } .to raise_error(Nanoc::Core::Errors::UnmetDependency) stage.send(:compile_rep, other_rep, phase_stack:, is_outdated: true) stage.send(:compile_rep, rep, phase_stack:, is_outdated: true) end end end end nanoc-4.13.3/nanoc-core/spec/nanoc/core/config_loader_spec.rb000066400000000000000000000151761472033334600240550ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Core::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::Core::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::Core::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::Core::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)) 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::Core::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 be(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 be(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 be(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::Core::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::Core::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::Core::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::Core::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::Core::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::Core::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::Core::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 be(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.13.3/nanoc-core/spec/nanoc/core/config_view_spec.rb000066400000000000000000000115061472033334600235520ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Core::ConfigView do let(:config) do Nanoc::Core::Configuration.new(dir: Dir.getwd, hash:) end let(:hash) { { output_dir: 'ootpoot/', amount: 9000, animal: 'donkey', foo: { bar: :baz } } } let(:view) { described_class.new(config, view_context) } let(:view_context) do Nanoc::Core::ViewContextForCompilation.new( reps: Nanoc::Core::ItemRepRepo.new, items: Nanoc::Core::ItemCollection.new(config), dependency_tracker:, compilation_context:, compiled_content_store:, ) end let(:dependency_tracker) { Nanoc::Core::DependencyTracker::Null.new } let(:compilation_context) do Nanoc::Core::CompilationContext.new( action_provider:, reps:, site:, compiled_content_cache:, compiled_content_store:, ) end let(:compiled_content_store) { Nanoc::Core::CompiledContentStore.new } let(:compiled_content_cache) { Nanoc::Core::CompiledContentCache.new(config:) } let(:site) do Nanoc::Core::Site.new( config:, code_snippets: [], data_source: Nanoc::Core::InMemoryDataSource.new(items, layouts), ) end let(:items) { Nanoc::Core::ItemCollection.new(config) } let(:layouts) { Nanoc::Core::LayoutCollection.new(config) } let(:reps) { Nanoc::Core::ItemRepRepo.new } let(:action_provider) do Class.new(Nanoc::Core::ActionProvider) do def self.for(_context) raise NotImplementedError end def initialize; end end.new end 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 be_nil } end end describe '#fetch' do before do expect(dependency_tracker).to receive(:bounce).with(config, attributes: [key]) end context 'with existing key' do subject { view.fetch(key) } let(:key) { :animal } 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' } } # rubocop:disable Style/RedundantFetchBlock 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 be(true) } end context 'with non-existing key' do let(:key) { :weapon } it { is_expected.to be(false) } end end describe '#env_name' do subject { view.env_name } before do expect(dependency_tracker).to receive(:bounce).with(config, attributes: true) end context 'when configuration is constructed with an env_name' do let(:config) do Nanoc::Core::Configuration.new(dir: Dir.getwd, hash:, env_name: 'produx10n') end it { is_expected.to eql('produx10n') } end context 'when configuration is not constructed with an env_name' do let(:config) do Nanoc::Core::Configuration.new(dir: Dir.getwd, hash:) end it { is_expected.to be_nil } 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([[:output_dir, 'ootpoot/'], [: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 be(: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.13.3/nanoc-core/spec/nanoc/core/configuration_spec.rb000066400000000000000000000434651472033334600241330ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Core::Configuration do let(:hash) { { foo: 'bar' } } let(:config) { described_class.new(hash:, dir: Dir.getwd) } describe '#key?' do subject { config.key?(key) } context 'non-existent key' do let(:key) { :donkey } it { is_expected.to be(false) } end context 'existent key' do let(:key) { :foo } it { is_expected.to be(true) } 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:, 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 '#dig' do subject { config.dig(:foo, :bar, :baz) } let(:hash) do { foo: { bar: { baz: 1 } } } end let(:config) { described_class.new(hash:, dir: Dir.getwd) } it 'works like Hash#dig' do expect(subject).to eq(1) end end describe '#fetch' do let(:hash) { { foo: 123 } } let(:config) { described_class.new(hash:, dir: Dir.getwd) } context 'key exists' do subject { config.fetch(:foo) } it { is_expected.to eq(123) } end context 'key does not exist, and called without fallback nor block' do subject { config.fetch(:bar) } it 'raises KeyError' do expect { subject }.to raise_error(KeyError) end end context 'key does not exist, and called with fallback' do subject { config.fetch(:bar, 1000) } it { is_expected.to eq(1000) } end context 'key does not exist, and called with block' do subject { config.fetch(:bar) { 2000 } } # rubocop:disable Style/RedundantFetchBlock it { is_expected.to eq(2000) } end end describe '#[]' do let(:hash) { { foo: 123 } } let(:config) { described_class.new(hash:, dir: Dir.getwd) } context 'key exists' do subject { config[:foo] } it { is_expected.to eq(123) } end context 'key does not exist' do subject { config[:bar] } it { is_expected.to be_nil } end end describe '#[]=' do subject { config[:foo] = 234 } let(:hash) { { foo: 123 } } let(:config) { described_class.new(hash:, dir: Dir.getwd) } it 'modifies' do expect { subject } .to change { config[:foo] } .from(123) .to(234) end end describe '#attributes' do subject { config.attributes } let(:hash) { { foo: 123 } } let(:config) { described_class.new(hash:, dir: Dir.getwd) } it 'returns itself as a hash' do expect(subject).to eq(foo: 123) end end describe '#without' do subject { config.without(:foo) } let(:hash) { { foo: 123, bar: 234 } } let(:config) { described_class.new(hash:, dir: Dir.getwd) } it 'returns a new config' do expect(subject).to be_a(described_class) end it 'removes only the requested key' do expect(config.key?(:foo)).to be(true) expect(subject.key?(:foo)).to be(false) end it 'retains dir' do expect(subject.dir).to eq(config.dir) end it 'retains env_name' do expect(subject.env_name).to eq(config.env_name) end end describe '#update' do subject { config.update(other_hash) } let(:hash) { { foo: 100, bar: 200 } } let(:config) { described_class.new(hash:, dir: Dir.getwd) } let(:other_hash) { { bar: 300, qux: 400 } } it 'retains :foo' do expect { subject } .not_to change { config[:foo] } .from(100) end it 'updates :bar' do expect { subject } .to change { config[:bar] } .from(200) .to(300) end it 'adds :qux' do expect { subject } .to change { config[:qux] } .from(nil) .to(400) end end describe '#freeze' do subject { config.freeze } let(:hash) { { foo: { bar: 100 } } } let(:config) { described_class.new(hash:, dir: Dir.getwd) } it 'freezes' do expect { subject } .to change(config, :frozen?) .from(false) .to(true) end it 'freezes children' do expect { subject } .to change { config[:foo].frozen? } .from(false) .to(true) end end describe '#action_provider' do subject { config.action_provider } let(:hash) { { foo: { bar: 100 } } } let(:config) { described_class.new(hash:, dir: Dir.getwd) } context 'no action_provider key present' do let(:hash) { { foo: 123 } } it 'raises' do # Maybe not the best… but it works for now expect { subject }.to raise_error(NoMethodError) end end context 'action_provider key present' do let(:hash) { { foo: 123, action_provider: 'rulez' } } it { is_expected.to eq(:rulez) } end end describe '#reference' do subject { config.reference } let(:hash) { { foo: { bar: 100 } } } let(:config) { described_class.new(hash:, dir: Dir.getwd) } it { is_expected.to eq('configuration') } end describe '#inspect' do subject { config.inspect } let(:hash) { { foo: { bar: 100 } } } let(:config) { described_class.new(hash:, dir: Dir.getwd) } it { is_expected.to eq('') } end describe '#merge' do subject { config1.merge(config2).to_h } 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) } 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 subject { config } let(:hash) { { foo: 'bar', environments: { test: { foo: 'test-bar' }, default: { foo: 'default-bar' } } } } let(:config) { described_class.new(hash:, dir: Dir.getwd, env_name:).with_environment } 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 'fails' 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 'fails' do expect { subject }.to raise_error(JsonSchema::Error) end end context 'invalid prune (exclude has incorrect type)' do let(:hash) { { prune: { exclude: 'nothing' } } } it 'fails' 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 'fails' 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 'fails' do expect { subject }.to raise_error(JsonSchema::Error) end end context 'invalid checks (all has invalid type)' do let(:hash) do { checks: { all: 123 } } end it 'passes' do expect { subject }.to raise_error(JsonSchema::Error) end end context 'invalid checks (all.exclude_files has invalid type)' do let(:hash) do { checks: { all: { exclude_files: 'everything' } } } 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 'fails' 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 'fails' 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 'fails' 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 'fails' 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 'fails' 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.13.3/nanoc-core/spec/nanoc/core/content_spec.rb000066400000000000000000000040531472033334600227240ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Core::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::Core::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::Core::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: } } context 'with relative filename param' do let(:filename) { 'foo.md' } it 'raises' do expect { subject }.to raise_error( ArgumentError, 'Content filename foo.md is not absolute', ) end end context 'with absolute filename param' do let(:filename) { '/foo.md' } it 'returns textual content' do expect(subject).to be_a(Nanoc::Core::TextualContent) expect(subject.string).to eql('foo') expect(subject.filename).to eql('/foo.md') end 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::Core::TextualContent) expect(subject.string).to eql('foo') expect(subject.filename).to be_nil end end end end describe '#binary?' do subject { content.binary? } let(:content) { described_class.new('/home/denis/stuff.txt') } it 'raises' do expect { subject }.to raise_error(NotImplementedError) end end end nanoc-4.13.3/nanoc-core/spec/nanoc/core/context_spec.rb000066400000000000000000000023061472033334600227350ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Core module Helper37F4A8EAF27F4DB0B4AB61117975D519 def my_sample 'sample succeeded!' end end end end describe Nanoc::Core::Context do let(:context) do described_class.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') expect(context).to respond_to(:foo) end it 'returns null for unknown instance variables' do expect(eval('@asdf', context.get_binding)).to be_nil end it 'does not provide missing instance methods' do expect { eval('asdf', context.get_binding) }.to raise_error(NameError) expect(context).not_to respond_to(:asdf) end it 'supports #include' do eval('include Nanoc::Core::Helper37F4A8EAF27F4DB0B4AB61117975D519', context.get_binding) expect(eval('my_sample()', context.get_binding)).to eq('sample succeeded!') end it 'has correct examples' do expect('Nanoc::Core::Context#initialize') .to have_correct_yard_examples .in_file('nanoc-core/lib/nanoc/core/context.rb') end end nanoc-4.13.3/nanoc-core/spec/nanoc/core/core_ext/000077500000000000000000000000001472033334600215215ustar00rootroot00000000000000nanoc-4.13.3/nanoc-core/spec/nanoc/core/core_ext/array_spec.rb000066400000000000000000000024111472033334600241740ustar00rootroot00000000000000# frozen_string_literal: true describe Array do describe '#__nanoc_symbolize_keys_recursively' do it 'converts 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 '#__nanoc_stringify_keys_recursively' do it 'converts 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 '#__nanoc_freeze_recursively' do it 'prevents 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 'prevents 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 'does not freeze infinitely' do a = [] a << a a.__nanoc_freeze_recursively expect(a).to be_frozen expect(a[0]).to be_frozen end end end nanoc-4.13.3/nanoc-core/spec/nanoc/core/core_ext/hash_spec.rb000066400000000000000000000030071472033334600240030ustar00rootroot00000000000000# frozen_string_literal: true describe Hash do describe '#__nanoc_symbolize_keys_recursively' do it 'converts 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 'does 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 '#__nanoc_stringify_keys_recursively' do it 'converts 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 'does 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 '#__nanoc_freeze_recursively' do it 'prevents 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 'prevents 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 'does 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 end nanoc-4.13.3/nanoc-core/spec/nanoc/core/core_ext/string_spec.rb000066400000000000000000000013271472033334600243710ustar00rootroot00000000000000# frozen_string_literal: true describe String do describe '#__nanoc_cleaned_identifier' do it 'does not convert already clean paths' do expect('/foo/bar/'.__nanoc_cleaned_identifier).to eql('/foo/bar/') end it 'prepends slash if necessary' do expect('foo/bar/'.__nanoc_cleaned_identifier).to eql('/foo/bar/') end it 'appends slash if necessary' do expect('/foo/bar'.__nanoc_cleaned_identifier).to eql('/foo/bar/') end it 'removes double slashes at start' do expect('//foo/bar/'.__nanoc_cleaned_identifier).to eql('/foo/bar/') end it 'removes double slashes at end' do expect('/foo/bar//'.__nanoc_cleaned_identifier).to eql('/foo/bar/') end end end nanoc-4.13.3/nanoc-core/spec/nanoc/core/data_source_spec.rb000066400000000000000000000064271472033334600235520ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Core::DataSource, stdio: true do subject(:data_source) do described_class.new({}, nil, nil, {}) end it 'has an empty #up implementation' do # rubocop:disable RSpec/NoExpectationExample data_source.up end it 'has an empty #down implementation' do # rubocop:disable RSpec/NoExpectationExample 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::Core::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::Core::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::Core::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::Core::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.13.3/nanoc-core/spec/nanoc/core/dependency_props_spec.rb000066400000000000000000000276551472033334600246300ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Core::DependencyProps 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.to be(false) } end context 'raw_content active' do let(:props) { described_class.new(raw_content: true) } it { is_expected.to be(true) } 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(true) } end context 'compiled_content active' do let(:props) { described_class.new(compiled_content: true) } it { is_expected.to be(false) } 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(true) } end context 'raw_content is empty list' do let(:props) { described_class.new(raw_content: []) } it { is_expected.to be(false) } end context 'raw_content is non-empty list' do let(:props) { described_class.new(raw_content: ['/asdf.*']) } it { is_expected.to be(true) } end end describe '#attributes?' do subject { props.attributes? } context 'nothing active' do it { is_expected.to be(false) } end context 'attributes active' do let(:props) { described_class.new(attributes: true) } it { is_expected.to be(true) } end context 'attributes and compiled_content active' do let(:props) { described_class.new(attributes: true, compiled_content: true) } it { is_expected.to be(true) } end context 'compiled_content active' do let(:props) { described_class.new(compiled_content: true) } it { is_expected.to be(false) } 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(true) } end context 'attributes is empty list' do let(:props) { described_class.new(attributes: []) } it { is_expected.to be(false) } end context 'attributes is non-empty list' do let(:props) { described_class.new(attributes: [:donkey]) } it { is_expected.to be(true) } 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 subject { props.merge(other_props).attributes } 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 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 subject { props.merge(other_props).raw_content } 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 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.13.3/nanoc-core/spec/nanoc/core/dependency_store_spec.rb000066400000000000000000000415341472033334600246110ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Core::DependencyStore do let(:store) { described_class.new(items, layouts, config) } let(:item_a) { Nanoc::Core::Item.new('a', {}, '/a.md') } let(:item_b) { Nanoc::Core::Item.new('b', {}, '/b.md') } let(:item_c) { Nanoc::Core::Item.new('c', {}, '/c.md') } let(:layout_a) { Nanoc::Core::Layout.new('la', {}, '/la.md') } let(:layout_b) { Nanoc::Core::Layout.new('lb', {}, '/lb.md') } let(:items) { Nanoc::Core::ItemCollection.new(config, [item_a, item_b, item_c]) } let(:layouts) { Nanoc::Core::LayoutCollection.new(config, [layout_a, layout_b]) } let(:config) { Nanoc::Core::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 be(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 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 be(false) expect(deps[0].props.compiled_content?).to be(false) expect(deps[0].props.path?).to be(false) end it 'returns the specified props' do deps = store.dependencies_causing_outdatedness_of(item_a) expect(deps[0].props.attributes?).to be(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 be(false) expect(deps[0].props.compiled_content?).to be(false) expect(deps[0].props.path?).to be(false) end it 'returns the specified props' do deps = store.dependencies_causing_outdatedness_of(item_a) expect(deps[0].props.attributes?).to be(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 be(1) 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 be(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 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 item that will be removed' do before do store.record_dependency(item_a, item_b) store.items = Nanoc::Core::ItemCollection.new(config, [item_a]) end it 'retains dependency, but from nil' do deps = store.dependencies_causing_outdatedness_of(item_a) expect(deps.size).to be(1) expect(deps[0].from).to be_nil expect(deps[0].to).to eql(item_a) 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 be(false) expect(deps[0].props.attributes?).to be(false) expect(deps[0].props.path?).to be(false) end it 'returns the specified props' do deps = store.dependencies_causing_outdatedness_of(item_a) expect(deps[0].props.compiled_content?).to be(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 be(false) expect(deps[0].props.path?).to be(false) end it 'returns the specified props' do deps = store.dependencies_causing_outdatedness_of(item_a) expect(deps[0].props.attributes?).to be(true) expect(deps[0].props.compiled_content?).to be(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 be(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 be(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 be(1) expect(deps[0].from).to eql(item_b) expect(deps[0].to).to eql(item_a) expect(deps[0].props.raw_content?).to be(false) expect(deps[0].props.attributes?).to be(true) expect(deps[0].props.compiled_content?).to be(true) expect(deps[0].props.path?).to be(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 it 'has no new items' do expect(reloaded_store.new_items).to be_empty end it 'has no new layouts' do expect(reloaded_store.new_layouts).to be_empty end end context 'one new item' do let(:items_after) do Nanoc::Core::ItemCollection.new(config, [item_a, item_b, item_c, item_d]) end let(:item_d) { Nanoc::Core::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 it 'has one new item' do expect(reloaded_store.new_items).to contain_exactly(item_d) end it 'has no new layouts' do expect(reloaded_store.new_layouts).to be_empty end end context 'unrelated item removed' do let(:items_after) do Nanoc::Core::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::Core::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 be(1) expect(deps[0].from).to eql(config) expect(deps[0].to).to eql(item_a) expect(deps[0].props.raw_content?).to be(false) expect(deps[0].props.attributes?).to be(true) expect(deps[0].props.attributes).to contain_exactly(:donkey) expect(deps[0].props.compiled_content?).to be(false) expect(deps[0].props.path?).to be(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?).to be(false) expect(deps.first.props.compiled_content?).to be(true) 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(true) expect(deps.first.props.attributes).to be(true) expect(deps.first.props.compiled_content?).to be(false) 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(true) expect(deps.first.props.attributes).to contain_exactly(:giraffe) expect(deps.first.props.compiled_content?).to be(false) 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 subject { store.forget_dependencies_for(item_b) } 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 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(contain_exactly(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.13.3/nanoc-core/spec/nanoc/core/dependency_tracker_spec.rb000066400000000000000000000162771472033334600251160ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Core::DependencyTracker do let(:tracker) { described_class.new(store) } let(:store) { Nanoc::Core::DependencyStore.new(empty_items, empty_layouts, config) } let(:item_a) { Nanoc::Core::Item.new('a', {}, '/a.md') } let(:item_b) { Nanoc::Core::Item.new('b', {}, '/b.md') } let(:item_c) { Nanoc::Core::Item.new('c', {}, '/c.md') } let(:empty_items) { Nanoc::Core::ItemCollection.new(config) } let(:empty_layouts) { Nanoc::Core::LayoutCollection.new(config) } let(:config) { Nanoc::Core::Configuration.new(dir: Dir.getwd).with_defaults } shared_examples 'a null dependency tracker' do let(:tracker) { Nanoc::Core::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 be(true) expect(dep.props.raw_content?).to be(false) expect(dep.props.attributes?).to be(false) expect(dep.props.path?).to be(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 be(true) expect(dep.props.attributes?).to be(true) expect(dep.props.raw_content?).to be(false) expect(dep.props.path?).to be(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.13.3/nanoc-core/spec/nanoc/core/directed_graph_spec.rb000066400000000000000000000140561472033334600242220ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Core::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 contain_exactly([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 contain_exactly([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 contain_exactly([0, 1, { name: 'Mr. C' }], [0, 2, { name: 'Cooper' }]) } end end it 'has correct examples' do expect('Nanoc::Core::DirectedGraph') .to have_correct_yard_examples .in_file('nanoc-core/lib/nanoc/core/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 contain_exactly('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 contain_exactly('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 subject { graph.delete_edges_to('1') } 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 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::Core::DirectedGraph()') } end context 'one edge, no props' do before do graph.add_edge('1', '2') end it { is_expected.to eq('Nanoc::Core::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::Core::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::Core::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::Core::DirectedGraph("1" -> "2" props="donkey", "2" -> "3" props="zebra")') } end end end nanoc-4.13.3/nanoc-core/spec/nanoc/core/document_spec.rb000066400000000000000000000226511472033334600230740ustar00rootroot00000000000000# frozen_string_literal: true shared_examples 'a document' do describe '#initialize' do 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 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' } describe 'content arg' do context 'string' do it 'converts content' do expect(subject.content).to be_a(Nanoc::Core::TextualContent) expect(subject.content.string).to eql('Hello world') end end context 'content' do let(:content_arg) { Nanoc::Core::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 be(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 be(1) end end end describe 'identifier arg' do context 'string' do it 'converts identifier' do expect(subject.identifier).to be_a(Nanoc::Core::Identifier) expect(subject.identifier.to_s).to eql('/home.md') end end context 'identifier' do let(:identifier_arg) { Nanoc::Core::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::Core::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 subject { document_a == document_b } 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) } context 'same identifier' do let(:identifier_arg_a) { '/home.md' } let(:identifier_arg_b) { '/home.md' } it { is_expected.to be(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 be(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 be(false) } it 'has different hashes' do expect(document_a.hash).not_to eql(document_b.hash) end end end describe '#with_identifier_prefix' do subject { document.with_identifier_prefix('/animals') } let(:document) { described_class.new('kontent', { at: 'ribut' }, '/donkey.md') } it 'does not mutate the original' do # rubocop:disable RSpec/NoExpectationExample 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::Core::Identifier::InvalidFullIdentifierError) end it 'allos changing to a full identifier' do document.identifier = Nanoc::Core::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::Core::Identifier.new('/thing/', type: :legacy) expect(document.identifier).to eq('/thing/') expect(document.identifier).to be_legacy end end describe '#content=' do subject { document.content = Nanoc::Core::TextualContent.new('New!') } 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' } } } 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 subject { document.set_attribute(:key, 'value') } 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' } } } 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.13.3/nanoc-core/spec/nanoc/core/errors/000077500000000000000000000000001472033334600212255ustar00rootroot00000000000000nanoc-4.13.3/nanoc-core/spec/nanoc/core/errors/dependency_cycle_spec.rb000066400000000000000000000022001472033334600260530ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Core::Errors::DependencyCycle do subject(:error) { described_class.new(cycle) } let(:cycle) do [ rep_b, rep_c, rep_d, rep_e, ] end let(:rep_a) { Nanoc::Core::ItemRep.new(Nanoc::Core::Item.new('a', {}, '/a.md'), :default) } let(:rep_b) { Nanoc::Core::ItemRep.new(Nanoc::Core::Item.new('b', {}, '/b.md'), :default) } let(:rep_c) { Nanoc::Core::ItemRep.new(Nanoc::Core::Item.new('c', {}, '/c.md'), :default) } let(:rep_d) { Nanoc::Core::ItemRep.new(Nanoc::Core::Item.new('d', {}, '/d.md'), :default) } let(:rep_e) { Nanoc::Core::ItemRep.new(Nanoc::Core::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.13.3/nanoc-core/spec/nanoc/core/executor_spec.rb000066400000000000000000000471111472033334600231120ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Core::Executor do Class.new(Nanoc::Core::Filter) do identifier :simple_erb_uy2wbp6dcf4hlc4gbluauh07zuz2wvei def run(content, _params = {}) context = Nanoc::Core::Context.new(assigns) ERB.new(content).result(context.get_binding) end end let(:executor) { described_class.new(rep, compilation_context, dependency_tracker) } let(:compilation_context) do Nanoc::Core::CompilationContext.new( action_provider:, reps:, site:, compiled_content_cache:, compiled_content_store:, ) end let(:item) { Nanoc::Core::Item.new(content, {}, '/index.md') } let(:rep) { Nanoc::Core::ItemRep.new(item, :donkey) } let(:content) { Nanoc::Core::TextualContent.new('Donkey Power').tap(&:freeze) } let(:action_provider) do Class.new(Nanoc::Core::ActionProvider) do def self.for(_context) raise NotImplementedError end def initialize; end end.new end let(:reps) do Nanoc::Core::ItemRepRepo.new end let(:site) do Nanoc::Core::Site.new( config:, code_snippets: [], data_source: Nanoc::Core::InMemoryDataSource.new(items, layouts), ) end let(:layout) do Nanoc::Core::Layout.new(layout_content, { bug: 'Gum Emperor' }, '/default.erb') end let(:layouts) { Nanoc::Core::LayoutCollection.new(config, [layout]) } let(:items) { Nanoc::Core::ItemCollection.new(config, []) } let(:layout_content) { 'head <%= @content %> foot' } let(:config_hash) { { string_pattern_type: 'glob' } } let(:config) { Nanoc::Core::Configuration.new(hash: config_hash, dir: Dir.getwd).with_defaults } let(:compiled_content_cache) do Nanoc::Core::CompiledContentCache.new(config:) end let(:compiled_content_store) { Nanoc::Core::CompiledContentStore.new } let(:dependency_tracker) { Nanoc::Core::DependencyTracker.new(double(:dependency_store)) } describe '#filter' do let(:assigns) { {} } let(:content) { Nanoc::Core::TextualContent.new('<%= "Donkey" %> Power') } context 'normal flow with textual rep' do subject { executor.filter(:simple_erb_uy2wbp6dcf4hlc4gbluauh07zuz2wvei) } before do expect(Nanoc::Core::NotificationCenter) .to receive(:post).with(:filtering_started, rep, :simple_erb_uy2wbp6dcf4hlc4gbluauh07zuz2wvei) expect(Nanoc::Core::NotificationCenter) .to receive(:post).with(:filtering_ended, rep, :simple_erb_uy2wbp6dcf4hlc4gbluauh07zuz2wvei) compiled_content_store.set_current(rep, content) end it 'does not set :pre in repo' do expect(compiled_content_store.get(rep, :pre)).to be_nil expect { subject }.not_to change { compiled_content_store.get(rep, :pre) } end it 'does not set :post in repo' do expect(compiled_content_store.get(rep, :post)).to be_nil expect { subject }.not_to change { compiled_content_store.get(rep, :post) } end it 'does not set :last in repo' do expect(compiled_content_store.get(rep, :last)).to be_nil expect { subject }.not_to change { compiled_content_store.get(rep, :last) } end it 'updates current content in repo' do expect { subject } .to change { compiled_content_store.get_current(rep).string } .from('<%= "Donkey" %> Power') .to('Donkey Power') end it 'returns frozen data' do executor.filter(:simple_erb_uy2wbp6dcf4hlc4gbluauh07zuz2wvei) expect(compiled_content_store.get_current(rep)).to be_frozen end end context 'normal flow with binary rep' do subject { executor.filter(:whatever) } let(:content) { Nanoc::Core::BinaryContent.new(File.expand_path('foo.dat')) } before do expect(Nanoc::Core::NotificationCenter) .to receive(:post).with(:filtering_started, rep, :whatever) expect(Nanoc::Core::NotificationCenter) .to receive(:post).with(:filtering_ended, rep, :whatever) File.write(content.filename, 'Foo Data') filter_class = Class.new(Nanoc::Core::Filter) do type :binary def run(filename, _params = {}) File.write(output_filename, "Compiled data for #{filename}") end end expect(Nanoc::Core::Filter).to receive(:named).with(:whatever) { filter_class } compiled_content_store.set_current(rep, content) end it 'does not set :pre in repo' do expect(compiled_content_store.get(rep, :pre)).to be_nil expect { subject }.not_to change { compiled_content_store.get(rep, :pre) } end it 'does not set :post in repo' do expect(compiled_content_store.get(rep, :post)).to be_nil expect { subject }.not_to change { compiled_content_store.get(rep, :post) } end it 'does not set :last in repo' do expect(compiled_content_store.get(rep, :last)).to be_nil expect { subject }.not_to change { compiled_content_store.get(rep, :last) } end it 'updates current content in repo' do expect { subject } .to change { File.read(compiled_content_store.get_current(rep).filename) } .from('Foo Data') .to(/\ACompiled data for (C:)?\/.*\/foo.dat\z/) end it 'returns frozen data' do executor.filter(:whatever) expect(compiled_content_store.get_current(rep)).to be_frozen end end context 'normal flow with binary rep and binary-to-text filter' do subject { executor.filter(:whatever) } let(:content) { Nanoc::Core::BinaryContent.new(File.expand_path('foo.dat')) } before do expect(Nanoc::Core::NotificationCenter) .to receive(:post).with(:filtering_started, rep, :whatever) expect(Nanoc::Core::NotificationCenter) .to receive(:post).with(:filtering_ended, rep, :whatever) File.write(content.filename, 'Foo Data') filter_class = Class.new(Nanoc::Core::Filter) do type binary: :text def run(filename, _params = {}) "Compiled data for #{filename}" end end expect(Nanoc::Core::Filter).to receive(:named).with(:whatever) { filter_class } compiled_content_store.set_current(rep, content) end it 'does not set :pre in repo' do expect(compiled_content_store.get(rep, :pre)).to be_nil expect { subject }.not_to change { compiled_content_store.get(rep, :pre) } end it 'does not set :post in repo' do expect(compiled_content_store.get(rep, :post)).to be_nil expect { subject }.not_to change { compiled_content_store.get(rep, :post) } end it 'does not set :last in repo' do expect(compiled_content_store.get(rep, :last)).to be_nil expect { subject }.not_to change { compiled_content_store.get(rep, :last) } end it 'updates current content repo' do expect { subject } .to change { compiled_content_store.get_current(rep) } .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::Core::NotificationCenter) .to receive(:post).with(:filtering_started, rep, :whatever) expect(Nanoc::Core::NotificationCenter) .to receive(:post).with(:filtering_ended, rep, :whatever) filter_class = Class.new(Nanoc::Core::Filter) do type text: :binary def run(content, _params = {}) File.write(output_filename, "Binary #{content}") end end expect(Nanoc::Core::Filter).to receive(:named).with(:whatever) { filter_class } compiled_content_store.set_current(rep, content) end it 'does not set :pre in repo' do expect(compiled_content_store.get(rep, :pre)).to be_nil expect { subject }.not_to change { compiled_content_store.get(rep, :pre) } end it 'does not set :post in repo' do expect(compiled_content_store.get(rep, :post)).to be_nil expect { subject }.not_to change { compiled_content_store.get(rep, :post) } end it 'does not set :last in repo' do expect(compiled_content_store.get(rep, :last)).to be_nil expect { subject }.not_to change { compiled_content_store.get(rep, :last) } end it 'updates current content in repo' do expect { subject } .to change { compiled_content_store.get_current(rep) } .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::Core::Filter::UnknownFilterError) end end context 'non-binary rep, binary-to-something filter' do before do filter_class = Class.new(Nanoc::Core::Filter) do type :binary def run(_content, _params = {}); end end expect(Nanoc::Core::Filter).to receive(:named).with(:whatever) { filter_class } compiled_content_store.set_current(rep, content) end it 'raises' do expect { executor.filter(:whatever) } .to raise_error(Nanoc::Core::Errors::CannotUseBinaryFilter) end end context 'binary rep, text-to-something filter' do let(:content) { Nanoc::Core::BinaryContent.new(File.expand_path('foo.md')) } before do compiled_content_store.set_current(rep, content) end it 'raises' do expect { executor.filter(:simple_erb_uy2wbp6dcf4hlc4gbluauh07zuz2wvei) } .to raise_error(Nanoc::Core::Errors::CannotUseTextualFilter) end end context 'binary filter that does not write anything' do let(:content) { Nanoc::Core::BinaryContent.new(File.expand_path('foo.dat')) } before do expect(Nanoc::Core::NotificationCenter) .to receive(:post).with(:filtering_started, rep, :whatever) expect(Nanoc::Core::NotificationCenter) .to receive(:post).with(:filtering_ended, rep, :whatever) File.write(content.filename, 'Foo Data') filter_class = Class.new(Nanoc::Core::Filter) do identifier :executor_spec_Toing1Oowoa3aewoop0k type :binary def run(_filename, _params = {}); end end compiled_content_store.set_current(rep, content) expect(Nanoc::Core::Filter).to receive(:named).with(:whatever) { filter_class } end example do expect { executor.filter(:whatever) } .to raise_error(Nanoc::Core::Filter::OutputNotWrittenError) end end context 'content is frozen' do before do compiled_content_store.set_current(rep, item.content) end let(:item) do Nanoc::Core::Item.new('foo bar', {}, '/foo.md').tap(&:freeze) end let(:filter_that_modifies_content) do Class.new(Nanoc::Core::Filter) do def run(content, _params = {}) content.gsub!('foo', 'moo') content end end end let(:filter_that_modifies_params) do Class.new(Nanoc::Core::Filter) do def run(_content, params = {}) params[:foo] = 'bar' 'asdf' end end end it 'errors when attempting to modify content' do expect(Nanoc::Core::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::Core::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 subject { executor.layout('/default.*') } let(:action_sequence) do Nanoc::Core::ActionSequenceBuilder.build do |b| b.add_filter(:simple_erb_uy2wbp6dcf4hlc4gbluauh07zuz2wvei, {}) end end before do rep.snapshot_defs = [Nanoc::Core::SnapshotDef.new(:pre, binary: false)] compiled_content_store.set_current(rep, content) allow(action_provider).to receive(:action_sequence_for).with(layout).and_return(action_sequence) end 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(compiled_content_store.get_current(rep).string).to eq('head Gum Emperor foot') end end context 'normal flow' do it 'updates :last in repo' do expect { subject } .to change { compiled_content_store.get_current(rep) } .from(some_textual_content('Donkey Power')) .to(some_textual_content('head Donkey Power foot')) end it 'sets frozen content' do subject expect(compiled_content_store.get_current(rep)).to be_frozen expect(compiled_content_store.get(rep, :pre)).to be_frozen end it 'does not create pre snapshot' do # a #layout is followed by a #snapshot(:pre, …) expect(compiled_content_store.get(rep, :pre)).to be_nil subject expect(compiled_content_store.get(rep, :pre)).to be_nil end it 'sends notifications' do expect(Nanoc::Core::NotificationCenter).to receive(:post).with(:filtering_started, rep, :simple_erb_uy2wbp6dcf4hlc4gbluauh07zuz2wvei).ordered expect(Nanoc::Core::NotificationCenter).to receive(:post).with(:filtering_ended, rep, :simple_erb_uy2wbp6dcf4hlc4gbluauh07zuz2wvei).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::Core::CompilationItemRepView.new(rep, view_context) } end before do executor.snapshot(:pre) end it 'does not set :last in repo' do expect(compiled_content_store.get(rep, :last)).to be_nil expect { subject }.not_to change { compiled_content_store.get(rep, :last) } end it 'updates current content in repo' do expect { subject } .to change { compiled_content_store.get_current(rep) } .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 'does not set :last in repo' do expect(compiled_content_store.get(rep, :last)).to be_nil expect { subject }.not_to change { compiled_content_store.get(rep, :last) } end it 'updates current content in repo' do expect { subject } .to change { compiled_content_store.get_current(rep) } .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::Core::LayoutCollection.new( config, [Nanoc::Core::Layout.new('head <%= @foo %> foot', {}, '/other.erb')], ) end it 'raises' do expect { subject }.to raise_error(Nanoc::Core::Errors::UnknownLayout) end end context 'no filter specified' do let(:action_sequence) do Nanoc::Core::ActionSequence.new end it 'raises' do expect { subject }.to raise_error(Nanoc::Core::CompilationContext::UndefinedFilterForLayoutError) end end context 'binary item' do let(:content) { Nanoc::Core::BinaryContent.new(File.expand_path('donkey.md')) } it 'raises' do expect { subject }.to raise_error( Nanoc::Core::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::Core::Filter) do def run(_content, params = {}) params[:foo] = 'bar' 'asdf' end end expect(Nanoc::Core::Filter).to receive(:named).with(:simple_erb_uy2wbp6dcf4hlc4gbluauh07zuz2wvei) { filter_class } expect { subject }.to raise_frozen_error end end describe '#snapshot' do subject { executor.snapshot(:something) } before do compiled_content_store.set_current(rep, content) File.write('donkey.dat', 'binary donkey') end context 'binary content' do let(:content) { Nanoc::Core::BinaryContent.new(File.expand_path('donkey.dat')) } it 'creates snapshots in repo' do expect { subject } .to change { compiled_content_store.get(rep, :something) } .from(nil) .to(some_binary_content('binary donkey')) end end context 'textual content' do let(:content) { Nanoc::Core::TextualContent.new('Donkey Power') } it 'creates snapshots in repo' do expect { subject } .to change { compiled_content_store.get(rep, :something) } .from(nil) .to(some_textual_content('Donkey Power')) end end context 'final snapshot' do let(:content) { Nanoc::Core::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')).to be(false) end end context 'no raw path' do it 'does not write' do executor.snapshot(:something) expect(File.file?('output/donkey.md')).to be(false) end end end end describe '#find_layout' do subject { executor.find_layout(arg) } before do allow(compilation_context).to receive(:site) { site } end context 'layout with cleaned identifier exists' do let(:arg) { '/default' } let(:layouts) do Nanoc::Core::LayoutCollection.new( config, [Nanoc::Core::Layout.new('head <%= @foo %> foot', {}, Nanoc::Core::Identifier.new('/default/', type: :legacy))], ) end it { is_expected.to eq(layouts.to_a[0]) } end context 'no layout with cleaned identifier exists' do let(:layouts) do Nanoc::Core::LayoutCollection.new( config, [Nanoc::Core::Layout.new('head <%= @foo %> foot', {}, '/default.erb')], ) end context 'globs' do let(:config_hash) { { string_pattern_type: 'glob' } } let(:arg) { '/default.*' } it { is_expected.to eq(layouts.to_a[0]) } end context 'no globs' do let(:config_hash) { { string_pattern_type: 'legacy' } } let(:arg) { '/default.*' } it 'raises' do expect { subject }.to raise_error(Nanoc::Core::Errors::UnknownLayout) end end end end end nanoc-4.13.3/nanoc-core/spec/nanoc/core/feature_spec.rb000066400000000000000000000056421472033334600227120ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Core::Feature do describe '.enabled?' do subject { described_class.enabled?(feature_name) } let(:feature_name) { 'magic' } before do described_class.reset_caches ENV['NANOC_FEATURES'] = +'' end context 'not set' do it { is_expected.to be(false) } end context 'set to list not including feature' do before { ENV['NANOC_FEATURES'] = 'foo,bar' } it { is_expected.to be(false) } end context 'set to all' do before { ENV['NANOC_FEATURES'] = 'all' } it { is_expected.to be(true) } end context 'set to list including feature' do before { ENV['NANOC_FEATURES'] = 'foo,magic,bar' } it { is_expected.to be(true) } end end describe '.enable' do subject do described_class.enable(feature_name) do described_class.enabled?(feature_name) end end let(:feature_name) { 'magic' } before do described_class.reset_caches ENV['NANOC_FEATURES'] = +'' end context 'not set' do it { is_expected.to be(true) } it 'unsets afterwards' do expect(described_class.enabled?(feature_name)).to be(false) end end context 'set to list not including feature' do before { ENV['NANOC_FEATURES'] = 'foo,bar' } it { is_expected.to be(true) } it 'unsets afterwards' do expect(described_class.enabled?(feature_name)).to be(false) end end context 'set to all' do before { ENV['NANOC_FEATURES'] = 'all' } it { is_expected.to be(true) } end context 'set to list including feature' do before { ENV['NANOC_FEATURES'] = 'foo,magic,bar' } it { is_expected.to be(true) } 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(described_class.all_outdated).to be_empty end describe 'fake outdated features' do before { described_class.define('abc', version: '4.2.x') } after { described_class.undefine('abc') } it 'detects outdated features' do expect(described_class.all_outdated).to eq(['abc']) end end end describe '.define and .undefine' do let(:feature_name) { 'testing123' } after { described_class.undefine(feature_name) if defined?(Nanoc::Core::Feature::TESTING123) } it 'can define' do described_class.define(feature_name, version: '4.3.x') expect(Nanoc::Core::Feature::TESTING123).not_to be_nil end it 'can undefine' do described_class.define(feature_name, version: '4.3.x') described_class.undefine(feature_name) expect { Nanoc::Core::Feature::TESTING123 }.to raise_error(NameError) end end end nanoc-4.13.3/nanoc-core/spec/nanoc/core/filter_spec.rb000066400000000000000000000206601472033334600225410ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Core::Filter do describe '.define' do context 'simple filter' do let(:filter_name) { :b5355bbb4d772b9853d21be57da614dba521dbbb } let(:filter_class) { described_class.named(filter_name) } before do described_class.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) { described_class.named(filter_name) } let(:filter) { filter_class.new(assigns) } let(:assigns) { { animal: 'Giraffe' } } before do described_class.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 let(:filter_name) { :ipk5rpblmorrrgkiodzuuanfujokae2g } let(:filter_class) { described_class.named(filter_name) } before do described_class.define(filter_name) do |content, _params| content.upcase end end it 'returns filter if exists' do expect(described_class.named!(filter_name)).not_to be_nil expect(described_class.named!(filter_name).identifier).to eq(filter_name) end it 'raises if non-existent' do expect { described_class.named!(:ajklsdfklasjflkd) } .to raise_error( Nanoc::Core::Filter::UnknownFilterError, '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::Core::Item.new('asdf', {}, '/donkey.md') } let(:item_rep) { Nanoc::Core::ItemRep.new(item, :animal) } let(:assigns) { { item:, item_rep: } } it { is_expected.to eq('item /donkey.md (rep animal)') } end context 'assigns contains layout' do let(:layout) { Nanoc::Core::Layout.new('asdf', {}, '/donkey.md') } let(:assigns) { { 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::Core::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::Core::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) } before do described_class.define(filter_name) do |content, _params| content.upcase end end let(:filter_name) { :z3xe2lejsgmuaa57jrfapvlkitgi1vwl } let(:filter_class) { described_class.named(filter_name) } let(:filter) { filter_class.new(assigns) } let(:item_views) { [item_view] } let(:item) { Nanoc::Core::Item.new('foo', {}, '/stuff.md') } let(:item_view) { Nanoc::Core::CompilationItemView.new(item, view_context) } let(:rep) { Nanoc::Core::ItemRep.new(item, :default) } let(:view_context) do Nanoc::Core::ViewContextForCompilation.new( reps:, items: Nanoc::Core::ItemCollection.new(config), dependency_tracker:, compilation_context:, compiled_content_store:, ) end let(:compilation_context) do Nanoc::Core::CompilationContext.new( action_provider:, reps:, site:, compiled_content_cache:, compiled_content_store:, ) end let(:action_provider) do Class.new(Nanoc::Core::ActionProvider) do def self.for(_context) raise NotImplementedError end def initialize; end end.new end let(:compiled_content_cache) { Nanoc::Core::CompiledContentCache.new(config:) } let(:compiled_content_store) { Nanoc::Core::CompiledContentStore.new } let(:dependency_tracker) { Nanoc::Core::DependencyTracker.new(dependency_store) } let(:dependency_store) { Nanoc::Core::DependencyStore.new(empty_items, empty_layouts, config) } let(:empty_items) { Nanoc::Core::ItemCollection.new(config) } let(:empty_layouts) { Nanoc::Core::LayoutCollection.new(config) } let(:config) { Nanoc::Core::Configuration.new(dir: Dir.getwd).with_defaults } let(:reps) { Nanoc::Core::ItemRepRepo.new } let(:site) do Nanoc::Core::Site.new( config:, code_snippets: [], data_source: Nanoc::Core::InMemoryDataSource.new(empty_items, empty_layouts), ) end 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::Core::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::Core::Errors::UnmetDependency) expect(res.rep).to eql(rep) # resume 2 expect(fiber.resume).not_to be_a(Nanoc::Core::Errors::UnmetDependency) end end context 'multiple reps exist' do let(:other_rep) { Nanoc::Core::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::Core::Errors::UnmetDependency) expect(res.rep).to eql(rep) # resume 2 res = fiber.resume expect(res).to be_a(Nanoc::Core::Errors::UnmetDependency) expect(res.rep).to eql(other_rep) # resume 3 expect(fiber.resume).not_to be_a(Nanoc::Core::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::Core::Item.new(content, {}, '/stuff.md') } let(:filename) { File.expand_path('foo.dat') } let(:content) { Nanoc::Core::BinaryContent.new(filename) } it 'creates dependency' do expect { subject } .to create_dependency_on(item_view) end end end end end nanoc-4.13.3/nanoc-core/spec/nanoc/core/identifiable_collection_spec.rb000066400000000000000000000177471472033334600261220ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Core::IdentifiableCollection do shared_examples 'a generic identifiable collection' do subject(:identifiable_collection) { described_class.new(config, objects) } let(:config) { Nanoc::Core::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 '#object_with_identifier' do subject(:object_with_identifier) { identifiable_collection.object_with_identifier(arg) } let(:objects) do [ Nanoc::Core::Item.new('Foo', {}, '/foo.md'), Nanoc::Core::Item.new('Bar', {}, '/bar.md'), Nanoc::Core::Item.new('Quz', {}, '/qux.md'), ] end shared_examples 'object_with_identifier' do context 'when given an identifier' do context 'when object does not exist' do let(:arg) { Nanoc::Core::Identifier.new('/nope.md') } it 'returns nil' do expect(object_with_identifier).to be_nil end end context 'when object exist' do let(:arg) { Nanoc::Core::Identifier.new('/foo.md') } it 'returns object' do expect(object_with_identifier).to eq(objects[0]) end end end context 'when given a string' do context 'when object does not exist' do let(:arg) { '/nope.md' } it 'returns nil' do expect(object_with_identifier).to be_nil end end context 'when object exist' do let(:arg) { '/foo.md' } it 'returns object' do expect(object_with_identifier).to eq(objects[0]) end end end end context 'when frozen' do before { identifiable_collection.freeze } include_examples 'object_with_identifier' end context 'when not frozen' do include_examples 'object_with_identifier' end end describe '#object_matching_glob' do subject(:object_matching_glob) { identifiable_collection.object_matching_glob(arg) } let(:objects) do [ Nanoc::Core::Item.new('Foo', {}, '/foo.md'), Nanoc::Core::Item.new('Bar', {}, '/bar.md'), Nanoc::Core::Item.new('Quz', {}, '/qux.md'), ] end shared_examples 'object_matching_glob' do context 'when object does not exist' do let(:arg) { '/nope.*' } it 'returns nil' do expect(object_matching_glob).to be_nil end end context 'when object exist' do let(:arg) { '/foo.*' } context 'when globs are enabled' do let(:config) { super().merge(string_pattern_type: 'glob') } it 'returns object' do expect(object_matching_glob).to eq(objects[0]) end end context 'when globs are disabled' do let(:config) { super().merge(string_pattern_type: 'legacy') } it 'returns nil' do expect(object_matching_glob).to be_nil end end end end context 'when frozen' do before { identifiable_collection.freeze } include_examples 'object_matching_glob' end context 'when not frozen' do include_examples 'object_matching_glob' end end describe '#find_all' do subject { identifiable_collection.find_all(arg) } let(:objects) do [ double(:identifiable, identifier: Nanoc::Core::Identifier.new('/about.css')), double(:identifiable, identifier: Nanoc::Core::Identifier.new('/about.md')), double(:identifiable, identifier: Nanoc::Core::Identifier.new('/style.css')), ] end let(:arg) { raise 'override me' } context 'with string' do let(:arg) { '/*.css' } it 'contains objects' do expect(subject.size).to be(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 be(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 '#freeze' do subject { identifiable_collection.freeze } let(:objects) do [ Nanoc::Core::Item.new('stuff', {}, Nanoc::Core::Identifier.new('/about.css')), Nanoc::Core::Item.new('stuff', {}, Nanoc::Core::Identifier.new('/about.md')), Nanoc::Core::Item.new('stuff', {}, Nanoc::Core::Identifier.new('/style.css')), ] end it 'freezes' do expect { subject } .to change(identifiable_collection, :frozen?) .from(false) .to(true) end end describe '#object_with_identifier' do subject { identifiable_collection.object_with_identifier(arg) } let(:objects) do [ Nanoc::Core::Item.new('stuff', {}, Nanoc::Core::Identifier.new('/about.css')), Nanoc::Core::Item.new('stuff', {}, Nanoc::Core::Identifier.new('/about.md')), Nanoc::Core::Item.new('stuff', {}, Nanoc::Core::Identifier.new('/style.css')), ] end let(:arg) { raise 'override me' } context 'with string' do let(:arg) { '/about.css' } it { is_expected.to eq(objects[0]) } end context 'with identifier' do let(:arg) { Nanoc::Core::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 subject { objects[0].identifier = '/bar' } let(:objects) do [ Nanoc::Core::Item.new('Foo', {}, '/foo'), ] end it 'makes /foo nil' do expect { subject } .to change { identifiable_collection.object_with_identifier('/foo') } .from(objects[0]) .to(nil) end it 'makes /bar non-nil' do expect { subject } .to change { identifiable_collection.object_with_identifier('/bar') } .from(nil) .to(objects[0]) end end describe '#each' do let(:objects) do [ Nanoc::Core::Item.new('Foo', {}, '/foo'), Nanoc::Core::Item.new('Bar', {}, '/bar'), ] end it 'loops' do res = identifiable_collection.map { _1.identifier.to_s } expect(res).to contain_exactly('/foo', '/bar') end end describe '#map' do let(:objects) do [ Nanoc::Core::Item.new('Foo', {}, '/foo'), Nanoc::Core::Item.new('Bar', {}, '/bar'), ] end it 'loops' do res = identifiable_collection.map { |i| i.identifier.to_s } expect(res).to contain_exactly('/foo', '/bar') end end end it 'cannot be instantiated' do expect { described_class.new } .to raise_error( RuntimeError, 'IdentifiableCollection is abstract and cannot be instantiated', ) end describe Nanoc::Core::ItemCollection do let(:expected_reference) { 'items' } it_behaves_like 'a generic identifiable collection' end describe Nanoc::Core::LayoutCollection do let(:expected_reference) { 'layouts' } it_behaves_like 'a generic identifiable collection' end end nanoc-4.13.3/nanoc-core/spec/nanoc/core/identifier_spec.rb000066400000000000000000000343611472033334600234010ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Core::Identifier do describe '.from' do subject { described_class.from(arg) } context 'given an identifier' do let(:arg) { described_class.new('/foo.md') } it 'returns an identifier' do expect(subject).to be_a(described_class) 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(described_class) expect(subject.to_s).to eq('/foo.md') expect(subject).to be_full end end context 'given something else' do let(:klass) do Class.new do def inspect 'this is #inspect' end end end let(:arg) { klass.new } it 'raises' do expect { subject }.to raise_error( Nanoc::Core::Identifier::NonCoercibleObjectError, 'this is #inspect cannot be converted into a Nanoc::Core::Identifier', ) 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::Core::Identifier::InvalidIdentifierError, 'Invalid identifier (does not start with a slash): "foo"', ) end it 'refuses string ending with a slash' do expect { described_class.new('/foo/') } .to raise_error( Nanoc::Core::Identifier::InvalidFullIdentifierError, 'Invalid full identifier (ends with a slash): "/foo/"', ) end it 'refuses string with only slash' do expect { described_class.new('/') } .to raise_error( Nanoc::Core::Identifier::InvalidFullIdentifierError, 'Invalid full identifier (ends with a slash): "/"', ) 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::Core::Identifier::InvalidTypeError, 'Invalid type for identifier: :donkey (can be :full or :legacy)', ) 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 be(true) end end describe '#inspect' do subject { identifier.inspect } let(:identifier) { described_class.new('/foo/bar') } it { is_expected.to eq '' } 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 be(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 be(false) end end end describe '#=~' do subject { identifier =~ pat } let(:identifier) { described_class.new('/foo/bar') } context 'given a regex' do context 'matching regex' do let(:pat) { %r{\A/foo/bar} } it { is_expected.to be(0) } end context 'non-matching regex' do let(:pat) { %r{\A/qux/monkey} } it { is_expected.to be_nil } end end context 'given a string' do context 'matching string' do let(:pat) { '/foo/*' } it { is_expected.to be(0) } end context 'non-matching string' do let(:pat) { '/qux/*' } it { is_expected.to be_nil } end end end describe '#match?' do subject { identifier.match?(pat) } let(:identifier) { described_class.new('/foo/bar') } 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 be(1) expect(identifier <=> '/foo/bar').to be(0) expect(identifier <=> '/foo/qux').to be(-1) end end describe '#prefix' do subject { identifier.prefix(prefix) } let(:identifier) { described_class.new('/foo') } context 'prefix not ending nor starting with a slash' do let(:prefix) { 'asdf' } it 'raises an error' do expect { subject }.to raise_error( Nanoc::Core::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::Core::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(described_class) 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(described_class) 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::Core::Identifier::UnsupportedLegacyOperationError, 'Cannot use this method on legacy identifiers', ) 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::Core::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::Core::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::Core::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 be(true) } end context 'full type' do let(:identifier) { described_class.new('/foo', type: :full) } it { is_expected.to be(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 be(false) } end context 'full type' do let(:identifier) { described_class.new('/foo', type: :full) } it { is_expected.to be(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.13.3/nanoc-core/spec/nanoc/core/in_memory_data_source_spec.rb000066400000000000000000000020311472033334600256130ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Core::InMemoryDataSource, stdio: true do subject(:data_source) do described_class.new(items, layouts, original_data_source) end let(:items) { Nanoc::Core::ItemCollection.new(config, []) } let(:layouts) { Nanoc::Core::LayoutCollection.new(config, []) } let(:config) { Nanoc::Core::Configuration.new(dir: Dir.getwd) } let(:klass) do Class.new(Nanoc::Core::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 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.13.3/nanoc-core/spec/nanoc/core/instrumentor_spec.rb000066400000000000000000000022411472033334600240200ustar00rootroot00000000000000# frozen_string_literal: true describe(Nanoc::Core::Instrumentor) do subject { described_class } before { Timecop.freeze(Time.local(2008, 9, 1, 10, 5, 0)) } after { Timecop.return } context 'when not enabled (i.e. by default)' do it 'does not send 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.not_to send_notification(:sample_notification, 5.0, 'garbage', 123) end end context 'when enabled' do around do |ex| described_class.enable { ex.run } end before do a = Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond) allow(Process) .to receive(:clock_gettime) .with(Process::CLOCK_MONOTONIC, :nanosecond) .and_return(a, a + 5_000_000_000) end it 'sends notification' do expect do subject.call(:sample_notification, 'garbage', 123) do # pass time, as defined by the clock_gettime mock end end.to send_notification(:sample_notification, 5.0, 'garbage', 123) end end end nanoc-4.13.3/nanoc-core/spec/nanoc/core/item_collection_with_reps_view_spec.rb000066400000000000000000000013361472033334600275420ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'support/identifiable_collection_view_examples' describe Nanoc::Core::ItemCollectionWithRepsView do let(:view_class) { Nanoc::Core::CompilationItemView } let(:collection_class) { Nanoc::Core::ItemCollection } it_behaves_like 'an identifiable collection view' do let(:element_class) { Nanoc::Core::Item } end describe '#inspect' do subject { view.inspect } let(:wrapped) do Nanoc::Core::ItemCollection.new(config) end let(:view) { described_class.new(wrapped, view_context) } let(:view_context) { nil } let(:config) { { string_pattern_type: 'glob' } } it { is_expected.to eql('') } end end nanoc-4.13.3/nanoc-core/spec/nanoc/core/item_collection_without_reps_view_spec.rb000066400000000000000000000013361472033334600302720ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'support/identifiable_collection_view_examples' describe Nanoc::Core::ItemCollectionWithoutRepsView do let(:view_class) { Nanoc::Core::BasicItemView } let(:collection_class) { Nanoc::Core::ItemCollection } it_behaves_like 'an identifiable collection view' do let(:element_class) { Nanoc::Core::Item } end describe '#inspect' do subject { view.inspect } let(:wrapped) do Nanoc::Core::ItemCollection.new(config) end let(:view) { described_class.new(wrapped, view_context) } let(:view_context) { nil } let(:config) { { string_pattern_type: 'glob' } } it { is_expected.to eql('') } end end nanoc-4.13.3/nanoc-core/spec/nanoc/core/item_rep_builder_spec.rb000066400000000000000000000121201472033334600245560ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Core::ItemRepBuilder do describe '.snapshot_defs' do let(:item) { Nanoc::Core::Item.new('asdf', {}, '/foo.md') } let(:rep) { Nanoc::Core::ItemRep.new(item, :default) } Class.new(Nanoc::Core::Filter) do identifier :RuleMemSpec_filter_b2b type :binary => :binary # rubocop:disable Style/HashSyntax def run(content, params = {}); end end Class.new(Nanoc::Core::Filter) do identifier :RuleMemSpec_filter_b2t type :binary => :text # rubocop:disable Style/HashSyntax def run(content, params = {}); end end Class.new(Nanoc::Core::Filter) do identifier :RuleMemSpec_filter_t2t type :text => :text # rubocop:disable Style/HashSyntax def run(content, params = {}); end end Class.new(Nanoc::Core::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 = Nanoc::Core::ActionSequenceBuilder.build do |b| end snapshot_defs = described_class.send(:snapshot_defs_for, action_sequence, rep) expect(snapshot_defs).to be_empty end context 'textual item' do let(:item) { Nanoc::Core::Item.new('asdf', {}, '/foo.md') } it 'generates initial textual snapshot def' do action_sequence = Nanoc::Core::ActionSequenceBuilder.build do |b| b.add_snapshot(:giraffe, nil, rep) end snapshot_defs = described_class.send(:snapshot_defs_for, action_sequence, rep) expect(snapshot_defs.size).to eq(1) expect(snapshot_defs[0].name).to eq(:giraffe) expect(snapshot_defs[0]).not_to be_binary end it 'generated follow-up textual snapshot def if previous filter is textual' do action_sequence = Nanoc::Core::ActionSequenceBuilder.build do |b| b.add_snapshot(:giraffe, nil, rep) b.add_filter(:RuleMemSpec_filter_t2t, arguments: 'irrelevant') b.add_snapshot(:zebra, nil, rep) end snapshot_defs = described_class.send(:snapshot_defs_for, action_sequence, rep) expect(snapshot_defs.size).to eq(2) expect(snapshot_defs[0].name).to eq(:giraffe) expect(snapshot_defs[0]).not_to be_binary expect(snapshot_defs[1].name).to eq(:zebra) expect(snapshot_defs[1]).not_to be_binary end it 'generated follow-up binary snapshot def if previous filter is text-to-bianry' do action_sequence = Nanoc::Core::ActionSequenceBuilder.build do |b| b.add_snapshot(:giraffe, nil, rep) b.add_filter(:RuleMemSpec_filter_t2b, arguments: 'irrelevant') b.add_snapshot(:zebra, nil, rep) end snapshot_defs = described_class.send(:snapshot_defs_for, action_sequence, rep) expect(snapshot_defs.size).to eq(2) expect(snapshot_defs[0].name).to eq(:giraffe) expect(snapshot_defs[0]).not_to be_binary expect(snapshot_defs[1].name).to eq(:zebra) expect(snapshot_defs[1]).to be_binary end end context 'binary item' do let(:item) { Nanoc::Core::Item.new(Nanoc::Core::BinaryContent.new('/asdf.dat'), {}, '/foo.md') } it 'generates initial binary snapshot def' do action_sequence = Nanoc::Core::ActionSequenceBuilder.build do |b| b.add_snapshot(:giraffe, nil, rep) end snapshot_defs = described_class.send(:snapshot_defs_for, action_sequence, rep) expect(snapshot_defs.size).to eq(1) expect(snapshot_defs[0].name).to eq(:giraffe) expect(snapshot_defs[0]).to be_binary end it 'generated follow-up binary snapshot def if previous filter is binary' do action_sequence = Nanoc::Core::ActionSequenceBuilder.build do |b| b.add_snapshot(:giraffe, nil, rep) b.add_filter(:RuleMemSpec_filter_b2b, arguments: 'irrelevant') b.add_snapshot(:zebra, nil, rep) end snapshot_defs = described_class.send(:snapshot_defs_for, action_sequence, rep) expect(snapshot_defs.size).to eq(2) expect(snapshot_defs[0].name).to eq(:giraffe) expect(snapshot_defs[0]).to be_binary expect(snapshot_defs[1].name).to eq(:zebra) expect(snapshot_defs[1]).to be_binary end it 'generated follow-up textual snapshot def if previous filter is binary-to-text' do action_sequence = Nanoc::Core::ActionSequenceBuilder.build do |b| b.add_snapshot(:giraffe, nil, rep) b.add_filter(:RuleMemSpec_filter_b2t, arguments: 'irrelevant') b.add_snapshot(:zebra, nil, rep) end snapshot_defs = described_class.send(:snapshot_defs_for, action_sequence, rep) expect(snapshot_defs.size).to eq(2) expect(snapshot_defs[0].name).to eq(:giraffe) expect(snapshot_defs[0]).to be_binary expect(snapshot_defs[1].name).to eq(:zebra) expect(snapshot_defs[1]).not_to be_binary end end end end nanoc-4.13.3/nanoc-core/spec/nanoc/core/item_rep_router_spec.rb000066400000000000000000000153531472033334600244630ustar00rootroot00000000000000# frozen_string_literal: true describe(Nanoc::Core::ItemRepRouter) do subject(:item_rep_router) { described_class.new(reps, action_provider, site) } let(:config) { Nanoc::Core::Configuration.new(dir: Dir.getwd).with_defaults } let(:site) do Nanoc::Core::Site.new( config:, code_snippets: [], data_source: Nanoc::Core::InMemoryDataSource.new(items, layouts), ) end let(:items) { Nanoc::Core::ItemCollection.new(config, []) } let(:layouts) { Nanoc::Core::LayoutCollection.new(config, []) } let(:action_provider) do Class.new(Nanoc::Core::ActionProvider) do def self.for(_context) raise NotImplementedError end def initialize @map = {} end def []=(key, value) @map[key] = value end def rep_names_for(_item) raise NotImplementedError end def action_sequence_for(obj) @map.fetch(obj) end def snapshots_defs_for(_rep) raise NotImplementedError end end.new end let(:item) { Nanoc::Core::Item.new('content', {}, '/foo.md') } let(:reps) do Nanoc::Core::ItemRepRepo.new.tap do |r| r << Nanoc::Core::ItemRep.new(item, :default) r << Nanoc::Core::ItemRep.new(item, :csv) end end describe '#run' do subject { item_rep_router.run } let(:memory_without_paths) do actions = [ Nanoc::Core::ProcessingActions::Filter.new(:erb, {}), Nanoc::Core::ProcessingActions::Snapshot.new([], []), ] Nanoc::Core::ActionSequence.new(actions:) end let(:action_sequence_for_default) do actions = [ Nanoc::Core::ProcessingActions::Filter.new(:erb, {}), Nanoc::Core::ProcessingActions::Snapshot.new([:last], ['/foo/index.html']), ] Nanoc::Core::ActionSequence.new(actions:) end let(:action_sequence_for_csv) do actions = [ Nanoc::Core::ProcessingActions::Filter.new(:erb, {}), Nanoc::Core::ProcessingActions::Snapshot.new([:last], ['/foo.csv']), ] Nanoc::Core::ActionSequence.new(actions:) end example do action_provider[reps[item][0]] = action_sequence_for_default action_provider[reps[item][1]] = action_sequence_for_csv subject expect(reps[item][0].raw_paths).to eql(last: [Dir.getwd + '/output/foo/index.html']) expect(reps[item][0].paths).to eql(last: ['/foo/']) expect(reps[item][1].raw_paths).to eql(last: [Dir.getwd + '/output/foo.csv']) expect(reps[item][1].paths).to eql(last: ['/foo.csv']) end it 'picks the paths last returned' do action_provider[reps[item][0]] = action_sequence_for_default action_provider[reps[item][1]] = action_sequence_for_csv subject expect(reps[item][0].raw_paths).to eql(last: [Dir.getwd + '/output/foo/index.html']) expect(reps[item][0].paths).to eql(last: ['/foo/']) expect(reps[item][1].raw_paths).to eql(last: [Dir.getwd + '/output/foo.csv']) expect(reps[item][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(:paths_to_reps) { {} } let(:rep) { reps[item][0] } 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::Core::ItemRep.new(item, :other) } } it 'errors' do expect { subject }.to raise_error(Nanoc::Core::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::Core::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.13.3/nanoc-core/spec/nanoc/core/item_rep_selector/000077500000000000000000000000001472033334600234155ustar00rootroot00000000000000nanoc-4.13.3/nanoc-core/spec/nanoc/core/item_rep_selector/item_rep_priority_queue_spec.rb000066400000000000000000000171001472033334600317240ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Core::ItemRepSelector::ItemRepPriorityQueue do subject(:micro_graph) { described_class.new(reps) } let(:items) do [ Nanoc::Core::Item.new('item A', {}, '/a.md'), Nanoc::Core::Item.new('item B', {}, '/b.md'), Nanoc::Core::Item.new('item C', {}, '/c.md'), Nanoc::Core::Item.new('item D', {}, '/d.md'), Nanoc::Core::Item.new('item E', {}, '/e.md'), ] end let(:reps) do [ Nanoc::Core::ItemRep.new(items[0], :default), Nanoc::Core::ItemRep.new(items[1], :default), Nanoc::Core::ItemRep.new(items[2], :default), Nanoc::Core::ItemRep.new(items[3], :default), Nanoc::Core::ItemRep.new(items[4], :default), ] end context 'when there are no dependencies' do it 'runs through reps in order' do expect(micro_graph.next).to eq(reps[0]) micro_graph.mark_ok expect(micro_graph.next).to eq(reps[1]) micro_graph.mark_ok expect(micro_graph.next).to eq(reps[2]) micro_graph.mark_ok expect(micro_graph.next).to eq(reps[3]) micro_graph.mark_ok expect(micro_graph.next).to eq(reps[4]) micro_graph.mark_ok expect(micro_graph.next).to be_nil end end context 'when is a simple dependency' do it 'schedules the dependency next up' do expect(micro_graph.next).to eq(reps[0]) micro_graph.mark_failed(reps[2]) expect(micro_graph.next).to eq(reps[2]) micro_graph.mark_ok expect(micro_graph.next).to eq(reps[1]) micro_graph.mark_ok expect(micro_graph.next).to eq(reps[3]) micro_graph.mark_ok expect(micro_graph.next).to eq(reps[4]) micro_graph.mark_ok expect(micro_graph.next).to eq(reps[0]) micro_graph.mark_ok expect(micro_graph.next).to be_nil end end context 'when is a transitive dependency' do it 'schedules the dependency next up' do expect(micro_graph.next).to eq(reps[0]) micro_graph.mark_failed(reps[2]) expect(micro_graph.next).to eq(reps[2]) micro_graph.mark_failed(reps[4]) expect(micro_graph.next).to eq(reps[4]) micro_graph.mark_ok expect(micro_graph.next).to eq(reps[1]) micro_graph.mark_ok expect(micro_graph.next).to eq(reps[3]) micro_graph.mark_ok expect(micro_graph.next).to eq(reps[2]) micro_graph.mark_ok expect(micro_graph.next).to eq(reps[0]) micro_graph.mark_ok expect(micro_graph.next).to be_nil end end context 'when is a circular dependency of size 2' do it 'schedules the dependency next up' do expect(micro_graph.next).to eq(reps[0]) micro_graph.mark_failed(reps[2]) expect(micro_graph.next).to eq(reps[2]) expect { micro_graph.mark_failed(reps[0]) } .to raise_error(Nanoc::Core::Errors::DependencyCycle) end end context 'when is a circular dependency of size 3' do it 'schedules the dependency next up' do expect(micro_graph.next).to eq(reps[0]) micro_graph.mark_failed(reps[2]) expect(micro_graph.next).to eq(reps[2]) micro_graph.mark_failed(reps[4]) expect(micro_graph.next).to eq(reps[4]) expect { micro_graph.mark_failed(reps[0]) } .to raise_error(Nanoc::Core::Errors::DependencyCycle) end end context 'when is a circular dependency of size 4' do it 'schedules the dependency next up' do expect(micro_graph.next).to eq(reps[0]) micro_graph.mark_failed(reps[2]) expect(micro_graph.next).to eq(reps[2]) micro_graph.mark_failed(reps[4]) expect(micro_graph.next).to eq(reps[4]) micro_graph.mark_failed(reps[3]) expect(micro_graph.next).to eq(reps[3]) expect { micro_graph.mark_failed(reps[0]) } .to raise_error(Nanoc::Core::Errors::DependencyCycle) end end context 'when there are two regular dependencies' do it 'schedules the dependency next up' do expect(micro_graph.next).to eq(reps[0]) micro_graph.mark_failed(reps[2]) expect(micro_graph.next).to eq(reps[2]) micro_graph.mark_ok expect(micro_graph.next).to eq(reps[1]) micro_graph.mark_failed(reps[4]) expect(micro_graph.next).to eq(reps[4]) micro_graph.mark_ok expect(micro_graph.next).to eq(reps[3]) micro_graph.mark_ok expect(micro_graph.next).to eq(reps[1]) micro_graph.mark_ok expect(micro_graph.next).to eq(reps[0]) micro_graph.mark_ok expect(micro_graph.next).to be_nil end end context 'when there is one regular dependency and one cyclical dependency' do it 'schedules the dependency next up' do expect(micro_graph.next).to eq(reps[0]) micro_graph.mark_failed(reps[2]) expect(micro_graph.next).to eq(reps[2]) micro_graph.mark_ok expect(micro_graph.next).to eq(reps[1]) micro_graph.mark_failed(reps[4]) expect(micro_graph.next).to eq(reps[4]) expect { micro_graph.mark_failed(reps[1]) } .to raise_error(Nanoc::Core::Errors::DependencyCycle) end end context 'when there is a transitive dependency' do it 'schedules the dependency next up' do expect(micro_graph.next).to eq(reps[0]) micro_graph.mark_failed(reps[1]) expect(micro_graph.next).to eq(reps[1]) micro_graph.mark_failed(reps[2]) expect(micro_graph.next).to eq(reps[2]) micro_graph.mark_failed(reps[3]) expect(micro_graph.next).to eq(reps[3]) micro_graph.mark_failed(reps[4]) expect(micro_graph.next).to eq(reps[4]) micro_graph.mark_ok expect(micro_graph.next).to eq(reps[3]) micro_graph.mark_ok expect(micro_graph.next).to eq(reps[2]) micro_graph.mark_ok expect(micro_graph.next).to eq(reps[1]) micro_graph.mark_ok expect(micro_graph.next).to eq(reps[0]) micro_graph.mark_ok expect(micro_graph.next).to be_nil end end context 'when there is an item with dependencies on many other items that also have dependences' do # 0 -> 2 # 2 -> 4 # 4 OK # 1 -> 2 # 2 -> 3 # 3 OK it 'schedules the dependency next up' do expect(micro_graph.next).to eq(reps[0]) micro_graph.mark_failed(reps[2]) expect(micro_graph.next).to eq(reps[2]) micro_graph.mark_failed(reps[4]) expect(micro_graph.next).to eq(reps[4]) micro_graph.mark_ok expect(micro_graph.next).to eq(reps[1]) micro_graph.mark_failed(reps[2]) expect(micro_graph.next).to eq(reps[2]) micro_graph.mark_failed(reps[3]) expect(micro_graph.next).to eq(reps[3]) micro_graph.mark_ok expect(micro_graph.next).to eq(reps[1]) micro_graph.mark_ok expect(micro_graph.next).to eq(reps[2]) micro_graph.mark_ok expect(micro_graph.next).to eq(reps[0]) micro_graph.mark_ok expect(micro_graph.next).to be_nil end end context 'when there is an item with dependencies on an item that was delayed due to another dependency' do # 0 -> 3 # 3 OK # 1 -> 0 # 0 OK it 'schedules the dependency next up' do expect(micro_graph.next).to eq(reps[0]) micro_graph.mark_failed(reps[3]) expect(micro_graph.next).to eq(reps[3]) micro_graph.mark_ok expect(micro_graph.next).to eq(reps[1]) micro_graph.mark_failed(reps[0]) expect(micro_graph.next).to eq(reps[0]) micro_graph.mark_ok expect(micro_graph.next).to eq(reps[2]) micro_graph.mark_ok expect(micro_graph.next).to eq(reps[4]) micro_graph.mark_ok expect(micro_graph.next).to eq(reps[1]) micro_graph.mark_ok expect(micro_graph.next).to be_nil end end end nanoc-4.13.3/nanoc-core/spec/nanoc/core/item_rep_selector_spec.rb000066400000000000000000000126771472033334600247710ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Core::ItemRepSelector do let(:selector) { described_class.new(reps_for_selector) } let(:item) do Nanoc::Core::Item.new('stuff', {}, '/foo.md') end let(:reps_array) do [ Nanoc::Core::ItemRep.new(item, :a), Nanoc::Core::ItemRep.new(item, :b), Nanoc::Core::ItemRep.new(item, :c), Nanoc::Core::ItemRep.new(item, :d), Nanoc::Core::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::Core::Errors::UnmetDependency.new(names_to_reps[name], :foo) 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::Core::Errors::UnmetDependency.new(reps_array[2], :foo) 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| raise 'heh' rescue => e raise Nanoc::Core::Errors::CompilationError.new(e, rep) end end it 'raises original error' do expect { subject }.to raise_error(Nanoc::Core::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::Core::Errors::UnmetDependency.new(reps_array[2], :foo) if idx == 1 rescue => e raise Nanoc::Core::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::Core::Errors::UnmetDependency.new(r, :foo) } end example do expect { subject }.to raise_error(Nanoc::Core::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::Core::Errors::UnmetDependency.new(reps_array[1], :foo) when reps_array[1] raise Nanoc::Core::Errors::UnmetDependency.new(reps_array[2], :foo) when reps_array[2] raise Nanoc::Core::Errors::UnmetDependency.new(reps_array[0], :foo) end end end example do expect { subject }.to raise_error(Nanoc::Core::Errors::DependencyCycle, <<~EOS) The site cannot be compiled because there is a dependency cycle: (1) item /foo.md, rep :c, uses compiled content of (2) item /foo.md, rep :a, uses compiled content of (3) item /foo.md, rep :b, 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 c d 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 e c b a] expect(tentatively_yielded).to eq %i[a d b e c b a] end end end end nanoc-4.13.3/nanoc-core/spec/nanoc/core/item_rep_spec.rb000066400000000000000000000012241472033334600230530ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Core::ItemRep do let(:item) { Nanoc::Core::Item.new('asdf', {}, '/foo.md') } let(:rep) { described_class.new(item, :giraffe) } describe '#snapshot?' do subject { rep.snapshot?(snapshot_name) } let(:snapshot_name) { raise 'override me' } before do rep.snapshot_defs = [Nanoc::Core::SnapshotDef.new(:donkey, binary: false)] end context 'snapshot does not exist' do let(:snapshot_name) { :giraffe } it { is_expected.to be(false) } end context 'snapshot exists' do let(:snapshot_name) { :donkey } it { is_expected.to be(true) } end end end nanoc-4.13.3/nanoc-core/spec/nanoc/core/item_rep_writer_spec.rb000066400000000000000000000101121472033334600244430ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Core::ItemRepWriter do describe '#write' do subject { described_class.new.write(item_rep, compiled_content_store, snapshot_name, written_paths) } let(:raw_path) { Dir.getwd + '/output/blah.dat' } let(:item) { Nanoc::Core::Item.new(orig_content, {}, '/foo') } let(:item_rep) do Nanoc::Core::ItemRep.new(item, :default).tap do |ir| ir.raw_paths = raw_paths end end let(:snapshot_contents) do { last: Nanoc::Core::TextualContent.new('last content'), donkey: Nanoc::Core::TextualContent.new('donkey content'), } end let(:snapshot_name) { :donkey } let(:raw_paths) do { snapshot_name => [raw_path] } end let(:compiled_content_store) { Nanoc::Core::CompiledContentStore.new } let(:written_paths) { [] } before do expect(File.directory?('output')).to be_falsy snapshot_contents.each_pair do |key, value| compiled_content_store.set(item_rep, key, value) end end context 'binary item rep' do let(:orig_content) { Nanoc::Core::BinaryContent.new(File.expand_path('foo.dat')) } let(:snapshot_contents) do { last: Nanoc::Core::BinaryContent.new(File.expand_path('input-last.dat')), donkey: Nanoc::Core::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::Core::NotificationCenter).to receive(:post) .with(:rep_write_started, item_rep, Dir.getwd + '/output/blah.dat') expect(Nanoc::Core::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 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::Core::TextualContent.new('Hallo Welt') } it 'writes' do expect(Nanoc::Core::NotificationCenter).to receive(:post) .with(:rep_write_started, item_rep, Dir.getwd + '/output/blah.dat') expect(Nanoc::Core::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.13.3/nanoc-core/spec/nanoc/core/item_spec.rb000066400000000000000000000007571472033334600222170ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Core::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 it 'updates reference after updating identifier' do expect { item.identifier = '/foo2.md' } .to change(item, :reference) .from('item:/foo.md') .to('item:/foo2.md') end end end nanoc-4.13.3/nanoc-core/spec/nanoc/core/layout_collection_view_spec.rb000066400000000000000000000013171472033334600260340ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'support/identifiable_collection_view_examples' describe Nanoc::Core::LayoutCollectionView do let(:view_class) { Nanoc::Core::LayoutView } let(:collection_class) { Nanoc::Core::LayoutCollection } it_behaves_like 'an identifiable collection view' do let(:element_class) { Nanoc::Core::Layout } end describe '#inspect' do subject { view.inspect } let(:wrapped) do Nanoc::Core::LayoutCollection.new(config) end let(:view) { described_class.new(wrapped, view_context) } let(:view_context) { nil } let(:config) { { string_pattern_type: 'glob' } } it { is_expected.to eql('') } end end nanoc-4.13.3/nanoc-core/spec/nanoc/core/layout_spec.rb000066400000000000000000000007771472033334600226000ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Core::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 it 'updates reference after updating identifier' do expect { layout.identifier = '/foo2.md' } .to change(layout, :reference) .from('layout:/foo.md') .to('layout:/foo2.md') end end end nanoc-4.13.3/nanoc-core/spec/nanoc/core/layout_view_spec.rb000066400000000000000000000010121472033334600236110ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'support/document_view_examples' describe Nanoc::Core::LayoutView do let(:entity_class) { Nanoc::Core::Layout } let(:other_view_class) { Nanoc::Core::CompilationItemView } it_behaves_like 'a document view' describe '#inspect' do subject { view.inspect } let(:item) { Nanoc::Core::Layout.new('content', {}, '/asdf') } let(:view) { described_class.new(item, nil) } it { is_expected.to eql('') } end end nanoc-4.13.3/nanoc-core/spec/nanoc/core/lazy_value_spec.rb000066400000000000000000000065601472033334600234320ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Core::LazyValue do describe '#value' do subject { lazy_value.value } let(:value_arg) { +'Hello world' } let(:lazy_value) { described_class.new(value_arg) } 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 subject { lazy_value.map(&:upcase) } let(:value_arg) { -> { 'Hello world' } } let(:lazy_value) { described_class.new(value_arg) } 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 subject { described_class.new(value_arg) } let(:value_arg) { 'Hello world' } 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 be(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 be(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.13.3/nanoc-core/spec/nanoc/core/mutable_config_view_spec.rb000066400000000000000000000006671472033334600252710ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Core::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.13.3/nanoc-core/spec/nanoc/core/mutable_item_collection_view_spec.rb000066400000000000000000000033321472033334600271650ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'support/identifiable_collection_view_examples' require_relative 'support/mutable_identifiable_collection_view_examples' describe Nanoc::Core::MutableItemCollectionView do let(:view_class) { Nanoc::Core::MutableItemView } let(:collection_class) { Nanoc::Core::ItemCollection } let(:config) do { string_pattern_type: 'glob' } end it_behaves_like 'an identifiable collection view' do let(:element_class) { Nanoc::Core::Item } end it_behaves_like 'a mutable identifiable collection view' describe '#create' do let(:item) do Nanoc::Core::Layout.new('content', {}, '/asdf') end let(:wrapped) do Nanoc::Core::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.object_with_identifier('/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.object_with_identifier('/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 subject { view.inspect } let(:wrapped) do Nanoc::Core::ItemCollection.new(config) end let(:view) { described_class.new(wrapped, view_context) } let(:view_context) { nil } let(:config) { { string_pattern_type: 'glob' } } it { is_expected.to eql('') } end end nanoc-4.13.3/nanoc-core/spec/nanoc/core/mutable_item_view_spec.rb000066400000000000000000000013741472033334600247560ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'support/mutable_document_view_examples' describe Nanoc::Core::MutableItemView do let(:entity_class) { Nanoc::Core::Item } let(:view) { described_class.new(item, nil) } let(:item) { entity_class.new('content', {}, '/asdf') } it_behaves_like 'a mutable document view' 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 subject { view.inspect } let(:item) { Nanoc::Core::Item.new('content', {}, '/asdf') } let(:view) { described_class.new(item, nil) } it { is_expected.to eql('') } end end nanoc-4.13.3/nanoc-core/spec/nanoc/core/mutable_layout_collection_view_spec.rb000066400000000000000000000033551472033334600275510ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'support/identifiable_collection_view_examples' require_relative 'support/mutable_identifiable_collection_view_examples' describe Nanoc::Core::MutableLayoutCollectionView do let(:view_class) { Nanoc::Core::MutableLayoutView } let(:collection_class) { Nanoc::Core::LayoutCollection } let(:config) do { string_pattern_type: 'glob' } end it_behaves_like 'an identifiable collection view' do let(:element_class) { Nanoc::Core::Layout } end it_behaves_like 'a mutable identifiable collection view' describe '#create' do let(:layout) do Nanoc::Core::Layout.new('content', {}, '/asdf') end let(:wrapped) do Nanoc::Core::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.object_with_identifier('/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.object_with_identifier('/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 subject { view.inspect } let(:wrapped) do Nanoc::Core::LayoutCollection.new(config) end let(:view) { described_class.new(wrapped, view_context) } let(:view_context) { nil } let(:config) { { string_pattern_type: 'glob' } } it { is_expected.to eql('') } end end nanoc-4.13.3/nanoc-core/spec/nanoc/core/mutable_layout_view_spec.rb000066400000000000000000000007501472033334600253320ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'support/mutable_document_view_examples' describe Nanoc::Core::MutableLayoutView do let(:entity_class) { Nanoc::Core::Layout } it_behaves_like 'a mutable document view' describe '#inspect' do subject { view.inspect } let(:item) { Nanoc::Core::Item.new('content', {}, '/asdf') } let(:view) { described_class.new(item, nil) } it { is_expected.to eql('') } end end nanoc-4.13.3/nanoc-core/spec/nanoc/core/notification_center_spec.rb000066400000000000000000000014541472033334600253020ustar00rootroot00000000000000# frozen_string_literal: true shared_examples 'a notification center' do it 'receives notification after subscribing' do res = false subject.on :ping_received, :test do res = true end subject.post(:ping_received).sync expect(res).to be(true) end it 'does not receive notification after unsubscribing' do res = false subject.on :ping_received, :test do res = true end subject.remove :ping_received, :test subject.post(:ping_received).sync expect(res).to be(false) end end describe Nanoc::Core::NotificationCenter do describe 'class' do subject { described_class } it_behaves_like 'a notification center' end describe 'instance' do subject { described_class.instance } it_behaves_like 'a notification center' end end nanoc-4.13.3/nanoc-core/spec/nanoc/core/outdatedness_checker_spec.rb000066400000000000000000001721501472033334600254440ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Core::OutdatednessChecker do Class.new(Nanoc::Core::Filter) do identifier :always_outdated_3zh5qfqlqysghkd5ipek8glxzrljrylr always_outdated def run(content, _params) content.upcase end end let(:site) do Nanoc::Core::Site.new( config: config_after, code_snippets: code_snippets_after, data_source: Nanoc::Core::InMemoryDataSource.new(items_after_coll, layouts_after_coll), ) end let(:config_before) { Nanoc::Core::Configuration.new(dir: Dir.getwd).with_defaults } let(:config_after) { config_before } let(:code_snippet_a_before) { Nanoc::Core::CodeSnippet.new('aaa', 'lib/a.rb') } let(:code_snippet_b_before) { Nanoc::Core::CodeSnippet.new('bbb', 'lib/b.rb') } let(:code_snippet_a_after) { code_snippet_a_before } let(:code_snippet_b_after) { code_snippet_b_before } let(:code_snippets_before) { [code_snippet_a_before, code_snippet_b_before] } let(:code_snippets_after) { [code_snippet_a_after, code_snippet_b_after] } let(:item_home_before) { Nanoc::Core::Item.new('Home', {}, '/home.md') } let(:item_home_rep_before) { Nanoc::Core::ItemRep.new(item_home_before, :default) } let(:item_articles_before) { Nanoc::Core::Item.new('Articles', {}, '/articles.html.erb') } let(:item_articles_rep_before) { Nanoc::Core::ItemRep.new(item_articles_before, :default) } let(:item_article_a_before) { Nanoc::Core::Item.new('Article A', {}, '/articles/2019-a.md') } let(:item_article_a_rep_before) { Nanoc::Core::ItemRep.new(item_article_a_before, :default) } let(:item_article_b_before) { Nanoc::Core::Item.new('Article B', {}, '/articles/2022-b.md') } let(:item_article_b_rep_before) { Nanoc::Core::ItemRep.new(item_article_b_before, :default) } let(:item_article_c_before) { Nanoc::Core::Item.new('Article C', {}, '/articles/2022-c.md') } let(:item_article_c_rep_before) { Nanoc::Core::ItemRep.new(item_article_c_before, :default) } let(:item_home_after) { item_home_before } let(:item_home_rep_after) { item_home_rep_before } let(:item_articles_after) { item_articles_before } let(:item_articles_rep_after) { item_articles_rep_before } let(:item_article_a_after) { item_article_a_before } let(:item_article_a_rep_after) { item_article_a_rep_before } let(:item_article_b_after) { item_article_b_before } let(:item_article_b_rep_after) { item_article_b_rep_before } let(:item_article_c_after) { item_article_c_before } let(:item_article_c_rep_after) { item_article_c_rep_before } let(:items_before_array) { [item_home_before, item_articles_before, item_article_a_before, item_article_b_before, item_article_c_before] } let(:items_after_array) { [item_home_after, item_articles_after, item_article_a_after, item_article_b_after, item_article_c_after] } let(:items_before_coll) { Nanoc::Core::ItemCollection.new(config_before, items_before_array) } let(:items_after_coll) { Nanoc::Core::ItemCollection.new(config_after, items_after_array) } let(:reps) do Nanoc::Core::ItemRepRepo.new.tap do |rr| rr << item_home_rep_after rr << item_articles_rep_after rr << item_article_a_rep_after rr << item_article_b_rep_after rr << item_article_c_rep_after end end let(:layout_default_before) { Nanoc::Core::Layout.new('Default', { kind: 'default' }, '/default.html.erb') } let(:layout_articles_before) { Nanoc::Core::Layout.new('Articles', { kind: 'article' }, '/articles.html.erb') } let(:layout_default_after) { layout_default_before } let(:layout_articles_after) { layout_articles_before } let(:layouts_before_array) { [layout_default_before, layout_articles_before] } let(:layouts_after_array) { [layout_default_after, layout_articles_after] } let(:layouts_before_coll) { Nanoc::Core::LayoutCollection.new(config_before, layouts_before_array) } let(:layouts_after_coll) { Nanoc::Core::LayoutCollection.new(config_after, layouts_after_array) } let(:outdatedness_checker) do described_class.new( site:, checksum_store:, checksums: checksums_after, dependency_store:, action_sequence_store:, action_sequences: action_sequences_after, reps:, ) end let(:checksum_store) do Nanoc::Core::ChecksumStore.new( config: config_before, objects: items_before_array + layouts_before_array, ).tap do |store| store.checksums = checksums_before.to_h end end let(:checksums_before) do Nanoc::Core::CompilationStages::CalculateChecksums.new( items: items_before_coll, layouts: layouts_before_coll, code_snippets: code_snippets_before, config: config_before, ).run end let(:checksums_after) do Nanoc::Core::CompilationStages::CalculateChecksums.new( items: items_after_coll, layouts: layouts_after_coll, code_snippets: code_snippets_after, config: config_after, ).run end let(:dependency_store) do # NOTE: No dependencies to start with, but those will be filled in on an # ad-hoc basis. Nanoc::Core::DependencyStore.new( items_before_coll, layouts_before_coll, config_before, ) end let(:action_sequence_store) do Nanoc::Core::ActionSequenceStore.new(config: config_before).tap do |store| action_sequences_before.each_pair do |obj, action_sequence| store[obj] = action_sequence.serialize end end end let(:action_sequences_before) do { item_home_rep_before => some_action_sequence_for_item_rep, item_articles_rep_before => some_action_sequence_for_item_rep, item_article_a_rep_before => some_action_sequence_for_item_rep, item_article_b_rep_before => some_action_sequence_for_item_rep, item_article_c_rep_before => some_action_sequence_for_item_rep, layout_default_before => some_action_sequence_for_layout, layout_articles_before => some_action_sequence_for_layout, } end let(:action_sequences_after) { action_sequences_before } let(:some_action_sequence_for_item_rep) do Nanoc::Core::ActionSequenceBuilder.build do |b| b.add_filter(:erb, {}) end end let(:some_action_sequence_for_layout) do Nanoc::Core::ActionSequenceBuilder.build do |b| b.add_filter(:erb, {}) end end let(:different_action_sequence_for_item_rep) do Nanoc::Core::ActionSequenceBuilder.build do |b| b.add_filter(:xyzzy, {}) end end let(:different_action_sequence_for_layout) do Nanoc::Core::ActionSequenceBuilder.build do |b| b.add_filter(:xyzzy, {}) end end let(:always_outdated_action_sequence_for_item_rep) do Nanoc::Core::ActionSequenceBuilder.build do |b| b.add_filter(:always_outdated_3zh5qfqlqysghkd5ipek8glxzrljrylr, {}) end end let(:always_outdated_action_sequence_for_layout) do Nanoc::Core::ActionSequenceBuilder.build do |b| b.add_filter(:always_outdated_3zh5qfqlqysghkd5ipek8glxzrljrylr, {}) end end context 'when nothing has changed' do it 'marks all items as NOT outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_home_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_articles_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_a_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_b_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_c_after)).to be_empty end end context 'when home item has changed content' do let(:item_home_after) { Nanoc::Core::Item.new('Home UPDATED', {}, '/home.md') } let(:item_home_rep_after) { Nanoc::Core::ItemRep.new(item_home_after, :default) } it 'marks home item as outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_home_after)).to contain_exactly(Nanoc::Core::OutdatednessReasons::ContentModified) end it 'marks other items as NOT outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_articles_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_a_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_b_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_c_after)).to be_empty end end context 'when article item has changed raw content' do let(:item_article_a_after) { Nanoc::Core::Item.new('Article A UPDATED', {}, '/articles/2019-a.md') } let(:item_article_a_rep_after) { Nanoc::Core::ItemRep.new(item_article_a_after, :default) } context 'when articles item depends on raw content of articles' do let(:dependency_store) do Nanoc::Core::DependencyStore.new( items_before_coll, layouts_before_coll, config_before, ).tap do |store| store.record_dependency(item_articles_before, item_article_a_before, raw_content: true) store.record_dependency(item_articles_before, item_article_b_before, raw_content: true) store.record_dependency(item_articles_before, item_article_c_before, raw_content: true) end end it 'marks article item as outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_article_a_after)).to contain_exactly(Nanoc::Core::OutdatednessReasons::ContentModified) end it 'marks articles item as outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_articles_after)).to contain_exactly(Nanoc::Core::OutdatednessReasons::DependenciesOutdated) end it 'marks other items as NOT outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_home_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_b_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_c_after)).to be_empty end end context 'when articles item depends on attributes of articles' do let(:dependency_store) do Nanoc::Core::DependencyStore.new( items_before_coll, layouts_before_coll, config_before, ).tap do |store| store.record_dependency(item_articles_before, item_article_a_before, attributes: true) store.record_dependency(item_articles_before, item_article_b_before, attributes: true) store.record_dependency(item_articles_before, item_article_c_before, attributes: true) end end it 'marks article item as outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_article_a_after)).to contain_exactly(Nanoc::Core::OutdatednessReasons::ContentModified) end it 'marks other items as NOT outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_home_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_articles_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_b_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_c_after)).to be_empty end end context 'when articles item depends on compiled content of articles' do let(:dependency_store) do Nanoc::Core::DependencyStore.new( items_before_coll, layouts_before_coll, config_before, ).tap do |store| store.record_dependency(item_articles_before, item_article_a_before, compiled_content: true) store.record_dependency(item_articles_before, item_article_b_before, compiled_content: true) store.record_dependency(item_articles_before, item_article_c_before, compiled_content: true) end end it 'marks article item as outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_article_a_after)).to contain_exactly(Nanoc::Core::OutdatednessReasons::ContentModified) end it 'marks articles item as outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_articles_after)).to contain_exactly(Nanoc::Core::OutdatednessReasons::DependenciesOutdated) end it 'marks other items as NOT outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_home_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_b_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_c_after)).to be_empty end end context 'when articles item depends on path of articles' do let(:dependency_store) do Nanoc::Core::DependencyStore.new( items_before_coll, layouts_before_coll, config_before, ).tap do |store| store.record_dependency(item_articles_before, item_article_a_before, path: true) store.record_dependency(item_articles_before, item_article_b_before, path: true) store.record_dependency(item_articles_before, item_article_c_before, path: true) end end it 'marks article item as outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_article_a_after)).to contain_exactly(Nanoc::Core::OutdatednessReasons::ContentModified) end it 'marks other items as NOT outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_home_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_articles_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_b_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_c_after)).to be_empty end end end context 'when article item has changed attributes' do let(:item_article_a_after) { Nanoc::Core::Item.new('Article A', { title: 'UPDATED title' }, '/articles/2019-a.md') } let(:item_article_a_rep_after) { Nanoc::Core::ItemRep.new(item_article_a_after, :default) } context 'when articles item depends on raw content of articles' do let(:dependency_store) do Nanoc::Core::DependencyStore.new( items_before_coll, layouts_before_coll, config_before, ).tap do |store| store.record_dependency(item_articles_before, item_article_a_before, raw_content: true) store.record_dependency(item_articles_before, item_article_b_before, raw_content: true) store.record_dependency(item_articles_before, item_article_c_before, raw_content: true) end end it 'marks article item as outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_article_a_after)).to contain_exactly(Nanoc::Core::OutdatednessReasons::AttributesModified) end it 'marks other items as NOT outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_home_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_articles_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_b_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_c_after)).to be_empty end end context 'when articles item has a generic attributes dependency on articles' do let(:dependency_store) do Nanoc::Core::DependencyStore.new( items_before_coll, layouts_before_coll, config_before, ).tap do |store| store.record_dependency(item_articles_before, item_article_a_before, attributes: true) store.record_dependency(item_articles_before, item_article_b_before, attributes: true) store.record_dependency(item_articles_before, item_article_c_before, attributes: true) end end it 'marks article item as outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_article_a_after)).to contain_exactly(Nanoc::Core::OutdatednessReasons::AttributesModified) end it 'marks articles item as outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_articles_after)).to contain_exactly(Nanoc::Core::OutdatednessReasons::DependenciesOutdated) end it 'marks other items as NOT outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_home_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_b_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_c_after)).to be_empty end end context 'when articles item has a specific attributes dependency on articles, and is triggered' do let(:dependency_store) do Nanoc::Core::DependencyStore.new( items_before_coll, layouts_before_coll, config_before, ).tap do |store| store.record_dependency(item_articles_before, item_article_a_before, attributes: [:title]) store.record_dependency(item_articles_before, item_article_b_before, attributes: [:title]) store.record_dependency(item_articles_before, item_article_c_before, attributes: [:title]) end end it 'marks article item as outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_article_a_after)).to contain_exactly(Nanoc::Core::OutdatednessReasons::AttributesModified) end it 'marks articles item as outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_articles_after)).to contain_exactly(Nanoc::Core::OutdatednessReasons::DependenciesOutdated) end it 'marks other items as NOT outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_home_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_b_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_c_after)).to be_empty end end context 'when articles item has a specific attributes dependency on articles, but is not triggered' do let(:dependency_store) do Nanoc::Core::DependencyStore.new( items_before_coll, layouts_before_coll, config_before, ).tap do |store| store.record_dependency(item_articles_before, item_article_a_before, attributes: [:updated_on]) store.record_dependency(item_articles_before, item_article_b_before, attributes: [:updated_on]) store.record_dependency(item_articles_before, item_article_c_before, attributes: [:updated_on]) end end it 'marks article item as outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_article_a_after)).to contain_exactly(Nanoc::Core::OutdatednessReasons::AttributesModified) end it 'marks other items as NOT outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_home_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_articles_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_b_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_c_after)).to be_empty end end context 'when articles item depends on compiled content of articles' do let(:dependency_store) do Nanoc::Core::DependencyStore.new( items_before_coll, layouts_before_coll, config_before, ).tap do |store| store.record_dependency(item_articles_before, item_article_a_before, compiled_content: true) store.record_dependency(item_articles_before, item_article_b_before, compiled_content: true) store.record_dependency(item_articles_before, item_article_c_before, compiled_content: true) end end it 'marks article item as outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_article_a_after)).to contain_exactly(Nanoc::Core::OutdatednessReasons::AttributesModified) end it 'marks articles item as outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_articles_after)).to contain_exactly(Nanoc::Core::OutdatednessReasons::DependenciesOutdated) end it 'marks other items as NOT outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_home_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_b_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_c_after)).to be_empty end end context 'when articles item depends on path of articles' do let(:dependency_store) do Nanoc::Core::DependencyStore.new( items_before_coll, layouts_before_coll, config_before, ).tap do |store| store.record_dependency(item_articles_before, item_article_a_before, path: true) store.record_dependency(item_articles_before, item_article_b_before, path: true) store.record_dependency(item_articles_before, item_article_c_before, path: true) end end it 'marks article item as outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_article_a_after)).to contain_exactly(Nanoc::Core::OutdatednessReasons::AttributesModified) end it 'marks other items as NOT outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_home_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_articles_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_b_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_c_after)).to be_empty end end end context 'when layout has changed raw content' do let(:layout_default_after) { Nanoc::Core::Layout.new('Default UPDATED', { kind: 'default' }, '/default.html.erb') } context 'when articles item depends on raw content of layout' do let(:dependency_store) do Nanoc::Core::DependencyStore.new( items_before_coll, layouts_before_coll, config_before, ).tap do |store| store.record_dependency(item_articles_before, layout_default_after, raw_content: true) end end it 'marks articles item as outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_articles_after)).to contain_exactly(Nanoc::Core::OutdatednessReasons::DependenciesOutdated) end it 'marks other items as NOT outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_home_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_a_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_b_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_c_after)).to be_empty end end context 'when articles item has a generic attributes dependency on layout' do let(:dependency_store) do Nanoc::Core::DependencyStore.new( items_before_coll, layouts_before_coll, config_before, ).tap do |store| store.record_dependency(item_articles_before, layout_default_after, attributes: true) end end it 'marks all items as NOT outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_home_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_articles_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_a_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_b_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_c_after)).to be_empty end end context 'when articles item has a specific attributes dependency on layout, and is triggered' do let(:dependency_store) do Nanoc::Core::DependencyStore.new( items_before_coll, layouts_before_coll, config_before, ).tap do |store| store.record_dependency(item_articles_before, layout_default_after, attributes: [:title]) end end it 'marks all items as NOT outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_home_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_articles_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_a_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_b_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_c_after)).to be_empty end end context 'when articles item has a specific attributes dependency on articles, but is not triggered' do let(:dependency_store) do Nanoc::Core::DependencyStore.new( items_before_coll, layouts_before_coll, config_before, ).tap do |store| store.record_dependency(item_articles_before, layout_default_after, attributes: [:author]) end end it 'marks all items as NOT outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_home_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_articles_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_a_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_b_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_c_after)).to be_empty end end context 'when home item has a transitive dependency via articles item on layout' do let(:dependency_store) do Nanoc::Core::DependencyStore.new( items_before_coll, layouts_before_coll, config_before, ).tap do |store| store.record_dependency(item_home_before, item_articles_before, compiled_content: true) store.record_dependency(item_articles_before, layout_default_after, raw_content: true) end end it 'marks home item as outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_home_after)).to contain_exactly(Nanoc::Core::OutdatednessReasons::DependenciesOutdated) end it 'marks articles item as outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_articles_after)).to contain_exactly(Nanoc::Core::OutdatednessReasons::DependenciesOutdated) end it 'marks other items as NOT outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_article_a_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_b_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_c_after)).to be_empty end end end context 'when layout has changed attributes' do let(:layout_default_after) { Nanoc::Core::Layout.new('Default', { title: 'Title UPDATED' }, '/default.html.erb') } context 'when articles item depends on raw content of layout' do let(:dependency_store) do Nanoc::Core::DependencyStore.new( items_before_coll, layouts_before_coll, config_before, ).tap do |store| store.record_dependency(item_articles_before, layout_default_after, raw_content: true) end end it 'marks all items as NOT outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_home_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_articles_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_a_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_b_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_c_after)).to be_empty end end context 'when articles item has a generic attributes dependency on layout' do let(:dependency_store) do Nanoc::Core::DependencyStore.new( items_before_coll, layouts_before_coll, config_before, ).tap do |store| store.record_dependency(item_articles_before, layout_default_after, attributes: true) end end it 'marks articles item as outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_articles_after)).to contain_exactly(Nanoc::Core::OutdatednessReasons::DependenciesOutdated) end it 'marks other items as NOT outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_home_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_a_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_b_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_c_after)).to be_empty end end context 'when articles item has a specific attributes dependency on layout, and is triggered' do let(:dependency_store) do Nanoc::Core::DependencyStore.new( items_before_coll, layouts_before_coll, config_before, ).tap do |store| store.record_dependency(item_articles_before, layout_default_after, attributes: [:title]) end end it 'marks articles item as outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_articles_after)).to contain_exactly(Nanoc::Core::OutdatednessReasons::DependenciesOutdated) end it 'marks other items as NOT outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_home_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_a_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_b_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_c_after)).to be_empty end end context 'when articles item has a specific attributes dependency on articles, but is not triggered' do let(:dependency_store) do Nanoc::Core::DependencyStore.new( items_before_coll, layouts_before_coll, config_before, ).tap do |store| store.record_dependency(item_articles_before, layout_default_after, attributes: [:author]) end end it 'marks other items as NOT outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_home_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_articles_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_a_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_b_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_c_after)).to be_empty end end end context 'when article item has changed rules' do let(:item_article_a_after) { Nanoc::Core::Item.new('Article A', {}, '/articles/2019-a.md') } let(:item_article_a_rep_after) { Nanoc::Core::ItemRep.new(item_article_a_after, :default) } let(:action_sequences_after) do action_sequences_before.merge( { item_article_a_rep_before => different_action_sequence_for_item_rep, }, ) end context 'when articles item depends on raw content of articles' do let(:dependency_store) do Nanoc::Core::DependencyStore.new( items_before_coll, layouts_before_coll, config_before, ).tap do |store| store.record_dependency(item_articles_before, item_article_a_before, raw_content: true) store.record_dependency(item_articles_before, item_article_b_before, raw_content: true) store.record_dependency(item_articles_before, item_article_c_before, raw_content: true) end end it 'marks article item as outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_article_a_after)).to contain_exactly(Nanoc::Core::OutdatednessReasons::RulesModified) end it 'marks other items as NOT outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_home_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_articles_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_b_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_c_after)).to be_empty end end context 'when articles item depends on attributes of articles' do let(:dependency_store) do Nanoc::Core::DependencyStore.new( items_before_coll, layouts_before_coll, config_before, ).tap do |store| store.record_dependency(item_articles_before, item_article_a_before, attributes: true) store.record_dependency(item_articles_before, item_article_b_before, attributes: true) store.record_dependency(item_articles_before, item_article_c_before, attributes: true) end end it 'marks article item as outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_article_a_after)).to contain_exactly(Nanoc::Core::OutdatednessReasons::RulesModified) end it 'marks other items as NOT outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_home_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_articles_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_b_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_c_after)).to be_empty end end context 'when articles item depends on compiled content of articles' do let(:dependency_store) do Nanoc::Core::DependencyStore.new( items_before_coll, layouts_before_coll, config_before, ).tap do |store| store.record_dependency(item_articles_before, item_article_a_before, compiled_content: true) store.record_dependency(item_articles_before, item_article_b_before, compiled_content: true) store.record_dependency(item_articles_before, item_article_c_before, compiled_content: true) end end it 'marks article item as outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_article_a_after)).to contain_exactly(Nanoc::Core::OutdatednessReasons::RulesModified) end it 'marks articles item as outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_articles_after)).to contain_exactly(Nanoc::Core::OutdatednessReasons::DependenciesOutdated) end it 'marks other items as NOT outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_home_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_b_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_c_after)).to be_empty end end context 'when articles item depends on path of articles' do let(:dependency_store) do Nanoc::Core::DependencyStore.new( items_before_coll, layouts_before_coll, config_before, ).tap do |store| store.record_dependency(item_articles_before, item_article_a_before, path: true) store.record_dependency(item_articles_before, item_article_b_before, path: true) store.record_dependency(item_articles_before, item_article_c_before, path: true) end end it 'marks article item as outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_article_a_after)).to contain_exactly(Nanoc::Core::OutdatednessReasons::RulesModified) end it 'marks articles item as outdated' do # FIXME: This is not optimal. The path has not changed, and so the # articles item should not be considered as outdated. This is because # the `RulesModified` outdatedness reason has the property `path: true`. expect(outdatedness_checker.outdatedness_reasons_for(item_articles_after)).to contain_exactly(Nanoc::Core::OutdatednessReasons::DependenciesOutdated) end it 'marks other items as NOT outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_home_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_b_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_c_after)).to be_empty # FIXME: This is not optimal. Also see related test case above. # expect(outdatedness_checker.outdatedness_reasons_for(item_articles_after)).to be_empty end end end context 'when code snippets are changed' do let(:code_snippet_b_after) { Nanoc::Core::CodeSnippet.new('bbb UPDATED', 'lib/b.rb') } it 'marks all items as outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_home_after)).to contain_exactly(Nanoc::Core::OutdatednessReasons::CodeSnippetsModified) expect(outdatedness_checker.outdatedness_reasons_for(item_articles_after)).to contain_exactly(Nanoc::Core::OutdatednessReasons::CodeSnippetsModified) expect(outdatedness_checker.outdatedness_reasons_for(item_article_a_after)).to contain_exactly(Nanoc::Core::OutdatednessReasons::CodeSnippetsModified) expect(outdatedness_checker.outdatedness_reasons_for(item_article_b_after)).to contain_exactly(Nanoc::Core::OutdatednessReasons::CodeSnippetsModified) expect(outdatedness_checker.outdatedness_reasons_for(item_article_c_after)).to contain_exactly(Nanoc::Core::OutdatednessReasons::CodeSnippetsModified) end end context 'when using an always-outdated filter' do # NOTE: This modifies both before AND after action sequences. let(:action_sequences_before) do super().merge( { item_article_a_rep_before => always_outdated_action_sequence_for_item_rep, }, ) end it 'marks article item as outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_article_a_after)).to contain_exactly(Nanoc::Core::OutdatednessReasons::UsesAlwaysOutdatedFilter) end it 'marks other items as NOT outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_home_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_articles_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_b_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_c_after)).to be_empty end end context 'when path is present but not written' do before do item_article_a_rep_after.raw_paths = { last: ["#{site.config.output_dir}/articles.html"], } end it 'marks article item as outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_article_a_after)).to contain_exactly(Nanoc::Core::OutdatednessReasons::NotWritten) end it 'marks other items as NOT outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_home_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_articles_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_b_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_c_after)).to be_empty end end context 'when config has changed' do let(:config_after) { Nanoc::Core::Configuration.new(dir: Dir.getwd, hash: { name: 'Name UPDATED' }).with_defaults } context 'when there are no dependencies on the config' do it 'marks all items as NOT outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_home_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_articles_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_a_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_b_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_c_after)).to be_empty end end context 'when articles item has a generic attribute dependency on config' do let(:dependency_store) do Nanoc::Core::DependencyStore.new( items_before_coll, layouts_before_coll, config_before, ).tap do |store| store.record_dependency(item_articles_before, config_before, attributes: true) end end it 'marks articles item as outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_articles_after)).to contain_exactly(Nanoc::Core::OutdatednessReasons::DependenciesOutdated) end it 'marks other items as NOT outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_home_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_a_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_b_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_c_after)).to be_empty end end context 'when articles item has a specific attribute dependency on config, and is triggered' do let(:dependency_store) do Nanoc::Core::DependencyStore.new( items_before_coll, layouts_before_coll, config_before, ).tap do |store| store.record_dependency(item_articles_before, config_before, attributes: [:name]) end end it 'marks articles item as outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_articles_after)).to contain_exactly(Nanoc::Core::OutdatednessReasons::DependenciesOutdated) end it 'marks other items as NOT outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_home_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_a_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_b_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_c_after)).to be_empty end end context 'when articles item has a specific attribute dependency on config, but is not triggered' do let(:dependency_store) do Nanoc::Core::DependencyStore.new( items_before_coll, layouts_before_coll, config_before, ).tap do |store| store.record_dependency(item_articles_before, config_before, attributes: [:author]) end end it 'marks other items as NOT outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_home_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_articles_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_a_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_b_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_c_after)).to be_empty end end end context 'when an item is added to the site' do let(:item_article_d_after) { Nanoc::Core::Item.new('Article D', { kind: 'article' }, '/articles/2022-d.md') } let(:item_article_d_rep_after) { Nanoc::Core::ItemRep.new(item_article_d_after, :default) } let(:items_after_array) { super() + [item_article_d_after] } let(:reps) do super().tap do |rr| rr << item_article_d_rep_after end end let(:action_sequences_after) do super().merge( { item_article_d_rep_after => some_action_sequence_for_item_rep, }, ) end context 'when there are no dependencies on the new item' do before do # Store old dependency store old_dependency_store = Nanoc::Core::DependencyStore.new( items_before_coll, layouts_before_coll, config_before, ) old_dependency_store.store # Reload dependency_store.items = items_after_coll dependency_store.layouts = layouts_after_coll dependency_store.load end it 'marks all items as NOT outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_home_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_articles_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_a_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_b_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_c_after)).to be_empty end it 'marks new item as outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_article_d_after)).not_to be_empty # FIXME: It should be DocumentAdded (though it’s not a big issue) # expect(outdatedness_checker.outdatedness_reasons_for(item_article_d_after)).to match_array([ # Nanoc::Core::OutdatednessReasons::DocumentAdded, # ]) end end # NOTE: Generic attribute dependency on item collection is not an option, # and not needed. IdentifiableCollectionView generates dependencies with # specific attributes only. context 'when articles item has a specific attribute dependency on all items, and attribute matches' do before do # Store old dependency store old_dependency_store = Nanoc::Core::DependencyStore.new( items_before_coll, layouts_before_coll, config_before, ) old_dependency_store.record_dependency(item_articles_before, items_before_coll, attributes: { kind: 'article' }) old_dependency_store.store # Reload dependency_store.items = items_after_coll dependency_store.layouts = layouts_after_coll dependency_store.load end it 'marks articles item as outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_articles_after)).to contain_exactly(Nanoc::Core::OutdatednessReasons::DependenciesOutdated) end it 'marks new item as outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_article_d_after)).not_to be_empty # FIXME: It should be DocumentAdded: # expect(outdatedness_checker.outdatedness_reasons_for(item_article_d_after)).to match_array([ # Nanoc::Core::OutdatednessReasons::DocumentAdded, # ]) end it 'marks other items as NOT outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_home_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_a_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_b_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_c_after)).to be_empty end end context 'when articles item has a specific attribute dependency on all items, and attribute does not match' do before do # Store old dependency store old_dependency_store = Nanoc::Core::DependencyStore.new( items_before_coll, layouts_before_coll, config_before, ) old_dependency_store.record_dependency(item_articles_before, items_before_coll, attributes: { kind: 'non-article' }) old_dependency_store.store # Reload dependency_store.items = items_after_coll dependency_store.layouts = layouts_after_coll dependency_store.load end it 'marks new item as outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_article_d_after)).not_to be_empty # FIXME: It should be DocumentAdded: # expect(outdatedness_checker.outdatedness_reasons_for(item_article_d_after)).to match_array([ # Nanoc::Core::OutdatednessReasons::DocumentAdded, # ]) end it 'marks other items as NOT outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_home_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_articles_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_a_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_b_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_c_after)).to be_empty end end context 'when articles item has a generic raw content dependency on all items' do before do # Store old dependency store old_dependency_store = Nanoc::Core::DependencyStore.new( items_before_coll, layouts_before_coll, config_before, ) old_dependency_store.record_dependency(item_articles_before, items_before_coll, raw_content: true) old_dependency_store.store # Reload dependency_store.items = items_after_coll dependency_store.layouts = layouts_after_coll dependency_store.load end it 'marks articles item as outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_articles_after)).to contain_exactly(Nanoc::Core::OutdatednessReasons::DependenciesOutdated) end it 'marks new item as outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_article_d_after)).not_to be_empty # FIXME: It should be DocumentAdded: # expect(outdatedness_checker.outdatedness_reasons_for(item_article_d_after)).to match_array([ # Nanoc::Core::OutdatednessReasons::DocumentAdded, # ]) end it 'marks other items as NOT outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_home_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_a_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_b_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_c_after)).to be_empty end end context 'when articles item has a specific raw content dependency (string pattern) on all items' do before do # Store old dependency store old_dependency_store = Nanoc::Core::DependencyStore.new( items_before_coll, layouts_before_coll, config_before, ) old_dependency_store.record_dependency(item_articles_before, items_before_coll, raw_content: ['/articles/*.md']) old_dependency_store.store # Reload dependency_store.items = items_after_coll dependency_store.layouts = layouts_after_coll dependency_store.load end it 'marks articles item as outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_articles_after)).to contain_exactly(Nanoc::Core::OutdatednessReasons::DependenciesOutdated) end it 'marks new item as outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_article_d_after)).not_to be_empty # FIXME: It should be DocumentAdded: # expect(outdatedness_checker.outdatedness_reasons_for(item_article_d_after)).to match_array([ # Nanoc::Core::OutdatednessReasons::DocumentAdded, # ]) end it 'marks other items as NOT outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_home_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_a_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_b_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_c_after)).to be_empty end end context 'when articles item has a specific raw content dependency (regex pattern) on all items' do before do # Store old dependency store old_dependency_store = Nanoc::Core::DependencyStore.new( items_before_coll, layouts_before_coll, config_before, ) old_dependency_store.record_dependency(item_articles_before, items_before_coll, raw_content: [%r{^/articles/.*}]) old_dependency_store.store # Reload dependency_store.items = items_after_coll dependency_store.layouts = layouts_after_coll dependency_store.load end it 'marks articles item as outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_articles_after)).to contain_exactly(Nanoc::Core::OutdatednessReasons::DependenciesOutdated) end it 'marks new item as outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_article_d_after)).not_to be_empty # FIXME: It should be DocumentAdded: # expect(outdatedness_checker.outdatedness_reasons_for(item_article_d_after)).to match_array([ # Nanoc::Core::OutdatednessReasons::DocumentAdded, # ]) end it 'marks other items as NOT outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_home_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_a_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_b_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_c_after)).to be_empty end end end context 'when a layout is added to the site' do let(:layout_new_after) { Nanoc::Core::Layout.new('Layout', { kind: 'article' }, '/articles/2022-d.md') } let(:layouts_after_array) { super() + [layout_new_after] } let(:action_sequences_after) do super().merge( { layout_new_after => some_action_sequence_for_layout, }, ) end context 'when there are no dependencies on the new item' do before do # Store old dependency store old_dependency_store = Nanoc::Core::DependencyStore.new( items_before_coll, layouts_before_coll, config_before, ) old_dependency_store.store # Reload dependency_store.items = items_after_coll dependency_store.layouts = layouts_after_coll dependency_store.load end it 'marks all items as NOT outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_home_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_articles_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_a_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_b_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_c_after)).to be_empty end end context 'when articles item has a specific attribute dependency on all layouts, and attribute matches' do before do # Store old dependency store old_dependency_store = Nanoc::Core::DependencyStore.new( items_before_coll, layouts_before_coll, config_before, ) old_dependency_store.record_dependency(item_articles_before, layouts_before_coll, attributes: { kind: 'article' }) old_dependency_store.store # Reload dependency_store.items = items_after_coll dependency_store.layouts = layouts_after_coll dependency_store.load end it 'marks articles item as outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_articles_after)).to contain_exactly(Nanoc::Core::OutdatednessReasons::DependenciesOutdated) end it 'marks other items as NOT outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_home_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_a_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_b_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_c_after)).to be_empty end end context 'when articles item has a specific attribute dependency on all layouts, but attribute does not match' do before do # Store old dependency store old_dependency_store = Nanoc::Core::DependencyStore.new( items_before_coll, layouts_before_coll, config_before, ) old_dependency_store.record_dependency(item_articles_before, layouts_before_coll, attributes: { kind: 'note' }) old_dependency_store.store # Reload dependency_store.items = items_after_coll dependency_store.layouts = layouts_after_coll dependency_store.load end it 'marks all items as NOT outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_home_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_articles_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_a_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_b_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_c_after)).to be_empty end end context 'when articles item has a generic raw content dependency on all layouts' do before do # Store old dependency store old_dependency_store = Nanoc::Core::DependencyStore.new( items_before_coll, layouts_before_coll, config_before, ) old_dependency_store.record_dependency(item_articles_before, layouts_before_coll, raw_content: true) old_dependency_store.store # Reload dependency_store.items = items_after_coll dependency_store.layouts = layouts_after_coll dependency_store.load end it 'marks articles item as outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_articles_after)).to contain_exactly(Nanoc::Core::OutdatednessReasons::DependenciesOutdated) end it 'marks other items as NOT outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_home_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_a_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_b_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_c_after)).to be_empty end end context 'when articles item has a specific raw content dependency (string pattern) on all layouts' do before do # Store old dependency store old_dependency_store = Nanoc::Core::DependencyStore.new( items_before_coll, layouts_before_coll, config_before, ) old_dependency_store.record_dependency(item_articles_before, layouts_before_coll, raw_content: ['/articles/*.md']) old_dependency_store.store # Reload dependency_store.items = items_after_coll dependency_store.layouts = layouts_after_coll dependency_store.load end it 'marks articles item as outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_articles_after)).to contain_exactly(Nanoc::Core::OutdatednessReasons::DependenciesOutdated) end it 'marks other items as NOT outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_home_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_a_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_b_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_c_after)).to be_empty end end context 'when articles item has a specific raw content dependency (regex pattern) on all layouts' do before do # Store old dependency store old_dependency_store = Nanoc::Core::DependencyStore.new( items_before_coll, layouts_before_coll, config_before, ) old_dependency_store.record_dependency(item_articles_before, layouts_before_coll, raw_content: [%r{^/articles/.*}]) old_dependency_store.store # Reload dependency_store.items = items_after_coll dependency_store.layouts = layouts_after_coll dependency_store.load end it 'marks articles item as outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_articles_after)).to contain_exactly(Nanoc::Core::OutdatednessReasons::DependenciesOutdated) end it 'marks other items as NOT outdated' do expect(outdatedness_checker.outdatedness_reasons_for(item_home_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_a_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_b_after)).to be_empty expect(outdatedness_checker.outdatedness_reasons_for(item_article_c_after)).to be_empty end end end # NOTE: When an item or layout is removed from the site, the dependency on the # item/layout collection will not trigger outdatedness. If any specific item # in the collection is used, then that will create individual dependencies. # # Open questions: # - What if you do only do `@items.find_all('/articles/*').size`? end nanoc-4.13.3/nanoc-core/spec/nanoc/core/outdatedness_rules_spec.rb000066400000000000000000000512111472033334600251640ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Core::OutdatednessRules do Class.new(Nanoc::Core::Filter) do identifier :always_outdated_voibwz9nhgf6gbpkdznrxcwkqgzlwnif always_outdated def run(content, _params) content.upcase end end describe '#apply' do subject { rule_class.instance.apply(obj, basic_outdatedness_checker) } let(:obj) { item_rep } let(:basic_outdatedness_checker) do Nanoc::Core::BasicOutdatednessChecker.new( site:, checksum_store:, checksums:, dependency_store:, action_sequence_store:, action_sequences:, reps:, ) end let(:item_rep) { Nanoc::Core::ItemRep.new(item, :default) } let(:item) { Nanoc::Core::Item.new('stuff', {}, '/foo.md') } let(:layout) { Nanoc::Core::Layout.new('layoutz', {}, '/page.erb') } let(:config) { Nanoc::Core::Configuration.new(dir: Dir.getwd).with_defaults } let(:code_snippets) { [] } let(:objects) { [config] + code_snippets + [item] } let(:site) do Nanoc::Core::Site.new( config:, code_snippets:, data_source: Nanoc::Core::InMemoryDataSource.new(items, layouts), ) end let(:action_sequences) { {} } let(:reps) { Nanoc::Core::ItemRepRepo.new } let(:dependency_store) { Nanoc::Core::DependencyStore.new(items, layouts, config) } let(:action_sequence_store) { Nanoc::Core::ActionSequenceStore.new(config:) } let(:checksum_store) { Nanoc::Core::ChecksumStore.new(config:, objects:) } let(:checksums) do checksums = {} [items, layouts].each do |documents| documents.each do |document| checksums[[document.reference, :content]] = Nanoc::Core::Checksummer.calc_for_content_of(document) checksums[[document.reference, :each_attribute]] = Nanoc::Core::Checksummer.calc_for_each_attribute_of(document) end end [items, layouts, code_snippets].each do |objs| objs.each do |obj| checksums[obj.reference] = Nanoc::Core::Checksummer.calc(obj) end end checksums[config.reference] = Nanoc::Core::Checksummer.calc(config) checksums[[config.reference, :each_attribute]] = Nanoc::Core::Checksummer.calc_for_each_attribute_of(config) Nanoc::Core::ChecksumCollection.new(checksums) end let(:items) { Nanoc::Core::ItemCollection.new(config, [item]) } let(:layouts) { Nanoc::Core::LayoutCollection.new(config, [layout]) } before do allow(site).to receive_messages(code_snippets:, config:) end describe 'CodeSnippetsModified' do let(:rule_class) { Nanoc::Core::OutdatednessRules::CodeSnippetsModified } context 'no snippets' do let(:code_snippets) { [] } it { is_expected.to be_nil } end context 'only non-outdated snippets' do let(:code_snippet) { Nanoc::Core::CodeSnippet.new('asdf', 'lib/foo.md') } let(:code_snippets) { [code_snippet] } before { checksum_store.add(code_snippet) } it { is_expected.to be_nil } end context 'only outdated snippets' do let(:code_snippet) { Nanoc::Core::CodeSnippet.new('asdf', 'lib/foo.md') } let(:code_snippet_old) { Nanoc::Core::CodeSnippet.new('aaaaaaaa', 'lib/foo.md') } let(:code_snippets) { [code_snippet] } before { checksum_store.add(code_snippet_old) } it { is_expected.not_to be_nil } end end describe 'NotWritten' do let(:rule_class) { Nanoc::Core::OutdatednessRules::NotWritten } context 'no path' do before { item_rep.paths = {} } it { is_expected.to be_nil } end context 'path for last snapshot' do let(:path) { Dir.getwd + '/output/foo.txt' } before { item_rep.raw_paths = { last: [path] } } context 'not written' do it { is_expected.not_to be_nil } end context 'written' do before do FileUtils.mkdir_p(File.dirname(path)) File.write(path, 'hello') end it { is_expected.to be_nil } end end context 'path for other snapshot' do let(:path) { Dir.getwd + '/output/foo.txt' } before { item_rep.raw_paths = { donkey: [path] } } context 'not written' do it { is_expected.not_to be_nil } end context 'written' do before do FileUtils.mkdir_p(File.dirname(path)) File.write(path, 'hello') end it { is_expected.to be_nil } end end context 'path inside output dir not inside current directory' do let(:path) { output_dir + '/foo.txt' } let(:config) { super().merge(output_dir:) } let(:output_dir) { Dir.mktmpdir('nanoc-outdatendess-rules-spec') } before { item_rep.raw_paths = { donkey: [path] } } context 'not written' do it { is_expected.not_to be_nil } end context 'written' do before { File.write(path, 'hello') } it { is_expected.to be_nil } end end end describe 'ContentModified' do let(:rule_class) { Nanoc::Core::OutdatednessRules::ContentModified } context 'item' do let(:obj) { item } before { reps << item_rep } context 'no checksum available' do it { is_expected.not_to be_nil } end context 'checksum available and same' do before { checksum_store.add(item) } it { is_expected.to be_nil } end context 'checksum available, but content different' do let(:old_item) { Nanoc::Core::Item.new('other stuff!!!!', {}, '/foo.md') } before { checksum_store.add(old_item) } it { is_expected.not_to be_nil } end context 'checksum available, but attributes different' do let(:old_item) { Nanoc::Core::Item.new('stuff', { greeting: 'hi' }, '/foo.md') } before { checksum_store.add(old_item) } it { is_expected.to be_nil } end end context 'item rep' do let(:obj) { item_rep } context 'no checksum available' do it { is_expected.not_to be_nil } end context 'checksum available and same' do before { checksum_store.add(item) } it { is_expected.to be_nil } end context 'checksum available, but content different' do let(:old_item) { Nanoc::Core::Item.new('other stuff!!!!', {}, '/foo.md') } before { checksum_store.add(old_item) } it { is_expected.not_to be_nil } end context 'checksum available, but attributes different' do let(:old_item) { Nanoc::Core::Item.new('stuff', { greeting: 'hi' }, '/foo.md') } before { checksum_store.add(old_item) } it { is_expected.to be_nil } end end end describe 'AttributesModified' do let(:rule_class) { Nanoc::Core::OutdatednessRules::AttributesModified } context 'item' do let(:obj) { item } before { reps << item_rep } context 'no checksum available' do it { is_expected.not_to be_nil } end context 'checksum available and same' do before { checksum_store.add(item) } it { is_expected.to be_nil } end context 'checksum available, but content different' do let(:old_item) { Nanoc::Core::Item.new('other stuff!!!!', {}, '/foo.md') } before { checksum_store.add(old_item) } it { is_expected.to be_nil } end context 'checksum available, but attributes different' do let(:old_item) { Nanoc::Core::Item.new('stuff', { greeting: 'hi' }, '/foo.md') } before { checksum_store.add(old_item) } it { is_expected.not_to be_nil } it 'has the one changed attribute' do expect(subject.attributes).to contain_exactly(:greeting) end end context 'attribute kept identical' do let(:item) { Nanoc::Core::Item.new('stuff', { greeting: 'hi' }, '/foo.md') } let(:old_item) { Nanoc::Core::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::Core::Item.new('stuff', { greeting: 'hi' }, '/foo.md') } let(:old_item) { Nanoc::Core::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::Core::Item.new('stuff', { greeting: 'hi' }, '/foo.md') } let(:old_item) { Nanoc::Core::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::Core::Item.new('stuff', {}, '/foo.md') } let(:old_item) { Nanoc::Core::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.not_to be_nil } end context 'checksum available and same' do before { checksum_store.add(item) } it { is_expected.to be_nil } end context 'checksum available, but content different' do let(:old_item) { Nanoc::Core::Item.new('other stuff!!!!', {}, '/foo.md') } before { checksum_store.add(old_item) } it { is_expected.to be_nil } end context 'checksum available, but attributes different' do let(:old_item) { Nanoc::Core::Item.new('stuff', { greeting: 'hi' }, '/foo.md') } before { checksum_store.add(old_item) } it { is_expected.not_to be_nil } 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::Core::OutdatednessRules::RulesModified } let(:old_mem) do Nanoc::Core::ActionSequenceBuilder.build 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.to be_nil } end context 'memory is different' do let(:new_mem) do Nanoc::Core::ActionSequenceBuilder.build do |b| b.add_filter(:erb, {}) b.add_filter(:donkey, {}) end end it { is_expected.not_to be_nil } end context 'memory is the same, but refers to a layout' do let(:old_mem) do Nanoc::Core::ActionSequenceBuilder.build 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::Core::ActionSequenceBuilder.build do |b| b.add_filter(:erb, {}) end end let(:old_layout_mem) { new_layout_mem } it { is_expected.to be_nil } end context 'referenced layout does not exist' do let(:new_layout_mem) do Nanoc::Core::ActionSequenceBuilder.build do |b| b.add_filter(:erb, {}) end end let(:old_layout_mem) do Nanoc::Core::ActionSequenceBuilder.build do |b| b.add_filter(:haml, {}) end end let(:old_mem) do Nanoc::Core::ActionSequenceBuilder.build 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.to be_nil } end context 'filter name is different' do let(:new_layout_mem) do Nanoc::Core::ActionSequenceBuilder.build do |b| b.add_filter(:erb, {}) end end let(:old_layout_mem) do Nanoc::Core::ActionSequenceBuilder.build do |b| b.add_filter(:haml, {}) end end it { is_expected.not_to be_nil } end context 'params are different' do let(:new_layout_mem) do Nanoc::Core::ActionSequenceBuilder.build do |b| b.add_filter(:erb, {}) end end let(:old_layout_mem) do Nanoc::Core::ActionSequenceBuilder.build do |b| b.add_filter(:erb, foo: 123) end end it { is_expected.not_to be_nil } end end end describe 'ContentModified, AttributesModified' do subject do [ Nanoc::Core::OutdatednessRules::ContentModified, Nanoc::Core::OutdatednessRules::AttributesModified, ].map { |c| !!c.instance.apply(new_obj, basic_outdatedness_checker) } # rubocop:disable Style/DoubleNegation end let(:stored_obj) { raise 'override me' } let(:new_obj) { raise 'override me' } let(:items) { Nanoc::Core::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::Core::Item } it_behaves_like 'a document' end context 'layout' do let(:klass) { Nanoc::Core::Layout } it_behaves_like 'a document' end # … end describe 'UsesAlwaysOutdatedFilter' do let(:rule_class) { Nanoc::Core::OutdatednessRules::UsesAlwaysOutdatedFilter } let(:action_sequences) { { item_rep => mem } } context 'unknown filter' do let(:mem) do Nanoc::Core::ActionSequenceBuilder.build do |b| b.add_snapshot(:donkey, '/foo.md', item_rep) b.add_filter(:asdf, {}) end end it { is_expected.to be_nil } end context 'known filter, not always outdated' do let(:mem) do Nanoc::Core::ActionSequenceBuilder.build do |b| b.add_snapshot(:donkey, '/foo.md', item_rep) b.add_filter(:erb, {}) end end it { is_expected.to be_nil } end context 'known filter, always outdated' do let(:mem) do Nanoc::Core::ActionSequenceBuilder.build do |b| b.add_snapshot(:donkey, '/foo.md', item_rep) b.add_filter(:always_outdated_voibwz9nhgf6gbpkdznrxcwkqgzlwnif, {}) end end it { is_expected.not_to be_nil } end end describe 'ItemAdded' do let(:rule_class) { Nanoc::Core::OutdatednessRules::ItemAdded } let(:items_before) do Nanoc::Core::ItemCollection.new(config, [old_item]) end let(:items_after) do Nanoc::Core::ItemCollection.new(config, [old_item, new_item]) end let(:old_item) { Nanoc::Core::Item.new('stuff', {}, '/old.md') } let(:new_item) { Nanoc::Core::Item.new('new', {}, '/new.md') } let(:dependency_store) do Nanoc::Core::DependencyStore.new(items_before, layouts, config).store Nanoc::Core::DependencyStore.new(items_after, layouts, config).tap(&:load) end context 'when used on old item' do let(:obj) { Nanoc::Core::ItemRep.new(old_item, :default) } example do expect(subject).to be_nil end end context 'when used on new item' do let(:obj) { Nanoc::Core::ItemRep.new(new_item, :default) } example do expect(subject).to eq(Nanoc::Core::OutdatednessReasons::DocumentAdded) end end end describe 'LayoutAdded' do let(:rule_class) { Nanoc::Core::OutdatednessRules::LayoutAdded } let(:layouts_before) do Nanoc::Core::LayoutCollection.new(config, [old_layout]) end let(:layouts_after) do Nanoc::Core::LayoutCollection.new(config, [old_layout, new_layout]) end let(:old_layout) { Nanoc::Core::Layout.new('stuff', {}, '/old.md') } let(:new_layout) { Nanoc::Core::Layout.new('new', {}, '/new.md') } let(:dependency_store) do Nanoc::Core::DependencyStore.new(items, layouts_before, config).store Nanoc::Core::DependencyStore.new(items, layouts_after, config).tap(&:load) end context 'when used on old layout' do let(:obj) { old_layout } example do expect(subject).to be_nil end end context 'when used on new layout' do let(:obj) { new_layout } example do expect(subject).to eq(Nanoc::Core::OutdatednessReasons::DocumentAdded) end end end end end nanoc-4.13.3/nanoc-core/spec/nanoc/core/outdatedness_status_spec.rb000066400000000000000000000057571472033334600253730ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Core::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::Core::OutdatednessReasons::CodeSnippetsModified, ] end let(:status) { described_class.new(reasons:) } it { is_expected.to eql(reasons) } end context 'two passed in' do let(:reasons) do [ Nanoc::Core::OutdatednessReasons::CodeSnippetsModified, Nanoc::Core::OutdatednessReasons::ContentModified, ] end let(:status) { described_class.new(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::Core::DependencyProps.new(attributes: true) end let(:status) { described_class.new(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:) } let(:props) { Nanoc::Core::DependencyProps.new } let(:rule) do Class.new(Nanoc::Core::OutdatednessRule) do affects_props :compiled_content, :path def apply(*args); end end end context 'no props' do it { is_expected.to be(true) } end context 'some props' do context 'same props' do let(:props) { Nanoc::Core::DependencyProps.new(compiled_content: true, path: true) } it { is_expected.to be(false) } end context 'different props' do let(:props) { Nanoc::Core::DependencyProps.new(attributes: true) } it { is_expected.to be(true) } end end context 'all props' do let(:props) { Nanoc::Core::DependencyProps.new(raw_content: true, attributes: true, compiled_content: true, path: true) } it { is_expected.to be(false) } end end describe '#update' do subject { status.update(reason) } let(:reason) { Nanoc::Core::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::Core::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::Core::DependencyProps.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.13.3/nanoc-core/spec/nanoc/core/outdatedness_store_spec.rb000066400000000000000000000024521472033334600251710ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Core::OutdatednessStore do subject(:store) { described_class.new(config:) } let(:item) { Nanoc::Core::Item.new('foo', {}, '/foo.md') } let(:rep) { Nanoc::Core::ItemRep.new(item, :foo) } let(:config) { Nanoc::Core::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.to be(false) } end context 'rep added' do before { store.add(rep) } it { is_expected.to be(true) } end context 'rep added and removed' do before do store.add(rep) store.remove(rep) end it { is_expected.to be(false) } 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(true) } end end describe 'reloading' do subject do store.store store.load store.include?(rep) end context 'not added' do it { is_expected.to be(false) } end context 'added' do before { store.add(rep) } it { is_expected.to be(true) } end end end nanoc-4.13.3/nanoc-core/spec/nanoc/core/pattern_spec.rb000066400000000000000000000035071472033334600227320ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Core::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 be(true) expect(pattern.match?('/foo/xyz/bar.html')).to be(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 be(true) expect(pattern.match?('/foo/xyz/bar.html')).to be(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 be(true) expect(pattern.match?('/foo/xyz/bar.html')).to be(false) end it 'converts from symbol' do pattern = described_class.from(:'/foo/x[ab]z/bar.*') expect(pattern.match?('/foo/xaz/bar.html')).to be(true) expect(pattern.match?('/foo/xyz/bar.html')).to be(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 nanoc-4.13.3/nanoc-core/spec/nanoc/core/post_compile_item_rep_collection_view_spec.rb000066400000000000000000000004201472033334600310720ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'support/item_rep_collection_view_examples' describe Nanoc::Core::PostCompileItemRepCollectionView do let(:expected_view_class) { Nanoc::Core::PostCompileItemRepView } it_behaves_like 'an item rep collection view' end nanoc-4.13.3/nanoc-core/spec/nanoc/core/post_compile_item_rep_view_spec.rb000066400000000000000000000164601472033334600266720ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'support/item_rep_view_examples' describe Nanoc::Core::PostCompileItemRepView do let(:expected_item_view_class) { Nanoc::Core::PostCompileItemView } let(:compiled_content_cache) do # Pretend binary snapshots exist on disk so the binary cache can cache them. snapshot_contents .select { |_, content| content.binary? } .each do |_, binary_content| allow(FileUtils).to receive(:cp).with(binary_content.filename, anything) .and_wrap_original do |_meth, _src, dst| File.new(dst, 'w').close end end Nanoc::Core::CompiledContentCache.new(config:).tap do |ccc| ccc[item_rep] = snapshot_contents end end let(:snapshot_contents) do { last: Nanoc::Core::TextualContent.new('content-last'), pre: Nanoc::Core::TextualContent.new('content-pre'), donkey: Nanoc::Core::TextualContent.new('content-donkey'), } end let(:dependency_tracker) { Nanoc::Core::DependencyTracker.new(double(:dependency_store)) } let(:config) { Nanoc::Core::Configuration.new(dir: Dir.getwd).with_defaults } let(:items) { Nanoc::Core::ItemCollection.new(config) } let(:layouts) { Nanoc::Core::LayoutCollection.new(config) } let(:reps) { Nanoc::Core::ItemRepRepo.new } let(:view_context) do Nanoc::Core::ViewContextForCompilation.new( reps: Nanoc::Core::ItemRepRepo.new, items: Nanoc::Core::ItemCollection.new(config), dependency_tracker:, compilation_context:, compiled_content_store:, ) end let(:view) { described_class.new(item_rep, view_context) } let(:item) { Nanoc::Core::Item.new('asdf', {}, '/foo') } let(:item_rep) do Nanoc::Core::ItemRep.new(item, :jacques).tap do |rep| rep.snapshot_defs = snapshot_contents.map do |name, content| Nanoc::Core::SnapshotDef.new(name, binary: content.binary?) end end end let(:compilation_context) do Nanoc::Core::CompilationContext.new( action_provider:, reps:, site:, compiled_content_cache:, compiled_content_store:, ) end let(:compiled_content_store) { Nanoc::Core::CompiledContentStore.new } let(:site) do Nanoc::Core::Site.new( config:, code_snippets: [], data_source: Nanoc::Core::InMemoryDataSource.new(items, layouts), ) end let(:action_provider) do Class.new(Nanoc::Core::ActionProvider) do def self.for(_context) raise NotImplementedError end def initialize; end end.new end it_behaves_like 'an item rep view' describe '#raw_path' do context 'no args' do subject { view.raw_path } it 'does not raise' do # rubocop:disable RSpec/NoExpectationExample 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 # rubocop:disable RSpec/NoExpectationExample 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 temp_file = Tempfile.new('binary junk') temp_file.write('binary data here') temp_file.sync { last: Nanoc::Core::TextualContent.new('content-last'), pre: Nanoc::Core::BinaryContent.new(temp_file.path), donkey: Nanoc::Core::TextualContent.new('content-donkey'), } end it 'raises error' do expect { subject }.to raise_error(Nanoc::Core::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::Core::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::Core::TextualContent.new('content-last'), pre: Nanoc::Core::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::Core::TextualContent.new('content-last'), pre: Nanoc::Core::TextualContent.new('content-pre'), donkey: Nanoc::Core::TextualContent.new('content-donkey'), } end include_examples 'returns pre content' end context 'pre snapshot exists' do let(:snapshot_contents) do { pre: Nanoc::Core::TextualContent.new('content-pre'), donkey: Nanoc::Core::TextualContent.new('content-donkey'), } end include_examples 'returns pre content' end context 'last snapshot exists' do let(:snapshot_contents) do { last: Nanoc::Core::TextualContent.new('content-last'), donkey: Nanoc::Core::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::Core::TextualContent.new('content-donkey'), } end include_examples 'raises no-such-snapshot error' end end end end end nanoc-4.13.3/nanoc-core/spec/nanoc/core/post_compile_item_view_spec.rb000066400000000000000000000054021472033334600260160ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Core::PostCompileItemView do let(:item) { Nanoc::Core::Item.new('blah', {}, '/foo.md') } let(:rep_a) { Nanoc::Core::ItemRep.new(item, :no_mod) } let(:rep_b) { Nanoc::Core::ItemRep.new(item, :modded).tap { |r| r.modified = true } } let(:reps) do Nanoc::Core::ItemRepRepo.new.tap do |reps| reps << rep_a reps << rep_b end end let(:config) { Nanoc::Core::Configuration.new(dir: Dir.getwd).with_defaults } let(:site) do Nanoc::Core::Site.new( config:, code_snippets: [], data_source: Nanoc::Core::InMemoryDataSource.new(items, layouts), ) end let(:items) { Nanoc::Core::ItemCollection.new(config, []) } let(:layouts) { Nanoc::Core::LayoutCollection.new(config, []) } let(:view_context) do Nanoc::Core::ViewContextForCompilation.new( reps:, items: Nanoc::Core::ItemCollection.new(config), dependency_tracker: Nanoc::Core::DependencyTracker::Null.new, compilation_context:, compiled_content_store:, ) end let(:compilation_context) do Nanoc::Core::CompilationContext.new( action_provider:, reps:, site:, compiled_content_cache:, compiled_content_store:, ) end let(:action_provider) do Class.new(Nanoc::Core::ActionProvider) do def self.for(_context) raise NotImplementedError end def initialize; end end.new end let(:compiled_content_cache) { Nanoc::Core::CompiledContentCache.new(config:) } let(:compiled_content_store) { Nanoc::Core::CompiledContentStore.new } 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::Core::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::Core::PostCompileItemRepCollectionView) end end end nanoc-4.13.3/nanoc-core/spec/nanoc/core/prefixed_data_source_spec.rb000066400000000000000000000015331472033334600254310ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Core::PrefixedDataSource, stdio: true do subject(:data_source) do described_class.new(original_data_source, '/itemz', '/layoutz') end let(:klass) do Class.new(Nanoc::Core::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 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.13.3/nanoc-core/spec/nanoc/core/processing_action_spec.rb000066400000000000000000000005261472033334600247640ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Core::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.13.3/nanoc-core/spec/nanoc/core/processing_actions/000077500000000000000000000000001472033334600236055ustar00rootroot00000000000000nanoc-4.13.3/nanoc-core/spec/nanoc/core/processing_actions/filter_spec.rb000066400000000000000000000057741472033334600264460ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Core::ProcessingActions::Filter do let(:action) { described_class.new(:foo, awesome: true) } describe '#serialize' do subject { action.serialize } it { is_expected.to eql([:filter, :foo, 'v+eiDx9FKFH7+UBdX93/FK7/pRM=']) } 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 be(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 be(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 be(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 be(false) end end end end nanoc-4.13.3/nanoc-core/spec/nanoc/core/pruner_spec.rb000066400000000000000000000251461472033334600225730ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Core::Pruner, stdio: true do subject(:pruner) { described_class.new(config, reps, dry_run:, exclude:) } let(:config) { Nanoc::Core::Configuration.new(hash: {}, dir: Dir.getwd).with_defaults } let(:dry_run) { false } let(:exclude) { [] } let(:reps) do Nanoc::Core::ItemRepRepo.new.tap do |reps| reps << Nanoc::Core::ItemRep.new(item, :default).tap do |rep| rep.raw_paths = { last: [Dir.getwd + '/output/asdf.html'] } end reps << Nanoc::Core::ItemRep.new(item, :text).tap do |rep| rep.raw_paths = { last: [Dir.getwd + '/output/asdf.txt'] } end end end let(:item) { Nanoc::Core::Item.new('asdf', {}, '/a.md') } 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::Core::ItemRepRepo.new.tap do |reps| reps << Nanoc::Core::ItemRep.new(item, :a).tap do |rep| rep.raw_paths = { last: [Dir.getwd + '/output/foo.html'] } end reps << Nanoc::Core::ItemRep.new(item, :b).tap do |rep| rep.raw_paths = { last: [Dir.getwd + '/output/bar.html'] } end reps << Nanoc::Core::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') if Nanoc::Core.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.13.3/nanoc-core/spec/nanoc/core/regexp_pattern_spec.rb000066400000000000000000000013511472033334600242770ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Core::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 be(true) expect(pattern.match?('the answer is donkey')).to be(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 nanoc-4.13.3/nanoc-core/spec/nanoc/core/site_loader_spec.rb000066400000000000000000000147571472033334600235600ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Core::SiteLoader do let(:loader) { described_class.new } Class.new(Nanoc::Core::DataSource) do identifier :iyf5eeqefhzyu6vdda7cibfbhou1mnm7 def items [Nanoc::Core::Item.new('I am Denis!', {}, '/about.md')] end def layouts [Nanoc::Core::Layout.new('<%= yield %>', {}, '/page.erb')] end end describe '#new_from_cwd' do subject { loader.new_from_cwd } context 'no config file' do it 'errors' do expect { subject }.to raise_error( Nanoc::Core::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::Core::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 an item' do expect(subject.items.size).to eq(1) expect(subject.items.object_with_identifier('/about.md').content) .to be_a(Nanoc::Core::TextualContent) expect(subject.items.object_with_identifier('/about.md').content.string) .to eq('I am Denis!') expect(subject.items.object_with_identifier('/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.object_with_identifier('/page.erb').content) .to be_a(Nanoc::Core::TextualContent) expect(subject.layouts.object_with_identifier('/page.erb').content.string) .to eq('<%= yield %>') expect(subject.layouts.object_with_identifier('/page.erb').identifier.to_s) .to eq('/page.erb') end context 'some items, layouts, and code snippets' do before do FileUtils.mkdir_p('lib') File.write('lib/foo.rb', '$spirit_animal = :donkey') 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 end end context 'nanoc.yaml config file' do before do File.write('nanoc.yaml', <<~EOS) foo: bar data_sources: - type: iyf5eeqefhzyu6vdda7cibfbhou1mnm7 EOS end it_behaves_like 'a directory with a config file' end context 'config.yaml config file' do before do File.write('config.yaml', <<~EOS) foo: bar data_sources: - type: iyf5eeqefhzyu6vdda7cibfbhou1mnm7 EOS 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::Core::Errors::UnknownDataSource) end end context 'environments defined' do before do File.write('nanoc.yaml', <<-EOS.gsub(/^ {10}/, '')) animal: donkey data_sources: - type: iyf5eeqefhzyu6vdda7cibfbhou1mnm7 environments: staging: animal: giraffe EOS 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::Core::DataSource identifier :site_loader_spec_sample def items [ Nanoc::Core::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.object_with_identifier('/generated.txt').content) .to be_a(Nanoc::Core::TextualContent) expect(subject.items.object_with_identifier('/generated.txt').content.string) .to eq('Generated content!') expect(subject.items.object_with_identifier('/generated.txt').attributes) .to eq(generated: true) expect(subject.items.object_with_identifier('/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::Core::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 'comment # 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 'comment # 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 'comment # -*- 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 'comment # -*- 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.13.3/nanoc-core/spec/nanoc/core/site_spec.rb000066400000000000000000000033721472033334600222210ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Core::Site do describe '#freeze' do let(:site) do described_class.new( config:, code_snippets:, data_source: Nanoc::Core::InMemoryDataSource.new(items, layouts), ) end let(:config) do Nanoc::Core::Configuration.new(dir: Dir.getwd).with_defaults end let(:code_snippets) do [ Nanoc::Core::CodeSnippet.new('FOO = 123', 'hello.rb'), Nanoc::Core::CodeSnippet.new('BAR = 123', 'hi.rb'), ] end let(:items) do Nanoc::Core::ItemCollection.new( config, [ Nanoc::Core::Item.new('foo', {}, '/foo.md'), Nanoc::Core::Item.new('bar', {}, '/bar.md'), ], ) end let(:layouts) do Nanoc::Core::LayoutCollection.new( config, [ Nanoc::Core::Layout.new('foo', {}, '/foo.md'), Nanoc::Core::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.13.3/nanoc-core/spec/nanoc/core/store_spec.rb000066400000000000000000000100161472033334600224020ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Core::Store do let(:test_store_klass) do Class.new(Nanoc::Core::Store) do def data @data end def data=(new_data) @data = new_data end end end describe '#tmp_path_for' do context 'passing config' do subject { described_class.tmp_path_for(config:, store_name: 'giraffes') } def gen_hash(path) Digest::SHA1.hexdigest(File.absolute_path(path))[0..12] end let(:code_snippets) { [] } let(:items) { [] } let(:layouts) { [] } 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::Core::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::Core::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 it 'deletes and reloads on error' do store = test_store_klass.new('test', 1) # Create store.load store.data = { fun: 'sure' } store.store # Test stored values store = test_store_klass.new('test', 1) store.load expect(store.data).to eq(fun: 'sure') # Mess up File.write('test.data.db', 'Damn {}#}%@}$^)@&$&*^#@ broken stores!!!') # Reload store = test_store_klass.new('test', 1) store.load expect(store.data).to be_nil end it 'can write humongous amounts of data' do # Skip running on GitHub actions etc because this thing just uses far too many resources skip 'GitHub Actions does not give us enough resources to run this' if ENV['CI'] store = test_store_klass.new('test', 1) # Create huge string array = [] 100.times do |i| raw = 'x' * 1_000_037 raw << i.to_s io = StringIO.new 100.times { io << raw } array << io.string end # Write store.data = { data: array } expect { store.store }.not_to raise_exception end end nanoc-4.13.3/nanoc-core/spec/nanoc/core/string_pattern_spec.rb000066400000000000000000000023511472033334600243140ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Core::StringPattern do describe '#match?' do it 'matches simple strings' do pattern = described_class.new('d*key') expect(pattern.match?('donkey')).to be(true) expect(pattern.match?('giraffe')).to be(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 be(true) expect(pattern.match?('/foo/x/bar/donkey.animal')).to be(true) expect(pattern.match?('/foo/x/railroad/donkey.animal')).to be(false) end it 'matches with extglob option' do pattern = described_class.new('{b,gl}oat') expect(pattern.match?('boat')).to be(true) expect(pattern.match?('gloat')).to be(true) expect(pattern.match?('stoat')).to be(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 subject { pattern.to_s } let(:pattern) { described_class.new('/foo/*/bar/**/*.animal') } it 'returns the regex' do expect(subject).to eq('/foo/*/bar/**/*.animal') end end end nanoc-4.13.3/nanoc-core/spec/nanoc/core/support/000077500000000000000000000000001472033334600214255ustar00rootroot00000000000000nanoc-4.13.3/nanoc-core/spec/nanoc/core/support/document_view_examples.rb000066400000000000000000000245121472033334600265240ustar00rootroot00000000000000# frozen_string_literal: true shared_examples 'a document view' do let(:view) { described_class.new(document, view_context) } let(:dependency_tracker) { Nanoc::Core::DependencyTracker.new(dependency_store) } let(:dependency_store) { Nanoc::Core::DependencyStore.new(items, layouts, config) } let(:base_item) { Nanoc::Core::Item.new('base', {}, '/base.md') } let(:items) { Nanoc::Core::ItemCollection.new(config) } let(:layouts) { Nanoc::Core::LayoutCollection.new(config) } let(:reps) { Nanoc::Core::ItemRepRepo.new } let(:config) { Nanoc::Core::Configuration.new(dir: Dir.getwd).with_defaults } let(:view_context) do Nanoc::Core::ViewContextForCompilation.new( reps: Nanoc::Core::ItemRepRepo.new, items: Nanoc::Core::ItemCollection.new(config), dependency_tracker:, compilation_context:, compiled_content_store:, ) end let(:compilation_context) do Nanoc::Core::CompilationContext.new( action_provider:, reps:, site:, compiled_content_cache:, compiled_content_store:, ) end let(:compiled_content_store) { Nanoc::Core::CompiledContentStore.new } let(:compiled_content_cache) { Nanoc::Core::CompiledContentCache.new(config:) } let(:site) do Nanoc::Core::Site.new( config:, code_snippets: [], data_source: Nanoc::Core::InMemoryDataSource.new(items, layouts), ) end let(:action_provider) do Class.new(Nanoc::Core::ActionProvider) do def self.for(_context) raise NotImplementedError end def initialize; end end.new end before do dependency_tracker.enter(base_item) end describe '#frozen?' do subject { view.frozen? } let(:document) { entity_class.new('content', {}, '/asdf') } 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 subject { view[key] } let(:document) { entity_class.new('stuff', { animal: 'donkey' }, '/foo') } 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 be(true) expect(dep.props.raw_content?).to be(false) expect(dep.props.compiled_content?).to be(false) expect(dep.props.path?).to be(false) end end context 'with non-existant key' do let(:key) { :weapon } 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([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 be(true) expect(dep.props.raw_content?).to be(false) expect(dep.props.compiled_content?).to be(false) expect(dep.props.path?).to be(false) end end end describe '#attributes' do subject { view.attributes } let(:document) { entity_class.new('stuff', { animal: 'donkey' }, '/foo') } 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 be(true) expect(dep.props.raw_content?).to be(false) expect(dep.props.compiled_content?).to be(false) expect(dep.props.path?).to be(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 subject { view.fetch(key) } 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 be(true) expect(dep.props.raw_content?).to be(false) expect(dep.props.compiled_content?).to be(false) expect(dep.props.path?).to be(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 be(true) expect(dep.props.raw_content?).to be(false) expect(dep.props.compiled_content?).to be(false) expect(dep.props.path?).to be(false) end end context 'with block' do subject { view.fetch(key) { 'nothing sorry' } } # rubocop:disable Style/RedundantFetchBlock 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 be(true) expect(dep.props.raw_content?).to be(false) expect(dep.props.compiled_content?).to be(false) expect(dep.props.path?).to be(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 subject { view.key?(key) } let(:document) { entity_class.new('stuff', { animal: 'donkey' }, '/foo') } context 'with existant key' do let(:key) { :animal } it { is_expected.to be(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 be(true) expect(dep.props.raw_content?).to be(false) expect(dep.props.compiled_content?).to be(false) expect(dep.props.path?).to be(false) end end context 'with non-existant key' do let(:key) { :weapon } it { is_expected.to be(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 be(true) expect(dep.props.raw_content?).to be(false) expect(dep.props.compiled_content?).to be(false) expect(dep.props.path?).to be(false) end end end describe '#hash' do subject { view.hash } let(:document) { double(:document, identifier: '/foo') } it { is_expected.to eq([described_class, '/foo'].hash) } end describe '#raw_content' do subject { view.raw_content } let(:document) { entity_class.new('stuff', { animal: 'donkey' }, '/foo') } 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 be(true) expect(dep.props.attributes?).to be(false) expect(dep.props.compiled_content?).to be(false) expect(dep.props.path?).to be(false) end end end nanoc-4.13.3/nanoc-core/spec/nanoc/core/support/identifiable_collection_view_examples.rb000066400000000000000000000253221472033334600315400ustar00rootroot00000000000000# 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::Core::ViewContextForCompilation.new( reps:, items: Nanoc::Core::ItemCollection.new(config), dependency_tracker:, compilation_context:, compiled_content_store:, ) end let(:compilation_context) do Nanoc::Core::CompilationContext.new( action_provider:, reps:, site:, compiled_content_cache:, compiled_content_store:, ) end let(:action_provider) do Class.new(Nanoc::Core::ActionProvider) do def self.for(_context) raise NotImplementedError end def initialize; end end.new end let(:site) do Nanoc::Core::Site.new( config:, code_snippets: [], data_source: Nanoc::Core::InMemoryDataSource.new(items, layouts), ) end let(:items) { Nanoc::Core::ItemCollection.new(config, []) } let(:layouts) { Nanoc::Core::LayoutCollection.new(config, []) } let(:dependency_tracker) do Nanoc::Core::DependencyTracker::Null.new end let(:compiled_content_store) { Nanoc::Core::CompiledContentStore.new } let(:compiled_content_cache) { Nanoc::Core::CompiledContentCache.new(config:) } let(:reps) { Nanoc::Core::ItemRepRepo.new } let(:config) { Nanoc::Core::Configuration.new(dir: Dir.getwd).with_defaults } describe '#frozen?' do subject { view.frozen? } let(:wrapped) do collection_class.new( config, [ double(:identifiable, identifier: Nanoc::Core::Identifier.new('/foo')), double(:identifiable, identifier: Nanoc::Core::Identifier.new('/bar')), ], ) end context 'non-frozen collection' do it { is_expected.to be(false) } end context 'frozen collection' do before do expect(wrapped).to all(receive(:freeze)) wrapped.freeze end it { is_expected.to be(true) } end end describe '#_unwrap' do subject { view._unwrap } let(:wrapped) do collection_class.new( config, [ double(:identifiable, identifier: Nanoc::Core::Identifier.new('/foo')), double(:identifiable, identifier: Nanoc::Core::Identifier.new('/bar')), double(:identifiable, identifier: Nanoc::Core::Identifier.new('/baz')), ], ) end it { is_expected.to 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::Core::Identifier.new('/foo')), double(:identifiable, identifier: Nanoc::Core::Identifier.new('/bar')), double(:identifiable, identifier: Nanoc::Core::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 subject { view.size } let(:wrapped) do collection_class.new( config, [ double(:identifiable, identifier: Nanoc::Core::Identifier.new('/foo')), double(:identifiable, identifier: Nanoc::Core::Identifier.new('/bar')), double(:identifiable, identifier: Nanoc::Core::Identifier.new('/baz')), ], ) end it 'creates dependency' do expect(dependency_tracker).to receive(:bounce).with(wrapped, raw_content: true) subject end it { is_expected.to eq 3 } end describe '#[]' do subject { view[arg] } let(:page_object) do element_class.new('content', {}, Nanoc::Core::Identifier.new('/page.erb')) end let(:home_object) do element_class.new('content', {}, Nanoc::Core::Identifier.new('/home.erb')) end let(:wrapped) do collection_class.new( config, [ page_object, home_object, ], ) end 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, with exact match' do let(:arg) { '/home.erb' } it 'does not create dependency' do expect(dependency_tracker).not_to receive(:bounce) 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::Core::Identifier.new('/home.erb') } it 'does not create dependency' do expect(dependency_tracker).not_to receive(:bounce) 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) { Nanoc::Core::Configuration.new(dir: Dir.getwd, hash: { string_pattern_type: 'legacy' }).with_defaults } 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::Core::Identifier.new('/about.css')), double(:identifiable, identifier: Nanoc::Core::Identifier.new('/about.md')), double(:identifiable, identifier: Nanoc::Core::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 be(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 be(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 do view.find_all do |iv| expect(iv).to be_a(Nanoc::Core::View) iv.identifier =~ /css/ end end 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 be(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 describe '#where' do around do |ex| Nanoc::Core::Feature.enable('where') { ex.run } end let(:wrapped) do collection_class.new( config, [ double( :identifiable, identifier: Nanoc::Core::Identifier.new('/bare.md'), attributes: {}, ), double( :identifiable, identifier: Nanoc::Core::Identifier.new('/note.md'), attributes: { kind: 'note' }, ), double( :identifiable, identifier: Nanoc::Core::Identifier.new('/note-2020.md'), attributes: { kind: 'note', year: 2020 }, ), double( :identifiable, identifier: Nanoc::Core::Identifier.new('/note-2021.md'), attributes: { kind: 'note', year: 2021 }, ), ], ) end context 'with one attribute' do subject { view.where(kind: 'note') } it 'creates dependency' do expect(dependency_tracker).to receive(:bounce).with(wrapped, attributes: { kind: 'note' }) subject end it 'contains views' do expect(subject.size).to be(3) note = subject.find { |iv| iv.identifier == '/note.md' } note2020 = subject.find { |iv| iv.identifier == '/note-2020.md' } note2021 = subject.find { |iv| iv.identifier == '/note-2021.md' } expect(note.class).to equal(view_class) expect(note2020.class).to equal(view_class) expect(note2021.class).to equal(view_class) end end context 'with two attributes' do subject { view.where(kind: 'note', year: 2020) } it 'creates dependency' do expect(dependency_tracker).to receive(:bounce).with(wrapped, attributes: { kind: 'note', year: 2020 }) subject end it 'contains views' do expect(subject.size).to be(1) note2020 = subject.find { |iv| iv.identifier == '/note-2020.md' } expect(note2020.class).to equal(view_class) end end end end nanoc-4.13.3/nanoc-core/spec/nanoc/core/support/item_rep_collection_view_examples.rb000066400000000000000000000062711472033334600307270ustar00rootroot00000000000000# frozen_string_literal: true shared_examples 'an item rep collection view' do let(:view) { described_class.new(wrapped, view_context) } let(:view_context) { nil } 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 { is_expected.to 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 { is_expected.to eq 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 be(: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 { is_expected.to 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::Core::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.13.3/nanoc-core/spec/nanoc/core/support/item_rep_view_examples.rb000066400000000000000000000230261472033334600265110ustar00rootroot00000000000000# frozen_string_literal: true shared_examples 'an item rep view' do # needs expected_item_view_class let(:view_context) do Nanoc::Core::ViewContextForCompilation.new( reps:, items:, dependency_tracker:, compilation_context:, compiled_content_store:, ) end let(:compilation_context) do Nanoc::Core::CompilationContext.new( action_provider:, reps:, site:, compiled_content_cache:, compiled_content_store:, ) end let(:items) { Nanoc::Core::ItemCollection.new(config) } let(:layouts) { Nanoc::Core::LayoutCollection.new(config) } let(:reps) { Nanoc::Core::ItemRepRepo.new } let(:compiled_content_store) { Nanoc::Core::CompiledContentStore.new } let(:compiled_content_cache) { Nanoc::Core::CompiledContentCache.new(config:) } let(:action_provider) do Class.new(Nanoc::Core::ActionProvider) do def self.for(_context) raise NotImplementedError end def initialize; end end.new end let(:dependency_tracker) { Nanoc::Core::DependencyTracker.new(dependency_store) } let(:dependency_store) { Nanoc::Core::DependencyStore.new(empty_items, empty_layouts, config) } let(:base_item) { Nanoc::Core::Item.new('base', {}, '/base.md') } let(:empty_items) { Nanoc::Core::ItemCollection.new(config) } let(:empty_layouts) { Nanoc::Core::LayoutCollection.new(config) } let(:config) { Nanoc::Core::Configuration.new(dir: Dir.getwd).with_defaults } let(:site) do Nanoc::Core::Site.new( config:, code_snippets: [], data_source: Nanoc::Core::InMemoryDataSource.new(items, layouts), ) end before do dependency_tracker.enter(base_item) end describe '#frozen?' do subject { view.frozen? } let(:item_rep) { Nanoc::Core::ItemRep.new(item, :jacques) } let(:item) { Nanoc::Core::Item.new('asdf', {}, '/foo') } let(:view) { described_class.new(item_rep, view_context) } 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::Core::ItemRep.new(item, :jacques) } let(:item) { Nanoc::Core::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 subject { view.hash } let(:item_rep) { Nanoc::Core::ItemRep.new(item, :jacques) } let(:item) { Nanoc::Core::Item.new('asdf', {}, '/foo') } let(:view) { described_class.new(item_rep, view_context) } it { is_expected.to eq([described_class, Nanoc::Core::Identifier.new('/foo'), :jacques].hash) } end describe '#snapshot?' do subject { view.snapshot?(snapshot_name) } let(:view) { described_class.new(rep, view_context) } let(:rep) do Nanoc::Core::ItemRep.new(item, :default).tap do |ir| ir.compiled = true ir.snapshot_defs = [ Nanoc::Core::SnapshotDef.new(:last, binary: false), ] end end let(:item) do Nanoc::Core::Item.new('content', {}, '/asdf.md') end let(:snapshot_name) { raise 'override me' } before do compiled_content_store.set(rep, :last, Nanoc::Core::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 be(true) expect(dep.props.raw_content?).to be(false) expect(dep.props.attributes?).to be(false) expect(dep.props.path?).to be(false) end it { is_expected.not_to be(false) } 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 be(true) expect(dep.props.raw_content?).to be(false) expect(dep.props.attributes?).to be(false) expect(dep.props.path?).to be(false) end it { is_expected.to be(false) } end end describe '#path' do subject { view.path } let(:view) { described_class.new(rep, view_context) } let(:rep) do Nanoc::Core::ItemRep.new(item, :default).tap do |ir| ir.paths = { last: ['/about/'], } end end let(:item) do Nanoc::Core::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 be(true) expect(dep.props.raw_content?).to be(false) expect(dep.props.attributes?).to be(false) expect(dep.props.compiled_content?).to be(false) end it { is_expected.to eq('/about/') } end describe '#binary?' do subject { view.binary? } let(:item_rep) { Nanoc::Core::ItemRep.new(item, :jacques) } let(:item) { Nanoc::Core::Item.new('asdf', {}, '/foo') } let(:view) { described_class.new(item_rep, view_context) } context 'no :last snapshot' do before do item_rep.snapshot_defs = [] end it 'raises' do expect { subject }.to raise_error(Nanoc::Core::Errors::NoSuchSnapshot) end end context ':last snapshot is textual' do before do item_rep.snapshot_defs = [Nanoc::Core::SnapshotDef.new(:last, binary: false)] end it { is_expected.to be(false) } end context ':last snapshot is binary' do before do item_rep.snapshot_defs = [Nanoc::Core::SnapshotDef.new(:last, binary: true)] end it { is_expected.to be(true) } end end describe '#item' do subject { view.item } let(:item_rep) { Nanoc::Core::ItemRep.new(item, :jacques) } let(:item) { Nanoc::Core::Item.new('asdf', {}, '/foo') } let(:view) { described_class.new(item_rep, view_context) } 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 subject { view.inspect } let(:item_rep) { Nanoc::Core::ItemRep.new(item, :jacques) } let(:item) { Nanoc::Core::Item.new('asdf', {}, '/foo') } let(:view) { described_class.new(item_rep, view_context) } it { is_expected.to eql('<' + described_class.to_s + ' item.identifier=/foo name=jacques>') } end end nanoc-4.13.3/nanoc-core/spec/nanoc/core/support/mutable_document_view_examples.rb000066400000000000000000000146021472033334600302340ustar00rootroot00000000000000# frozen_string_literal: true shared_examples 'a mutable document view' do let(:view) { described_class.new(document, view_context) } let(:view_context) do Nanoc::Core::ViewContextForCompilation.new( reps: Nanoc::Core::ItemRepRepo.new, items: Nanoc::Core::ItemCollection.new(config), dependency_tracker:, compilation_context:, compiled_content_store:, ) end let(:dependency_tracker) { Nanoc::Core::DependencyTracker.new(double(:dependency_store)) } let(:config) { Nanoc::Core::Configuration.new(dir: Dir.getwd).with_defaults } let(:items) { Nanoc::Core::ItemCollection.new(config) } let(:layouts) { Nanoc::Core::LayoutCollection.new(config) } let(:reps) { Nanoc::Core::ItemRepRepo.new } let(:compilation_context) do Nanoc::Core::CompilationContext.new( action_provider:, reps:, site:, compiled_content_cache:, compiled_content_store:, ) end let(:compiled_content_store) { Nanoc::Core::CompiledContentStore.new } let(:compiled_content_cache) { Nanoc::Core::CompiledContentCache.new(config:) } let(:site) do Nanoc::Core::Site.new( config:, code_snippets: [], data_source: Nanoc::Core::InMemoryDataSource.new(items, layouts), ) end let(:action_provider) do Class.new(Nanoc::Core::ActionProvider) do def self.for(_context) raise NotImplementedError end def initialize; end end.new end 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::Core::Item.new('content', {}, '/foo.md') expect { view[:item] = item }.to raise_error(Nanoc::Core::MutableDocumentViewMixin::DisallowedAttributeValueError) end it 'disallows layouts' do layout = Nanoc::Core::Layout.new('content', {}, '/foo.md') expect { view[:layout] = layout }.to raise_error(Nanoc::Core::MutableDocumentViewMixin::DisallowedAttributeValueError) end it 'disallows item views' do item = Nanoc::Core::CompilationItemView.new(Nanoc::Core::Item.new('content', {}, '/foo.md'), nil) expect { view[:item] = item }.to raise_error(Nanoc::Core::MutableDocumentViewMixin::DisallowedAttributeValueError) end it 'disallows layout views' do layout = Nanoc::Core::LayoutView.new(Nanoc::Core::Layout.new('content', {}, '/foo.md'), nil) expect { view[:layout] = layout }.to raise_error(Nanoc::Core::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 subject { view.identifier = arg } let(:document) { entity_class.new('content', {}, '/about.md') } 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::Core::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::Core::Identifier::NonCoercibleObjectError) end end end describe '#update_attributes' do subject { view.update_attributes(update) } let(:document) { entity_class.new('content', {}, '/asdf') } let(:update) { { friend: 'Giraffe' } } 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.13.3/nanoc-core/spec/nanoc/core/support/mutable_identifiable_collection_view_examples.rb000066400000000000000000000020271472033334600332460ustar00rootroot00000000000000# frozen_string_literal: true shared_examples 'a mutable identifiable collection view' do let(:view) { described_class.new(wrapped, view_context) } let(:view_context) { nil } let(:config) do {} end describe '#delete_if' do let(:wrapped) do collection_class.new( config, [double(:identifiable, identifier: Nanoc::Core::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.13.3/nanoc-core/spec/nanoc/core/temp_filename_factory_spec.rb000066400000000000000000000050201472033334600256010ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Core::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 it 'is threadsafe' do # Create factory # Must happen beforehand, because creation is not threadsafe factory = subject pool = Concurrent::FixedThreadPool.new(100) # Post 10_000.times { pool.post { factory.create(prefix) } } # Wait for completion pool.shutdown pool.wait_for_termination # Check expect(factory.create(prefix)).to end_with('/10000') 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 described_class.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.13.3/nanoc-core/spec/nanoc/core/textual_compiled_content_cache_spec.rb000066400000000000000000000032041472033334600274660ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Core::TextualCompiledContentCache do let(:cache) { described_class.new(config:) } let(:items) { [item] } let(:item) { Nanoc::Core::Item.new('asdf', {}, '/foo.md') } let(:item_rep) { Nanoc::Core::ItemRep.new(item, :default) } let(:other_item) { Nanoc::Core::Item.new('asdf', {}, '/sneaky.md') } let(:other_item_rep) { Nanoc::Core::ItemRep.new(other_item, :default) } let(:content) { Nanoc::Core::Content.create('omg') } let(:config) { Nanoc::Core::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:) end it 'has no content' do expect(cache[other_item_rep]).to be_nil end end end end nanoc-4.13.3/nanoc-core/spec/nanoc/core/textual_content_spec.rb000066400000000000000000000052331472033334600244730ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Core::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 be(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 nanoc-4.13.3/nanoc-core/spec/nanoc/core/utils_spec.rb000066400000000000000000000004611472033334600224110ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Core::Utils do describe '.expand_path_without_drive_identifier' do # TODO: Test on Windows subject { described_class.expand_path_without_drive_identifier('foo.html', '/home/denis') } it { is_expected.to eq('/home/denis/foo.html') } end end nanoc-4.13.3/nanoc-core/spec/nanoc/core/yaml_loader_spec.rb000066400000000000000000000011541472033334600235410ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Core::YamlLoader do subject(:loader) { described_class } let(:yaml_input) do <<~YAML point: &point_data x: 42 y: 43 rect: origin: *point_data extent: {w: 10, h: 11} YAML end let(:expected_output) do { 'point' => { 'x' => 42, 'y' => 43 }, 'rect' => { 'origin' => { 'x' => 42, 'y' => 43 }, 'extent' => { 'w' => 10, 'h' => 11 }, }, } end describe 'load' do it 'accepts YAML aliases' do expect(loader.load(yaml_input)).to eq expected_output end end end nanoc-4.13.3/nanoc-core/spec/spec_helper.rb000066400000000000000000000002521472033334600205000ustar00rootroot00000000000000# frozen_string_literal: true require_relative '../../common/spec/spec_helper_head_core' require 'nanoc/core' require_relative '../../common/spec/spec_helper_foot_core' nanoc-4.13.3/nanoc-dart-sass/000077500000000000000000000000001472033334600157025ustar00rootroot00000000000000nanoc-4.13.3/nanoc-dart-sass/.rspec000066400000000000000000000000611472033334600170140ustar00rootroot00000000000000-r ./spec/spec_helper.rb --format Fuubar --color nanoc-4.13.3/nanoc-dart-sass/LICENSE000066400000000000000000000020711472033334600167070ustar00rootroot00000000000000Copyright (c) 2022–… 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.13.3/nanoc-dart-sass/NEWS.md000066400000000000000000000010421472033334600167750ustar00rootroot00000000000000# nanoc-dart-sass news ## 1.0.4 (2024-11-04) Fixes: - Fixed imports on Windows, by stripping drive identifier (#1703, #1716) ## 1.0.3 (2024-04-19) Enhancements: - Added support for importing partials and directories (#1690, #1699) ## 1.0.2 (2023-09-30) - Fix dart-sass compatibility (#1664) ## 1.0.1 (2023-06-15) - Added support for importing relative paths (#1646, #1653) ## 1.0.0 (2022-12-17) Initial release. Fixes: - Apply Sass syntax detection logic to dependencies (#1630) [ntkme] ## 0.0.1 (2022-11-20) Initial pre-release. nanoc-4.13.3/nanoc-dart-sass/README.md000066400000000000000000000013671472033334600171700ustar00rootroot00000000000000# nanoc-dart-sass This provides a filter that allows [Nanoc](https://nanoc.app) to process content via [Dart Sass](https://sass-lang.com/dart-sass). This filter offers similar functionality to Nanoc’s built-in `:sass` filter. The built-in `:sass` filter, however, uses the [Ruby Sass](https://sass-lang.com/ruby-sass) implementation, which has reached end of life. ## Installation Add `nanoc-dart-sass` to the `nanoc` group of your Gemfile: ```ruby group :nanoc do gem 'nanoc-dart-sass' end ``` ## Usage Call the `:dart_sass` filter. For example: ```ruby filter :dart_sass ``` Options passed to this filter will be passed on to Dart Sass. For example: ```ruby filter :dart_sass, syntax: 'scss' ``` ```ruby filter :dart_sass, syntax: 'scss' ``` nanoc-4.13.3/nanoc-dart-sass/Rakefile000066400000000000000000000004341472033334600173500ustar00rootroot00000000000000# 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.13.3/nanoc-dart-sass/lib/000077500000000000000000000000001472033334600164505ustar00rootroot00000000000000nanoc-4.13.3/nanoc-dart-sass/lib/nanoc-dart-sass.rb000066400000000000000000000000711472033334600217700ustar00rootroot00000000000000# frozen_string_literal: true require 'nanoc/dart_sass' nanoc-4.13.3/nanoc-dart-sass/lib/nanoc/000077500000000000000000000000001472033334600175465ustar00rootroot00000000000000nanoc-4.13.3/nanoc-dart-sass/lib/nanoc/dart-sass.rb000066400000000000000000000000741472033334600217750ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'dart_sass' nanoc-4.13.3/nanoc-dart-sass/lib/nanoc/dart_sass.rb000066400000000000000000000002451472033334600220570ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module DartSass end end require 'sass-embedded' require 'nanoc/dart_sass/version' require 'nanoc/dart_sass/filter' nanoc-4.13.3/nanoc-dart-sass/lib/nanoc/dart_sass/000077500000000000000000000000001472033334600215315ustar00rootroot00000000000000nanoc-4.13.3/nanoc-dart-sass/lib/nanoc/dart_sass/filter.rb000066400000000000000000000067731472033334600233600ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module DartSass class Filter < Nanoc::Filter identifier :dart_sass # Runs the content through [Dart Sass](https://sass-lang.com/dart-sass). # Parameters passed as `:args` will be passed on to Dart Sass. # # @param [String] content The content to filter # # @return [String] The filtered content def run(content, params = {}) # Read syntax syntax = params[:syntax] syntax ||= Util.syntax_from_ext(item.identifier.ext) result = Sass.compile_string( content, importer: NanocImporter.new(@items, item), **params, syntax:, ) result.css end class NanocImporter def initialize(items, source_item) @items = items @source_item = source_item end def canonicalize(url, *, **) # Construct proper URL with `nanoc:` prefix if needed if url.start_with?('nanoc:') url else "nanoc:#{url}" end end def load(url) item = find_item_for_url(url) { contents: item.raw_content, syntax: Util.syntax_from_ext(item.identifier.ext), } end private def find_item_for_url(url) pat = url.sub(/\Ananoc:/, '') is_extension_given = !pat.match?(%r{(/|^)[^.]+$}) # Convert to absolute pattern pat = if pat.start_with?('/') pat else dirname = File.dirname(@source_item.identifier.to_s) Nanoc::Core::Utils.expand_path_without_drive_identifier(pat, dirname) end items = collect_items(pat, is_extension_given) # Get the single matching item, or error if there isn’t exactly one items = items.compact case items.size when 0 raise "Could not find an item matching pattern `#{pat}`" when 1 items.first else raise "It is not clear which item to import. Multiple items match `#{pat}`: #{items.map { _1.identifier.to_s }.sort.join(', ')}" end end # Given a pattern, return a collection of items that match this pattern. # This goes beyond what Nanoc patterns typically support by e.g. # supporting partials and index imports. def collect_items(pat, is_extension_given) items = [] # Try as a regular path items.concat(try_pat(pat, is_extension_given)) # Try as a partial partial_pat = File.join(File.dirname(pat), "_#{File.basename(pat)}") items.concat(try_pat(partial_pat, is_extension_given)) # Try as index unless is_extension_given items.concat(@items.find_all(File.join(pat, '/index.*'))) items.concat(@items.find_all(File.join(pat, '/_index.*'))) end items end def try_pat(pat, is_extension_given) if is_extension_given @items.find_all(pat) else @items.find_all("#{pat}.*") end end end module Util module_function def syntax_from_ext(ext) case ext when 'sass' :indented when 'scss' :scss when 'css' :css else nil end end end private_constant :Util end end end nanoc-4.13.3/nanoc-dart-sass/lib/nanoc/dart_sass/version.rb000066400000000000000000000001361472033334600235430ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module DartSass VERSION = '1.0.4' end end nanoc-4.13.3/nanoc-dart-sass/nanoc-dart-sass.gemspec000066400000000000000000000015041472033334600222440ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'lib/nanoc/dart_sass/version' Gem::Specification.new do |s| s.name = 'nanoc-dart-sass' s.version = Nanoc::DartSass::VERSION s.homepage = 'https://nanoc.app/' s.summary = 'Dart Sass filter for Nanoc' s.description = 'Provides a :dart_sass 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 = '>= 3.1' s.add_dependency('nanoc-core', '~> 4.13', '>= 4.13.1') s.add_dependency('sass-embedded', '~> 1.56') s.metadata = { 'rubygems_mfa_required' => 'true', 'source_code_uri' => "https://github.com/nanoc/nanoc/tree/#{s.name}-v#{s.version}/#{s.name}", } end nanoc-4.13.3/nanoc-dart-sass/nanoc-dart-sass.manifest000066400000000000000000000002251472033334600224260ustar00rootroot00000000000000NEWS.md README.md lib/nanoc-dart-sass.rb lib/nanoc/dart-sass.rb lib/nanoc/dart_sass.rb lib/nanoc/dart_sass/filter.rb lib/nanoc/dart_sass/version.rb nanoc-4.13.3/nanoc-dart-sass/spec/000077500000000000000000000000001472033334600166345ustar00rootroot00000000000000nanoc-4.13.3/nanoc-dart-sass/spec/gem_spec.rb000066400000000000000000000007321472033334600207450ustar00rootroot00000000000000# frozen_string_literal: true describe 'nanoc-dart-sass.gem', chdir: false, stdio: true do subject(:build_gem) do TTY::Command.new.run('gem build nanoc-dart-sass.gemspec') end around do |ex| Dir['*.gem'].each { |f| FileUtils.rm(f) } ex.run Dir['*.gem'].each { |f| FileUtils.rm(f) } end it 'builds gem' do expect { build_gem } .to change { Dir['*.gem'] } .from([]) .to(include(match(/^nanoc-dart-sass-.*\.gem$/))) end end nanoc-4.13.3/nanoc-dart-sass/spec/manifest_spec.rb000066400000000000000000000002221472033334600217750ustar00rootroot00000000000000# frozen_string_literal: true describe 'manifest', chdir: false do example do expect('nanoc-dart-sass').to have_a_valid_manifest end end nanoc-4.13.3/nanoc-dart-sass/spec/nanoc/000077500000000000000000000000001472033334600177325ustar00rootroot00000000000000nanoc-4.13.3/nanoc-dart-sass/spec/nanoc/dart_sass/000077500000000000000000000000001472033334600217155ustar00rootroot00000000000000nanoc-4.13.3/nanoc-dart-sass/spec/nanoc/dart_sass/filter/000077500000000000000000000000001472033334600232025ustar00rootroot00000000000000nanoc-4.13.3/nanoc-dart-sass/spec/nanoc/dart_sass/filter/nanoc_importer_spec.rb000066400000000000000000000146301472033334600275640ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::DartSass::Filter::NanocImporter do let(:importer) { described_class.new(items_view, source_item) } let(:items_view) { Nanoc::Core::ItemCollectionWithoutRepsView.new(items, view_context) } let(:config) { Nanoc::Core::Configuration.new(dir: Dir.getwd).with_defaults } let(:base_item) { Nanoc::Core::Item.new('base', {}, '/base.md') } let(:dependency_store) { Nanoc::Core::DependencyStore.new(items, layouts, config) } let(:dependency_tracker) { Nanoc::Core::DependencyTracker.new(dependency_store) } let(:items) { Nanoc::Core::ItemCollection.new(config, items_array) } let(:layouts) { Nanoc::Core::LayoutCollection.new(config) } let(:reps) { Nanoc::Core::ItemRepRepo.new } let(:view_context) do Nanoc::Core::ViewContextForCompilation.new( reps: Nanoc::Core::ItemRepRepo.new, items:, dependency_tracker:, compilation_context:, compiled_content_store:, ) end let(:compilation_context) do Nanoc::Core::CompilationContext.new( action_provider:, reps:, site:, compiled_content_cache:, compiled_content_store:, ) end let(:compiled_content_store) { Nanoc::Core::CompiledContentStore.new } let(:compiled_content_cache) { Nanoc::Core::CompiledContentCache.new(config:) } let(:site) do Nanoc::Core::Site.new( config:, code_snippets: [], data_source: Nanoc::Core::InMemoryDataSource.new(items, layouts), ) end let(:action_provider) do Class.new(Nanoc::Core::ActionProvider) do def self.for(_context) raise NotImplementedError end end.new end let(:screen_item) { Nanoc::Core::Item.new('screen content here', {}, '/assets/style/screen.scss') } let(:colors_item) { Nanoc::Core::Item.new('colors content here', {}, '/assets/style/colors.scss') } let(:partial_item) { Nanoc::Core::Item.new('partial content here', {}, '/assets/style/_partial.scss') } let(:source_item) { screen_item } let(:items_array) do [ screen_item, colors_item, partial_item, ] end describe '#canonicalize' do subject { importer.canonicalize(url) } context 'when given a URL with nanoc: prefix' do let(:url) { 'nanoc:foo' } it { is_expected.to eq('nanoc:foo') } end context 'when given a URL without nanoc: prefix' do let(:url) { 'foo' } it { is_expected.to eq('nanoc:foo') } end end describe '#load' do subject(:load_call) { importer.load(url) } context 'when importing absolute path with extension' do let(:url) { '/assets/style/colors.scss' } it { is_expected.to eq({ contents: 'colors content here', syntax: :scss }) } end context 'when importing absolute path without extension' do let(:url) { '/assets/style/colors' } it { is_expected.to eq({ contents: 'colors content here', syntax: :scss }) } end context 'when importing relative path with dot with extension' do let(:url) { './colors.scss' } it { is_expected.to eq({ contents: 'colors content here', syntax: :scss }) } end context 'when importing relative path with dot without extension' do let(:url) { './colors' } it { is_expected.to eq({ contents: 'colors content here', syntax: :scss }) } end context 'when importing relative path without dot with extension' do let(:url) { 'colors.scss' } it { is_expected.to eq({ contents: 'colors content here', syntax: :scss }) } end context 'when importing relative path without dot without extension' do let(:url) { 'colors' } it { is_expected.to eq({ contents: 'colors content here', syntax: :scss }) } end context 'when importing partial with relative path without dot with extension' do let(:url) { 'partial.scss' } it { is_expected.to eq({ contents: 'partial content here', syntax: :scss }) } end context 'when importing partial with relative path without dot without extension' do let(:url) { 'partial' } it { is_expected.to eq({ contents: 'partial content here', syntax: :scss }) } end context 'with index (not a partial)' do let(:foundation_item) { Nanoc::Core::Item.new('foundation/index content here', {}, '/assets/style/foundation/index.scss') } let(:source_item) { screen_item } let(:items_array) do [ screen_item, foundation_item, ] end context 'when importing index with relative path without dot without extension' do let(:url) { 'foundation' } it { is_expected.to eq({ contents: 'foundation/index content here', syntax: :scss }) } end context 'when importing index with relative path with dot with extension' do let(:url) { 'foundation.*' } it 'raises' do expect { load_call }.to raise_error('Could not find an item matching pattern `/assets/style/foundation.*`') end end end context 'with index (partial)' do let(:foundation_item) { Nanoc::Core::Item.new('foundation/index content here', {}, '/assets/style/foundation/_index.scss') } let(:source_item) { screen_item } let(:items_array) do [ screen_item, foundation_item, ] end context 'when importing index with relative path without dot without extension' do let(:url) { 'foundation' } it { is_expected.to eq({ contents: 'foundation/index content here', syntax: :scss }) } end context 'when importing index with relative path with dot with extension' do let(:url) { 'foundation.*' } it 'raises' do expect { load_call }.to raise_error('Could not find an item matching pattern `/assets/style/foundation.*`') end end end context 'with ambiguous import' do let(:color_scss_item) { Nanoc::Core::Item.new('foundation/index content here', {}, '/assets/style/color.scss') } let(:color_sass_item) { Nanoc::Core::Item.new('foundation/index content here', {}, '/assets/style/color.sass') } let(:source_item) { screen_item } let(:items_array) do [ screen_item, color_scss_item, color_sass_item, ] end let(:url) { 'color.*' } it 'raises' do expect { load_call }.to raise_error('It is not clear which item to import. Multiple items match `/assets/style/color.*`: /assets/style/color.sass, /assets/style/color.scss') end end end end nanoc-4.13.3/nanoc-dart-sass/spec/nanoc/dart_sass/filter_spec.rb000066400000000000000000000100231472033334600245350ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::DartSass::Filter, helper: true do it 'supports simple .scss' do # Create item ctx.create_item('stuff', {}, '/foo.scss') ctx.create_rep(ctx.items['/foo.scss'], '/assets/foo.css') ctx.item = ctx.items['/foo.scss'] filter = described_class.new(ctx.assigns) res = filter.run(<<~SCSS) $primary-color: #333; body { color: $primary-color; } SCSS expect(res.strip).to match(/color: #333/m) end it 'supports simple .scss with manual syntax' do # Create item ctx.create_item('stuff', {}, '/foo.css') ctx.create_rep(ctx.items['/foo.css'], '/assets/foo.css') ctx.item = ctx.items['/foo.css'] filter = described_class.new(ctx.assigns) res = filter.run(<<~SCSS, syntax: 'scss') $primary-color: #333; body { color: $primary-color; } SCSS expect(res.strip).to match(/color: #333/m) end it 'supports simple .sass' do # Create item ctx.create_item('stuff', {}, '/foo.sass') ctx.create_rep(ctx.items['/foo.sass'], '/assets/foo.css') ctx.item = ctx.items['/foo.sass'] filter = described_class.new(ctx.assigns) res = filter.run(<<~SASS) $primary-color: #333 body color: $primary-color SASS expect(res.strip).to match(/color: #333/m) end context 'when one item depends on another with absolute path' do before do # Create item ctx.create_item('stuff', {}, '/foo.scss') ctx.create_rep(ctx.items['/foo.scss'], '/assets/foo.css') ctx.item = ctx.items['/foo.scss'] # Create other item ctx.create_item('$primary-color: #900;', {}, '/defs.scss') ctx.create_rep(ctx.items['/defs.scss'], '/assets/defs.css') end let(:content) do <<~SCSS @use '/defs.*'; body { color: defs.$primary-color; } SCSS end it 'supports reading from dependencies' do filter = described_class.new(ctx.assigns) res = filter.run(content) expect(res.strip).to match(/color: #900/m) end it 'creates Nanoc dependencies' do filter = described_class.new(ctx.assigns) expect { filter.run(content) } .to create_dependency_from(ctx.items['/foo.scss']) .onto([instance_of(Nanoc::Core::ItemCollection), ctx.items['/defs.scss']]) end end context 'when one item depends on another with relative path' do before do # Create item ctx.create_item('stuff', {}, '/assets/style/foo.scss') ctx.create_rep(ctx.items['/assets/style/foo.scss'], '/assets/foo.css') ctx.item = ctx.items['/assets/style/foo.scss'] # Create other items ctx.create_item('$fg-color: #900;', {}, '/assets/style/defs1.scss') ctx.create_rep(ctx.items['/assets/style/defs1.scss'], '/assets/defs1.css') ctx.create_item('$bg-color: #dff;', {}, '/assets/style/defs2.scss') ctx.create_rep(ctx.items['/assets/style/defs2.scss'], '/assets/defs2.css') ctx.create_item('$hl: #f00;', {}, '/assets/style/defs3.scss') ctx.create_rep(ctx.items['/assets/style/defs3.scss'], '/assets/defs3.css') end let(:content) do <<~SCSS @use './defs1.*'; @use 'defs2.*'; @use 'defs3'; body { color: defs1.$fg-color; background: defs2.$bg-color; } SCSS end it 'supports reading from dependencies' do filter = described_class.new(ctx.assigns) res = filter.run(content) expect(res.strip).to match(/color: #900/m) expect(res.strip).to match(/background: #dff/m) end it 'creates Nanoc dependencies' do filter = described_class.new(ctx.assigns) expect { filter.run(content) } .to create_dependency_from(ctx.items['/assets/style/foo.scss']) .onto( [ instance_of(Nanoc::Core::ItemCollection), ctx.items['/assets/style/defs1.scss'], ctx.items['/assets/style/defs2.scss'], ctx.items['/assets/style/defs3.scss'], ], ) end end end nanoc-4.13.3/nanoc-dart-sass/spec/spec_helper.rb000066400000000000000000000002471472033334600214550ustar00rootroot00000000000000# frozen_string_literal: true require_relative '../../common/spec/spec_helper_head' require 'nanoc/dart_sass' require_relative '../../common/spec/spec_helper_foot' nanoc-4.13.3/nanoc-deploying/000077500000000000000000000000001472033334600157735ustar00rootroot00000000000000nanoc-4.13.3/nanoc-deploying/.rspec000066400000000000000000000000611472033334600171050ustar00rootroot00000000000000-r ./spec/spec_helper.rb --format Fuubar --color nanoc-4.13.3/nanoc-deploying/LICENSE000066400000000000000000000020711472033334600170000ustar00rootroot00000000000000Copyright (c) 2014–… 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.13.3/nanoc-deploying/NEWS.md000066400000000000000000000004101472033334600170640ustar00rootroot00000000000000# nanoc-deploying news ## 1.0.2 (2022-05-28) - Made the Git deployer perform `git push` even if there is nothing to commit (#1575) ## 1.0.1 (2021-01-01) Enhancements: - Added support for Ruby 3.x ## 1.0.0 (2020-03-07) Initial release (extracted from nanoc) nanoc-4.13.3/nanoc-deploying/README.md000066400000000000000000000003471472033334600172560ustar00rootroot00000000000000# nanoc-deploying This provides the `deploy` command and associated functionality for [Nanoc](https://nanoc.app). For details, see the [Deploying Nanoc sites](https://nanoc.app/doc/deploying/) chapter of the Nanoc documentation. nanoc-4.13.3/nanoc-deploying/Rakefile000066400000000000000000000004341472033334600174410ustar00rootroot00000000000000# 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.13.3/nanoc-deploying/lib/000077500000000000000000000000001472033334600165415ustar00rootroot00000000000000nanoc-4.13.3/nanoc-deploying/lib/nanoc-deploying.rb000066400000000000000000000000711472033334600221520ustar00rootroot00000000000000# frozen_string_literal: true require 'nanoc/deploying' nanoc-4.13.3/nanoc-deploying/lib/nanoc/000077500000000000000000000000001472033334600176375ustar00rootroot00000000000000nanoc-4.13.3/nanoc-deploying/lib/nanoc/deploying.rb000066400000000000000000000011401472033334600221520ustar00rootroot00000000000000# frozen_string_literal: true require 'nanoc-core' require 'nanoc-cli' module Nanoc module Deploying end end require 'nanoc/deploying/version' require 'nanoc/deploying/deployer' require 'nanoc/deploying/deployers' require 'nanoc/deploying/command_runners' root = File.dirname(__FILE__) deploying_command_path = File.join(root, 'deploying', 'commands', 'deploy.rb') command = Cri::Command.load_file(deploying_command_path, infer_name: true) Nanoc::CLI.after_setup do Nanoc::CLI.add_command(command) Nanoc::CLI::Commands::ShowPlugins.add_plugin_class(Nanoc::Deploying::Deployer, 'Deployers') end nanoc-4.13.3/nanoc-deploying/lib/nanoc/deploying/000077500000000000000000000000001472033334600216315ustar00rootroot00000000000000nanoc-4.13.3/nanoc-deploying/lib/nanoc/deploying/command_runners.rb000066400000000000000000000002451472033334600253510ustar00rootroot00000000000000# frozen_string_literal: true # @api private module Nanoc module Deploying module CommandRunners end end end require_relative 'command_runners/deploy' nanoc-4.13.3/nanoc-deploying/lib/nanoc/deploying/command_runners/000077500000000000000000000000001472033334600250235ustar00rootroot00000000000000nanoc-4.13.3/nanoc-deploying/lib/nanoc/deploying/command_runners/deploy.rb000066400000000000000000000066151472033334600266540ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Deploying module CommandRunners class Deploy < ::Nanoc::CLI::CommandRunner def run @site = load_site Nanoc::Core::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::Core::TrivialError, 'The site has no deployment configurations.' end if arguments.length > 1 raise Nanoc::Core::TrivialError, "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::Core::TrivialError, '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::Core::TrivialError, "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::Core::TrivialError, "The specified deploy target has an unrecognised kind “#{name}” (expected one of #{names.join(', ')})." end deployer_class end end end end end nanoc-4.13.3/nanoc-deploying/lib/nanoc/deploying/commands/000077500000000000000000000000001472033334600234325ustar00rootroot00000000000000nanoc-4.13.3/nanoc-deploying/lib/nanoc/deploying/commands/deploy.rb000066400000000000000000000012701472033334600252530ustar00rootroot00000000000000# 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' runner Nanoc::Deploying::CommandRunners::Deploy nanoc-4.13.3/nanoc-deploying/lib/nanoc/deploying/deployer.rb000066400000000000000000000027271472033334600240110ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module 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 end nanoc-4.13.3/nanoc-deploying/lib/nanoc/deploying/deployers.rb000066400000000000000000000003621472033334600241650ustar00rootroot00000000000000# frozen_string_literal: true require 'tty-command' # @api private module Nanoc module Deploying module Deployers end end end require_relative 'deployers/fog' require_relative 'deployers/git' require_relative 'deployers/rsync' nanoc-4.13.3/nanoc-deploying/lib/nanoc/deploying/deployers/000077500000000000000000000000001472033334600236375ustar00rootroot00000000000000nanoc-4.13.3/nanoc-deploying/lib/nanoc/deploying/deployers/fog.rb000066400000000000000000000137571472033334600247540ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Deploying module 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 end end nanoc-4.13.3/nanoc-deploying/lib/nanoc/deploying/deployers/git.rb000066400000000000000000000072121472033334600247510ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Deploying module Deployers # A deployer that deploys a site using [Git](https://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::Core::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 TTY::Command::ExitError 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 TTY::Command::ExitError raise Errors::BranchDoesNotExist.new(branch) end # Commit (if needed) unless 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}]) end # Push 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, dry_run: false) TTY::Command.new(**tty_command_options).run(*cmd, dry_run:) end def run_cmd_unless_dry(cmd) run_cmd(cmd, dry_run:) end def clean_repo? TTY::Command.new(**tty_command_options).run('git status --porcelain').out.empty? end def tty_command_options if Nanoc::CLI.verbosity >= 1 { uuid: false } else { printer: :null } end end end end end end nanoc-4.13.3/nanoc-deploying/lib/nanoc/deploying/deployers/rsync.rb000066400000000000000000000040271472033334600253250ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Deploying module 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) TTY::Command.new(printer: :null).run(*cmd) end end end end end nanoc-4.13.3/nanoc-deploying/lib/nanoc/deploying/version.rb000066400000000000000000000001371472033334600236440ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Deploying VERSION = '1.0.2' end end nanoc-4.13.3/nanoc-deploying/nanoc-deploying.gemspec000066400000000000000000000016041472033334600224270ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'lib/nanoc/deploying/version' Gem::Specification.new do |s| s.name = 'nanoc-deploying' s.version = Nanoc::Deploying::VERSION s.homepage = 'https://nanoc.app/' s.summary = 'Deploying support for Nanoc' s.description = 'Provides deploying functionality 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 = '>= 3.1' s.add_dependency('nanoc-checking', '~> 1.0') s.add_dependency('nanoc-cli', '~> 4.11', '>= 4.11.15') s.add_dependency('nanoc-core', '~> 4.11', '>= 4.11.15') s.metadata = { 'rubygems_mfa_required' => 'true', 'source_code_uri' => "https://github.com/nanoc/nanoc/tree/#{s.name}-v#{s.version}/#{s.name}", } end nanoc-4.13.3/nanoc-deploying/nanoc-deploying.manifest000066400000000000000000000006161472033334600226140ustar00rootroot00000000000000NEWS.md README.md lib/nanoc-deploying.rb lib/nanoc/deploying.rb lib/nanoc/deploying/command_runners.rb lib/nanoc/deploying/command_runners/deploy.rb lib/nanoc/deploying/commands/deploy.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/deploying/version.rb nanoc-4.13.3/nanoc-deploying/spec/000077500000000000000000000000001472033334600167255ustar00rootroot00000000000000nanoc-4.13.3/nanoc-deploying/spec/gem_spec.rb000066400000000000000000000007141472033334600210360ustar00rootroot00000000000000# frozen_string_literal: true describe 'nanoc-deploying.gem', chdir: false, stdio: true do subject do TTY::Command.new.run('gem build nanoc-deploying.gemspec') end around do |ex| Dir['*.gem'].each { |f| FileUtils.rm(f) } ex.run Dir['*.gem'].each { |f| FileUtils.rm(f) } end it 'builds gem' do expect { subject } .to change { Dir['*.gem'] } .from([]) .to(include(match(/^nanoc-deploying-.*\.gem$/))) end end nanoc-4.13.3/nanoc-deploying/spec/manifest_spec.rb000066400000000000000000000002221472033334600220660ustar00rootroot00000000000000# frozen_string_literal: true describe 'manifest', chdir: false do example do expect('nanoc-deploying').to have_a_valid_manifest end end nanoc-4.13.3/nanoc-deploying/spec/nanoc/000077500000000000000000000000001472033334600200235ustar00rootroot00000000000000nanoc-4.13.3/nanoc-deploying/spec/nanoc/deploying/000077500000000000000000000000001472033334600220155ustar00rootroot00000000000000nanoc-4.13.3/nanoc-deploying/spec/nanoc/deploying/command_runners/000077500000000000000000000000001472033334600252075ustar00rootroot00000000000000nanoc-4.13.3/nanoc-deploying/spec/nanoc/deploying/command_runners/deploy_spec.rb000066400000000000000000000217771472033334600300600ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Deploying::CommandRunners::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', <<~CHECKS, check :donkey do add_issue('things are broken', subject: 'success.txt') end deploy_check :donkey CHECKS ) 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::Core::TrivialError, 'The site has no deployment configurations.', ) end include_examples 'no effective deploy' context 'configuration created in preprocessor' do before do File.write( 'Rules', <<~RULES + File.read('Rules'), preprocess do @config[:deploy] = { default: { dst: 'remote' }, } end 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::Core::TrivialError, '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::Core::TrivialError, '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::Core::TrivialError, 'Only one deployment target can be specified on the command line.', ) end end end end end end nanoc-4.13.3/nanoc-deploying/spec/nanoc/deploying/deployers/000077500000000000000000000000001472033334600240235ustar00rootroot00000000000000nanoc-4.13.3/nanoc-deploying/spec/nanoc/deploying/deployers/fog_spec.rb000066400000000000000000000164101472033334600261370ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Deploying::Deployers::Fog, stdio: true do subject { deployer.run } let(:deployer) do described_class.new( 'output/', config, dry_run: is_dry_run, ) end let(:is_dry_run) { false } let(:config) do { bucket: 'bucky', provider:, local_root: 'remote', } end let(:provider) { 'local' } before do skip_unless_gem_available('fog/core') # create output FileUtils.mkdir_p('output') FileUtils.mkdir_p('output/etc') File.write('output/woof', 'I am a dog!') File.write('output/etc/meow', 'I am a cat!') # create local cloud FileUtils.mkdir_p('remote') end 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 describe '#read_etags' do subject { deployer.send(:read_etags, files) } context 'when using local provider' do let(:provider) { 'local' } let(:files) do [ double('file_a'), double('file_b'), ] end it { is_expected.to eq({}) } end context 'when using aws provider' do let(:provider) { 'aws' } let(:files) do [ double('file_a', key: 'key_a', etag: 'etag_a'), double('file_b', key: 'key_b', etag: 'etag_b'), ] end let(:expected) do { 'key_a' => 'etag_a', 'key_b' => 'etag_b', } end it { is_expected.to eq(expected) } end end describe '#calc_local_etag' do subject { deployer.send(:calc_local_etag, file_path) } let(:file_path) { 'blah.tmp' } before do File.write(file_path, 'hallo') end context 'when using local provider' do let(:provider) { 'local' } it { is_expected.to be_nil } end context 'when using aws provider' do let(:provider) { 'aws' } it { is_expected.to eq('598d4c200461b81522a3328565c25f7c') } end end describe '#needs_upload?' do subject { deployer.send(:needs_upload?, key, file_path, etags) } let(:file_path) { 'blah.tmp' } let(:key) { '/moo/remote/blah.tmp.123' } let(:provider) { 'aws' } before do File.write(file_path, 'hallo') end context 'missing remote etag' do let(:etags) { {} } it { is_expected.to be true } end context 'different etags' do let(:etags) { { key => 'some-other-etag' } } it { is_expected.to be true } end context 'identical etags' do let(:etags) { { key => '598d4c200461b81522a3328565c25f7c' } } it { is_expected.to be false } end end end nanoc-4.13.3/nanoc-deploying/spec/nanoc/deploying/deployers/git_spec.rb000066400000000000000000000223001472033334600261420ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Deploying::Deployers::Git, stdio: true do subject { deployer.run } let(:deployer) { described_class.new(output_dir, options, dry_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) TTY::Command.new.run(*args).out 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') # Ensure we’re on the master branch (--initial-branch is not supported by all Git versions) unless `git symbolic-ref --short HEAD` == 'master' system('git', 'checkout', '--quiet', '-b', 'master') end 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, but still pushes' do expect { subject } .to output(/Deploying via Git to branch “#{branch}” on remote “#{remote}”…/) .to_stdout 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(TTY::Command::ExitError) 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(TTY::Command::ExitError) 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') # Ensure we’re on the master branch (--initial-branch is not supported by all Git versions) unless `git symbolic-ref --short HEAD` == 'master' system('git', 'checkout', '--quiet', '-b', 'master') end end end let(:branch) { 'master' } include_examples 'branch configured properly' end end context 'custom branch' do let(:branch) { 'giraffe' } let(:branch_options) { { 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') # Ensure we’re on the master branch (--initial-branch is not supported by all Git versions) unless `git symbolic-ref --short HEAD` == 'master' system('git', 'checkout', '--quiet', '-b', 'master') end 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: } } 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.13.3/nanoc-deploying/spec/nanoc/deploying/deployers/rsync_spec.rb000066400000000000000000000031711472033334600265220ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Deploying::Deployers::Rsync, stdio: true do subject { deployer.run } let(:deployer) do described_class.new( 'output', config, **extra_opts, ) end let(:config) { {} } let(:extra_opts) { {} } before do skip_unless_have_command 'rsync' # create output FileUtils.mkdir_p('output') FileUtils.mkdir_p('output/etc') File.write('output/woof', 'I am a dog!') File.write('output/etc/meow', 'I am a cat!') # create local cloud FileUtils.mkdir_p('remote') end context 'destination is missing' do let(:config) { {} } it 'raises' do expect { subject }.to raise_error(RuntimeError, 'No dst found in deployment configuration') end end context 'destination is incorrect' do let(:config) { { dst: 'asdf/' } } it 'raises' do expect { subject }.to raise_error(RuntimeError, 'dst requires no trailing slash') end end context 'destination is correct' do let(:config) { { dst: 'asdf' } } context 'dry run' do let(:extra_opts) { { dry_run: true } } it 'runs' do opts = Nanoc::Deploying::Deployers::Rsync::DEFAULT_OPTIONS args = ['echo', 'rsync', opts, 'output/', 'asdf'].flatten expect(deployer).to receive(:run_shell_cmd).with(args) deployer.run end end context 'actual run' do it 'runs' do opts = Nanoc::Deploying::Deployers::Rsync::DEFAULT_OPTIONS args = ['rsync', opts, 'output/', 'asdf'].flatten expect(deployer).to receive(:run_shell_cmd).with(args) deployer.run end end end end nanoc-4.13.3/nanoc-deploying/spec/spec_helper.rb000066400000000000000000000002471472033334600215460ustar00rootroot00000000000000# frozen_string_literal: true require_relative '../../common/spec/spec_helper_head' require 'nanoc/deploying' require_relative '../../common/spec/spec_helper_foot' nanoc-4.13.3/nanoc-external/000077500000000000000000000000001472033334600156235ustar00rootroot00000000000000nanoc-4.13.3/nanoc-external/.rspec000066400000000000000000000000611472033334600167350ustar00rootroot00000000000000-r ./spec/spec_helper.rb --format Fuubar --color nanoc-4.13.3/nanoc-external/LICENSE000066400000000000000000000020711472033334600166300ustar00rootroot00000000000000Copyright (c) 2014–… 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.13.3/nanoc-external/NEWS.md000066400000000000000000000006161472033334600167240ustar00rootroot00000000000000# nanoc-external news ## 1.0.5 (2021-01-01) Enhancements: * Added support for Ruby 3.x ## 1.0.4 (2020-03-07) Fixes: * Restored compatibility with Nanoc 4.11.14. ## 1.0.3 (2019-04-28) Fixes: * Restored compatibility with latest version of Nanoc ## 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.13.3/nanoc-external/README.md000066400000000000000000000015311472033334600171020ustar00rootroot00000000000000# nanoc-external This provides a filter that allows [Nanoc](https://nanoc.app) 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.13.3/nanoc-external/Rakefile000066400000000000000000000004341472033334600172710ustar00rootroot00000000000000# 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.13.3/nanoc-external/lib/000077500000000000000000000000001472033334600163715ustar00rootroot00000000000000nanoc-4.13.3/nanoc-external/lib/nanoc-external.rb000066400000000000000000000000701472033334600216310ustar00rootroot00000000000000# frozen_string_literal: true require 'nanoc/external' nanoc-4.13.3/nanoc-external/lib/nanoc/000077500000000000000000000000001472033334600174675ustar00rootroot00000000000000nanoc-4.13.3/nanoc-external/lib/nanoc/external.rb000066400000000000000000000002121472033334600216310ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module External end end require 'nanoc/external/version' require 'nanoc/external/filter' nanoc-4.13.3/nanoc-external/lib/nanoc/external/000077500000000000000000000000001472033334600213115ustar00rootroot00000000000000nanoc-4.13.3/nanoc-external/lib/nanoc/external/filter.rb000066400000000000000000000005621472033334600231260ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module External class Filter < Nanoc::Filter identifier :external def run(content, params = {}) cmd = params.fetch(:exec) opts = params.fetch(:options, []) command = TTY::Command.new(printer: :null) command.run(cmd, *opts, input: content).out end end end end nanoc-4.13.3/nanoc-external/lib/nanoc/external/version.rb000066400000000000000000000001361472033334600233230ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module External VERSION = '1.0.5' end end nanoc-4.13.3/nanoc-external/nanoc-external.gemspec000066400000000000000000000014231472033334600221060ustar00rootroot00000000000000# 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 = 'https://nanoc.app/' 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 = '>= 3.1' s.add_dependency('nanoc-core', '~> 4.11', '>= 4.11.14') s.metadata = { 'rubygems_mfa_required' => 'true', 'source_code_uri' => "https://github.com/nanoc/nanoc/tree/#{s.name}-v#{s.version}/#{s.name}", } end nanoc-4.13.3/nanoc-external/nanoc-external.manifest000066400000000000000000000001721472033334600222710ustar00rootroot00000000000000NEWS.md README.md lib/nanoc-external.rb lib/nanoc/external.rb lib/nanoc/external/filter.rb lib/nanoc/external/version.rb nanoc-4.13.3/nanoc-external/spec/000077500000000000000000000000001472033334600165555ustar00rootroot00000000000000nanoc-4.13.3/nanoc-external/spec/filter_spec.rb000066400000000000000000000005321472033334600214010ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::External::Filter do example do filter = described_class.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.13.3/nanoc-external/spec/gem_spec.rb000066400000000000000000000007111472033334600206630ustar00rootroot00000000000000# frozen_string_literal: true describe 'nanoc-external.gem', chdir: false, stdio: true do subject do TTY::Command.new.run('gem build nanoc-external.gemspec') end around do |ex| Dir['*.gem'].each { |f| FileUtils.rm(f) } ex.run Dir['*.gem'].each { |f| FileUtils.rm(f) } end it 'builds gem' do expect { subject } .to change { Dir['*.gem'] } .from([]) .to(include(match(/^nanoc-external-.*\.gem$/))) end end nanoc-4.13.3/nanoc-external/spec/manifest_spec.rb000066400000000000000000000002211472033334600217150ustar00rootroot00000000000000# frozen_string_literal: true describe 'manifest', chdir: false do example do expect('nanoc-external').to have_a_valid_manifest end end nanoc-4.13.3/nanoc-external/spec/spec_helper.rb000066400000000000000000000002461472033334600213750ustar00rootroot00000000000000# frozen_string_literal: true require_relative '../../common/spec/spec_helper_head' require 'nanoc/external' require_relative '../../common/spec/spec_helper_foot' nanoc-4.13.3/nanoc-live/000077500000000000000000000000001472033334600147405ustar00rootroot00000000000000nanoc-4.13.3/nanoc-live/.rspec000066400000000000000000000000611472033334600160520ustar00rootroot00000000000000-r ./spec/spec_helper.rb --format Fuubar --color nanoc-4.13.3/nanoc-live/LICENSE000066400000000000000000000020711472033334600157450ustar00rootroot00000000000000Copyright (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.13.3/nanoc-live/NEWS.md000066400000000000000000000023701472033334600160400ustar00rootroot00000000000000# Release notes for nanoc-live ## 1.1.0 (2024-06-26) Enhancements: - Added `--focus` option to the `compile` and `live` commands (#1707) Fixes: - Fixed wrong documentation regarding listening IP addresses (#1584) [Jan M. Faber] Changes: - Dropped support for Ruby 3.0 (EOL) (#1704) ## 1.0.0 (2021-02-20) Idential to 1.0.0b8. ## 1.0.0b8 (2021-01-16) Fixes: - Fixed issue which could cause nanoc-live to keep running and use 100% CPU (#1538) ## 1.0.0b7 (2021-01-01) Enhancements: - Added support for Ruby 3.x ## 1.0.0b6 (2020-03-07) Fixes: - Restored compatibility with Nanoc 4.11.14. ## 1.0.0b5 (2019-11-16) Fixes: - Restored compatibility with Nanoc 4.11.13. ## 1.0.0b4 (2019-04-30) Fixes: - Restored compatibility with most recent version of Nanoc. ## 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.13.3/nanoc-live/README.md000066400000000000000000000002111472033334600162110ustar00rootroot00000000000000# nanoc-live The _nanoc-live_ gem provides a new `nanoc live` command, and is required to make the `nanoc compile --watch` option work. nanoc-4.13.3/nanoc-live/Rakefile000066400000000000000000000004341472033334600164060ustar00rootroot00000000000000# 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.13.3/nanoc-live/lib/000077500000000000000000000000001472033334600155065ustar00rootroot00000000000000nanoc-4.13.3/nanoc-live/lib/nanoc/000077500000000000000000000000001472033334600166045ustar00rootroot00000000000000nanoc-4.13.3/nanoc-live/lib/nanoc/live.rb000066400000000000000000000007621472033334600200750ustar00rootroot00000000000000# frozen_string_literal: true require 'adsf/live' require 'listen' require 'nanoc' require 'nanoc/orig_cli' module Nanoc module Live end end require_relative 'live/version' require_relative 'live/live_recompiler' require_relative 'live/command_runners/live' root = File.dirname(__FILE__) live_command_path = File.join(root, 'live', 'commands', 'live.rb') command = Cri::Command.load_file(live_command_path, infer_name: true) Nanoc::CLI.after_setup do Nanoc::CLI.add_command(command) end nanoc-4.13.3/nanoc-live/lib/nanoc/live/000077500000000000000000000000001472033334600175435ustar00rootroot00000000000000nanoc-4.13.3/nanoc-live/lib/nanoc/live/command_runners/000077500000000000000000000000001472033334600227355ustar00rootroot00000000000000nanoc-4.13.3/nanoc-live/lib/nanoc/live/command_runners/live.rb000066400000000000000000000025061472033334600242240ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Live module CommandRunners class Live < ::Nanoc::CLI::CommandRunner def run if defined?(Guard::Nanoc) $stderr.puts '-' * 40 $stderr.puts 'NOTE:' $stderr.puts 'You are using the `nanoc live` command provided by `nanoc-live`, but the `guard-nanoc` gem is also installed, which also provides a `nanoc live` command.' if defined?(Bundler) $stderr.puts 'Recommendation: Remove `guard-nanoc` from your Gemfile, either manually or by running the `bundle remove guard-nanoc` command.' else $stderr.puts 'Recommendation: Uninstall `guard-nanoc` (run the `gem uninstall guard-nanoc` command).' end $stderr.puts '-' * 40 end 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, focus: options[:focus]).run end end end end end nanoc-4.13.3/nanoc-live/lib/nanoc/live/commands/000077500000000000000000000000001472033334600213445ustar00rootroot00000000000000nanoc-4.13.3/nanoc-live/lib/nanoc/live/commands/live.rb000066400000000000000000000014251472033334600226320ustar00rootroot00000000000000# 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 127.0.0.1. Running this static web server requires `adsf` (not `asdf`!). EOS option :H, :handler, 'specify the handler to use (webrick/puma/...)', argument: :required option :o, :host, 'specify the host to listen on', default: '127.0.0.1', argument: :required option :p, :port, 'specify the port to listen on', transform: Nanoc::CLI::Transform::Port, default: 3000, argument: :required option nil, :focus, 'compile only items matching the given pattern', argument: :required, multiple: true no_params runner Nanoc::Live::CommandRunners::Live nanoc-4.13.3/nanoc-live/lib/nanoc/live/live_recompiler.rb000066400000000000000000000070721472033334600232560ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Live class LiveRecompiler def initialize(command_runner:, focus:) @command_runner = command_runner @focus = focus end def run run_parent do |site| handle_changes(site, @command_runner, focus: @focus) 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::Core::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::Core::SiteLoader.new site = Nanoc::Core::Site.new( config: Nanoc::Core::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(&block) # create initial child pipe_read, pipe_write = IO.pipe fork { run_child(pipe_write, pipe_read, &block) } 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, &block) } pipe_read.close end rescue Interrupt end def handle_changes(site, command_runner, focus:) Nanoc::CLI::ErrorHandler.handle_while(exit_on_error: false) do unsafe_handle_changes(site, command_runner, focus:) end end def unsafe_handle_changes(site, command_runner, focus:) time_before = Time.now puts 'Compiling site…' compiler = Nanoc::Core::Compiler.new_for(site, focus:) listener = Nanoc::CLI::CompileListeners::Aggregate.new( command_runner:, site:, 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." if focus warn 'CAUTION: A --focus option is specified. Not the entire site has been compiled.' warn 'Re-run without --focus to compile the entire site.' end puts end def gen_lib_changes Nanoc::Core::ChangesStream.new do |cl| listener = Listen.to('lib') { |*| cl.lib } listener.start sleep end end def gen_config_and_rules_changes Nanoc::Core::ChangesStream.new do |cl| only = /(\/|\A)(nanoc\.yaml|config\.yaml|rules|Rules|rules\.rb|Rules\.rb)\z/ listener = Listen.to('.', only:) { |*| cl.unknown } listener.start sleep end end end end end nanoc-4.13.3/nanoc-live/lib/nanoc/live/version.rb000066400000000000000000000001321472033334600215510ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Live VERSION = '1.1.0' end end nanoc-4.13.3/nanoc-live/nanoc-live.gemspec000066400000000000000000000016501472033334600203420ustar00rootroot00000000000000# 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 = 'https://nanoc.app/' 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 = '>= 3.1' s.add_dependency('adsf-live', '~> 1.4') s.add_dependency('listen', '~> 3.0') s.add_dependency('nanoc-cli', '~> 4.11', '>= 4.11.14') s.add_dependency('nanoc-core', '~> 4.11', '>= 4.11.14') s.metadata = { 'rubygems_mfa_required' => 'true', 'source_code_uri' => "https://github.com/nanoc/nanoc/tree/#{s.name}-v#{s.version}/#{s.name}", } end nanoc-4.13.3/nanoc-live/nanoc-live.manifest000066400000000000000000000002501472033334600205200ustar00rootroot00000000000000NEWS.md README.md lib/nanoc/live.rb lib/nanoc/live/command_runners/live.rb lib/nanoc/live/commands/live.rb lib/nanoc/live/live_recompiler.rb lib/nanoc/live/version.rb nanoc-4.13.3/nanoc-live/spec/000077500000000000000000000000001472033334600156725ustar00rootroot00000000000000nanoc-4.13.3/nanoc-live/spec/gem_spec.rb000066400000000000000000000006751472033334600200110ustar00rootroot00000000000000# frozen_string_literal: true describe 'nanoc-live.gem', chdir: false, stdio: true do subject do TTY::Command.new.run('gem build nanoc-live.gemspec') end around do |ex| Dir['*.gem'].each { |f| FileUtils.rm(f) } ex.run Dir['*.gem'].each { |f| FileUtils.rm(f) } end it 'builds gem' do expect { subject } .to change { Dir['*.gem'] } .from([]) .to(include(match(/^nanoc-live-.*\.gem$/))) end end nanoc-4.13.3/nanoc-live/spec/manifest_spec.rb000066400000000000000000000002151472033334600210350ustar00rootroot00000000000000# frozen_string_literal: true describe 'manifest', chdir: false do example do expect('nanoc-live').to have_a_valid_manifest end end nanoc-4.13.3/nanoc-live/spec/nanoc/000077500000000000000000000000001472033334600167705ustar00rootroot00000000000000nanoc-4.13.3/nanoc-live/spec/nanoc/live/000077500000000000000000000000001472033334600177275ustar00rootroot00000000000000nanoc-4.13.3/nanoc-live/spec/nanoc/live/command_runners/000077500000000000000000000000001472033334600231215ustar00rootroot00000000000000nanoc-4.13.3/nanoc-live/spec/nanoc/live/command_runners/live_spec.rb000066400000000000000000000033211472033334600254160ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Live::CommandRunners::Live, fork: true, site: true, stdio: 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.13.3/nanoc-live/spec/nanoc/live/live_recompiler_spec.rb000066400000000000000000000104121472033334600244440ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Live::LiveRecompiler, fork: true, site: true, stdio: true do before do Nanoc::CLI::ErrorHandler.enable end # TODO: vary let(:focus) { nil } it 'detects content changes' do command = nil command_runner = Nanoc::CLI::CommandRunner.new({}, [], command) live_recompiler = described_class.new(command_runner:, focus:) 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:, focus:) 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:, focus:) 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:, focus:) 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.file?('output/lol.html') && 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:, focus:) 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.13.3/nanoc-live/spec/nanoc/live_spec.rb000066400000000000000000000001511472033334600212630ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Live::VERSION do it { is_expected.to be_a(String) } end nanoc-4.13.3/nanoc-live/spec/spec_helper.rb000066400000000000000000000004261472033334600205120ustar00rootroot00000000000000# frozen_string_literal: true require_relative '../../common/spec/spec_helper_head' require 'nanoc' if Nanoc::Core.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.13.3/nanoc-org-mode/000077500000000000000000000000001472033334600155125ustar00rootroot00000000000000nanoc-4.13.3/nanoc-org-mode/.rspec000066400000000000000000000000611472033334600166240ustar00rootroot00000000000000-r ./spec/spec_helper.rb --format Fuubar --color nanoc-4.13.3/nanoc-org-mode/LICENSE000066400000000000000000000020711472033334600165170ustar00rootroot00000000000000Copyright (c) 2024–… 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.13.3/nanoc-org-mode/NEWS.md000066400000000000000000000000771472033334600166140ustar00rootroot00000000000000# nanoc-org-mode news ## 1.0.0 (2024-03-15) Initial release. nanoc-4.13.3/nanoc-org-mode/README.md000066400000000000000000000005411472033334600167710ustar00rootroot00000000000000# nanoc-org-mode This provides a filter that allows [Nanoc](https://nanoc.app) to process content via [Org Mode](https://orgmode.org/). ## Installation Add `nanoc-org-mode` to the `nanoc` group of your Gemfile: ```ruby group :nanoc do gem 'nanoc-org-mode' end ``` ## Usage Call the `:org_mode` filter. For example: ```ruby filter :org_mode ``` nanoc-4.13.3/nanoc-org-mode/Rakefile000066400000000000000000000004341472033334600171600ustar00rootroot00000000000000# 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.13.3/nanoc-org-mode/lib/000077500000000000000000000000001472033334600162605ustar00rootroot00000000000000nanoc-4.13.3/nanoc-org-mode/lib/nanoc-org-mode.rb000066400000000000000000000000701472033334600214070ustar00rootroot00000000000000# frozen_string_literal: true require 'nanoc/org_mode' nanoc-4.13.3/nanoc-org-mode/lib/nanoc/000077500000000000000000000000001472033334600173565ustar00rootroot00000000000000nanoc-4.13.3/nanoc-org-mode/lib/nanoc/org-mode.rb000066400000000000000000000000731472033334600214140ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'org_mode' nanoc-4.13.3/nanoc-org-mode/lib/nanoc/org_mode.rb000066400000000000000000000002351472033334600214760ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module OrgMode end end require 'org-ruby' require 'nanoc/org_mode/version' require 'nanoc/org_mode/filter' nanoc-4.13.3/nanoc-org-mode/lib/nanoc/org_mode/000077500000000000000000000000001472033334600211515ustar00rootroot00000000000000nanoc-4.13.3/nanoc-org-mode/lib/nanoc/org_mode/filter.rb000066400000000000000000000007351472033334600227700ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module OrgMode class Filter < Nanoc::Filter identifier :org_mode # Runs the content through [Org Mode](https://orgmode.org/) via # [org-ruby](https://github.com/wallyqs/org-ruby). # # @param [String] content The content to filter # # @return [String] The filtered content def run(content, _params = {}) ::Orgmode::Parser.new(content).to_html end end end end nanoc-4.13.3/nanoc-org-mode/lib/nanoc/org_mode/version.rb000066400000000000000000000001351472033334600231620ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module OrgMode VERSION = '1.0.0' end end nanoc-4.13.3/nanoc-org-mode/nanoc-org-mode.gemspec000066400000000000000000000014551472033334600216710ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'lib/nanoc/org_mode/version' Gem::Specification.new do |s| s.name = 'nanoc-org-mode' s.version = Nanoc::OrgMode::VERSION s.homepage = 'https://nanoc.app/' s.summary = 'Org Mode filter for Nanoc' s.description = 'Provides an :org_mode 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 = '>= 3.1' s.add_dependency('nanoc-core', '~> 4.12') s.add_dependency('org-ruby', '~> 0.9') s.metadata = { 'rubygems_mfa_required' => 'true', 'source_code_uri' => "https://github.com/nanoc/nanoc/tree/#{s.name}-v#{s.version}/#{s.name}", } end nanoc-4.13.3/nanoc-org-mode/nanoc-org-mode.manifest000066400000000000000000000002201472033334600220410ustar00rootroot00000000000000NEWS.md README.md lib/nanoc-org-mode.rb lib/nanoc/org-mode.rb lib/nanoc/org_mode.rb lib/nanoc/org_mode/filter.rb lib/nanoc/org_mode/version.rb nanoc-4.13.3/nanoc-org-mode/spec/000077500000000000000000000000001472033334600164445ustar00rootroot00000000000000nanoc-4.13.3/nanoc-org-mode/spec/gem_spec.rb000066400000000000000000000007271472033334600205610ustar00rootroot00000000000000# frozen_string_literal: true describe 'nanoc-org-mode.gem', chdir: false, stdio: true do subject(:build_gem) do TTY::Command.new.run('gem build nanoc-org-mode.gemspec') end around do |ex| Dir['*.gem'].each { |f| FileUtils.rm(f) } ex.run Dir['*.gem'].each { |f| FileUtils.rm(f) } end it 'builds gem' do expect { build_gem } .to change { Dir['*.gem'] } .from([]) .to(include(match(/^nanoc-org-mode-.*\.gem$/))) end end nanoc-4.13.3/nanoc-org-mode/spec/manifest_spec.rb000066400000000000000000000002211472033334600216040ustar00rootroot00000000000000# frozen_string_literal: true describe 'manifest', chdir: false do example do expect('nanoc-org-mode').to have_a_valid_manifest end end nanoc-4.13.3/nanoc-org-mode/spec/nanoc/000077500000000000000000000000001472033334600175425ustar00rootroot00000000000000nanoc-4.13.3/nanoc-org-mode/spec/nanoc/org_mode/000077500000000000000000000000001472033334600213355ustar00rootroot00000000000000nanoc-4.13.3/nanoc-org-mode/spec/nanoc/org_mode/filter_spec.rb000066400000000000000000000011211472033334600241540ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::OrgMode::Filter, helper: true do it 'converts org-mode to HTML' do # Create item ctx.create_item('stuff', {}, '/foo.scss') ctx.create_rep(ctx.items['/foo.scss'], '/assets/foo.css') ctx.item = ctx.items['/foo.scss'] filter = described_class.new(ctx.assigns) res = filter.run(<<~SOURCE) * My novel First paragraph. ** A second-level heading Here is the second paragraph. ----- A third paragraph. SOURCE expect(res.strip).to match(%r{

First paragraph.

}m) end end nanoc-4.13.3/nanoc-org-mode/spec/spec_helper.rb000066400000000000000000000002461472033334600212640ustar00rootroot00000000000000# frozen_string_literal: true require_relative '../../common/spec/spec_helper_head' require 'nanoc/org_mode' require_relative '../../common/spec/spec_helper_foot' nanoc-4.13.3/nanoc-spec/000077500000000000000000000000001472033334600147335ustar00rootroot00000000000000nanoc-4.13.3/nanoc-spec/.rspec000066400000000000000000000000611472033334600160450ustar00rootroot00000000000000-r ./spec/spec_helper.rb --format Fuubar --color nanoc-4.13.3/nanoc-spec/LICENSE000066400000000000000000000020711472033334600157400ustar00rootroot00000000000000Copyright (c) 2014–… 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.13.3/nanoc-spec/NEWS.md000066400000000000000000000001771472033334600160360ustar00rootroot00000000000000# nanoc-spec news ## 0.0.2 (2021-01-01) Enhancements: - Added support for Ruby 3.x ## 0.0.1 (2019-11-16) Initial release. nanoc-4.13.3/nanoc-spec/README.md000066400000000000000000000004571472033334600162200ustar00rootroot00000000000000# nanoc-spec This provides `Nanoc::Spec`, which contains a bunch of functionality that facilitates writing tests for [Nanoc](https://nanoc.app). ## Installation Add `nanoc-spec` to the `nanoc` group of your Gemfile: ```ruby group :nanoc do gem 'nanoc-spec' end ``` ## Usage Don’t — for now. nanoc-4.13.3/nanoc-spec/Rakefile000066400000000000000000000004341472033334600164010ustar00rootroot00000000000000# 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.13.3/nanoc-spec/lib/000077500000000000000000000000001472033334600155015ustar00rootroot00000000000000nanoc-4.13.3/nanoc-spec/lib/nanoc-spec.rb000066400000000000000000000000641472033334600200540ustar00rootroot00000000000000# frozen_string_literal: true require 'nanoc/spec' nanoc-4.13.3/nanoc-spec/lib/nanoc/000077500000000000000000000000001472033334600165775ustar00rootroot00000000000000nanoc-4.13.3/nanoc-spec/lib/nanoc/spec.rb000066400000000000000000000153271472033334600200660ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Spec end end require 'nanoc/spec/version' module Nanoc module Spec module Helper def chdir(dir) here = Dir.getwd Dir.chdir(dir) yield ensure Dir.chdir(here) end def command?(cmd) TTY::Which.exist?(cmd) 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 attr_reader :erbout # @param [Module] mod The helper module to create a context for def initialize(mod) @mod = mod @erbout = +'' @action_sequence = {} @config = Nanoc::Core::Configuration.new(dir: Dir.getwd).with_defaults @reps = Nanoc::Core::ItemRepRepo.new @items = Nanoc::Core::ItemCollection.new(@config) @layouts = Nanoc::Core::LayoutCollection.new(@config) @compiled_content_store = Nanoc::Core::CompiledContentStore.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::Core::Identifier, String] identifier This item's identifier # # @return [Nanoc::Core::CompilationItemView] A view for the newly created item def create_item(content, attributes, identifier) item = Nanoc::Core::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::Core::Identifier, String] identifier This layout's identifier # # @return [Nanoc::Core::CompilationItemView] A view for the newly created layout def create_layout(content, attributes, identifier) layout = Nanoc::Core::Layout.new(content, attributes, identifier) @layouts = @layouts.add(layout) self end # Creates a new representation for the given item. # # @param [Nanoc::Core::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::Core::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::Core::Context) { include mod } klass.new(assigns) end def item=(item) @item = item&._unwrap end def item_rep=(item_rep) @item_rep = item_rep&._unwrap end # @return [Nanoc::Core::MutableConfigView] def config assigns[:config] end # @return [Nanoc::Core::CompilationItemView, nil] def item assigns[:item] end # @return [Nanoc::Core::BasicItemRepView, nil] def item_rep assigns[:item_rep] end # @return [Nanoc::Core::ItemCollectionWithRepsView] def items assigns[:items] end # @return [Nanoc::Core::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 compiled_content_store view_context.compiled_content_store end def assigns { config: Nanoc::Core::MutableConfigView.new(@config, view_context), item_rep: @item_rep ? Nanoc::Core::CompilationItemRepView.new(@item_rep, view_context) : nil, item: @item ? Nanoc::Core::CompilationItemView.new(@item, view_context) : nil, items: Nanoc::Core::ItemCollectionWithRepsView.new(@items, view_context), layouts: Nanoc::Core::LayoutCollectionView.new(@layouts, view_context), _erbout: @erbout, } end def dependency_store @_dependency_store ||= Nanoc::Core::DependencyStore.new(@items, @layouts, @config) end def dependency_tracker @_dependency_tracker ||= Nanoc::Core::DependencyTracker.new(dependency_store) end private def view_context compilation_context = Nanoc::Core::CompilationContext.new( action_provider: @action_provider, reps: @reps, site:, compiled_content_cache: Nanoc::Core::CompiledContentCache.new(config: @config), compiled_content_store: @compiled_content_store, ) Nanoc::Core::ViewContextForCompilation.new( reps: @reps, items: @items, dependency_tracker:, compilation_context:, compiled_content_store: @compiled_content_store, ) end def new_action_provider Class.new(Nanoc::Core::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::Core::SnapshotDef.new(:last, binary: false)] end end.new(self) end def new_compiler_for(site) Nanoc::Core::CompilerLoader.new.load(site, action_provider: @action_provider) end def site @_site ||= Nanoc::Core::Site.new( config: @config, code_snippets: [], data_source: Nanoc::Core::InMemoryDataSource.new(@items, @layouts), ) end end module HelperHelper def ctx @_ctx ||= HelperContext.new(described_class) end def helper @_helper ||= ctx.helper end end end end nanoc-4.13.3/nanoc-spec/lib/nanoc/spec/000077500000000000000000000000001472033334600175315ustar00rootroot00000000000000nanoc-4.13.3/nanoc-spec/lib/nanoc/spec/version.rb000066400000000000000000000001321472033334600215370ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Spec VERSION = '0.0.2' end end nanoc-4.13.3/nanoc-spec/nanoc-spec.gemspec000066400000000000000000000014611472033334600203300ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'lib/nanoc/spec/version' Gem::Specification.new do |s| s.name = 'nanoc-spec' s.version = Nanoc::Spec::VERSION s.homepage = 'https://nanoc.app/' s.summary = 'Testing functionality for Nanoc' s.description = 'Provides Nanoc::Spec, containing functionality for writing tests 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 = '>= 3.1' s.add_dependency('nanoc-core', '~> 4.11', '>= 4.11.13') s.metadata = { 'rubygems_mfa_required' => 'true', 'source_code_uri' => "https://github.com/nanoc/nanoc/tree/#{s.name}-v#{s.version}/#{s.name}", } end nanoc-4.13.3/nanoc-spec/nanoc-spec.manifest000066400000000000000000000001211472033334600205030ustar00rootroot00000000000000NEWS.md README.md lib/nanoc-spec.rb lib/nanoc/spec.rb lib/nanoc/spec/version.rb nanoc-4.13.3/nanoc-spec/spec/000077500000000000000000000000001472033334600156655ustar00rootroot00000000000000nanoc-4.13.3/nanoc-spec/spec/gem_spec.rb000066400000000000000000000006751472033334600200040ustar00rootroot00000000000000# frozen_string_literal: true describe 'nanoc-spec.gem', chdir: false, stdio: true do subject do TTY::Command.new.run('gem build nanoc-spec.gemspec') end around do |ex| Dir['*.gem'].each { |f| FileUtils.rm(f) } ex.run Dir['*.gem'].each { |f| FileUtils.rm(f) } end it 'builds gem' do expect { subject } .to change { Dir['*.gem'] } .from([]) .to(include(match(/^nanoc-spec-.*\.gem$/))) end end nanoc-4.13.3/nanoc-spec/spec/manifest_spec.rb000066400000000000000000000002151472033334600210300ustar00rootroot00000000000000# frozen_string_literal: true describe 'manifest', chdir: false do example do expect('nanoc-spec').to have_a_valid_manifest end end nanoc-4.13.3/nanoc-spec/spec/nanoc/000077500000000000000000000000001472033334600167635ustar00rootroot00000000000000nanoc-4.13.3/nanoc-spec/spec/nanoc/spec/000077500000000000000000000000001472033334600177155ustar00rootroot00000000000000nanoc-4.13.3/nanoc-spec/spec/nanoc/spec/helper_context_spec.rb000066400000000000000000000027021472033334600243000ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Spec::HelperContext do subject(:ctx) { described_class.new(helper) } let(:helper) do Module.new {} end 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 subject { ctx.create_rep(ctx.items['/foo.md'], '/foo.html') } before do ctx.create_item('foo', {}, '/foo.md') end 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.13.3/nanoc-spec/spec/spec_helper.rb000066400000000000000000000002421472033334600205010ustar00rootroot00000000000000# frozen_string_literal: true require_relative '../../common/spec/spec_helper_head' require 'nanoc/spec' require_relative '../../common/spec/spec_helper_foot' nanoc-4.13.3/nanoc-tilt/000077500000000000000000000000001472033334600147555ustar00rootroot00000000000000nanoc-4.13.3/nanoc-tilt/.rspec000066400000000000000000000000611472033334600160670ustar00rootroot00000000000000-r ./spec/spec_helper.rb --format Fuubar --color nanoc-4.13.3/nanoc-tilt/LICENSE000066400000000000000000000020711472033334600157620ustar00rootroot00000000000000Copyright (c) 2014–… 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.13.3/nanoc-tilt/NEWS.md000066400000000000000000000000731472033334600160530ustar00rootroot00000000000000# nanoc-tilt news ## 1.0.0 (2022-10-29) Initial release. nanoc-4.13.3/nanoc-tilt/README.md000066400000000000000000000010141472033334600162300ustar00rootroot00000000000000# nanoc-tilt This provides a filter that allows [Nanoc](https://nanoc.app) to process content via [Tilt](https://github.com/rtomayko/tilt). ## Installation Add `nanoc-tilt` to the `nanoc` group of your Gemfile: ```ruby group :nanoc do gem 'nanoc-tilt' end ``` ## Usage Call the `:tilt` filter. For example: ```ruby filter :tilt ``` Options passed to this filter will be passed on to the tilt filter. For example: ```ruby filter :tilt, args: { escape: true } ``` ```ruby filter :tilt, args: { escape: false } ``` nanoc-4.13.3/nanoc-tilt/Rakefile000066400000000000000000000004341472033334600164230ustar00rootroot00000000000000# 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.13.3/nanoc-tilt/lib/000077500000000000000000000000001472033334600155235ustar00rootroot00000000000000nanoc-4.13.3/nanoc-tilt/lib/nanoc-tilt.rb000066400000000000000000000000641472033334600201200ustar00rootroot00000000000000# frozen_string_literal: true require 'nanoc/tilt' nanoc-4.13.3/nanoc-tilt/lib/nanoc/000077500000000000000000000000001472033334600166215ustar00rootroot00000000000000nanoc-4.13.3/nanoc-tilt/lib/nanoc/tilt.rb000066400000000000000000000002161472033334600201210ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Tilt end end require 'tilt' require 'nanoc/tilt/version' require 'nanoc/tilt/filter' nanoc-4.13.3/nanoc-tilt/lib/nanoc/tilt/000077500000000000000000000000001472033334600175755ustar00rootroot00000000000000nanoc-4.13.3/nanoc-tilt/lib/nanoc/tilt/filter.rb000066400000000000000000000016271472033334600214150ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Tilt class Filter < Nanoc::Filter identifier :tilt # Runs the content through [Tilt](https://github.com/rtomayko/tilt). # Parameters passed as `:args` will be passed on to Tilt. # # @param [String] content The content to filter # # @return [String] The filtered content def run(content, params = {}) # Get options options = params.fetch(:args, {}) # Create context context = ::Nanoc::Core::Context.new(assigns) # Get result proc = assigns[:content] ? -> { assigns[:content] } : nil # Find Tilt template class ext = item.identifier.ext || item[:extension] template_class = ::Tilt[ext] # Render template = template_class.new(options) { content } template.render(context, assigns, &proc) end end end end nanoc-4.13.3/nanoc-tilt/lib/nanoc/tilt/version.rb000066400000000000000000000001321472033334600216030ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Tilt VERSION = '1.0.0' end end nanoc-4.13.3/nanoc-tilt/nanoc-tilt.gemspec000066400000000000000000000014431472033334600203740ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'lib/nanoc/tilt/version' Gem::Specification.new do |s| s.name = 'nanoc-tilt' s.version = Nanoc::Tilt::VERSION s.homepage = 'https://nanoc.app/' s.summary = 'Tilt filter for Nanoc' s.description = 'Provides a :tilt 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 = '>= 3.1' s.add_dependency('nanoc-core', '~> 4.11', '>= 4.11.14') s.add_dependency('tilt', '~> 2.0') s.metadata = { 'rubygems_mfa_required' => 'true', 'source_code_uri' => "https://github.com/nanoc/nanoc/tree/#{s.name}-v#{s.version}/#{s.name}", } end nanoc-4.13.3/nanoc-tilt/nanoc-tilt.manifest000066400000000000000000000001521472033334600205530ustar00rootroot00000000000000NEWS.md README.md lib/nanoc-tilt.rb lib/nanoc/tilt.rb lib/nanoc/tilt/filter.rb lib/nanoc/tilt/version.rb nanoc-4.13.3/nanoc-tilt/spec/000077500000000000000000000000001472033334600157075ustar00rootroot00000000000000nanoc-4.13.3/nanoc-tilt/spec/gem_spec.rb000066400000000000000000000007131472033334600200170ustar00rootroot00000000000000# frozen_string_literal: true describe 'nanoc-tilt.gem', chdir: false, stdio: true do subject(:build_gem) do TTY::Command.new.run('gem build nanoc-tilt.gemspec') end around do |ex| Dir['*.gem'].each { |f| FileUtils.rm(f) } ex.run Dir['*.gem'].each { |f| FileUtils.rm(f) } end it 'builds gem' do expect { build_gem } .to change { Dir['*.gem'] } .from([]) .to(include(match(/^nanoc-tilt-.*\.gem$/))) end end nanoc-4.13.3/nanoc-tilt/spec/manifest_spec.rb000066400000000000000000000002151472033334600210520ustar00rootroot00000000000000# frozen_string_literal: true describe 'manifest', chdir: false do example do expect('nanoc-tilt').to have_a_valid_manifest end end nanoc-4.13.3/nanoc-tilt/spec/nanoc/000077500000000000000000000000001472033334600170055ustar00rootroot00000000000000nanoc-4.13.3/nanoc-tilt/spec/nanoc/tilt/000077500000000000000000000000001472033334600177615ustar00rootroot00000000000000nanoc-4.13.3/nanoc-tilt/spec/nanoc/tilt/filter_spec.rb000066400000000000000000000013771472033334600226150ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Tilt::Filter do it 'supports .erb' do item = Nanoc::Core::Item.new('stuff', {}, '/foo.erb') item_rep = Nanoc::Core::ItemRep.new(item, :default) filter = described_class.new({ item:, item_rep: }) res = filter.run('<%= "a" * 3 %>') expect(res.strip).to eq('aaa') end it 'supports .erb with options' do item = Nanoc::Core::Item.new('stuff', {}, '/foo.erb') item_rep = Nanoc::Core::ItemRep.new(item, :default) filter = described_class.new({ item:, item_rep: }) res = filter.run('<%= "&" * 3 %>', args: { escape: false }) expect(res.strip).to eq('&&&') res = filter.run('<%= "&" * 3 %>', args: { escape: true }) expect(res.strip).to eq('&&&') end end nanoc-4.13.3/nanoc-tilt/spec/spec_helper.rb000066400000000000000000000002421472033334600205230ustar00rootroot00000000000000# frozen_string_literal: true require_relative '../../common/spec/spec_helper_head' require 'nanoc/tilt' require_relative '../../common/spec/spec_helper_foot' nanoc-4.13.3/nanoc/000077500000000000000000000000001472033334600140035ustar00rootroot00000000000000nanoc-4.13.3/nanoc/.rspec000066400000000000000000000000611472033334600151150ustar00rootroot00000000000000-r ./spec/spec_helper.rb --format Fuubar --color nanoc-4.13.3/nanoc/LICENSE000066400000000000000000000020711472033334600150100ustar00rootroot00000000000000Copyright (c) 2007–… 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.13.3/nanoc/NEWS.md000066400000000000000000002072741472033334600151150ustar00rootroot00000000000000# Nanoc news ## 4.13.3 (2024-11-23) Fixes: - Made the `:haml` filter work correctly in combination with the `rendering` helper (#1639, #1719) ## 4.13.2 (2024-11-12) Fixes: - Improved handling of Windows file paths (starting with a drive identifier, like C:) (#1717, #1718) ## 4.13.1 (2024-11-04) Enhancements: - Dropped explicit dependency on `psych`, simplifying usage on Windows (#1712, #1713) ## 4.13.0 (2024-06-19) Features: - Added `--focus` option to the `compile` and `live` commands (#1707) Fixes: - Made `delete` log lines use relative (not absolute) paths (#1706) Changes: - Dropped support for Ruby 3.0 (EOL) (#1704) ## 4.12.21 (2024-04-27) Fixes: - Fixed a rare “private method printf” error (#1694) Enhancements: - Sped up checksummer and made it more reliable and correct (#1693) - Removed Nokogiri warning message on JRuby (#1700) ## 4.12.20 (2024-03-15) Enhancements: - Add base64 as a gem dependency (#1681) - Specify pry as one of nanoc-cli's runtime dependencies (#1679) [Junichi Sato] - Add `org` extension (for Org Mode) to list of text file extensions (#1688) [Antar] ## 4.12.19 (2023-12-08) Fixes: - Restored support for YAML aliases (#1677) [zor-el] - Fixed inadvertent reference to `Nanoc::` namespace from within `nanoc-cli` gem (#1673) Enhancements: - Made the default Rules file use `#passthrough` (#1668) [Hendrik Jäger] - Added support for slim 5.x (#1675) - Added support for psych 5.x (#1674) - Added `source_code_uri` metadata to gemspecs (#1669) [Junichi Sato] ## 4.12.18 (2023-10-15) Fixes: - Fixed example kramdown rules generated by `create-site` to have the correct extension for non-index .md files (#1665) [Hendrik Jäger] - Fixed issue which could cause not all pages to be compiled after an interrupt (⌃C) (#1641, #1667) Enhancements: - Made `create-site` generate a `Gemfile` that includes `nanoc` (#1666) ## 4.12.17 (2023-09-30) Fixes: - Fix dart-sass compatibility (#1664) - Remove accidental dependency from `nanoc-core` onto `nanoc` ## 4.12.16 (2023-06-24) Fixes: - Made error handler print full error message if available (#1657) ## 4.12.15 (2023-02-06) Fixes: - Fixed an issue which made items always be considered outdated in sites with data sources with a custom `items_root` defined (#1640, #1644) ## 4.12.14 (2022-12-03) Fixes: - Fixed an issue which would cause `Invalid argument @ io_write` on larger sites (#1636) Enhancements: - Made Nanoc store temporary data (in `tmp/nanoc/`) in compressed form (#1637) ## 4.12.13 (2022-11-30) Fixes: - Prevented an issue where items could have two leading slashes in their identifier when using the `filesystem` data source’s `extra_files` option (#1622) - Fixed an issue which would make `nanoc compile --watch` or `nanoc live` crash if the layouts directory does not exist (#1625, #1626) - Fixed the usage of the `show-rules` command (it takes no arguments) (#1628) - Fixed an issue which could make Nanoc crash with `Stopwatch::AlreadyRunningError` (#1631, #1633) ## 4.12.12 (2022-11-15) Fixes: - Fixed a bug that could incorrectly raise compilation dependency cycle errors (#1620) ## 4.12.11 (2022-11-12) Enhancements: - Further improved compilation speed, especially for partial recompilation (#1609, #1612, #1613, #1615) ## 4.12.10 (2022-10-18) Enhancements: - Improved compilation speed, especially for partial recompilation (#1602, #1603, #1604) ## 4.12.9 (2022-10-08) Fixes: - Fixed compatibility with Haml 6.0 (#1595, #1593) ## 4.12.8 (2022-10-03) Fixes: - Fixed incorrect documentation regarding the IP addresses that the `live` and `view` commands listen on (#1584, #1585) Enhancements: - Wrapped URLs printed to stdout in angular brackets (like ``) so terminals can detect them properly (#1589) [Daniel Aleksandersen] - Made various performance improvements (#1596, #1597, #1598) ## 4.12.7 (2022-06-06) Fixes: - Dropped support for incompatible Psych versions (#1577, #1583) Enhancements: - Replaced UglifyJS (unmaintained) with Terser (#1578, #1579) - Dropped support for Ruby 2.6 (EOL) (#1580) ## 4.12.6 (2022-05-28) Fixes: - Fixed a crash in `nanc view` when a TUN device is present (#1567) [Sergio Durigan Junior] - Fixed a crash when content/ has non-writable binary files (#1576) - Fixed inconsistency in allowed YAML classes in metadata (#1574) [Daniel Aleksandersen] ## 4.12.5 (2022-01-22) Fixes: - Prefer YAML.safe_load over YAML.load (#1566) Enhancements: - Added `Time` to the list of permitted classes for YAML (#1565) [Hideaki Nagamine] ## 4.12.4 (2022-01-15) Fixes: - Fixed “can’t be called from trap context” exception (#1559, #1564) Enhancements: - Added `Date` to the list of permitted classes for YAML (#1560) [Damien Mathieu] - Made Nanoc log the filename when it is not absolute (#1557) [Johnny Willemsen] ## 4.12.3 (2021-10-09) Fixes: - Switched ERB to use keyword arguments (#1547, #1550) [Daniel Aleksandersen] - Removed use_helper re-definition warning (#1548, #1553) [Daniel Aleksandersen] - Fixed issue which could cause items not to be recalculated when removing items in the preprocess block (#1554, #1555) - Dropped Nokogumbo in favor of Nokogiri (#1546, #1549) [Daniel Aleksandersen] ## 4.12.2 (2021-05-28) Enhancements: - Add support for `video/@poster` to relativize_paths (#1544) [Masayuki Higashino] ## 4.12.1 (2021-04-05) Enhancements: - Made Nanoc expand paths containing `~` (home directory) in the filesystem data source configuration (!4, !5, !6) ## 4.12.0 (2021-02-20) Features: - Added `-W`/`--watch` flag to the `compile` command (requires the `nanoc-live` gem) Enhancements: - Made the compiler cache binary items ## 4.11.23 (2021-01-16) Fixes: - Fixed issue which could cause nanoc-live to keep running and use 100% CPU (#1538) ## 4.11.22 (2021-01-01) Changes: - Dropped support for Ruby 2.4 (EOL) Fixes: - Added support for Ruby 3.0 ## 4.11.21 (2020-12-18) Fixes: - Made clonefile usage optional You can add `clonefile` to your Gemfile to let Nanoc make use of your filesystem’s copy-on-write functionality (if available), which is not only faster but also more space-efficient. In the previous release, 4.11.20, clonefile was mandatory, even though it won’t properly compile on older operating systems. ## 4.11.20 (2020-12-18) Enhancements: - Made Nanoc use copy-on-write when available (preferred over hardlinking/copying) (#1509, #1511) [Daniel Aleksandersen] ## 4.11.19 (2020-10-16) Fixes: - Fixed an issue which could cause compiled pages to contain the content of another page (#1501, #1510) ## 4.11.18 (2020-06-17) Fixes: - Fixed a `Encoding::CompatibilityError` that would be thrown e.g. when using the `contracts` library (#1502, #1505) [Ethan Crawford] - Fixed an issue which could cause Nanoc not to compile all items on Ruby 2.7 (#1501, #1507) ## 4.11.17 (2020-06-12) Fixes: - Fixed issue which could cause Nanoc to throw a timeout exception (#1503) ## 4.11.16 (2020-04-26) Fixes: - Fixed uninitialized constant/undefined method bug with guard-nanoc (#1494, #1495) - Removed remaining more “Using the last argument …” warnings (#1498) Enhancements: - Ignore redirects to self (misued for cookies) (#1496) [Daniel Aleksandersen] ## 4.11.15 (2020-03-07) Enhancements: - Made `prune` print files to delete when using dry run (#1474, #1476) - Made Nanoc emit a warning when the `contracts` library is loaded, as it can cause severe slowdowns (#1483, #1489) - Made the default Rules handle `index.*` files (#1485, #1490) Fixes: - Fixed warnings on Ruby 2.7 - Made `@rep` (not just `@item_rep`) available in the Rendering helper (#1484, #1488) - Made the `:relativize_paths` filter relativize the `value` attribute, not the `content` one, of the `param` HTML element (#1491) - Made the `:relativize_paths` filter support relativizing paths in `srcset` attributes (#1475, #1492) - Fixed an issue that could cause items to be marked as outdated when they shouldn’t be (#1471, #1493). ## 4.11.14 (2019-11-10) Fixes: - Fixed an issue where removing and re-adding an item would not cause dependent items to be recompiled (#1463, #1464) ## 4.11.13 (2019-11-02) Fixes: - Fixed an issue which could cause Nanoc to freeze when generating the output diff on Windows ## 4.11.12 (2019-09-21) Fixes: - Fixed an issue with the output diff, where some hunks would not appear (#1457) ## 4.11.11 (2019-09-07) Fixes: - Made output diff more reliable (#1455, #1456) ## 4.11.10 (2019-08-31) Fixes: - Fixed problem with sporadic `DDMetrics::Stopwatch::AlreadyRunningError` (#1445) ## 4.11.9 (2019-08-17) Enhancements: - Allowed specifying excluded files across all checks (#1438) - Replace handlebars.rb with ruby-handlebars (#1443) - Improved dependency tracking for Sass imports (#1451, #1452) [Romain Goyet] ## 4.11.8 (2019-08-08) Fixes: - Fixed issue with `#find_all` yielding incorrect objects, causing a crash when attempting to access attributes (#1446, #1447) ## 4.11.7 (2019-07-28) Fixes: - Fixed handling of protocol-relative URLs (#1444) ## 4.11.6 (2019-07-06) Fixes: - Fixed crash in the `show-data` command when printing layout outdatedness (#1432) - Fixed crash in the `view` command when host/port is not specified Enhancements: - Added `#env_name` to `@config` (#1437) - Made the `external_links` checker detect more kinds of broken links (#1422) [Daniel Aleksandersen] - Made the `external_links` checker shuffle the list of links (#1429) [Daniel Aleksandersen] ## 4.11.5 (2019-04-30) Fixes: - Fixed an incompatibility introduced in Cri 2.15.4 (#1423, #1426) ## 4.11.4 (2019-04-28) Fixes: - Fixed an issue which could cause `#compiled_content` to sometimes return compiled content that erroneously includes the layout (#1412, #1421) ## 4.11.3 (2019-04-27) Enhancements: - Set a User-Agent for HTTP requests made by the `external_links` check (#1417) [Daniel Aleksandersen] - Added `atom:link[type]` attributes (#1415) [Daniel Aleksandersen] - Made the `external_links` check treat permanent redirects as errors (#1419) [Daniel Aleksandersen] ## 4.11.2 (2019-02-16) Fixes: - Fixed issue which prevented Sass (not SCSS) files from being imported (#1397, #1407) [Alexander Groß] ## 4.11.1 (2019-02-09) Fixes: - Fixed an issue which caused hardlinking to fail in certain situations (#1405, #1406) ## 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](https://nanoc.app/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](https://nanoc.app/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](https://github.com/macournoyer/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/guard/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::OrigCLI 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](https://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](https://github.com/ddfreyne/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](https://nanoc.app/). Especially the blog and the updated manual will be useful. New: - New `rdiscount` filter ([RDiscount site](https://github.com/davidfstr/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.13.3/nanoc/README.md000066400000000000000000000003151472033334600152610ustar00rootroot00000000000000![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](https://nanoc.app) for more information. nanoc-4.13.3/nanoc/Rakefile000066400000000000000000000006731472033334600154560ustar00rootroot00000000000000# 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.13.3/nanoc/bin/000077500000000000000000000000001472033334600145535ustar00rootroot00000000000000nanoc-4.13.3/nanoc/bin/nanoc000077500000000000000000000006041472033334600155770ustar00rootroot00000000000000#!/usr/bin/env ruby # frozen_string_literal: true require 'nanoc' begin require 'nanoc-rust' NanocRust.activate! rescue LoadError end require 'nanoc/orig_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.13.3/nanoc/lib/000077500000000000000000000000001472033334600145515ustar00rootroot00000000000000nanoc-4.13.3/nanoc/lib/nanoc.rb000066400000000000000000000020701472033334600161730ustar00rootroot00000000000000# frozen_string_literal: true # Load external dependencies require 'addressable' require 'colored' require 'ddplugin' require 'json' require 'parallel' module Nanoc end # Load general requirements require 'cgi' require 'digest' require 'English' require 'fileutils' require 'forwardable' require 'logger' require 'net/http' require 'net/https' require 'open3' require 'pathname' require 'set' require 'singleton' require 'stringio' require 'tempfile' require 'time' require 'timeout' require 'tmpdir' require 'tty-which' require 'uri' # Load extracted Nanoc dependencies require 'nanoc-core' require 'nanoc-cli' require 'nanoc-checking' require 'nanoc-deploying' # Re-export from Nanoc::Core Nanoc::Identifier = Nanoc::Core::Identifier Nanoc::DataSource = Nanoc::Core::DataSource Nanoc::Filter = Nanoc::Core::Filter Nanoc::Error = Nanoc::Core::Error Nanoc::Check = Nanoc::Checking::Check # Load Nanoc require 'nanoc/version' require 'nanoc/checking' require 'nanoc/extra' require 'nanoc/data_sources' require 'nanoc/filters' require 'nanoc/helpers' require 'nanoc/rule_dsl' nanoc-4.13.3/nanoc/lib/nanoc/000077500000000000000000000000001472033334600156475ustar00rootroot00000000000000nanoc-4.13.3/nanoc/lib/nanoc/data_sources.rb000066400000000000000000000001701472033334600206460ustar00rootroot00000000000000# frozen_string_literal: true # @api private module Nanoc::DataSources end require_relative 'data_sources/filesystem' nanoc-4.13.3/nanoc/lib/nanoc/data_sources/000077500000000000000000000000001472033334600203235ustar00rootroot00000000000000nanoc-4.13.3/nanoc/lib/nanoc/data_sources/filesystem.rb000066400000000000000000000336301472033334600230410ustar00rootroot00000000000000# 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 class AmbiguousMetadataAssociationError < ::Nanoc::Core::Error def initialize(content_filenames, meta_filename) super("There are multiple content files (#{content_filenames.sort.join(', ')}) that could match the file containing metadata (#{meta_filename}).") end end 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::Core::Item) end # See {Nanoc::DataSource#layouts}. def layouts load_objects(layouts_dir_name, Nanoc::Core::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::Core::ChangesStream.new do |cl| full_dir = dir ? File.expand_path(dir) : nil if full_dir && File.directory?(full_dir) listener = Listen.to(full_dir) do |_modifieds, _addeds, _deleteds| cl.unknown end listener.start cl.to_stop { listener.stop } end 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:, attributes:, content: nil, filename: nil, 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..]) if is_binary && klass == Nanoc::Core::Item meta = (meta_filename && Nanoc::Core::YamlLoader.load_file(meta_filename)) || {} ProtoDocument.new(is_binary: true, filename: content_filename, attributes: meta) elsif is_binary && klass == Nanoc::Core::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? dir_name = Tools.expand_and_relativize_path(dir_name) 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::DataSources::Filesystem::AmbiguousMetadataAssociationError.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:, meta_filename:, extension: content_filename ? ext_of(content_filename)[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..]) elsif meta_filename identifier_for_filename(meta_filename[dir_name.length..]) 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::Core::BinaryContent.new(full_content_filename) else Nanoc::Core::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 mtime = [meta_mtime, content_mtime].compact.max raise 'meta_mtime and content_mtime are both nil' unless mtime mtime end # e.g. # # { # 'content/foo' => [ 'yaml', ['html', 'md'] ], # 'content/bar' => [ 'yaml', [nil] ], # 'content/qux' => [ nil, ['html'] ] # } def all_split_files_in(dir_name) dir_name = Tools.expand_and_relativize_path(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 if (config[:identifier_type] != 'full') && ![0, 1].include?(content_filenames.size) raise Errors::MultipleContentFiles.new(meta_filenames, basename) 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..] || '' } : [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::Core::Identifier.new(filename) end regex = if /(^|\/)index(\.[^\/]+)?$/.match?(filename) allow_periods_in_identifiers? ? /\/?(index)?(\.[^\/.]+)?$/ : /\/?index(\.[^\/]+)?$/ else allow_periods_in_identifiers? ? /\.[^\/.]+$/ : /\.[^\/]+$/ end Nanoc::Core::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.13.3/nanoc/lib/nanoc/data_sources/filesystem/000077500000000000000000000000001472033334600225075ustar00rootroot00000000000000nanoc-4.13.3/nanoc/lib/nanoc/data_sources/filesystem/errors.rb000066400000000000000000000033241472033334600243520ustar00rootroot00000000000000# 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.13.3/nanoc/lib/nanoc/data_sources/filesystem/parser.rb000066400000000000000000000044231472033334600243330ustar00rootroot00000000000000# 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:, 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:, attributes: meta, attributes_data: pieces[2]) end # @return [Hash] def parse_metadata(data, filename) begin meta = Nanoc::Core::YamlLoader.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.13.3/nanoc/lib/nanoc/data_sources/filesystem/tools.rb000066400000000000000000000152621472033334600242020ustar00rootroot00000000000000# 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::Core::TrivialError # @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::Core::TrivialError # @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..] end end end when 'file' fn when 'directory' nil else raise UnsupportedFileTypeError.new(fn) end end.compact.flatten end # Expands the path (including resolving ~ for home directory) and returns # a relative path to the expanded path. def expand_and_relativize_path(path) from = Pathname.new(Dir.getwd) to = Pathname.new(File.expand_path(path)) to.relative_path_from(from).to_s 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 [Nanoc::Core::TrivialError] 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.dup.delete_prefix('/')}"] when Array extra_files.map { |extra_file| "#{dir_name}/#{extra_file.dup.delete_prefix('/')}" } else raise( Nanoc::Core::TrivialError, "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 = Nanoc::Core::Utils.expand_path_without_drive_identifier(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.13.3/nanoc/lib/nanoc/extra.rb000066400000000000000000000005321472033334600173170ustar00rootroot00000000000000# 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::Core::Pruner end require_relative 'extra/srcset_parser' require_relative 'extra/core_ext' nanoc-4.13.3/nanoc/lib/nanoc/extra/000077500000000000000000000000001472033334600167725ustar00rootroot00000000000000nanoc-4.13.3/nanoc/lib/nanoc/extra/core_ext.rb000066400000000000000000000001031472033334600211210ustar00rootroot00000000000000# frozen_string_literal: true require 'nanoc/extra/core_ext/time' nanoc-4.13.3/nanoc/lib/nanoc/extra/core_ext/000077500000000000000000000000001472033334600206025ustar00rootroot00000000000000nanoc-4.13.3/nanoc/lib/nanoc/extra/core_ext/time.rb000066400000000000000000000006431472033334600220700ustar00rootroot00000000000000# 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.13.3/nanoc/lib/nanoc/extra/srcset_parser.rb000066400000000000000000000032341472033334600222000ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc module Extra # @api private class SrcsetParser class InvalidFormat < ::Nanoc::Core::Error def initialize super('Invalid srcset format') end end REGEX_REST = / ( # Zero or one of the following: ( # A width descriptor, consisting of: \s+ # ASCII whitespace \d+ # a valid non-negative integer w # a U+0077 LATIN SMALL LETTER W character ) | ( # A pixel density descriptor, consisting of \s+ # ASCII whitespace (\d*\.)?\d+ # a valid floating-point number x # and a U+0078 LATIN SMALL LETTER X character. ) )* /x def initialize(value) @value = value end def call matches = [] loop do match = {} scan(/\s*/) match[:url] = scan(/[^, ]+/) match[:rest] = scan(REGEX_REST) scan(/\s*/) matches << match next if try_scan(/,/) break if eos? raise(InvalidFormat) end matches rescue InvalidFormat @value end private def scan(pattern) match = try_scan(pattern) match || raise(InvalidFormat) end def try_scan(pattern) scanner.scan(pattern) end def eos? scanner.eos? end def scanner @_scanner ||= StringScanner.new(@value) end end end end nanoc-4.13.3/nanoc/lib/nanoc/filters.rb000066400000000000000000000021271472033334600176460ustar00rootroot00000000000000# 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/terser' require_relative 'filters/xsl' require_relative 'filters/yui_compressor' nanoc-4.13.3/nanoc/lib/nanoc/filters/000077500000000000000000000000001472033334600173175ustar00rootroot00000000000000nanoc-4.13.3/nanoc/lib/nanoc/filters/asciidoc.rb000066400000000000000000000007471472033334600214320ustar00rootroot00000000000000# 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 = {}) TTY::Command.new(printer: :null).run('asciidoc -o - -', input: content).out end end end nanoc-4.13.3/nanoc/lib/nanoc/filters/asciidoctor.rb000066400000000000000000000003621472033334600221500ustar00rootroot00000000000000# 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.13.3/nanoc/lib/nanoc/filters/bluecloth.rb000066400000000000000000000007351472033334600216320ustar00rootroot00000000000000# 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.13.3/nanoc/lib/nanoc/filters/coffeescript.rb000066400000000000000000000007721472033334600223260ustar00rootroot00000000000000# 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.13.3/nanoc/lib/nanoc/filters/colorize_syntax.rb000066400000000000000000000105341472033334600231030ustar00rootroot00000000000000# 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 = {}) @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 'nokogiri' 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.13.3/nanoc/lib/nanoc/filters/colorize_syntax/000077500000000000000000000000001472033334600225535ustar00rootroot00000000000000nanoc-4.13.3/nanoc/lib/nanoc/filters/colorize_syntax/colorizers.rb000066400000000000000000000104011472033334600252670ustar00rootroot00000000000000# frozen_string_literal: true module Nanoc::Filters::ColorizeSyntax::Colorizers class Abstract extend DDPlugin::Plugin def process(_code, _language, params = {}) raise NotImplementedError end def postprocess(_language, _element); end private def check_availability(*cmd) TTY::Command.new(printer: :null).run!(*cmd).success? 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? TTY::Command.new(printer: :null).run(*cmd, input: code).out 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 TTY::Command.new(printer: :null).run(*cmd, input: code).out 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.13.3/nanoc/lib/nanoc/filters/erb.rb000066400000000000000000000016171472033334600204210ustar00rootroot00000000000000# 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 [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::Core::Context.new(assigns)

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

      # Get result
      trim_mode = params[:trim_mode]
      erb = ::ERB.new(content, trim_mode:)
      erb.filename = filename
      erb.result(assigns_binding)
    end
  end
end
nanoc-4.13.3/nanoc/lib/nanoc/filters/erubi.rb000066400000000000000000000016531472033334600207570ustar00rootroot00000000000000# 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::Core::Context.new(assigns)

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

      # Get result
      engine_opts = { bufvar: '_erbout', filename: }.merge(params)
      engine = ::Erubi::Engine.new(content, engine_opts)
      eval(engine.src, assigns_binding, filename)
    end
  end
end
nanoc-4.13.3/nanoc/lib/nanoc/filters/erubis.rb000066400000000000000000000015451472033334600211420ustar00rootroot00000000000000# 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::Core::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:).result(assigns_binding)
    end

    private

    def erubis_with_erbout
      @_erubis_with_erbout ||= Class.new(::Erubis::Eruby) { include ::Erubis::ErboutEnhancer }
    end
  end
end
nanoc-4.13.3/nanoc/lib/nanoc/filters/haml.rb000066400000000000000000000022631472033334600205700ustar00rootroot00000000000000# 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:,
        outvar: '_erbout',
        disable_capture: true,
      )

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

      # Get result
      proc = assigns[:content] ? -> { assigns[:content] } : nil

      # Render
      haml_major_version = ::Haml::VERSION[0]
      case haml_major_version
      when '5'
        ::Haml::Engine.new(content, options).render(context, assigns, &proc)
      when '6'
        template = Tilt::HamlTemplate.new(options) { content }
        template.render(context, assigns, &proc)
      else
        raise Nanoc::Core::TrivialError.new(
          "Cannot run Haml filter: unsupported Haml major version: #{haml_major_version}",
        )
      end
    end
  end
end
nanoc-4.13.3/nanoc/lib/nanoc/filters/handlebars.rb000066400000000000000000000016041472033334600217500ustar00rootroot00000000000000# frozen_string_literal: true

module Nanoc::Filters
  # @api private
  class Handlebars < Nanoc::Filter
    identifier :handlebars

    requires 'ruby-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::Handlebars.new
      template = handlebars.compile(content)
      template.call(context)
    end
  end
end
nanoc-4.13.3/nanoc/lib/nanoc/filters/kramdown.rb000066400000000000000000000017611472033334600214730ustar00rootroot00000000000000# frozen_string_literal: true

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

    requires 'kramdown'

    # Runs the content through [Kramdown](https://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.13.3/nanoc/lib/nanoc/filters/less.rb000066400000000000000000000045101472033334600206120ustar00rootroot00000000000000# 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:)
        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.13.3/nanoc/lib/nanoc/filters/markaby.rb000066400000000000000000000007661472033334600213030ustar00rootroot00000000000000# 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.13.3/nanoc/lib/nanoc/filters/maruku.rb000066400000000000000000000010041472033334600211430ustar00rootroot00000000000000# 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.13.3/nanoc/lib/nanoc/filters/mustache.rb000066400000000000000000000010371472033334600214560ustar00rootroot00000000000000# frozen_string_literal: true

module Nanoc::Filters
  # @api private
  class Mustache < Nanoc::Filter
    identifier :mustache

    requires 'mustache'

    # Runs the content through
    # [Mustache](https://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.13.3/nanoc/lib/nanoc/filters/pandoc.rb000066400000000000000000000023111472033334600211050ustar00rootroot00000000000000# 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.13.3/nanoc/lib/nanoc/filters/rainpress.rb000066400000000000000000000010031472033334600216440ustar00rootroot00000000000000# frozen_string_literal: true

module Nanoc::Filters
  # @api private
  class Rainpress < Nanoc::Filter
    identifier :rainpress

    requires 'rainpress'

    # Runs the content through [Rainpress](https://github.com/ddfreyne/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.13.3/nanoc/lib/nanoc/filters/rdiscount.rb000066400000000000000000000011061472033334600216540ustar00rootroot00000000000000# frozen_string_literal: true

module Nanoc::Filters
  # @api private
  class RDiscount < Nanoc::Filter
    identifier :rdiscount

    requires 'rdiscount'

    # Runs the content through [RDiscount](https://github.com/davidfstr/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.13.3/nanoc/lib/nanoc/filters/rdoc.rb000066400000000000000000000010761472033334600205770ustar00rootroot00000000000000# 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.13.3/nanoc/lib/nanoc/filters/redcarpet.rb000066400000000000000000000024571472033334600216250ustar00rootroot00000000000000# 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.13.3/nanoc/lib/nanoc/filters/redcloth.rb000066400000000000000000000031511472033334600214500ustar00rootroot00000000000000# 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.13.3/nanoc/lib/nanoc/filters/relativize_paths.rb000066400000000000000000000161301472033334600232220ustar00rootroot00000000000000# 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

    prepend MemoWise

    SELECTORS =
      [
        '*/@href',
        '*/@src',
        'object/@data',
        'video/@poster',
        'param[@name="movie"]/@value',
        'form/@action',
        'comment()',
        { path: '*/@srcset', type: :srcset },
      ].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 = {})
      # 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

    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
    memo_wise :excludes

    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 'nokogiri'
        ::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)
      handle_selectors(selectors, doc, namespaces, klass, type, params)

      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 handle_selectors(selectors, doc, namespaces, klass, type, params)
      selectors_by_type(selectors).each do |selector_type, sub_selectors|
        selector = sub_selectors.map { |sel| "descendant-or-self::#{sel.fetch(:path)}" }.join('|')

        doc.xpath(selector, namespaces).each do |node|
          if node.name == 'comment'
            nokogiri_process_comment(node, doc, sub_selectors, namespaces, klass, type, params)
          elsif path_is_relativizable?(node.content, params)
            node.content = relativize_node(node, selector_type)
          end
        end
      end
    end

    def selectors_by_type(selectors)
      typed_selectors =
        selectors.map do |s|
          if s.respond_to?(:keys)
            s
          else
            { path: s, type: :basic }
          end
        end

      typed_selectors.group_by { |s| s.fetch(:type) }
    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 = {})
      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.13.3/nanoc/lib/nanoc/filters/yui_compressor.rb000066400000000000000000000012201472033334600227210ustar00rootroot00000000000000# 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.13.3/nanoc/lib/nanoc/helpers.rb000066400000000000000000000007201472033334600176350ustar00rootroot00000000000000# 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.13.3/nanoc/lib/nanoc/helpers/000077500000000000000000000000001472033334600173115ustar00rootroot00000000000000nanoc-4.13.3/nanoc/lib/nanoc/helpers/blogging.rb000066400000000000000000000176021472033334600214340ustar00rootroot00000000000000# frozen_string_literal: true

module Nanoc::Helpers
  # @see https://nanoc.app/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::Core::TrivialError.new('Cannot build Atom feed: site configuration has no base_url')
        end
      end

      def validate_feed_item
        if title.nil?
          raise Nanoc::Core::TrivialError.new('Cannot build Atom feed: no title in params, item or site config')
        end
        if author_name.nil?
          raise Nanoc::Core::TrivialError.new('Cannot build Atom feed: no author_name in params, item or site config')
        end
        if author_uri.nil?
          raise Nanoc::Core::TrivialError.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::Core::TrivialError.new('Cannot build Atom feed: no articles')
        end
        if relevant_articles.any? { |a| a[:created_at].nil? }
          raise Nanoc::Core::TrivialError.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, type: 'text/html')
          xml.link(rel: 'self',      href: feed_url,             type: 'application/atom+xml')

          # 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, type: 'text/html')

          # 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::Core::TrivialError.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::Core::TrivialError.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.13.3/nanoc/lib/nanoc/helpers/breadcrumbs.rb000066400000000000000000000111011472033334600221210ustar00rootroot00000000000000# frozen_string_literal: true

module Nanoc::Helpers
  # @see https://nanoc.app/doc/reference/helpers/#breadcrumbs
  module Breadcrumbs
    class AmbiguousAncestorError < ::Nanoc::Core::Error
      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, &)
        acc = [obj]

        res = yield(obj)
        if res
          acc + unfold(res, &)
        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::Core::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::Core::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.13.3/nanoc/lib/nanoc/helpers/capturing.rb000066400000000000000000000122131472033334600216310ustar00rootroot00000000000000# frozen_string_literal: true

module Nanoc::Helpers
  # @see https://nanoc.app/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(&)
        existing_behavior = @params.fetch(:existing, :error)

        # Capture
        content_string = capture(&)

        # Get existing contents and prep for store
        compiled_content_store = @item._context.compiled_content_store
        rep = @item.reps[:default]._unwrap
        capture_name = :"__capture_#{@name}"
        old_content_string =
          case existing_behavior
          when :overwrite
            ''
          when :append
            c = compiled_content_store.get(rep, capture_name)
            c ? c.string : ''
          when :error
            contents = compiled_content_store.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::Core::TextualContent.new(old_content_string + content_string)
        compiled_content_store.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?
            # FIXME: is :last appropriate?
            Fiber.yield(Nanoc::Core::Errors::UnmetDependency.new(rep, :last))
            return run
          end
        end

        compiled_content_store = @config._context.compiled_content_store
        content = compiled_content_store.get(rep, :"__capture_#{@name}")
        content&.string
      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, &)
      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(&)
      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..]

      # 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.13.3/nanoc/lib/nanoc/helpers/child_parent.rb000066400000000000000000000011071472033334600222710ustar00rootroot00000000000000# frozen_string_literal: true

module Nanoc::Helpers
  # @see https://nanoc.app/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.13.3/nanoc/lib/nanoc/helpers/filtering.rb000066400000000000000000000021621472033334600216220ustar00rootroot00000000000000# frozen_string_literal: true

module Nanoc::Helpers
  # @see https://nanoc.app/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::Core::NotificationCenter.post(:filtering_started, @item_rep._unwrap, filter_name)
      filtered_data = filter.setup_and_run(data, arguments)
      Nanoc::Core::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.13.3/nanoc/lib/nanoc/helpers/html_escape.rb000066400000000000000000000022231472033334600221210ustar00rootroot00000000000000# frozen_string_literal: true

module Nanoc::Helpers
  # @see https://nanoc.app/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('"', '"')
          .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.13.3/nanoc/lib/nanoc/helpers/link_to.rb000066400000000000000000000053221472033334600212770ustar00rootroot00000000000000# frozen_string_literal: true

module Nanoc::Helpers
  # @see https://nanoc.app/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::Core::CompilationItemView, Nanoc::Core::BasicItemView, Nanoc::Core::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.13.3/nanoc/lib/nanoc/helpers/rendering.rb000066400000000000000000000041471472033334600216210ustar00rootroot00000000000000# frozen_string_literal: true

module Nanoc::Helpers
  # @see https://nanoc.app/doc/reference/helpers/#rendering
  module Rendering
    include Nanoc::Helpers::Capturing

    # @param [String] identifier
    # @param [Hash] other_assigns
    #
    # @raise [Nanoc::Core::Errors::UnknownLayout]
    # @raise [Nanoc::Core::Errors::CannotDetermineFilter]
    # @raise [Nanoc::Filter::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::Core::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,
        rep: @item_rep,
        items: @items,
        layout: layout_view,
        layouts: @layouts,
        config: @config,
      }.merge(other_assigns)

      # Get filter name
      filter_name_and_args = @config._context.compilation_context.filter_name_and_args_for_layout(layout)
      filter_name = filter_name_and_args.name
      filter_args = filter_name_and_args.args
      raise Nanoc::Core::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.13.3/nanoc/lib/nanoc/helpers/tagging.rb000066400000000000000000000016711472033334600212630ustar00rootroot00000000000000# frozen_string_literal: true

module Nanoc::Helpers
  # @see https://nanoc.app/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.13.3/nanoc/lib/nanoc/helpers/text.rb000066400000000000000000000013101472033334600206150ustar00rootroot00000000000000# frozen_string_literal: true

module Nanoc::Helpers
  # @see https://nanoc.app/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.13.3/nanoc/lib/nanoc/helpers/xml_sitemap.rb000066400000000000000000000030251472033334600221600ustar00rootroot00000000000000# frozen_string_literal: true

module Nanoc::Helpers
  # @see https://nanoc.app/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.13.3/nanoc/lib/nanoc/orig_cli.rb000066400000000000000000000005341472033334600177650ustar00rootroot00000000000000# frozen_string_literal: true

require 'nanoc-cli'

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

Nanoc::CLI.after_setup do
  root = File.dirname(__FILE__)
  commands_path = File.join(root, 'orig_cli', 'commands')
  Nanoc::CLI.add_command(Cri::Command.load_file(File.join(commands_path, 'show-rules.rb'), infer_name: true))
end
nanoc-4.13.3/nanoc/lib/nanoc/orig_cli/000077500000000000000000000000001472033334600174365ustar00rootroot00000000000000nanoc-4.13.3/nanoc/lib/nanoc/orig_cli/commands/000077500000000000000000000000001472033334600212375ustar00rootroot00000000000000nanoc-4.13.3/nanoc/lib/nanoc/orig_cli/commands/show-rules.rb000066400000000000000000000030761472033334600237020ustar00rootroot00000000000000# frozen_string_literal: true

usage 'show-rules'
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::OrigCLI::Commands
  class ShowRules < ::Nanoc::CLI::CommandRunner
    def run
      site = load_site

      res = Nanoc::Core::Compiler.new_for(site).run_until_reps_built
      reps = res.fetch(:reps)

      action_provider = Nanoc::Core::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:, reps:) }
      layouts.each { |e| explain_layout(e, 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::OrigCLI::Commands::ShowRules
nanoc-4.13.3/nanoc/lib/nanoc/rule_dsl.rb000066400000000000000000000016401472033334600200060ustar00rootroot00000000000000# frozen_string_literal: true

module Nanoc
  # @api private
  module RuleDSL
  end
end

require_relative 'rule_dsl/errors'
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::Core::Checksummer.define_behavior(
  Nanoc::RuleDSL::CompilationRuleContext,
  Nanoc::Core::Checksummer::RuleContextUpdateBehavior,
)

Nanoc::Core::Checksummer.define_behavior(
  Nanoc::RuleDSL::RulesCollection,
  Nanoc::Core::Checksummer::DataUpdateBehavior,
)
nanoc-4.13.3/nanoc/lib/nanoc/rule_dsl/000077500000000000000000000000001472033334600174605ustar00rootroot00000000000000nanoc-4.13.3/nanoc/lib/nanoc/rule_dsl/action_provider.rb000066400000000000000000000055561472033334600232070ustar00rootroot00000000000000# frozen_string_literal: true

module Nanoc::RuleDSL
  class ActionProvider < Nanoc::Core::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:, 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::RuleDSL::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::Core::InMemoryDataSource.new(ctx.items._unwrap, ctx.layouts._unwrap, site.data_source)
    end

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

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

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

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

    # @api private
    def new_postprocessor_context(site, view_context)
      Nanoc::Core::Context.new(
        config: Nanoc::Core::ConfigView.new(site.config, view_context),
        items: Nanoc::Core::PostCompileItemCollectionView.new(site.items, view_context),
      )
    end
  end
end
nanoc-4.13.3/nanoc/lib/nanoc/rule_dsl/action_recorder.rb000066400000000000000000000046171472033334600231570ustar00rootroot00000000000000# frozen_string_literal: true

module Nanoc
  module RuleDSL
    class ActionRecorder
      include Nanoc::Core::ContractsSupport

      contract Nanoc::Core::ItemRep => C::Any
      def initialize(item_rep)
        @item_rep = item_rep

        @action_sequence_builder = Nanoc::Core::ActionSequenceBuilder.new

        @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, @item_rep)
        end

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

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

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

        @action_sequence_builder.add_snapshot(snapshot_name, path, @item_rep)
        case snapshot_name
        when :last
          @last_snapshot = true
        when :pre
          @pre_snapshot = true
        end
        nil
      end

      contract C::None => Nanoc::Core::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.13.3/nanoc/lib/nanoc/rule_dsl/action_sequence_calculator.rb000066400000000000000000000113411472033334600253630ustar00rootroot00000000000000# frozen_string_literal: true

module Nanoc::RuleDSL
  class ActionSequenceCalculator
    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::Core::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::Core::ActionSequence]
    def [](obj)
      case obj
      when Nanoc::Core::ItemRep
        new_action_sequence_for_rep(obj)
      when Nanoc::Core::Layout
        new_action_sequence_for_layout(obj)
      else
        raise UnsupportedObjectTypeException.new(obj)
      end
    end

    def new_action_sequence_for_rep(rep)
      view_context =
        Nanoc::Core::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:, site: @site, view_context:)
      recorder.snapshot(:post) if recorder.any_layouts?
      recorder.snapshot(:last)
      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:,
      )
    end

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

      unless res
        raise NoActionSequenceForLayoutException.new(layout)
      end

      Nanoc::Core::ActionSequenceBuilder.build 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::Core::ProcessingActions::Snapshot) }
          actions[-1] = actions.last.update(snapshot_names: action.snapshot_names, paths: action.paths)
        else
          actions << action
        end
      end
      Nanoc::Core::ActionSequence.new(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::Core::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:)
      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::Core::ViewContextForPreCompilation.new(items: @site.items)

      basic_path =
        routing_rule.apply_to(
          rep,
          site: @site,
          view_context:,
        )

      if basic_path && !basic_path.start_with?('/')
        raise PathWithoutInitialSlashError.new(rep, basic_path)
      end

      basic_path
    end
  end
end
nanoc-4.13.3/nanoc/lib/nanoc/rule_dsl/compilation_rule.rb000066400000000000000000000011501472033334600233470ustar00rootroot00000000000000# frozen_string_literal: true

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

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

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

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

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

      super(rep:, site:, view_context:)
    end

    # Filters the current representation (calls {Nanoc::Core::ItemRep#filter} with
    # the given arguments on the rep).
    #
    # @see Nanoc::Core::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::Core::ItemRep#layout} with
    # the given arguments on the rep).
    #
    # @see Nanoc::Core::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::Core::ItemRep#snapshot} with the given arguments on the rep.
    #
    # @see Nanoc::Core::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::Core::UNDEFINED)
      @_recorder.snapshot(snapshot_name, 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}"
      @_write_snapshot_counter += 1

      case arg
      when String, Nanoc::Core::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:)
        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.13.3/nanoc/lib/nanoc/rule_dsl/compiler_dsl.rb000066400000000000000000000254341472033334600224710ustar00rootroot00000000000000# frozen_string_literal: true

module Nanoc::RuleDSL
  class CompilerDSL < Nanoc::Core::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: })
    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::Core::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::RuleDSL::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::Core::Pattern.from(arg)
      when 'legacy'
        Nanoc::Core::Pattern.from(identifier_to_regex(arg))
      else
        raise(
          Nanoc::Core::TrivialError,
          "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.13.3/nanoc/lib/nanoc/rule_dsl/errors.rb000066400000000000000000000014651472033334600213270ustar00rootroot00000000000000# frozen_string_literal: true

module Nanoc
  module RuleDSL
    module Errors
      # Error that is raised when no rules file can be found in the current
      # working directory.
      class NoRulesFileFound < ::Nanoc::Core::Error
        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 < ::Nanoc::Core::Error
        # @param [Nanoc::Core::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
    end
  end
end
nanoc-4.13.3/nanoc/lib/nanoc/rule_dsl/routing_rule.rb000066400000000000000000000015171472033334600225270ustar00rootroot00000000000000# frozen_string_literal: true

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

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

    contract Nanoc::Core::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::Core::ItemRep, C::KeywordArgs[
      site: Nanoc::Core::Site,
      view_context: Nanoc::Core::ViewContextForPreCompilation,
    ] => C::Any
    def apply_to(rep, site:, view_context:)
      context = Nanoc::RuleDSL::RoutingRuleContext.new(
        rep:, site:, view_context:,
      )

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

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

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

    contract C::None => Symbol
    attr_reader :rep_name

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

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

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

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

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

    contract C::KeywordArgs[
      rep: Nanoc::Core::ItemRep,
      site: Nanoc::Core::Site,
      view_context: Nanoc::Core::ViewContextForPreCompilation,
    ] => C::Any
    def initialize(rep:, site:, view_context:)
      super({
        item: Nanoc::Core::BasicItemView.new(rep.item, view_context),
        rep: Nanoc::Core::BasicItemRepView.new(rep, view_context),
        item_rep: Nanoc::Core::BasicItemRepView.new(rep, view_context),
        items: Nanoc::Core::ItemCollectionWithoutRepsView.new(site.items, view_context),
        layouts: Nanoc::Core::LayoutCollectionView.new(site.layouts, view_context),
        config: Nanoc::Core::ConfigView.new(site.config, view_context),
      })
    end
  end
end
nanoc-4.13.3/nanoc/lib/nanoc/rule_dsl/rules_collection.rb000066400000000000000000000077261472033334600233660ustar00rootroot00000000000000# 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::Core::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::Core::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::Core::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::Core::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.13.3/nanoc/lib/nanoc/rule_dsl/rules_loader.rb000066400000000000000000000015071472033334600224700ustar00rootroot00000000000000# 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::RuleDSL::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.13.3/nanoc/lib/nanoc/version.rb000066400000000000000000000001441472033334600176600ustar00rootroot00000000000000# frozen_string_literal: true

module Nanoc
  # The current Nanoc version.
  VERSION = '4.13.3'
end
nanoc-4.13.3/nanoc/nanoc.gemspec000066400000000000000000000026501472033334600164510ustar00rootroot00000000000000# frozen_string_literal: true

require_relative 'lib/nanoc/version'

Gem::Specification.new do |s|
  s.name        = 'nanoc'
  s.version     = Nanoc::VERSION
  s.homepage    = 'https://nanoc.app/'
  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']
  s.executables        = ['nanoc']
  s.require_paths      = ['lib']

  s.required_ruby_version = '>= 3.1'

  s.add_dependency('addressable', '~> 2.5')
  s.add_dependency('colored', '~> 1.2')
  s.add_dependency('nanoc-checking', '~> 1.0', '>= 1.0.2')
  s.add_dependency('nanoc-cli', "= #{Nanoc::VERSION}")
  s.add_dependency('nanoc-core', "= #{Nanoc::VERSION}")
  s.add_dependency('nanoc-deploying', '~> 1.0')
  s.add_dependency('parallel', '~> 1.12')
  s.add_dependency('tty-command', '~> 0.8')
  s.add_dependency('tty-which', '~> 0.4')
  s.metadata = {
    'rubygems_mfa_required' => 'true',
    'source_code_uri' => "https://github.com/nanoc/nanoc/tree/#{s.version}/#{s.name}",
  }
end
nanoc-4.13.3/nanoc/nanoc.manifest000066400000000000000000000044171472033334600166370ustar00rootroot00000000000000LICENSE
NEWS.md
README.md

bin/nanoc

lib/nanoc.rb
lib/nanoc/orig_cli.rb
lib/nanoc/orig_cli/commands/show-rules.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/extra.rb
lib/nanoc/extra/core_ext.rb
lib/nanoc/extra/core_ext/time.rb
lib/nanoc/extra/srcset_parser.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/terser.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/errors.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/version.rb
nanoc-4.13.3/nanoc/spec/000077500000000000000000000000001472033334600147355ustar00rootroot00000000000000nanoc-4.13.3/nanoc/spec/contributors_spec.rb000066400000000000000000000012071472033334600210310ustar00rootroot00000000000000# 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 'includes 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 'is sorted' do
    expect(contributors_in_readme).to be_humanly_sorted
  end
end
nanoc-4.13.3/nanoc/spec/gem_spec.rb000066400000000000000000000006561472033334600170530ustar00rootroot00000000000000# frozen_string_literal: true

describe 'nanoc.gem', chdir: false, stdio: true do
  subject do
    TTY::Command.new.run('gem build nanoc.gemspec')
  end

  around do |ex|
    Dir['*.gem'].each { |f| FileUtils.rm(f) }
    ex.run
    Dir['*.gem'].each { |f| FileUtils.rm(f) }
  end

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

describe 'manifest', chdir: false do
  example do
    expect('nanoc').to have_a_valid_manifest
  end
end
nanoc-4.13.3/nanoc/spec/meta_spec.rb000066400000000000000000000010101472033334600172120ustar00rootroot00000000000000# frozen_string_literal: true

describe 'meta', chdir: false do
  it 'has the same license for all projects' do
    Dir.chdir('..') do
      root_license = File.read('LICENSE').sub(/20\d\d/, '20xx')

      projects = %w[nanoc guard-nanoc] + Dir['nanoc-*']
      projects.each do |project|
        project_license = File.read(File.join(project, 'LICENSE')).sub(/20\d\d/, '20xx')
        expect(project_license).to eq(root_license), "expected license for #{project} to be same as root license"
      end
    end
  end
end
nanoc-4.13.3/nanoc/spec/nanoc/000077500000000000000000000000001472033334600160335ustar00rootroot00000000000000nanoc-4.13.3/nanoc/spec/nanoc/core/000077500000000000000000000000001472033334600167635ustar00rootroot00000000000000nanoc-4.13.3/nanoc/spec/nanoc/core/checksummer_spec.rb000066400000000000000000000056231472033334600226360ustar00rootroot00000000000000# frozen_string_literal: true

# NOTE: this spec checks all the bits that aren’t in Core.

describe Nanoc::Core::Checksummer do
  subject { described_class.calc(obj, Nanoc::Core::Checksummer::VerboseDigest) }

  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#0>') }
  end

  context 'Nanoc::RuleDSL::CompilationRuleContext' do
    let(:obj) { Nanoc::RuleDSL::CompilationRuleContext.new(rep:, site:, recorder:, view_context:) }

    let(:rep) { Nanoc::Core::ItemRep.new(item, :pdf) }
    let(:item) { Nanoc::Core::Item.new('stuff', {}, '/stuff.md') }

    let(:site) do
      Nanoc::Core::Site.new(
        config:,
        code_snippets:,
        data_source: Nanoc::Core::InMemoryDataSource.new(items, layouts),
      )
    end

    let(:config) { Nanoc::Core::Configuration.new(dir: Dir.getwd, hash: { 'foo' => 'bar' }) }
    let(:code_snippets) { [Nanoc::Core::CodeSnippet.new('asdf', '/bob.rb')] }
    let(:items) { Nanoc::Core::ItemCollection.new(config, [item]) }
    let(:layouts) { Nanoc::Core::LayoutCollection.new(config, [Nanoc::Core::Layout.new('asdf', {}, '/foo.md')]) }

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

    let(:expected_item_checksum) { 'Nanoc::Core::Item#2>,attributes=Hash#5<>,identifier=Nanoc::Core::Identifier#6>>' }
    let(:expected_item_rep_checksum) { 'Nanoc::Core::ItemRep#9>' }
    let(:expected_layout_checksum) { 'Nanoc::Core::Layout#15>,attributes=@5,identifier=Nanoc::Core::Identifier#18>>' }
    let(:expected_config_checksum) { 'Nanoc::Core::Configuration#21=String#23,>' }

    let(:expected_checksum) do
      [
        'Nanoc::RuleDSL::CompilationRuleContext#0<',
        'item=',
        'Nanoc::Core::BasicItemView#1<' + expected_item_checksum + '>',
        ',rep=',
        'Nanoc::Core::BasicItemRepView#8<' + expected_item_rep_checksum + '>',
        ',items=',
        'Nanoc::Core::ItemCollectionWithoutRepsView#11>',
        ',layouts=',
        'Nanoc::Core::LayoutCollectionView#13>',
        ',config=',
        'Nanoc::Core::ConfigView#20<' + expected_config_checksum + '>',
        '>',
      ].join('')
    end

    it { is_expected.to eql(expected_checksum) }
  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#0\z}) }
  end
end
nanoc-4.13.3/nanoc/spec/nanoc/data_sources/000077500000000000000000000000001472033334600205075ustar00rootroot00000000000000nanoc-4.13.3/nanoc/spec/nanoc/data_sources/filesystem/000077500000000000000000000000001472033334600226735ustar00rootroot00000000000000nanoc-4.13.3/nanoc/spec/nanoc/data_sources/filesystem/parser_spec.rb000066400000000000000000000246471472033334600255430ustar00rootroot00000000000000# frozen_string_literal: true

describe Nanoc::DataSources::Filesystem::Parser do
  subject(:parser) { described_class.new(config:) }

  let(:config) do
    Nanoc::Core::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) { 'test_meta.txt' }

      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) { 'test_content.txt' }

      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 'date attribute' do
          let(:content) { "---\ncreated_at: 2022-01-01\n---" }

          it 'has attributes' do
            if Psych::VERSION <= '4.0.6' && Timecop::VERSION <= '0.9.5'
              skip <<~MESSAGE.lines.map(&:chomp).join(' ')
                Psych 4.0.5 introduces an incompatibility with Timecop 0.9.5,
                which causes dates not to be parsed correctly (see
                https://github.com/travisjeffery/timecop/issues/390).
              MESSAGE
            end

            expect(subject.attributes).to eq('created_at' => Date.new(2022, 1, 1))
          end
        end

        context 'time attribute' do
          let(:content) { "---\ncreated_at: 2022-01-01T14:05:00+04:00\n---" }

          it 'has attributes' do
            expect(subject.attributes).to eq('created_at' => Time.new(2022, 1, 1, 14, 5, 0, '+04:00'))
          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) { 'test_content.txt' }
      let(:meta_filename) { 'test_meta.txt' }

      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.13.3/nanoc/spec/nanoc/data_sources/filesystem/tools_spec.rb000066400000000000000000000052101472033334600253700ustar00rootroot00000000000000# frozen_string_literal: true

describe Nanoc::DataSources::Filesystem::Tools do
  describe '.read_file' do
    subject { described_class.read_file(filename, 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.13.3/nanoc/spec/nanoc/data_sources/filesystem_spec.rb000066400000000000000000000265541472033334600242460ustar00rootroot00000000000000# frozen_string_literal: true

describe Nanoc::DataSources::Filesystem, site: true do
  let(:data_source) { described_class.new(site.config, nil, nil, params) }
  let(:now) { Time.local(2008, 1, 2, 14, 5, 0) }
  let(:params) { {} }
  let(:site) { Nanoc::Core::SiteLoader.new.new_from_cwd }

  describe '#load_objects' do
    subject { data_source.send(:load_objects, dir_with_objects, klass) }

    let(:dir_with_objects) { 'foo' }
    let(:klass) { raise 'override me' }

    context 'items' do
      let(:klass) { Nanoc::Core::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::Core::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::Core::BinaryContent)
          expect(subject[0].attributes).to eq(expected_attributes)
          expect(subject[0].identifier).to eq(Nanoc::Core::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::DataSources::Filesystem::AmbiguousMetadataAssociationError,
              'There are multiple content files (foo/a.md, foo/a.txt) 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::Core::TextualContent)
          expect(items[0].identifier).to eq(Nanoc::Core::Identifier.new('/a.md', type: :full))
          expect(items[0].attributes[:title]).to eq('Aaah')

          expect(items[1].content).to be_a(Nanoc::Core::TextualContent)
          expect(items[1].identifier).to eq(Nanoc::Core::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::DataSources::Filesystem::AmbiguousMetadataAssociationError,
              'There are multiple content files (foo/a.md, foo/a.txt) 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::Core::TextualContent)
          expect(items[0].identifier).to eq(Nanoc::Core::Identifier.new('/a.md', type: :full))
          expect(items[0].attributes[:title]).to eq('Ho')

          expect(items[1].content).to be_a(Nanoc::Core::TextualContent)
          expect(items[1].identifier).to eq(Nanoc::Core::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::Core::TextualContent)
          expect(items[0].identifier).to eq(Nanoc::Core::Identifier.new('/a.md', type: :full))
          expect(items[0].attributes[:title]).to be_nil

          expect(items[1].content).to be_a(Nanoc::Core::TextualContent)
          expect(items[1].identifier).to eq(Nanoc::Core::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::Core::TextualContent)
          expect(subject[0].content.string).to eq("---\ntitle: Hi\n---\n\nhi")
          expect(subject[0].identifier).to eq(Nanoc::Core::Identifier.new('/a.txt', type: :full))
          expect(subject[0].attributes[:title]).to be_nil
          expect(subject[0].attributes[:author]).to eq('Denis')
        end
      end

      context 'content file outside of current working directory' do
        let(:params) { { identifier_type: 'full' } }

        let(:dir_with_objects) { Dir.mktmpdir }

        before do
          File.write(File.join(dir_with_objects, 'foo.txt'), "---\ntitle: I am foo\n---\n\nHi!")
        end

        it 'assigns metadata to the file that doesn’t have any yet' do
          expect(subject.size).to eq(1)

          items = subject.sort_by { |i| i.identifier.to_s }

          expect(items[0].content).to be_a(Nanoc::Core::TextualContent)
          expect(items[0].identifier).to eq(Nanoc::Core::Identifier.new('/foo.txt', type: :full))
          expect(items[0].attributes[:title]).to eq('I am foo')
          expect(items[0].attributes[:content_filename]).to start_with('../')
          expect(items[0].attributes[:content_filename]).to end_with('/foo.txt')
        end
      end
    end
  end

  describe '#changes_for_dir' do
    subject { data_source.changes_for_dir(temp_dir_base_unexpanded) }

    let(:temp_dir_base_unexpanded) { '~/tmp_nanoc_Mj2glnoP' }
    let(:temp_dir_base_expanded) { File.expand_path(temp_dir_base_unexpanded) }
    let(:temp_dir) { Dir.mktmpdir(nil, temp_dir_base_expanded) }

    before do
      if Nanoc::Core.on_windows?
        skip 'nanoc-live is not currently supported on Windows'
      end
    end

    context 'when directory exists' do
      before do
        FileUtils.mkdir_p(temp_dir_base_expanded)
      end

      after do
        FileUtils.rm_rf(temp_dir_base_expanded)
      end

      it 'returns a stream' do
        expect(subject).to be_a(Nanoc::Core::ChangesStream)
      end

      it 'contains one element after changing' do
        FileUtils.mkdir_p(File.join(temp_dir, 'content'))

        enum = SlowEnumeratorTools.buffer(subject.to_enum, 1)
        q = SizedQueue.new(1)
        Thread.new { q << enum.take(1).first }

        # Try until we find a change
        ok = false
        20.times do |i|
          File.write(File.join(temp_dir, 'content/wat.md'), "stuff #{i}")
          begin
            expect(q.pop(true)).to eq(:unknown)
            ok = true
            break
          rescue ThreadError
            sleep 0.1
          end
        end
        expect(ok).to be(true)

        subject.stop
      end
    end

    context 'when directory does not exist' do
      it 'returns a stream' do
        expect(subject).to be_a(Nanoc::Core::ChangesStream)
      end

      it 'does not raise' do
        subject

        t = Thread.new do
          Thread.current.abort_on_exception = true
          Thread.current.report_on_exception = false

          begin
            Timeout.timeout(0.1) { subject.to_enum.take(1).first }
            Thread.current[:outcome] = :not_timed_out
          rescue Timeout::Error
            Thread.current[:outcome] = :timed_out
          end
        end

        t.join
        expect(t[:outcome]).to eq(:timed_out)

        subject.stop
      end
    end
  end
end
nanoc-4.13.3/nanoc/spec/nanoc/extra/000077500000000000000000000000001472033334600171565ustar00rootroot00000000000000nanoc-4.13.3/nanoc/spec/nanoc/extra/srcset_parser_spec.rb000066400000000000000000000024251472033334600233770ustar00rootroot00000000000000# frozen_string_literal: true

describe Nanoc::Extra::SrcsetParser do
  subject(:parsed) { described_class.new(value).call }

  let(:value) { 'http://example.com/a.jpg' }

  test_cases = {
    'http://example.com/a.jpg 2w 3x' =>
      [{ url: 'http://example.com/a.jpg', rest: ' 2w 3x' }],

    'http://example.com/a.jpg 2w' =>
      [{ url: 'http://example.com/a.jpg', rest: ' 2w' }],

    'http://example.com/a.jpg 2w 2w 2w 2w 2w' =>
      [{ url: 'http://example.com/a.jpg', rest: ' 2w 2w 2w 2w 2w' }],

    'http://example.com/a.jpg 123456x' =>
      [{ url: 'http://example.com/a.jpg', rest: ' 123456x' }],

    '   http://example.com/a.jpg 2w 3x   ' =>
      [{ url: 'http://example.com/a.jpg', rest: ' 2w 3x' }],

    '   http://example.com/a.jpg 2w 3x  , http://example.com/b.jpg  4x 4x 5w  ' =>
      [{ url: 'http://example.com/a.jpg', rest: ' 2w 3x' }, { url: 'http://example.com/b.jpg', rest: '  4x 4x 5w' }],
  }

  test_cases.each do |input, expected_output|
    context "with #{input}" do
      let(:value) { input }

      it 'parses properly' do
        expect(parsed).to eq(expected_output)
      end
    end
  end

  context 'with bad input' do
    let(:value) { ' http://example.com/a.jpg bad' }

    it 'falls back to input' do
      expect(parsed).to eq(value)
    end
  end
end
nanoc-4.13.3/nanoc/spec/nanoc/filters/000077500000000000000000000000001472033334600175035ustar00rootroot00000000000000nanoc-4.13.3/nanoc/spec/nanoc/filters/asciidoc_spec.rb000066400000000000000000000004431472033334600226210ustar00rootroot00000000000000# frozen_string_literal: true

describe Nanoc::Filters::AsciiDoc do
  subject { described_class.new }

  before do
    skip_unless_have_command 'asciidoc'
  end

  example do
    expect(subject.setup_and_run('== Blah blah'))
      .to match(%r{

Blah blah

}) end end nanoc-4.13.3/nanoc/spec/nanoc/filters/asciidoctor_spec.rb000066400000000000000000000004441472033334600233470ustar00rootroot00000000000000# 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.13.3/nanoc/spec/nanoc/filters/colorize_syntax/000077500000000000000000000000001472033334600227375ustar00rootroot00000000000000nanoc-4.13.3/nanoc/spec/nanoc/filters/colorize_syntax/rouge_spec.rb000066400000000000000000000110071472033334600254160ustar00rootroot00000000000000# 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) { described_class.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:) } it { is_expected.to eql output } context 'with pygments wrapper' do let(:wrap) { true } let(:params) { super().merge(wrap:) } it { is_expected.to eql output } context 'with css_class' do let(:css_class) { 'nanoc' } let(:params) { super().merge(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:) } 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:) } 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.13.3/nanoc/spec/nanoc/filters/erb_spec.rb000066400000000000000000000072151472033334600216170ustar00rootroot00000000000000# 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 subject do filter.setup_and_run('<% raise "boom %>') end let(:filter) { described_class.new(layout:) } let(:layout) { Nanoc::Core::Layout.new('asdf', {}, '/default.erb') } 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 subject do filter.setup_and_run('% res[:success] = true', params) end let(:filter) { described_class.new } let(:res) { { success: false } } context 'trim mode unchanged' do let(:params) do { locals: { 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: }, } end it 'honors trim mode' do expect { subject }.to change { res[:success] }.from(false).to(true) end end end end nanoc-4.13.3/nanoc/spec/nanoc/filters/less_spec.rb000066400000000000000000000070371472033334600220170ustar00rootroot00000000000000# 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') 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.13.3/nanoc/spec/nanoc/filters/relativize_paths_spec.rb000066400000000000000000000153331472033334600244240ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Filters::RelativizePaths do subject(:filter) { described_class.new(assigns) } let(:assigns) do { item_rep: } end let(:item) do Nanoc::Core::Item.new('contentz', {}, '/sub/page.html') end let(:item_rep) do Nanoc::Core::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 context 'img src' do let(:content) { '' } it { is_expected.to eq('') } end context 'form action' do let(:content) { '
' } it { is_expected.to eq('
') } end context 'object data' do let(:content) { '' } it { is_expected.to eq('') } end context 'param value' do let(:content) { '' } it { is_expected.to eq('') } end context 'img srcset' do let(:content) { '' } it { is_expected.to eq('') } end context 'video' do let(:content) { '' } it { is_expected.to eq('') } 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.13.3/nanoc/spec/nanoc/filters/sass_spec.rb000066400000000000000000000356721472033334600220300ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Filters::SassCommon do context 'with item, items, config context' do subject(:sass_sourcemap) do Nanoc::Filter.named(:sass_sourcemap).new(sass_sourcemap_params) end let(:sass) { Nanoc::Filter.named(:sass).new(sass_params) } let(:sass_params) do { item: item_main_view, item_rep: item_main_default_rep_view, items: item_views, config:, } end let(:sass_sourcemap_params) do { item: item_main_view, item_rep: item_main_sourcemap_rep_view, items: item_views, config:, } end let(:item_main) do Nanoc::Core::Item.new( content_main, { content_filename: 'content/style/main.sass' }, '/style/main.sass', ) end let(:content_main) do Nanoc::Core::TextualContent.new( '/* irrelevant */', filename: File.expand_path('content/style/main.sass'), ) end let(:item_blue) do Nanoc::Core::Item.new( content_blue, { content_filename: 'content/style/colors/blue.sass' }, '/style/colors/blue.sass', ) end let(:content_blue) do Nanoc::Core::TextualContent.new( ".blue\n color: blue", filename: File.expand_path('content/style/colors/blue.sass'), ) end let(:item_red) do Nanoc::Core::Item.new( content_red, { content_filename: 'content/style/colors/red.scss' }, '/style/colors/red.scss', ) end let(:content_red) do Nanoc::Core::TextualContent.new( '.red { color: red; }', filename: File.expand_path('content/style/colors/red.scss'), ) end let(:item_partial_scss) do Nanoc::Core::Item.new( content_partial_scss, { content_filename: 'content/style/_partial.scss' }, '/style/_partial.scss', ) end let(:content_partial_scss) do Nanoc::Core::TextualContent.new( '* { margin: 0; }', filename: File.expand_path('content/style/_partial.scss'), ) end let(:item_partial_sass) do Nanoc::Core::Item.new( content_partial_sass, { content_filename: 'content/style/_sass-partial.sass' }, '/style/_sass-partial.sass', ) end let(:content_partial_sass) do sass = <<~SASS * margin: 0 SASS Nanoc::Core::TextualContent.new( sass, filename: File.expand_path('content/style/_sass-partial.sass'), ) end let(:item_partial_sass_anonymous) do Nanoc::Core::Item.new( content_partial_sass_anonymous, { content_filename: 'content/style/_anonymous-sass-partial' }, '/style/_anonymous-sass-partial', ) end let(:content_partial_sass_anonymous) do sass = <<~SASS * margin: 0 SASS Nanoc::Core::TextualContent.new( sass, filename: File.expand_path('content/style/_anonymous-sass-partial'), ) end let(:item_main_default_rep) do Nanoc::Core::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::Core::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::Core::CompilationItemView.new(item_main, view_context) } let(:item_main_default_rep_view) { Nanoc::Core::CompilationItemRepView.new(item_main_default_rep, view_context) } let(:item_main_sourcemap_rep_view) { Nanoc::Core::CompilationItemRepView.new(item_main_sourcemap_rep, view_context) } let(:items) { Nanoc::Core::ItemCollection.new(config, [item_main, item_blue, item_red, item_partial_scss, item_partial_sass, item_partial_sass_anonymous]) } let(:layouts) { Nanoc::Core::LayoutCollection.new(config) } let(:item_views) { Nanoc::Core::ItemCollectionWithRepsView.new(items, view_context) } let(:view_context) do Nanoc::Core::ViewContextForCompilation.new( reps:, items:, dependency_tracker:, compilation_context:, compiled_content_store:, ) end let(:reps) do Nanoc::Core::ItemRepRepo.new.tap do |reps| [item_blue, item_red, item_partial_scss, item_partial_sass, item_partial_sass_anonymous].each do |item| reps << Nanoc::Core::ItemRep.new(item, :default).tap do |rep| rep.compiled = true rep.snapshot_defs = [Nanoc::Core::SnapshotDef.new(:last, binary: false)] end end reps << item_main_default_rep reps << item_main_sourcemap_rep end end let(:dependency_tracker) { Nanoc::Core::DependencyTracker.new(dependency_store) } let(:dependency_store) { Nanoc::Core::DependencyStore.new(empty_items, empty_layouts, config) } let(:compilation_context) do Nanoc::Core::CompilationContext.new( action_provider:, reps:, site:, compiled_content_cache:, compiled_content_store:, ) end let(:action_provider) do Class.new(Nanoc::Core::ActionProvider) do def self.for(_context) raise NotImplementedError end def initialize; end end.new end let(:compiled_content_cache) { Nanoc::Core::CompiledContentCache.new(config:) } let(:compiled_content_store) do Nanoc::Core::CompiledContentStore.new.tap do |repo| repo.set(reps[item_blue].first, :last, content_blue) repo.set(reps[item_red].first, :last, content_red) repo.set(reps[item_partial_scss].first, :last, content_partial_scss) repo.set(reps[item_partial_sass].first, :last, content_partial_sass) repo.set(reps[item_partial_sass_anonymous].first, :last, content_partial_sass_anonymous) end end let(:site) do Nanoc::Core::Site.new( config:, code_snippets: [], data_source: Nanoc::Core::InMemoryDataSource.new(items, layouts), ) end let(:empty_items) { Nanoc::Core::ItemCollection.new(config) } let(:empty_layouts) { Nanoc::Core::LayoutCollection.new(config) } let(:config) { Nanoc::Core::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 SASS' do content = <<~SASS .foo color: #f00 SASS expect(sass.setup_and_run(content, syntax: :sass)) .to match(/^\.foo\s*\{\s*color:\s*(red|#f00);?\s*\}/m) end it 'supports SASS as default syntax' do content = <<~SASS .foo color: #f00 SASS expect(sass.setup_and_run(content)) .to match(/^\.foo\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 SCSS 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 'can import SASS partials by relative path' do expect(sass.setup_and_run('@import sass-partial')) .to match(/\A\*\s*\{\s*margin:\s+0;\s*\}\s*\z/) end it 'cannot import anonymous SASS partials by relative path' do expect { sass.setup_and_run('@import anonymous-sass-partial') } .to raise_error(Sass::SyntaxError, /File to import not found/) 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 SCSS extension' do expect(sass.setup_and_run('@import partial.scss')) .to match(/\A\*\s*\{\s*margin:\s+0;\s*\}\s*\z/) end it 'can import partials by relative path with SASS extension' do expect(sass.setup_and_run('@import sass-partial.sass')) .to match(/\A\*\s*\{\s*margin:\s+0;\s*\}\s*\z/) end it 'cannot import partials by relative path without extension' do expect { sass.setup_and_run('@import anonymous-sass-partial') } .to raise_error(Sass::SyntaxError, /File to import not found/) end it 'cannot import partials by nested relative path with SCSS extension' do expect { sass.setup_and_run('@import content/style/partial.scss') } .to raise_error(Sass::SyntaxError, /File to import not found/) end it 'cannot import partials by nested relative path with SASS extension' do expect { sass.setup_and_run('@import content/style/sass-partial.sass') } .to raise_error(Sass::SyntaxError, /File to import not found/) end it 'cannot import partials by nested relative path without extension' do expect { sass.setup_and_run('@import content/style/anonymous-sass-partial') } .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_scss.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::Core::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 SASS by identifier' do expect(sass.setup_and_run('@import /style/colors/blue.sass')) .to match(/\A\.blue\s+\{\s*color:\s+blue;?\s*\}\s*\z/) end it 'can import SCSS by identifier' do expect(sass.setup_and_run('@import /style/colors/red.scss')) .to match(/\A\.red\s+\{\s*color:\s+red;?\s*\}\s*\z/) end it 'can import SASS by identifier without extension' do expect(sass.setup_and_run('@import /style/_anonymous-sass-partial')) .to match(/\A\*\s+\{\s*margin:\s+0;?\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.13.3/nanoc/spec/nanoc/helpers/000077500000000000000000000000001472033334600174755ustar00rootroot00000000000000nanoc-4.13.3/nanoc/spec/nanoc/helpers/blogging_spec.rb000066400000000000000000000135051472033334600226300ustar00rootroot00000000000000# 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 contain_exactly('/1', '/2') end end describe '#sorted_articles' do subject { helper.sorted_articles } before do attrs = { kind: 'item' } ctx.create_item('blah', attrs, '/0') attrs = { kind: 'article', created_at: (Date.today - 1).to_s } ctx.create_item('blah blah', attrs, '/1') attrs = { kind: 'article', created_at: (Time.now - 500).to_s } ctx.create_item('blah blah blah', attrs, '/2') end it 'returns the two articles in descending order' do expect(subject.map(&:identifier)).to eq(['/2', '/1']) end end describe '#url_for' do subject { helper.url_for(ctx.items['/stuff']) } let(:item_attributes) { {} } before do ctx.create_item('Stuff', item_attributes, '/stuff') ctx.create_rep(ctx.items['/stuff'], '/rep/path/stuff.html') ctx.config[:base_url] = base_url end context 'without base_url' do let(:base_url) { nil } it 'raises' do expect { subject }.to raise_error(Nanoc::Error) end end context 'with base_url' do let(:base_url) { 'http://url.base' } context 'with custom_url_in_feed' do let(:item_attributes) do { custom_url_in_feed: 'http://example.com/stuff.html' } end it 'returns custom URL' do expect(subject).to eql('http://example.com/stuff.html') end end context 'without custom_url_in_feed' do context 'with custom_path_in_feed' do let(:item_attributes) do { custom_path_in_feed: '/stuff.html' } end it 'returns base URL + custom path' do expect(subject).to eql('http://url.base/stuff.html') end end context 'without custom_path_in_feed' do it 'returns base URL + path' do expect(subject).to eql('http://url.base/rep/path/stuff.html') end end end end end describe '#feed_url' do subject { helper.feed_url } let(:item_attributes) { {} } before do ctx.create_item('Feed', item_attributes, '/feed') ctx.create_rep(ctx.items['/feed'], '/feed.xml') ctx.item = ctx.items['/feed'] ctx.config[:base_url] = base_url end context 'without base_url' do let(:base_url) { nil } it 'raises' do expect { subject }.to raise_error(Nanoc::Error) end end context 'with base_url' do let(:base_url) { 'http://url.base' } context 'with feed_url' do let(:item_attributes) do { feed_url: 'http://custom.feed.url/feed.rss' } end it 'returns custom URL' do expect(subject).to eql('http://custom.feed.url/feed.rss') end end context 'without feed_url' do it 'returns base URL + path' do expect(subject).to eql('http://url.base/feed.xml') end end end end describe '#attribute_to_time' do subject { helper.attribute_to_time(arg) } let(:noon_s) { 1_446_903_076 } let(:beginning_of_day_s) { 1_446_854_400 } let(:around_noon_local) { Time.at(noon_s - Time.at(noon_s).utc_offset) } let(:around_noon_utc) { Time.at(noon_s) } let(:beginning_of_day_utc) { Time.at(beginning_of_day_s) } context 'with Time instance' do let(:arg) { around_noon_utc } it { is_expected.to eql(around_noon_utc) } end context 'with Date instance' do let(:arg) { Date.new(2015, 11, 7) } it { is_expected.to eql(beginning_of_day_utc) } end context 'with DateTime instance' do let(:arg) { DateTime.new(2015, 11, 7, 13, 31, 16) } it { is_expected.to eql(around_noon_utc) } end context 'with string' do let(:arg) { '2015-11-7 13:31:16' } it { is_expected.to eql(around_noon_local) } end end describe '#atom_tag_for' do subject { helper.atom_tag_for(ctx.items['/stuff']) } let(:item_attributes) { { created_at: '2015-05-19 12:34:56' } } let(:item_rep_path) { '/stuff.xml' } let(:base_url) { 'http://url.base' } before do ctx.create_item('Stuff', item_attributes, '/stuff') ctx.create_rep(ctx.items['/stuff'], item_rep_path) ctx.config[:base_url] = base_url end context 'item with path' do let(:item_rep_path) { '/stuff.xml' } it { is_expected.to eql('tag:url.base,2015-05-19:/stuff.xml') } end context 'item without path' do let(:item_rep_path) { nil } it { is_expected.to eql('tag:url.base,2015-05-19:/stuff') } end context 'bare URL without subdir' do let(:base_url) { 'http://url.base' } it { is_expected.to eql('tag:url.base,2015-05-19:/stuff.xml') } end context 'bare URL with subdir' do let(:base_url) { 'http://url.base/sub' } it { is_expected.to eql('tag:url.base,2015-05-19:/sub/stuff.xml') } end context 'created_at is date' do let(:item_attributes) do { created_at: Date.parse('2015-05-19 12:34:56') } end it { is_expected.to eql('tag:url.base,2015-05-19:/stuff.xml') } end context 'created_at is time' do let(:item_attributes) do { created_at: Time.parse('2015-05-19 12:34:56') } end it { is_expected.to eql('tag:url.base,2015-05-19:/stuff.xml') } end # TODO: handle missing base_dir # TODO: handle missing created_at end end nanoc-4.13.3/nanoc/spec/nanoc/helpers/breadcrumbs_spec.rb000066400000000000000000000214321472033334600233270ustar00rootroot00000000000000# 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::Core::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::Core::Identifier.new('/foo/', type: :legacy)) ctx.create_item('root', {}, Nanoc::Core::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::Core::Identifier.new('/foo/bar/', type: :legacy)) ctx.create_item('child', {}, Nanoc::Core::Identifier.new('/foo/', type: :legacy)) ctx.create_item('root', {}, Nanoc::Core::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::Core::Identifier.new('/foo/bar/', type: :legacy)) ctx.create_item('root', {}, Nanoc::Core::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::Core::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::Core::Identifier.new('/foo.md')) ctx.create_item('root', {}, Nanoc::Core::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::Core::Identifier.new('/foo/bar.md')) ctx.create_item('child', {}, Nanoc::Core::Identifier.new('/foo.md')) ctx.create_item('root', {}, Nanoc::Core::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::Core::Identifier.new('/foo/bar.md')) ctx.create_item('root', {}, Nanoc::Core::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::Core::Identifier.new('/foo/index.md')) ctx.create_item('root', {}, Nanoc::Core::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::Core::Identifier.new('/1.5/stuff.md')) ctx.create_item('child0', {}, Nanoc::Core::Identifier.new('/1.4.md')) ctx.create_item('child1', {}, Nanoc::Core::Identifier.new('/1.5.md')) ctx.create_item('child2', {}, Nanoc::Core::Identifier.new('/1.6.md')) ctx.create_item('root', {}, Nanoc::Core::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::Core::Identifier.new('/foo/stuff.md')) ctx.create_item('child0', {}, Nanoc::Core::Identifier.new('/foo.md.erb')) ctx.create_item('child1', {}, Nanoc::Core::Identifier.new('/foo.md')) ctx.create_item('child2', {}, Nanoc::Core::Identifier.new('/foo.erb')) ctx.create_item('root', {}, Nanoc::Core::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:) } 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:) } 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::Core::Identifier.new('/foo/stuff.zip')) ctx.create_item('grandchild2', {}, Nanoc::Core::Identifier.new('/foo/stuff.md')) ctx.create_item('grandchild3', {}, Nanoc::Core::Identifier.new('/foo/stuff.png')) ctx.create_item('child', {}, Nanoc::Core::Identifier.new('/foo.md')) ctx.create_item('root', {}, Nanoc::Core::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.13.3/nanoc/spec/nanoc/helpers/capturing_spec.rb000066400000000000000000000174511472033334600230400ustar00rootroot00000000000000# 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.compiled_content_store.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.compiled_content_store.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.compiled_content_store.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::Core::Errors::UnmetDependency) item.reps[:default]._unwrap.compiled = true ctx.compiled_content_store.set( item.reps[:default]._unwrap, :__capture_foo, Nanoc::Core::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.compiled_content_store.set( item.reps[:default]._unwrap, :__capture_foo, Nanoc::Core::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 subject { helper.capture { _erbout << 'new content' } } let(:_erbout) { +'existing 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.13.3/nanoc/spec/nanoc/helpers/child_parent_spec.rb000066400000000000000000000054061472033334600234750ustar00rootroot00000000000000# 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::Core::Identifier.new('/foo/', type: :legacy) } before do ctx.create_item('abc', {}, Nanoc::Core::Identifier.new('/foo/a/', type: :legacy)) ctx.create_item('def', {}, Nanoc::Core::Identifier.new('/foo/a/b/', type: :legacy)) ctx.create_item('xyz', {}, Nanoc::Core::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::Core::Identifier.new('/foo.md', type: :full) } before do ctx.create_item('abc', {}, Nanoc::Core::Identifier.new('/foo/a.md', type: :full)) ctx.create_item('def', {}, Nanoc::Core::Identifier.new('/foo/a/b.md', type: :full)) ctx.create_item('xyz', {}, Nanoc::Core::Identifier.new('/bar.md', type: :full)) ctx.create_item('xyz', {}, Nanoc::Core::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::Core::Identifier.new('/foo/bar/', type: :legacy) } before do ctx.create_item('abc', {}, Nanoc::Core::Identifier.new('/foo/', type: :legacy)) ctx.create_item('def', {}, Nanoc::Core::Identifier.new('/foo/qux/', type: :legacy)) ctx.create_item('xyz', {}, Nanoc::Core::Identifier.new('/foo/bar/asdf/', type: :legacy)) ctx.create_item('opq', {}, Nanoc::Core::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::Core::Identifier.new('/foo/bar.md', type: :full) } before do ctx.create_item('abc', {}, Nanoc::Core::Identifier.new('/foo.md', type: :full)) ctx.create_item('def', {}, Nanoc::Core::Identifier.new('/foo/qux.md', type: :full)) ctx.create_item('xyz', {}, Nanoc::Core::Identifier.new('/foo/bar/asdf.md', type: :full)) ctx.create_item('opq', {}, Nanoc::Core::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.13.3/nanoc/spec/nanoc/helpers/filtering_spec.rb000066400000000000000000000027071472033334600230250ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Helpers::Filtering, helper: true do describe '#filter' do subject { ERB.new(content).result(helper.get_binding) } 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 context 'basic case' do it { is_expected.to eql('AXB') } it 'notifies filtering_started' do expect { subject }.to send_notification(:filtering_started, ctx.item_rep._unwrap, :erb) end it 'notifies filtering_ended' do expect { subject }.to send_notification(:filtering_ended, ctx.item_rep._unwrap, :erb) 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::Filter::UnknownFilterError) 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 end end nanoc-4.13.3/nanoc/spec/nanoc/helpers/html_escape_spec.rb000066400000000000000000000025651472033334600233300ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::Helpers::HTMLEscape, helper: true do describe '#html_escape' do subject { helper.html_escape(string) } context 'when given angular brackets' do let(:string) { '
' } it { is_expected.to eql('<br/>') } end context 'when given ampersand' do let(:string) { 'red & blue' } it { is_expected.to eql('red & blue') } end context 'when given double quotes' do let(:string) { 'projection="isometric"' } it { is_expected.to eql('projection="isometric"') } end context 'when given single quotes' do let(:string) { "projection='perspective'" } it { is_expected.to eql('projection='perspective'') } 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.13.3/nanoc/spec/nanoc/helpers/link_to_spec.rb000066400000000000000000000207551472033334600225040ustar00rootroot00000000000000# 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') ctx.create_rep(target, '/target.html') end let(:target) { ctx.items['/target'] } 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.13.3/nanoc/spec/nanoc/helpers/rendering_spec.rb000066400000000000000000000115651472033334600230210ustar00rootroot00000000000000# 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::Core::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) ctx.create_item('some item', {}, '/some.md') ctx.item = ctx.items['/some.md'] ctx.create_rep(ctx.item, '/some.html') ctx.item_rep = ctx.item.reps[:default] end context 'legacy identifier' do let(:layout_identifier) { Nanoc::Core::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(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::Core::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::Core::LayoutView') } end context 'printing unwrapped layout class' do let(:layout_content) { 'blah <%= @layout._unwrap.class %>' } it { is_expected.to eql('blah Nanoc::Core::Layout') } end context 'printing wrapped item class' do let(:layout_content) { 'item=<%= @item.class %>' } it { is_expected.to eql('item=Nanoc::Core::CompilationItemView') } end context 'printing wrapped item rep class' do let(:layout_content) { 'item_rep=<%= @item_rep.class %>' } it { is_expected.to eql('item_rep=Nanoc::Core::CompilationItemRepView') } end context 'printing wrapped rep class' do let(:layout_content) { 'rep=<%= @rep.class %>' } it { is_expected.to eql('rep=Nanoc::Core::CompilationItemRepView') } 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::Core::Errors::UnknownLayout) end end context 'layout with unknown filter' do let(:action_sequence_for_layout) do [Nanoc::Core::ProcessingActions::Filter.new(:donkey, {})] end let(:layout_content) { 'blah' } it 'raises' do expect { subject }.to raise_error(Nanoc::Filter::UnknownFilterError) end end context 'layout without filter' do let(:action_sequence_for_layout) do [Nanoc::Core::ProcessingActions::Filter.new(nil, {})] end let(:layout_content) { 'blah' } it 'raises' do expect { subject }.to raise_error(Nanoc::Core::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.13.3/nanoc/spec/nanoc/helpers/tagging_spec.rb000066400000000000000000000060471472033334600224630ustar00rootroot00000000000000# 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: 'https://nanoc.app/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) { 'https://nanoc.app/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.13.3/nanoc/spec/nanoc/helpers/text_spec.rb000066400000000000000000000025401472033334600220210ustar00rootroot00000000000000# 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.13.3/nanoc/spec/nanoc/integration/000077500000000000000000000000001472033334600203565ustar00rootroot00000000000000nanoc-4.13.3/nanoc/spec/nanoc/integration/compile_command_spec.rb000066400000000000000000000041041472033334600250420ustar00rootroot00000000000000# 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.write('content/a.html', '

A

') File.write('content/b.html', '

B

') # 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 Nanoc::CLI.run(%w[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 Nanoc::CLI.run(%w[compile]) # Check expect(File.read('output/index.html')).to eq('

B

') end it 'recompiles only items under focus' do # Create items File.write('content/a.html', '

A

') File.write('content/b.html', '

B

') # Create routes File.write('Rules', <<~RULES) compile '/**/*' do write ext: '.html' end RULES # Compile Nanoc::CLI.run(%w[compile --focus /a.*]) # Check expect(File.read('output/a.html')).to eq('

A

') expect(File.file?('output/b.html')).to be(false) end end nanoc-4.13.3/nanoc/spec/nanoc/integration/outdatedness_integration_spec.rb000066400000000000000000000210761472033334600270300ustar00rootroot00000000000000# 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 Nanoc::CLI.run(%w[compile]) end 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 Nanoc::CLI.run(%w[compile]) end 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 Nanoc::CLI.run(%w[compile]) end 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 Nanoc::CLI.run(%w[compile]) end 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.13.3/nanoc/spec/nanoc/integration/partial_recompilation_spec.rb000066400000000000000000000034051472033334600263000ustar00rootroot00000000000000# 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.13.3/nanoc/spec/nanoc/integration/write_nil_spec.rb000066400000000000000000000044341472033334600237160ustar00rootroot00000000000000# 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.13.3/nanoc/spec/nanoc/orig_cli/000077500000000000000000000000001472033334600176225ustar00rootroot00000000000000nanoc-4.13.3/nanoc/spec/nanoc/orig_cli/commands/000077500000000000000000000000001472033334600214235ustar00rootroot00000000000000nanoc-4.13.3/nanoc/spec/nanoc/orig_cli/commands/show_rules_spec.rb000066400000000000000000000075311472033334600251620ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::OrigCLI::Commands::ShowRules, site: true, stdio: 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:, layouts:, compiler:, config:, ) end let(:items) do Nanoc::Core::ItemCollection.new( config, [ Nanoc::Core::Item.new('About Me', {}, '/about.md'), Nanoc::Core::Item.new('About My Dog', {}, '/dog.md'), Nanoc::Core::Item.new('Raw Data', {}, '/other.dat'), ], ) end let(:reps) do Nanoc::Core::ItemRepRepo.new.tap do |reps| reps << Nanoc::Core::ItemRep.new(items.object_with_identifier('/about.md'), :default) reps << Nanoc::Core::ItemRep.new(items.object_with_identifier('/about.md'), :text) reps << Nanoc::Core::ItemRep.new(items.object_with_identifier('/dog.md'), :default) reps << Nanoc::Core::ItemRep.new(items.object_with_identifier('/dog.md'), :text) reps << Nanoc::Core::ItemRep.new(items.object_with_identifier('/other.dat'), :default) end end let(:layouts) do Nanoc::Core::LayoutCollection.new( config, [ Nanoc::Core::Layout.new('Default', {}, '/default.erb'), Nanoc::Core::Layout.new('Article', {}, '/article.haml'), Nanoc::Core::Layout.new('Other', {}, '/other.xyzzy'), ], ) end let(:config) { Nanoc::Core::Configuration.new(dir: Dir.getwd).with_defaults } let(:action_provider) do Class.new(Nanoc::Core::ActionProvider) do attr_reader :rules_collection def self.for(_context) raise NotImplementedError end def initialize(rules_collection) @rules_collection = rules_collection end end.new(rules_collection) end 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::Core::Pattern.from('/dog.*'), :default, proc {}), ) rc.add_item_compilation_rule( Nanoc::RuleDSL::CompilationRule.new(Nanoc::Core::Pattern.from('/*.md'), :default, proc {}), ) rc.add_item_compilation_rule( Nanoc::RuleDSL::CompilationRule.new(Nanoc::Core::Pattern.from('/**/*'), :text, proc {}), ) rc.layout_filter_mapping[Nanoc::Core::Pattern.from('/*.haml')] = [:haml, {}] rc.layout_filter_mapping[Nanoc::Core::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::Core::Compiler).to receive(:new_for).with(site).and_return(compiler) expect(compiler).to receive(:run_until_reps_built).and_return(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.13.3/nanoc/spec/nanoc/orig_cli_spec.rb000066400000000000000000000021571472033334600211660ustar00rootroot00000000000000# frozen_string_literal: true describe Nanoc::OrigCLI 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 end nanoc-4.13.3/nanoc/spec/nanoc/regressions/000077500000000000000000000000001472033334600203765ustar00rootroot00000000000000nanoc-4.13.3/nanoc/spec/nanoc/regressions/gh_1015_spec.rb000066400000000000000000000007131472033334600230020ustar00rootroot00000000000000# 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.13.3/nanoc/spec/nanoc/regressions/gh_1047_spec.rb000066400000000000000000000020121472033334600230010ustar00rootroot00000000000000# 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::Core::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.13.3/nanoc/spec/nanoc/regressions/gh_1064_spec.rb000066400000000000000000000007311472033334600230060ustar00rootroot00000000000000# 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', <<~EOS) compile '/**/*.html' do filter :erb write item.identifier.to_s end EOS Nanoc::CLI.run(%w[compile]) end it 'does not output filename more than once' do regex = /skip.*index\.html.*skip.*index\.html/m expect { Nanoc::CLI.run(%w[compile --verbose]) }.not_to output(regex).to_stdout end it 'outputs filename' do regex = /skip.*index\.html/ expect { Nanoc::CLI.run(%w[compile --verbose]) }.to output(regex).to_stdout end end nanoc-4.13.3/nanoc/spec/nanoc/regressions/gh_1107_spec.rb000066400000000000000000000004741472033334600230100ustar00rootroot00000000000000# frozen_string_literal: true describe 'GH-1107', site: true, stdio: true do before do 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.13.3/nanoc/spec/nanoc/regressions/gh_1185_spec.rb000066400000000000000000000007101472033334600230070ustar00rootroot00000000000000# 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 # rubocop:disable RSpec/NoExpectationExample Nanoc::CLI.run(%w[compile]) end end nanoc-4.13.3/nanoc/spec/nanoc/regressions/gh_1216_spec.rb000066400000000000000000000043361472033334600230120ustar00rootroot00000000000000# 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 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.13.3/nanoc/spec/nanoc/regressions/gh_1248_spec.rb000066400000000000000000000007071472033334600230150ustar00rootroot00000000000000# 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 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.13.3/nanoc/spec/nanoc/regressions/gh_1313_spec.rb000066400000000000000000000010231472033334600227760ustar00rootroot00000000000000# 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 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.13.3/nanoc/spec/nanoc/regressions/gh_1319_spec.rb000066400000000000000000000007071472033334600230140ustar00rootroot00000000000000# 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 Nanoc::CLI.run(%w[compile]) end example do expect(File.read('output/stuff.html')).to eq('abc') end end nanoc-4.13.3/nanoc/spec/nanoc/regressions/gh_1323_spec.rb000066400000000000000000000010321472033334600227770ustar00rootroot00000000000000# 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::Filter::FilterReturnedNilError) } end end nanoc-4.13.3/nanoc/spec/nanoc/regressions/gh_1328_spec.rb000066400000000000000000000022231472033334600230070ustar00rootroot00000000000000# 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 Nanoc::CLI.run([]) end it 'fails check for foo.html' do expect { Nanoc::CLI.run(%w[check ilinks]) } .to raise_error(Nanoc::Core::TrivialError, '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::Core::TrivialError, '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::Core::TrivialError, 'One or more checks failed') .and output(%r{output/foo\.htm:}).to_stdout end end nanoc-4.13.3/nanoc/spec/nanoc/regressions/gh_1338_spec.rb000066400000000000000000000010141472033334600230050ustar00rootroot00000000000000# 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 # rubocop:disable RSpec/NoExpectationExample Nanoc::CLI.run([]) end end nanoc-4.13.3/nanoc/spec/nanoc/regressions/gh_1342_spec.rb000066400000000000000000000007501472033334600230060ustar00rootroot00000000000000# 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 # rubocop:disable RSpec/NoExpectationExample Nanoc::CLI.run([]) Nanoc::CLI.run([]) end end nanoc-4.13.3/nanoc/spec/nanoc/regressions/gh_1352_spec.rb000066400000000000000000000004601472033334600230050ustar00rootroot00000000000000# 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.13.3/nanoc/spec/nanoc/regressions/gh_1358_spec.rb000066400000000000000000000011421472033334600230110ustar00rootroot00000000000000# 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.13.3/nanoc/spec/nanoc/regressions/gh_1372_spec.rb000066400000000000000000000014241472033334600230100ustar00rootroot00000000000000# 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.13.3/nanoc/spec/nanoc/regressions/gh_1374_spec.rb000066400000000000000000000006421472033334600230130ustar00rootroot00000000000000# 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.13.3/nanoc/spec/nanoc/regressions/gh_1378_spec.rb000066400000000000000000000011401472033334600230110ustar00rootroot00000000000000# 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.13.3/nanoc/spec/nanoc/regressions/gh_1412_spec.rb000066400000000000000000000015611472033334600230050ustar00rootroot00000000000000# frozen_string_literal: true describe 'GH-1412', site: true, stdio: true do before do FileUtils.mkdir_p('content') File.write('content/a.erb', 'A <%= @items["/b.*"].compiled_content %>') File.write('content/b.erb', 'B <%= @items["/a.*"].compiled_content %>') FileUtils.mkdir_p('layouts') File.write('layouts/default.erb', '[<%= yield %>]') File.write('Rules', <<~EOS) compile '/*' do layout '/default.*' filter :erb write ext: 'html' end layout '/*', :erb EOS end example do Nanoc::CLI.run([]) expect(File.file?('output/a.html')).to be(true) expect(File.read('output/a.html')).to eq('[A B <%= @items["/a.*"].compiled_content %>]') expect(File.file?('output/b.html')).to be(true) expect(File.read('output/b.html')).to eq('[B A <%= @items["/b.*"].compiled_content %>]') end end nanoc-4.13.3/nanoc/spec/nanoc/regressions/gh_1428_spec.rb000066400000000000000000000006101472033334600230060ustar00rootroot00000000000000# frozen_string_literal: true describe 'GH-1428', site: true, stdio: true do before do FileUtils.mkdir_p('layouts') File.write('layouts/default.erb', 'layout stuff') File.write('Rules', <<~EOS) ignore '/*' layout '/*', :erb EOS end example do # rubocop:disable RSpec/NoExpectationExample Nanoc::CLI.run([]) Nanoc::CLI.run(['show-data']) end end nanoc-4.13.3/nanoc/spec/nanoc/regressions/gh_1463_spec.rb000066400000000000000000000025701472033334600230140ustar00rootroot00000000000000# frozen_string_literal: true describe 'GH-1463', site: true, stdio: true do before do FileUtils.mkdir_p('content') FileUtils.mkdir_p('content/org1') File.write('content/org1.erb', <<~CONTENT)
    <% children_of(@item).each do |c| %>
  • <%= c[:title] %>
  • <% end %>
CONTENT File.write('content/org1/oink.md', <<~CONTENT) --- title: Oink --- here is oink content CONTENT FileUtils.mkdir_p('lib') File.write('lib/default.rb', <<~LIB) include Nanoc::Helpers::ChildParent LIB File.write('Rules', <<~RULES) compile '/**/*' do filter :erb write ext: 'html' end RULES end example do Nanoc::CLI.run([]) expect(File.file?('output/org1.html')).to be(true) expect(File.read('output/org1.html')).to match(%r{
  • Oink
  • }) # Remove oink FileUtils.rm('content/org1/oink.md') Nanoc::CLI.run([]) expect(File.file?('output/org1.html')).to be(true) expect(File.read('output/org1.html')).not_to match(%r{
  • Oink
  • }) # Re-add oink File.write('content/org1/oink.md', <<~CONTENT) --- title: Oink --- here is oink content CONTENT Nanoc::CLI.run([]) expect(File.file?('output/org1.html')).to be(true) expect(File.read('output/org1.html')).to match(%r{
  • Oink
  • }) end end nanoc-4.13.3/nanoc/spec/nanoc/regressions/gh_1554_spec.rb000066400000000000000000000021061472033334600230100ustar00rootroot00000000000000# frozen_string_literal: true describe 'GH-1554', site: true, stdio: true do example do FileUtils.mkdir_p('content') FileUtils.mkdir_p('content/parts') File.write('content/main.erb', 'Stuff') File.write('content/parts/a.txt', "---\ndraft: false\n---\nPart A") File.write('content/parts/b.txt', "---\ndraft: false\n---\nPart B") File.write('Rules', <<~CONTENT) preprocess do @items.delete_if { |i| i[:draft] } end compile '/*.erb' do filter :erb write ext: 'txt' end compile '/**/*.txt' do write ext: 'txt' end CONTENT File.write('content/main.erb', '<%= @items.find_all("/parts/*").map(&:compiled_content).sort.join("\n") %>') Nanoc::CLI.run([]) expect(File.file?('output/main.txt')).to be(true) expect(File.read('output/main.txt')).to eq("Part A\nPart B") File.write('content/parts/b.txt', "---\ndraft: true\n---\nPart B") Nanoc::CLI.run([]) expect(File.file?('output/main.txt')).to be(true) expect(File.read('output/main.txt')).to eq('Part A') end end nanoc-4.13.3/nanoc/spec/nanoc/regressions/gh_1572_spec.rb000066400000000000000000000015271472033334600230160ustar00rootroot00000000000000# frozen_string_literal: true describe 'GH-1572', site: true, stdio: true do example do FileUtils.mkdir_p('content') allow_any_instance_of(Nanoc::Core::BinaryCompiledContentCache) .to receive(:use_clonefile?) .and_return(false) File.write('content/repro.jpg', '') File.chmod(0o400, 'content/repro.jpg') expect(File.stat('content/repro.jpg').mode).to eq(0o100400) Nanoc::CLI.run([]) expect(File.file?('output/repro.jpg')).to be(true) expect(File.read('output/repro.jpg')).to eq('') expect(File.stat('output/repro.jpg').mode).to eq(0o100400) FileUtils.rm_f('output/repro.jpg') Nanoc::CLI.run([]) expect(File.file?('output/repro.jpg')).to be(true) expect(File.read('output/repro.jpg')).to eq('') expect(File.stat('output/repro.jpg').mode).to eq(0o100400) end end nanoc-4.13.3/nanoc/spec/nanoc/regressions/gh_761_spec.rb000066400000000000000000000011141472033334600227250ustar00rootroot00000000000000# 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.13.3/nanoc/spec/nanoc/regressions/gh_913_spec.rb000066400000000000000000000007301472033334600227270ustar00rootroot00000000000000# 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 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.13.3/nanoc/spec/nanoc/regressions/gh_928_spec.rb��������������������������������������������0000664�0000000�0000000�00000000265�14720333346�0022740�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.13.3/nanoc/spec/nanoc/regressions/gh_937_spec.rb��������������������������������������������0000664�0000000�0000000�00000001377�14720333346�0022745�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.13.3/nanoc/spec/nanoc/regressions/gh_942_spec.rb��������������������������������������������0000664�0000000�0000000�00000001024�14720333346�0022726�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.13.3/nanoc/spec/nanoc/regressions/gh_947_spec.rb��������������������������������������������0000664�0000000�0000000�00000001052�14720333346�0022734�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.13.3/nanoc/spec/nanoc/regressions/gh_948_spec.rb��������������������������������������������0000664�0000000�0000000�00000000657�14720333346�0022747�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 # rubocop:disable RSpec/NoExpectationExample Nanoc::CLI.run(%w[compile]) end end ���������������������������������������������������������������������������������nanoc-4.13.3/nanoc/spec/nanoc/regressions/gh_951_spec.rb��������������������������������������������0000664�0000000�0000000�00000000640�14720333346�0022731�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 be(true) end end ������������������������������������������������������������������������������������������������nanoc-4.13.3/nanoc/spec/nanoc/regressions/gh_954_spec.rb��������������������������������������������0000664�0000000�0000000�00000001775�14720333346�0022746�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.13.3/nanoc/spec/nanoc/regressions/gh_970a_spec.rb�������������������������������������������0000664�0000000�0000000�00000000650�14720333346�0023074�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.13.3/nanoc/spec/nanoc/regressions/gh_970b_spec.rb�������������������������������������������0000664�0000000�0000000�00000003032�14720333346�0023072�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 Nanoc::CLI.run(%w[compile]) end 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.13.3/nanoc/spec/nanoc/regressions/gh_974_spec.rb��������������������������������������������0000664�0000000�0000000�00000000607�14720333346�0022741�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 be(true) end end �������������������������������������������������������������������������������������������������������������������������nanoc-4.13.3/nanoc/spec/nanoc/regressions/gh_981_spec.rb��������������������������������������������0000664�0000000�0000000�00000001102�14720333346�0022726�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.13.3/nanoc/spec/nanoc/rule_dsl/�������������������������������������������������������������0000775�0000000�0000000�00000000000�14720333346�0017644�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������nanoc-4.13.3/nanoc/spec/nanoc/rule_dsl/action_recorder_spec.rb��������������������������������������0000664�0000000�0000000�00000014617�14720333346�0024356�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::Core::Item.new('stuff', {}, '/foo.md') } let(:rep) { Nanoc::Core::ItemRep.new(item, :default) } describe '#filter' do it 'records filter call without arguments' do recorder.filter(:erb) expect(action_sequence.size).to be(1) expect(action_sequence[0]).to be_a(Nanoc::Core::ProcessingActions::Filter) expect(action_sequence[0].filter_name).to be(: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 be(1) expect(action_sequence[0]).to be_a(Nanoc::Core::ProcessingActions::Filter) expect(action_sequence[0].filter_name).to be(: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 be(2) expect(action_sequence[0]).to be_a(Nanoc::Core::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::Core::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 be(2) expect(action_sequence[0]).to be_a(Nanoc::Core::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::Core::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::Core::ActionSequenceBuilder::CannotCreateMultipleSnapshotsWithSameNameError) end end context 'no arguments' do subject { recorder.snapshot(:foo) } it 'records' do subject expect(action_sequence.size).to be(1) expect(action_sequence[0]).to be_a(Nanoc::Core::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 be(1) expect(action_sequence[0]).to be_a(Nanoc::Core::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 be(1) expect(action_sequence[0]).to be_a(Nanoc::Core::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::Core::Identifier.from('/routed-foo.html') } } it 'records' do subject expect(action_sequence.size).to be(1) expect(action_sequence[0]).to be_a(Nanoc::Core::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 be(1) expect(action_sequence[0]).to be_a(Nanoc::Core::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 be(2) expect(action_sequence[0]).to be_a(Nanoc::Core::ProcessingActions::Snapshot) expect(action_sequence[0].snapshot_names).to eql([:foo]) expect(action_sequence[1]).to be_a(Nanoc::Core::ProcessingActions::Snapshot) expect(action_sequence[1].snapshot_names).to eql([:bar]) end end end �����������������������������������������������������������������������������������������������������������������nanoc-4.13.3/nanoc/spec/nanoc/rule_dsl/action_sequence_calculator_spec.rb���������������������������0000664�0000000�0000000�00000017211�14720333346�0026563�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# frozen_string_literal: true describe(Nanoc::RuleDSL::ActionSequenceCalculator) do subject(:action_sequence_calculator) do described_class.new(site:, rules_collection:) end let(:rules_collection) { Nanoc::RuleDSL::RulesCollection.new } let(:config) { Nanoc::Core::Configuration.new(dir: Dir.getwd).with_defaults } let(:items) { Nanoc::Core::ItemCollection.new(config) } let(:layouts) { Nanoc::Core::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:, layouts: } data_source_class.new(config, '/', '/', data_source_config) end let(:site) do Nanoc::Core::Site.new(config:, code_snippets: [], data_source:) end describe '#[]' do subject { action_sequence_calculator[obj] } context 'with item rep' do let(:obj) { Nanoc::Core::ItemRep.new(item, :csv) } let(:item) { Nanoc::Core::Item.new('content', {}, Nanoc::Core::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::Core::Pattern.from('/list.*'), :csv, rules_proc) rules_collection.add_item_compilation_rule(rule) end example do subject expect(subject[0]).to be_a(Nanoc::Core::ProcessingActions::Snapshot) expect(subject[0].snapshot_names).to eql([:raw]) expect(subject[0].paths).to be_empty expect(subject[1]).to be_a(Nanoc::Core::ProcessingActions::Filter) expect(subject[1].filter_name).to be(:erb) expect(subject[1].params).to eql(speed: :over_9000) expect(subject[2]).to be_a(Nanoc::Core::ProcessingActions::Snapshot) expect(subject[2].snapshot_names).to eql([:pre]) expect(subject[2].paths).to be_empty expect(subject[3]).to be_a(Nanoc::Core::ProcessingActions::Layout) expect(subject[3].layout_identifier).to eql('/default.*') expect(subject[3].params).to be_nil expect(subject[4]).to be_a(Nanoc::Core::ProcessingActions::Filter) expect(subject[4].filter_name).to be(:typohero) expect(subject[4].params).to eql({}) expect(subject[5]).to be_a(Nanoc::Core::ProcessingActions::Snapshot) expect(subject[5].snapshot_names).to eql(%i[post last]) expect(subject[5].paths).to be_empty expect(subject.size).to be(6) end end context 'no routing rule exists' do before do # Add compilation rule compilation_rule = Nanoc::RuleDSL::CompilationRule.new(Nanoc::Core::Pattern.from('/list.*'), :csv, proc {}) rules_collection.add_item_compilation_rule(compilation_rule) end example do subject expect(subject[0]).to be_a(Nanoc::Core::ProcessingActions::Snapshot) expect(subject[0].snapshot_names).to eql(%i[raw last pre]) expect(subject[0].paths).to be_empty expect(subject.size).to be(1) end end context 'routing rule exists' do before do # Add compilation rule compilation_rule = Nanoc::RuleDSL::CompilationRule.new(Nanoc::Core::Pattern.from('/list.*'), :csv, proc {}) rules_collection.add_item_compilation_rule(compilation_rule) # Add routing rule routing_rule = Nanoc::RuleDSL::RoutingRule.new(Nanoc::Core::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::Core::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 be(1) end end context 'routing rule for other rep exists' do before do # Add compilation rule compilation_rule = Nanoc::RuleDSL::CompilationRule.new(Nanoc::Core::Pattern.from('/list.*'), :csv, proc {}) rules_collection.add_item_compilation_rule(compilation_rule) # Add routing rule routing_rule = Nanoc::RuleDSL::RoutingRule.new(Nanoc::Core::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::Core::ProcessingActions::Snapshot) expect(subject[0].snapshot_names).to eql(%i[raw last pre]) expect(subject[0].paths).to be_empty expect(subject.size).to be(1) end end end context 'with layout' do let(:obj) { Nanoc::Core::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::Core::Pattern.from('/*.erb') rules_collection.layout_filter_mapping[pat] = [:erb, { x: 123 }] end it 'contains memory for the rule' do expect(subject.size).to be(1) expect(subject[0]).to be_a(Nanoc::Core::ProcessingActions::Filter) expect(subject[0].filter_name).to be(: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::Core::ActionSequenceBuilder.build do |b| b.add_snapshot(:a1, nil, rep) b.add_snapshot(:a2, '/a2.md', rep) b.add_snapshot(:a3, nil, rep) b.add_filter(:erb, awesomeness: 'high') b.add_snapshot(:b1, '/b1.md', rep) b.add_snapshot(:b2, nil, rep) b.add_snapshot(:b3, '/b3.md', rep) b.add_filter(:erb, awesomeness: 'high') b.add_snapshot(:c, nil, rep) end end let(:item) { Nanoc::Core::Item.new('asdf', {}, '/foo.md') } let(:rep) { Nanoc::Core::ItemRep.new(item, :default) } example do expect(subject[0]).to be_a(Nanoc::Core::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::Core::ProcessingActions::Filter) expect(subject[2]).to be_a(Nanoc::Core::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::Core::ProcessingActions::Filter) expect(subject[4]).to be_a(Nanoc::Core::ProcessingActions::Snapshot) expect(subject[4].snapshot_names).to eql([:c]) expect(subject[4].paths).to be_empty expect(subject.size).to be(5) end end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nanoc-4.13.3/nanoc/spec/nanoc/rule_dsl/rule_context_spec.rb�����������������������������������������0000664�0000000�0000000�00000025301�14720333346�0023717�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# frozen_string_literal: true shared_examples 'a rule context' do let(:item_identifier) { Nanoc::Core::Identifier.new('/foo.md') } let(:item) { Nanoc::Core::Item.new('content', {}, item_identifier) } let(:config) { Nanoc::Core::Configuration.new(dir: Dir.getwd) } let(:items) { Nanoc::Core::ItemCollection.new(config) } let(:layouts) { Nanoc::Core::LayoutCollection.new(config) } let(:site) do Nanoc::Core::Site.new( config:, code_snippets: [], data_source:, ) end let(:data_source) do Nanoc::Core::InMemoryDataSource.new(items, layouts) end let(:rep) { Nanoc::Core::ItemRep.new(item, :default) } let(:reps) { double(:reps) } let(:compilation_context) { double(:compilation_context) } let(:view_context) do Nanoc::Core::ViewContextForPreCompilation.new(items:) end let(:dependency_tracker) { Nanoc::Core::DependencyTracker::Null.new } describe '#initialize' do it 'wraps objects in view classes' do expect(subject.rep.class).to eql(Nanoc::Core::BasicItemRepView) expect(subject.item.class).to eql(Nanoc::Core::BasicItemView) expect(subject.config.class).to eql(Nanoc::Core::ConfigView) expect(subject.layouts.class).to eql(Nanoc::Core::LayoutCollectionView) expect(subject.items.class).to eql(Nanoc::Core::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::Core::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::Core::Identifier.new('/foo/', type: :legacy) } let(:parent_identifier) { Nanoc::Core::Identifier.new('/', type: :legacy) } let(:parent) { Nanoc::Core::Item.new('parent', {}, parent_identifier) } let(:child_identifier) { Nanoc::Core::Identifier.new('/foo/bar/', type: :legacy) } let(:child) { Nanoc::Core::Item.new('child', {}, child_identifier) } let(:items) do Nanoc::Core::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::Core::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::Core::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::Core::Identifier.new('/foo/', type: :legacy) } let(:parent_identifier) { Nanoc::Core::Identifier.new('/', type: :legacy) } let(:parent) { Nanoc::Core::Item.new('parent', {}, parent_identifier) } let(:child_identifier) { Nanoc::Core::Identifier.new('/foo/bar/', type: :legacy) } let(:child) { Nanoc::Core::Item.new('child', {}, child_identifier) } let(:items) do Nanoc::Core::ItemCollection.new(config, [item, parent, child]) end it 'is a view without reps access' do expect(subject.class).to eql(Nanoc::Core::ItemCollectionWithoutRepsView) end it 'contains all items' do expect(subject._unwrap).to contain_exactly(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:, site:, view_context:) end let(:item_identifier) { Nanoc::Core::Identifier.new('/foo.md') } let(:item) { Nanoc::Core::Item.new('content', {}, item_identifier) } let(:rep) { Nanoc::Core::ItemRep.new(item, :default) } let(:config) { Nanoc::Core::Configuration.new(dir: Dir.getwd) } let(:items) { Nanoc::Core::ItemCollection.new(config) } let(:site) do Nanoc::Core::Site.new( config:, code_snippets: [], data_source:, ) end let(:view_context) do Nanoc::Core::ViewContextForPreCompilation.new(items:) end it_behaves_like 'a rule context' end describe(Nanoc::RuleDSL::CompilationRuleContext) do subject(:rule_context) do described_class.new(rep:, site:, recorder:, view_context:) end let(:item_identifier) { Nanoc::Core::Identifier.new('/foo.md') } let(:item) { Nanoc::Core::Item.new('content', {}, item_identifier) } let(:rep) { Nanoc::Core::ItemRep.new(item, :default) } let(:config) { Nanoc::Core::Configuration.new(dir: Dir.getwd) } let(:items) { Nanoc::Core::ItemCollection.new(config) } let(:layouts) { Nanoc::Core::LayoutCollection.new(config) } let(:site) do Nanoc::Core::Site.new( config:, code_snippets: [], data_source:, ) end let(:data_source) do Nanoc::Core::InMemoryDataSource.new(items, layouts) end let(:rep) { Nanoc::Core::ItemRep.new(item, :default) } let(:view_context) do Nanoc::Core::ViewContextForPreCompilation.new(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:) } 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::Core::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::Core::Identifier.new('/foo.html') } let(:identifier_b) { Nanoc::Core::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.13.3/nanoc/spec/nanoc/rule_dsl/rule_spec.rb�������������������������������������������������0000664�0000000�0000000�00000010323�14720333346�0022151�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::Core::Pattern.from(%r{/(.*)/(.*)/}) } let(:block) { proc {} } describe '#matches' do subject { rule.matches(identifier) } context 'does not match' do let(:identifier) { Nanoc::Core::Identifier.new('/moo/', type: :legacy) } it { is_expected.to be_nil } end context 'matches' do let(:identifier) { Nanoc::Core::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 be(:xml) } its(:pattern) { is_expected.to eql(pattern) } end describe '#applicable_to?' do subject { rule.applicable_to?(item) } let(:item) { Nanoc::Core::Item.new('', {}, '/foo.md') } context 'pattern matches' do let(:pattern) { Nanoc::Core::Pattern.from(%r{^/foo.*}) } it { is_expected.to be } end context 'pattern does not match' do let(:pattern) { Nanoc::Core::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::Core::Item.new('', {}, '/foo.md') } let(:rep) { Nanoc::Core::ItemRep.new(item, :amazings) } let(:site) { Nanoc::Core::Site.new(config:, data_source:, code_snippets: []) } let(:data_source) { Nanoc::Core::InMemoryDataSource.new(items, layouts) } let(:config) { Nanoc::Core::Configuration.new(dir: Dir.getwd) } let(:view_context) { Nanoc::Core::ViewContextForPreCompilation.new(items:) } let(:items) { Nanoc::Core::ItemCollection.new(config, []) } let(:layouts) { Nanoc::Core::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::Core::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 be(: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 be(:xml) } its(:pattern) { is_expected.to eql(pattern) } its(:snapshot_name) { is_expected.to be(:donkey) } end end describe '#apply_to' do subject { rule.apply_to(rep, site:, 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::Core::Pattern.from(%r{/(.*)/(.*)/}) } let(:block) { proc {} } it_behaves_like 'a generic rule' describe '#apply_to' do subject { rule.apply_to(rep, site:, recorder:, view_context:) } let(:recorder) { Nanoc::RuleDSL::ActionRecorder.new(rep) } let(:rep) { nil } it_behaves_like 'Rule#apply_to' end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nanoc-4.13.3/nanoc/spec/nanoc/rule_dsl/rules_collection_spec.rb�������������������������������������0000664�0000000�0000000�00000017352�14720333346�0024560�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 subject { rules_collection.compilation_rule_for(rep) } let(:item) { Nanoc::Core::Item.new('content', {}, '/foo.md') } let(:rep) { Nanoc::Core::ItemRep.new(item, rep_name) } let(:rep_name) { :default } 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::Core::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::Core::Pattern.from('/foo.*'), :default, proc {}) end let(:rule_b) do Nanoc::RuleDSL::CompilationRule.new(Nanoc::Core::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::Core::Pattern.from('/foo.*'), :default, proc {}) end let(:rule_b) do Nanoc::RuleDSL::CompilationRule.new(Nanoc::Core::Pattern.from('/*.*'), :default, proc {}) end let(:rule_c) do Nanoc::RuleDSL::CompilationRule.new(Nanoc::Core::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 subject { rules_collection.item_compilation_rules_for(item) } let(:item) { Nanoc::Core::Item.new('content', {}, '/foo.md') } 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::Core::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::Core::Pattern.from('/foo.*'), :default, proc {}) end let(:rule_b) do Nanoc::RuleDSL::CompilationRule.new(Nanoc::Core::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::Core::Pattern.from('/foo.*'), :default, proc {}) end let(:rule_b) do Nanoc::RuleDSL::CompilationRule.new(Nanoc::Core::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 subject { rules_collection.routing_rules_for(rep) } let(:item) { Nanoc::Core::Item.new('content', {}, '/foo.md') } let(:rep) { Nanoc::Core::ItemRep.new(item, :default) } let(:rules) do [ # Matching item, matching rep Nanoc::RuleDSL::RoutingRule.new( Nanoc::Core::Pattern.from('/foo.*'), :default, proc {}, snapshot_name: :a ), Nanoc::RuleDSL::RoutingRule.new( Nanoc::Core::Pattern.from('/foo.*'), :default, proc {}, snapshot_name: :b ), # Matching item, non-matching rep Nanoc::RuleDSL::RoutingRule.new( Nanoc::Core::Pattern.from('/foo.*'), :raw, proc {}, snapshot_name: :a ), Nanoc::RuleDSL::RoutingRule.new( Nanoc::Core::Pattern.from('/foo.*'), :raw, proc {}, snapshot_name: :b ), # Non-matching item, matching rep Nanoc::RuleDSL::RoutingRule.new( Nanoc::Core::Pattern.from('/bar.*'), :default, proc {}, snapshot_name: :a ), Nanoc::RuleDSL::RoutingRule.new( Nanoc::Core::Pattern.from('/bar.*'), :default, proc {}, snapshot_name: :b ), # Non-matching item, non-matching rep Nanoc::RuleDSL::RoutingRule.new( Nanoc::Core::Pattern.from('/bar.*'), :raw, proc {}, snapshot_name: :a ), Nanoc::RuleDSL::RoutingRule.new( Nanoc::Core::Pattern.from('/bar.*'), :raw, proc {}, snapshot_name: :b ), # Matching item, matching rep, but not the first Nanoc::RuleDSL::RoutingRule.new( Nanoc::Core::Pattern.from('/*.*'), :default, proc {}, snapshot_name: :a ), Nanoc::RuleDSL::RoutingRule.new( Nanoc::Core::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 subject { rules_collection.filter_for_layout(layout) } let(:layout) { Nanoc::Core::Layout.new('Some content', {}, '/foo.md') } let(:mapping) { {} } before do mapping.each_pair do |key, value| rules_collection.layout_filter_mapping[Nanoc::Core::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.13.3/nanoc/spec/nanoc/spec_spec.rb����������������������������������������������������������0000664�0000000�0000000�00000002702�14720333346�0020325�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# frozen_string_literal: true describe Nanoc::Spec::HelperContext do subject(:ctx) { described_class.new(helper) } let(:helper) do Module.new {} end 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 subject { ctx.create_rep(ctx.items['/foo.md'], '/foo.html') } before do ctx.create_item('foo', {}, '/foo.md') end 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.13.3/nanoc/spec/nanoc/version_spec.rb�������������������������������������������������������0000664�0000000�0000000�00000000251�14720333346�0021055�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# frozen_string_literal: true describe Nanoc::VERSION do it 'is the same as Nanoc::Core::VERSION' do expect(Nanoc::VERSION).to eq(Nanoc::Core::VERSION) end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nanoc-4.13.3/nanoc/spec/regression_filenames_spec.rb������������������������������������������������0000664�0000000�0000000�00000001036�14720333346�0022477�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 'has 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.13.3/nanoc/spec/spec_helper.rb��������������������������������������������������������������0000664�0000000�0000000�00000000213�14720333346�0017547�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.13.3/nanoc/test/����������������������������������������������������������������������������0000775�0000000�0000000�00000000000�14720333346�0014762�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������nanoc-4.13.3/nanoc/test/base/�����������������������������������������������������������������������0000775�0000000�0000000�00000000000�14720333346�0015674�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������nanoc-4.13.3/nanoc/test/base/test_compiler.rb�������������������������������������������������������0000664�0000000�0000000�00000015705�14720333346�0021102�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# frozen_string_literal: true require 'helper' class Nanoc::Core::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::Core::SiteLoader.new.new_from_cwd Nanoc::Core::Compiler.compile(site) 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| Nanoc::Core::Compiler.compile(site) assert_predicate Dir['output/*'], :empty? end end def test_compile_with_one_rep with_site do |_site| File.write('content/index.html', 'o hello') site = Nanoc::Core::SiteLoader.new.new_from_cwd Nanoc::Core::Compiler.compile(site) assert_equal(1, Dir['output/*'].size) assert File.file?('output/index.html') assert_equal('o hello', File.read('output/index.html')) end end def test_compile_with_two_independent_reps with_site do |_site| File.write('content/foo.html', 'o hai') File.write('content/bar.html', 'o bai') site = Nanoc::Core::SiteLoader.new.new_from_cwd Nanoc::Core::Compiler.compile(site) assert_equal(2, Dir['output/*'].size) assert File.file?('output/foo/index.html') assert File.file?('output/bar/index.html') assert_equal('o hai', File.read('output/foo/index.html')) assert_equal('o bai', File.read('output/bar/index.html')) end end def test_compile_with_two_dependent_reps with_site(compilation_rule_content: 'filter :erb') do |_site| File.write('content/foo.html', '<%= @items.find { |i| i.identifier == "/bar/" }.compiled_content %>!!!') File.write('content/bar.html', 'manatee') site = Nanoc::Core::SiteLoader.new.new_from_cwd Nanoc::Core::Compiler.compile(site) assert_equal(2, Dir['output/*'].size) assert File.file?('output/foo/index.html') assert File.file?('output/bar/index.html') assert_equal('manatee!!!', File.read('output/foo/index.html')) assert_equal('manatee', File.read('output/bar/index.html')) end end def test_compile_with_two_mutually_dependent_reps with_site(compilation_rule_content: 'filter :erb') do |_site| File.write('content/foo.html', '<%= @items.find { |i| i.identifier == "/bar/" }.compiled_content %>') File.write('content/bar.html', '<%= @items.find { |i| i.identifier == "/foo/" }.compiled_content %>') site = Nanoc::Core::SiteLoader.new.new_from_cwd assert_raises Nanoc::Core::Errors::DependencyCycle do Nanoc::Core::Compiler.compile(site) 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::Core::SiteLoader.new.new_from_cwd error = assert_raises(Nanoc::Error) do Nanoc::Core::Compiler.compile(site) 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.write('content/index.html', '[<%= @item.compiled_content(:snapshot => :aaa) %>]') # 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::Core::SiteLoader.new.new_from_cwd Nanoc::Core::Compiler.compile(site) # 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.write('content/a.html', '[<%= @items.find { |i| i.identifier == "/z/" }.compiled_content(:snapshot => :guts) %>]') File.write('content/z.html', 'stuff') # 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::Core::SiteLoader.new.new_from_cwd Nanoc::Core::Compiler.compile(site) # 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.write('content/index.html', 'stuff') # Compile site = Nanoc::Core::SiteLoader.new.new_from_cwd Nanoc::Core::Compiler.compile(site) # Check assert_predicate Dir['tmp/text_items/*'], :empty? end end end �����������������������������������������������������������nanoc-4.13.3/nanoc/test/base/test_site.rb�����������������������������������������������������������0000664�0000000�0000000�00000007643�14720333346�0020236�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# frozen_string_literal: true require 'helper' class Nanoc::Core::SiteTest < Nanoc::TestCase def test_initialize_with_dir_without_config_yaml assert_raises(Nanoc::Core::ConfigLoader::NoConfigFileFoundError) do Nanoc::Core::SiteLoader.new.new_from_cwd end end def test_initialize_with_dir_with_config_yaml File.write('config.yaml', 'output_dir: public_html') site = Nanoc::Core::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.write('nanoc.yaml', 'output_dir: public_html') site = Nanoc::Core::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.write('nanoc.yaml', 'data_sources: [{ items_root: "/bar/" }]') site = Nanoc::Core::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.write('nanoc.yaml', <<~EOF) output_dir: public_html parent_config_file: foo/foo.yaml EOF FileUtils.mkdir_p('foo') FileUtils.cd('foo') do File.write('foo.yaml', <<~EOF) parent_config_file: ../bar/bar.yaml EOF end FileUtils.mkdir_p('bar') FileUtils.cd('bar') do File.write('bar.yaml', <<~EOF) enable_output_diff: true foo: bar output_dir: output EOF end site = Nanoc::Core::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.write('nanoc.yaml', <<~EOF) parent_config_file: foo/foo.yaml EOF assert_raises(Nanoc::Core::ConfigLoader::NoParentConfigFileFoundError) do Nanoc::Core::SiteLoader.new.new_from_cwd end end def test_initialize_with_parent_config_file_cycle File.write('nanoc.yaml', <<~EOF) parent_config_file: foo/foo.yaml EOF FileUtils.mkdir_p('foo') FileUtils.cd('foo') do File.write('foo.yaml', <<~EOF) parent_config_file: ../nanoc.yaml EOF end assert_raises(Nanoc::Core::ConfigLoader::CyclicalConfigFileError) do Nanoc::Core::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::Core::SiteLoader.new.new_from_cwd site.items.each do |item| assert_instance_of Nanoc::Core::Identifier, item.identifier end site.layouts.each do |layout| assert_instance_of Nanoc::Core::Identifier, layout.identifier end end end def test_multiple_items_with_same_identifier with_site do File.write('content/sam.html', 'I am Sam!') FileUtils.mkdir_p('content/sam') File.write('content/sam/index.html', 'I am Sam, too!') assert_raises(Nanoc::Core::Site::DuplicateIdentifierError) do Nanoc::Core::SiteLoader.new.new_from_cwd end end end def test_multiple_layouts_with_same_identifier with_site do File.write('layouts/sam.html', 'I am Sam!') FileUtils.mkdir_p('layouts/sam') File.write('layouts/sam/index.html', 'I am Sam, too!') assert_raises(Nanoc::Core::Site::DuplicateIdentifierError) do Nanoc::Core::SiteLoader.new.new_from_cwd end end end end ���������������������������������������������������������������������������������������������nanoc-4.13.3/nanoc/test/data_sources/���������������������������������������������������������������0000775�0000000�0000000�00000000000�14720333346�0017436�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������nanoc-4.13.3/nanoc/test/data_sources/test_filesystem.rb���������������������������������������������0000664�0000000�0000000�00000057017�14720333346�0023220�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# 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.write('foo/bar.html', "---\nnum: 1\n---\ntest 1") File.write('foo/b.c.html', "---\nnum: 2\n---\ntest 2") File.write('foo/a/b/c.html', "---\nnum: 3\n---\ntest 3") File.write('foo/ugly.html~', "---\nnum: 4\n---\ntest 4") File.write('foo/ugly.html.orig', "---\nnum: 5\n---\ntest 5") File.write('foo/ugly.html.rej', "---\nnum: 6\n---\ntest 6") File.write('foo/ugly.html.bak', "---\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.write('foo/bar.html', "---\nnum: 1\n---\ntest 1") File.write('foo/bar.md', "---\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.write('foo/stuff.dat', 'random binary data') # Load items = data_source.send(:load_objects, 'foo', Nanoc::Core::Item) # Check assert_equal 1, items.size assert_predicate items[0].content, :binary? assert_equal "#{Dir.getwd}/foo/stuff.dat", items[0].content.filename assert_instance_of Nanoc::Core::BinaryContent, items[0].content 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.write('foo/stuff.dat', 'random binary data') # Load assert_raises(Nanoc::DataSources::Filesystem::Errors::BinaryLayout) do data_source.send(:load_objects, 'foo', Nanoc::Core::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.write('foo/a/b/c.yaml', "---\nnum: 1\n") File.write('foo/b.c.yaml', "---\nnum: 2\n") File.write('foo/b.c.html', 'test 2') File.write('foo/car.html', 'test 3') File.write('foo/ugly.yaml~', 'blah') File.write('foo/ugly.html~', 'blah') File.write('foo/ugly.html.orig', 'blah') File.write('foo/ugly.html.rej', 'blah') File.write('foo/ugly.html.bak', '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')].max, }, '/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.write('foo/a/b/c.yaml', "---\nnum: 1\n") File.write('foo/b.yaml', "---\nnum: 2\n") File.write('foo/b.html.erb', 'test 2') File.write('foo/car.html', 'test 3') File.write('foo/ugly.yaml~', 'blah') File.write('foo/ugly.html~', 'blah') File.write('foo/ugly.html.orig', 'blah') File.write('foo/ugly.html.rej', 'blah') File.write('foo/ugly.html.bak', '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')].max, }, '/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::Core::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_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.write(filename, 'test') end # Write stray files %w[foo.html~ foo.yaml.orig bar.entry.html.bak].each do |filename| File.write(filename, '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.write(filename, 'test') end # Write stray files %w[foo.html~ foo.yaml.orig bar.entry.html.bak].each do |filename| File.write(filename, '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.write(filename, '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.write(filename, '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_instance_of Array, res.values[0][1] 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.write(filename, '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.13.3/nanoc/test/data_sources/test_filesystem_tools.rb���������������������������������������0000664�0000000�0000000�00000012122�14720333346�0024424�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# 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 16.times do |i| FileUtils.mkdir_p("dir#{i}") File.write("dir#{i}/foo.md", '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 16.times do |i| FileUtils.mkdir_p("dir#{i}") File.write("dir#{i}/foo.md", '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.write('foo/x.md', 'o hai from foo/x') File.write('bar/y.md', '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.write('bar', 'o hai from bar') FileUtils.mkdir_p('dir') File.write('dir/foo', '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.write('foo', '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.write('foo', '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.write('dir/.DS_Store', 'o hai') File.write('dir/.htaccess', '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.write('dir/.other', '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.write('dir/.other', 'o hai') File.write('dir/.DS_Store', '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_extra_files_string_with_leading_slash # Write sample files FileUtils.mkdir_p('dir') File.write('dir/.other', 'o hai') File.write('dir/.DS_Store', 'o hai') actual_files = Nanoc::DataSources::Filesystem::Tools.all_files_in('dir', '/**/.DS_Store').sort assert_equal ['dir/.DS_Store'].sort, actual_files.sort end def test_extra_files_array_with_leading_slash # Write sample files FileUtils.mkdir_p('dir') File.write('dir/.other', 'o hai') File.write('dir/.DS_Store', 'o hai') actual_files = Nanoc::DataSources::Filesystem::Tools.all_files_in('dir', ['/**/.DS_Store']).sort assert_equal ['dir/.DS_Store'].sort, actual_files.sort end def test_unknown_pattern # Write sample files FileUtils.mkdir_p('dir') File.write('dir/.other', 'o hai') pattern = { dotfiles: '**/.other' } assert_raises Nanoc::Core::TrivialError, "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.13.3/nanoc/test/extra/����������������������������������������������������������������������0000775�0000000�0000000�00000000000�14720333346�0016105�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������nanoc-4.13.3/nanoc/test/extra/core_ext/�������������������������������������������������������������0000775�0000000�0000000�00000000000�14720333346�0017715�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������nanoc-4.13.3/nanoc/test/extra/core_ext/test_time.rb�������������������������������������������������0000664�0000000�0000000�00000001025�14720333346�0022235�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# 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.13.3/nanoc/test/filters/��������������������������������������������������������������������0000775�0000000�0000000�00000000000�14720333346�0016432�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������nanoc-4.13.3/nanoc/test/filters/colorize_syntax/����������������������������������������������������0000775�0000000�0000000�00000000000�14720333346�0021666�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������nanoc-4.13.3/nanoc/test/filters/colorize_syntax/test_coderay.rb�������������������������������������0000664�0000000�0000000�00000016374�14720333346�0024713�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# frozen_string_literal: true require 'helper' class Nanoc::Filters::ColorizeSyntax::CoderayTest < Nanoc::TestCase CODERAY_PRE = '<div class="CodeRay"><div class="code">' CODERAY_POST = '</div></div>' def test_coderay_simple if_have 'nokogiri' do # Create filter filter = ::Nanoc::Filters::ColorizeSyntax.new # Get input and expected output input = '<pre title="moo"><code class="language-ruby"># comment</code></pre>' expected_output = CODERAY_PRE + '<pre title="moo"><code class="language-ruby"><span class="comment"># comment</span></code></pre>' + 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 = %(<pre title="moo"><code>#!ruby # comment</code></pre>) expected_output = CODERAY_PRE + '<pre title="moo"><code class="language-ruby"><span class="comment"># comment</span></code></pre>' + 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 = %(<pre title="moo"><code>def moo ; end #!ruby # comment</code></pre>) expected_output = "<pre title=\"moo\"><code>def moo ; end\n#!ruby\n# comment</code></pre>" # 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 = %(<pre title="moo"><code class="language-ruby">#!ruby # comment</code></pre>) expected_output = CODERAY_PRE + %(<pre title="moo"><code class="language-ruby"><span class="doctype">#!ruby</span> <span class="comment"># comment</span></code></pre>) + 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 = '<pre title="moo"><code class="abc language-ruby xyz"># comment</code></pre>' expected_output = CODERAY_PRE + '<pre title="moo"><code class="abc language-ruby xyz"><span class="comment"># comment</span></code></pre>' + 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('<p>whatever</p>', 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 = '<p>foo<br/>bar</p>' expected_output = '<p>foo<br/>bar</p>' # 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 = '<p>foo<br/>bar</p>' expected_output = '<p>foo<br />bar</p>' # 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 <pre><code> #!/usr/bin/env ruby puts 'hi!' </code></pre> after EOS expected_output = <<~EOS.sub(/\s*\Z/m, '') before <pre><code> #!/usr/bin/env ruby puts 'hi!' </code></pre> 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 <pre><code> #!ruby #!/usr/bin/env ruby puts 'hi!' </code></pre> after EOS expected_output = <<~EOS.sub(/\s*\Z/m, '') before #{CODERAY_PRE}<pre><code class="language-ruby"><span class="doctype">#!/usr/bin/env ruby</span> puts <span class="string"><span class="delimiter">'</span><span class="content">hi!</span><span class="delimiter">'</span></span></code></pre>#{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 = '<code class="language-ruby"># comment</code>' expected_output = '<code class="language-ruby"># comment</code>' # 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 = '<code class="language-ruby"># comment</code>' expected_output = '<code class="language-ruby"><span class="comment"># comment</span></code>' # 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 <pre><code class="language-ruby"> def foo end </code></pre> after EOS expected_output = <<~EOS.sub(/\s*\Z/m, '') before #{CODERAY_PRE}<pre><code class="language-ruby"> <span class="keyword">def</span> <span class="function">foo</span> <span class="keyword">end</span></code></pre>#{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.13.3/nanoc/test/filters/colorize_syntax/test_common.rb��������������������������������������0000664�0000000�0000000�00000006442�14720333346�0024550�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# 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 = '<pre title="moo"><code class="language-ruby"># comment</code></pre>' 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 = '<pre title="moo"><code class="language-ruby"># comment</code></pre>' 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 <!DOCTYPE html> <html> <head> <title>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 'nokogiri' # 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 original_path = ENV.fetch('PATH', nil) 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| input = '
    puts "foo"
    ' filter.setup_and_run( input, colorizers: { ruby: colorizer }, ) flunk 'expected colorizer to raise if no executable is available' rescue end ensure ENV['PATH'] = original_path end end end nanoc-4.13.3/nanoc/test/filters/colorize_syntax/test_pygmentize.rb000066400000000000000000000024711472033334600254510ustar00rootroot00000000000000# 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 = %r{
    puts( | )"foo"
    } # Run filter actual_output = filter.setup_and_run(input, default_colorizer: :pygmentize) assert_match(expected_output, actual_output) end end end nanoc-4.13.3/nanoc/test/filters/colorize_syntax/test_pygments.rb000066400000000000000000000013551472033334600251240ustar00rootroot00000000000000# frozen_string_literal: true require 'helper' class Nanoc::Filters::ColorizeSyntax::PygmentsTest < Nanoc::TestCase def test_pygmentsrb skip 'pygments.rb does not support Windows' if Nanoc::Core.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.13.3/nanoc/test/filters/colorize_syntax/test_simon.rb000066400000000000000000000013071472033334600244000ustar00rootroot00000000000000# 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.13.3/nanoc/test/filters/test_bluecloth.rb000066400000000000000000000011071472033334600217760ustar00rootroot00000000000000# 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.13.3/nanoc/test/filters/test_coffeescript.rb000066400000000000000000000006371472033334600225000ustar00rootroot00000000000000# 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.13.3/nanoc/test/filters/test_erubi.rb000066400000000000000000000047341472033334600211340ustar00rootroot00000000000000# 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::Core::Item.new('asdf', {}, '/about.md') item_rep = Nanoc::Core::ItemRep.new(item, :xml) filter = ::Nanoc::Filters::Erubi.new(item:, 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 'syntax error', e.message raised = true end assert raised end def test_filter_regular_error # Create filter item = Nanoc::Core::Item.new('asdf', {}, '/about.md') item_rep = Nanoc::Core::ItemRep.new(item, :xml) filter = ::Nanoc::Filters::Erubi.new(item:, 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.13.3/nanoc/test/filters/test_erubis.rb000066400000000000000000000036451472033334600213170ustar00rootroot00000000000000# 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.13.3/nanoc/test/filters/test_haml.rb000066400000000000000000000066661472033334600207550ustar00rootroot00000000000000# 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_equal '(__TEMPLATE__)', 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.write('stuff', "
    Max Payne\nMona Sax
    ") # Run filter filter = ::Nanoc::Filters::Haml.new result = filter.setup_and_run("%body\n ~ File.read('stuff')") assert_match(/Max Payne\nMona Sax/, result) end def test_filter_render with_site(legacy: false) do |_site| # Prepare File.write('lib/helpers.rb', "include Nanoc::Helpers::Rendering\n") FileUtils.mkdir_p('layouts/partials') File.write('layouts/partials/foobar.haml', <<~HAML) .foobar %h2 This is a partial rendered from a page - if block_given? - _erbout << yield HAML # NOTE: Using `- _erbout << yield` instead of `= yield` above to avoid escaping. File.write('content/page.haml', <<~HAML) %h1 Example page = render("/partials/foobar.haml") do %h2 This should work normally HAML File.write('Rules', <<~RULES) compile '/**/*.haml' do filter :haml write ext: 'html' end layout '/**/*.haml', :haml RULES # Compile Nanoc::CLI.run(%w[compile]) # Check expected_regex = %r{

    Example\ page

    .* .*

    This\ is\ a\ partial\ rendered\ from\ a\ page

    .*

    This\ should\ work\ normally

    .*
    }xm assert_match expected_regex, File.read('output/page.html') end end end nanoc-4.13.3/nanoc/test/filters/test_handlebars.rb000066400000000000000000000034011472033334600221170ustar00rootroot00000000000000# frozen_string_literal: true require 'helper' class Nanoc::Filters::HandlebarsTest < Nanoc::TestCase def test_filter if_have 'handlebars' do # Create data item = Nanoc::Core::Item.new( 'content', { title: 'Max Payne', protagonist: 'Max Payne', location: 'here' }, '/games/max-payne', ) layout = Nanoc::Core::Layout.new( 'layout content', { name: 'Max Payne' }, '/default', ) config = { animals: 'cats and dogs' } # Create filter assigns = { item:, layout:, 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::Core::Item.new( 'content', { title: 'Max Payne', protagonist: 'Max Payne', location: 'here' }, '/games/max-payne', ) # Create filter assigns = { 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.13.3/nanoc/test/filters/test_kramdown.rb000066400000000000000000000033241472033334600216420ustar00rootroot00000000000000# 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::Core::Item.new('foo', {}, '/foo.md') item_view = Nanoc::Core::CompilationItemView.new(item, nil) item_rep = Nanoc::Core::ItemRep.new(item, :default) item_rep_view = Nanoc::Core::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::Core::Item.new('foo', {}, '/foo.md') item_view = Nanoc::Core::CompilationItemView.new(item, nil) item_rep = Nanoc::Core::ItemRep.new(item, :default) item_rep_view = Nanoc::Core::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.13.3/nanoc/test/filters/test_markaby.rb000066400000000000000000000005651472033334600214520ustar00rootroot00000000000000# frozen_string_literal: true require 'helper' class Nanoc::Filters::MarkabyTest < Nanoc::TestCase def test_filter skip 'known broken on Ruby 3.x' if RUBY_VERSION.start_with?('3') # Create filter filter = ::Nanoc::Filters::Markaby.new # Run filter result = filter.setup_and_run("html do\nend") assert_equal('', result) end end nanoc-4.13.3/nanoc/test/filters/test_maruku.rb000066400000000000000000000005271472033334600213260ustar00rootroot00000000000000# 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.13.3/nanoc/test/filters/test_mustache.rb000066400000000000000000000017211472033334600216300ustar00rootroot00000000000000# frozen_string_literal: true require 'helper' class Nanoc::Filters::MustacheTest < Nanoc::TestCase def test_filter # Create item item = Nanoc::Core::Item.new( 'content', { title: 'Max Payne', protagonist: 'Max Payne' }, '/games/max-payne', ) # Create filter filter = ::Nanoc::Filters::Mustache.new(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::Core::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:, ) # Run filter result = filter.setup_and_run('Max says: {{yield}}.') assert_equal('Max says: No Payne No Gayne.', result) end end nanoc-4.13.3/nanoc/test/filters/test_pandoc.rb000066400000000000000000000022501472033334600212610ustar00rootroot00000000000000# 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:) assert_match '